diff --git a/.changeset/README.md b/.changeset/README.md
new file mode 100644
index 0000000..654c6d4
--- /dev/null
+++ b/.changeset/README.md
@@ -0,0 +1,8 @@
+# Changesets
+
+Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
+with multi-package repos, or single-package repos to help you version and publish your code. You can
+find the full documentation for it [in our repository](https://github.com/changesets/changesets).
+
+We have a quick list of common questions to get you started engaging with this project in
+[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
diff --git a/.changeset/config.json b/.changeset/config.json
new file mode 100644
index 0000000..188b6fa
--- /dev/null
+++ b/.changeset/config.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
+ "changelog": "@changesets/cli/changelog",
+ "commit": false,
+ "access": "public",
+ "baseBranch": "main",
+ "ignore": [],
+ "snapshot": {
+ "useCalculatedVersion": true,
+ "prereleaseTemplate": "rc.{datetime}"
+ }
+}
\ No newline at end of file
diff --git a/.changeset/nice-birds-mate.md b/.changeset/nice-birds-mate.md
new file mode 100644
index 0000000..a00d7b0
--- /dev/null
+++ b/.changeset/nice-birds-mate.md
@@ -0,0 +1,20 @@
+---
+"@adeficior/data-modifier-farmersdelight": major
+"@adeficior/data-modifier-ingredients": major
+"@adeficior/data-modifier-builder": major
+"@adeficior/data-modifier-codegen": major
+"@adeficior/testing": major
+"@adeficior/data-modifier-botania": major
+"@adeficior/data-modifier-content": major
+"@adeficior/data-modifier-recipes": major
+"@adeficior/data-modifier-thermal": major
+"@adeficior/data-modifier-create": major
+"@adeficior/data-modifier-models": major
+"@adeficior/data-modifier-lang": major
+"@adeficior/data-modifier-loot": major
+"@adeficior/data-modifier-tags": major
+"@adeficior/data-modifier-core": major
+"@adeficior/data-modifier": major
+---
+
+refactor into modular system
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..de4db68
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,17 @@
+version: 2
+
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ actions:
+ patterns: ["*"]
+
+ - package-ecosystem: "bun"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 10
+ versioning-strategy: increase
diff --git a/.github/metadata-config.json b/.github/metadata-config.json
index 5b3a7d0..8d44574 100644
--- a/.github/metadata-config.json
+++ b/.github/metadata-config.json
@@ -2,5 +2,7 @@
"$schema": "https://workflows.somethingcatchy.net/schema/config.json",
"$version": "1.0",
"type": "bun-library",
- "formatter": "prettier"
-}
+ "formatter": "prettier",
+ "tsconfig": false,
+ "workflows": false
+}
\ No newline at end of file
diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
deleted file mode 100644
index 8a6a3be..0000000
--- a/.github/workflows/pre-release.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# @pssbletrngle/github-meta-generator
-# 1.0.107
-# 2026-07-24T11:28:41.812Z
-# 549a03ac9f117d50
-#
-
-name: Trigger Prerelease
-
-on:
- workflow_dispatch:
- push:
- branches: [main]
-
-permissions:
- contents: write
- actions: write
-
-jobs:
- trigger:
- runs-on: ubuntu-latest
- steps:
- - uses: PssbleTrngle/find-release@v1
- if: ${{ github.event_name != 'pull_request' }}
- id: action
- with:
- type: draft
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - uses: benc-uk/workflow-dispatch@v1
- if: ${{ toJson(steps.action.outputs.matched) }}
- with:
- workflow: release.yml
- inputs: '{ "rc": true, "version": "${{ steps.action.outputs.tag }}-rc.${{ github.run_number }}" }'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 531950f..a6b8895 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,25 +1,14 @@
-#
-# @pssbletrngle/github-meta-generator
-# 1.0.107
-# 2026-07-24T11:28:41.812Z
-# e963d9412a165adf
-#
-
name: Release
on:
- workflow_dispatch:
- inputs:
- rc:
- type: boolean
- default: false
- version:
- type: string
- required: true
+ push:
+ branches:
+ - main
+ - develop
permissions:
id-token: write
- contents: read
+ contents: write
jobs:
release:
@@ -33,23 +22,29 @@ jobs:
- name: Install Dependencies
run: bun install --frozen-lockfile
- - name: Update version
- uses: jossef/action-set-json-field@v2
- with:
- file: package.json
- field: version
- value: ${{ github.event.inputs.version }}
-
- run: bun run build
- - uses: actions/setup-node@v7
- with:
- node-version: "24"
- registry-url: "https://registry.npmjs.org"
- package-manager-cache: false
+ - run: bun run prune
+
+ - name: Bump Version
+ if: ${{ github.ref_name != 'main' }}
+ run: bunx changeset version --snapshot rc
+
+ - name: Bump Version
+ if: ${{ github.ref_name == 'main' }}
+ run: bunx changeset version
+
+ - name: Update Lockfile
+ run: bun update
+
+ - name: Publish Packages
+ if: ${{ github.ref_name != 'main' }}
+ run: bunx publish --tag rc --no-git-tag
- - if: ${{ github.event.inputs.rc }}
- run: npm publish --no-git-checks --provenance --access public --tag rc
+ - name: Publish Packages
+ if: ${{ github.ref_name == 'main' }}
+ run: bunx public
- - if: ${{ !github.event.inputs.rc }}
- run: npm publish --no-git-checks --provenance --access public
+ - name: Push Changes
+ if: ${{ github.ref_name == 'main' }}
+ run: git push
diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml
deleted file mode 100644
index 6531316..0000000
--- a/.github/workflows/semantic-release.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# @pssbletrngle/github-meta-generator
-# 1.0.107
-# 2026-07-24T11:28:41.812Z
-# d37cd3dba38e6a39
-#
-
-name: Trigger Semantic Release
-
-permissions:
- contents: read
- actions: write
-
-on:
- workflow_dispatch:
- release:
- types: [published]
-
-jobs:
- trigger:
- runs-on: ubuntu-latest
- steps:
- - uses: benc-uk/workflow-dispatch@v1
- with:
- workflow: release.yml
- inputs: '{ "version": "${{ github.event.release.tag_name }}" }'
diff --git a/.gitignore b/.gitignore
index 37cd425..5adeefe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,4 +132,19 @@ dist
# webstorm
.idea
-test-output
\ No newline at end of file
+test-output
+
+.turbo
+
+# generated by prepare script
+eslint.config.js
+tsconfig.json
+tsconfig.*.json
+**/@types/modules.d.ts
+**/@types/registry.d.ts
+
+!packages/configs/tsconfig.json
+!packages/configs/tsconfig.*.json
+!packages/configs/eslint.config.js
+
+.npmrc
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index 8b7691c..7694eed 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -14,3 +14,27 @@
path = test/resources/1.21.1/default/vanilla-data
url = https://github.com/misode/mcmeta.git
shallow = true
+[submodule "modules/botania/test/resources/Botania"]
+ path = modules/botania/test/resources/1.20.1/default/botania
+ url = https://github.com/VazkiiMods/Botania.git
+ shallow = true
+[submodule "modules/create/test/resources/1.20.1/default/Create"]
+ path = modules/create/test/resources/1.20.1/default/create
+ url = https://github.com/Creators-of-Create/Create.git
+ shallow = true
+[submodule "modules/create/test/resources/1.21.1/default/create"]
+ path = modules/create/test/resources/1.21.1/default/create
+ url = https://github.com/Creators-of-Create/Create.git
+ shallow = true
+[submodule "modules/farmersdelight/test/resources/1.20.1/default/farmersdelight"]
+ path = modules/farmersdelight/test/resources/1.20.1/default/farmersdelight
+ url = https://github.com/vectorwing/FarmersDelight.git
+ shallow = true
+[submodule "modules/farmersdelight/test/resources/1.21.1/default/farmersdelight"]
+ path = modules/farmersdelight/test/resources/1.21.1/default/farmersdelight
+ url = https://github.com/vectorwing/FarmersDelight.git
+ shallow = true
+[submodule "modules/thermal/test/resources/1.20.1/default/foundation"]
+ path = modules/thermal/test/resources/1.20.1/default/foundation
+ url = https://github.com/CoFH/ThermalFoundation.git
+ shallow = true
diff --git a/.husky/pre-commit b/.husky/pre-commit
index f8acb82..94b52bc 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,11 +1,4 @@
-#
-# @pssbletrngle/github-meta-generator
-# 1.0.107
-# 2026-07-24T11:28:41.812Z
-# e8a379315d1fa5c9
-#
-
bun run format
-bun run lint --fix
+bun run lint -- -- --fix
git update-index --again
diff --git a/@types/generated.d.ts b/@types/generated.d.ts
deleted file mode 100644
index 953d5a9..0000000
--- a/@types/generated.d.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-declare module "@adeficior/data-modifier/generated" {
- export type RegistryId = string;
-
- export type CreativeModeTabId = string;
-
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- export type InferIds = string;
-
- export type ItemId = string;
-
- export type BlockId = string;
-
- export type FluidId = string;
-
- export type RecipeSerializerId = string;
-}
diff --git a/bun.lock b/bun.lock
index 7057505..97d16ac 100644
--- a/bun.lock
+++ b/bun.lock
@@ -3,46 +3,416 @@
"configVersion": 1,
"workspaces": {
"": {
- "name": "@adeficior/data-modifier",
+ "name": "@adeficior/monorepo",
+ "devDependencies": {
+ "@changesets/cli": "^2.31.1",
+ "@types/bun": "latest",
+ "husky": "^9.1.7",
+ "prettier": "catalog:",
+ "turbo": "^2.10.7",
+ },
+ },
+ "core": {
+ "name": "@adeficior/data-modifier-core",
+ "version": "1.0.0",
"dependencies": {
- "@adeficior/resource-merger": "^2.0.0-rc.16",
- "arg": "^5.0.2",
- "chalk": "^5.6.2",
- "command-line-usage": "^7.0.4",
"json5": "^2.2.3",
- "lodash-es": "^4.18.1",
- "minimatch": "^5.1.9",
- "node-stream-zip": "^1.15.0",
- "prettier": "^3.8.4",
- "prismarine-nbt": "^2.8.0",
- "zod": "^4.4.3",
+ "lodash-es": "catalog:",
+ "minimatch": "catalog:",
+ "prettier": "catalog:",
+ "zod": "catalog:",
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ "lib": {
+ "name": "@adeficior/data-modifier",
+ "version": "1.0.0",
+ "bin": {
+ "datamod": "dist/cli.js",
+ },
+ "dependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-loot": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/resource-merger": "catalog:",
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "lodash-es": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ },
+ },
+ "modules/botania": {
+ "name": "@adeficior/data-modifier-botania",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ },
+ },
+ "modules/content": {
+ "name": "@adeficior/data-modifier-content",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-loot": "workspace:*",
+ "@adeficior/data-modifier-models": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-loot": "workspace:*",
+ "@adeficior/data-modifier-models": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "lodash-es": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ "modules/create": {
+ "name": "@adeficior/data-modifier-create",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
},
+ },
+ "modules/farmersdelight": {
+ "name": "@adeficior/data-modifier-farmersdelight",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ },
+ },
+ "modules/ingredients": {
+ "name": "@adeficior/data-modifier-ingredients",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "lodash-es": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ },
+ },
+ "modules/lang": {
+ "name": "@adeficior/data-modifier-lang",
+ "version": "1.0.0",
"devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "lodash-es": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ },
+ },
+ "modules/loot": {
+ "name": "@adeficior/data-modifier-loot",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ },
+ },
+ "modules/models": {
+ "name": "@adeficior/data-modifier-models",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ "modules/recipes": {
+ "name": "@adeficior/data-modifier-recipes",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "lodash-es": "catalog:",
+ "minimatch": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ "modules/tags": {
+ "name": "@adeficior/data-modifier-tags",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "lodash-es": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ "modules/thermal": {
+ "name": "@adeficior/data-modifier-thermal",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ },
+ },
+ "packages/builder": {
+ "name": "@adeficior/data-modifier-builder",
+ "version": "1.0.0",
+ "bin": {
+ "datamod-builder": "./src/cli.ts",
+ },
+ "dependencies": {
+ "arg": "catalog:",
+ "handlebars": "^4.7.9",
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ },
+ "peerDependencies": {
+ "prettier": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ "packages/codegen": {
+ "name": "@adeficior/data-modifier-codegen",
+ "version": "1.0.0",
+ "bin": {
+ "datamod-codegen": "./dist/cli.ts",
+ },
+ "dependencies": {
+ "arg": "catalog:",
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "lodash-es": "catalog:",
+ "prettier": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ "packages/configs": {
+ "name": "@adeficior/configs",
+ "bin": {
+ "datamod-dev": "./src/cli.ts",
+ },
+ "dependencies": {
"@eslint/js": "^10.0.1",
- "@types/bun": "latest",
- "@types/command-line-usage": "^5.0.4",
- "@types/lodash-es": "^4.17.12",
- "@types/minimatch": "^3.0.5",
- "@types/prettier": "^2.7.3",
+ "arg": "catalog:",
"eslint": "^10.5.0",
- "husky": "^9.1.7",
- "typescript": "^5",
+ "eslint-plugin-import": "^2.32.0",
"typescript-eslint": "^8.61.0",
},
+ "peerDependencies": {
+ "prettier": "catalog:",
+ "typescript": "catalog:",
+ },
},
+ "packages/testing": {
+ "name": "@adeficior/testing",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ },
+ "peerDependencies": {
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ },
+ },
+ },
+ "catalog": {
+ "@adeficior/pack-resolver": "^2.0.0",
+ "@adeficior/resource-merger": "^2.0.1",
+ "@types/lodash-es": "^4.17.12",
+ "arg": "^5.0.2",
+ "lodash-es": "^4.18.1",
+ "minimatch": "^10.2.6",
+ "prettier": "^3.8.4",
+ "typescript": "^5",
+ "zod": "^4.4.3",
},
"packages": {
- "@adeficior/pack-resolver": ["@adeficior/pack-resolver@2.0.0-rc.51", "", { "dependencies": { "minimatch": "^10.2.5", "node-stream-zip": "^1.15.0", "zip-a-folder": "^6.1.1" } }, "sha512-BND9qFeAKVvXjgDuvC3ZbpN4eASzZpEUnICYxNXCFj7WetWrk8wYfRKO3WjyI+kmaruLPoFwA5msgY3cB7TnGw=="],
+ "@adeficior/configs": ["@adeficior/configs@workspace:packages/configs"],
+
+ "@adeficior/data-modifier": ["@adeficior/data-modifier@workspace:lib"],
+
+ "@adeficior/data-modifier-botania": ["@adeficior/data-modifier-botania@workspace:modules/botania"],
+
+ "@adeficior/data-modifier-builder": ["@adeficior/data-modifier-builder@workspace:packages/builder"],
+
+ "@adeficior/data-modifier-codegen": ["@adeficior/data-modifier-codegen@workspace:packages/codegen"],
+
+ "@adeficior/data-modifier-content": ["@adeficior/data-modifier-content@workspace:modules/content"],
+
+ "@adeficior/data-modifier-core": ["@adeficior/data-modifier-core@workspace:core"],
+
+ "@adeficior/data-modifier-create": ["@adeficior/data-modifier-create@workspace:modules/create"],
+
+ "@adeficior/data-modifier-farmersdelight": ["@adeficior/data-modifier-farmersdelight@workspace:modules/farmersdelight"],
+
+ "@adeficior/data-modifier-ingredients": ["@adeficior/data-modifier-ingredients@workspace:modules/ingredients"],
+
+ "@adeficior/data-modifier-lang": ["@adeficior/data-modifier-lang@workspace:modules/lang"],
+
+ "@adeficior/data-modifier-loot": ["@adeficior/data-modifier-loot@workspace:modules/loot"],
+
+ "@adeficior/data-modifier-models": ["@adeficior/data-modifier-models@workspace:modules/models"],
+
+ "@adeficior/data-modifier-recipes": ["@adeficior/data-modifier-recipes@workspace:modules/recipes"],
+
+ "@adeficior/data-modifier-tags": ["@adeficior/data-modifier-tags@workspace:modules/tags"],
+
+ "@adeficior/data-modifier-thermal": ["@adeficior/data-modifier-thermal@workspace:modules/thermal"],
+
+ "@adeficior/pack-resolver": ["@adeficior/pack-resolver@2.0.0", "", { "dependencies": { "minimatch": "^10.2.5", "node-stream-zip": "^1.15.0", "zip-a-folder": "^6.1.1" } }, "sha512-Tm4yHwKO85wJQvWntAhlrnBNvogkXE0twCqgna4VoH8RJ+Trc8Fy+ta2olq5KpcPcPZ/5QIphZuv3iDOfxsTQA=="],
+
+ "@adeficior/resource-merger": ["@adeficior/resource-merger@2.0.1", "", { "dependencies": { "@adeficior/pack-resolver": "^2.0.0", "arg": "^5.0.2", "command-line-usage": "^6.1.3" }, "bin": { "merge": "dist/cli/index.js" } }, "sha512-YwJCymQpliEyf3TDe2O9sjIJWPUxdVaEZolBTnjYn6zTiIWOTNcyiyoQumfn1nSK5GPUwWpoVdi+MEyWXuas+w=="],
+
+ "@adeficior/testing": ["@adeficior/testing@workspace:packages/testing"],
+
+ "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
+
+ "@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.1.1", "", { "dependencies": { "@changesets/config": "^3.1.4", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA=="],
+
+ "@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@6.0.10", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "semver": "^7.5.3" } }, "sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A=="],
+
+ "@changesets/changelog-git": ["@changesets/changelog-git@0.2.1", "", { "dependencies": { "@changesets/types": "^6.1.0" } }, "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q=="],
+
+ "@changesets/cli": ["@changesets/cli@2.31.1", "", { "dependencies": { "@changesets/apply-release-plan": "^7.1.1", "@changesets/assemble-release-plan": "^6.0.10", "@changesets/changelog-git": "^0.2.1", "@changesets/config": "^3.1.4", "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.4", "@changesets/get-release-plan": "^4.0.16", "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.7", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@changesets/write": "^0.4.0", "@inquirer/external-editor": "^1.0.2", "@manypkg/get-packages": "^1.1.3", "ansi-colors": "^4.1.3", "enquirer": "^2.4.1", "fs-extra": "^7.0.1", "mri": "^1.2.0", "package-manager-detector": "^0.2.0", "picocolors": "^1.1.0", "resolve-from": "^5.0.0", "semver": "^7.5.3", "spawndamnit": "^3.0.1", "term-size": "^2.1.0" }, "bin": { "changeset": "bin.js" } }, "sha512-uO05WTcRBwuVOJVSW8Cmpqw6q0WDL53ajGCMyszutvOe5toOnunbpM4jZzf+qxBOz7i0AzopZ8diBuewjmF40w=="],
+
+ "@changesets/config": ["@changesets/config@3.1.4", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.4", "@changesets/logger": "^0.1.1", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1", "micromatch": "^4.0.8" } }, "sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q=="],
+
+ "@changesets/errors": ["@changesets/errors@0.2.0", "", { "dependencies": { "extendable-error": "^0.1.5" } }, "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow=="],
+
+ "@changesets/get-dependents-graph": ["@changesets/get-dependents-graph@2.1.4", "", { "dependencies": { "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "picocolors": "^1.1.0", "semver": "^7.5.3" } }, "sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg=="],
+
+ "@changesets/get-release-plan": ["@changesets/get-release-plan@4.0.16", "", { "dependencies": { "@changesets/assemble-release-plan": "^6.0.10", "@changesets/config": "^3.1.4", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.7", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g=="],
+
+ "@changesets/get-version-range-type": ["@changesets/get-version-range-type@0.4.0", "", {}, "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ=="],
- "@adeficior/resource-merger": ["@adeficior/resource-merger@2.0.0-rc.16", "", { "dependencies": { "@adeficior/pack-resolver": "^2.0.0-rc.51", "arg": "^5.0.2", "command-line-usage": "^6.1.3" }, "bin": { "merge": "dist/cli/index.js" } }, "sha512-VgmwHYzoy3JqBI3PcqGVs35UXdXl9tWoh9eHlObEv0RBtl/U+YJIh3slkALqy0Q1twlDeG9ZAS5aoPL9c3cmPQ=="],
+ "@changesets/git": ["@changesets/git@3.0.4", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@manypkg/get-packages": "^1.1.3", "is-subdir": "^1.1.1", "micromatch": "^4.0.8", "spawndamnit": "^3.0.1" } }, "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw=="],
- "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
+ "@changesets/logger": ["@changesets/logger@0.1.1", "", { "dependencies": { "picocolors": "^1.1.0" } }, "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg=="],
+
+ "@changesets/parse": ["@changesets/parse@0.4.3", "", { "dependencies": { "@changesets/types": "^6.1.0", "js-yaml": "^4.1.1" } }, "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A=="],
+
+ "@changesets/pre": ["@changesets/pre@2.0.2", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1" } }, "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug=="],
+
+ "@changesets/read": ["@changesets/read@0.6.7", "", { "dependencies": { "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/parse": "^0.4.3", "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "p-filter": "^2.1.0", "picocolors": "^1.1.0" } }, "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA=="],
+
+ "@changesets/should-skip-package": ["@changesets/should-skip-package@0.1.2", "", { "dependencies": { "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw=="],
+
+ "@changesets/types": ["@changesets/types@6.1.0", "", {}, "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA=="],
+
+ "@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="],
+
+ "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
"@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="],
- "@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="],
+ "@eslint/config-helpers": ["@eslint/config-helpers@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw=="],
"@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="],
@@ -62,9 +432,33 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
- "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
+ "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="],
+
+ "@manypkg/find-root": ["@manypkg/find-root@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@types/node": "^12.7.1", "find-up": "^4.1.0", "fs-extra": "^8.1.0" } }, "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA=="],
+
+ "@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="],
+
+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
+
+ "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
+
+ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+
+ "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
+
+ "@turbo/darwin-64": ["@turbo/darwin-64@2.10.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-po+7rfJfUnFXjWlcoN2RwhErgzCdRtBc1T26vYPcywHlggmCQiQe1uWaE4j+BibI2uY9/2pDoFzMN0rmSaPFOw=="],
+
+ "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.10.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+zB2btDJ00lnPRuqOvpVvgl4x34k/djZQGZTTCfjn7JgNCl8QFY5Njo5+dqkY1g/+9gbbsnAvWm9CmJg9ebcXA=="],
+
+ "@turbo/linux-64": ["@turbo/linux-64@2.10.8", "", { "os": [ "linux", "android", ], "cpu": "x64" }, "sha512-K1dxqiVisyN7cViVsfQLs6xscQbYuI8aO2nbUhFURDACgEDfZRdP/b4CCxeosBJpcMfhYyiibWqJorCnvz9kKg=="],
+
+ "@turbo/linux-arm64": ["@turbo/linux-arm64@2.10.8", "", { "os": [ "linux", "android", ], "cpu": "arm64" }, "sha512-Gi77ibVnrE1fEmvr+/wBD/yvRqhwp/RQuCp2+//lv1U1wNFFyVg0V7Wj8FG9FXPFAw5QHReo8rxc9+wBSDZjzA=="],
+
+ "@turbo/windows-64": ["@turbo/windows-64@2.10.8", "", { "os": "win32", "cpu": "x64" }, "sha512-znnLO1haJPYTHoKMKwlAvlkjRiYbbhBzME6wIGaMd+fwir23U6jVd1ecaTWWi1fbnRVqxMfgDBKseQ/hLKb83g=="],
- "@types/command-line-usage": ["@types/command-line-usage@5.0.4", "", {}, "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg=="],
+ "@turbo/windows-arm64": ["@turbo/windows-arm64@2.10.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-VN30vh3b3Czh2WzYHNTfF1FE0YMZ5aHsLO8dBMGHJewA6792wX6iJR8ZxlzFW6WdOu0gEAKIvlYhfyT81Wkm4Q=="],
+
+ "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
@@ -72,81 +466,151 @@
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
- "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
-
- "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="],
-
- "@types/minimatch": ["@types/minimatch@3.0.5", "", {}, "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="],
+ "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
- "@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
+ "@types/lodash": ["@types/lodash@4.17.25", "", {}, "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ=="],
- "@types/prettier": ["@types/prettier@2.7.3", "", {}, "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA=="],
+ "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="],
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.61.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/type-utils": "8.61.0", "@typescript-eslint/utils": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.61.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw=="],
+ "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="],
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.61.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/types": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w=="],
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
- "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.61.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.61.0", "@typescript-eslint/types": "^8.61.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA=="],
+ "@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.61.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0" } }, "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA=="],
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
- "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.61.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ=="],
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
- "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.61.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0", "@typescript-eslint/utils": "8.61.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A=="],
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
- "@typescript-eslint/types": ["@typescript-eslint/types@8.61.0", "", {}, "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg=="],
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.61.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.61.0", "@typescript-eslint/tsconfig-utils": "8.61.0", "@typescript-eslint/types": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA=="],
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.61.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/types": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA=="],
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.61.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ=="],
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
- "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
- "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
+ "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
+ "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
+
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
- "array-back": ["array-back@6.2.3", "", {}, "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw=="],
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "array-back": ["array-back@4.0.2", "", {}, "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg=="],
+
+ "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
+
+ "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
+
+ "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
+
+ "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
+
+ "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
+
+ "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
+
+ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
+
+ "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
+
+ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
- "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
- "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+ "better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="],
- "brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
+ "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
- "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
- "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+ "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="],
- "chalk-template": ["chalk-template@0.4.0", "", { "dependencies": { "chalk": "^4.1.2" } }, "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg=="],
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
+
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
+
+ "chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "chardet": ["chardet@2.2.0", "", {}, "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA=="],
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
- "command-line-usage": ["command-line-usage@7.0.4", "", { "dependencies": { "array-back": "^6.2.2", "chalk-template": "^0.4.0", "table-layout": "^4.1.1", "typical": "^7.3.0" } }, "sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg=="],
+ "command-line-usage": ["command-line-usage@6.1.3", "", { "dependencies": { "array-back": "^4.0.2", "chalk": "^2.4.2", "table-layout": "^1.0.2", "typical": "^5.2.0" } }, "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw=="],
+
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+ "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
+
+ "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
+
+ "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
+ "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
+
+ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
+
+ "detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="],
+
+ "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
+
+ "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
+
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+
+ "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
+
+ "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="],
+
+ "es-abstract-get": ["es-abstract-get@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "is-callable": "^1.2.7", "object-inspect": "^1.13.4" } }, "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg=="],
+
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
+
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
+
+ "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
+
+ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
+
+ "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
+
+ "es-to-primitive": ["es-to-primitive@1.3.4", "", { "dependencies": { "es-abstract-get": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", "is-symbol": "^1.1.1" } }, "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw=="],
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
- "eslint": ["eslint@10.5.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ=="],
+ "eslint": ["eslint@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ=="],
+
+ "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="],
+
+ "eslint-module-utils": ["eslint-module-utils@2.14.0", "", { "dependencies": { "debug": "^3.2.7" }, "peerDependencies": { "eslint": "*" }, "optionalPeers": ["eslint"] }, "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig=="],
+
+ "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
@@ -154,6 +618,8 @@
"espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="],
+ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
@@ -162,44 +628,148 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
- "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
-
- "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
+ "extendable-error": ["extendable-error@0.1.7", "", {}, "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
+ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
+
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
+ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
+
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
- "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
+ "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="],
+
+ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
+
+ "fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+ "function.prototype.name": ["function.prototype.name@1.2.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2", "hasown": "^2.0.4", "is-callable": "^1.2.7", "is-document.all": "^1.0.0" } }, "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew=="],
+
+ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
+
+ "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
+
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
+
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
+
+ "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
+ "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
+
+ "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
+
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "handlebars": ["handlebars@4.7.9", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ=="],
+
+ "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
+
"has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+ "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
+
+ "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
+
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
+
+ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
+
+ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
+
+ "human-id": ["human-id@4.2.0", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA=="],
+
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
- "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
+
+ "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
+
+ "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
+
+ "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
+
+ "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
+
+ "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
+
+ "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
+
+ "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="],
+
+ "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
+
+ "is-document.all": ["is-document.all@1.0.0", "", { "dependencies": { "call-bound": "^1.0.4" } }, "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g=="],
+
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+ "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
+
+ "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
+ "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
+
+ "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
+
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
+
+ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
+
+ "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
+
+ "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
+
+ "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
+
+ "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
+
+ "is-subdir": ["is-subdir@1.2.0", "", { "dependencies": { "better-path-resolve": "1.0.0" } }, "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw=="],
+
+ "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
+
+ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
+
+ "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
+
+ "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
+
+ "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
+
+ "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="],
+
+ "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+ "js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
+
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
@@ -208,6 +778,8 @@
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+ "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
@@ -216,140 +788,274 @@
"lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
- "lodash.reduce": ["lodash.reduce@4.6.0", "", {}, "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw=="],
+ "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],
"lzma": ["lzma@2.3.2", "", { "bin": { "lzma.js": "bin/lzma.js" } }, "sha512-DcfiawQ1avYbW+hsILhF38IKAlnguc/fjHrychs9hdxe4qLykvhT5VTGNs5YRWgaNePh7NTxGD4uv4gKsRomCQ=="],
- "minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
+
+ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
+
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+
+ "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
+
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
+ "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
- "node-stream-zip": ["node-stream-zip@1.15.0", "", {}, "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="],
+ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
+
+ "node-exports-info": ["node-exports-info@1.6.2", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag=="],
+
+ "node-stream-zip": ["node-stream-zip@1.16.0", "", {}, "sha512-ObaRrRoR8T68wF6suxHd7R4XQNamij6ZQHrwG7Dx1D2zeHcDNLsIOBcWrIwtDm7AsCXBguaPHgXhcjxDa2szrg=="],
+
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
+
+ "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
+
+ "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
+
+ "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="],
+
+ "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
+
+ "object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="],
+
+ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
+ "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="],
+
+ "own-keys": ["own-keys@1.0.2", "", { "dependencies": { "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg=="],
+
+ "p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="],
+
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+ "p-map": ["p-map@2.1.0", "", {}, "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="],
+
+ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
+
+ "package-manager-detector": ["package-manager-detector@0.2.11", "", { "dependencies": { "quansync": "^0.2.7" } }, "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ=="],
+
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
- "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
- "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
+ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
- "prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="],
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
- "prismarine-nbt": ["prismarine-nbt@2.8.0", "", { "dependencies": { "protodef": "^1.18.0" } }, "sha512-5D6FUZq0PNtf3v/41ImDlwThVesOv5adyqCRMZLzmkUGEmRJNNh5C6AsnvrClBftXs+IF0yqPnZoj8kcNPiMGg=="],
+ "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
- "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
+ "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="],
- "protodef": ["protodef@1.19.0", "", { "dependencies": { "lodash.reduce": "^4.6.0", "protodef-validator": "^1.3.0", "readable-stream": "^4.4.0" } }, "sha512-94f3GR7pk4Qi5YVLaLvWBfTGUIzzO8hyo7vFVICQuu5f5nwKtgGDaeC1uXIu49s5to/49QQhEYeL0aigu1jEGA=="],
+ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
+
+ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
- "protodef-validator": ["protodef-validator@1.4.0", "", { "dependencies": { "ajv": "^6.5.4" }, "bin": { "protodef-validator": "cli.js" } }, "sha512-2y2coBolqCEuk5Kc3QwO7ThR+/7TZiOit4FrpAgl+vFMvq8w76nDhh09z08e2NQOdrgPLsN2yzXsvRvtADgUZQ=="],
+ "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
- "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
+ "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
+
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
+ "read-yaml-file": ["read-yaml-file@1.1.0", "", { "dependencies": { "graceful-fs": "^4.1.5", "js-yaml": "^3.6.1", "pify": "^4.0.1", "strip-bom": "^3.0.0" } }, "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA=="],
"reduce-flatten": ["reduce-flatten@2.0.0", "", {}, "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w=="],
- "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+ "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
+
+ "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
+
+ "resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="],
+
+ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
+
+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
+
+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
+
+ "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="],
+
+ "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
- "semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
+ "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
+
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+
+ "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
+
+ "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
+
+ "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
- "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+ "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
+
+ "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
+
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
+
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
+
+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+
+ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "^7.0.5", "signal-exit": "^4.0.1" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="],
+
+ "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
+
+ "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
+
+ "string.prototype.trim": ["string.prototype.trim@1.2.11", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "has-property-descriptors": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w=="],
+
+ "string.prototype.trimend": ["string.prototype.trimend@1.0.10", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.2" } }, "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw=="],
+
+ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
+
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
- "table-layout": ["table-layout@4.1.1", "", { "dependencies": { "array-back": "^6.2.2", "wordwrapjs": "^5.1.0" } }, "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA=="],
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "table-layout": ["table-layout@1.0.2", "", { "dependencies": { "array-back": "^4.0.1", "deep-extend": "~0.6.0", "typical": "^5.2.0", "wordwrapjs": "^4.0.0" } }, "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A=="],
+
+ "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="],
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
+ "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
+
+ "turbo": ["turbo@2.10.8", "", { "optionalDependencies": { "@turbo/darwin-64": "2.10.8", "@turbo/darwin-arm64": "2.10.8", "@turbo/linux-64": "2.10.8", "@turbo/linux-arm64": "2.10.8", "@turbo/windows-64": "2.10.8", "@turbo/windows-arm64": "2.10.8" }, "bin": { "turbo": "bin/turbo" } }, "sha512-9+8YX5QOkGXzZxcIykTHgaooRHGMWO+jfdyRK0o+rN0U7hBIig2MrJ8r/aNzIPDPhdA73SGb0O+tIztaModTMg=="],
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+ "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
+
+ "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
+
+ "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="],
+
+ "typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="],
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
- "typescript-eslint": ["typescript-eslint@8.61.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.61.0", "@typescript-eslint/parser": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0", "@typescript-eslint/utils": "8.61.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw=="],
+ "typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
- "typical": ["typical@7.3.0", "", {}, "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw=="],
+ "typical": ["typical@5.2.0", "", {}, "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg=="],
+
+ "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
+
+ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
+
+ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
+
+ "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+ "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
+
+ "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
+
+ "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
+
+ "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="],
+
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
- "wordwrapjs": ["wordwrapjs@5.1.1", "", {}, "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg=="],
+ "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
+
+ "wordwrapjs": ["wordwrapjs@4.0.1", "", { "dependencies": { "reduce-flatten": "^2.0.0", "typical": "^5.2.0" } }, "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
- "zip-a-folder": ["zip-a-folder@6.1.1", "", { "dependencies": { "lzma": "^2.3.2", "tinyglobby": "^0.2.16" }, "bin": { "zip-a-folder": "dist/cli.mjs" } }, "sha512-8hjtUn4YQpj8HZvDwtGHhol27oDf+D1x70ldKwA3Bwru6gup62fDVrBTd+BC90/8REgjdCa5ep7EsBiGHudSdA=="],
+ "zip-a-folder": ["zip-a-folder@6.1.3", "", { "dependencies": { "lzma": "^2.3.2", "tinyglobby": "^0.2.17" }, "bin": { "zip-a-folder": "dist/cli.mjs" } }, "sha512-6A4bKRyxLv9oVZwcBuF8zkV9YOdGD0XCVUgCKWiuK9lBXTQ3WSDYzGsA0ROSFfX2alGfvpf+zYtrfof2DFOGzg=="],
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
- "@adeficior/pack-resolver/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+ "@changesets/apply-release-plan/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
- "@adeficior/resource-merger/command-line-usage": ["command-line-usage@6.1.3", "", { "dependencies": { "array-back": "^4.0.2", "chalk": "^2.4.2", "table-layout": "^1.0.2", "typical": "^5.2.0" } }, "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw=="],
+ "@changesets/write/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
- "@eslint/config-array/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+ "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="],
- "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+ "@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
- "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+ "@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
- "chalk-template/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+ "@manypkg/get-packages/@changesets/types": ["@changesets/types@4.1.0", "", {}, "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw=="],
- "eslint/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+ "@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
- "@adeficior/pack-resolver/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
+ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="],
- "@adeficior/resource-merger/command-line-usage/array-back": ["array-back@4.0.2", "", {}, "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg=="],
+ "chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
- "@adeficior/resource-merger/command-line-usage/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+ "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
- "@adeficior/resource-merger/command-line-usage/table-layout": ["table-layout@1.0.2", "", { "dependencies": { "array-back": "^4.0.1", "deep-extend": "~0.6.0", "typical": "^5.2.0", "wordwrapjs": "^4.0.0" } }, "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A=="],
+ "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
- "@adeficior/resource-merger/command-line-usage/typical": ["typical@5.2.0", "", {}, "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg=="],
+ "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
- "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
+ "eslint-plugin-import/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
- "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
+ "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
- "chalk-template/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
- "chalk-template/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+ "fdir/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
- "eslint/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
+ "node-exports-info/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
- "@adeficior/pack-resolver/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ "read-yaml-file/js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="],
- "@adeficior/resource-merger/command-line-usage/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+ "tinyglobby/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
- "@adeficior/resource-merger/command-line-usage/table-layout/wordwrapjs": ["wordwrapjs@4.0.1", "", { "dependencies": { "reduce-flatten": "^2.0.0", "typical": "^5.2.0" } }, "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA=="],
+ "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
- "@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ "@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
- "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="],
- "chalk-template/chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+ "read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
- "chalk-template/chalk/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+ "@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
- "eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ "eslint-plugin-import/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
- "chalk-template/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+ "@manypkg/find-root/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
}
}
diff --git a/bunfig.toml b/bunfig.toml
new file mode 100644
index 0000000..e6cdf82
--- /dev/null
+++ b/bunfig.toml
@@ -0,0 +1,2 @@
+[install]
+publicHoistPattern = ["*eslint*", "@types/bun"]
diff --git a/core/package.json b/core/package.json
new file mode 100644
index 0000000..7b57181
--- /dev/null
+++ b/core/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "@adeficior/data-modifier-core",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js",
+ "./testing": "./dist/testing/index.js",
+ "./serializer": "./dist/serializer/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "typesVersions": {
+ "*": {
+ "testing": [
+ "./dist/testing/index.d.ts"
+ ],
+ "serializer": [
+ "./dist/serializer/index.d.ts"
+ ]
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:"
+ },
+ "dependencies": {
+ "json5": "^2.2.3",
+ "lodash-es": "catalog:",
+ "minimatch": "catalog:",
+ "prettier": "catalog:",
+ "zod": "catalog:"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:"
+ }
+}
\ No newline at end of file
diff --git a/src/common/class.ts b/core/src/common/class.ts
similarity index 90%
rename from src/common/class.ts
rename to core/src/common/class.ts
index f0dfcc4..307871a 100644
--- a/src/common/class.ts
+++ b/core/src/common/class.ts
@@ -3,4 +3,5 @@ type Constructor = new (...args: any[]) => T;
type AbstractConstructor = abstract new (...args: any[]) => T;
/* eslint-enable @typescript-eslint/no-explicit-any */
+// TODO export this somewhere?
export type Class = Constructor | AbstractConstructor;
diff --git a/core/src/common/context.ts b/core/src/common/context.ts
new file mode 100644
index 0000000..2f574b4
--- /dev/null
+++ b/core/src/common/context.ts
@@ -0,0 +1,3 @@
+import { type BaseContext } from "@adeficior/pack-resolver";
+
+export type LoaderContext = BaseContext;
diff --git a/src/error.ts b/core/src/common/error.ts
similarity index 90%
rename from src/error.ts
rename to core/src/common/error.ts
index 750b6ab..18274fe 100644
--- a/src/error.ts
+++ b/core/src/common/error.ts
@@ -1,5 +1,8 @@
-import type { InferIds, RegistryId } from "@adeficior/data-modifier/generated";
-import type { Logger } from "@adeficior/pack-resolver";
+import {
+ type InferIds,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import { type Logger } from "@adeficior/pack-resolver";
import { ZodError } from "zod";
export class IllegalShapeError extends Error {
diff --git a/src/common/id.ts b/core/src/common/id.ts
similarity index 97%
rename from src/common/id.ts
rename to core/src/common/id.ts
index 624898e..b31b698 100644
--- a/src/common/id.ts
+++ b/core/src/common/id.ts
@@ -1,4 +1,4 @@
-import zod from "zod";
+import * as z from "zod";
export type Id = Readonly<{
namespace: string;
@@ -16,10 +16,9 @@ export type NormalizedId =
: `${string}:${T}`;
export type TagId = NormalizedId<`#${string}`>;
-
export type TagInput = IdInput<`#${string}`>;
-export const IdSchema = zod
+export const IdSchema = z
.string()
.regex(
/^#?([a-z0-9_.-]+:)?[a-z0-9_.-/]+$/i,
diff --git a/src/packFormat.ts b/core/src/common/packFormat.ts
similarity index 88%
rename from src/packFormat.ts
rename to core/src/common/packFormat.ts
index 1d9b1a8..a529ba0 100644
--- a/src/packFormat.ts
+++ b/core/src/common/packFormat.ts
@@ -74,10 +74,6 @@ export const packFormatOf = defineFormats({
"1.21.9": "88.0",
});
-export function lootTableFolder(packFormat: SemVerInput) {
- return isAtLeastVersion(packFormat, "44") ? "loot_table" : "loot_tables";
-}
-
-export function recipeFolder(packFormat: SemVerInput) {
- return isAtLeastVersion(packFormat, "44") ? "recipe" : "recipes";
+export function jsonFilePattern(packType: string, folder: string) {
+ return `${packType}/*/${folder}/**/*.json`;
}
diff --git a/core/src/common/textHelper.ts b/core/src/common/textHelper.ts
new file mode 100644
index 0000000..301ea23
--- /dev/null
+++ b/core/src/common/textHelper.ts
@@ -0,0 +1,28 @@
+import { type Acceptable, type Logger } from "@adeficior/pack-resolver";
+import json from "json5";
+import { format } from "prettier";
+
+export function fromJson(input: Acceptable): T {
+ const data = input.toString();
+ return json.parse(data.replaceAll("\r\n", ""));
+}
+
+export function toJson(input: unknown) {
+ return formatJson(JSON.stringify(input));
+}
+
+export function formatJson(input: string) {
+ return format(input, { parser: "json" });
+}
+
+export function tryParseJson(logger: Logger, content: Acceptable) {
+ try {
+ return fromJson(content);
+ } catch (error) {
+ if (error instanceof SyntaxError) {
+ logger.trace(`unable to parse json: ${error.message}`);
+ return null;
+ }
+ throw error;
+ }
+}
diff --git a/core/src/conditions.ts b/core/src/conditions.ts
new file mode 100644
index 0000000..bf0fec2
--- /dev/null
+++ b/core/src/conditions.ts
@@ -0,0 +1,75 @@
+import { createId } from "./common/id";
+import { any, every, type Predicate } from "./serializer/predicate";
+
+export type ForgeCondition = Readonly<{
+ type: string;
+ [key: string]: unknown;
+}>;
+
+export type FabricCondition = Readonly<{
+ condition: string;
+ [key: string]: unknown;
+}>;
+
+export type WithConditions = T &
+ Readonly<{
+ // TODO add neoforge conditions
+ conditions?: ForgeCondition[];
+ "neoforge:conditions"?: ForgeCondition[];
+ "fabric:load_conditions"?: FabricCondition[];
+ }>;
+
+export function withDisabledConditions(value: T): WithConditions {
+ return {
+ ...value,
+ conditions: [
+ {
+ type: "forge:false",
+ },
+ ],
+ "neoforge:conditions": [
+ {
+ type: "neoforge:false",
+ },
+ ],
+ "fabric:load_conditions": [
+ {
+ condition: "fabric:not",
+ value: {
+ condition: "fabric:all_mods_loaded",
+ values: ["minecraft"],
+ },
+ },
+ ],
+ };
+}
+
+export type ConditionContext = {
+ mods: string[];
+};
+
+function forgePredicate(
+ condition: ForgeCondition,
+): Predicate {
+ const { path } = createId(condition.type);
+ if (path === "mod_loaded")
+ return ({ mods }) => mods.includes(condition.mod_id as string);
+ if (path === "and")
+ return every(...(condition.values as ForgeCondition[]).map(forgePredicate));
+ if (path === "or")
+ return any(...(condition.values as ForgeCondition[]).map(forgePredicate));
+
+ return () => true;
+}
+
+export function conditionsPredicate(
+ value: WithConditions,
+): Predicate {
+ // TODO fabric
+ // TODO tests
+
+ return every(
+ ...(value["neoforge:conditions"] ?? []).map(forgePredicate),
+ ...(value["conditions"] ?? []).map(forgePredicate),
+ );
+}
diff --git a/core/src/config.ts b/core/src/config.ts
new file mode 100644
index 0000000..a46780b
--- /dev/null
+++ b/core/src/config.ts
@@ -0,0 +1,8 @@
+import { type SemVerInput } from "./common/packFormat";
+import { type CoreModuleOptions } from "./module";
+
+export type ModuleSetupOptions = {
+ packFormat: SemVerInput;
+};
+
+export type DataModifierOptions = ModuleSetupOptions & CoreModuleOptions;
diff --git a/core/src/container.ts b/core/src/container.ts
new file mode 100644
index 0000000..c9948e8
--- /dev/null
+++ b/core/src/container.ts
@@ -0,0 +1,12 @@
+import { type Services } from "@adeficior/data-modifier-core/generated";
+import { type ModuleConfig } from "./modules/define";
+
+export type Container = {
+ get>(key: TKey): Services[TKey];
+ getOrNull>(
+ key: TKey,
+ ): Services[TKey] | null;
+
+ get(key: string): T;
+ getOrNull(key: string): T | null;
+};
diff --git a/core/src/emit/abstract.ts b/core/src/emit/abstract.ts
new file mode 100644
index 0000000..592cb9d
--- /dev/null
+++ b/core/src/emit/abstract.ts
@@ -0,0 +1,10 @@
+import { type Acceptable, type Resolver } from "@adeficior/pack-resolver";
+import { type LoaderContext } from "../common/context";
+import { type Id } from "../common/id";
+
+export type PathProvider = (id: Id) => string;
+
+export interface ClearableEmitter {
+ clear(): void;
+ resolver(context: LoaderContext): Resolver;
+}
diff --git a/core/src/emit/combined.ts b/core/src/emit/combined.ts
new file mode 100644
index 0000000..1afe4ca
--- /dev/null
+++ b/core/src/emit/combined.ts
@@ -0,0 +1,25 @@
+import { combineResolvers, extendContext } from "@adeficior/pack-resolver";
+import { type LoaderContext } from "../common/context";
+import { type ClearableEmitter } from "./abstract";
+
+export class CombinedEmitters implements ClearableEmitter {
+ private entries: ClearableEmitter[] = [];
+
+ add(emitter: T): T {
+ this.entries.push(emitter);
+ return emitter;
+ }
+
+ resolver(context: LoaderContext) {
+ return combineResolvers(
+ this.entries.map((it) =>
+ it.resolver(extendContext(context, { emitter: it.constructor.name })),
+ ),
+ { async: true },
+ );
+ }
+
+ clear() {
+ this.entries.forEach((it) => it.clear());
+ }
+}
diff --git a/src/emit/custom.ts b/core/src/emit/custom.ts
similarity index 71%
rename from src/emit/custom.ts
rename to core/src/emit/custom.ts
index f61e1e3..c9e5f84 100644
--- a/src/emit/custom.ts
+++ b/core/src/emit/custom.ts
@@ -1,11 +1,11 @@
-import { simpleResolver, type BaseContext } from "@adeficior/pack-resolver";
-import type { IdInput } from "../common/id.js";
-import { createId } from "../common/id.js";
-import Registry from "../common/registry.js";
-import { toJson } from "../textHelper.js";
-import type { ClearableEmitter, PathProvider } from "./index.js";
-
-export default class CustomEmitter implements ClearableEmitter {
+import { simpleResolver } from "@adeficior/pack-resolver";
+import { type LoaderContext } from "../common/context";
+import { createId, type IdInput } from "../common/id";
+import { toJson } from "../common/textHelper";
+import { Registry } from "../registry/impl";
+import { type ClearableEmitter, type PathProvider } from "./abstract";
+
+export class CustomEmitter implements ClearableEmitter {
constructor(
private readonly pathProvider: PathProvider,
private readonly encoder: (
@@ -36,7 +36,7 @@ export default class CustomEmitter implements ClearableEmitter {
else this.add(id, factory());
}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return simpleResolver(async (acceptor) => {
await this.customEntries.forEachAsync(async (entry, id) => {
const path = this.pathProvider(id);
diff --git a/src/emit/ruled.ts b/core/src/emit/ruled.ts
similarity index 69%
rename from src/emit/ruled.ts
rename to core/src/emit/ruled.ts
index 10faeed..92ad0a7 100644
--- a/src/emit/ruled.ts
+++ b/core/src/emit/ruled.ts
@@ -1,18 +1,31 @@
import {
extendLoggerContext,
simpleResolver,
- type BaseContext,
+ type Logger,
} from "@adeficior/pack-resolver";
-import type { Id } from "../common/id.js";
-import { toJson } from "../textHelper.js";
-import type {
- ClearableEmitter,
- PathProvider,
- RegistryProvider,
-} from "./index.js";
-import type Rule from "./rule/index.js";
-
-export default class RuledEmitter<
+import { type LoaderContext } from "../common/context";
+import { type Id } from "../common/id";
+import { toJson } from "../common/textHelper";
+import { type RegistryProvider } from "../registry/abstract";
+import { type ClearableEmitter, type PathProvider } from "./abstract";
+
+// TODO this should be replacer or at least nullable replacer?
+export type Modifier = (recipe: T) => T | null;
+
+export abstract class Rule {
+ protected constructor(private readonly modifier: Modifier) {}
+
+ // TODO why logger?
+ abstract matches(id: Id, recipe: T, logger: Logger): boolean;
+
+ abstract printWarning(logger: Logger): void;
+
+ modify(value: T) {
+ return this.modifier(value);
+ }
+}
+
+export class RuledEmitter<
TEntry,
TRule extends Rule,
> implements ClearableEmitter {
@@ -41,7 +54,7 @@ export default class RuledEmitter<
if (required) this.requiredRules.add(rule);
}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return simpleResolver(async (acceptor) => {
const missingRules = new Set(this.requiredRules);
await this.provider.forEachAsync(async (recipe, id) => {
diff --git a/core/src/index.ts b/core/src/index.ts
new file mode 100644
index 0000000..46eb63d
--- /dev/null
+++ b/core/src/index.ts
@@ -0,0 +1,29 @@
+export type * from "./common/class";
+export type * from "./common/context";
+export * from "./common/error";
+export * from "./common/id";
+export * from "./common/packFormat";
+export * from "./common/textHelper";
+
+export type * from "./config";
+
+export type * from "./container";
+export * from "./modules/define";
+
+export type * from "./emit/abstract";
+export * from "./emit/combined";
+export * from "./emit/custom";
+export * from "./emit/ruled";
+
+export type * from "./load/abstract";
+export * from "./load/json";
+
+export type * from "./registry/abstract";
+export * from "./registry/dump";
+export * from "./registry/empty";
+export * from "./registry/impl";
+export type * from "./registry/lookup";
+export type * from "./registry/registered";
+
+export * from "./conditions";
+export { default as module, type CoreModuleOptions } from "./module";
diff --git a/core/src/load/abstract.ts b/core/src/load/abstract.ts
new file mode 100644
index 0000000..c252433
--- /dev/null
+++ b/core/src/load/abstract.ts
@@ -0,0 +1,4 @@
+import { type Acceptable, type Acceptor } from "@adeficior/pack-resolver";
+import { type LoaderContext } from "../common/context";
+
+export type Loader = Acceptor;
diff --git a/core/src/load/json.ts b/core/src/load/json.ts
new file mode 100644
index 0000000..b8c0238
--- /dev/null
+++ b/core/src/load/json.ts
@@ -0,0 +1,65 @@
+import { type Acceptable, type Acceptor } from "@adeficior/pack-resolver";
+import { type LoaderContext } from "../common/context";
+import { tryCatching } from "../common/error";
+import { type Id, type IdInput } from "../common/id";
+import { tryParseJson } from "../common/textHelper";
+import {
+ conditionsPredicate,
+ type ConditionContext,
+ type WithConditions,
+} from "../conditions";
+import { type RegistryProvider } from "../registry/abstract";
+import { Registry } from "../registry/impl";
+
+export abstract class JsonLoader implements RegistryProvider, Acceptor {
+ private readonly registry = new Registry();
+
+ constructor(private readonly context?: ConditionContext) {}
+
+ protected abstract parse(json: unknown, id: Id): T | null;
+
+ get(id: IdInput): T | undefined {
+ return this.registry.get(id);
+ }
+
+ forEach(consumer: (value: T, id: Id) => void): void {
+ this.registry.forEach(consumer);
+ }
+
+ async forEachAsync(
+ consumer: (value: T, id: Id) => Promise,
+ ): Promise {
+ await this.registry.forEachAsync(consumer);
+ }
+
+ private shouldLoad(value: WithConditions) {
+ if (!this.context) return true;
+ const predicate = conditionsPredicate(value);
+ return predicate(this.context);
+ }
+
+ async accept(
+ path: string,
+ content: PromiseLike,
+ context: LoaderContext,
+ ) {
+ const match =
+ /(data|assets)\/(?[\w-]+)\/\w+\/(?[\w-_/]+).json/.exec(
+ path,
+ );
+ if (!match?.groups) return false;
+
+ const { namespace, rest } = match.groups;
+ const id: Id = { namespace: namespace!, path: rest! };
+
+ const json = tryParseJson>(context.logger, await content);
+ if (!json) return false;
+
+ if (!this.shouldLoad(json)) return false;
+
+ const parsed = tryCatching(context.logger, () => this.parse(json, id));
+ if (!parsed) return false;
+
+ this.registry.set(id, parsed);
+ }
+}
diff --git a/core/src/module.ts b/core/src/module.ts
new file mode 100644
index 0000000..ff8cd19
--- /dev/null
+++ b/core/src/module.ts
@@ -0,0 +1,65 @@
+import {
+ createLogger,
+ createResolver,
+ type Logger,
+ type Resolver,
+} from "@adeficior/pack-resolver";
+import { name } from "../package.json";
+import { defineModule, type AfterSetupEvent } from "./modules/define";
+import { RegistryDumpLoader } from "./registry/dump";
+import { EmptyRegistryLookup } from "./registry/empty";
+import { type RegistryLookup } from "./registry/lookup";
+
+export type CoreModuleOptions = {
+ logger?: Logger;
+ dump?: true | string | Resolver;
+};
+
+async function resolveDumpDir({
+ dump,
+}: CoreModuleOptions): Promise {
+ if (!dump) return null;
+ if (dump === true) return resolveDumpDir({ dump: "dump" });
+ if (typeof dump === "string")
+ return createResolver({ from: dump, logger: false });
+ return dump;
+}
+
+export default defineModule<{
+ hooks: {
+ "setup:after": AfterSetupEvent;
+ };
+ services: {
+ registries: RegistryLookup;
+ logger: Logger;
+ };
+ options: CoreModuleOptions;
+}>({
+ importModule: name,
+ types: {
+ options: "CoreModuleOptions",
+ services: {
+ registries: "RegistryLookup",
+ logger: {
+ module: "@adeficior/pack-resolver",
+ name: "Logger",
+ },
+ },
+ hooks: {
+ "setup:after": "AfterSetupEvent",
+ },
+ },
+ setup: async (pack) => {
+ const dumpResolver = await resolveDumpDir(pack.options);
+
+ if (dumpResolver) {
+ const loader = new RegistryDumpLoader();
+ await dumpResolver.extract(loader);
+ pack.service("registries", () => loader);
+ } else {
+ pack.service("registries", () => new EmptyRegistryLookup());
+ }
+
+ pack.service("logger", () => pack.options.logger ?? createLogger());
+ },
+});
diff --git a/core/src/modules/define.ts b/core/src/modules/define.ts
new file mode 100644
index 0000000..3e1b974
--- /dev/null
+++ b/core/src/modules/define.ts
@@ -0,0 +1,128 @@
+import { type Hooks } from "@adeficior/data-modifier-core/generated";
+import packageJson from "../../package.json";
+import { type ModuleSetupOptions } from "../config";
+import { type Container } from "../container";
+import { type ClearableEmitter } from "../emit/abstract";
+import { type Loader } from "../load/abstract";
+
+export type DependencyType = "required" | "optional";
+
+export type ImportOptions =
+ | string
+ | {
+ module?: string;
+ name?: string;
+ };
+
+export type ServiceOptions = {
+ import?: ImportOptions;
+};
+
+type Registration<
+ TTypes extends Record,
+ TAbstract = unknown,
+ TArgs extends unknown[] = [],
+> = (
+ key: TKey,
+ factory: (container: Container) => TImpl,
+ ...args: TArgs
+) => () => TImpl;
+
+export type EventHandler = (event: T) => Promise | void;
+
+export type ModuleTypes = {
+ // TODO these need to force being nullable
+ // or modules need to provide default values
+ options?: Record;
+ services?: Record;
+ emitters?: Record;
+ loaders?: Record;
+ hooks?: Record;
+};
+
+export type ModuleType<
+ T extends ModuleTypes,
+ TKey extends keyof ModuleTypes,
+> = NonNullable;
+
+export type ModuleHooks = Hooks &
+ ModuleType;
+
+// export type ModuleServices = Services & ModuleType;
+
+export type AfterSetupEvent = {
+ callHook>(
+ type: K,
+ event: ModuleHooks[K],
+ ): Promise;
+};
+
+export type SetupEvent = {
+ service: Registration>;
+ loader: Registration, Loader, [string]>;
+ emitter: Registration, ClearableEmitter>;
+ options: ModuleSetupOptions & ModuleType;
+ hook: >(
+ type: K,
+ handler: EventHandler[K]>,
+ ) => void;
+};
+
+type ModuleImports = {
+ options: ImportOptions;
+ services: Record, ImportOptions>;
+ emitters: Record, ImportOptions>;
+ loaders: Record, ImportOptions>;
+ hooks: Record, ImportOptions>;
+};
+
+type PackagedModule = {
+ importModule: string;
+ name?: string;
+};
+
+type LocalModule = {
+ name: string;
+};
+
+export type ModuleConfigInput = {
+ dependencies?: Record;
+ setup?: EventHandler>;
+ afterSetup?: EventHandler>;
+ types?: Partial>;
+} & (PackagedModule | LocalModule);
+
+export type ModuleConfig = {
+ name: string;
+ importModule?: string;
+ dependencies: Record;
+ setup: EventHandler>;
+ afterSetup: EventHandler>;
+ types: ModuleImports;
+};
+
+export function defineModule({
+ types,
+ dependencies,
+ ...config
+}: ModuleConfigInput): ModuleConfig {
+ const actualDependencies = { ...dependencies };
+ const name = config.name ?? (config as PackagedModule).importModule;
+ if (name !== packageJson.name) {
+ actualDependencies[packageJson.name] = "required";
+ }
+
+ return {
+ dependencies: actualDependencies,
+ setup: () => {},
+ afterSetup: () => {},
+ types: {
+ emitters: types?.emitters ?? {},
+ loaders: types?.loaders ?? {},
+ hooks: types?.hooks ?? {},
+ services: types?.services ?? {},
+ } as ModuleImports,
+ ...config,
+ name,
+ };
+}
diff --git a/core/src/registry/abstract.ts b/core/src/registry/abstract.ts
new file mode 100644
index 0000000..068bc5d
--- /dev/null
+++ b/core/src/registry/abstract.ts
@@ -0,0 +1,7 @@
+import { type Id, type IdInput } from "../common/id";
+
+export interface RegistryProvider {
+ get(id: IdInput): T | undefined;
+ forEach(consumer: (recipe: T, id: Id) => void): void;
+ forEachAsync(consumer: (recipe: T, id: Id) => Promise): Promise;
+}
diff --git a/src/loader/registry/dump.ts b/core/src/registry/dump.ts
similarity index 70%
rename from src/loader/registry/dump.ts
rename to core/src/registry/dump.ts
index 2f0b758..e051d0f 100644
--- a/src/loader/registry/dump.ts
+++ b/core/src/registry/dump.ts
@@ -1,20 +1,16 @@
-import type { RegistryId } from "@adeficior/data-modifier/generated";
-import type {
- Acceptable,
- Acceptor,
- BaseContext,
-} from "@adeficior/pack-resolver";
-import zod from "zod";
-import type { IdInput, NormalizedId } from "../../common/id.js";
-import { encodeId } from "../../common/id.js";
-import Registry from "../../common/registry.js";
-import { tryCatching, UnknownRegistryEntry } from "../../error.js";
-import { tryParseJson } from "../index.js";
-import type RegistryLookup from "./index.js";
+import { type RegistryId } from "@adeficior/data-modifier/generated";
+import { type Acceptable, type Acceptor } from "@adeficior/pack-resolver";
+import * as z from "zod";
+import { type LoaderContext } from "../common/context";
+import { tryCatching, UnknownRegistryEntry } from "../common/error";
+import { encodeId, type IdInput, type NormalizedId } from "../common/id";
+import { tryParseJson } from "../common/textHelper";
+import { Registry } from "./impl";
+import { type RegistryLookup } from "./lookup";
-const schema = zod.array(zod.string());
+const schema = z.array(z.string());
-export default class RegistryDumpLoader implements RegistryLookup, Acceptor {
+export class RegistryDumpLoader implements RegistryLookup, Acceptor {
private readonly registry = new Registry, RegistryId>();
private registryOf(registry: RegistryId) {
@@ -24,7 +20,7 @@ export default class RegistryDumpLoader implements RegistryLookup, Acceptor {
async accept(
path: string,
content: PromiseLike,
- context: BaseContext,
+ context: LoaderContext,
) {
const match = /(?[\w-/]+)\/[\w-]+.json/.exec(path);
if (!match?.groups) {
diff --git a/core/src/registry/empty.ts b/core/src/registry/empty.ts
new file mode 100644
index 0000000..bd11b11
--- /dev/null
+++ b/core/src/registry/empty.ts
@@ -0,0 +1,29 @@
+import { type RegistryId } from "@adeficior/data-modifier/generated";
+import { encodeId, type IdInput, type NormalizedId } from "../common/id";
+import { type RegistryLookup } from "./lookup";
+
+export class EmptyRegistryLookup implements RegistryLookup {
+ isKnown(): boolean {
+ return false;
+ }
+
+ registries(): NormalizedId[] {
+ return [];
+ }
+
+ keys() {
+ return undefined;
+ }
+
+ validate() {
+ // Nothing done
+ }
+
+ validateEntry() {
+ // Nothing done
+ }
+
+ addCustom(_key: RegistryId, id: IdInput) {
+ return encodeId(id);
+ }
+}
diff --git a/src/common/registry.ts b/core/src/registry/impl.ts
similarity index 83%
rename from src/common/registry.ts
rename to core/src/registry/impl.ts
index 7d0e571..be9d05b 100644
--- a/src/common/registry.ts
+++ b/core/src/registry/impl.ts
@@ -1,7 +1,11 @@
-import type { Id, IdInput, NormalizedId } from "./id.js";
-import { createId, encodeId } from "./id.js";
+import { type Id, type IdInput, type NormalizedId } from "../common/id";
+import { createId, encodeId } from "../common/id";
+import { type RegistryProvider } from "./abstract";
-export default class Registry {
+export class Registry<
+ TEntry,
+ TId extends string = string,
+> implements RegistryProvider {
private readonly entries = new Map, TEntry>();
set(key: IdInput, value: TEntry) {
diff --git a/src/loader/registry/index.ts b/core/src/registry/lookup.ts
similarity index 64%
rename from src/loader/registry/index.ts
rename to core/src/registry/lookup.ts
index 09311ac..0fea744 100644
--- a/src/loader/registry/index.ts
+++ b/core/src/registry/lookup.ts
@@ -1,7 +1,10 @@
-import type { InferIds, RegistryId } from "@adeficior/data-modifier/generated";
-import type { IdInput, NormalizedId } from "../../common/id.js";
+import {
+ type InferIds,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import { type IdInput, type NormalizedId } from "../common/id";
-export default interface RegistryLookup {
+export interface RegistryLookup {
registries(): NormalizedId[];
keys(
diff --git a/core/src/registry/registered.ts b/core/src/registry/registered.ts
new file mode 100644
index 0000000..8eed8af
--- /dev/null
+++ b/core/src/registry/registered.ts
@@ -0,0 +1,10 @@
+import { type RegistryId } from "@adeficior/data-modifier/generated";
+import { type NormalizedId } from "../common/id";
+import { type RegistryLookup } from "./lookup";
+
+export type RegistryIds = Record, NormalizedId[]>;
+
+export interface Registered {
+ validate(lookup: RegistryLookup): void;
+ ids(): RegistryIds;
+}
diff --git a/src/serializer/index.ts b/core/src/serializer/abstract.ts
similarity index 96%
rename from src/serializer/index.ts
rename to core/src/serializer/abstract.ts
index 494373b..a9ac374 100644
--- a/src/serializer/index.ts
+++ b/core/src/serializer/abstract.ts
@@ -1,4 +1,4 @@
-import type { SerializerModule } from "./module";
+import { type SerializerModule } from "./module";
export interface Serializer> {
serialize(output: Out): unknown;
diff --git a/src/serializer/checks.ts b/core/src/serializer/checks.ts
similarity index 100%
rename from src/serializer/checks.ts
rename to core/src/serializer/checks.ts
diff --git a/core/src/serializer/index.ts b/core/src/serializer/index.ts
new file mode 100644
index 0000000..dd3e125
--- /dev/null
+++ b/core/src/serializer/index.ts
@@ -0,0 +1,7 @@
+export type { Serializer } from "./abstract";
+export * from "./checks";
+export * from "./module";
+export * from "./predicate";
+export * from "./replacer";
+export * from "./versioned";
+export * from "./wrapped";
diff --git a/src/serializer/module.ts b/core/src/serializer/module.ts
similarity index 84%
rename from src/serializer/module.ts
rename to core/src/serializer/module.ts
index 8e9b67e..02ca23a 100644
--- a/src/serializer/module.ts
+++ b/core/src/serializer/module.ts
@@ -1,9 +1,7 @@
import { type ZodType } from "zod";
-import type { Class } from "../common/class";
-import type { Predicate } from "../common/filters";
-import type { Ingredient } from "../common/ingredient";
-import type { Result } from "../common/result";
-import { isAtLeastVersion, type SemVerInput } from "../packFormat";
+import { type Class } from "../common/class";
+import { isAtLeastVersion, type SemVerInput } from "../common/packFormat";
+import { type Predicate } from "./predicate";
type Mapper = (
it: From,
@@ -93,8 +91,3 @@ export function selectSerializerModule(
isAtLeastVersion(packFormat, it),
)?.[1];
}
-
-export interface WithSerializerModules {
- ingredientModules(): Record>;
- resultModules(): Record>;
-}
diff --git a/src/common/filters.ts b/core/src/serializer/predicate.ts
similarity index 61%
rename from src/common/filters.ts
rename to core/src/serializer/predicate.ts
index d13db77..69a14f7 100644
--- a/src/common/filters.ts
+++ b/core/src/serializer/predicate.ts
@@ -1,7 +1,17 @@
-import type { InferIds, RegistryId } from "@adeficior/data-modifier/generated";
-import type { TagRegistry } from "../loader/tags.js";
-import type { IdInput, NormalizedId, TagInput } from "./id.js";
-import { encodeId } from "./id.js";
+import {
+ type InferIds,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import {
+ encodeId,
+ type IdInput,
+ type NormalizedId,
+ type TagInput,
+} from "../common/id";
+
+export type TagChecker = {
+ contains(id: TagInput, entry: IdInput>): boolean;
+};
export type Predicate = (value: T) => boolean;
export type CommonFilter = RegExp | Predicate | T;
@@ -9,7 +19,7 @@ export type CommonFilter = RegExp | Predicate | T;
export function createIdPredicate(
test: CommonFilter,
resolve: (value: TEntry) => NormalizedId[],
- tags?: TagRegistry,
+ tags?: TagChecker,
): Predicate {
if (typeof test === "function") {
return (entry) => resolve(entry).some((id) => test(id as TId));
@@ -32,9 +42,10 @@ export function createIdPredicate(
}
}
-export function resolveIDTest(
+// TODO this needs to be in core still somehow?
+export function resolveIdTest(
test: CommonFilter>>,
- tags?: TagRegistry,
+ tags?: TagChecker,
): Predicate>> {
return createIdPredicate>, NormalizedId>>(
test,
@@ -42,3 +53,11 @@ export function resolveIDTest(
tags,
);
}
+
+export function every(...predicates: Predicate[]): Predicate {
+ return (...args) => predicates.every((it) => it(...args));
+}
+
+export function any(...predicates: Predicate[]): Predicate {
+ return (...args) => predicates.some((it) => it(...args));
+}
diff --git a/core/src/serializer/replacer.ts b/core/src/serializer/replacer.ts
new file mode 100644
index 0000000..5ea5dd8
--- /dev/null
+++ b/core/src/serializer/replacer.ts
@@ -0,0 +1,10 @@
+import { type Predicate } from "./predicate";
+
+export type Replacer = (value: T) => T;
+
+export function createReplacer(from: Predicate, to: T): Replacer {
+ return (it: T) => {
+ if (from(it)) return to;
+ return it;
+ };
+}
diff --git a/src/serializer/versioned.ts b/core/src/serializer/versioned.ts
similarity index 84%
rename from src/serializer/versioned.ts
rename to core/src/serializer/versioned.ts
index 9c60973..d1b060c 100644
--- a/src/serializer/versioned.ts
+++ b/core/src/serializer/versioned.ts
@@ -1,13 +1,13 @@
-import { AbstractSerializer, type Serializer } from ".";
-import type { Class } from "../common/class";
-import type { InputOutput } from "../common/inputOutput";
-import { IllegalShapeError, transformErrors } from "../error";
-import type RegistryLookup from "../loader/registry";
-import { type SemVerInput } from "../packFormat";
+import { type Class } from "../common/class";
+import { IllegalShapeError, transformErrors } from "../common/error";
+import { type SemVerInput } from "../common/packFormat";
+import { type RegistryLookup } from "../registry/lookup";
+import { type Registered } from "../registry/registered";
+import { AbstractSerializer, type Serializer } from "./abstract";
import { selectSerializerModule, type SerializerModule } from "./module";
export abstract class VersionedSerializer<
- Out extends InputOutput,
+ Out extends Registered,
S extends Serializer,
> extends AbstractSerializer {
private readonly serializer: SerializerModule;
diff --git a/src/serializer/wrapped.ts b/core/src/serializer/wrapped.ts
similarity index 89%
rename from src/serializer/wrapped.ts
rename to core/src/serializer/wrapped.ts
index 04ebfaa..4b59f36 100644
--- a/src/serializer/wrapped.ts
+++ b/core/src/serializer/wrapped.ts
@@ -1,5 +1,5 @@
-import { AbstractSerializer, type Serializer } from ".";
-import type { SerializerModule } from "./module";
+import { AbstractSerializer, type Serializer } from "./abstract";
+import { type SerializerModule } from "./module";
export class WrapperSerializer<
Out,
diff --git a/core/src/testing/index.ts b/core/src/testing/index.ts
new file mode 100644
index 0000000..366adf0
--- /dev/null
+++ b/core/src/testing/index.ts
@@ -0,0 +1 @@
+export * from "./mocks";
diff --git a/core/src/testing/mocks.ts b/core/src/testing/mocks.ts
new file mode 100644
index 0000000..9e0b33a
--- /dev/null
+++ b/core/src/testing/mocks.ts
@@ -0,0 +1,21 @@
+import { mock } from "bun:test";
+import { type RegistryProvider } from "../registry/abstract";
+import { type RegistryLookup } from "../registry/lookup";
+
+export function mockRegistryLookup() {
+ return {
+ addCustom: mock(),
+ isKnown: mock(),
+ keys: mock(),
+ registries: mock(),
+ validateEntry: mock(),
+ } satisfies RegistryLookup;
+}
+
+export function mockRegistryProvider() {
+ return {
+ forEach: mock(),
+ forEachAsync: mock(),
+ get: mock(),
+ } satisfies RegistryProvider;
+}
diff --git a/test/id.test.ts b/core/test/id.test.ts
similarity index 89%
rename from test/id.test.ts
rename to core/test/id.test.ts
index ff02a0d..bed939d 100644
--- a/test/id.test.ts
+++ b/core/test/id.test.ts
@@ -1,8 +1,7 @@
import { expect, it } from "bun:test";
-import { resolveIDTest } from "../src/common/filters.js";
-import type { Id, NormalizedId } from "../src/common/id.js";
-import { createId, encodeId } from "../src/common/id.js";
-import Registry from "../src/common/registry.js";
+import { type Id, type NormalizedId } from "../src";
+import { Registry, createId, encodeId } from "../src";
+import { resolveIdTest } from "../src/serializer";
it("parses id from string", () => {
expect(createId("minecraft:stone")).toMatchObject({
@@ -59,7 +58,7 @@ it("encodes id correctly", () => {
});
it("correctly tests id using string", () => {
- const predicate = resolveIDTest("minecraft:stone");
+ const predicate = resolveIdTest("minecraft:stone");
expect(predicate({ namespace: "minecraft", path: "stone" })).toBeTruthy();
expect(predicate("minecraft:stone")).toBeTruthy();
@@ -70,7 +69,7 @@ it("correctly tests id using string", () => {
});
it("correctly tests id using regex", () => {
- const predicate = resolveIDTest(/.+:oak_.+/);
+ const predicate = resolveIdTest(/.+:oak_.+/);
expect(
predicate({ namespace: "minecraft", path: "oak_planks" }),
@@ -83,7 +82,7 @@ it("correctly tests id using regex", () => {
});
it("correctly tests id using predicate", () => {
- const predicate = resolveIDTest((it) => it.includes("one"));
+ const predicate = resolveIdTest((it) => it.includes("one"));
expect(predicate({ namespace: "minecraft", path: "stone" })).toBeTruthy();
expect(predicate("minecraft:bone_block")).toBeTruthy();
diff --git a/test/semVer.test.ts b/core/test/semVer.test.ts
similarity index 98%
rename from test/semVer.test.ts
rename to core/test/semVer.test.ts
index 8185c00..b2f92c3 100644
--- a/test/semVer.test.ts
+++ b/core/test/semVer.test.ts
@@ -4,7 +4,7 @@ import {
isAtLeastVersion,
packFormatOf,
parseSemVer,
-} from "../src/index.js";
+} from "../src";
describe("semantic versions", () => {
it("parses semantic versions correctly", async () => {
diff --git a/eslint.config.js b/eslint.config.js
deleted file mode 100644
index 4e04b12..0000000
--- a/eslint.config.js
+++ /dev/null
@@ -1,28 +0,0 @@
-//@ts-check
-import js from "@eslint/js";
-import { defineConfig } from "eslint/config";
-import ts from "typescript-eslint";
-
-export default defineConfig([
- js.configs.recommended,
- ts.configs.recommended,
- {
- rules: {
- "no-console": "error",
- "@typescript-eslint/consistent-type-imports": "error",
- "@typescript-eslint/consistent-type-exports": "error",
- "@typescript-eslint/no-unused-vars": [
- "error",
- {
- argsIgnorePattern: "^_",
- },
- ],
- },
- languageOptions: {
- parserOptions: {
- tsconfigRootDir: import.meta.dirname,
- projectService: true,
- },
- },
- },
-]);
diff --git a/lib/package.json b/lib/package.json
new file mode 100644
index 0000000..a1721c2
--- /dev/null
+++ b/lib/package.json
@@ -0,0 +1,86 @@
+{
+ "name": "@adeficior/data-modifier",
+ "version": "1.0.0",
+ "description": "Data Modifier",
+ "bin": {
+ "datamod": "dist/cli.js"
+ },
+ "exports": {
+ ".": "./dist/index.js",
+ "./parser": "./dist/parser/index.js",
+ "./parser/botania": "./dist/parser/botania.js",
+ "./parser/create": "./dist/parser/create.js",
+ "./parser/thermal": "./dist/parser/thermal.js",
+ "./parser/farmersdelight": "./dist/parser/farmersdelight.js",
+ "./parser/adAstra": "./dist/parser/adAstra.js",
+ "./parser/roots": "./dist/parser/roots.js",
+ "./ids": "./dist/schema/ids.js",
+ "./cli": "./dist/cli/index.js"
+ },
+ "typesVersions": {
+ "*": {
+ "parser": [
+ "./dist/parser/index.d.ts"
+ ],
+ "parser/botania": [
+ "./dist/parser/botania.d.ts"
+ ],
+ "parser/create": [
+ "./dist/parser/create.d.ts"
+ ],
+ "parser/thermal": [
+ "./dist/parser/thermal.d.ts"
+ ],
+ "parser/farmersdelight": [
+ "./dist/parser/farmersdelight.d.ts"
+ ],
+ "parser/adAstra": [
+ "./dist/parser/adAstra.d.ts"
+ ],
+ "parser/roots": [
+ "./dist/parser/roots.d.ts"
+ ],
+ "ids": [
+ "./dist/schema/ids.d.ts"
+ ],
+ "cli": [
+ "./dist/cli/ids.d.ts"
+ ]
+ }
+ },
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "dev": "bun run build --watch",
+ "check": "tsc --noEmit",
+ "cli": "bun run build && node dist/cli/index.js",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:"
+ },
+ "dependencies": {
+ "@adeficior/resource-merger": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-loot": "workspace:*"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "lodash-es": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:"
+ }
+}
\ No newline at end of file
diff --git a/lib/src/builtinModules.ts b/lib/src/builtinModules.ts
new file mode 100644
index 0000000..483def9
--- /dev/null
+++ b/lib/src/builtinModules.ts
@@ -0,0 +1,21 @@
+import {
+ module as core,
+ type CoreModuleOptions,
+} from "@adeficior/data-modifier-core";
+import ingredients from "@adeficior/data-modifier-ingredients";
+import loot from "@adeficior/data-modifier-loot";
+import recipes from "@adeficior/data-modifier-recipes";
+import tags from "@adeficior/data-modifier-tags";
+import { type DataModifierFactory } from "./instance";
+
+export function installBuiltinModules(
+ coreOptions: CoreModuleOptions,
+): DataModifierFactory {
+ return (builder) => {
+ builder.install(tags);
+ builder.install(ingredients);
+ builder.install(recipes);
+ builder.install(loot);
+ builder.install(core, coreOptions);
+ };
+}
diff --git a/lib/src/cli.ts b/lib/src/cli.ts
new file mode 100644
index 0000000..98c5312
--- /dev/null
+++ b/lib/src/cli.ts
@@ -0,0 +1,7 @@
+// TODO implement
+
+import { createLogger } from "@adeficior/pack-resolver";
+
+const logger = createLogger();
+
+logger.warn("not implemented yet");
diff --git a/src/emit/blacklist.ts b/lib/src/emit/blacklist.ts
similarity index 68%
rename from src/emit/blacklist.ts
rename to lib/src/emit/blacklist.ts
index 4145a57..11f0e0e 100644
--- a/src/emit/blacklist.ts
+++ b/lib/src/emit/blacklist.ts
@@ -1,20 +1,32 @@
-import type { InferIds, RegistryId } from "@adeficior/data-modifier/generated";
-import type { BaseContext, DataConsumer } from "@adeficior/pack-resolver";
-import { arrayOrSelf, simpleResolver } from "@adeficior/pack-resolver";
+import {
+ encodeId,
+ toJson,
+ type ClearableEmitter,
+ type LoaderContext,
+ type NormalizedId,
+ type RegistryLookup,
+} from "@adeficior/data-modifier-core";
+import {
+ ItemIngredient,
+ type IngredientFilter,
+ type IngredientSerializer,
+ type Predicates,
+} from "@adeficior/data-modifier-ingredients";
+import {
+ type InferIds,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import {
+ arrayOrSelf,
+ simpleResolver,
+ type DataConsumer,
+} from "@adeficior/pack-resolver";
import { uniq } from "lodash-es";
-import type { NormalizedId } from "../common/id.js";
-import { encodeId } from "../common/id.js";
-import type { IngredientFilter } from "../common/ingredient/filter.js";
-import createIngredientPredicate from "../common/ingredient/filter.js";
-import { ItemIngredient } from "../common/ingredient/index.js";
-import type { PackContext } from "../loader/context.js";
-import { toJson } from "../textHelper.js";
-import type { ClearableEmitter } from "./index.js";
export type HideMode = "jei" | "polytone";
-export interface BlacklistOptions {
+export type BlacklistOptions = {
hideFrom?: HideMode | HideMode[];
-}
+};
export interface BlacklistRules {
hide(...inputs: IngredientFilter[]): void;
@@ -26,14 +38,14 @@ export interface BlacklistRules {
type RegistryIdInput = InferIds | RegExp;
-export default class BlacklistEmitter
- implements BlacklistRules, ClearableEmitter
-{
+export class BlacklistEmitter implements BlacklistRules, ClearableEmitter {
private hidden: NormalizedId[] = [];
private readonly hideModes: HideMode[];
constructor(
- private readonly context: PackContext,
+ private readonly registries: RegistryLookup,
+ private readonly predicates: Predicates,
+ private readonly ingredientSerializer: IngredientSerializer,
options: BlacklistOptions,
) {
this.hideModes = arrayOrSelf(options.hideFrom);
@@ -49,10 +61,10 @@ export default class BlacklistEmitter
const ids = entries
.flatMap((entry) => {
if (typeof entry === "string") {
- this.context.lookup.validateEntry(type, entry);
+ this.registries.validateEntry(type, entry);
return [entry];
} else {
- const keys = this.context.lookup.keys(type);
+ const keys = this.registries.keys(type);
if (!keys)
throw new Error(
`cannot hide using regex/predicates, registry ${encodeId(
@@ -68,13 +80,13 @@ export default class BlacklistEmitter
}
private filterItemIds(test: IngredientFilter) {
- const keys = this.context.lookup.keys("minecraft:item");
+ const keys = this.registries.keys("minecraft:item");
if (!keys)
throw new Error(
"you can only use regex/predicates to blacklist items if a registry dump is loaded",
);
- const predicate = createIngredientPredicate(test, this.context);
+ const predicate = this.predicates.ingredient(test);
return [...keys.keys()].filter((it) => predicate(new ItemIngredient(it)));
}
@@ -84,12 +96,12 @@ export default class BlacklistEmitter
return this.filterItemIds(input);
}
- const ingredient = this.context.ingredients.deserialize(input);
+ const ingredient = this.ingredientSerializer.deserialize(input);
return Object.values(ingredient.ids()).flat();
}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return simpleResolver(async (acceptor) => {
const hiddenIds = uniq(this.hidden).sort();
if (hiddenIds.length === 0) return;
@@ -106,7 +118,7 @@ export default class BlacklistEmitter
private async emitJei(
acceptor: DataConsumer,
hiddenIds: NormalizedId[],
- context: BaseContext,
+ context: LoaderContext,
) {
const content = hiddenIds.join("\n");
const path = "jei/blacklist.cfg";
@@ -116,9 +128,9 @@ export default class BlacklistEmitter
private async emitPolytone(
acceptor: DataConsumer,
hiddenIds: NormalizedId[],
- context: BaseContext,
+ context: LoaderContext,
) {
- const tabs = this.context.lookup.keys("minecraft:creative_mode_tab");
+ const tabs = this.registries.keys("minecraft:creative_mode_tab");
if (!tabs)
throw new Error(
@@ -126,7 +138,7 @@ export default class BlacklistEmitter
);
const content = toJson({
- targets: [...tabs.values()],
+ targets: [...tabs.values()].toSorted(),
removals: [
{
type: "items_match",
diff --git a/src/emit/packMetadata.ts b/lib/src/emit/packMetadata.ts
similarity index 90%
rename from src/emit/packMetadata.ts
rename to lib/src/emit/packMetadata.ts
index e9fb63b..5d8e719 100644
--- a/src/emit/packMetadata.ts
+++ b/lib/src/emit/packMetadata.ts
@@ -1,11 +1,14 @@
+import {
+ parseSemVer,
+ toJson,
+ type SemVerInput,
+} from "@adeficior/data-modifier-core";
import {
combineResolvers,
simpleResolver,
type Logger,
type Resolver,
} from "@adeficior/pack-resolver";
-import { parseSemVer, type SemVerInput } from "../packFormat";
-import { toJson } from "../textHelper";
export type PackMetadata = {
pack: {
diff --git a/src/emit/polytoneTabs.ts b/lib/src/emit/polytoneTabs.ts
similarity index 90%
rename from src/emit/polytoneTabs.ts
rename to lib/src/emit/polytoneTabs.ts
index 382da49..6fdc239 100644
--- a/src/emit/polytoneTabs.ts
+++ b/lib/src/emit/polytoneTabs.ts
@@ -1,18 +1,21 @@
-import type {
- CreativeModeTabId,
- ItemId,
-} from "@adeficior/data-modifier/generated";
import {
- arrayOrSelf,
- combineResolvers,
- type BaseContext,
-} from "@adeficior/pack-resolver";
+ type ClearableEmitter,
+ type IdInput,
+ type LoaderContext,
+ type NormalizedId,
+ type RegistryLookup,
+} from "@adeficior/data-modifier-core";
+import {
+ createId,
+ CustomEmitter,
+ encodeId,
+} from "@adeficior/data-modifier-core";
+import {
+ type CreativeModeTabId,
+ type ItemId,
+} from "@adeficior/data-modifier/generated";
+import { arrayOrSelf, combineResolvers } from "@adeficior/pack-resolver";
import { difference, uniq } from "lodash-es";
-import type { IdInput, NormalizedId } from "../common/id.js";
-import { createId, encodeId } from "../common/id.js";
-import type RegistryLookup from "../loader/registry/index.js";
-import CustomEmitter from "./custom.js";
-import type { ClearableEmitter } from "./index.js";
interface TabModifications {
icon?: IdInput;
@@ -130,9 +133,7 @@ function translateOptions(options: AddOptions = {}): Partial {
}
}
-export default class PolytoneTabsEmitter
- implements PolytoneTabs, ClearableEmitter
-{
+export class PolytoneTabsEmitter implements PolytoneTabs, ClearableEmitter {
private readonly entries = new CustomEmitter(
(id) =>
`assets/${id.namespace}/polytone/creative_tab_modifiers/${id.path}.json`,
@@ -150,7 +151,7 @@ export default class PolytoneTabsEmitter
this.tabs.clear();
}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return combineResolvers(
[this.entries.resolver(context), this.tabs.resolver(context)],
{
diff --git a/src/emit/recipeGraph.ts b/lib/src/emit/recipeGraph.ts
similarity index 91%
rename from src/emit/recipeGraph.ts
rename to lib/src/emit/recipeGraph.ts
index cd50de9..e1c31b4 100644
--- a/src/emit/recipeGraph.ts
+++ b/lib/src/emit/recipeGraph.ts
@@ -1,25 +1,32 @@
-import { simpleResolver, type BaseContext } from "@adeficior/pack-resolver";
-import type { ClearableEmitter } from ".";
-import {
- resolveIDTest,
- type CommonFilter,
- type Predicate,
-} from "../common/filters";
import {
createId,
encodeId,
prefix,
+ Registry,
suffix,
+ toJson,
+ type ClearableEmitter,
type IdInput,
+ type LoaderContext,
type NormalizedId,
-} from "../common/id";
-import type { InputOutput } from "../common/inputOutput";
-import Registry from "../common/registry";
-import type { JsonLoader } from "../loader";
-import { type TagRegistryHolder } from "../loader/tags";
-import type { RecipeHolder } from "../parser";
-import type { ItemId, RecipeSerializerId, RegistryId } from "../stubTypes";
-import { toJson } from "../textHelper";
+ type Registered,
+} from "@adeficior/data-modifier-core";
+import {
+ resolveIdTest,
+ type CommonFilter,
+ type Predicate,
+} from "@adeficior/data-modifier-core/serializer";
+import {
+ type RecipeHolder,
+ type RecipeLoaderAccessor,
+} from "@adeficior/data-modifier-recipes";
+import { type TagRegistryHolder } from "@adeficior/data-modifier-tags";
+import {
+ type ItemId,
+ type RecipeSerializerId,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import { simpleResolver } from "@adeficior/pack-resolver";
type Node = {
id: NormalizedId;
@@ -37,7 +44,7 @@ type Edge = {
label?: string;
};
-export interface RecipeGraphOptions {
+export type RecipeGraphOptions = {
// output: "visjs"
resolveTags?: boolean;
brokenIcon?: string;
@@ -45,7 +52,7 @@ export interface RecipeGraphOptions {
id: NormalizedId,
registry: NormalizedId,
) => string;
-}
+};
const defaultOptions: Required = {
resolveTags: true,
@@ -81,7 +88,7 @@ export class RecipeGraphEmitter
private readonly options: Required;
constructor(
- private readonly recipes: JsonLoader,
+ private readonly recipes: RecipeLoaderAccessor,
private readonly tags: TagRegistryHolder,
options: RecipeGraphOptions = {},
) {
@@ -124,7 +131,7 @@ export class RecipeGraphEmitter
label?: string,
) {
this.representations.push({
- test: resolveIDTest(type),
+ test: resolveIdTest(type),
icon: encodeId(icon),
label,
});
@@ -144,7 +151,7 @@ export class RecipeGraphEmitter
this.representations = [];
}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return simpleResolver(async (acceptor) => {
const builder = new GraphBuilder(
this.tags,
@@ -174,7 +181,7 @@ class GraphBuilder {
private readonly options: Required,
) {}
- private addIONode(recipeId: NormalizedId, from: InputOutput, input: boolean) {
+ private addIONode(recipeId: NormalizedId, from: Registered, input: boolean) {
const entry = Object.entries(from.ids()).find((it) => it[1].length > 0);
if (!entry) return;
diff --git a/lib/src/index.ts b/lib/src/index.ts
new file mode 100644
index 0000000..1f324a1
--- /dev/null
+++ b/lib/src/index.ts
@@ -0,0 +1,5 @@
+export * from "./instance";
+
+// TODO re-export bundled modules
+// and some (!) core stuff like ID
+// need to move core things into sub-path exports
diff --git a/lib/src/installTarget.ts b/lib/src/installTarget.ts
new file mode 100644
index 0000000..bde085d
--- /dev/null
+++ b/lib/src/installTarget.ts
@@ -0,0 +1,157 @@
+import {
+ CombinedEmitters,
+ type AfterSetupEvent,
+ type Container,
+ type DependencyType,
+ type EventHandler,
+ type Loader,
+ type ModuleConfig,
+ type ModuleSetupOptions,
+ type ModuleType,
+ type ModuleTypes,
+ type SetupEvent,
+} from "@adeficior/data-modifier-core";
+import { uniqBy } from "lodash-es";
+
+export type Installable = {
+ name: string;
+ dependencies: Record;
+};
+
+function directDependencies(
+ module: Installable,
+ others: Installable[],
+): ReadonlyArray {
+ const direct = Object.keys(module.dependencies);
+
+ if (module.name && direct.includes(module.name)) {
+ throw new Error(`module ${module.name} cannot depend on itself`);
+ }
+
+ return direct.map((name) => {
+ const match = others.find((it) => it.name === name);
+ if (match) return match;
+ // TODO skip optional
+ throw new Error(`unable to find module ${name} required by ${module.name}`);
+ });
+}
+
+function recursiveDependencies(
+ module: Installable,
+ others: Installable[],
+ path: string[],
+): ReadonlyArray {
+ const direct = directDependencies(module, others);
+ // TODO check path for recursive dependency cycles
+ return uniqBy(
+ [
+ ...direct,
+ ...direct.flatMap((it) =>
+ recursiveDependencies(it, others, [...path, it.name]),
+ ),
+ ],
+ (it) => it.name,
+ );
+}
+
+export function resolveDependencies(
+ module: Installable,
+ others: Installable[],
+) {
+ return recursiveDependencies(module, others, [module.name]);
+}
+
+export function sortModules(modules: T[]): T[] {
+ const withDependencies = modules.map((module) => ({
+ module,
+ dependencies: resolveDependencies(module, modules).map((it) => it.name),
+ }));
+ return withDependencies
+ .toSorted((a, b) => {
+ if (a.dependencies.includes(b.module.name)) return 1;
+ if (b.dependencies.includes(a.module.name)) return -1;
+ return a.dependencies.length - b.dependencies.length;
+ })
+ .map((it) => it.module);
+}
+
+class EventBus {
+ private readonly handlers = new Map>>();
+
+ subscribe(type: string, handler: EventHandler) {
+ const set = this.handlers.get(type);
+ if (set) set.add(handler);
+ else this.handlers.set(type, new Set([handler]));
+ }
+
+ async dispatch(type: string, event: unknown) {
+ const handlers = [...(this.handlers.get(type) ?? [])];
+ await Promise.all(handlers.map((handle) => handle(event)));
+ }
+}
+
+export class InstallTarget implements Container {
+ private frozen = false;
+ private readonly services = new Map();
+ protected readonly emitters = new CombinedEmitters();
+ protected readonly loaders: Record = {};
+
+ constructor(protected readonly options: ModuleSetupOptions) {}
+
+ get(key: string): T {
+ const instance = this.getOrNull(key);
+ if (instance) return instance;
+ throw new Error(`no instance of type '${key}' registered`);
+ }
+
+ getOrNull(key: string): T | null {
+ return this.services.get(key) as T | null;
+ }
+
+ async install(
+ modules: ModuleConfig[],
+ options: ModuleType,
+ ) {
+ if (this.frozen) {
+ throw new Error(
+ "installation cannot happen after modifier has been created",
+ );
+ }
+
+ const bus = new EventBus();
+
+ const sorted = sortModules(modules);
+
+ for (const module of sorted) {
+ const combinedOptions = { ...this.options, ...options };
+
+ const event: SetupEvent = {
+ hook: (type, handler) =>
+ bus.subscribe(type, handler as EventHandler),
+ service: (key, factory) => {
+ const instance = factory(this);
+ this.services.set(key, instance);
+ return () => instance;
+ },
+ emitter: (key, factory) => {
+ const instance = event.service(`emitter:${key}`, factory);
+ this.emitters.add(instance());
+ return instance;
+ },
+ loader: (key, factory, pattern) => {
+ const instance = event.service(`loader:${key}`, factory);
+ this.loaders[pattern] = instance();
+ return instance;
+ },
+ options: combinedOptions,
+ };
+
+ await module.setup(event);
+ }
+
+ this.frozen = true;
+ bus.dispatch("after:setup", {
+ callHook: (...args) => bus.dispatch(...args),
+ } satisfies AfterSetupEvent);
+ }
+}
diff --git a/lib/src/instance.ts b/lib/src/instance.ts
new file mode 100644
index 0000000..ca7d520
--- /dev/null
+++ b/lib/src/instance.ts
@@ -0,0 +1,125 @@
+import {
+ type Container,
+ type DataModifierOptions,
+ type LoaderContext,
+ type ModuleConfig,
+ type ModuleSetupOptions,
+ type ModuleType,
+ type ModuleTypes,
+} from "@adeficior/data-modifier-core";
+import {
+ combineResolvers,
+ distributedAcceptor,
+ filterAcceptor,
+ type Acceptor,
+ type Logger,
+ type Resolver,
+} from "@adeficior/pack-resolver";
+import { createMergingAcceptor } from "@adeficior/resource-merger";
+import { installBuiltinModules } from "./builtinModules";
+import { overwritePackMetadata } from "./emit/packMetadata";
+import { InstallTarget } from "./installTarget";
+
+export interface DataModifier extends Container {
+ loadFromMultiple(resolvers: Resolver[]): Promise;
+ loadFrom(resolvers: Resolver): Promise;
+ emit(to: Acceptor, options?: LoaderEmitOptions): Promise;
+ reset(): void;
+}
+
+export type LoaderEmitOptions = {
+ description?: string;
+};
+
+class DataModifierImpl extends InstallTarget implements DataModifier {
+ constructor(options: ModuleSetupOptions) {
+ super(options);
+ }
+
+ loadFromMultiple(resolvers: Resolver[]) {
+ const combined = combineResolvers(resolvers);
+ return this.loadFrom(combined);
+ }
+
+ async loadFrom(resolver: Resolver) {
+ const acceptor: Acceptor = filterAcceptor(
+ createMergingAcceptor(distributedAcceptor(this.loaders)),
+ {
+ include: ["assets/**/*.json", "data/**/*.json"],
+ },
+ );
+
+ await resolver.extract(acceptor);
+ }
+
+ private resolver({
+ description,
+ ...context
+ }: LoaderContext & LoaderEmitOptions) {
+ const emittersResolver = this.emitters.resolver(context);
+
+ return overwritePackMetadata(emittersResolver, {
+ ...context,
+ description,
+ packFormat: this.options.packFormat,
+ });
+ }
+
+ async emit(to: Acceptor, options: LoaderEmitOptions = {}) {
+ const logger = this.get("logger");
+ await this.resolver({ ...options, logger }).extract(to);
+ }
+
+ async run(from: Resolver, to: Acceptor) {
+ await this.loadFrom(from);
+ await this.emit(to);
+ }
+
+ reset() {
+ this.emitters.clear();
+ }
+}
+
+export type DataModifierBuilder = {
+ install(
+ module: ModuleConfig,
+ options?: ModuleType,
+ ): void;
+};
+
+export type DataModifierFactory = (builder: DataModifierBuilder) => void;
+
+type GatheredModule = {
+ module: ModuleConfig;
+ options?: ModuleType;
+};
+
+export async function createDataModifier(
+ { packFormat, ...coreOptions }: DataModifierOptions,
+ factory: DataModifierFactory = () => {},
+): Promise {
+ const moduleSetupOptions: ModuleSetupOptions = { packFormat };
+ const instance = new DataModifierImpl(moduleSetupOptions);
+
+ const gatheredModules: GatheredModule[] = [];
+
+ const builder: DataModifierBuilder = {
+ install: (module, options) => {
+ gatheredModules.push({ module, options } as GatheredModule);
+ },
+ };
+
+ factory(builder);
+ installBuiltinModules(coreOptions)(builder);
+
+ const modules = gatheredModules.map((it) => it.module);
+
+ // TODO only pass options to module that belong to it
+ const moduleOptions = gatheredModules.reduce(
+ (a, b) => ({ ...a, ...b.options }),
+ {},
+ );
+
+ await instance.install(modules, moduleOptions);
+ return instance;
+}
diff --git a/lib/src/module.ts b/lib/src/module.ts
new file mode 100644
index 0000000..cf2a771
--- /dev/null
+++ b/lib/src/module.ts
@@ -0,0 +1,13 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { name } from "../package.json";
+
+export default defineModule({
+ name,
+ dependencies: {
+ // TODO make optional or not needed at all
+ "@adeficior/data-modifier-ingredients": "required",
+ "@adeficior/data-modifier-tags": "required",
+ "@adeficior/data-modifier-recipes": "required",
+ "@adeficior/data-modifier-loot": "required",
+ },
+});
diff --git a/src/parser/recipe/adAstra/conversion.ts b/lib/src/serializer/recipe/adAstra/conversion.ts
similarity index 76%
rename from src/parser/recipe/adAstra/conversion.ts
rename to lib/src/serializer/recipe/adAstra/conversion.ts
index 667b2cf..b9d1713 100644
--- a/src/parser/recipe/adAstra/conversion.ts
+++ b/lib/src/serializer/recipe/adAstra/conversion.ts
@@ -1,14 +1,17 @@
-import { encodeId } from "../../../common/id.js";
+import { encodeId, IllegalShapeError } from "@adeficior/data-modifier-core";
import {
- FluidIngredient,
type Ingredient,
-} from "../../../common/ingredient/index.js";
-import type { Result } from "../../../common/result/index.js";
-import { FluidResult } from "../../../common/result/index.js";
-import { IllegalShapeError } from "../../../error.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type { RecipeModifier, RecipeParseContext } from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
+ type Result,
+ FluidIngredient,
+ FluidResult,
+} from "@adeficior/data-modifier-ingredients";
+import {
+ type RecipeDefinition,
+ type RecipeModifier,
+ type RecipeParseContext,
+ Recipe,
+ RecipeParser,
+} from "@adeficior/data-modifier-recipes";
export type FluidConversionRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/adAstra/hammering.ts b/lib/src/serializer/recipe/adAstra/hammering.ts
similarity index 84%
rename from src/parser/recipe/adAstra/hammering.ts
rename to lib/src/serializer/recipe/adAstra/hammering.ts
index 0ed81c7..e88513d 100644
--- a/src/parser/recipe/adAstra/hammering.ts
+++ b/lib/src/serializer/recipe/adAstra/hammering.ts
@@ -1,7 +1,7 @@
import {
ManyToOneRecipeParser,
type ManyToOneRecipeDefinition,
-} from "../manyToOne.js";
+} from "@adeficior/data-modifier-recipes";
export type HammeringRecipeDefinition = ManyToOneRecipeDefinition;
diff --git a/src/parser/recipe/adAstra/inputOutput.ts b/lib/src/serializer/recipe/adAstra/inputOutput.ts
similarity index 79%
rename from src/parser/recipe/adAstra/inputOutput.ts
rename to lib/src/serializer/recipe/adAstra/inputOutput.ts
index 1606232..74dc10d 100644
--- a/src/parser/recipe/adAstra/inputOutput.ts
+++ b/lib/src/serializer/recipe/adAstra/inputOutput.ts
@@ -1,15 +1,18 @@
-import z from "zod";
-import { encodeId, IdSchema } from "../../../common/id.js";
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import { ItemResult } from "../../../common/result/index.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type { ResultSerializer } from "../../../serializer/results/index.js";
-import RecipeParser, {
+import { encodeId, IdSchema } from "@adeficior/data-modifier-core";
+import {
+ ItemResult,
+ type Ingredient,
+ type ResultSerializer,
+} from "@adeficior/data-modifier-ingredients";
+import {
+ OneToOneRecipe,
Recipe,
+ RecipeParser,
+ type RecipeDefinition,
type RecipeModifier,
type RecipeParseContext,
-} from "../index.js";
-import { OneToOneRecipe } from "../oneToOne.js";
+} from "@adeficior/data-modifier-recipes";
+import * as z from "zod";
// TODO this will also be the new item format, can re-use that
const IdResultSchema = z.object({
diff --git a/src/parser/recipe/adAstra/nasaWorkbench.ts b/lib/src/serializer/recipe/adAstra/nasaWorkbench.ts
similarity index 85%
rename from src/parser/recipe/adAstra/nasaWorkbench.ts
rename to lib/src/serializer/recipe/adAstra/nasaWorkbench.ts
index 4b99829..d34ff21 100644
--- a/src/parser/recipe/adAstra/nasaWorkbench.ts
+++ b/lib/src/serializer/recipe/adAstra/nasaWorkbench.ts
@@ -1,7 +1,7 @@
import {
ManyToOneRecipeParser,
type ManyToOneRecipeDefinition,
-} from "../manyToOne.js";
+} from "@adeficior/data-modifier-recipes";
export type NasaWorkbenchRecipeDefinition = ManyToOneRecipeDefinition;
diff --git a/src/parser/recipe/adAstra/spaceStation.ts b/lib/src/serializer/recipe/adAstra/spaceStation.ts
similarity index 84%
rename from src/parser/recipe/adAstra/spaceStation.ts
rename to lib/src/serializer/recipe/adAstra/spaceStation.ts
index ba47fca..c956f98 100644
--- a/src/parser/recipe/adAstra/spaceStation.ts
+++ b/lib/src/serializer/recipe/adAstra/spaceStation.ts
@@ -1,14 +1,18 @@
-import { omit } from "lodash-es";
-import z from "zod";
+import { IllegalShapeError } from "@adeficior/data-modifier-core";
import {
ItemIngredient,
ItemTagIngredient,
type Ingredient,
-} from "../../../common/ingredient/index.js";
-import { IllegalShapeError } from "../../../error.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type { RecipeModifier, RecipeParseContext } from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
+} from "@adeficior/data-modifier-ingredients";
+import {
+ Recipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
+import { omit } from "lodash-es";
+import * as z from "zod";
const WrappedIngredientSchema = z.object({
ingredient: z.record(z.string(), z.unknown()),
diff --git a/src/parser/recipe/roots/component.ts b/lib/src/serializer/recipe/roots/component.ts
similarity index 80%
rename from src/parser/recipe/roots/component.ts
rename to lib/src/serializer/recipe/roots/component.ts
index ebb5528..d489f8b 100644
--- a/src/parser/recipe/roots/component.ts
+++ b/lib/src/serializer/recipe/roots/component.ts
@@ -1,7 +1,11 @@
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type { RecipeModifier, RecipeParseContext } from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
+import { type Ingredient } from "@adeficior/data-modifier-ingredients";
+import {
+ Recipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
export type RootComponentRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/roots/ritual.ts b/lib/src/serializer/recipe/roots/ritual.ts
similarity index 83%
rename from src/parser/recipe/roots/ritual.ts
rename to lib/src/serializer/recipe/roots/ritual.ts
index fafa10e..9250524 100644
--- a/src/parser/recipe/roots/ritual.ts
+++ b/lib/src/serializer/recipe/roots/ritual.ts
@@ -1,11 +1,14 @@
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { Result } from "../../../common/result/index.js";
-import type { RecipeModifier, RecipeParseContext } from "../index.js";
-import RecipeParser from "../index.js";
+import {
+ type Ingredient,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
import {
ManyToOneRecipe,
+ RecipeParser,
type ManyToOneRecipeDefinition,
-} from "../manyToOne.js";
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
export type RootRitualRecipeDefinition = ManyToOneRecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/sullys/polishing.ts b/lib/src/serializer/recipe/sullys/polishing.ts
similarity index 64%
rename from src/parser/recipe/sullys/polishing.ts
rename to lib/src/serializer/recipe/sullys/polishing.ts
index 1f90593..2010c10 100644
--- a/src/parser/recipe/sullys/polishing.ts
+++ b/lib/src/serializer/recipe/sullys/polishing.ts
@@ -1,5 +1,7 @@
-import type { ManyToOneRecipeDefinition } from "../manyToOne.js";
-import { ManyToOneRecipeParser } from "../manyToOne.js";
+import {
+ ManyToOneRecipeParser,
+ type ManyToOneRecipeDefinition,
+} from "@adeficior/data-modifier-recipes";
export type GrindstonePolishingDefinition = ManyToOneRecipeDefinition &
Readonly<{
diff --git a/test/__snapshots__/jeiBlacklist.test.ts.snap b/lib/test/__snapshots__/jeiBlacklist.test.ts.snap
similarity index 73%
rename from test/__snapshots__/jeiBlacklist.test.ts.snap
rename to lib/test/__snapshots__/jeiBlacklist.test.ts.snap
index 9f7556d..9eea09b 100644
--- a/test/__snapshots__/jeiBlacklist.test.ts.snap
+++ b/lib/test/__snapshots__/jeiBlacklist.test.ts.snap
@@ -1,19 +1,38 @@
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
exports[`blacklist tests generated a jei blacklist config file: jei blacklist config file 1`] = `
-"forge:milk
-minecraft:ice
+"minecraft:ice
+minecraft:lava
minecraft:stone
minecraft:water"
`;
exports[`blacklist tests generated a blacklist using dumped ids: jei blacklist config file using registry dump 1`] = `
-"minecraft:dark_oak_boat
+"create:cut_granite
+create:cut_granite_brick_slab
+create:cut_granite_brick_stairs
+create:cut_granite_brick_wall
+create:cut_granite_bricks
+create:cut_granite_slab
+create:cut_granite_stairs
+create:cut_granite_wall
+create:granite_pillar
+create:layered_granite
+create:polished_cut_granite
+create:polished_cut_granite_slab
+create:polished_cut_granite_stairs
+create:polished_cut_granite_wall
+create:small_granite_brick_slab
+create:small_granite_brick_stairs
+create:small_granite_brick_wall
+create:small_granite_bricks
+minecraft:dark_oak_boat
minecraft:dark_oak_button
minecraft:dark_oak_chest_boat
minecraft:dark_oak_door
minecraft:dark_oak_fence
minecraft:dark_oak_fence_gate
+minecraft:dark_oak_hanging_sign
minecraft:dark_oak_leaves
minecraft:dark_oak_log
minecraft:dark_oak_planks
@@ -34,6 +53,7 @@ minecraft:oak_chest_boat
minecraft:oak_door
minecraft:oak_fence
minecraft:oak_fence_gate
+minecraft:oak_hanging_sign
minecraft:oak_leaves
minecraft:oak_log
minecraft:oak_planks
diff --git a/lib/test/__snapshots__/polytoneBlacklist.test.ts.snap b/lib/test/__snapshots__/polytoneBlacklist.test.ts.snap
new file mode 100644
index 0000000..7887352
--- /dev/null
+++ b/lib/test/__snapshots__/polytoneBlacklist.test.ts.snap
@@ -0,0 +1,43 @@
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
+
+exports[`blacklist tests does not create the jei blacklist config if nothing is hidden: creates a polytone tab modifier 1`] = `
+"{
+ "targets": [
+ "botania:botania",
+ "create:base",
+ "create:palettes",
+ "farmersdelight:farmersdelight",
+ "minecraft:building_blocks",
+ "minecraft:colored_blocks",
+ "minecraft:combat",
+ "minecraft:food_and_drinks",
+ "minecraft:functional_blocks",
+ "minecraft:hotbar",
+ "minecraft:ingredients",
+ "minecraft:inventory",
+ "minecraft:natural_blocks",
+ "minecraft:op_blocks",
+ "minecraft:redstone_blocks",
+ "minecraft:search",
+ "minecraft:spawn_eggs",
+ "minecraft:tools_and_utilities",
+ "thermal:thermal.blocks",
+ "thermal:thermal.devices",
+ "thermal:thermal.foods",
+ "thermal:thermal.items",
+ "thermal:thermal.tools"
+ ],
+ "removals": [
+ {
+ "type": "items_match",
+ "items": [
+ "minecraft:ice",
+ "minecraft:lava",
+ "minecraft:stone",
+ "minecraft:water"
+ ]
+ }
+ ]
+}
+"
+`;
diff --git a/test/__snapshots__/polytoneTabs.test.ts.snap b/lib/test/__snapshots__/polytoneTabs.test.ts.snap
similarity index 100%
rename from test/__snapshots__/polytoneTabs.test.ts.snap
rename to lib/test/__snapshots__/polytoneTabs.test.ts.snap
diff --git a/lib/test/__snapshots__/recipeGraph.test.ts.snap b/lib/test/__snapshots__/recipeGraph.test.ts.snap
new file mode 100644
index 0000000..2208907
--- /dev/null
+++ b/lib/test/__snapshots__/recipeGraph.test.ts.snap
@@ -0,0 +1,120 @@
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
+
+exports[`recipe graph generated nodes & edges: nodes.json 1`] = `
+[
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:recipe/chest",
+ "image": "https://icons.macarena.ceo/icons/minecraft/crafting_bench.png",
+ "registry": "minecraft:recipe_serializer",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:chest",
+ "image": "https://icons.macarena.ceo/icons/minecraft/chest.png",
+ "label": "minecraft:chest",
+ "registry": "minecraft:item",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:recipe/oak_stairs",
+ "image": "https://icons.macarena.ceo/icons/minecraft/crafting_bench.png",
+ "registry": "minecraft:recipe_serializer",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:oak_planks",
+ "image": "https://icons.macarena.ceo/icons/minecraft/oak_planks.png",
+ "label": "minecraft:oak_planks",
+ "registry": "minecraft:item",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:oak_stairs",
+ "image": "https://icons.macarena.ceo/icons/minecraft/oak_stairs.png",
+ "label": "minecraft:oak_stairs",
+ "registry": "minecraft:item",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:recipe/oak_slab",
+ "image": "https://icons.macarena.ceo/icons/minecraft/crafting_bench.png",
+ "registry": "minecraft:recipe_serializer",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:oak_slab",
+ "image": "https://icons.macarena.ceo/icons/minecraft/oak_slab.png",
+ "label": "minecraft:oak_slab",
+ "registry": "minecraft:item",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "minecraft:recipe/oak_planks",
+ "image": "https://icons.macarena.ceo/icons/minecraft/crafting_bench.png",
+ "registry": "minecraft:recipe_serializer",
+ "shape": "image",
+ },
+ {
+ "brokenImage": "https://icons.macarena.ceo/icons/minecraft/bedrock.png",
+ "id": "#minecraft:oak_logs",
+ "image": "https://icons.macarena.ceo/icons/minecraft/oak_log.png",
+ "label": "#minecraft:oak_logs",
+ "registry": "minecraft:item",
+ "shape": "image",
+ },
+]
+`;
+
+exports[`recipe graph generated nodes & edges: edges.json 1`] = `
+[
+ {
+ "arrows": "to",
+ "from": "minecraft:recipe/chest",
+ "to": "minecraft:chest",
+ },
+ {
+ "arrows": "to",
+ "from": "minecraft:oak_planks",
+ "to": "minecraft:recipe/oak_stairs",
+ },
+ {
+ "arrows": "to",
+ "from": "minecraft:recipe/oak_stairs",
+ "to": "minecraft:oak_stairs",
+ },
+ {
+ "arrows": "to",
+ "from": "minecraft:oak_planks",
+ "to": "minecraft:recipe/oak_slab",
+ },
+ {
+ "arrows": "to",
+ "from": "minecraft:recipe/oak_slab",
+ "to": "minecraft:oak_slab",
+ },
+ {
+ "arrows": "to",
+ "from": "#minecraft:oak_logs",
+ "to": "minecraft:recipe/oak_planks",
+ },
+ {
+ "arrows": "to",
+ "from": "minecraft:recipe/oak_planks",
+ "to": "minecraft:oak_planks",
+ },
+ {
+ "arrows": "to",
+ "from": "minecraft:oak_planks",
+ "label": "uses any #minecraft:planks",
+ "to": "minecraft:recipe/chest",
+ },
+]
+`;
diff --git a/test/__snapshots__/registryDump.test.ts.snap b/lib/test/__snapshots__/registryDump.test.ts.snap
similarity index 100%
rename from test/__snapshots__/registryDump.test.ts.snap
rename to lib/test/__snapshots__/registryDump.test.ts.snap
diff --git a/lib/test/dependencies.test.ts b/lib/test/dependencies.test.ts
new file mode 100644
index 0000000..d4ad1f5
--- /dev/null
+++ b/lib/test/dependencies.test.ts
@@ -0,0 +1,37 @@
+import { describe, expect, it } from "bun:test";
+import {
+ resolveDependencies,
+ sortModules,
+ type Installable,
+} from "../src/installTarget";
+
+const valid: Installable[] = [
+ { name: "c", dependencies: { a: "required", b: "optional" } },
+ { name: "a", dependencies: {} },
+ { name: "e", dependencies: { b: "required" } },
+ { name: "b", dependencies: { a: "required" } },
+ { name: "d", dependencies: { g: "required", c: "optional" } },
+ { name: "f", dependencies: {} },
+ { name: "g", dependencies: {} },
+];
+
+describe("dependency resolution", () => {
+ it("sorts modules correctly", () => {
+ const sorted = sortModules(valid);
+
+ const names = sorted.map((it) => it.name);
+
+ expect(names).toMatchObject(["a", "f", "g", "b", "c", "e", "d"]);
+ });
+
+ it("resolves dependencies correctly", () => {
+ const modules = resolveDependencies(
+ { name: "test", dependencies: { f: "required", d: "optional" } },
+ valid,
+ );
+
+ const names = modules.map((it) => it.name).toSorted();
+
+ expect(names).toMatchObject(["a", "b", "c", "d", "f", "g"]);
+ });
+});
diff --git a/lib/test/jeiBlacklist.test.ts b/lib/test/jeiBlacklist.test.ts
new file mode 100644
index 0000000..1e694dc
--- /dev/null
+++ b/lib/test/jeiBlacklist.test.ts
@@ -0,0 +1,128 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { type Ingredient } from "@adeficior/data-modifier-ingredients";
+import {
+ BlockIngredient,
+ FluidIngredient,
+ ItemIngredient,
+} from "@adeficior/data-modifier-ingredients";
+import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { createDumpResolver } from "@adeficior/testing";
+import { describe, expect, it } from "bun:test";
+import {
+ BlacklistEmitter,
+ type BlacklistOptions,
+ type BlacklistRules,
+} from "../src/emit/blacklist";
+import { setupInstance } from "./util/setup";
+
+const module = defineModule<{
+ options: BlacklistOptions;
+ emitters: {
+ blacklist: BlacklistRules;
+ };
+}>({
+ name: "internal",
+ dependencies: {
+ "@adeficior/data-modifier-ingredients": "required",
+ },
+ setup: (pack) => {
+ pack.emitter(
+ "blacklist",
+ (container) =>
+ new BlacklistEmitter(
+ container.get("registries"),
+ container.get("predicates"),
+ container.get("serializer:ingredients"),
+ pack.options,
+ ),
+ );
+ },
+});
+
+const version = "1.20.1";
+const instance = await setupInstance(
+ version,
+ {
+ load: false,
+ dump: await createDumpResolver(version),
+ },
+ (modules) => {
+ modules.install(module, { hideFrom: ["jei"] });
+ },
+);
+
+const blacklist = instance.get("emitter:blacklist");
+
+describe("blacklist tests", () => {
+ it("generated a jei blacklist config file", async () => {
+ const acceptor = createTestAcceptor();
+
+ blacklist.hide("minecraft:stone");
+ blacklist.hide(new FluidIngredient("water"));
+ blacklist.hide(new BlockIngredient("water"));
+ blacklist.hide([
+ new ItemIngredient("ice"),
+ new FluidIngredient("minecraft:lava"),
+ ]);
+
+ await instance.emit(acceptor);
+
+ expect(acceptor.at("jei/blacklist.cfg")).toMatchSnapshot(
+ "jei blacklist config file",
+ );
+ });
+
+ it("does not create the jei blacklist config if nothing is hidden", async () => {
+ const acceptor = createTestAcceptor();
+
+ await instance.emit(acceptor);
+
+ expect(acceptor.at("jei/blacklist.cfg")).toBeNull();
+ });
+
+ it("generated a blacklist using dumped ids", async () => {
+ const acceptor = createTestAcceptor();
+
+ blacklist.hide(/minecraft:.*oak.*/);
+ blacklist.hide(
+ (it: Ingredient) =>
+ it instanceof ItemIngredient && it.id.includes("granite"),
+ );
+
+ await instance.emit(acceptor);
+
+ expect(acceptor.at("jei/blacklist.cfg")).toMatchSnapshot(
+ "jei blacklist config file using registry dump",
+ );
+ });
+
+ // move to seperate test after extracting into module
+ it.skip("fails when trying to use a regex/predicate without a registry dump", async () => {
+ const acceptor = createTestAcceptor();
+
+ const message =
+ "you can only use regex/predicates to blacklist items if a registry dump is loaded";
+ expect(() => blacklist.hide(/whatever/)).toThrow(message);
+ expect(() => blacklist.hide(() => true)).toThrow(message);
+
+ await instance.emit(acceptor);
+
+ expect(acceptor.at("jei/blacklist.cfg")).toBeNull();
+ });
+
+ it("validates custom registry ids", async () => {
+ const acceptor = createTestAcceptor();
+
+ expect(() => blacklist.hideEntry("example", /whatever/)).toThrow(
+ `cannot hide using regex/predicates, registry minecraft:example not loaded`,
+ );
+ blacklist.hideEntry("minecraft:worldgen/biome", "minecraft:basalt_deltas");
+ blacklist.hideEntry("minecraft:worldgen/biome", /minecraft:.+_forest/);
+
+ await instance.emit(acceptor);
+
+ expect(acceptor.at("jei/blacklist.cfg")).toMatchSnapshot(
+ "jei blacklist config file using biome registry",
+ );
+ });
+});
diff --git a/lib/test/options.test.ts b/lib/test/options.test.ts
new file mode 100644
index 0000000..c6a8ef0
--- /dev/null
+++ b/lib/test/options.test.ts
@@ -0,0 +1,44 @@
+import { defineModule, packFormatOf } from "@adeficior/data-modifier-core";
+import { createTestLogger } from "@adeficior/pack-resolver/testing";
+import { expect, it, mock } from "bun:test";
+import { createDataModifier } from "../src";
+
+it("correctly passed options to installed modules", async () => {
+ const mockA = mock();
+ const moduleA = defineModule<{
+ options: {
+ moduleAKey: string;
+ };
+ }>({
+ name: "a",
+ setup: ({ options }) => {
+ mockA(options.moduleAKey);
+ },
+ });
+
+ const mockB = mock();
+ const moduleB = defineModule<{
+ options: {
+ moduleBKey: string;
+ };
+ }>({
+ name: "b",
+ setup: ({ options }) => {
+ mockB(options.moduleBKey);
+ },
+ });
+
+ await createDataModifier(
+ {
+ packFormat: packFormatOf("1.21.1"),
+ logger: createTestLogger(),
+ },
+ (modules) => {
+ modules.install(moduleA, { moduleAKey: "valueA" });
+ modules.install(moduleB, { moduleBKey: "valueB" });
+ },
+ );
+
+ expect(mockA).toBeCalledWith("valueA");
+ expect(mockB).toBeCalledWith("valueB");
+});
diff --git a/test/packMetadata.test.ts b/lib/test/packMetadata.test.ts
similarity index 100%
rename from test/packMetadata.test.ts
rename to lib/test/packMetadata.test.ts
diff --git a/lib/test/polytoneBlacklist.test.ts b/lib/test/polytoneBlacklist.test.ts
new file mode 100644
index 0000000..b7c90b2
--- /dev/null
+++ b/lib/test/polytoneBlacklist.test.ts
@@ -0,0 +1,85 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import {
+ BlockIngredient,
+ FluidIngredient,
+ ItemIngredient,
+} from "@adeficior/data-modifier-ingredients";
+import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { createDumpResolver } from "@adeficior/testing";
+import { describe, expect, it } from "bun:test";
+import {
+ BlacklistEmitter,
+ type BlacklistOptions,
+ type BlacklistRules,
+} from "../src/emit/blacklist";
+import { setupInstance } from "./util/setup";
+
+const module = defineModule<{
+ options: BlacklistOptions;
+ emitters: {
+ blacklist: BlacklistRules;
+ };
+}>({
+ name: "internal",
+ dependencies: {
+ "@adeficior/data-modifier-ingredients": "required",
+ },
+ setup: (pack) => {
+ pack.emitter(
+ "blacklist",
+ (container) =>
+ new BlacklistEmitter(
+ container.get("registries"),
+ container.get("predicates"),
+ container.get("serializer:ingredients"),
+ pack.options,
+ ),
+ );
+ },
+});
+
+const version = "1.20.1";
+const instance = await setupInstance(
+ version,
+ {
+ load: false,
+ dump: await createDumpResolver(version),
+ },
+ (modules) => {
+ modules.install(module, { hideFrom: ["polytone"] });
+ },
+);
+
+const blacklist = instance.get("emitter:blacklist");
+
+describe("blacklist tests", () => {
+ it("does not generate a jei blacklist config file", async () => {
+ const acceptor = createTestAcceptor();
+
+ blacklist.hide("minecraft:stone");
+
+ await instance.emit(acceptor);
+
+ expect(acceptor.at("jei/blacklist.cfg")).toBeNull();
+ });
+
+ it("does not create the jei blacklist config if nothing is hidden", async () => {
+ const acceptor = createTestAcceptor();
+
+ blacklist.hide("minecraft:stone");
+ blacklist.hide(new FluidIngredient("water"));
+ blacklist.hide(new BlockIngredient("water"));
+ blacklist.hide([
+ new ItemIngredient("ice"),
+ new FluidIngredient("minecraft:lava"),
+ ]);
+
+ await instance.emit(acceptor);
+
+ expect(
+ acceptor.at(
+ "assets/generated/polytone/creative_tab_modifiers/hidden.json",
+ ),
+ ).toMatchSnapshot("creates a polytone tab modifier");
+ });
+});
diff --git a/test/polytoneTabs.test.ts b/lib/test/polytoneTabs.test.ts
similarity index 68%
rename from test/polytoneTabs.test.ts
rename to lib/test/polytoneTabs.test.ts
index 1db7648..8a9ae6c 100644
--- a/test/polytoneTabs.test.ts
+++ b/lib/test/polytoneTabs.test.ts
@@ -1,25 +1,52 @@
+import { defineModule, encodeId } from "@adeficior/data-modifier-core";
import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { createDumpResolver } from "@adeficior/testing";
import { describe, expect, it } from "bun:test";
-import { encodeId } from "../src/index.js";
-import setupLoader from "./shared/loaderSetup.js";
+import {
+ PolytoneTabsEmitter,
+ type PolytoneTabs,
+} from "../src/emit/polytoneTabs";
+import { setupInstance } from "./util/setup";
+
+const module = defineModule<{
+ emitters: {
+ tabs: PolytoneTabs;
+ };
+}>({
+ name: "internal",
+ setup: (pack) => {
+ pack.emitter(
+ "tabs",
+ (container) => new PolytoneTabsEmitter(container.get("registries")),
+ );
+ },
+});
const version = "1.20.1";
-const { loader } = setupLoader({
+const instance = await setupInstance(
version,
- load: false,
- hideFrom: ["polytone"],
-});
+ {
+ load: false,
+ // TODO do in setup
+ dump: await createDumpResolver(version),
+ },
+ (modules) => {
+ modules.install(module);
+ },
+);
+
+const tabs = instance.get("emitter:tabs");
describe("creates addition entries", () => {
it("emits per tab key", async () => {
const acceptor = createTestAcceptor();
- loader.tabs.add(
+ tabs.add(
["something:test_tab", { namespace: "example", path: "food" }],
["minecraft:diamond", { namespace: "forge", path: "the_logo" }],
);
- await loader.emit(acceptor);
+ await instance.emit(acceptor);
expect(
acceptor.at(
@@ -34,11 +61,11 @@ describe("creates addition entries", () => {
it("adds after predicate", async () => {
const acceptor = createTestAcceptor();
- loader.tabs.add("example:tab", ["minecraft:oak_log"], {
+ tabs.add("example:tab", ["minecraft:oak_log"], {
after: "minecraft:stone_axe",
});
- await loader.emit(acceptor);
+ await instance.emit(acceptor);
expect(
acceptor.at("assets/example/polytone/creative_tab_modifiers/tab.json"),
@@ -48,11 +75,11 @@ describe("creates addition entries", () => {
it("adds before predicate", async () => {
const acceptor = createTestAcceptor();
- loader.tabs.add("example:tab", ["minecraft:oak_planks"], {
+ tabs.add("example:tab", ["minecraft:oak_planks"], {
before: "minecraft:stone_hoe",
});
- await loader.emit(acceptor);
+ await instance.emit(acceptor);
expect(
acceptor.at("assets/example/polytone/creative_tab_modifiers/tab.json"),
@@ -62,11 +89,11 @@ describe("creates addition entries", () => {
it("uses custom file name", async () => {
const acceptor = createTestAcceptor();
- loader.tabs.add("something:test_tab", ["minecraft:diamond"], {
+ tabs.add("something:test_tab", ["minecraft:diamond"], {
file: "other:id",
});
- await loader.emit(acceptor);
+ await instance.emit(acceptor);
expect(
acceptor.at(
@@ -79,16 +106,14 @@ describe("creates addition entries", () => {
});
it("fails trying to merge modifiers with different targets", () => {
- loader.tabs.add("something:test_tab", ["minecraft:diamond"], {
+ tabs.add("something:test_tab", ["minecraft:diamond"], {
file: "other:id",
});
expect(() =>
- loader.tabs.add(
- "something:another_tab",
- ["minecraft:diamond_chestplate"],
- { file: "other:id" },
- ),
+ tabs.add("something:another_tab", ["minecraft:diamond_chestplate"], {
+ file: "other:id",
+ }),
).toThrow("trying to merge modifiers with different targets");
});
});
@@ -97,12 +122,12 @@ describe("create removal entries", () => {
it("resolves filter correctly", async () => {
const acceptor = createTestAcceptor();
- loader.tabs.remove(
+ tabs.remove(
["something:test_tab", { namespace: "example", path: "food" }],
["minecraft:diamond", { namespace: "forge", path: "the_logo" }],
);
- await loader.emit(acceptor);
+ await instance.emit(acceptor);
expect(
acceptor.at(
@@ -117,11 +142,11 @@ describe("create removal entries", () => {
it("uses custom file name", async () => {
const acceptor = createTestAcceptor();
- loader.tabs.remove("something:test_tab", ["minecraft:diamond"], {
+ tabs.remove("something:test_tab", ["minecraft:diamond"], {
file: "other:id",
});
- await loader.emit(acceptor);
+ await instance.emit(acceptor);
expect(
acceptor.at(
@@ -138,11 +163,11 @@ describe("create new tabs", () => {
it("emits and mergers csv", async () => {
const acceptor = createTestAcceptor();
- const id = loader.tabs.create("something:test_tab");
- loader.tabs.create("something:another_tab");
- loader.tabs.create("minecraft:more_blocks");
+ const id = tabs.create("something:test_tab");
+ tabs.create("something:another_tab");
+ tabs.create("minecraft:more_blocks");
- await loader.emit(acceptor);
+ await instance.emit(acceptor);
expect(encodeId(id)).toMatch("something:test_tab");
expect(
diff --git a/lib/test/recipeGraph.test.ts b/lib/test/recipeGraph.test.ts
new file mode 100644
index 0000000..4c7f3f4
--- /dev/null
+++ b/lib/test/recipeGraph.test.ts
@@ -0,0 +1,66 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { describe, expect, it } from "bun:test";
+import {
+ RecipeGraphEmitter,
+ type RecipeGraphAccessor,
+ type RecipeGraphOptions,
+} from "../src/emit/recipeGraph";
+import { setupInstance } from "./util/setup";
+
+const module = defineModule<{
+ options: RecipeGraphOptions;
+ emitters: {
+ "graph:recipes": RecipeGraphAccessor;
+ };
+}>({
+ name: "internal",
+ dependencies: {
+ "@adeficior/data-modifier-recipes": "required",
+ "@adeficior/data-modifier-tags": "required",
+ },
+ setup: (pack) => {
+ pack.emitter(
+ "graph:recipes",
+ (container) =>
+ new RecipeGraphEmitter(
+ container.get("loader:recipes"),
+ container.get("loader:tags"),
+ pack.options,
+ ),
+ );
+ },
+});
+
+const version = "1.21.1";
+const instance = await setupInstance(
+ version,
+ {
+ include: ["data/*/recipe/**/*.json", "data/*/tags/**/*.json"],
+ },
+ (modules) => {
+ modules.install(module);
+ },
+);
+
+const recipeGraph = instance.get("emitter:graph:recipes");
+
+describe("recipe graph", () => {
+ it("generated nodes & edges", async () => {
+ const acceptor = createTestAcceptor();
+
+ recipeGraph.show("minecraft:chest");
+ recipeGraph.show("minecraft:oak_stairs");
+ recipeGraph.show("minecraft:oak_slab");
+ recipeGraph.show("minecraft:oak_planks");
+
+ await instance.emit(acceptor);
+
+ expect(acceptor.jsonAt("graph/recipe/nodes.json")).toMatchSnapshot(
+ "nodes.json",
+ );
+ expect(acceptor.jsonAt("graph/recipe/edges.json")).toMatchSnapshot(
+ "edges.json",
+ );
+ });
+});
diff --git a/lib/test/util/setup.ts b/lib/test/util/setup.ts
new file mode 100644
index 0000000..0bf6d30
--- /dev/null
+++ b/lib/test/util/setup.ts
@@ -0,0 +1,51 @@
+import {
+ packFormatOf,
+ type DataModifierOptions,
+} from "@adeficior/data-modifier-core";
+import { type ResolverOptions } from "@adeficior/pack-resolver";
+import { createTestLogger } from "@adeficior/pack-resolver/testing";
+import { createTestDataResolver } from "@adeficior/testing";
+import { afterEach, beforeAll } from "bun:test";
+import {
+ createDataModifier,
+ type DataModifier,
+ type DataModifierFactory,
+} from "../../src";
+
+export async function setupInstance(
+ version: string,
+ {
+ load = true,
+ from,
+ include,
+ ...options
+ }: Partial<
+ DataModifierOptions & Pick
+ > & {
+ load?: boolean;
+ } = {},
+ factory?: DataModifierFactory,
+): Promise {
+ const logger = createTestLogger();
+ const instance = await createDataModifier(
+ {
+ logger,
+ packFormat: packFormatOf(version),
+ ...options,
+ },
+ factory,
+ );
+
+ if (load) {
+ beforeAll(async () => {
+ const data = await createTestDataResolver(version, { include, from });
+ await instance.loadFrom(data);
+ });
+ }
+
+ afterEach(() => {
+ instance.reset();
+ });
+
+ return instance;
+}
diff --git a/modules/botania/package.json b/modules/botania/package.json
new file mode 100644
index 0000000..c5c4ae7
--- /dev/null
+++ b/modules/botania/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@adeficior/data-modifier-botania",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/modules/botania/src/index.ts b/modules/botania/src/index.ts
new file mode 100644
index 0000000..2059820
--- /dev/null
+++ b/modules/botania/src/index.ts
@@ -0,0 +1,12 @@
+export { default } from "./module";
+export * from "./serializer/apothecary";
+export * from "./serializer/blocks";
+export * from "./serializer/brew";
+export * from "./serializer/elvenTrade";
+export * from "./serializer/gogWrapper";
+export * from "./serializer/manaInfusion";
+export * from "./serializer/nbtWrapper";
+export * from "./serializer/orechid";
+export * from "./serializer/pureDaisy";
+export * from "./serializer/runicAltar";
+export * from "./serializer/terraPlate";
diff --git a/modules/botania/src/module.ts b/modules/botania/src/module.ts
new file mode 100644
index 0000000..bcee601
--- /dev/null
+++ b/modules/botania/src/module.ts
@@ -0,0 +1,13 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { name } from "../package.json";
+import { registerParsers } from "./registration";
+
+export default defineModule({
+ importModule: name,
+ dependencies: {
+ "@adeficior/data-modifier-recipes": "required",
+ },
+ setup: (pack) => {
+ pack.hook("recipes:register-parser", registerParsers);
+ },
+});
diff --git a/modules/botania/src/registration.ts b/modules/botania/src/registration.ts
new file mode 100644
index 0000000..9243f38
--- /dev/null
+++ b/modules/botania/src/registration.ts
@@ -0,0 +1,40 @@
+import { type RegisterRecipeParser } from "@adeficior/data-modifier-recipes";
+import {
+ ShapedParser,
+ ShapelessParser,
+} from "@adeficior/data-modifier-recipes";
+import { ApothecaryRecipeParser } from "./serializer/apothecary";
+import { BrewRecipeParser } from "./serializer/brew";
+import { ElvenTradeRecipeParser } from "./serializer/elvenTrade";
+import { GogWrapperRecipeParser } from "./serializer/gogWrapper";
+import { ManaInfusionRecipeParser } from "./serializer/manaInfusion";
+import { NbtWrapperRecipeParser } from "./serializer/nbtWrapper";
+import { OrechidRecipeParser } from "./serializer/orechid";
+import { PureDaisyRecipeParser } from "./serializer/pureDaisy";
+import { RunicAltarRecipeParser } from "./serializer/runicAltar";
+import { TerraPlateRecipeParser } from "./serializer/terraPlate";
+
+export function registerParsers(event: RegisterRecipeParser) {
+ event.register("botania:nbt_output_wrapper", new NbtWrapperRecipeParser());
+ event.register("botania:orechid", new OrechidRecipeParser());
+ event.register("botania:orechid_ignem", new OrechidRecipeParser());
+ event.register("botania:marimorphosis", new OrechidRecipeParser());
+ event.register("botania:pure_daisy", new PureDaisyRecipeParser());
+ event.register(
+ "botania:state_copying_pure_daisy",
+ new PureDaisyRecipeParser(),
+ );
+ event.register("botania:mana_upgrade", new ShapedParser());
+ event.register("botania:water_bottle_matching_shaped", new ShapedParser());
+ event.register("botania:runic_altar", new RunicAltarRecipeParser());
+ event.register("botania:runic_altar_head", new RunicAltarRecipeParser());
+ event.register("botania:terra_plate", new TerraPlateRecipeParser());
+ event.register("botania:elven_trade", new ElvenTradeRecipeParser());
+ event.register("botania:brew", new BrewRecipeParser());
+ event.register("botania:twig_wand", new ShapedParser());
+ event.register("botania:mana_infusion", new ManaInfusionRecipeParser());
+ event.register("botania:mana_upgrade_shapeless", new ShapelessParser());
+ event.register("botania:armor_upgrade", new ShapedParser());
+ event.register("botania:gog_alternation", new GogWrapperRecipeParser());
+ event.register("botania:petal_apothecary", new ApothecaryRecipeParser());
+}
diff --git a/src/parser/recipe/botania/apothecary.ts b/modules/botania/src/serializer/apothecary.ts
similarity index 86%
rename from src/parser/recipe/botania/apothecary.ts
rename to modules/botania/src/serializer/apothecary.ts
index a799369..4b9594f 100644
--- a/src/parser/recipe/botania/apothecary.ts
+++ b/modules/botania/src/serializer/apothecary.ts
@@ -1,11 +1,14 @@
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { Result } from "../../../common/result/index.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import RecipeParser, {
+import {
+ type Ingredient,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
+import {
Recipe,
+ RecipeParser,
+ type RecipeDefinition,
type RecipeModifier,
type RecipeParseContext,
-} from "../index.js";
+} from "@adeficior/data-modifier-recipes";
export type ApothecaryRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/modules/botania/src/serializer/blocks.ts b/modules/botania/src/serializer/blocks.ts
new file mode 100644
index 0000000..20c2712
--- /dev/null
+++ b/modules/botania/src/serializer/blocks.ts
@@ -0,0 +1,45 @@
+import {
+ OneToOneRecipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
+import { ingredientSerializerModules, resultSerializerModules } from "./module";
+
+export type BotaniaBlockRecipeDefinition = RecipeDefinition &
+ Readonly<{
+ input: unknown;
+ output: unknown;
+ }>;
+
+export class BotaniaBlockRecipe extends OneToOneRecipe {
+ override serialize(
+ context: RecipeParseContext,
+ ): Partial {
+ return {
+ output: context.results.serialize(this.result),
+ input: context.ingredients.serialize(this.ingredient),
+ };
+ }
+}
+
+export class BotaniaBlockRecipeParser<
+ TDefinition extends BotaniaBlockRecipeDefinition,
+> extends RecipeParser {
+ override resultModules() {
+ return resultSerializerModules;
+ }
+
+ override ingredientModules() {
+ return ingredientSerializerModules;
+ }
+
+ deserialize(
+ definition: TDefinition,
+ context: RecipeParseContext,
+ ): BotaniaBlockRecipe {
+ const ingredient = context.ingredients.deserialize(definition.input);
+ const result = context.results.deserialize(definition.output);
+ return new BotaniaBlockRecipe(ingredient, result);
+ }
+}
diff --git a/src/parser/recipe/botania/brew.ts b/modules/botania/src/serializer/brew.ts
similarity index 78%
rename from src/parser/recipe/botania/brew.ts
rename to modules/botania/src/serializer/brew.ts
index d7305c8..21ba24e 100644
--- a/src/parser/recipe/botania/brew.ts
+++ b/modules/botania/src/serializer/brew.ts
@@ -1,7 +1,11 @@
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type { RecipeModifier, RecipeParseContext } from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
+import { type Ingredient } from "@adeficior/data-modifier-ingredients";
+import {
+ Recipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
export type BrewRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/botania/elvenTrade.ts b/modules/botania/src/serializer/elvenTrade.ts
similarity index 81%
rename from src/parser/recipe/botania/elvenTrade.ts
rename to modules/botania/src/serializer/elvenTrade.ts
index 85d13f4..4c0df2a 100644
--- a/src/parser/recipe/botania/elvenTrade.ts
+++ b/modules/botania/src/serializer/elvenTrade.ts
@@ -1,6 +1,9 @@
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import RecipeParser, { type RecipeParseContext } from "../index.js";
-import { ManyToManyRecipe } from "../manyToMany.js";
+import {
+ ManyToManyRecipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
export type ElvenTradeRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/botania/gogWrapper.ts b/modules/botania/src/serializer/gogWrapper.ts
similarity index 85%
rename from src/parser/recipe/botania/gogWrapper.ts
rename to modules/botania/src/serializer/gogWrapper.ts
index dbc9f24..9aad1a9 100644
--- a/src/parser/recipe/botania/gogWrapper.ts
+++ b/modules/botania/src/serializer/gogWrapper.ts
@@ -1,10 +1,10 @@
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type {
- RecipeHolder,
- RecipeModifier,
- RecipeParseContext,
-} from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
+import {
+ type RecipeDefinition,
+ type RecipeHolder,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
+import { Recipe, RecipeParser } from "@adeficior/data-modifier-recipes";
export type GogWrapperRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/botania/manaInfusion.ts b/modules/botania/src/serializer/manaInfusion.ts
similarity index 81%
rename from src/parser/recipe/botania/manaInfusion.ts
rename to modules/botania/src/serializer/manaInfusion.ts
index f080943..df41f62 100644
--- a/src/parser/recipe/botania/manaInfusion.ts
+++ b/modules/botania/src/serializer/manaInfusion.ts
@@ -1,10 +1,16 @@
+import {
+ type Ingredient,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
+import {
+ Recipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
import { notNull } from "@adeficior/pack-resolver";
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { Result } from "../../../common/result/index.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type { RecipeModifier, RecipeParseContext } from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
-import { ingredientSerializerModules } from "./blocks.js";
+import { ingredientSerializerModules } from "./module";
export type ManaInfusionRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/modules/botania/src/serializer/module.ts b/modules/botania/src/serializer/module.ts
new file mode 100644
index 0000000..e66bdda
--- /dev/null
+++ b/modules/botania/src/serializer/module.ts
@@ -0,0 +1,61 @@
+import { IdSchema } from "@adeficior/data-modifier-core";
+import {
+ createSerializerModule,
+ hasType,
+} from "@adeficior/data-modifier-core/serializer";
+import {
+ BlockIngredient,
+ BlockResult,
+ BlockTagIngredient,
+ type Ingredient,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
+import * as z from "zod";
+
+const ingredientSerializer15 = createSerializerModule((builder) => {
+ const blockType = "block" as const;
+ builder.register(
+ BlockIngredient,
+ hasType(blockType),
+ z.object({
+ type: z.literal(blockType),
+ block: IdSchema,
+ }),
+ ({ block }) => new BlockIngredient(block),
+ ({ id }) => ({ block: id, type: blockType }),
+ );
+
+ const tagType = "tag" as const;
+ builder.register(
+ BlockTagIngredient,
+ hasType(tagType),
+ z.object({
+ type: z.literal(tagType),
+ tag: IdSchema,
+ }),
+ ({ tag }) => new BlockTagIngredient(tag),
+ ({ tag }) => ({ tag, type: tagType }),
+ );
+});
+
+const resultSerializer15 = createSerializerModule((builder) => {
+ const blockType = "block" as const;
+ builder.register(
+ BlockResult,
+ hasType(blockType),
+ z.object({
+ type: z.literal(blockType),
+ block: IdSchema,
+ }),
+ ({ block }) => new BlockResult(block),
+ ({ id }) => ({ block: id, type: blockType }),
+ );
+});
+
+export const resultSerializerModules = {
+ 15: resultSerializer15,
+};
+
+export const ingredientSerializerModules = {
+ 15: ingredientSerializer15,
+};
diff --git a/src/parser/recipe/botania/nbtWrapper.ts b/modules/botania/src/serializer/nbtWrapper.ts
similarity index 82%
rename from src/parser/recipe/botania/nbtWrapper.ts
rename to modules/botania/src/serializer/nbtWrapper.ts
index f6ec00f..bcac910 100644
--- a/src/parser/recipe/botania/nbtWrapper.ts
+++ b/modules/botania/src/serializer/nbtWrapper.ts
@@ -1,10 +1,10 @@
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type {
- RecipeHolder,
- RecipeModifier,
- RecipeParseContext,
-} from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
+import {
+ type RecipeDefinition,
+ type RecipeHolder,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
+import { Recipe, RecipeParser } from "@adeficior/data-modifier-recipes";
export type NbtWrapperRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/botania/orechid.ts b/modules/botania/src/serializer/orechid.ts
similarity index 83%
rename from src/parser/recipe/botania/orechid.ts
rename to modules/botania/src/serializer/orechid.ts
index a83f583..fca98ef 100644
--- a/src/parser/recipe/botania/orechid.ts
+++ b/modules/botania/src/serializer/orechid.ts
@@ -1,6 +1,7 @@
-import BotaniaBlockRecipeParser, {
+import {
+ BotaniaBlockRecipeParser,
type BotaniaBlockRecipeDefinition,
-} from "./blocks.js";
+} from "./blocks";
export type OrechidRecipeDefinition = BotaniaBlockRecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/botania/pureDaisy.ts b/modules/botania/src/serializer/pureDaisy.ts
similarity index 59%
rename from src/parser/recipe/botania/pureDaisy.ts
rename to modules/botania/src/serializer/pureDaisy.ts
index 7c84051..8c81690 100644
--- a/src/parser/recipe/botania/pureDaisy.ts
+++ b/modules/botania/src/serializer/pureDaisy.ts
@@ -1,11 +1,15 @@
-import z from "zod";
-import { IdSchema } from "../../../common/id.js";
-import { BlockResult, type Result } from "../../../common/result/index.js";
-import { isObjectWith } from "../../../serializer/checks.js";
-import { createSerializerModule } from "../../../serializer/module.js";
-import BotaniaBlockRecipeParser, {
+import { IdSchema } from "@adeficior/data-modifier-core";
+import {
+ createSerializerModule,
+ isObjectWith,
+} from "@adeficior/data-modifier-core/serializer";
+import { type Result } from "@adeficior/data-modifier-ingredients";
+import { BlockResult } from "@adeficior/data-modifier-ingredients";
+import * as z from "zod";
+import {
+ BotaniaBlockRecipeParser,
type BotaniaBlockRecipeDefinition,
-} from "./blocks.js";
+} from "./blocks";
export type PureDaisyRecipeDefinition = BotaniaBlockRecipeDefinition;
diff --git a/src/parser/recipe/botania/runicAltar.ts b/modules/botania/src/serializer/runicAltar.ts
similarity index 81%
rename from src/parser/recipe/botania/runicAltar.ts
rename to modules/botania/src/serializer/runicAltar.ts
index 5d993bd..84b6b2c 100644
--- a/src/parser/recipe/botania/runicAltar.ts
+++ b/modules/botania/src/serializer/runicAltar.ts
@@ -1,6 +1,9 @@
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import RecipeParser, { type RecipeParseContext } from "../index.js";
-import { ManyToOneRecipe } from "../manyToOne.js";
+import {
+ ManyToOneRecipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
export type RunicAltarRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/botania/terraPlate.ts b/modules/botania/src/serializer/terraPlate.ts
similarity index 74%
rename from src/parser/recipe/botania/terraPlate.ts
rename to modules/botania/src/serializer/terraPlate.ts
index 55eeba7..9264f28 100644
--- a/src/parser/recipe/botania/terraPlate.ts
+++ b/modules/botania/src/serializer/terraPlate.ts
@@ -1,9 +1,8 @@
import {
ManyToOneRecipeParser,
type ManyToOneRecipeDefinition,
-} from "../manyToOne.js";
+} from "@adeficior/data-modifier-recipes";
-export type ShapelessRecipeDefinition = ManyToOneRecipeDefinition;
export type TerraPlateRecipeDefinition = ManyToOneRecipeDefinition &
Readonly<{
mana?: number;
diff --git a/modules/botania/test/1.20.1/loader.test.ts b/modules/botania/test/1.20.1/loader.test.ts
new file mode 100644
index 0000000..7ec8b08
--- /dev/null
+++ b/modules/botania/test/1.20.1/loader.test.ts
@@ -0,0 +1,36 @@
+import { type IdInput } from "@adeficior/data-modifier-core";
+import { setupRecipeLoader } from "@adeficior/data-modifier-recipes/testing";
+import { provided, type DataProvider } from "@adeficior/testing";
+import { expect, it } from "bun:test";
+import { basename } from "node:path";
+import { registerParsers } from "../../src/registration";
+
+const version = basename(import.meta.dir);
+
+const { loader, logger } = setupRecipeLoader(version, registerParsers);
+
+function* recipes(): DataProvider<[IdInput]> {
+ yield ["mana infusion", "botania:mana_infusion/carrot_to_beetroot_seeds"];
+ yield ["elven trade", "botania:elven_trade/dreamwood_log"];
+ yield ["marimorphosis", "botania:marimorphosis/metamorphic_swamp_stone"];
+ yield ["orechid", "botania:orechid/lapis_ore"];
+ yield ["orechid ignem", "botania:orechid_ignem/nether_quartz_ore"];
+ yield ["petal apothecary", "botania:petal_apothecary/daffomill"];
+ yield ["pure daisy", "botania:pure_daisy/snow_block"];
+ yield ["runic altar", "botania:runic_altar/pride"];
+ yield ["terra plate", "botania:terra_plate/terrasteel_ingot"];
+}
+
+provided("loads recipes", recipes(), (id) => {
+ expect(loader.get(id)).toBeDefined();
+});
+
+it("has no unknown recipe loaders", () => {
+ expect(loader.unknownRecipeTypes().map((it) => it.type)).toBeEmpty();
+});
+
+it("does not encounter any errors", () => {
+ expect(logger.trace).not.toHaveBeenCalled();
+ expect(logger.warn).not.toHaveBeenCalled();
+ expect(logger.error).not.toHaveBeenCalled();
+});
diff --git a/modules/botania/test/resources/1.20.1/default/botania b/modules/botania/test/resources/1.20.1/default/botania
new file mode 160000
index 0000000..93cd87f
--- /dev/null
+++ b/modules/botania/test/resources/1.20.1/default/botania
@@ -0,0 +1 @@
+Subproject commit 93cd87fbce60e5f793c6044d3625e3ea02ff7968
diff --git a/modules/botania/test/resources/1.20.1/default/config.json b/modules/botania/test/resources/1.20.1/default/config.json
new file mode 100644
index 0000000..9c4c597
--- /dev/null
+++ b/modules/botania/test/resources/1.20.1/default/config.json
@@ -0,0 +1,10 @@
+{
+ "packs": {
+ "botania": {
+ "paths": [
+ "Xplat/src/generated/resources",
+ "Xplat/src/main/resources"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/content/package.json b/modules/content/package.json
new file mode 100644
index 0000000..8a0d337
--- /dev/null
+++ b/modules/content/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "@adeficior/data-modifier-content",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@adeficior/data-modifier-models": "workspace:*",
+ "@adeficior/data-modifier-loot": "workspace:*",
+ "@types/lodash-es": "catalog:"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "lodash-es": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-models": "workspace:*",
+ "@adeficior/data-modifier-loot": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/src/emit/content/blockDefinition.ts b/modules/content/src/emitter/blockDefinition.ts
similarity index 79%
rename from src/emit/content/blockDefinition.ts
rename to modules/content/src/emitter/blockDefinition.ts
index b9d4e5a..4152765 100644
--- a/src/emit/content/blockDefinition.ts
+++ b/modules/content/src/emitter/blockDefinition.ts
@@ -1,15 +1,20 @@
-import type { BlockId } from "@adeficior/data-modifier/generated";
-import type { BaseContext } from "@adeficior/pack-resolver";
-import type { Id, IdInput } from "../../common/id.js";
-import type {
- BlockDefinition,
- BlockProperties,
-} from "../../schema/content/blockDefinition.js";
-import type { BlockstateRules } from "../assets/blockstates.js";
-import type { ModelRules } from "../assets/models.js";
-import CustomEmitter from "../custom.js";
-import type { LootRules } from "../data/loot.js";
-import type { ClearableEmitter } from "../index.js";
+import {
+ type ClearableEmitter,
+ type Id,
+ type IdInput,
+ type LoaderContext,
+} from "@adeficior/data-modifier-core";
+import { CustomEmitter } from "@adeficior/data-modifier-core";
+import { type LootRules } from "@adeficior/data-modifier-loot";
+import {
+ type BlockstateRules,
+ type ModelRules,
+} from "@adeficior/data-modifier-models";
+import { type BlockId } from "@adeficior/data-modifier/generated";
+import {
+ type BlockDefinition,
+ type BlockProperties,
+} from "../schema/blockDefinition";
export type BlockDefinitionOptions = Readonly<{
blockstate?: boolean;
@@ -46,6 +51,7 @@ function resolveProperties(from: PropertiesOrCopy): BlockProperties | string {
export abstract class AbstractBlockDefinitionRules implements BlockDefinitionRules {
constructor(
+ // TODO inject
private readonly models: ModelRules,
private readonly blockstates: BlockstateRules,
private readonly loot: LootRules,
@@ -93,7 +99,7 @@ export abstract class AbstractBlockDefinitionRules implements BlockDefinitionRul
}
}
-export default class BlockDefinitionEmitter
+export class BlockDefinitionEmitter
extends AbstractBlockDefinitionRules
implements ClearableEmitter
{
@@ -108,7 +114,7 @@ export default class BlockDefinitionEmitter
return definition;
}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return this.custom.resolver(context);
}
diff --git a/src/emit/content/innerBlockDefinition.ts b/modules/content/src/emitter/innerBlockDefinition.ts
similarity index 77%
rename from src/emit/content/innerBlockDefinition.ts
rename to modules/content/src/emitter/innerBlockDefinition.ts
index a8158c3..6a1c774 100644
--- a/src/emit/content/innerBlockDefinition.ts
+++ b/modules/content/src/emitter/innerBlockDefinition.ts
@@ -1,10 +1,12 @@
-import type { IdInput } from "../../common/id.js";
-import type { BlockDefinition } from "../../schema/content/blockDefinition.js";
-import type { BlockstateRules } from "../assets/blockstates.js";
-import type { ModelRules } from "../assets/models.js";
-import type { LootRules } from "../data/loot.js";
-import type { BlockDefinitionRules } from "./blockDefinition.js";
-import { AbstractBlockDefinitionRules } from "./blockDefinition.js";
+import { type IdInput } from "@adeficior/data-modifier-core";
+import { type LootRules } from "@adeficior/data-modifier-loot";
+import {
+ type BlockstateRules,
+ type ModelRules,
+} from "@adeficior/data-modifier-models";
+import { type BlockDefinition } from "../schema/blockDefinition";
+import { type BlockDefinitionRules } from "./blockDefinition";
+import { AbstractBlockDefinitionRules } from "./blockDefinition";
/* eslint-disable @typescript-eslint/no-explicit-any */
@@ -65,7 +67,7 @@ function createCurriedEmitter(id: IdInput, emitter: BlockDefinitionRules) {
/* eslint-enable @typescript-eslint/no-explicit-any */
-export default function createInnerBlockDefinitionBuilder(
+export function createInnerBlockDefinitionBuilder(
id: IdInput,
models: ModelRules,
blockstates: BlockstateRules,
diff --git a/src/emit/content/itemDefinition.ts b/modules/content/src/emitter/itemDefinition.ts
similarity index 65%
rename from src/emit/content/itemDefinition.ts
rename to modules/content/src/emitter/itemDefinition.ts
index 15c0a0e..40991f4 100644
--- a/src/emit/content/itemDefinition.ts
+++ b/modules/content/src/emitter/itemDefinition.ts
@@ -1,18 +1,22 @@
-import type { BaseContext } from "@adeficior/pack-resolver";
-import type { Id, IdInput } from "../../common/id.js";
-import { prefix } from "../../common/id.js";
-import type { BlockDefinition } from "../../schema/content/blockDefinition.js";
-import type {
- ItemDefinition,
- ItemProperties,
-} from "../../schema/content/itemDefinition.js";
-import type { BlockstateRules } from "../assets/blockstates.js";
-import type { ModelRulesGroup } from "../assets/models.js";
-import CustomEmitter from "../custom.js";
-import type { LootRules } from "../data/loot.js";
-import type { ClearableEmitter } from "../index.js";
-import type { BlockDefinitionRulesWithoutId } from "./innerBlockDefinition.js";
-import createInnerBlockDefinitionBuilder from "./innerBlockDefinition.js";
+import {
+ type ClearableEmitter,
+ type Id,
+ type IdInput,
+ type LoaderContext,
+} from "@adeficior/data-modifier-core";
+import { CustomEmitter, prefix } from "@adeficior/data-modifier-core";
+import { type LootRules } from "@adeficior/data-modifier-loot";
+import {
+ type BlockstateRules,
+ type ModelRules,
+} from "@adeficior/data-modifier-models";
+import { type BlockDefinition } from "../schema/blockDefinition";
+import {
+ type ItemDefinition,
+ type ItemProperties,
+} from "../schema/itemDefinition";
+import { type BlockDefinitionRulesWithoutId } from "./innerBlockDefinition";
+import { createInnerBlockDefinitionBuilder } from "./innerBlockDefinition";
export type ItemDefinitionOptions = Readonly<{
model?: boolean;
@@ -23,8 +27,7 @@ type ExtendedItemProperties = ItemProperties & {
};
type BlockDefinitionInput =
- | BlockDefinition
- | ((rules: BlockDefinitionRulesWithoutId) => BlockDefinition);
+ BlockDefinition | ((rules: BlockDefinitionRulesWithoutId) => BlockDefinition);
export interface ItemDefinitionRules {
add(id: IdInput, definition: T): T;
@@ -44,13 +47,15 @@ export interface ItemDefinitionRules {
): ItemDefinition;
}
-export default class ItemDefinitionEmitter
+export class ItemDefinitionEmitter
implements ItemDefinitionRules, ClearableEmitter
{
private readonly custom = new CustomEmitter(this.filePath);
constructor(
- private readonly models: ModelRulesGroup,
+ // TODO inject
+ private readonly itemModels: ModelRules,
+ private readonly blockModels: ModelRules,
private readonly blockstates: BlockstateRules,
private readonly loot: LootRules,
) {}
@@ -68,7 +73,7 @@ export default class ItemDefinitionEmitter
this.custom.clear();
}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return this.custom.resolver(context);
}
@@ -77,7 +82,7 @@ export default class ItemDefinitionEmitter
{ type, ...properties }: ExtendedItemProperties = {},
options?: ItemDefinitionOptions,
) {
- if (options?.model !== false) this.models.items.flat(id);
+ if (options?.model !== false) this.itemModels.flat(id);
return this.add(id, {
type: type ?? "basic",
@@ -89,7 +94,7 @@ export default class ItemDefinitionEmitter
if (typeof input !== "function") return input;
const blockBuilder = createInnerBlockDefinitionBuilder(
id,
- this.models.blocks,
+ this.blockModels,
this.blockstates,
this.loot,
);
@@ -109,7 +114,7 @@ export default class ItemDefinitionEmitter
) {
if (options?.model !== false) {
const parent = prefix(id, "block/");
- this.models.items.add(id, { parent });
+ this.itemModels.add(id, { parent });
}
const blockDefinition = this.createBlockDefinition(id, block);
diff --git a/modules/content/src/index.ts b/modules/content/src/index.ts
new file mode 100644
index 0000000..47d7746
--- /dev/null
+++ b/modules/content/src/index.ts
@@ -0,0 +1,11 @@
+export type {
+ BlockDefinitionOptions,
+ BlockDefinitionRules,
+} from "./emitter/blockDefinition";
+export type {
+ ItemDefinitionOptions,
+ ItemDefinitionRules,
+} from "./emitter/itemDefinition";
+export { default } from "./module";
+export type * from "./schema/blockDefinition";
+export type * from "./schema/itemDefinition";
diff --git a/modules/content/src/module.ts b/modules/content/src/module.ts
new file mode 100644
index 0000000..312a039
--- /dev/null
+++ b/modules/content/src/module.ts
@@ -0,0 +1,52 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { name } from "../package.json";
+import {
+ BlockDefinitionEmitter,
+ type BlockDefinitionRules,
+} from "./emitter/blockDefinition";
+import {
+ ItemDefinitionEmitter,
+ type ItemDefinitionRules,
+} from "./emitter/itemDefinition";
+
+export default defineModule<{
+ emitters: {
+ "content:blocks": BlockDefinitionRules;
+ "content:items": ItemDefinitionRules;
+ };
+}>({
+ importModule: name,
+ dependencies: {
+ // TODO optional?
+ "@adeficior/data-modifier-loot": "required",
+ "@adeficior/data-modifier-models": "required",
+ },
+ types: {
+ emitters: {
+ "content:blocks": "BlockDefinitionRules",
+ "content:items": "ItemDefinitionRules",
+ },
+ },
+ setup: (pack) => {
+ pack.emitter(
+ "content:blocks",
+ (container) =>
+ new BlockDefinitionEmitter(
+ container.get("emitter:models:block"),
+ container.get("emitter:blockstates"),
+ container.get("emitter:loot"),
+ ),
+ );
+
+ pack.emitter(
+ "content:items",
+ (container) =>
+ new ItemDefinitionEmitter(
+ container.get("emitter:models:item"),
+ container.get("emitter:models:block"),
+ container.get("emitter:blockstates"),
+ container.get("emitter:loot"),
+ ),
+ );
+ },
+});
diff --git a/src/schema/content/blockDefinition.ts b/modules/content/src/schema/blockDefinition.ts
similarity index 100%
rename from src/schema/content/blockDefinition.ts
rename to modules/content/src/schema/blockDefinition.ts
diff --git a/src/schema/content/itemDefinition.ts b/modules/content/src/schema/itemDefinition.ts
similarity index 85%
rename from src/schema/content/itemDefinition.ts
rename to modules/content/src/schema/itemDefinition.ts
index 3afbaa1..692f16d 100644
--- a/src/schema/content/itemDefinition.ts
+++ b/modules/content/src/schema/itemDefinition.ts
@@ -1,4 +1,4 @@
-import type { BlockDefinition } from "./blockDefinition.js";
+import { type BlockDefinition } from "./blockDefinition";
// TODO missing values
export type Rarity = "common" | "rare";
diff --git a/modules/content/test/__snapshots__/block.test.ts.snap b/modules/content/test/__snapshots__/block.test.ts.snap
new file mode 100644
index 0000000..753b030
--- /dev/null
+++ b/modules/content/test/__snapshots__/block.test.ts.snap
@@ -0,0 +1,117 @@
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
+
+exports[`block definitions generates additional resources for block definitions: basic block definition 1`] = `
+{
+ "properties": {
+ "material": "stone",
+ },
+ "type": "basic",
+}
+`;
+
+exports[`block definitions generates additional resources for block definitions: basic loot table 1`] = `
+{
+ "pools": [
+ {
+ "conditions": [
+ {
+ "condition": "minecraft:survives_explosion",
+ },
+ ],
+ "entries": [
+ {
+ "name": "example:ruby_ore",
+ "type": "minecraft:item",
+ },
+ ],
+ "rolls": 1,
+ },
+ ],
+ "type": "minecraft:block",
+}
+`;
+
+exports[`block definitions generates additional resources for block definitions: basic blockstate 1`] = `
+{
+ "variants": {
+ "": {
+ "model": "example:block/ruby_ore",
+ },
+ },
+}
+`;
+
+exports[`block definitions generates additional resources for block definitions: basic block model 1`] = `
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "example:block/ruby_ore",
+ },
+}
+`;
+
+exports[`block definitions generates additional resources for item definitions: basic item definition 1`] = `
+{
+ "properties": {
+ "rarity": "rare",
+ "stack_size": 24,
+ },
+ "type": "basic",
+}
+`;
+
+exports[`block definitions generates additional resources for item definitions: basic item model 1`] = `
+{
+ "parent": "minecraft:item/generated",
+ "textures": {
+ "layer0": "example:item/ruby",
+ },
+}
+`;
+
+exports[`block definitions generates block resources for block item definitions: basic item definition 1`] = `
+{
+ "block": {
+ "properties": {
+ "material": "metal",
+ },
+ "type": "basic",
+ },
+ "properties": {
+ "rarity": "rare",
+ },
+ "type": "block_item",
+}
+`;
+
+exports[`block definitions generates block resources for block item definitions: basic item model 1`] = `
+{
+ "parent": "example:block/ruby_block",
+}
+`;
+
+exports[`block definitions generates block resources for block item definitions: included blockstate 1`] = `
+{
+ "variants": {
+ "": {
+ "model": "example:block/ruby_block",
+ },
+ },
+}
+`;
+
+exports[`block definitions generates block resources for block item definitions: included block model 1`] = `
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "example:block/ruby_block",
+ },
+}
+`;
+
+exports[`block definitions correctly resolves copy reference for block properties: block definition using reference for properties 1`] = `
+{
+ "properties": "minecraft:emerald_ore",
+ "type": "basic",
+}
+`;
\ No newline at end of file
diff --git a/modules/content/test/__snapshots__/blockItem.test.ts.snap b/modules/content/test/__snapshots__/blockItem.test.ts.snap
new file mode 100644
index 0000000..6d6ee9a
--- /dev/null
+++ b/modules/content/test/__snapshots__/blockItem.test.ts.snap
@@ -0,0 +1,117 @@
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
+
+exports[`block item definitions generates additional resources for block definitions: basic block definition 1`] = `
+{
+ "properties": {
+ "material": "stone",
+ },
+ "type": "basic",
+}
+`;
+
+exports[`block item definitions generates additional resources for block definitions: basic loot table 1`] = `
+{
+ "pools": [
+ {
+ "conditions": [
+ {
+ "condition": "minecraft:survives_explosion",
+ },
+ ],
+ "entries": [
+ {
+ "name": "example:ruby_ore",
+ "type": "minecraft:item",
+ },
+ ],
+ "rolls": 1,
+ },
+ ],
+ "type": "minecraft:block",
+}
+`;
+
+exports[`block item definitions generates additional resources for block definitions: basic blockstate 1`] = `
+{
+ "variants": {
+ "": {
+ "model": "example:block/ruby_ore",
+ },
+ },
+}
+`;
+
+exports[`block item definitions generates additional resources for block definitions: basic block model 1`] = `
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "example:block/ruby_ore",
+ },
+}
+`;
+
+exports[`block item definitions generates additional resources for item definitions: basic item definition 1`] = `
+{
+ "properties": {
+ "rarity": "rare",
+ "stack_size": 24,
+ },
+ "type": "basic",
+}
+`;
+
+exports[`block item definitions generates additional resources for item definitions: basic item model 1`] = `
+{
+ "parent": "minecraft:item/generated",
+ "textures": {
+ "layer0": "example:item/ruby",
+ },
+}
+`;
+
+exports[`block item definitions generates block resources for block item definitions: basic item definition 1`] = `
+{
+ "block": {
+ "properties": {
+ "material": "metal",
+ },
+ "type": "basic",
+ },
+ "properties": {
+ "rarity": "rare",
+ },
+ "type": "block_item",
+}
+`;
+
+exports[`block item definitions generates block resources for block item definitions: basic item model 1`] = `
+{
+ "parent": "example:block/ruby_block",
+}
+`;
+
+exports[`block item definitions generates block resources for block item definitions: included blockstate 1`] = `
+{
+ "variants": {
+ "": {
+ "model": "example:block/ruby_block",
+ },
+ },
+}
+`;
+
+exports[`block item definitions generates block resources for block item definitions: included block model 1`] = `
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "example:block/ruby_block",
+ },
+}
+`;
+
+exports[`block item definitions correctly resolves copy reference for block properties: block definition using reference for properties 1`] = `
+{
+ "properties": "minecraft:emerald_ore",
+ "type": "basic",
+}
+`;
\ No newline at end of file
diff --git a/modules/content/test/__snapshots__/item.test.ts.snap b/modules/content/test/__snapshots__/item.test.ts.snap
new file mode 100644
index 0000000..af71063
--- /dev/null
+++ b/modules/content/test/__snapshots__/item.test.ts.snap
@@ -0,0 +1,117 @@
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
+
+exports[`item definitions generates additional resources for block definitions: basic block definition 1`] = `
+{
+ "properties": {
+ "material": "stone",
+ },
+ "type": "basic",
+}
+`;
+
+exports[`item definitions generates additional resources for block definitions: basic loot table 1`] = `
+{
+ "pools": [
+ {
+ "conditions": [
+ {
+ "condition": "minecraft:survives_explosion",
+ },
+ ],
+ "entries": [
+ {
+ "name": "example:ruby_ore",
+ "type": "minecraft:item",
+ },
+ ],
+ "rolls": 1,
+ },
+ ],
+ "type": "minecraft:block",
+}
+`;
+
+exports[`item definitions generates additional resources for block definitions: basic blockstate 1`] = `
+{
+ "variants": {
+ "": {
+ "model": "example:block/ruby_ore",
+ },
+ },
+}
+`;
+
+exports[`item definitions generates additional resources for block definitions: basic block model 1`] = `
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "example:block/ruby_ore",
+ },
+}
+`;
+
+exports[`item definitions generates additional resources for item definitions: basic item definition 1`] = `
+{
+ "properties": {
+ "rarity": "rare",
+ "stack_size": 24,
+ },
+ "type": "basic",
+}
+`;
+
+exports[`item definitions generates additional resources for item definitions: basic item model 1`] = `
+{
+ "parent": "minecraft:item/generated",
+ "textures": {
+ "layer0": "example:item/ruby",
+ },
+}
+`;
+
+exports[`item definitions generates block resources for block item definitions: basic item definition 1`] = `
+{
+ "block": {
+ "properties": {
+ "material": "metal",
+ },
+ "type": "basic",
+ },
+ "properties": {
+ "rarity": "rare",
+ },
+ "type": "block_item",
+}
+`;
+
+exports[`item definitions generates block resources for block item definitions: basic item model 1`] = `
+{
+ "parent": "example:block/ruby_block",
+}
+`;
+
+exports[`item definitions generates block resources for block item definitions: included blockstate 1`] = `
+{
+ "variants": {
+ "": {
+ "model": "example:block/ruby_block",
+ },
+ },
+}
+`;
+
+exports[`item definitions generates block resources for block item definitions: included block model 1`] = `
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "example:block/ruby_block",
+ },
+}
+`;
+
+exports[`item definitions correctly resolves copy reference for block properties: block definition using reference for properties 1`] = `
+{
+ "properties": "minecraft:emerald_ore",
+ "type": "basic",
+}
+`;
\ No newline at end of file
diff --git a/modules/content/test/block.test.ts b/modules/content/test/block.test.ts
new file mode 100644
index 0000000..b679229
--- /dev/null
+++ b/modules/content/test/block.test.ts
@@ -0,0 +1,67 @@
+import { type LoaderContext } from "@adeficior/data-modifier-core";
+import { createLogger } from "@adeficior/pack-resolver";
+import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { afterEach, describe, expect, it } from "bun:test";
+import { createBlockDefinitionEmitter } from "./util/emitters";
+
+const version = "1.20.1";
+const context: LoaderContext = { logger: createLogger() };
+const { emitter, resolver, reset } = createBlockDefinitionEmitter(
+ version,
+ context,
+);
+
+afterEach(reset);
+
+describe("block definitions", () => {
+ it("generates additional resources for block definitions", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.basic("example:ruby_ore", { material: "stone" });
+
+ await resolver.extract(acceptor);
+
+ expect(
+ acceptor.jsonAt("content/example/block/ruby_ore.json"),
+ ).toMatchSnapshot("basic block definition");
+ expect(
+ acceptor.jsonAt("data/example/loot_tables/blocks/ruby_ore.json"),
+ ).toMatchSnapshot("basic loot table");
+ expect(
+ acceptor.jsonAt("assets/example/blockstates/ruby_ore.json"),
+ ).toMatchSnapshot("basic blockstate");
+ expect(
+ acceptor.jsonAt("assets/example/models/block/ruby_ore.json"),
+ ).toMatchSnapshot("basic block model");
+ });
+
+ it("uses custom definition types", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.basic("example:sapphire_block", {
+ material: "stone",
+ type: "example_block",
+ });
+
+ await resolver.extract(acceptor);
+
+ expect(
+ acceptor.jsonAt("content/example/block/sapphire_block.json"),
+ ).toMatchObject({ type: "example_block" });
+ });
+
+ it("correctly resolves copy reference for block properties", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.basic("example:ruby_ore", {
+ material: "stone",
+ copy: "minecraft:emerald_ore",
+ });
+
+ await resolver.extract(acceptor);
+
+ expect(
+ acceptor.jsonAt("content/example/block/ruby_ore.json"),
+ ).toMatchSnapshot("block definition using reference for properties");
+ });
+});
diff --git a/modules/content/test/blockItem.test.ts b/modules/content/test/blockItem.test.ts
new file mode 100644
index 0000000..ca6d94b
--- /dev/null
+++ b/modules/content/test/blockItem.test.ts
@@ -0,0 +1,96 @@
+import { type LoaderContext } from "@adeficior/data-modifier-core";
+import { createLogger } from "@adeficior/pack-resolver";
+import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { afterEach, describe, expect, it } from "bun:test";
+import { createItemDefinitionEmitter } from "./util/emitters";
+
+const version = "1.20.1";
+const context: LoaderContext = { logger: createLogger() };
+const { emitter, resolver, reset } = createItemDefinitionEmitter(
+ version,
+ context,
+);
+
+afterEach(reset);
+
+describe("block item definitions", () => {
+ it("generates block resources for block item definitions", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.blockItem("example:ruby_block", {
+ rarity: "rare",
+ block: (blocks) => blocks.basic({ material: "metal" }, { loot: false }),
+ });
+
+ await resolver.extract(acceptor);
+
+ expect(
+ acceptor.jsonAt("content/example/item/ruby_block.json"),
+ ).toMatchSnapshot("basic item definition");
+ expect(
+ acceptor.jsonAt("assets/example/models/item/ruby_block.json"),
+ ).toMatchSnapshot("basic item model");
+
+ expect(
+ acceptor.jsonAt("data/example/loot_tables/blocks/ruby_block.json"),
+ ).toBeNull();
+ expect(acceptor.jsonAt("content/example/block/ruby_block.json")).toBeNull();
+ expect(
+ acceptor.jsonAt("assets/example/blockstates/ruby_block.json"),
+ ).toMatchSnapshot("included blockstate");
+ expect(
+ acceptor.jsonAt("assets/example/models/block/ruby_block.json"),
+ ).toMatchSnapshot("included block model");
+ });
+
+ it("uses custom definition types", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.blockItem("example:sapphire_ore", {
+ type: "example_block_item",
+ block: (blocks) =>
+ blocks.basic({ material: "stone", type: "example_block" }),
+ });
+
+ await resolver.extract(acceptor);
+
+ expect(
+ acceptor.jsonAt("content/example/item/sapphire_ore.json"),
+ ).toMatchObject({
+ type: "example_block_item",
+ block: {
+ type: "example_block",
+ },
+ });
+ });
+
+ it("uses custom definition types for nested block", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.basic("example:sapphire", { type: "example" });
+ emitter.blockItem("example:sapphire_ore", {
+ type: "example_block_item",
+ block: (blocks) =>
+ blocks.add({
+ type: "example_block",
+ properties: {
+ material: "ice",
+ },
+ }),
+ });
+
+ await resolver.extract(acceptor);
+
+ expect(acceptor.jsonAt("content/example/item/sapphire.json")).toMatchObject(
+ { type: "example" },
+ );
+ expect(
+ acceptor.jsonAt("content/example/item/sapphire_ore.json"),
+ ).toMatchObject({
+ type: "example_block_item",
+ block: {
+ type: "example_block",
+ },
+ });
+ });
+});
diff --git a/modules/content/test/item.test.ts b/modules/content/test/item.test.ts
new file mode 100644
index 0000000..363651e
--- /dev/null
+++ b/modules/content/test/item.test.ts
@@ -0,0 +1,46 @@
+import { type LoaderContext } from "@adeficior/data-modifier-core";
+import { createLogger } from "@adeficior/pack-resolver";
+import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { afterEach, describe, expect, it } from "bun:test";
+import { createItemDefinitionEmitter } from "./util/emitters";
+
+const version = "1.20.1";
+const context: LoaderContext = { logger: createLogger() };
+const { emitter, resolver, reset } = createItemDefinitionEmitter(
+ version,
+ context,
+);
+
+afterEach(reset);
+
+describe("item definitions", () => {
+ it("generates additional resources for item definitions", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.basic("example:ruby", {
+ rarity: "rare",
+ stack_size: 24,
+ });
+
+ await resolver.extract(acceptor);
+
+ expect(acceptor.jsonAt("content/example/item/ruby.json")).toMatchSnapshot(
+ "basic item definition",
+ );
+ expect(
+ acceptor.jsonAt("assets/example/models/item/ruby.json"),
+ ).toMatchSnapshot("basic item model");
+ });
+
+ it("uses custom definition types", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.basic("example:sapphire", { type: "example" });
+
+ await resolver.extract(acceptor);
+
+ expect(acceptor.jsonAt("content/example/item/sapphire.json")).toMatchObject(
+ { type: "example" },
+ );
+ });
+});
diff --git a/modules/content/test/util/emitters.ts b/modules/content/test/util/emitters.ts
new file mode 100644
index 0000000..9555fbe
--- /dev/null
+++ b/modules/content/test/util/emitters.ts
@@ -0,0 +1,85 @@
+import {
+ CombinedEmitters,
+ packFormatOf,
+ type LoaderContext,
+ type SemVerInput,
+} from "@adeficior/data-modifier-core";
+import {
+ mockRegistryLookup,
+ mockRegistryProvider,
+} from "@adeficior/data-modifier-core/testing";
+import { mockPredicates } from "@adeficior/data-modifier-ingredients/testing";
+import { type Resolver } from "@adeficior/pack-resolver";
+import { LootTableEmitter } from "../../../loot/src/emitter";
+import { BlockstateEmitter } from "../../../models/src/emitter/blockstates";
+import { ModelEmitter } from "../../../models/src/emitter/models";
+import { BlockDefinitionEmitter } from "../../src/emitter/blockDefinition";
+import { ItemDefinitionEmitter } from "../../src/emitter/itemDefinition";
+
+// TODO move to testing package and use in recipes
+type ExternalResolver = {
+ emitter: Omit;
+ resolver: Resolver;
+ reset: () => void;
+};
+
+export function createBlockDefinitionEmitter(
+ version: SemVerInput,
+ context: LoaderContext,
+): ExternalResolver {
+ // TODO mock instead?
+ const emitters = new CombinedEmitters();
+ const blockModels = emitters.add(new ModelEmitter("block"));
+ const blockStates = emitters.add(new BlockstateEmitter());
+ const loot = emitters.add(
+ new LootTableEmitter(
+ packFormatOf(version),
+ mockRegistryProvider(),
+ mockRegistryLookup(),
+ mockPredicates(),
+ ),
+ );
+
+ const emitter = emitters.add(
+ new BlockDefinitionEmitter(blockModels, blockStates, loot),
+ );
+
+ const resolver = emitters.resolver(context);
+ const reset = () => emitter.clear();
+
+ return { emitter, resolver, reset };
+}
+
+export function createItemDefinitionEmitter(
+ version: SemVerInput,
+ context: LoaderContext,
+): ExternalResolver {
+ // TODO mock instead?
+ const emitters = new CombinedEmitters();
+ const itemModels = emitters.add(new ModelEmitter("item"));
+ const blockModels = emitters.add(new ModelEmitter("block"));
+ const blockStates = emitters.add(new BlockstateEmitter());
+ const loot = emitters.add(
+ new LootTableEmitter(
+ packFormatOf(version),
+ mockRegistryProvider(),
+ mockRegistryLookup(),
+ mockPredicates(),
+ ),
+ );
+
+ const emitter = emitters.add(
+ new ItemDefinitionEmitter(
+ // TODO mock instead?
+ itemModels,
+ blockModels,
+ blockStates,
+ loot,
+ ),
+ );
+
+ const resolver = emitters.resolver(context);
+ const reset = () => emitter.clear();
+
+ return { emitter, resolver, reset };
+}
diff --git a/modules/create/package.json b/modules/create/package.json
new file mode 100644
index 0000000..2db7894
--- /dev/null
+++ b/modules/create/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@adeficior/data-modifier-create",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/modules/create/src/index.ts b/modules/create/src/index.ts
new file mode 100644
index 0000000..5638286
--- /dev/null
+++ b/modules/create/src/index.ts
@@ -0,0 +1,3 @@
+export { default } from "./module";
+export * from "./serializer/assembly";
+export * from "./serializer/processing";
diff --git a/modules/create/src/module.ts b/modules/create/src/module.ts
new file mode 100644
index 0000000..bcee601
--- /dev/null
+++ b/modules/create/src/module.ts
@@ -0,0 +1,13 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { name } from "../package.json";
+import { registerParsers } from "./registration";
+
+export default defineModule({
+ importModule: name,
+ dependencies: {
+ "@adeficior/data-modifier-recipes": "required",
+ },
+ setup: (pack) => {
+ pack.hook("recipes:register-parser", registerParsers);
+ },
+});
diff --git a/modules/create/src/registration.ts b/modules/create/src/registration.ts
new file mode 100644
index 0000000..0a6c636
--- /dev/null
+++ b/modules/create/src/registration.ts
@@ -0,0 +1,25 @@
+import { type RegisterRecipeParser } from "@adeficior/data-modifier-recipes";
+import { ShapedParser } from "@adeficior/data-modifier-recipes";
+import { AssemblyRecipeParser } from "./serializer/assembly";
+import { CreateProcessingRecipeParser } from "./serializer/processing";
+
+export function registerParsers(event: RegisterRecipeParser) {
+ event.register("create:mixing", new CreateProcessingRecipeParser());
+ event.register("create:pressing", new CreateProcessingRecipeParser());
+ event.register("create:emptying", new CreateProcessingRecipeParser());
+ event.register("create:crushing", new CreateProcessingRecipeParser());
+ event.register("create:milling", new CreateProcessingRecipeParser());
+ event.register("create:compacting", new CreateProcessingRecipeParser());
+ event.register("create:filling", new CreateProcessingRecipeParser());
+ event.register("create:cutting", new CreateProcessingRecipeParser());
+ event.register("create:item_application", new CreateProcessingRecipeParser());
+ event.register(
+ "create:sandpaper_polishing",
+ new CreateProcessingRecipeParser(),
+ );
+ event.register("create:deploying", new CreateProcessingRecipeParser());
+ event.register("create:splashing", new CreateProcessingRecipeParser());
+ event.register("create:haunting", new CreateProcessingRecipeParser());
+ event.register("create:mechanical_crafting", new ShapedParser());
+ event.register("create:sequenced_assembly", new AssemblyRecipeParser());
+}
diff --git a/src/parser/recipe/create/assembly.ts b/modules/create/src/serializer/assembly.ts
similarity index 86%
rename from src/parser/recipe/create/assembly.ts
rename to modules/create/src/serializer/assembly.ts
index b40530b..b299d9c 100644
--- a/src/parser/recipe/create/assembly.ts
+++ b/modules/create/src/serializer/assembly.ts
@@ -1,12 +1,15 @@
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { Result } from "../../../common/result/index.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type {
- RecipeHolder,
- RecipeModifier,
- RecipeParseContext,
-} from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
+import {
+ type Ingredient,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
+import {
+ Recipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeHolder,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
export type AssemblyRecipeDefinition = RecipeDefinition &
Readonly<{
diff --git a/src/parser/recipe/create/processing.ts b/modules/create/src/serializer/processing.ts
similarity index 69%
rename from src/parser/recipe/create/processing.ts
rename to modules/create/src/serializer/processing.ts
index 4fd52c5..42e5c1c 100644
--- a/src/parser/recipe/create/processing.ts
+++ b/modules/create/src/serializer/processing.ts
@@ -1,5 +1,7 @@
-import type { ManyToManyRecipeDefinition } from "../manyToMany.js";
-import { ManyToManyRecipeParser } from "../manyToMany.js";
+import {
+ ManyToManyRecipeParser,
+ type ManyToManyRecipeDefinition,
+} from "@adeficior/data-modifier-recipes";
export type CreateProcessingRecipeDefinition = ManyToManyRecipeDefinition &
Readonly<{
diff --git a/modules/create/test/1.20.1/__snapshots__/emitter.test.ts.snap b/modules/create/test/1.20.1/__snapshots__/emitter.test.ts.snap
new file mode 100644
index 0000000..86d97a1
--- /dev/null
+++ b/modules/create/test/1.20.1/__snapshots__/emitter.test.ts.snap
@@ -0,0 +1,73 @@
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
+
+exports[`recipe ingredient replacement replaces ingredients in create recipes: modified create:raw_zinc_block recipe 1`] = `
+{
+ "category": "misc",
+ "key": {
+ "C": {
+ "tag": "forge:raw_materials/iron",
+ },
+ },
+ "pattern": [
+ "CCC",
+ "CCC",
+ "CCC",
+ ],
+ "result": {
+ "item": "create:raw_zinc_block",
+ },
+ "show_notification": true,
+ "type": "minecraft:crafting_shaped",
+}
+`;
+
+exports[`recipe ingredient replacement replaces ingredients in create recipes: modified create:raw_zinc recipe 1`] = `
+{
+ "ingredients": [
+ {
+ "tag": "forge:raw_materials/iron",
+ },
+ ],
+ "processingTime": 400,
+ "results": [
+ {
+ "item": "create:crushed_raw_zinc",
+ },
+ {
+ "chance": 0.75,
+ "item": "create:experience_nugget",
+ },
+ ],
+ "type": "create:crushing",
+}
+`;
+
+exports[`recipe ingredient replacement replaces ingredients in create recipes: modified create:zinc_ingot_from_raw_ore recipe 1`] = `
+{
+ "category": "misc",
+ "cookingtime": 100,
+ "experience": 0.7,
+ "ingredient": {
+ "tag": "forge:raw_materials/iron",
+ },
+ "result": {
+ "item": "create:zinc_ingot",
+ },
+ "type": "minecraft:blasting",
+}
+`;
+
+exports[`recipe ingredient replacement replaces ingredients in create recipes: modified create:zinc_ingot_from_raw_ore recipe 2`] = `
+{
+ "category": "misc",
+ "cookingtime": 200,
+ "experience": 0.7,
+ "ingredient": {
+ "tag": "forge:raw_materials/iron",
+ },
+ "result": {
+ "item": "create:zinc_ingot",
+ },
+ "type": "minecraft:smelting",
+}
+`;
diff --git a/modules/create/test/1.20.1/emitter.test.ts b/modules/create/test/1.20.1/emitter.test.ts
new file mode 100644
index 0000000..6487051
--- /dev/null
+++ b/modules/create/test/1.20.1/emitter.test.ts
@@ -0,0 +1,41 @@
+import { ItemTagIngredient } from "@adeficior/data-modifier-ingredients";
+import { setupRecipeEmitter } from "@adeficior/data-modifier-recipes/testing";
+import { createTestAcceptor } from "@adeficior/pack-resolver/testing";
+import { describe, expect, it } from "bun:test";
+import { basename } from "node:path";
+import { registerParsers } from "../../src/registration";
+
+const version = basename(import.meta.dir);
+const { emitter, resolver } = setupRecipeEmitter(version, registerParsers);
+
+describe("recipe ingredient replacement", () => {
+ it("replaces ingredients in create recipes", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.replaceIngredient(
+ "#forge:raw_materials/zinc",
+ new ItemTagIngredient("forge:raw_materials/iron"),
+ );
+
+ await resolver.extract(acceptor);
+
+ expect(
+ acceptor.jsonAt(
+ "data/create/recipes/crafting/materials/raw_zinc_block.json",
+ ),
+ ).toMatchSnapshot("modified create:raw_zinc_block recipe");
+ expect(
+ acceptor.jsonAt("data/create/recipes/crushing/raw_zinc.json"),
+ ).toMatchSnapshot("modified create:raw_zinc recipe");
+ expect(
+ acceptor.jsonAt(
+ "data/create/recipes/blasting/zinc_ingot_from_raw_ore.json",
+ ),
+ ).toMatchSnapshot("modified create:zinc_ingot_from_raw_ore recipe");
+ expect(
+ acceptor.jsonAt(
+ "data/create/recipes/smelting/zinc_ingot_from_raw_ore.json",
+ ),
+ ).toMatchSnapshot("modified create:zinc_ingot_from_raw_ore recipe");
+ });
+});
diff --git a/modules/create/test/1.20.1/loader.test.ts b/modules/create/test/1.20.1/loader.test.ts
new file mode 100644
index 0000000..7182670
--- /dev/null
+++ b/modules/create/test/1.20.1/loader.test.ts
@@ -0,0 +1,24 @@
+import { setupRecipeLoader } from "@adeficior/data-modifier-recipes/testing";
+import { provided } from "@adeficior/testing";
+import { expect, it } from "bun:test";
+import { basename } from "node:path";
+import { registerParsers } from "../../src/registration";
+import { recipesIds } from "../providers/recipeIds";
+
+const version = basename(import.meta.dir);
+
+const { loader, logger } = setupRecipeLoader(version, registerParsers);
+
+provided("loads recipes", recipesIds(), (id) => {
+ expect(loader.get(id)).toBeDefined();
+});
+
+it("has no unknown recipe loaders", () => {
+ expect(loader.unknownRecipeTypes().map((it) => it.type)).toBeEmpty();
+});
+
+it("does not encounter any errors", () => {
+ expect(logger.trace).not.toHaveBeenCalled();
+ expect(logger.warn).not.toHaveBeenCalled();
+ expect(logger.error).not.toHaveBeenCalled();
+});
diff --git a/modules/create/test/1.21.1/loader.test.ts b/modules/create/test/1.21.1/loader.test.ts
new file mode 100644
index 0000000..bded544
--- /dev/null
+++ b/modules/create/test/1.21.1/loader.test.ts
@@ -0,0 +1,26 @@
+import { setupRecipeLoader } from "@adeficior/data-modifier-recipes/testing";
+import { provided } from "@adeficior/testing";
+import { expect, it } from "bun:test";
+import { basename } from "node:path";
+import { registerParsers } from "../../src/registration";
+import { recipesIds } from "../providers/recipeIds";
+
+const version = basename(import.meta.dir);
+
+const { loader, logger } = setupRecipeLoader(version, registerParsers, [
+ "create",
+]);
+
+provided("loads recipes", recipesIds(), (id) => {
+ expect(loader.get(id)).toBeDefined();
+});
+
+it("has no unknown recipe loaders", () => {
+ expect(loader.unknownRecipeTypes().map((it) => it.type)).toBeEmpty();
+});
+
+it("does not encounter any errors", () => {
+ expect(logger.trace).not.toHaveBeenCalled();
+ expect(logger.warn).not.toHaveBeenCalled();
+ expect(logger.error).not.toHaveBeenCalled();
+});
diff --git a/modules/create/test/providers/recipeIds.ts b/modules/create/test/providers/recipeIds.ts
new file mode 100644
index 0000000..39da49e
--- /dev/null
+++ b/modules/create/test/providers/recipeIds.ts
@@ -0,0 +1,20 @@
+import { type IdInput } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
+
+export function* recipesIds(): DataProvider<[IdInput]> {
+ yield ["compacting", "create:compacting/granite_from_flint"];
+ yield ["crushing", "create:crushing/deepslate_gold_ore"];
+ yield ["cutting", "create:cutting/andesite_alloy"];
+ yield ["deploying", "create:deploying/cogwheel"];
+ yield ["emptying", "create:emptying/honey_bottle"];
+ yield ["filling", "create:filling/blaze_cake"];
+ yield ["haunting", "create:haunting/nether_brick"];
+ yield ["item application", "create:item_application/railway_casing"];
+ yield ["mechanical crafting", "create:mechanical_crafting/crushing_wheel"];
+ yield ["milling", "create:milling/charcoal"];
+ yield ["mixing", "create:mixing/tea"];
+ yield ["pressing", "create:pressing/path"];
+ yield ["sandpaper polishing", "create:sandpaper_polishing/rose_quartz"];
+ yield ["sequenced assembly", "create:sequenced_assembly/precision_mechanism"];
+ yield ["splashing", "create:splashing/soul_sand"];
+}
diff --git a/modules/create/test/resources/1.20.1/default/config.json b/modules/create/test/resources/1.20.1/default/config.json
new file mode 100644
index 0000000..67077e1
--- /dev/null
+++ b/modules/create/test/resources/1.20.1/default/config.json
@@ -0,0 +1,10 @@
+{
+ "packs": {
+ "create": {
+ "paths": [
+ "src/generated/resources",
+ "src/main/resources"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/create/test/resources/1.20.1/default/create b/modules/create/test/resources/1.20.1/default/create
new file mode 160000
index 0000000..5881242
--- /dev/null
+++ b/modules/create/test/resources/1.20.1/default/create
@@ -0,0 +1 @@
+Subproject commit 588124239141029c5321afd38d20c2eaf872b32b
diff --git a/modules/create/test/resources/1.21.1/default/config.json b/modules/create/test/resources/1.21.1/default/config.json
new file mode 100644
index 0000000..67077e1
--- /dev/null
+++ b/modules/create/test/resources/1.21.1/default/config.json
@@ -0,0 +1,10 @@
+{
+ "packs": {
+ "create": {
+ "paths": [
+ "src/generated/resources",
+ "src/main/resources"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/create/test/resources/1.21.1/default/create b/modules/create/test/resources/1.21.1/default/create
new file mode 160000
index 0000000..87b3c6a
--- /dev/null
+++ b/modules/create/test/resources/1.21.1/default/create
@@ -0,0 +1 @@
+Subproject commit 87b3c6a65fd00c023a07b37b0353144bc7e6a5bf
diff --git a/modules/farmersdelight/package.json b/modules/farmersdelight/package.json
new file mode 100644
index 0000000..226f6bf
--- /dev/null
+++ b/modules/farmersdelight/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@adeficior/data-modifier-farmersdelight",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-recipes": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/modules/farmersdelight/src/index.ts b/modules/farmersdelight/src/index.ts
new file mode 100644
index 0000000..090ff90
--- /dev/null
+++ b/modules/farmersdelight/src/index.ts
@@ -0,0 +1,3 @@
+export { default } from "./module";
+export * from "./serializer/cooking";
+export * from "./serializer/cutting";
diff --git a/modules/farmersdelight/src/module.ts b/modules/farmersdelight/src/module.ts
new file mode 100644
index 0000000..bcee601
--- /dev/null
+++ b/modules/farmersdelight/src/module.ts
@@ -0,0 +1,13 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { name } from "../package.json";
+import { registerParsers } from "./registration";
+
+export default defineModule({
+ importModule: name,
+ dependencies: {
+ "@adeficior/data-modifier-recipes": "required",
+ },
+ setup: (pack) => {
+ pack.hook("recipes:register-parser", registerParsers);
+ },
+});
diff --git a/modules/farmersdelight/src/registration.ts b/modules/farmersdelight/src/registration.ts
new file mode 100644
index 0000000..7e57147
--- /dev/null
+++ b/modules/farmersdelight/src/registration.ts
@@ -0,0 +1,9 @@
+import { type RegisterRecipeParser } from "@adeficior/data-modifier-recipes";
+import { CookingRecipeParser } from "./serializer/cooking";
+import { CuttingRecipeParser } from "./serializer/cutting";
+
+export function registerParsers(event: RegisterRecipeParser) {
+ event.register("farmersdelight:cooking", new CookingRecipeParser());
+ event.register("farmersdelight:cutting", new CuttingRecipeParser());
+ event.register("farmersrespite:brewing", new CookingRecipeParser());
+}
diff --git a/src/parser/recipe/farmersdelight/cooking.ts b/modules/farmersdelight/src/serializer/cooking.ts
similarity index 72%
rename from src/parser/recipe/farmersdelight/cooking.ts
rename to modules/farmersdelight/src/serializer/cooking.ts
index 5aa3f71..fbfae4f 100644
--- a/src/parser/recipe/farmersdelight/cooking.ts
+++ b/modules/farmersdelight/src/serializer/cooking.ts
@@ -1,9 +1,15 @@
+import {
+ type Ingredient,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
+import {
+ Recipe,
+ RecipeParser,
+ type RecipeDefinition,
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
import { notNull } from "@adeficior/pack-resolver";
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { Result } from "../../../common/result/index.js";
-import type { RecipeDefinition } from "../../../schema/data/recipe.js";
-import type { RecipeModifier, RecipeParseContext } from "../index.js";
-import RecipeParser, { Recipe } from "../index.js";
export type CookingRecipeDefinition = RecipeDefinition &
Readonly<{
@@ -46,12 +52,12 @@ export class CookingRecipe extends Recipe {
return {
ingredients: context.ingredients.serializeList(this.ingredients),
result: context.results.serialize(this.result),
- container: context.ingredients.serializeOptional(this.container),
+ container: context.results.serializeOptional(this.container?.asResult()),
};
}
}
-export default class CookingRecipeParser extends RecipeParser<
+export class CookingRecipeParser extends RecipeParser<
CookingRecipeDefinition,
CookingRecipe
> {
@@ -63,9 +69,9 @@ export default class CookingRecipeParser extends RecipeParser<
definition.ingredients,
);
const result = context.results.deserialize(definition.result);
- const container = context.ingredients.deserializeOptional(
- definition.container,
- );
+ const container = context.results
+ .deserializeOptional(definition.container)
+ ?.asIngredient();
return new CookingRecipe(ingredients, result, container);
}
}
diff --git a/src/parser/recipe/farmersdelight/cutting.ts b/modules/farmersdelight/src/serializer/cutting.ts
similarity index 71%
rename from src/parser/recipe/farmersdelight/cutting.ts
rename to modules/farmersdelight/src/serializer/cutting.ts
index 79bf7a9..503645c 100644
--- a/src/parser/recipe/farmersdelight/cutting.ts
+++ b/modules/farmersdelight/src/serializer/cutting.ts
@@ -1,18 +1,15 @@
-import type { Ingredient } from "../../../common/ingredient/index.js";
-import type { Result } from "../../../common/result/index.js";
-import RecipeParser, {
- type RecipeModifier,
- type RecipeParseContext,
-} from "../index.js";
+import {
+ type Ingredient,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
import {
ManyToManyRecipe,
+ RecipeParser,
type ManyToManyRecipeDefinition,
-} from "../manyToMany.js";
-
-export type ToolInput = Readonly<{
- type: "farmersdelight:tool_action";
- action: string;
-}>;
+ type RecipeModifier,
+ type RecipeParseContext,
+} from "@adeficior/data-modifier-recipes";
+import { resultSerializerModules } from "./module";
export type CuttingRecipeDefinition = Omit<
ManyToManyRecipeDefinition,
@@ -23,16 +20,6 @@ export type CuttingRecipeDefinition = Omit<
result: ManyToManyRecipeDefinition["results"];
}>;
-// TODO create actual input
-// function isToolInput(input: T | ToolInput): input is ToolInput {
-// return (
-// !!input &&
-// typeof input === "object" &&
-// "type" in input &&
-// input.type === "farmersdelight:tool_action"
-// );
-// }
-
export class CuttingRecipe extends ManyToManyRecipe {
constructor(
ingredients: Ingredient[],
@@ -66,10 +53,14 @@ export class CuttingRecipe extends ManyToManyRecipe {
}
}
-export default class CuttingRecipeParser extends RecipeParser<
+export class CuttingRecipeParser extends RecipeParser<
CuttingRecipeDefinition,
CuttingRecipe
> {
+ override resultModules() {
+ return resultSerializerModules;
+ }
+
deserialize(
definition: CuttingRecipeDefinition,
context: RecipeParseContext,
diff --git a/modules/farmersdelight/src/serializer/module.ts b/modules/farmersdelight/src/serializer/module.ts
new file mode 100644
index 0000000..a68deb6
--- /dev/null
+++ b/modules/farmersdelight/src/serializer/module.ts
@@ -0,0 +1,34 @@
+import { IdSchema } from "@adeficior/data-modifier-core";
+import {
+ createSerializerModule,
+ isObjectWith,
+} from "@adeficior/data-modifier-core/serializer";
+import {
+ ChanceSchema,
+ CountSchema,
+ ItemResult,
+ type Result,
+} from "@adeficior/data-modifier-ingredients";
+import * as z from "zod";
+
+const chanceResultSchema = z.object({
+ chance: ChanceSchema,
+ item: z.object({
+ id: IdSchema,
+ count: CountSchema,
+ }),
+});
+
+const resultSerializer44 = createSerializerModule((builder) => {
+ builder.register(
+ ItemResult,
+ isObjectWith("item"),
+ chanceResultSchema,
+ ({ item, chance }) => new ItemResult(item.id, item.count, chance),
+ ({ id, count, chance }) => ({ item: { id, count }, chance }),
+ );
+});
+
+export const resultSerializerModules = {
+ 44: resultSerializer44,
+};
diff --git a/modules/farmersdelight/test/1.20.1/loader.test.ts b/modules/farmersdelight/test/1.20.1/loader.test.ts
new file mode 100644
index 0000000..70e1ddb
--- /dev/null
+++ b/modules/farmersdelight/test/1.20.1/loader.test.ts
@@ -0,0 +1,33 @@
+import { type IdInput } from "@adeficior/data-modifier-core";
+import { setupRecipeLoader } from "@adeficior/data-modifier-recipes/testing";
+import { provided, type DataProvider } from "@adeficior/testing";
+import { expect, it } from "bun:test";
+import { basename } from "node:path";
+import { registerParsers } from "../../src/registration";
+
+const version = basename(import.meta.dir);
+
+const { loader, logger } = setupRecipeLoader(version, registerParsers);
+
+function* recipes(): DataProvider<[IdInput]> {
+ yield ["cutting", "farmersdelight:cutting/blue_orchid"];
+ yield ["cooking", "farmersdelight:cooking/pasta_with_mutton_chop"];
+ yield [
+ "cooking with difference ingredient",
+ "farmersdelight:cooking/ratatouille",
+ ];
+}
+
+provided("loads recipes", recipes(), (id) => {
+ expect(loader.get(id)).toBeDefined();
+});
+
+it("has no unknown recipe loaders", () => {
+ expect(loader.unknownRecipeTypes().map((it) => it.type)).toBeEmpty();
+});
+
+it("does not encounter any errors", () => {
+ expect(logger.trace).not.toHaveBeenCalled();
+ expect(logger.warn).not.toHaveBeenCalled();
+ expect(logger.error).not.toHaveBeenCalled();
+});
diff --git a/modules/farmersdelight/test/1.21.1/loader.test.ts b/modules/farmersdelight/test/1.21.1/loader.test.ts
new file mode 100644
index 0000000..70e1ddb
--- /dev/null
+++ b/modules/farmersdelight/test/1.21.1/loader.test.ts
@@ -0,0 +1,33 @@
+import { type IdInput } from "@adeficior/data-modifier-core";
+import { setupRecipeLoader } from "@adeficior/data-modifier-recipes/testing";
+import { provided, type DataProvider } from "@adeficior/testing";
+import { expect, it } from "bun:test";
+import { basename } from "node:path";
+import { registerParsers } from "../../src/registration";
+
+const version = basename(import.meta.dir);
+
+const { loader, logger } = setupRecipeLoader(version, registerParsers);
+
+function* recipes(): DataProvider<[IdInput]> {
+ yield ["cutting", "farmersdelight:cutting/blue_orchid"];
+ yield ["cooking", "farmersdelight:cooking/pasta_with_mutton_chop"];
+ yield [
+ "cooking with difference ingredient",
+ "farmersdelight:cooking/ratatouille",
+ ];
+}
+
+provided("loads recipes", recipes(), (id) => {
+ expect(loader.get(id)).toBeDefined();
+});
+
+it("has no unknown recipe loaders", () => {
+ expect(loader.unknownRecipeTypes().map((it) => it.type)).toBeEmpty();
+});
+
+it("does not encounter any errors", () => {
+ expect(logger.trace).not.toHaveBeenCalled();
+ expect(logger.warn).not.toHaveBeenCalled();
+ expect(logger.error).not.toHaveBeenCalled();
+});
diff --git a/modules/farmersdelight/test/resources/1.20.1/default/config.json b/modules/farmersdelight/test/resources/1.20.1/default/config.json
new file mode 100644
index 0000000..c3a359b
--- /dev/null
+++ b/modules/farmersdelight/test/resources/1.20.1/default/config.json
@@ -0,0 +1,10 @@
+{
+ "packs": {
+ "farmersdelight": {
+ "paths": [
+ "src/generated/resources",
+ "src/main/resources"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/farmersdelight/test/resources/1.20.1/default/farmersdelight b/modules/farmersdelight/test/resources/1.20.1/default/farmersdelight
new file mode 160000
index 0000000..00d826c
--- /dev/null
+++ b/modules/farmersdelight/test/resources/1.20.1/default/farmersdelight
@@ -0,0 +1 @@
+Subproject commit 00d826c390f772bd762b1a7eb5da4e5e30375c10
diff --git a/modules/farmersdelight/test/resources/1.21.1/default/config.json b/modules/farmersdelight/test/resources/1.21.1/default/config.json
new file mode 100644
index 0000000..c3a359b
--- /dev/null
+++ b/modules/farmersdelight/test/resources/1.21.1/default/config.json
@@ -0,0 +1,10 @@
+{
+ "packs": {
+ "farmersdelight": {
+ "paths": [
+ "src/generated/resources",
+ "src/main/resources"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/farmersdelight/test/resources/1.21.1/default/farmersdelight b/modules/farmersdelight/test/resources/1.21.1/default/farmersdelight
new file mode 160000
index 0000000..57fd50f
--- /dev/null
+++ b/modules/farmersdelight/test/resources/1.21.1/default/farmersdelight
@@ -0,0 +1 @@
+Subproject commit 57fd50f23249b008ca694a4df36d85e0dcdff64f
diff --git a/modules/ingredients/package.json b/modules/ingredients/package.json
new file mode 100644
index 0000000..da073dc
--- /dev/null
+++ b/modules/ingredients/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "@adeficior/data-modifier-ingredients",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js",
+ "./testing": "./dist/testing/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "typesVersions": {
+ "*": {
+ "testing": [
+ "./dist/testing/index.d.ts"
+ ]
+ }
+ },
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@types/lodash-es": "catalog:"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ "lodash-es": "catalog:",
+ "@adeficior/data-modifier-tags": "workspace:*",
+ "@adeficior/data-modifier-core": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/src/common/fields.ts b/modules/ingredients/src/fields.ts
similarity index 91%
rename from src/common/fields.ts
rename to modules/ingredients/src/fields.ts
index c0cfa6e..d0bf885 100644
--- a/src/common/fields.ts
+++ b/modules/ingredients/src/fields.ts
@@ -1,4 +1,4 @@
-import z from "zod";
+import * as z from "zod";
export const CountSchema = z.codec(
z.number().int().positive().optional(),
diff --git a/modules/ingredients/src/index.ts b/modules/ingredients/src/index.ts
new file mode 100644
index 0000000..a7198f8
--- /dev/null
+++ b/modules/ingredients/src/index.ts
@@ -0,0 +1,16 @@
+export { default } from "./module";
+
+export * from "./fields";
+export * from "./ingredient/impl";
+export type * from "./ingredient/input";
+export * from "./result/impl";
+export type * from "./result/input";
+export * from "./units";
+
+export type { IngredientSerializer } from "./serializer/ingredients/index";
+export * from "./serializer/ingredients/ingredientMap";
+export type { ResultSerializer } from "./serializer/results/index";
+export type * from "./serializer/withModules";
+
+export type { Predicates } from "./predicates";
+export type { IngredientFilter } from "./predicates/ingredients";
diff --git a/src/common/ingredient/index.ts b/modules/ingredients/src/ingredient/impl.ts
similarity index 92%
rename from src/common/ingredient/index.ts
rename to modules/ingredients/src/ingredient/impl.ts
index f2f2ccc..0b0d56e 100644
--- a/src/common/ingredient/index.ts
+++ b/modules/ingredients/src/ingredient/impl.ts
@@ -1,23 +1,29 @@
-import type {
- BlockId,
- FluidId,
- ItemId,
- RegistryId,
-} from "@adeficior/data-modifier/generated";
-import { uniq } from "lodash-es";
-import type RegistryLookup from "../../loader/registry";
import {
encodeId,
toTag,
type IdInput,
type NormalizedId,
+ type Registered,
+ type RegistryIds,
+ type RegistryLookup,
type TagId,
-} from "../id";
-import type { InputOutput, RegistryIds } from "../inputOutput";
-import { BlockResult, FluidResult, ItemResult, type Result } from "../result";
+} from "@adeficior/data-modifier-core";
+import {
+ type BlockId,
+ type FluidId,
+ type ItemId,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import { uniq } from "lodash-es";
+import {
+ BlockResult,
+ FluidResult,
+ ItemResult,
+ type Result,
+} from "../result/impl";
import { BUCKET } from "../units";
-export abstract class Ingredient implements InputOutput {
+export abstract class Ingredient implements Registered {
validate(_: RegistryLookup): void {}
abstract ids(): RegistryIds;
abstract asResult(): Result;
diff --git a/modules/ingredients/src/ingredient/input.ts b/modules/ingredients/src/ingredient/input.ts
new file mode 100644
index 0000000..2f1acc9
--- /dev/null
+++ b/modules/ingredients/src/ingredient/input.ts
@@ -0,0 +1,4 @@
+import { type ItemId } from "@adeficior/data-modifier/generated";
+import { type Ingredient } from "./impl";
+
+export type IngredientInput = ItemId | Ingredient | IngredientInput[];
diff --git a/modules/ingredients/src/module.ts b/modules/ingredients/src/module.ts
new file mode 100644
index 0000000..e3c8e72
--- /dev/null
+++ b/modules/ingredients/src/module.ts
@@ -0,0 +1,60 @@
+import { defineModule } from "@adeficior/data-modifier-core";
+import { name } from "../package.json";
+import { createPredicates, type Predicates } from "./predicates";
+import {
+ createIngredientSerializer,
+ type IngredientSerializer,
+} from "./serializer/ingredients";
+import {
+ createResultSerializer,
+ type ResultSerializer,
+} from "./serializer/results";
+
+export default defineModule<{
+ services: {
+ "serializer:results": ResultSerializer;
+ "serializer:ingredients": IngredientSerializer;
+ predicates: Predicates;
+ };
+}>({
+ importModule: name,
+ dependencies: {
+ // TODO could be optional somehow
+ "@adeficior/data-modifier-tags": "required",
+ },
+ types: {
+ services: {
+ "serializer:results": "ResultSerializer",
+ "serializer:ingredients": "IngredientSerializer",
+ predicates: "Predicates",
+ },
+ },
+ setup: (pack) => {
+ const ingredientSerializer = pack.service(
+ "serializer:ingredients",
+ (container) =>
+ createIngredientSerializer(
+ pack.options.packFormat,
+ container.get("registries"),
+ ),
+ );
+
+ pack.service("serializer:results", (container) =>
+ createResultSerializer(
+ pack.options.packFormat,
+ container.get("registries"),
+ ),
+ );
+
+ // TODO make tags optional
+ pack.service("predicates", (container) =>
+ createPredicates(
+ container.get("registries"),
+ // TODO somehow I depend on tags which does not make sense because tags depends on core
+ // solved if moved to ingredients package?
+ container.get("loader:tags"),
+ ingredientSerializer(),
+ ),
+ );
+ },
+});
diff --git a/modules/ingredients/src/predicates/index.ts b/modules/ingredients/src/predicates/index.ts
new file mode 100644
index 0000000..add88c9
--- /dev/null
+++ b/modules/ingredients/src/predicates/index.ts
@@ -0,0 +1,77 @@
+import {
+ type IdInput,
+ type NormalizedId,
+ type RegistryLookup,
+} from "@adeficior/data-modifier-core";
+import {
+ resolveIdTest,
+ type CommonFilter,
+ type Predicate,
+} from "@adeficior/data-modifier-core/serializer";
+import {
+ type TagRegistry,
+ type TagRegistryHolder,
+} from "@adeficior/data-modifier-tags";
+import {
+ type InferIds,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import { type Ingredient } from "../ingredient/impl";
+import { type Result } from "../result/impl";
+import { type IngredientSerializer } from "../serializer/ingredients";
+import {
+ createIngredientPredicate,
+ type IngredientFilter,
+} from "./ingredients";
+
+export type Predicates = {
+ ingredient(filter: IngredientFilter): Predicate;
+ result(filter: IngredientFilter): Predicate;
+ id(
+ filter: CommonFilter>>,
+ registry: T,
+ ): Predicate>>;
+};
+
+export function createPredicates(
+ registries: RegistryLookup,
+ tags: TagRegistryHolder,
+ ingredientSerializer: IngredientSerializer,
+): Predicates {
+ return new PredicatesImpl(registries, tags, ingredientSerializer);
+}
+
+class PredicatesImpl implements Predicates {
+ constructor(
+ // TODO inject
+ private readonly registries: RegistryLookup,
+ private readonly tags: TagRegistryHolder,
+ private readonly ingredientSerializer: IngredientSerializer,
+ ) {}
+
+ ingredient(filter: IngredientFilter): Predicate {
+ return createIngredientPredicate(filter, {
+ serializer: this.ingredientSerializer,
+ registries: this.registries,
+ tags: this.tags,
+ });
+ }
+
+ result(filter: IngredientFilter): Predicate {
+ const ingredientPredicate = this.ingredient(filter);
+
+ return (result, ...args) => {
+ const ingredient = result.asIngredient();
+ return ingredientPredicate(ingredient, ...args);
+ };
+ }
+
+ id(
+ filter: CommonFilter>>,
+ registry: T | TagRegistry,
+ ): Predicate>> {
+ const tags =
+ typeof registry == "string" ? this.tags.registry(registry) : registry;
+ return resolveIdTest(filter, tags);
+ }
+}
diff --git a/src/common/ingredient/filter.ts b/modules/ingredients/src/predicates/ingredients.ts
similarity index 50%
rename from src/common/ingredient/filter.ts
rename to modules/ingredients/src/predicates/ingredients.ts
index 085f864..5295777 100644
--- a/src/common/ingredient/filter.ts
+++ b/modules/ingredients/src/predicates/ingredients.ts
@@ -1,4 +1,15 @@
-import type { RegistryId } from "@adeficior/data-modifier/generated";
+import {
+ IllegalShapeError,
+ type NormalizedId,
+ type RegistryLookup,
+} from "@adeficior/data-modifier-core";
+import {
+ createIdPredicate,
+ type CommonFilter,
+ type Predicate,
+} from "@adeficior/data-modifier-core/serializer";
+import { type TagRegistryHolder } from "@adeficior/data-modifier-tags";
+import { type RegistryId } from "@adeficior/data-modifier/generated";
import {
Ingredient,
ItemIngredient,
@@ -6,69 +17,65 @@ import {
ListIngredient,
RegistryEntryIngredient,
TagIngredient,
-} from ".";
-import { IllegalShapeError } from "../../error";
-import type { PackContext } from "../../loader/context";
-import {
- createIdPredicate,
- type CommonFilter,
- type Predicate,
-} from "../filters";
-import { type NormalizedId } from "../id";
-import type { IngredientInput } from "./input";
+} from "../ingredient/impl";
+import { type IngredientInput } from "../ingredient/input";
+import { type IngredientSerializer } from "../serializer/ingredients";
export type IngredientFilter =
- | CommonFilter
- | IngredientInput
- | `#${string}`;
+ CommonFilter | IngredientInput | `#${string}`;
+
+type Context = {
+ registries: RegistryLookup;
+ serializer: IngredientSerializer;
+ tags: TagRegistryHolder;
+};
-export default function createIngredientPredicate(
+export function createIngredientPredicate(
test: IngredientFilter,
- context: Pick,
+ context: Context,
): Predicate {
const unvalidated = createUnvalidatedFilter(test, context);
- return (it) => unvalidated(context.ingredients.validated(it));
+ return (it) => unvalidated(context.serializer.validated(it));
}
function createUnvalidatedFilter(
test: IngredientFilter,
- context: Pick,
+ context: Context,
): Predicate {
if (typeof test === "string") {
if (test.startsWith("#")) {
- return createIngredientPredicate(
+ return createUnvalidatedFilter(
new ItemTagIngredient(test.substring(1)),
context,
);
}
- return createIngredientPredicate(new ItemIngredient(test), context);
+ return createUnvalidatedFilter(new ItemIngredient(test), context);
}
if (test instanceof RegExp) {
- return filterByRegistry(test, context, "minecraft:item");
+ return filterByRegistry(test, context.tags, "minecraft:item");
}
if (typeof test === "function") {
- // TODO ingredients.create not needed if function wrapped
- return (it) => test(context.ingredients.deserialize(it));
+ return test;
}
if (test instanceof Ingredient) {
- test.validate(context.lookup);
+ test.validate(context.registries);
}
if (test instanceof TagIngredient) {
- return filterByRegistry(test.tag, context, test.registry);
+ return filterByRegistry(test.tag, context.tags, test.registry);
}
if (test instanceof RegistryEntryIngredient) {
- return filterByRegistry(test.id, context, test.registry);
+ return filterByRegistry(test.id, context.tags, test.registry);
}
if (test instanceof ListIngredient) {
const predicates = test.entries.map((it) =>
- createIngredientPredicate(it, context),
+ createUnvalidatedFilter(it, context),
);
return (it) => predicates.some((predicate) => predicate(it));
}
@@ -78,12 +85,12 @@ function createUnvalidatedFilter(
function filterByRegistry(
test: NormalizedId | RegExp,
- context: Pick,
+ tags: TagRegistryHolder,
registry: NormalizedId,
): Predicate {
return createIdPredicate(
test,
(it) => it.ids()[registry] ?? [],
- context.tags.registry(registry),
+ tags.registry(registry),
);
}
diff --git a/src/common/result/index.ts b/modules/ingredients/src/result/impl.ts
similarity index 86%
rename from src/common/result/index.ts
rename to modules/ingredients/src/result/impl.ts
index 5f45957..73cc643 100644
--- a/src/common/result/index.ts
+++ b/modules/ingredients/src/result/impl.ts
@@ -1,21 +1,26 @@
-import type {
- BlockId,
- FluidId,
- ItemId,
- RegistryId,
+import {
+ encodeId,
+ type IdInput,
+ type NormalizedId,
+ type Registered,
+ type RegistryIds,
+ type RegistryLookup,
+} from "@adeficior/data-modifier-core";
+import {
+ type BlockId,
+ type FluidId,
+ type ItemId,
+ type RegistryId,
} from "@adeficior/data-modifier/generated";
-import type RegistryLookup from "../../loader/registry";
-import { encodeId, type IdInput, type NormalizedId } from "../id";
import {
BlockIngredient,
FluidIngredient,
ItemIngredient,
type Ingredient,
-} from "../ingredient";
-import type { InputOutput, RegistryIds } from "../inputOutput";
+} from "../ingredient/impl";
import { BUCKET } from "../units";
-export abstract class Result implements InputOutput {
+export abstract class Result implements Registered {
validate(_: RegistryLookup): void {}
abstract ids(): RegistryIds;
abstract asIngredient(): Ingredient;
diff --git a/modules/ingredients/src/result/input.ts b/modules/ingredients/src/result/input.ts
new file mode 100644
index 0000000..542e505
--- /dev/null
+++ b/modules/ingredients/src/result/input.ts
@@ -0,0 +1,4 @@
+import { type ItemId } from "@adeficior/data-modifier/generated";
+import { type Result } from "./impl";
+
+export type ResultInput = ItemId | Result;
diff --git a/src/serializer/ingredients/15.ts b/modules/ingredients/src/serializer/ingredients/15.ts
similarity index 86%
rename from src/serializer/ingredients/15.ts
rename to modules/ingredients/src/serializer/ingredients/15.ts
index d70c16b..1b27b83 100644
--- a/src/serializer/ingredients/15.ts
+++ b/modules/ingredients/src/serializer/ingredients/15.ts
@@ -1,4 +1,11 @@
-import z from "zod";
+import { IdSchema, stripTag } from "@adeficior/data-modifier-core";
+import {
+ createSerializerModule,
+ hasType,
+ isObjectWith,
+} from "@adeficior/data-modifier-core/serializer";
+import * as z from "zod";
+import { AmountSchema, CountSchema } from "../../fields";
import {
BlockIngredient,
BlockTagIngredient,
@@ -8,12 +15,8 @@ import {
ItemIngredient,
ItemTagIngredient,
ToolActionIngredient,
-} from "../../";
-import { AmountSchema, CountSchema } from "../../common/fields";
-import { IdSchema, stripTag } from "../../common/id";
-import { hasType, isObjectWith } from "../checks";
-import { createSerializerModule } from "../module";
-import commonSerialization from "./common";
+} from "../../ingredient/impl";
+import { commonSerialization } from "./common";
export const serializer15 = createSerializerModule((builder) => {
commonSerialization(builder);
diff --git a/src/serializer/ingredients/44.ts b/modules/ingredients/src/serializer/ingredients/44.ts
similarity index 80%
rename from src/serializer/ingredients/44.ts
rename to modules/ingredients/src/serializer/ingredients/44.ts
index 0d68582..e470e7a 100644
--- a/src/serializer/ingredients/44.ts
+++ b/modules/ingredients/src/serializer/ingredients/44.ts
@@ -1,4 +1,11 @@
-import z from "zod";
+import { IdSchema, stripTag } from "@adeficior/data-modifier-core";
+import {
+ createSerializerModule,
+ hasType,
+ isObjectWith,
+} from "@adeficior/data-modifier-core/serializer";
+import * as z from "zod";
+import { AmountSchema, CountSchema } from "../../fields";
import {
FluidIngredient,
FluidTagIngredient,
@@ -8,28 +15,24 @@ import {
ItemTagIngredient,
ListIngredient,
ToolActionIngredient,
-} from "../../";
-import { AmountSchema, CountSchema } from "../../common/fields";
-import { IdSchema, stripTag } from "../../common/id";
-import { hasType, isObjectWith } from "../checks";
-import { createSerializerModule } from "../module";
-import commonSerialization from "./common";
+} from "../../ingredient/impl";
+import { commonSerialization } from "./common";
export const serializer44 = createSerializerModule((builder) => {
commonSerialization(builder);
// Do I also need to serialize as this?
builder.deserializer(hasType("neoforge:compound"), (it, deserialize) => {
- const { ingredients } = z
+ const { children } = z
.object({
- ingredients: z.array(z.any()),
+ children: z.array(z.any()),
})
.parse(it);
- return new ListIngredient(ingredients.map(deserialize));
+ return new ListIngredient(children.map(deserialize));
});
- const toolActionType = "farmersdelight:tool_action" as const;
+ const toolActionType = "farmersdelight:item_ability" as const;
builder.register(
ToolActionIngredient,
hasType(toolActionType),
diff --git a/src/serializer/ingredients/common.ts b/modules/ingredients/src/serializer/ingredients/common.ts
similarity index 75%
rename from src/serializer/ingredients/common.ts
rename to modules/ingredients/src/serializer/ingredients/common.ts
index 6fe469b..752dd2d 100644
--- a/src/serializer/ingredients/common.ts
+++ b/modules/ingredients/src/serializer/ingredients/common.ts
@@ -1,16 +1,14 @@
+import { type SerializerBuilder } from "@adeficior/data-modifier-core/serializer";
import {
IgnoredIngredient,
type Ingredient,
ItemIngredient,
ItemTagIngredient,
ListIngredient,
- Result,
-} from "../../";
-import type { SerializerBuilder } from "../module";
+} from "../../ingredient/impl";
+import { Result } from "../../result/impl";
-export default function commonSerialization(
- builder: SerializerBuilder,
-) {
+export function commonSerialization(builder: SerializerBuilder) {
builder.deserializer(
(it) => it instanceof Result,
(it) => it.asIngredient(),
diff --git a/src/serializer/ingredients/index.ts b/modules/ingredients/src/serializer/ingredients/index.ts
similarity index 76%
rename from src/serializer/ingredients/index.ts
rename to modules/ingredients/src/serializer/ingredients/index.ts
index 78f4eeb..df71b2b 100644
--- a/src/serializer/ingredients/index.ts
+++ b/modules/ingredients/src/serializer/ingredients/index.ts
@@ -1,17 +1,18 @@
-import { mapValues } from "lodash-es";
-import type { Serializer } from "..";
-import { Ingredient } from "../../common/ingredient";
-import type RegistryLookup from "../../loader/registry";
-import type { SemVerInput } from "../../packFormat";
import {
- IngredientMap,
- type IngredientMapInput,
-} from "../../parser/recipe/ingredientMap";
-import type { SerializerModule } from "../module";
-import { VersionedSerializer } from "../versioned";
-import { WrapperSerializer } from "../wrapped";
+ type RegistryLookup,
+ type SemVerInput,
+} from "@adeficior/data-modifier-core";
+import {
+ VersionedSerializer,
+ WrapperSerializer,
+ type Serializer,
+ type SerializerModule,
+} from "@adeficior/data-modifier-core/serializer";
+import { mapValues } from "lodash-es";
+import { Ingredient } from "../../ingredient/impl";
import { serializer15 } from "./15";
import { serializer44 } from "./44";
+import { IngredientMap, type IngredientMapInput } from "./ingredientMap";
export interface IngredientSerializer extends Serializer<
Ingredient,
@@ -21,7 +22,8 @@ export interface IngredientSerializer extends Serializer<
serializeIngredientMap(map: IngredientMap): IngredientMapInput;
}
-export class IngredientSerializerImpl
+
+class IngredientSerializerImpl
extends VersionedSerializer
implements IngredientSerializer
{
diff --git a/src/parser/recipe/ingredientMap.ts b/modules/ingredients/src/serializer/ingredients/ingredientMap.ts
similarity index 75%
rename from src/parser/recipe/ingredientMap.ts
rename to modules/ingredients/src/serializer/ingredients/ingredientMap.ts
index bf0a25c..1857c39 100644
--- a/src/parser/recipe/ingredientMap.ts
+++ b/modules/ingredients/src/serializer/ingredients/ingredientMap.ts
@@ -1,6 +1,6 @@
+import { type Replacer } from "@adeficior/data-modifier-core/serializer";
import { mapValues } from "lodash-es";
-import type { Replacer } from ".";
-import type { Ingredient } from "../../common/ingredient";
+import { type Ingredient } from "../../ingredient/impl";
export type IngredientMapInput = Record;
diff --git a/src/serializer/results/15.ts b/modules/ingredients/src/serializer/results/15.ts
similarity index 68%
rename from src/serializer/results/15.ts
rename to modules/ingredients/src/serializer/results/15.ts
index 1a51349..aaf50dc 100644
--- a/src/serializer/results/15.ts
+++ b/modules/ingredients/src/serializer/results/15.ts
@@ -1,10 +1,17 @@
-import z from "zod";
-import { BlockResult, FluidResult, ItemResult, type Result } from "../..";
-import { AmountSchema, ChanceSchema, CountSchema } from "../../common/fields";
-import { IdSchema } from "../../common/id";
-import { isObjectWith } from "../checks";
-import { createSerializerModule } from "../module";
-import commonSerialization from "./common";
+import { IdSchema } from "@adeficior/data-modifier-core";
+import {
+ createSerializerModule,
+ isObjectWith,
+} from "@adeficior/data-modifier-core/serializer";
+import * as z from "zod";
+import { AmountSchema, ChanceSchema, CountSchema } from "../../fields";
+import {
+ BlockResult,
+ FluidResult,
+ ItemResult,
+ type Result,
+} from "../../result/impl";
+import { commonSerialization } from "./common";
export const serializer15 = createSerializerModule((builder) => {
commonSerialization(builder);
diff --git a/src/serializer/results/44.ts b/modules/ingredients/src/serializer/results/44.ts
similarity index 62%
rename from src/serializer/results/44.ts
rename to modules/ingredients/src/serializer/results/44.ts
index ba3c1e7..b20f9bb 100644
--- a/src/serializer/results/44.ts
+++ b/modules/ingredients/src/serializer/results/44.ts
@@ -1,10 +1,12 @@
-import z from "zod";
-import { FluidResult, ItemResult, type Result } from "../..";
-import { AmountSchema, ChanceSchema, CountSchema } from "../../common/fields";
-import { IdSchema } from "../../common/id";
-import { isObjectWith } from "../checks";
-import { createSerializerModule } from "../module";
-import commonSerialization from "./common";
+import { IdSchema } from "@adeficior/data-modifier-core";
+import {
+ createSerializerModule,
+ isObjectWith,
+} from "@adeficior/data-modifier-core/serializer";
+import * as z from "zod";
+import { AmountSchema, ChanceSchema, CountSchema } from "../../fields";
+import { FluidResult, ItemResult, type Result } from "../../result/impl";
+import { commonSerialization } from "./common";
export const serializer44 = createSerializerModule((builder) => {
commonSerialization(builder);
diff --git a/modules/ingredients/src/serializer/results/common.ts b/modules/ingredients/src/serializer/results/common.ts
new file mode 100644
index 0000000..0063123
--- /dev/null
+++ b/modules/ingredients/src/serializer/results/common.ts
@@ -0,0 +1,17 @@
+import { type SerializerBuilder } from "@adeficior/data-modifier-core/serializer";
+import { Ingredient } from "../../ingredient/impl";
+import { IgnoredResult, ItemResult, type Result } from "../../result/impl";
+
+export function commonSerialization(builder: SerializerBuilder) {
+ builder.deserializer(
+ (it) => it instanceof Ingredient,
+ (it) => it.asResult(),
+ );
+
+ builder.deserializer(
+ (it) => typeof it === "string",
+ (input) => new ItemResult(input),
+ );
+
+ builder.serializer(IgnoredResult, (it) => it.raw);
+}
diff --git a/src/serializer/results/index.ts b/modules/ingredients/src/serializer/results/index.ts
similarity index 69%
rename from src/serializer/results/index.ts
rename to modules/ingredients/src/serializer/results/index.ts
index 012df23..4944a5c 100644
--- a/src/serializer/results/index.ts
+++ b/modules/ingredients/src/serializer/results/index.ts
@@ -1,10 +1,14 @@
-import type { Serializer } from "..";
-import { Result } from "../..";
-import type RegistryLookup from "../../loader/registry";
-import type { SemVerInput } from "../../packFormat";
-import { type SerializerModule } from "../module";
-import { VersionedSerializer } from "../versioned";
-import { WrapperSerializer } from "../wrapped";
+import {
+ type RegistryLookup,
+ type SemVerInput,
+} from "@adeficior/data-modifier-core";
+import {
+ VersionedSerializer,
+ WrapperSerializer,
+ type Serializer,
+ type SerializerModule,
+} from "@adeficior/data-modifier-core/serializer";
+import { Result } from "../../result/impl";
import { serializer15 } from "./15";
import { serializer44 } from "./44";
diff --git a/modules/ingredients/src/serializer/withModules.ts b/modules/ingredients/src/serializer/withModules.ts
new file mode 100644
index 0000000..257cfab
--- /dev/null
+++ b/modules/ingredients/src/serializer/withModules.ts
@@ -0,0 +1,8 @@
+import { type SerializerModule } from "@adeficior/data-modifier-core/serializer";
+import { type Ingredient } from "../ingredient/impl";
+import { type Result } from "../result/impl";
+
+export interface WithSerializerModules {
+ ingredientModules(): Record>;
+ resultModules(): Record>;
+}
diff --git a/modules/ingredients/src/testing/index.ts b/modules/ingredients/src/testing/index.ts
new file mode 100644
index 0000000..2635767
--- /dev/null
+++ b/modules/ingredients/src/testing/index.ts
@@ -0,0 +1,2 @@
+export * from "./mocks";
+export * from "./setup";
diff --git a/modules/ingredients/src/testing/mocks.ts b/modules/ingredients/src/testing/mocks.ts
new file mode 100644
index 0000000..1e68e57
--- /dev/null
+++ b/modules/ingredients/src/testing/mocks.ts
@@ -0,0 +1,42 @@
+import { type Serializer } from "@adeficior/data-modifier-core/serializer";
+import { mock } from "bun:test";
+import { type Ingredient } from "../ingredient/impl";
+import { type Predicates } from "../predicates";
+import { type Result } from "../result/impl";
+import { type IngredientSerializer } from "../serializer/ingredients";
+import { type ResultSerializer } from "../serializer/results";
+
+function mockSerializer>() {
+ return {
+ deserialize: mock(),
+ deserializeList: mock(),
+ serialize: mock(),
+ serializeList: mock(),
+ deserializeOptional: mock(),
+ serializeOptional: mock(),
+ selectModule: mock(),
+ validated: mock().mockImplementation((it) => it),
+ withModule: mock().mockReturnThis(),
+ } satisfies Serializer;
+}
+
+export function mockResultSerializer() {
+ return mockSerializer() satisfies ResultSerializer;
+}
+
+export function mockIngredientSerializer() {
+ const base = mockSerializer();
+ return {
+ ...base,
+ deserializeIngredientMap: mock(),
+ serializeIngredientMap: mock(),
+ } satisfies IngredientSerializer;
+}
+
+export function mockPredicates() {
+ return {
+ id: mock(),
+ ingredient: mock(),
+ result: mock(),
+ } satisfies Predicates;
+}
diff --git a/modules/ingredients/src/testing/setup.ts b/modules/ingredients/src/testing/setup.ts
new file mode 100644
index 0000000..1005a24
--- /dev/null
+++ b/modules/ingredients/src/testing/setup.ts
@@ -0,0 +1,36 @@
+import {
+ packFormatOf,
+ type RegistryLookup,
+} from "@adeficior/data-modifier-core";
+import { type TagRegistryHolder } from "@adeficior/data-modifier-tags";
+import { createPredicates, type Predicates } from "../predicates";
+import {
+ createIngredientSerializer,
+ type IngredientSerializer,
+} from "../serializer/ingredients";
+import {
+ createResultSerializer,
+ type ResultSerializer,
+} from "../serializer/results";
+
+export function setupIngredientSerializer(
+ version: string,
+ registries: RegistryLookup,
+): IngredientSerializer {
+ return createIngredientSerializer(packFormatOf(version), registries);
+}
+
+export function setupResultSerializer(
+ version: string,
+ registries: RegistryLookup,
+): ResultSerializer {
+ return createResultSerializer(packFormatOf(version), registries);
+}
+
+export function setupPredicates(
+ registries: RegistryLookup,
+ tags: TagRegistryHolder,
+ ingredients: IngredientSerializer,
+): Predicates {
+ return createPredicates(registries, tags, ingredients);
+}
diff --git a/src/common/units.ts b/modules/ingredients/src/units.ts
similarity index 100%
rename from src/common/units.ts
rename to modules/ingredients/src/units.ts
diff --git a/test/1.20.1/ingredientDeserializer.test.ts b/modules/ingredients/test/1.20.1/ingredientDeserializer.test.ts
similarity index 81%
rename from test/1.20.1/ingredientDeserializer.test.ts
rename to modules/ingredients/test/1.20.1/ingredientDeserializer.test.ts
index 98d7267..42ee0f7 100644
--- a/test/1.20.1/ingredientDeserializer.test.ts
+++ b/modules/ingredients/test/1.20.1/ingredientDeserializer.test.ts
@@ -1,13 +1,12 @@
+import { packFormatOf } from "@adeficior/data-modifier-core";
+import { provided, setupLookup } from "@adeficior/testing";
import { describe, expect } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf } from "../../src";
import { createIngredientSerializer } from "../../src/serializer/ingredients";
-import setupLookup from "../shared/dump";
import {
ingredientInputs,
invalidIngredientInputs,
-} from "../shared/provider/1.20.1/ingredientInputs";
-import { provided } from "../shared/provider/providers";
+} from "../util/providers/1.20.1/ingredientInputs";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
diff --git a/test/1.21.1/ingredientFilters.test.ts b/modules/ingredients/test/1.20.1/ingredientFilters.test.ts
similarity index 50%
rename from test/1.21.1/ingredientFilters.test.ts
rename to modules/ingredients/test/1.20.1/ingredientFilters.test.ts
index 0f5a2eb..f80e859 100644
--- a/test/1.21.1/ingredientFilters.test.ts
+++ b/modules/ingredients/test/1.20.1/ingredientFilters.test.ts
@@ -1,40 +1,35 @@
-import { beforeAll, describe, expect, it } from "bun:test";
+import {
+ packFormatOf,
+ UnknownRegistryEntry,
+} from "@adeficior/data-modifier-core";
+import { setupTagRegistry } from "@adeficior/data-modifier-tags/testing";
+import { provided, setupLookup } from "@adeficior/testing";
+import { describe, expect, it } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf, UnknownRegistryEntry } from "../../src";
-import { ItemIngredient } from "../../src/common/ingredient";
-import createIngredientPredicate from "../../src/common/ingredient/filter";
-import TagsLoader from "../../src/loader/tags";
+import { ItemIngredient } from "../../src";
+import { createPredicates } from "../../src/predicates";
import { createIngredientSerializer } from "../../src/serializer/ingredients";
-import setupLookup from "../shared/dump";
import {
invalidIngredientFilters,
matchingIngredientFilters,
missingIngredientFilters,
-} from "../shared/provider/1.21.1/ingredientFilters";
-import { provided } from "../shared/provider/providers";
-import { createTestDataResolver } from "../shared/testData";
+} from "../util/providers/1.20.1/ingredientFilters";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
-const data = await createTestDataResolver(version, {
- include: ["data/*/tags/**/*.json"],
-});
-
const ingredients = createIngredientSerializer(packFormatOf(version), lookup);
-const tags = new TagsLoader(packFormatOf(version));
-const context = { ingredients, lookup, tags };
-
-beforeAll(async () => data.extract(tags));
+const tags = setupTagRegistry(version);
+const predicates = createPredicates(lookup, tags, ingredients);
describe(`ingredient filter tests with ${version} format`, () => {
provided("matching filters", matchingIngredientFilters(), (filter, input) => {
- const predicate = createIngredientPredicate(filter, context);
+ const predicate = predicates.ingredient(filter);
const actual = predicate(input);
expect(actual).toBeTrue();
});
provided("missing filters", missingIngredientFilters(), (filter, input) => {
- const predicate = createIngredientPredicate(filter, context);
+ const predicate = predicates.ingredient(filter);
const actual = predicate(input);
expect(actual).toBeFalse();
});
@@ -43,15 +38,13 @@ describe(`ingredient filter tests with ${version} format`, () => {
"invalid filters",
invalidIngredientFilters(),
(filter, expected) => {
- expect(() => createIngredientPredicate(filter, context)).toThrow(
- expected,
- );
+ expect(() => predicates.ingredient(filter)).toThrow(expected);
},
);
it("invalid test subject", () => {
const filter = new ItemIngredient("minecraft:apple");
- const predicate = createIngredientPredicate(filter, context);
+ const predicate = predicates.ingredient(filter);
expect(() =>
predicate(new ItemIngredient("minecraft:horse_radish")),
).toThrow(UnknownRegistryEntry);
diff --git a/test/1.20.1/ingredientSerializer.test.ts b/modules/ingredients/test/1.20.1/ingredientSerializer.test.ts
similarity index 71%
rename from test/1.20.1/ingredientSerializer.test.ts
rename to modules/ingredients/test/1.20.1/ingredientSerializer.test.ts
index 0b5af62..30d3ca9 100644
--- a/test/1.20.1/ingredientSerializer.test.ts
+++ b/modules/ingredients/test/1.20.1/ingredientSerializer.test.ts
@@ -1,10 +1,9 @@
+import { packFormatOf } from "@adeficior/data-modifier-core";
+import { provided, setupLookup } from "@adeficior/testing";
import { describe, expect } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf } from "../../src";
import { createIngredientSerializer } from "../../src/serializer/ingredients";
-import setupLookup from "../shared/dump";
-import { serializedIngredients } from "../shared/provider/1.20.1/ingredientOutputs";
-import { provided } from "../shared/provider/providers";
+import { serializedIngredients } from "../util/providers/1.20.1/ingredientOutputs";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
diff --git a/test/1.20.1/resultDeserializer.test.ts b/modules/ingredients/test/1.20.1/resultDeserializer.test.ts
similarity index 80%
rename from test/1.20.1/resultDeserializer.test.ts
rename to modules/ingredients/test/1.20.1/resultDeserializer.test.ts
index 6df8a48..b6e7d5d 100644
--- a/test/1.20.1/resultDeserializer.test.ts
+++ b/modules/ingredients/test/1.20.1/resultDeserializer.test.ts
@@ -1,13 +1,12 @@
+import { packFormatOf } from "@adeficior/data-modifier-core";
+import { provided, setupLookup } from "@adeficior/testing";
import { describe, expect } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf } from "../../src";
import { createResultSerializer } from "../../src/serializer/results";
-import setupLookup from "../shared/dump";
import {
invalidResultInputs,
resultInputs,
-} from "../shared/provider/1.20.1/resultInputs";
-import { provided } from "../shared/provider/providers";
+} from "../util/providers/1.20.1/resultInputs";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
diff --git a/modules/ingredients/test/1.20.1/resultFilter.test.ts b/modules/ingredients/test/1.20.1/resultFilter.test.ts
new file mode 100644
index 0000000..7a2e12b
--- /dev/null
+++ b/modules/ingredients/test/1.20.1/resultFilter.test.ts
@@ -0,0 +1,48 @@
+import {
+ packFormatOf,
+ UnknownRegistryEntry,
+} from "@adeficior/data-modifier-core";
+import { setupTagRegistry } from "@adeficior/data-modifier-tags/testing";
+import { provided, setupLookup } from "@adeficior/testing";
+import { describe, expect, it } from "bun:test";
+import { basename } from "node:path";
+import { ItemIngredient, ItemResult } from "../../src";
+import { createPredicates } from "../../src/predicates";
+import { createIngredientSerializer } from "../../src/serializer/ingredients";
+import {
+ invalidResultFilters,
+ matchingResultFilters,
+ missingResultFilters,
+} from "../util/providers/1.20.1/resultFilters";
+
+const version = basename(import.meta.dir);
+const lookup = setupLookup(version);
+const ingredients = createIngredientSerializer(packFormatOf(version), lookup);
+const tags = setupTagRegistry(version);
+const predicates = createPredicates(lookup, tags, ingredients);
+
+describe(`result filter tests with ${version} format`, () => {
+ provided("matching filters", matchingResultFilters(), (filter, input) => {
+ const predicate = predicates.result(filter);
+ const actual = predicate(input);
+ expect(actual).toBeTrue();
+ });
+
+ provided("missing filters", missingResultFilters(), (filter, input) => {
+ const predicate = predicates.result(filter);
+ const actual = predicate(input);
+ expect(actual).toBeFalse();
+ });
+
+ provided("invalid filters", invalidResultFilters(), (filter, expected) => {
+ expect(() => predicates.result(filter)).toThrow(expected);
+ });
+
+ it("invalid test subject", () => {
+ const filter = new ItemIngredient("minecraft:apple");
+ const predicate = predicates.result(filter);
+ expect(() => predicate(new ItemResult("minecraft:horse_radish"))).toThrow(
+ UnknownRegistryEntry,
+ );
+ });
+});
diff --git a/test/1.20.1/resultSerializer.test.ts b/modules/ingredients/test/1.20.1/resultSerializer.test.ts
similarity index 70%
rename from test/1.20.1/resultSerializer.test.ts
rename to modules/ingredients/test/1.20.1/resultSerializer.test.ts
index a72a5ba..f9e899e 100644
--- a/test/1.20.1/resultSerializer.test.ts
+++ b/modules/ingredients/test/1.20.1/resultSerializer.test.ts
@@ -1,10 +1,9 @@
+import { packFormatOf } from "@adeficior/data-modifier-core";
+import { provided, setupLookup } from "@adeficior/testing";
import { describe, expect } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf } from "../../src";
import { createResultSerializer } from "../../src/serializer/results";
-import setupLookup from "../shared/dump";
-import { serializedResults } from "../shared/provider/1.20.1/resultOutputs";
-import { provided } from "../shared/provider/providers";
+import { serializedResults } from "../util/providers/1.20.1/resultOutputs";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
diff --git a/test/1.21.1/ingredient.test.ts b/modules/ingredients/test/1.21.1/ingredient.test.ts
similarity index 81%
rename from test/1.21.1/ingredient.test.ts
rename to modules/ingredients/test/1.21.1/ingredient.test.ts
index d483615..07ee19e 100644
--- a/test/1.21.1/ingredient.test.ts
+++ b/modules/ingredients/test/1.21.1/ingredient.test.ts
@@ -1,13 +1,12 @@
+import { packFormatOf } from "@adeficior/data-modifier-core";
+import { provided, setupLookup } from "@adeficior/testing";
import { describe, expect } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf } from "../../src";
import { createIngredientSerializer } from "../../src/serializer/ingredients";
-import setupLookup from "../shared/dump";
import {
ingredientInputs,
invalidIngredientInputs,
-} from "../shared/provider/1.21.1/ingredientInputs";
-import { provided } from "../shared/provider/providers";
+} from "../util/providers/1.21.1/ingredientInputs";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
diff --git a/test/1.20.1/ingredientFilters.test.ts b/modules/ingredients/test/1.21.1/ingredientFilters.test.ts
similarity index 50%
rename from test/1.20.1/ingredientFilters.test.ts
rename to modules/ingredients/test/1.21.1/ingredientFilters.test.ts
index e9981b2..320ce63 100644
--- a/test/1.20.1/ingredientFilters.test.ts
+++ b/modules/ingredients/test/1.21.1/ingredientFilters.test.ts
@@ -1,40 +1,36 @@
-import { beforeAll, describe, expect, it } from "bun:test";
+import {
+ packFormatOf,
+ UnknownRegistryEntry,
+} from "@adeficior/data-modifier-core";
+import { setupTagRegistry } from "@adeficior/data-modifier-tags/testing";
+import { provided, setupLookup } from "@adeficior/testing";
+import { describe, expect, it } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf, UnknownRegistryEntry } from "../../src";
-import { ItemIngredient } from "../../src/common/ingredient";
-import createIngredientPredicate from "../../src/common/ingredient/filter";
-import TagsLoader from "../../src/loader/tags";
+import { ItemIngredient } from "../../src";
+import { createPredicates } from "../../src/predicates";
import { createIngredientSerializer } from "../../src/serializer/ingredients";
-import setupLookup from "../shared/dump";
import {
invalidIngredientFilters,
matchingIngredientFilters,
missingIngredientFilters,
-} from "../shared/provider/1.20.1/ingredientFilters";
-import { provided } from "../shared/provider/providers";
-import { createTestDataResolver } from "../shared/testData";
+} from "../util/providers/1.21.1/ingredientFilters";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
-const data = await createTestDataResolver(version, {
- include: ["data/*/tags/**/*.json"],
-});
const ingredients = createIngredientSerializer(packFormatOf(version), lookup);
-const tags = new TagsLoader(packFormatOf(version));
-const context = { ingredients, lookup, tags };
-
-beforeAll(async () => data.extract(tags));
+const tags = setupTagRegistry(version);
+const predicates = createPredicates(lookup, tags, ingredients);
describe(`ingredient filter tests with ${version} format`, () => {
provided("matching filters", matchingIngredientFilters(), (filter, input) => {
- const predicate = createIngredientPredicate(filter, context);
+ const predicate = predicates.ingredient(filter);
const actual = predicate(input);
expect(actual).toBeTrue();
});
provided("missing filters", missingIngredientFilters(), (filter, input) => {
- const predicate = createIngredientPredicate(filter, context);
+ const predicate = predicates.ingredient(filter);
const actual = predicate(input);
expect(actual).toBeFalse();
});
@@ -43,15 +39,13 @@ describe(`ingredient filter tests with ${version} format`, () => {
"invalid filters",
invalidIngredientFilters(),
(filter, expected) => {
- expect(() => createIngredientPredicate(filter, context)).toThrow(
- expected,
- );
+ expect(() => predicates.ingredient(filter)).toThrow(expected);
},
);
it("invalid test subject", () => {
const filter = new ItemIngredient("minecraft:apple");
- const predicate = createIngredientPredicate(filter, context);
+ const predicate = predicates.ingredient(filter);
expect(() =>
predicate(new ItemIngredient("minecraft:horse_radish")),
).toThrow(UnknownRegistryEntry);
diff --git a/test/1.21.1/result.test.ts b/modules/ingredients/test/1.21.1/result.test.ts
similarity index 80%
rename from test/1.21.1/result.test.ts
rename to modules/ingredients/test/1.21.1/result.test.ts
index 66c51fa..5a3b86e 100644
--- a/test/1.21.1/result.test.ts
+++ b/modules/ingredients/test/1.21.1/result.test.ts
@@ -1,13 +1,12 @@
+import { packFormatOf } from "@adeficior/data-modifier-core";
+import { provided, setupLookup } from "@adeficior/testing";
import { describe, expect } from "bun:test";
import { basename } from "node:path";
-import { packFormatOf } from "../../src";
import { createResultSerializer } from "../../src/serializer/results";
-import setupLookup from "../shared/dump";
import {
invalidResultInputs,
resultInputs,
-} from "../shared/provider/1.21.1/resultInputs";
-import { provided } from "../shared/provider/providers";
+} from "../util/providers/1.21.1/resultInputs";
const version = basename(import.meta.dir);
const lookup = setupLookup(version);
diff --git a/modules/ingredients/test/1.21.1/resultFilter.test.ts b/modules/ingredients/test/1.21.1/resultFilter.test.ts
new file mode 100644
index 0000000..dcff61f
--- /dev/null
+++ b/modules/ingredients/test/1.21.1/resultFilter.test.ts
@@ -0,0 +1,48 @@
+import {
+ packFormatOf,
+ UnknownRegistryEntry,
+} from "@adeficior/data-modifier-core";
+import { setupTagRegistry } from "@adeficior/data-modifier-tags/testing";
+import { provided, setupLookup } from "@adeficior/testing";
+import { describe, expect, it } from "bun:test";
+import { basename } from "node:path";
+import { ItemIngredient, ItemResult } from "../../src";
+import { createPredicates } from "../../src/predicates";
+import { createIngredientSerializer } from "../../src/serializer/ingredients";
+import {
+ invalidResultFilters,
+ matchingResultFilters,
+ missingResultFilters,
+} from "../util/providers/1.21.1/resultFilters";
+
+const version = basename(import.meta.dir);
+const lookup = setupLookup(version);
+const ingredients = createIngredientSerializer(packFormatOf(version), lookup);
+const tags = setupTagRegistry(version);
+const predicates = createPredicates(lookup, tags, ingredients);
+
+describe(`result filter tests with ${version} format`, () => {
+ provided("matching filters", matchingResultFilters(), (filter, input) => {
+ const predicate = predicates.result(filter);
+ const actual = predicate(input);
+ expect(actual).toBeTrue();
+ });
+
+ provided("missing filters", missingResultFilters(), (filter, input) => {
+ const predicate = predicates.result(filter);
+ const actual = predicate(input);
+ expect(actual).toBeFalse();
+ });
+
+ provided("invalid filters", invalidResultFilters(), (filter, expected) => {
+ expect(() => predicates.result(filter)).toThrow(expected);
+ });
+
+ it("invalid test subject", () => {
+ const filter = new ItemIngredient("minecraft:apple");
+ const predicate = predicates.result(filter);
+ expect(() => predicate(new ItemResult("minecraft:horse_radish"))).toThrow(
+ UnknownRegistryEntry,
+ );
+ });
+});
diff --git a/test/shared/provider/1.21.1/ingredientFilters.ts b/modules/ingredients/test/util/providers/1.20.1/ingredientFilters.ts
similarity index 94%
rename from test/shared/provider/1.21.1/ingredientFilters.ts
rename to modules/ingredients/test/util/providers/1.20.1/ingredientFilters.ts
index b66a2d8..6d57601 100644
--- a/test/shared/provider/1.21.1/ingredientFilters.ts
+++ b/modules/ingredients/test/util/providers/1.20.1/ingredientFilters.ts
@@ -1,17 +1,17 @@
-import type { Class } from "../../../../src/common/class";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockIngredient,
BlockTagIngredient,
+ BUCKET,
FluidIngredient,
FluidTagIngredient,
type Ingredient,
+ type IngredientFilter,
ItemIngredient,
ItemTagIngredient,
ListIngredient,
-} from "../../../../src/common/ingredient";
-import type { IngredientFilter } from "../../../../src/common/ingredient/filter";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+} from "../../../../src";
export function* matchingIngredientFilters(): DataProvider<
[IngredientFilter, Ingredient]
diff --git a/test/shared/provider/1.20.1/ingredientInputs.ts b/modules/ingredients/test/util/providers/1.20.1/ingredientInputs.ts
similarity index 93%
rename from test/shared/provider/1.20.1/ingredientInputs.ts
rename to modules/ingredients/test/util/providers/1.20.1/ingredientInputs.ts
index b183396..11aa994 100644
--- a/test/shared/provider/1.20.1/ingredientInputs.ts
+++ b/modules/ingredients/test/util/providers/1.20.1/ingredientInputs.ts
@@ -1,16 +1,16 @@
-import type { Class } from "../../../../src/common/class";
-import type { Ingredient } from "../../../../src/common/ingredient";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockIngredient,
BlockTagIngredient,
+ BUCKET,
FluidIngredient,
FluidTagIngredient,
ItemIngredient,
ItemTagIngredient,
-} from "../../../../src/common/ingredient";
-import { BUCKET } from "../../../../src/common/units";
+ type Ingredient,
+} from "../../../../src";
import { ingredientLikeResults } from "../ingredientInputs";
-import type { DataProvider } from "../providers";
export function* invalidIngredientInputs(): DataProvider<
[unknown, Class | string]
diff --git a/test/shared/provider/1.20.1/ingredientOutputs.ts b/modules/ingredients/test/util/providers/1.20.1/ingredientOutputs.ts
similarity index 90%
rename from test/shared/provider/1.20.1/ingredientOutputs.ts
rename to modules/ingredients/test/util/providers/1.20.1/ingredientOutputs.ts
index 0f6e942..94dcc76 100644
--- a/test/shared/provider/1.20.1/ingredientOutputs.ts
+++ b/modules/ingredients/test/util/providers/1.20.1/ingredientOutputs.ts
@@ -1,15 +1,15 @@
-import type { Ingredient } from "../../../../src/common/ingredient";
+import { type DataProvider } from "@adeficior/testing";
+import { type Ingredient } from "../../../../src";
import {
BlockIngredient,
BlockTagIngredient,
+ BUCKET,
FluidIngredient,
FluidTagIngredient,
ItemIngredient,
ItemTagIngredient,
ListIngredient,
-} from "../../../../src/common/ingredient";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+} from "../../../../src";
export function* serializedIngredients(): DataProvider<[Ingredient, object]> {
yield [
diff --git a/test/shared/provider/1.20.1/resultFilters.ts b/modules/ingredients/test/util/providers/1.20.1/resultFilters.ts
similarity index 90%
rename from test/shared/provider/1.20.1/resultFilters.ts
rename to modules/ingredients/test/util/providers/1.20.1/resultFilters.ts
index 94d6a1c..8a86ac6 100644
--- a/test/shared/provider/1.20.1/resultFilters.ts
+++ b/modules/ingredients/test/util/providers/1.20.1/resultFilters.ts
@@ -1,22 +1,20 @@
-import type { Class } from "../../../../src/common/class";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockIngredient,
+ BlockResult,
BlockTagIngredient,
+ BUCKET,
FluidIngredient,
+ FluidResult,
FluidTagIngredient,
ItemIngredient,
+ ItemResult,
ItemTagIngredient,
ListIngredient,
-} from "../../../../src/common/ingredient";
-import type { IngredientFilter } from "../../../../src/common/ingredient/filter";
-import {
- BlockResult,
- FluidResult,
- ItemResult,
+ type IngredientFilter,
type Result,
-} from "../../../../src/common/result";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+} from "../../../../src";
export function* matchingResultFilters(): DataProvider<
[IngredientFilter, Result]
diff --git a/test/shared/provider/1.20.1/resultInputs.ts b/modules/ingredients/test/util/providers/1.20.1/resultInputs.ts
similarity index 90%
rename from test/shared/provider/1.20.1/resultInputs.ts
rename to modules/ingredients/test/util/providers/1.20.1/resultInputs.ts
index e3bbe36..c980bd7 100644
--- a/test/shared/provider/1.20.1/resultInputs.ts
+++ b/modules/ingredients/test/util/providers/1.20.1/resultInputs.ts
@@ -1,12 +1,12 @@
-import type { Class } from "../../../../src/common/class";
-import type { Result } from "../../../../src/common/result";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockResult,
+ BUCKET,
FluidResult,
ItemResult,
-} from "../../../../src/common/result";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+ type Result,
+} from "../../../../src";
import { resultLikeIngredients } from "../resultInputs";
export function* invalidResultInputs(): DataProvider<
diff --git a/test/shared/provider/1.20.1/resultOutputs.ts b/modules/ingredients/test/util/providers/1.20.1/resultOutputs.ts
similarity index 84%
rename from test/shared/provider/1.20.1/resultOutputs.ts
rename to modules/ingredients/test/util/providers/1.20.1/resultOutputs.ts
index ed75630..8fc6430 100644
--- a/test/shared/provider/1.20.1/resultOutputs.ts
+++ b/modules/ingredients/test/util/providers/1.20.1/resultOutputs.ts
@@ -1,11 +1,11 @@
+import { type DataProvider } from "@adeficior/testing";
import {
type Result,
BlockResult,
+ BUCKET,
FluidResult,
ItemResult,
-} from "../../../../src/common/result";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+} from "../../../../src";
export function* serializedResults(): DataProvider<[Result, object]> {
yield [
diff --git a/test/shared/provider/1.20.1/ingredientFilters.ts b/modules/ingredients/test/util/providers/1.21.1/ingredientFilters.ts
similarity index 94%
rename from test/shared/provider/1.20.1/ingredientFilters.ts
rename to modules/ingredients/test/util/providers/1.21.1/ingredientFilters.ts
index b66a2d8..6d57601 100644
--- a/test/shared/provider/1.20.1/ingredientFilters.ts
+++ b/modules/ingredients/test/util/providers/1.21.1/ingredientFilters.ts
@@ -1,17 +1,17 @@
-import type { Class } from "../../../../src/common/class";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockIngredient,
BlockTagIngredient,
+ BUCKET,
FluidIngredient,
FluidTagIngredient,
type Ingredient,
+ type IngredientFilter,
ItemIngredient,
ItemTagIngredient,
ListIngredient,
-} from "../../../../src/common/ingredient";
-import type { IngredientFilter } from "../../../../src/common/ingredient/filter";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+} from "../../../../src";
export function* matchingIngredientFilters(): DataProvider<
[IngredientFilter, Ingredient]
diff --git a/test/shared/provider/1.21.1/ingredientInputs.ts b/modules/ingredients/test/util/providers/1.21.1/ingredientInputs.ts
similarity index 92%
rename from test/shared/provider/1.21.1/ingredientInputs.ts
rename to modules/ingredients/test/util/providers/1.21.1/ingredientInputs.ts
index 1be234a..0cea025 100644
--- a/test/shared/provider/1.21.1/ingredientInputs.ts
+++ b/modules/ingredients/test/util/providers/1.21.1/ingredientInputs.ts
@@ -1,14 +1,14 @@
-import type { Class } from "../../../../src/common/class";
-import type { Ingredient } from "../../../../src/common/ingredient";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
+ BUCKET,
FluidIngredient,
FluidTagIngredient,
ItemIngredient,
ItemTagIngredient,
-} from "../../../../src/common/ingredient";
-import { BUCKET } from "../../../../src/common/units";
+ type Ingredient,
+} from "../../../../src";
import { ingredientLikeResults } from "../ingredientInputs";
-import type { DataProvider } from "../providers";
export function* invalidIngredientInputs(): DataProvider<
[unknown, Class | string]
diff --git a/test/shared/provider/1.21.1/resultFilters.ts b/modules/ingredients/test/util/providers/1.21.1/resultFilters.ts
similarity index 90%
rename from test/shared/provider/1.21.1/resultFilters.ts
rename to modules/ingredients/test/util/providers/1.21.1/resultFilters.ts
index 94d6a1c..8a86ac6 100644
--- a/test/shared/provider/1.21.1/resultFilters.ts
+++ b/modules/ingredients/test/util/providers/1.21.1/resultFilters.ts
@@ -1,22 +1,20 @@
-import type { Class } from "../../../../src/common/class";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockIngredient,
+ BlockResult,
BlockTagIngredient,
+ BUCKET,
FluidIngredient,
+ FluidResult,
FluidTagIngredient,
ItemIngredient,
+ ItemResult,
ItemTagIngredient,
ListIngredient,
-} from "../../../../src/common/ingredient";
-import type { IngredientFilter } from "../../../../src/common/ingredient/filter";
-import {
- BlockResult,
- FluidResult,
- ItemResult,
+ type IngredientFilter,
type Result,
-} from "../../../../src/common/result";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+} from "../../../../src";
export function* matchingResultFilters(): DataProvider<
[IngredientFilter, Result]
diff --git a/test/shared/provider/1.21.1/resultInputs.ts b/modules/ingredients/test/util/providers/1.21.1/resultInputs.ts
similarity index 87%
rename from test/shared/provider/1.21.1/resultInputs.ts
rename to modules/ingredients/test/util/providers/1.21.1/resultInputs.ts
index 0571c8a..c74d5d2 100644
--- a/test/shared/provider/1.21.1/resultInputs.ts
+++ b/modules/ingredients/test/util/providers/1.21.1/resultInputs.ts
@@ -1,8 +1,6 @@
-import type { Class } from "../../../../src/common/class";
-import type { Result } from "../../../../src/common/result";
-import { FluidResult, ItemResult } from "../../../../src/common/result";
-import { BUCKET } from "../../../../src/common/units";
-import type { DataProvider } from "../providers";
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
+import { BUCKET, FluidResult, ItemResult, type Result } from "../../../../src";
import { resultLikeIngredients } from "../resultInputs";
export function* invalidResultInputs(): DataProvider<
diff --git a/test/shared/provider/ingredientInputs.ts b/modules/ingredients/test/util/providers/ingredientInputs.ts
similarity index 82%
rename from test/shared/provider/ingredientInputs.ts
rename to modules/ingredients/test/util/providers/ingredientInputs.ts
index a666fb9..268c1ef 100644
--- a/test/shared/provider/ingredientInputs.ts
+++ b/modules/ingredients/test/util/providers/ingredientInputs.ts
@@ -1,3 +1,5 @@
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockIngredient,
BlockResult,
@@ -8,8 +10,6 @@ import {
type Ingredient,
type Result,
} from "../../../src";
-import type { Class } from "../../../src/common/class";
-import type { DataProvider } from "./providers";
export function* ingredientLikeResults(): DataProvider<
[Result, Class]
diff --git a/test/shared/provider/resultInputs.ts b/modules/ingredients/test/util/providers/resultInputs.ts
similarity index 82%
rename from test/shared/provider/resultInputs.ts
rename to modules/ingredients/test/util/providers/resultInputs.ts
index 9bcfbee..21c782b 100644
--- a/test/shared/provider/resultInputs.ts
+++ b/modules/ingredients/test/util/providers/resultInputs.ts
@@ -1,3 +1,5 @@
+import { type Class } from "@adeficior/data-modifier-core";
+import { type DataProvider } from "@adeficior/testing";
import {
BlockIngredient,
BlockResult,
@@ -8,8 +10,6 @@ import {
type Ingredient,
type Result,
} from "../../../src";
-import type { Class } from "../../../src/common/class";
-import type { DataProvider } from "./providers";
export function* resultLikeIngredients(): DataProvider<
[Ingredient, Class]
diff --git a/modules/lang/package.json b/modules/lang/package.json
new file mode 100644
index 0000000..b6068ff
--- /dev/null
+++ b/modules/lang/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@adeficior/data-modifier-lang",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@types/lodash-es": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ "lodash-es": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/src/emit/assets/lang.ts b/modules/lang/src/emitter.ts
similarity index 85%
rename from src/emit/assets/lang.ts
rename to modules/lang/src/emitter.ts
index 0875aac..ec77e82 100644
--- a/src/emit/assets/lang.ts
+++ b/modules/lang/src/emitter.ts
@@ -1,18 +1,25 @@
-import type { InferIds, RegistryId } from "@adeficior/data-modifier/generated";
import {
- arrayOrSelf,
- simpleResolver,
- type BaseContext,
-} from "@adeficior/pack-resolver";
+ createId,
+ encodeId,
+ Registry,
+ toJson,
+ type ClearableEmitter,
+ type Id,
+ type IdInput,
+ type LoaderContext,
+ type RegistryProvider,
+} from "@adeficior/data-modifier-core";
+import {
+ type Predicate,
+ type Replacer,
+} from "@adeficior/data-modifier-core/serializer";
+import {
+ type InferIds,
+ type RegistryId,
+} from "@adeficior/data-modifier/generated";
+import { arrayOrSelf, simpleResolver } from "@adeficior/pack-resolver";
import { mapValues, omitBy } from "lodash-es";
-import type { Predicate } from "../../common/filters.js";
-import type { Id, IdInput } from "../../common/id.js";
-import { createId, encodeId } from "../../common/id.js";
-import Registry from "../../common/registry.js";
-import type { Replacer } from "../../parser/recipe/index.js";
-import type { LangDefinition } from "../../schema/assets/lang.js";
-import { toJson } from "../../textHelper.js";
-import type { ClearableEmitter, RegistryProvider } from "../index.js";
+import { type LangDefinition } from "./schema";
type LangRule = Readonly<{
languages: string[];
@@ -50,13 +57,13 @@ export interface LangRules {
): void;
}
-export default class LangEmitter implements LangRules, ClearableEmitter {
+export class LangEmitter implements LangRules, ClearableEmitter {
private custom = new Registry();
private rules: LangRule[] = [];
constructor(private readonly registry: RegistryProvider) {}
- resolver(context: BaseContext) {
+ resolver(context: LoaderContext) {
return simpleResolver(async (acceptor) => {
const missingCustomFiles = new Set(this.custom.keys());
diff --git a/modules/lang/src/index.ts b/modules/lang/src/index.ts
new file mode 100644
index 0000000..26b627c
--- /dev/null
+++ b/modules/lang/src/index.ts
@@ -0,0 +1,4 @@
+export type { LangRules } from "./emitter";
+export type { LangRegistry } from "./loader";
+export { default } from "./module";
+export type { LangDefinition } from "./schema";
diff --git a/modules/lang/src/loader.ts b/modules/lang/src/loader.ts
new file mode 100644
index 0000000..37e06dc
--- /dev/null
+++ b/modules/lang/src/loader.ts
@@ -0,0 +1,20 @@
+import {
+ JsonLoader,
+ type Id,
+ type RegistryProvider,
+} from "@adeficior/data-modifier-core";
+import { LangSchema, type LangDefinition } from "./schema";
+
+export type LangRegistry = RegistryProvider;
+
+export class LangLoader
+ extends JsonLoader
+ implements LangRegistry
+{
+ protected parse(json: unknown, id: Id): LangDefinition | null {
+ const parsed = LangSchema.parse(json);
+ const existing = this.get(id);
+ if (!existing) return parsed;
+ return { ...existing, ...parsed };
+ }
+}
diff --git a/modules/lang/src/module.ts b/modules/lang/src/module.ts
new file mode 100644
index 0000000..0bcc5c6
--- /dev/null
+++ b/modules/lang/src/module.ts
@@ -0,0 +1,31 @@
+import { defineModule, jsonFilePattern } from "@adeficior/data-modifier-core";
+import { name } from "../package.json";
+import { LangEmitter, type LangRules } from "./emitter";
+import { LangLoader, type LangRegistry } from "./loader";
+
+export default defineModule<{
+ loaders: {
+ lang: LangRegistry;
+ };
+ emitters: {
+ lang: LangRules;
+ };
+}>({
+ importModule: name,
+ types: {
+ loaders: {
+ lang: "LangRegistry",
+ },
+ emitters: {
+ lang: "LangRules",
+ },
+ },
+ setup: (pack) => {
+ const loader = pack.loader(
+ "lang",
+ () => new LangLoader(),
+ jsonFilePattern("assets", "lang"),
+ );
+ pack.emitter("lang", () => new LangEmitter(loader()));
+ },
+});
diff --git a/modules/lang/src/schema.ts b/modules/lang/src/schema.ts
new file mode 100644
index 0000000..2cfe9b1
--- /dev/null
+++ b/modules/lang/src/schema.ts
@@ -0,0 +1,5 @@
+import * as z from "zod";
+
+export const LangSchema = z.record(z.string(), z.string());
+
+export type LangDefinition = z.infer;
diff --git a/test/__snapshots__/lang.test.ts.snap b/modules/lang/test/__snapshots__/emitter.test.ts.snap
similarity index 100%
rename from test/__snapshots__/lang.test.ts.snap
rename to modules/lang/test/__snapshots__/emitter.test.ts.snap
diff --git a/modules/lang/test/emitter.test.ts b/modules/lang/test/emitter.test.ts
new file mode 100644
index 0000000..ab2dfc8
--- /dev/null
+++ b/modules/lang/test/emitter.test.ts
@@ -0,0 +1,110 @@
+import { type LoaderContext } from "@adeficior/data-modifier-core";
+import {
+ createTestAcceptor,
+ createTestLogger,
+} from "@adeficior/pack-resolver/testing";
+import { createTestDataResolver } from "@adeficior/testing";
+import { afterEach, beforeAll, describe, expect, it } from "bun:test";
+import { LangEmitter } from "../src/emitter";
+import { LangLoader } from "../src/loader";
+
+const version = "1.20.1";
+const context: LoaderContext = { logger: createTestLogger() };
+const loader = new LangLoader();
+const emitter = new LangEmitter(loader);
+
+beforeAll(async () => {
+ const resolver = await createTestDataResolver(version, {
+ include: "assets/*/lang/en_us.json",
+ });
+ await resolver.extract(loader);
+});
+
+afterEach(() => {
+ emitter.clear();
+});
+
+describe("replacing translation entries", () => {
+ it("replaces using string value", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.replaceValue("diorite", "bird poop", { lang: "en_us" });
+
+ await emitter.resolver(context).extract(acceptor);
+
+ expect(acceptor.jsonAt("assets/minecraft/lang/en_us.json")).toMatchSnapshot(
+ "replaced diorite values",
+ );
+ });
+
+ it("replaces requires the case to match", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.replaceValue("Stone", "rock", {
+ lang: "en_us",
+ matchCase: true,
+ });
+
+ await emitter.resolver(context).extract(acceptor);
+
+ expect(acceptor.jsonAt("assets/minecraft/lang/en_us.json")).toMatchSnapshot(
+ "replaced matched case values",
+ );
+ });
+
+ it("emits custom values", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.addCustom("en_us", "something.else", "The Value");
+ emitter.entryName("minecraft:item", "minecraft:diamond", "Sapphire");
+
+ await emitter.resolver(context).extract(acceptor);
+
+ expect(acceptor.jsonAt("assets/minecraft/lang/en_us.json")).toMatchSnapshot(
+ "custom values",
+ );
+ });
+
+ it("emits custom values together with replaced values", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.addCustom("en_us", "something.else", "The Value");
+ emitter.entryName("minecraft:item", "minecraft:diamond", "Sapphire");
+ emitter.replaceValue("Diamond", "ruby");
+
+ await emitter.resolver(context).extract(acceptor);
+
+ expect(acceptor.jsonAt("assets/minecraft/lang/en_us.json")).toMatchSnapshot(
+ "custom values",
+ );
+ });
+
+ it("only modifies values of given mods", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.replaceValue("Iron", "Steel", {
+ lang: "en_us",
+ namespaces: ["create", "farmersdelight"],
+ });
+
+ await emitter.resolver(context).extract(acceptor);
+
+ expect(acceptor.paths()).toHaveLength(2);
+ });
+
+ it("respects keepCase option", async () => {
+ const acceptor = createTestAcceptor();
+
+ emitter.replaceValue("Dark Oak", "Mahogony", {
+ lang: "en_us",
+ namespaces: ["minecraft"],
+ keepCase: false,
+ });
+
+ await emitter.resolver(context).extract(acceptor);
+
+ expect(acceptor.jsonAt("assets/minecraft/lang/en_us.json")).toMatchSnapshot(
+ "kept case values",
+ );
+ });
+});
diff --git a/modules/lang/test/resources/1.20.1/default/additional/assets/create/lang/en_us.json b/modules/lang/test/resources/1.20.1/default/additional/assets/create/lang/en_us.json
new file mode 100644
index 0000000..6a5e996
--- /dev/null
+++ b/modules/lang/test/resources/1.20.1/default/additional/assets/create/lang/en_us.json
@@ -0,0 +1,3648 @@
+{
+ "advancement.create.andesite_alloy": "Sturdier Rocks",
+ "advancement.create.andesite_alloy.desc": "Obtain some Andesite Alloy, Create's most important resource",
+ "advancement.create.andesite_casing": "The Andesite Age",
+ "advancement.create.andesite_casing.desc": "Apply Andesite Alloy to stripped wood, creating a basic casing for your machines",
+ "advancement.create.anvil_plough": "Blacksmith Artillery",
+ "advancement.create.anvil_plough.desc": "Launch an Anvil with Mechanical Ploughs",
+ "advancement.create.arm_blaze_burner": "Combust-o-Tron",
+ "advancement.create.arm_blaze_burner.desc": "Instruct a Mechanical Arm to feed your Blaze Burner",
+ "advancement.create.arm_many_targets": "Organize-o-Tron",
+ "advancement.create.arm_many_targets.desc": "Program a Mechanical Arm with 10 or more output locations",
+ "advancement.create.backtank": "Pressure to Go",
+ "advancement.create.backtank.desc": "Create a Copper Backtank and make it accumulate air pressure",
+ "advancement.create.belt": "Kelp Drive",
+ "advancement.create.belt.desc": "Connect two Shafts with a Mechanical Belt",
+ "advancement.create.belt_funnel_kiss": "The Parrots and the Flaps",
+ "advancement.create.belt_funnel_kiss.desc": "Make two Belt-mounted Funnels kiss\n§7(Hidden Advancement)",
+ "advancement.create.brass": "Real Alloys",
+ "advancement.create.brass.desc": "Create Brass Ingots by alloying Copper and Zinc Ingots in your Blaze-heated Mechanical Mixer",
+ "advancement.create.brass_casing": "The Brass Age",
+ "advancement.create.brass_casing.desc": "Apply Brass Ingots to stripped wood, creating a casing for more sophisticated machines",
+ "advancement.create.burner": "Sentient Fireplace",
+ "advancement.create.burner.desc": "Obtain a Blaze Burner",
+ "advancement.create.cardboard": "Part and Parcel",
+ "advancement.create.cardboard.desc": "Produce or obtain your first Cardboard",
+ "advancement.create.cardboard_armor": "Full Stealth",
+ "advancement.create.cardboard_armor.desc": "Sneak around in full Cardboard Armor",
+ "advancement.create.cardboard_armor_trim": "Arts and Crafts",
+ "advancement.create.cardboard_armor_trim.desc": "Decorate your cardboard equipment with armor trims\n§7(Hidden Advancement)",
+ "advancement.create.cart_pickup": "Strong Arms",
+ "advancement.create.cart_pickup.desc": "Pick up a Minecart Contraption with at least 200 attached blocks",
+ "advancement.create.chained_drain": "On a Roll",
+ "advancement.create.chained_drain.desc": "Watch an item move across a row of Item Drains\n§7(Hidden Advancement)",
+ "advancement.create.chocolate_bucket": "A World of Imagination",
+ "advancement.create.chocolate_bucket.desc": "Obtain a bucket of molten chocolate",
+ "advancement.create.chute": "Vertical Logistics",
+ "advancement.create.chute.desc": "Transport some items by Chute",
+ "advancement.create.clockwork_bearing": "Contraption o'Clock",
+ "advancement.create.clockwork_bearing.desc": "Assemble a structure mounted on a Clockwork Bearing",
+ "advancement.create.compacting": "Compactification",
+ "advancement.create.compacting.desc": "Use a Mechanical Press and a Basin to create fewer items from more",
+ "advancement.create.conductor": "Conductor Instructor",
+ "advancement.create.conductor.desc": "Instruct a Train driver with a Train Schedule",
+ "advancement.create.contraption_actors": "Moving with Purpose",
+ "advancement.create.contraption_actors.desc": "Create a Contraption with drills, saws, or harvesters on board",
+ "advancement.create.copper": "Cuprum Bokum",
+ "advancement.create.copper.desc": "Amass some Copper Ingots for your exploits in fluid manipulation",
+ "advancement.create.copper_casing": "The Copper Age",
+ "advancement.create.copper_casing.desc": "Apply Copper Ingots to stripped wood, creating a waterproof casing for your machines",
+ "advancement.create.crafter_lazy_000": "Desperate Measures",
+ "advancement.create.crafter_lazy_000.desc": "Drastically slow down a Mechanical Crafter to procrastinate on proper infrastructure\n§7(Hidden Advancement)",
+ "advancement.create.cross_streams": "Don't Cross the Streams!",
+ "advancement.create.cross_streams.desc": "Watch two fluids meet in your pipe network\n§7(Hidden Advancement)",
+ "advancement.create.crusher_maxed_0000": "Crushing It",
+ "advancement.create.crusher_maxed_0000.desc": "Operate a pair of Crushing Wheels at maximum speed",
+ "advancement.create.crushing_wheel": "Wheels of Destruction",
+ "advancement.create.crushing_wheel.desc": "Place and power a set of Crushing Wheels",
+ "advancement.create.cuckoo_clock": "Is it Time?",
+ "advancement.create.cuckoo_clock.desc": "Witness your Cuckoo Clock announce bedtime",
+ "advancement.create.deployer": "Artificial Intelligence",
+ "advancement.create.deployer.desc": "Place and power a Deployer, the perfect reflection of yourself",
+ "advancement.create.display_board_0": "Dynamic Timetables",
+ "advancement.create.display_board_0.desc": "Forecast a Train's arrival on your Display Board with the help of Display Links",
+ "advancement.create.display_link": "Big Data",
+ "advancement.create.display_link.desc": "Use a Display Link to visualise information",
+ "advancement.create.diving_suit": "Ready for the Depths",
+ "advancement.create.diving_suit.desc": "Equip a Diving Helmet and a Copper Backtank, then jump into water",
+ "advancement.create.diving_suit_lava": "Swimming with the Striders",
+ "advancement.create.diving_suit_lava.desc": "Attempt to take a dive in lava with your netherite diving gear",
+ "advancement.create.drain": "Tumble Draining",
+ "advancement.create.drain.desc": "Watch a fluid-containing item be emptied by an Item Drain",
+ "advancement.create.ejector_maxed": "Springboard Champion",
+ "advancement.create.ejector_maxed.desc": "Get launched more than 30 blocks by a Weighted Ejector",
+ "advancement.create.encased_fan": "Wind Maker",
+ "advancement.create.encased_fan.desc": "Place and power an Encased Fan",
+ "advancement.create.extendo_grip": "Boioioing!",
+ "advancement.create.extendo_grip.desc": "Get hold of an Extendo Grip",
+ "advancement.create.extendo_grip_dual": "To Full Extent",
+ "advancement.create.extendo_grip_dual.desc": "Dual-wield Extendo Grips for superhuman reach\n§7(Hidden Advancement)",
+ "advancement.create.factory_gauge": "High Logistics",
+ "advancement.create.factory_gauge.desc": "Trigger an automatic package request using Factory Gauges",
+ "advancement.create.fan_processing": "Processing by Particle",
+ "advancement.create.fan_processing.desc": "Use an Encased Fan to process materials",
+ "advancement.create.fist_bump": "Pound It, Bro!",
+ "advancement.create.fist_bump.desc": "Make two Deployers fist-bump\n§7(Hidden Advancement)",
+ "advancement.create.foods": "Balanced Diet",
+ "advancement.create.foods.desc": "Create Chocolate Glazed Berries, a Honeyed Apple, and a Sweet Roll all from the same Spout",
+ "advancement.create.frogport": "Hungry hoppers",
+ "advancement.create.frogport.desc": "Catch packages from your Chain Conveyor using a Frogport",
+ "advancement.create.funnel": "Airport Aesthetic",
+ "advancement.create.funnel.desc": "Extract or insert items into a container using a Funnel",
+ "advancement.create.glass_pipe": "Flow Discovery",
+ "advancement.create.glass_pipe.desc": "Use your Wrench on a pipe that contains a fluid",
+ "advancement.create.hand_crank_000": "Workout Session",
+ "advancement.create.hand_crank_000.desc": "Use a Hand Crank until fully exhausted\n§7(Hidden Advancement)",
+ "advancement.create.haunted_bell": "Shadow Sense",
+ "advancement.create.haunted_bell.desc": "Toll a Haunted Bell",
+ "advancement.create.honey_drain": "Autonomous Bee-Keeping",
+ "advancement.create.honey_drain.desc": "Use pipes to pull honey from a Bee Nest or Beehive",
+ "advancement.create.hose_pulley": "Industrial Spillage",
+ "advancement.create.hose_pulley.desc": "Lower a Hose Pulley and watch it drain or fill a body of fluid",
+ "advancement.create.hose_pulley_lava": "Tapping the Mantle",
+ "advancement.create.hose_pulley_lava.desc": "Pump from a body of lava large enough to be considered infinite",
+ "advancement.create.lava_wheel_00000": "Magma Wheel",
+ "advancement.create.lava_wheel_00000.desc": "This shouldn't have worked\n§7(Hidden Advancement)",
+ "advancement.create.linked_controller": "Remote Activation",
+ "advancement.create.linked_controller.desc": "Activate a Redstone Link using a Linked Controller",
+ "advancement.create.long_train": "Ambitious Endeavours",
+ "advancement.create.long_train.desc": "Create a Train with at least 6 carriages",
+ "advancement.create.long_travel": "Field Trip",
+ "advancement.create.long_travel.desc": "Leave a Train Seat over 5000 blocks away from where you started travelling",
+ "advancement.create.mechanical_arm": "Busy Hands",
+ "advancement.create.mechanical_arm.desc": "Watch your Mechanical Arm transport its first item",
+ "advancement.create.mechanical_crafter": "Automated Assembly",
+ "advancement.create.mechanical_crafter.desc": "Place and power some Mechanical Crafters",
+ "advancement.create.mechanical_mixer": "Mixing It Up",
+ "advancement.create.mechanical_mixer.desc": "Combine ingredients in a Mechanical Mixer",
+ "advancement.create.mechanical_press": "Bonk!",
+ "advancement.create.mechanical_press.desc": "Create some sheets in a Mechanical Press",
+ "advancement.create.mechanical_pump_0": "Under Pressure",
+ "advancement.create.mechanical_pump_0.desc": "Place and power a Mechanical Pump",
+ "advancement.create.millstone": "Embrace the Grind",
+ "advancement.create.millstone.desc": "Use a Millstone to pulverise materials",
+ "advancement.create.musical_arm": "DJ Mechanico",
+ "advancement.create.musical_arm.desc": "Watch a Mechanical Arm operate your Jukebox\n§7(Hidden Advancement)",
+ "advancement.create.package_chute_throw": "Nothing but net",
+ "advancement.create.package_chute_throw.desc": "Land your cardboard package throw in an item chute\n§7(Hidden Advancement)",
+ "advancement.create.packager": "Post Production",
+ "advancement.create.packager.desc": "Package items from an inventory using the Packager",
+ "advancement.create.pipe_organ": "The Pipe Organ",
+ "advancement.create.pipe_organ.desc": "Attach 12 uniquely pitched Steam Whistles to a single Fluid Tank\n§7(Hidden Advancement)",
+ "advancement.create.portable_storage_interface": "Drive-by Exchange",
+ "advancement.create.portable_storage_interface.desc": "Use a Portable Storage Interface to take or insert items into a Contraption",
+ "advancement.create.potato_cannon": "Fwoomp!",
+ "advancement.create.potato_cannon.desc": "Defeat an enemy with your Potato Cannon",
+ "advancement.create.potato_cannon_collide": "Veggie Fireworks",
+ "advancement.create.potato_cannon_collide.desc": "Cause Potato Cannon projectiles of different types to collide with each other",
+ "advancement.create.precision_mechanism": "Complex Curiosities",
+ "advancement.create.precision_mechanism.desc": "Assemble a Precision Mechanism",
+ "advancement.create.pulley_maxed": "Rope to Nowhere",
+ "advancement.create.pulley_maxed.desc": "Extend a Rope Pulley over 200 blocks deep",
+ "advancement.create.red_signal": "Expert Driver",
+ "advancement.create.red_signal.desc": "Run a red Train Signal\n§7(Hidden Advancement)",
+ "advancement.create.root": "Welcome to Create",
+ "advancement.create.root.desc": "Here Be Contraptions",
+ "advancement.create.rose_quartz": "Supercharged",
+ "advancement.create.rose_quartz.desc": "Polish some Rose Quartz",
+ "advancement.create.saw_processing": "Workshop's Most Feared",
+ "advancement.create.saw_processing.desc": "Use an upright Mechanical Saw to process materials",
+ "advancement.create.self_deploying": "Self-Driving Cart",
+ "advancement.create.self_deploying.desc": "Create a Minecart Contraption that places tracks in front of itself",
+ "advancement.create.shifting_gears": "Shifting Gears",
+ "advancement.create.shifting_gears.desc": "Connect a Large Cogwheel to a Small Cogwheel, allowing you to change the speed of your Contraption",
+ "advancement.create.speed_controller": "Engineers hate this simple trick!",
+ "advancement.create.speed_controller.desc": "Fine-tune your Contraption with a Rotation Speed Controller",
+ "advancement.create.spout": "Sploosh",
+ "advancement.create.spout.desc": "Watch a fluid-containing item be filled by a Spout",
+ "advancement.create.steam_engine": "The Powerhouse",
+ "advancement.create.steam_engine.desc": "Use a Steam Engine to generate torque",
+ "advancement.create.steam_engine_maxed": "Full Steam",
+ "advancement.create.steam_engine_maxed.desc": "Run a boiler at the maximum level of power",
+ "advancement.create.steam_whistle": "Voice of an Angel",
+ "advancement.create.steam_whistle.desc": "Activate a Steam Whistle",
+ "advancement.create.stock_ticker": "Order Up!",
+ "advancement.create.stock_ticker.desc": "Employ a mob at your stock ticker and make your first requests",
+ "advancement.create.stressometer": "Stress for Nerds",
+ "advancement.create.stressometer.desc": "Get an exact readout with the help of Engineer's Goggles and a Stressometer",
+ "advancement.create.stressometer_maxed": "Perfectly Stressed",
+ "advancement.create.stressometer_maxed.desc": "Get a 100% readout from a Stressometer\n§7(Hidden Advancement)",
+ "advancement.create.sturdy_sheet": "The Sturdiest Rocks",
+ "advancement.create.sturdy_sheet.desc": "Assemble a Sturdy Sheet by refining Powdered Obsidian",
+ "advancement.create.super_glue": "Area of Connect",
+ "advancement.create.super_glue.desc": "Super Glue some blocks into a group",
+ "advancement.create.table_cloth_shop": "Open for business",
+ "advancement.create.table_cloth_shop.desc": "Put items up for sale using a Table Cloth",
+ "advancement.create.track_0": "A New Gauge",
+ "advancement.create.track_0.desc": "Obtain some Train Tracks",
+ "advancement.create.track_crafting_factory": "Track Factory",
+ "advancement.create.track_crafting_factory.desc": "Produce more than 1000 Train Tracks with the same Mechanical Press",
+ "advancement.create.track_signal": "Traffic Control",
+ "advancement.create.track_signal.desc": "Place a Train Signal",
+ "advancement.create.train": "All Aboard!",
+ "advancement.create.train.desc": "Assemble your first Train",
+ "advancement.create.train_casing_00": "The Locomotive Age",
+ "advancement.create.train_casing_00.desc": "Use Sturdy Sheets to create a casing for railway components",
+ "advancement.create.train_crash": "Terrible Service",
+ "advancement.create.train_crash.desc": "Witness a Train crash as a passenger\n§7(Hidden Advancement)",
+ "advancement.create.train_crash_backwards": "Blind Spot",
+ "advancement.create.train_crash_backwards.desc": "Crash into another Train while driving backwards\n§7(Hidden Advancement)",
+ "advancement.create.train_portal": "Dimensional Commuter",
+ "advancement.create.train_portal.desc": "Ride a Train through a portal",
+ "advancement.create.train_roadkill": "Road Kill",
+ "advancement.create.train_roadkill.desc": "Run over an enemy with your Train\n§7(Hidden Advancement)",
+ "advancement.create.train_whistle": "Choo Choo!",
+ "advancement.create.train_whistle.desc": "Assemble a Steam Whistle to your Train and activate it while driving",
+ "advancement.create.water_supply": "Puddle Collector",
+ "advancement.create.water_supply.desc": "Use the pulling end of a Fluid Pipe or Mechanical Pump to collect water",
+ "advancement.create.water_wheel": "Harnessed Hydraulics",
+ "advancement.create.water_wheel.desc": "Place a Water Wheel and use it to generate torque",
+ "advancement.create.windmill": "A mild Breeze",
+ "advancement.create.windmill.desc": "Assemble a windmill and use it to generate torque",
+ "advancement.create.windmill_maxed": "A strong Breeze",
+ "advancement.create.windmill_maxed.desc": "Assemble a windmill of maximum strength",
+ "advancement.create.wrench_goggles": "Kitted Out",
+ "advancement.create.wrench_goggles.desc": "Equip Engineer's Goggles and a Wrench",
+ "block.create.acacia_window": "Acacia Window",
+ "block.create.acacia_window_pane": "Acacia Window Pane",
+ "block.create.adjustable_chain_gearshift": "Adjustable Chain Gearshift",
+ "block.create.analog_lever": "Analog Lever",
+ "block.create.andesite_alloy_block": "Block of Andesite Alloy",
+ "block.create.andesite_bars": "Andesite Bars",
+ "block.create.andesite_belt_funnel": "Andesite Belt Funnel",
+ "block.create.andesite_casing": "Andesite Casing",
+ "block.create.andesite_door": "Andesite Door",
+ "block.create.andesite_encased_cogwheel": "Andesite Encased Cogwheel",
+ "block.create.andesite_encased_large_cogwheel": "Andesite Encased Large Cogwheel",
+ "block.create.andesite_encased_shaft": "Andesite Encased Shaft",
+ "block.create.andesite_funnel": "Andesite Funnel",
+ "block.create.andesite_ladder": "Andesite Ladder",
+ "block.create.andesite_pillar": "Andesite Pillar",
+ "block.create.andesite_scaffolding": "Andesite Scaffolding",
+ "block.create.andesite_table_cloth": "Andesite Table Cover",
+ "block.create.andesite_tunnel": "Andesite Tunnel",
+ "block.create.asurine": "Asurine",
+ "block.create.asurine_pillar": "Asurine Pillar",
+ "block.create.bamboo_window": "Bamboo Window",
+ "block.create.bamboo_window_pane": "Bamboo Window Pane",
+ "block.create.basin": "Basin",
+ "block.create.belt": "Belt",
+ "block.create.birch_window": "Birch Window",
+ "block.create.birch_window_pane": "Birch Window Pane",
+ "block.create.black_nixie_tube": "Black Nixie Tube",
+ "block.create.black_postbox": "Black Postbox",
+ "block.create.black_sail": "Black Sail",
+ "block.create.black_seat": "Black Seat",
+ "block.create.black_table_cloth": "Black Table Cloth",
+ "block.create.black_toolbox": "Black Toolbox",
+ "block.create.black_valve_handle": "Black Valve Handle",
+ "block.create.blaze_burner": "Blaze Burner",
+ "block.create.blue_nixie_tube": "Blue Nixie Tube",
+ "block.create.blue_postbox": "Blue Postbox",
+ "block.create.blue_sail": "Blue Sail",
+ "block.create.blue_seat": "Blue Seat",
+ "block.create.blue_table_cloth": "Blue Table Cloth",
+ "block.create.blue_toolbox": "Blue Toolbox",
+ "block.create.blue_valve_handle": "Blue Valve Handle",
+ "block.create.bound_cardboard_block": "Bound Block of Cardboard",
+ "block.create.brass_bars": "Brass Bars",
+ "block.create.brass_belt_funnel": "Brass Belt Funnel",
+ "block.create.brass_block": "Block of Brass",
+ "block.create.brass_casing": "Brass Casing",
+ "block.create.brass_door": "Brass Door",
+ "block.create.brass_encased_cogwheel": "Brass Encased Cogwheel",
+ "block.create.brass_encased_large_cogwheel": "Brass Encased Large Cogwheel",
+ "block.create.brass_encased_shaft": "Brass Encased Shaft",
+ "block.create.brass_funnel": "Brass Funnel",
+ "block.create.brass_ladder": "Brass Ladder",
+ "block.create.brass_scaffolding": "Brass Scaffolding",
+ "block.create.brass_table_cloth": "Brass Table Cover",
+ "block.create.brass_tunnel": "Brass Tunnel",
+ "block.create.brown_nixie_tube": "Brown Nixie Tube",
+ "block.create.brown_postbox": "Brown Postbox",
+ "block.create.brown_sail": "Brown Sail",
+ "block.create.brown_seat": "Brown Seat",
+ "block.create.brown_table_cloth": "Brown Table Cloth",
+ "block.create.brown_toolbox": "Brown Toolbox",
+ "block.create.brown_valve_handle": "Brown Valve Handle",
+ "block.create.calcite_pillar": "Calcite Pillar",
+ "block.create.cardboard_block": "Block of Cardboard",
+ "block.create.cart_assembler": "Cart Assembler",
+ "block.create.chain_conveyor": "Chain Conveyor",
+ "block.create.cherry_window": "Cherry Window",
+ "block.create.cherry_window_pane": "Cherry Window Pane",
+ "block.create.chocolate": "Chocolate",
+ "block.create.chute": "Chute",
+ "block.create.clipboard": "Clipboard",
+ "block.create.clipboard.tooltip.behaviour1": "Opens the _Interface_. _Sneak-Click_ to _place_ it on a surface.",
+ "block.create.clipboard.tooltip.behaviour2": "Some blocks' _settings_, such as filters, can be _copied (R-Click)_ and _applied (L-Click)_ elsewhere.",
+ "block.create.clipboard.tooltip.condition1": "When R-Clicked",
+ "block.create.clipboard.tooltip.condition2": "When Used on Blocks",
+ "block.create.clipboard.tooltip.summary": "_Keeps your notes_ and makes you look more professional.",
+ "block.create.clockwork_bearing": "Clockwork Bearing",
+ "block.create.clutch": "Clutch",
+ "block.create.cogwheel": "Cogwheel",
+ "block.create.content_observer": "Smart Observer",
+ "block.create.contraption_controls": "Contraption Controls",
+ "block.create.controller_rail": "Controller Rail",
+ "block.create.controller_rail.tooltip.summary": "A _uni-directional_ powered rail with _variable speed_, controlled by the _signal strength_ supplied to it.",
+ "block.create.controls": "Train Controls",
+ "block.create.copper_backtank": "Copper Backtank",
+ "block.create.copper_bars": "Copper Bars",
+ "block.create.copper_casing": "Copper Casing",
+ "block.create.copper_door": "Copper Door",
+ "block.create.copper_ladder": "Copper Ladder",
+ "block.create.copper_scaffolding": "Copper Scaffolding",
+ "block.create.copper_shingle_slab": "Copper Shingle Slab",
+ "block.create.copper_shingle_stairs": "Copper Shingle Stairs",
+ "block.create.copper_shingles": "Copper Shingles",
+ "block.create.copper_table_cloth": "Copper Table Cover",
+ "block.create.copper_tile_slab": "Copper Tile Slab",
+ "block.create.copper_tile_stairs": "Copper Tile Stairs",
+ "block.create.copper_tiles": "Copper Tiles",
+ "block.create.copper_valve_handle": "Copper Valve Handle",
+ "block.create.copycat_bars": "Copycat Bars",
+ "block.create.copycat_base": "Copycat Base",
+ "block.create.copycat_panel": "Copycat Panel",
+ "block.create.copycat_panel.tooltip.behaviour1": "Applies _held item_ as its _material_ if possible. _Click again_ to cycle _orientation_ or _powered_ state. Use a _Wrench_ to _reset_ the material.",
+ "block.create.copycat_panel.tooltip.condition1": "When R-Clicked",
+ "block.create.copycat_panel.tooltip.summary": "_Converts_ any _full block_ into a decorative panel. Also accepts _Bars_ and _Trapdoors_.",
+ "block.create.copycat_step": "Copycat Step",
+ "block.create.copycat_step.tooltip.behaviour1": "Applies _held item_ as its _material_ if possible. _Click again_ to cycle _orientation_ or _powered_ state. Use a _Wrench_ to _reset_ the material.",
+ "block.create.copycat_step.tooltip.condition1": "When R-Clicked",
+ "block.create.copycat_step.tooltip.summary": "_Converts_ any _full block_ into a decorative step.",
+ "block.create.creative_crate": "Creative Crate",
+ "block.create.creative_crate.tooltip.behaviour1": "Anything _extracting_ from this container will provide an _endless supply_ of the item specified. Items _inserted_ into this crate will be _voided._",
+ "block.create.creative_crate.tooltip.behaviour2": "Provides any material required by the Schematic.",
+ "block.create.creative_crate.tooltip.condition1": "When Item in Filter Slot",
+ "block.create.creative_crate.tooltip.condition2": "When next to Schematicannon",
+ "block.create.creative_crate.tooltip.summary": "This _Storage Container_ allows infinite replication of items.",
+ "block.create.creative_fluid_tank": "Creative Fluid Tank",
+ "block.create.creative_motor": "Creative Motor",
+ "block.create.crimsite": "Crimsite",
+ "block.create.crimsite_pillar": "Crimsite Pillar",
+ "block.create.crimson_window": "Crimson Window",
+ "block.create.crimson_window_pane": "Crimson Window Pane",
+ "block.create.crushing_wheel": "Crushing Wheel",
+ "block.create.crushing_wheel_controller": "Crushing Wheel Controller",
+ "block.create.cuckoo_clock": "Cuckoo Clock",
+ "block.create.cuckoo_clock.tooltip.behaviour1": "Shows the _current time_ and plays a tune twice a day. _Activates_ once at _noon_ and at dusk, as soon as _players can sleep_.",
+ "block.create.cuckoo_clock.tooltip.condition1": "When Powered by Kinetics",
+ "block.create.cuckoo_clock.tooltip.summary": "Fine craftsmanship for _decorating_ a space and _keeping track of time_.",
+ "block.create.cut_andesite": "Cut Andesite",
+ "block.create.cut_andesite_brick_slab": "Cut Andesite Brick Slab",
+ "block.create.cut_andesite_brick_stairs": "Cut Andesite Brick Stairs",
+ "block.create.cut_andesite_brick_wall": "Cut Andesite Brick Wall",
+ "block.create.cut_andesite_bricks": "Cut Andesite Bricks",
+ "block.create.cut_andesite_slab": "Cut Andesite Slab",
+ "block.create.cut_andesite_stairs": "Cut Andesite Stairs",
+ "block.create.cut_andesite_wall": "Cut Andesite Wall",
+ "block.create.cut_asurine": "Cut Asurine",
+ "block.create.cut_asurine_brick_slab": "Cut Asurine Brick Slab",
+ "block.create.cut_asurine_brick_stairs": "Cut Asurine Brick Stairs",
+ "block.create.cut_asurine_brick_wall": "Cut Asurine Brick Wall",
+ "block.create.cut_asurine_bricks": "Cut Asurine Bricks",
+ "block.create.cut_asurine_slab": "Cut Asurine Slab",
+ "block.create.cut_asurine_stairs": "Cut Asurine Stairs",
+ "block.create.cut_asurine_wall": "Cut Asurine Wall",
+ "block.create.cut_calcite": "Cut Calcite",
+ "block.create.cut_calcite_brick_slab": "Cut Calcite Brick Slab",
+ "block.create.cut_calcite_brick_stairs": "Cut Calcite Brick Stairs",
+ "block.create.cut_calcite_brick_wall": "Cut Calcite Brick Wall",
+ "block.create.cut_calcite_bricks": "Cut Calcite Bricks",
+ "block.create.cut_calcite_slab": "Cut Calcite Slab",
+ "block.create.cut_calcite_stairs": "Cut Calcite Stairs",
+ "block.create.cut_calcite_wall": "Cut Calcite Wall",
+ "block.create.cut_crimsite": "Cut Crimsite",
+ "block.create.cut_crimsite_brick_slab": "Cut Crimsite Brick Slab",
+ "block.create.cut_crimsite_brick_stairs": "Cut Crimsite Brick Stairs",
+ "block.create.cut_crimsite_brick_wall": "Cut Crimsite Brick Wall",
+ "block.create.cut_crimsite_bricks": "Cut Crimsite Bricks",
+ "block.create.cut_crimsite_slab": "Cut Crimsite Slab",
+ "block.create.cut_crimsite_stairs": "Cut Crimsite Stairs",
+ "block.create.cut_crimsite_wall": "Cut Crimsite Wall",
+ "block.create.cut_deepslate": "Cut Deepslate",
+ "block.create.cut_deepslate_brick_slab": "Cut Deepslate Brick Slab",
+ "block.create.cut_deepslate_brick_stairs": "Cut Deepslate Brick Stairs",
+ "block.create.cut_deepslate_brick_wall": "Cut Deepslate Brick Wall",
+ "block.create.cut_deepslate_bricks": "Cut Deepslate Bricks",
+ "block.create.cut_deepslate_slab": "Cut Deepslate Slab",
+ "block.create.cut_deepslate_stairs": "Cut Deepslate Stairs",
+ "block.create.cut_deepslate_wall": "Cut Deepslate Wall",
+ "block.create.cut_diorite": "Cut Diorite",
+ "block.create.cut_diorite_brick_slab": "Cut Diorite Brick Slab",
+ "block.create.cut_diorite_brick_stairs": "Cut Diorite Brick Stairs",
+ "block.create.cut_diorite_brick_wall": "Cut Diorite Brick Wall",
+ "block.create.cut_diorite_bricks": "Cut Diorite Bricks",
+ "block.create.cut_diorite_slab": "Cut Diorite Slab",
+ "block.create.cut_diorite_stairs": "Cut Diorite Stairs",
+ "block.create.cut_diorite_wall": "Cut Diorite Wall",
+ "block.create.cut_dripstone": "Cut Dripstone",
+ "block.create.cut_dripstone_brick_slab": "Cut Dripstone Brick Slab",
+ "block.create.cut_dripstone_brick_stairs": "Cut Dripstone Brick Stairs",
+ "block.create.cut_dripstone_brick_wall": "Cut Dripstone Brick Wall",
+ "block.create.cut_dripstone_bricks": "Cut Dripstone Bricks",
+ "block.create.cut_dripstone_slab": "Cut Dripstone Slab",
+ "block.create.cut_dripstone_stairs": "Cut Dripstone Stairs",
+ "block.create.cut_dripstone_wall": "Cut Dripstone Wall",
+ "block.create.cut_granite": "Cut Granite",
+ "block.create.cut_granite_brick_slab": "Cut Granite Brick Slab",
+ "block.create.cut_granite_brick_stairs": "Cut Granite Brick Stairs",
+ "block.create.cut_granite_brick_wall": "Cut Granite Brick Wall",
+ "block.create.cut_granite_bricks": "Cut Granite Bricks",
+ "block.create.cut_granite_slab": "Cut Granite Slab",
+ "block.create.cut_granite_stairs": "Cut Granite Stairs",
+ "block.create.cut_granite_wall": "Cut Granite Wall",
+ "block.create.cut_limestone": "Cut Limestone",
+ "block.create.cut_limestone_brick_slab": "Cut Limestone Brick Slab",
+ "block.create.cut_limestone_brick_stairs": "Cut Limestone Brick Stairs",
+ "block.create.cut_limestone_brick_wall": "Cut Limestone Brick Wall",
+ "block.create.cut_limestone_bricks": "Cut Limestone Bricks",
+ "block.create.cut_limestone_slab": "Cut Limestone Slab",
+ "block.create.cut_limestone_stairs": "Cut Limestone Stairs",
+ "block.create.cut_limestone_wall": "Cut Limestone Wall",
+ "block.create.cut_ochrum": "Cut Ochrum",
+ "block.create.cut_ochrum_brick_slab": "Cut Ochrum Brick Slab",
+ "block.create.cut_ochrum_brick_stairs": "Cut Ochrum Brick Stairs",
+ "block.create.cut_ochrum_brick_wall": "Cut Ochrum Brick Wall",
+ "block.create.cut_ochrum_bricks": "Cut Ochrum Bricks",
+ "block.create.cut_ochrum_slab": "Cut Ochrum Slab",
+ "block.create.cut_ochrum_stairs": "Cut Ochrum Stairs",
+ "block.create.cut_ochrum_wall": "Cut Ochrum Wall",
+ "block.create.cut_scorchia": "Cut Scorchia",
+ "block.create.cut_scorchia_brick_slab": "Cut Scorchia Brick Slab",
+ "block.create.cut_scorchia_brick_stairs": "Cut Scorchia Brick Stairs",
+ "block.create.cut_scorchia_brick_wall": "Cut Scorchia Brick Wall",
+ "block.create.cut_scorchia_bricks": "Cut Scorchia Bricks",
+ "block.create.cut_scorchia_slab": "Cut Scorchia Slab",
+ "block.create.cut_scorchia_stairs": "Cut Scorchia Stairs",
+ "block.create.cut_scorchia_wall": "Cut Scorchia Wall",
+ "block.create.cut_scoria": "Cut Scoria",
+ "block.create.cut_scoria_brick_slab": "Cut Scoria Brick Slab",
+ "block.create.cut_scoria_brick_stairs": "Cut Scoria Brick Stairs",
+ "block.create.cut_scoria_brick_wall": "Cut Scoria Brick Wall",
+ "block.create.cut_scoria_bricks": "Cut Scoria Bricks",
+ "block.create.cut_scoria_slab": "Cut Scoria Slab",
+ "block.create.cut_scoria_stairs": "Cut Scoria Stairs",
+ "block.create.cut_scoria_wall": "Cut Scoria Wall",
+ "block.create.cut_tuff": "Cut Tuff",
+ "block.create.cut_tuff_brick_slab": "Cut Tuff Brick Slab",
+ "block.create.cut_tuff_brick_stairs": "Cut Tuff Brick Stairs",
+ "block.create.cut_tuff_brick_wall": "Cut Tuff Brick Wall",
+ "block.create.cut_tuff_bricks": "Cut Tuff Bricks",
+ "block.create.cut_tuff_slab": "Cut Tuff Slab",
+ "block.create.cut_tuff_stairs": "Cut Tuff Stairs",
+ "block.create.cut_tuff_wall": "Cut Tuff Wall",
+ "block.create.cut_veridium": "Cut Veridium",
+ "block.create.cut_veridium_brick_slab": "Cut Veridium Brick Slab",
+ "block.create.cut_veridium_brick_stairs": "Cut Veridium Brick Stairs",
+ "block.create.cut_veridium_brick_wall": "Cut Veridium Brick Wall",
+ "block.create.cut_veridium_bricks": "Cut Veridium Bricks",
+ "block.create.cut_veridium_slab": "Cut Veridium Slab",
+ "block.create.cut_veridium_stairs": "Cut Veridium Stairs",
+ "block.create.cut_veridium_wall": "Cut Veridium Wall",
+ "block.create.cyan_nixie_tube": "Cyan Nixie Tube",
+ "block.create.cyan_postbox": "Cyan Postbox",
+ "block.create.cyan_sail": "Cyan Sail",
+ "block.create.cyan_seat": "Cyan Seat",
+ "block.create.cyan_table_cloth": "Cyan Table Cloth",
+ "block.create.cyan_toolbox": "Cyan Toolbox",
+ "block.create.cyan_valve_handle": "Cyan Valve Handle",
+ "block.create.dark_oak_window": "Dark Oak Window",
+ "block.create.dark_oak_window_pane": "Dark Oak Window Pane",
+ "block.create.deepslate_pillar": "Deepslate Pillar",
+ "block.create.deepslate_zinc_ore": "Deepslate Zinc Ore",
+ "block.create.deployer": "Deployer",
+ "block.create.depot": "Depot",
+ "block.create.desk_bell": "Desk Bell",
+ "block.create.desk_bell.tooltip.behaviour1": "_Plays a sound_ when arriving at the _target floor_.",
+ "block.create.desk_bell.tooltip.condition1": "When attached to Elevator Contraption",
+ "block.create.desk_bell.tooltip.summary": "Ring for Service! Emits a _redstone signal_ when activated.",
+ "block.create.diorite_pillar": "Diorite Pillar",
+ "block.create.display_board": "Display Board",
+ "block.create.display_link": "Display Link",
+ "block.create.dripstone_pillar": "Dripstone Pillar",
+ "block.create.elevator_contact": "Elevator Contact",
+ "block.create.elevator_pulley": "Elevator Pulley",
+ "block.create.encased_chain_drive": "Encased Chain Drive",
+ "block.create.encased_fan": "Encased Fan",
+ "block.create.encased_fluid_pipe": "Encased Fluid Pipe",
+ "block.create.experience_block": "Block of Experience",
+ "block.create.exposed_copper_shingle_slab": "Exposed Copper Shingle Slab",
+ "block.create.exposed_copper_shingle_stairs": "Exposed Copper Shingle Stairs",
+ "block.create.exposed_copper_shingles": "Exposed Copper Shingles",
+ "block.create.exposed_copper_tile_slab": "Exposed Copper Tile Slab",
+ "block.create.exposed_copper_tile_stairs": "Exposed Copper Tile Stairs",
+ "block.create.exposed_copper_tiles": "Exposed Copper Tiles",
+ "block.create.factory_gauge": "Factory Gauge",
+ "block.create.fake_track": "Track Marker for Maps",
+ "block.create.fluid_pipe": "Fluid Pipe",
+ "block.create.fluid_tank": "Fluid Tank",
+ "block.create.fluid_valve": "Fluid Valve",
+ "block.create.flywheel": "Flywheel",
+ "block.create.flywheel.tooltip.behaviour1": "Starts spinning.",
+ "block.create.flywheel.tooltip.condition1": "When Powered by Kinetics",
+ "block.create.flywheel.tooltip.summary": "_Embellish_ your _Machines_ with this imposing Wheel of Brass.",
+ "block.create.framed_glass": "Framed Glass",
+ "block.create.framed_glass_door": "Framed Glass Door",
+ "block.create.framed_glass_pane": "Framed Glass Pane",
+ "block.create.framed_glass_trapdoor": "Framed Glass Trapdoor",
+ "block.create.gantry_carriage": "Gantry Carriage",
+ "block.create.gantry_shaft": "Gantry Shaft",
+ "block.create.gearbox": "Gearbox",
+ "block.create.gearshift": "Gearshift",
+ "block.create.glass_fluid_pipe": "Glass Fluid Pipe",
+ "block.create.granite_pillar": "Granite Pillar",
+ "block.create.gray_nixie_tube": "Gray Nixie Tube",
+ "block.create.gray_postbox": "Gray Postbox",
+ "block.create.gray_sail": "Gray Sail",
+ "block.create.gray_seat": "Gray Seat",
+ "block.create.gray_table_cloth": "Gray Table Cloth",
+ "block.create.gray_toolbox": "Gray Toolbox",
+ "block.create.gray_valve_handle": "Gray Valve Handle",
+ "block.create.green_nixie_tube": "Green Nixie Tube",
+ "block.create.green_postbox": "Green Postbox",
+ "block.create.green_sail": "Green Sail",
+ "block.create.green_seat": "Green Seat",
+ "block.create.green_table_cloth": "Green Table Cloth",
+ "block.create.green_toolbox": "Green Toolbox",
+ "block.create.green_valve_handle": "Green Valve Handle",
+ "block.create.hand_crank": "Hand Crank",
+ "block.create.haunted_bell": "Haunted Bell",
+ "block.create.haunted_bell.tooltip.behaviour1": "Highlights nearby _Lightless Spots_ on which _Hostile Mobs_ can spawn.",
+ "block.create.haunted_bell.tooltip.condition1": "When Held or Rang",
+ "block.create.haunted_bell.tooltip.summary": "A _Cursed Bell_ haunted by lost souls of the Nether.",
+ "block.create.honey": "Honey",
+ "block.create.horizontal_framed_glass": "Horizontal Framed Glass",
+ "block.create.horizontal_framed_glass_pane": "Horizontal Framed Glass Pane",
+ "block.create.hose_pulley": "Hose Pulley",
+ "block.create.industrial_iron_block": "Block of Industrial Iron",
+ "block.create.industrial_iron_window": "Industrial Iron Window",
+ "block.create.industrial_iron_window_pane": "Industrial Iron Window Pane",
+ "block.create.item_drain": "Item Drain",
+ "block.create.item_hatch": "Item Hatch",
+ "block.create.item_hatch.tooltip.behaviour1": "_Deposits_ your _held item_ into the _container_ it is placed on. _Sneak-click_ to _deposit_ _everything_ except Hotbar items.",
+ "block.create.item_hatch.tooltip.condition1": "When R-Clicked",
+ "block.create.item_hatch.tooltip.summary": "_Quickly solves_ your _inventory_ clutter and makes it someone else's problem.",
+ "block.create.item_vault": "Item Vault",
+ "block.create.jungle_window": "Jungle Window",
+ "block.create.jungle_window_pane": "Jungle Window Pane",
+ "block.create.large_bogey": "Large Bogey",
+ "block.create.large_cogwheel": "Large Cogwheel",
+ "block.create.large_water_wheel": "Large Water Wheel",
+ "block.create.layered_andesite": "Layered Andesite",
+ "block.create.layered_asurine": "Layered Asurine",
+ "block.create.layered_calcite": "Layered Calcite",
+ "block.create.layered_crimsite": "Layered Crimsite",
+ "block.create.layered_deepslate": "Layered Deepslate",
+ "block.create.layered_diorite": "Layered Diorite",
+ "block.create.layered_dripstone": "Layered Dripstone",
+ "block.create.layered_granite": "Layered Granite",
+ "block.create.layered_limestone": "Layered Limestone",
+ "block.create.layered_ochrum": "Layered Ochrum",
+ "block.create.layered_scorchia": "Layered Scorchia",
+ "block.create.layered_scoria": "Layered Scoria",
+ "block.create.layered_tuff": "Layered Tuff",
+ "block.create.layered_veridium": "Layered Veridium",
+ "block.create.lectern_controller": "Lectern Controller",
+ "block.create.light_blue_nixie_tube": "Light Blue Nixie Tube",
+ "block.create.light_blue_postbox": "Light Blue Postbox",
+ "block.create.light_blue_sail": "Light Blue Sail",
+ "block.create.light_blue_seat": "Light Blue Seat",
+ "block.create.light_blue_table_cloth": "Light Blue Table Cloth",
+ "block.create.light_blue_toolbox": "Light Blue Toolbox",
+ "block.create.light_blue_valve_handle": "Light Blue Valve Handle",
+ "block.create.light_gray_nixie_tube": "Light Gray Nixie Tube",
+ "block.create.light_gray_postbox": "Light Gray Postbox",
+ "block.create.light_gray_sail": "Light Gray Sail",
+ "block.create.light_gray_seat": "Light Gray Seat",
+ "block.create.light_gray_table_cloth": "Light Gray Table Cloth",
+ "block.create.light_gray_toolbox": "Light Gray Toolbox",
+ "block.create.light_gray_valve_handle": "Light Gray Valve Handle",
+ "block.create.lime_nixie_tube": "Lime Nixie Tube",
+ "block.create.lime_postbox": "Lime Postbox",
+ "block.create.lime_sail": "Lime Sail",
+ "block.create.lime_seat": "Lime Seat",
+ "block.create.lime_table_cloth": "Lime Table Cloth",
+ "block.create.lime_toolbox": "Lime Toolbox",
+ "block.create.lime_valve_handle": "Lime Valve Handle",
+ "block.create.limestone": "Limestone",
+ "block.create.limestone_pillar": "Limestone Pillar",
+ "block.create.linear_chassis": "Linear Chassis",
+ "block.create.lit_blaze_burner": "Lit Blaze Burner",
+ "block.create.magenta_nixie_tube": "Magenta Nixie Tube",
+ "block.create.magenta_postbox": "Magenta Postbox",
+ "block.create.magenta_sail": "Magenta Sail",
+ "block.create.magenta_seat": "Magenta Seat",
+ "block.create.magenta_table_cloth": "Magenta Table Cloth",
+ "block.create.magenta_toolbox": "Magenta Toolbox",
+ "block.create.magenta_valve_handle": "Magenta Valve Handle",
+ "block.create.mangrove_window": "Mangrove Window",
+ "block.create.mangrove_window_pane": "Mangrove Window Pane",
+ "block.create.mechanical_arm": "Mechanical Arm",
+ "block.create.mechanical_bearing": "Mechanical Bearing",
+ "block.create.mechanical_crafter": "Mechanical Crafter",
+ "block.create.mechanical_drill": "Mechanical Drill",
+ "block.create.mechanical_harvester": "Mechanical Harvester",
+ "block.create.mechanical_mixer": "Mechanical Mixer",
+ "block.create.mechanical_piston": "Mechanical Piston",
+ "block.create.mechanical_piston_head": "Mechanical Piston Head",
+ "block.create.mechanical_plough": "Mechanical Plough",
+ "block.create.mechanical_press": "Mechanical Press",
+ "block.create.mechanical_pump": "Mechanical Pump",
+ "block.create.mechanical_roller": "Mechanical Roller",
+ "block.create.mechanical_saw": "Mechanical Saw",
+ "block.create.metal_bracket": "Metal Bracket",
+ "block.create.metal_bracket.tooltip.summary": "_Decorate_ your _Shafts, Cogwheels_ and _Pipes_ with an industrial and sturdy bit of reinforcement.",
+ "block.create.metal_girder": "Metal Girder",
+ "block.create.metal_girder_encased_shaft": "Metal Girder Encased Shaft",
+ "block.create.millstone": "Millstone",
+ "block.create.minecart_anchor": "Minecart Anchor",
+ "block.create.mysterious_cuckoo_clock": "Cuckoo Clock",
+ "block.create.netherite_backtank": "Netherite Backtank",
+ "block.create.nixie_tube": "Nixie Tube",
+ "block.create.nozzle": "Nozzle",
+ "block.create.nozzle.tooltip.summary": "Attach to the front of an _Encased Fan_ to distribute its effect on Entities in _all directions_.",
+ "block.create.oak_window": "Oak Window",
+ "block.create.oak_window_pane": "Oak Window Pane",
+ "block.create.ochrum": "Ochrum",
+ "block.create.ochrum_pillar": "Ochrum Pillar",
+ "block.create.orange_postbox": "Orange Postbox",
+ "block.create.orange_sail": "Orange Sail",
+ "block.create.orange_seat": "Orange Seat",
+ "block.create.orange_table_cloth": "Orange Table Cloth",
+ "block.create.orange_toolbox": "Orange Toolbox",
+ "block.create.orange_valve_handle": "Orange Valve Handle",
+ "block.create.ornate_iron_window": "Ornate Iron Window",
+ "block.create.ornate_iron_window_pane": "Ornate Iron Window Pane",
+ "block.create.oxidized_copper_shingle_slab": "Oxidized Copper Shingle Slab",
+ "block.create.oxidized_copper_shingle_stairs": "Oxidized Copper Shingle Stairs",
+ "block.create.oxidized_copper_shingles": "Oxidized Copper Shingles",
+ "block.create.oxidized_copper_tile_slab": "Oxidized Copper Tile Slab",
+ "block.create.oxidized_copper_tile_stairs": "Oxidized Copper Tile Stairs",
+ "block.create.oxidized_copper_tiles": "Oxidized Copper Tiles",
+ "block.create.package_frogport": "Package Frogport",
+ "block.create.packager": "Packager",
+ "block.create.peculiar_bell": "Peculiar Bell",
+ "block.create.peculiar_bell.tooltip.summary": "A decorative _Brass Bell_. Placing it right above open _Soul Fire_ may have side-effects...",
+ "block.create.pink_nixie_tube": "Pink Nixie Tube",
+ "block.create.pink_postbox": "Pink Postbox",
+ "block.create.pink_sail": "Pink Sail",
+ "block.create.pink_seat": "Pink Seat",
+ "block.create.pink_table_cloth": "Pink Table Cloth",
+ "block.create.pink_toolbox": "Pink Toolbox",
+ "block.create.pink_valve_handle": "Pink Valve Handle",
+ "block.create.piston_extension_pole": "Piston Extension Pole",
+ "block.create.placard": "Placard",
+ "block.create.placard.tooltip.behaviour1": "_Adds_ the held _item_ to the Placard. _Emits_ a brief _Redstone_ signal if a matching item was present already.",
+ "block.create.placard.tooltip.behaviour2": "_Removes_ the current _item_ in the frame.",
+ "block.create.placard.tooltip.condition1": "When R-Clicked with Item",
+ "block.create.placard.tooltip.condition2": "When Punched",
+ "block.create.placard.tooltip.summary": "_Frame_ your _items_ in brass using this fancy wall panel. Safe for contraptions!",
+ "block.create.polished_cut_andesite": "Polished Cut Andesite",
+ "block.create.polished_cut_andesite_slab": "Polished Cut Andesite Slab",
+ "block.create.polished_cut_andesite_stairs": "Polished Cut Andesite Stairs",
+ "block.create.polished_cut_andesite_wall": "Polished Cut Andesite Wall",
+ "block.create.polished_cut_asurine": "Polished Cut Asurine",
+ "block.create.polished_cut_asurine_slab": "Polished Cut Asurine Slab",
+ "block.create.polished_cut_asurine_stairs": "Polished Cut Asurine Stairs",
+ "block.create.polished_cut_asurine_wall": "Polished Cut Asurine Wall",
+ "block.create.polished_cut_calcite": "Polished Cut Calcite",
+ "block.create.polished_cut_calcite_slab": "Polished Cut Calcite Slab",
+ "block.create.polished_cut_calcite_stairs": "Polished Cut Calcite Stairs",
+ "block.create.polished_cut_calcite_wall": "Polished Cut Calcite Wall",
+ "block.create.polished_cut_crimsite": "Polished Cut Crimsite",
+ "block.create.polished_cut_crimsite_slab": "Polished Cut Crimsite Slab",
+ "block.create.polished_cut_crimsite_stairs": "Polished Cut Crimsite Stairs",
+ "block.create.polished_cut_crimsite_wall": "Polished Cut Crimsite Wall",
+ "block.create.polished_cut_deepslate": "Polished Cut Deepslate",
+ "block.create.polished_cut_deepslate_slab": "Polished Cut Deepslate Slab",
+ "block.create.polished_cut_deepslate_stairs": "Polished Cut Deepslate Stairs",
+ "block.create.polished_cut_deepslate_wall": "Polished Cut Deepslate Wall",
+ "block.create.polished_cut_diorite": "Polished Cut Diorite",
+ "block.create.polished_cut_diorite_slab": "Polished Cut Diorite Slab",
+ "block.create.polished_cut_diorite_stairs": "Polished Cut Diorite Stairs",
+ "block.create.polished_cut_diorite_wall": "Polished Cut Diorite Wall",
+ "block.create.polished_cut_dripstone": "Polished Cut Dripstone",
+ "block.create.polished_cut_dripstone_slab": "Polished Cut Dripstone Slab",
+ "block.create.polished_cut_dripstone_stairs": "Polished Cut Dripstone Stairs",
+ "block.create.polished_cut_dripstone_wall": "Polished Cut Dripstone Wall",
+ "block.create.polished_cut_granite": "Polished Cut Granite",
+ "block.create.polished_cut_granite_slab": "Polished Cut Granite Slab",
+ "block.create.polished_cut_granite_stairs": "Polished Cut Granite Stairs",
+ "block.create.polished_cut_granite_wall": "Polished Cut Granite Wall",
+ "block.create.polished_cut_limestone": "Polished Cut Limestone",
+ "block.create.polished_cut_limestone_slab": "Polished Cut Limestone Slab",
+ "block.create.polished_cut_limestone_stairs": "Polished Cut Limestone Stairs",
+ "block.create.polished_cut_limestone_wall": "Polished Cut Limestone Wall",
+ "block.create.polished_cut_ochrum": "Polished Cut Ochrum",
+ "block.create.polished_cut_ochrum_slab": "Polished Cut Ochrum Slab",
+ "block.create.polished_cut_ochrum_stairs": "Polished Cut Ochrum Stairs",
+ "block.create.polished_cut_ochrum_wall": "Polished Cut Ochrum Wall",
+ "block.create.polished_cut_scorchia": "Polished Cut Scorchia",
+ "block.create.polished_cut_scorchia_slab": "Polished Cut Scorchia Slab",
+ "block.create.polished_cut_scorchia_stairs": "Polished Cut Scorchia Stairs",
+ "block.create.polished_cut_scorchia_wall": "Polished Cut Scorchia Wall",
+ "block.create.polished_cut_scoria": "Polished Cut Scoria",
+ "block.create.polished_cut_scoria_slab": "Polished Cut Scoria Slab",
+ "block.create.polished_cut_scoria_stairs": "Polished Cut Scoria Stairs",
+ "block.create.polished_cut_scoria_wall": "Polished Cut Scoria Wall",
+ "block.create.polished_cut_tuff": "Polished Cut Tuff",
+ "block.create.polished_cut_tuff_slab": "Polished Cut Tuff Slab",
+ "block.create.polished_cut_tuff_stairs": "Polished Cut Tuff Stairs",
+ "block.create.polished_cut_tuff_wall": "Polished Cut Tuff Wall",
+ "block.create.polished_cut_veridium": "Polished Cut Veridium",
+ "block.create.polished_cut_veridium_slab": "Polished Cut Veridium Slab",
+ "block.create.polished_cut_veridium_stairs": "Polished Cut Veridium Stairs",
+ "block.create.polished_cut_veridium_wall": "Polished Cut Veridium Wall",
+ "block.create.portable_fluid_interface": "Portable Fluid Interface",
+ "block.create.portable_storage_interface": "Portable Storage Interface",
+ "block.create.powered_latch": "Powered Latch",
+ "block.create.powered_shaft": "Powered Shaft",
+ "block.create.powered_toggle_latch": "Powered Toggle Latch",
+ "block.create.pulley_magnet": "Pulley Magnet",
+ "block.create.pulse_extender": "Pulse Extender",
+ "block.create.pulse_repeater": "Pulse Repeater",
+ "block.create.pulse_timer": "Pulse Timer",
+ "block.create.purple_nixie_tube": "Purple Nixie Tube",
+ "block.create.purple_postbox": "Purple Postbox",
+ "block.create.purple_sail": "Purple Sail",
+ "block.create.purple_seat": "Purple Seat",
+ "block.create.purple_table_cloth": "Purple Table Cloth",
+ "block.create.purple_toolbox": "Purple Toolbox",
+ "block.create.purple_valve_handle": "Purple Valve Handle",
+ "block.create.radial_chassis": "Radial Chassis",
+ "block.create.railway_casing": "Train Casing",
+ "block.create.raw_zinc_block": "Block of Raw Zinc",
+ "block.create.red_nixie_tube": "Red Nixie Tube",
+ "block.create.red_postbox": "Red Postbox",
+ "block.create.red_sail": "Red Sail",
+ "block.create.red_seat": "Red Seat",
+ "block.create.red_table_cloth": "Red Table Cloth",
+ "block.create.red_toolbox": "Red Toolbox",
+ "block.create.red_valve_handle": "Red Valve Handle",
+ "block.create.redstone_contact": "Redstone Contact",
+ "block.create.redstone_link": "Redstone Link",
+ "block.create.redstone_requester": "Redstone Requester",
+ "block.create.refined_radiance_casing": "Radiant Casing",
+ "block.create.repackager": "Re-Packager",
+ "block.create.rope": "Rope",
+ "block.create.rope_pulley": "Rope Pulley",
+ "block.create.rose_quartz_block": "Block of Rose Quartz",
+ "block.create.rose_quartz_lamp": "Rose Quartz Lamp",
+ "block.create.rose_quartz_tiles": "Rose Quartz Tiles",
+ "block.create.rotation_speed_controller": "Rotation Speed Controller",
+ "block.create.sail_frame": "Windmill Sail Frame",
+ "block.create.schematic_table": "Schematic Table",
+ "block.create.schematic_table.tooltip.behaviour1": "Uploads a chosen File from your Schematics Folder.",
+ "block.create.schematic_table.tooltip.condition1": "When given an Empty Schematic",
+ "block.create.schematic_table.tooltip.summary": "Writes saved Schematics onto an _Empty Schematic_.",
+ "block.create.schematicannon": "Schematicannon",
+ "block.create.schematicannon.tooltip.behaviour1": "Opens the _Interface_",
+ "block.create.schematicannon.tooltip.condition1": "When R-Clicked",
+ "block.create.schematicannon.tooltip.summary": "Shoots blocks to recreate a deployed _Schematic_ in the World. Uses items from adjacent Inventories and _Gunpowder_ as fuel.",
+ "block.create.scorchia": "Scorchia",
+ "block.create.scorchia_pillar": "Scorchia Pillar",
+ "block.create.scoria": "Scoria",
+ "block.create.scoria_pillar": "Scoria Pillar",
+ "block.create.seat.tooltip.behaviour1": "Sits the player on the _Seat_. Press L-shift to leave the _Seat_.",
+ "block.create.seat.tooltip.condition1": "Right click on Seat",
+ "block.create.seat.tooltip.summary": "Sit yourself down and enjoy the ride! Will anchor a player onto a moving _contraption_. Great for static furniture too! Comes in a variety of colours.",
+ "block.create.secondary_linear_chassis": "Secondary Linear Chassis",
+ "block.create.sequenced_gearshift": "Sequenced Gearshift",
+ "block.create.shadow_steel_casing": "Shadow Casing",
+ "block.create.shaft": "Shaft",
+ "block.create.small_andesite_brick_slab": "Small Andesite Brick Slab",
+ "block.create.small_andesite_brick_stairs": "Small Andesite Brick Stairs",
+ "block.create.small_andesite_brick_wall": "Small Andesite Brick Wall",
+ "block.create.small_andesite_bricks": "Small Andesite Bricks",
+ "block.create.small_asurine_brick_slab": "Small Asurine Brick Slab",
+ "block.create.small_asurine_brick_stairs": "Small Asurine Brick Stairs",
+ "block.create.small_asurine_brick_wall": "Small Asurine Brick Wall",
+ "block.create.small_asurine_bricks": "Small Asurine Bricks",
+ "block.create.small_bogey": "Small Bogey",
+ "block.create.small_calcite_brick_slab": "Small Calcite Brick Slab",
+ "block.create.small_calcite_brick_stairs": "Small Calcite Brick Stairs",
+ "block.create.small_calcite_brick_wall": "Small Calcite Brick Wall",
+ "block.create.small_calcite_bricks": "Small Calcite Bricks",
+ "block.create.small_crimsite_brick_slab": "Small Crimsite Brick Slab",
+ "block.create.small_crimsite_brick_stairs": "Small Crimsite Brick Stairs",
+ "block.create.small_crimsite_brick_wall": "Small Crimsite Brick Wall",
+ "block.create.small_crimsite_bricks": "Small Crimsite Bricks",
+ "block.create.small_deepslate_brick_slab": "Small Deepslate Brick Slab",
+ "block.create.small_deepslate_brick_stairs": "Small Deepslate Brick Stairs",
+ "block.create.small_deepslate_brick_wall": "Small Deepslate Brick Wall",
+ "block.create.small_deepslate_bricks": "Small Deepslate Bricks",
+ "block.create.small_diorite_brick_slab": "Small Diorite Brick Slab",
+ "block.create.small_diorite_brick_stairs": "Small Diorite Brick Stairs",
+ "block.create.small_diorite_brick_wall": "Small Diorite Brick Wall",
+ "block.create.small_diorite_bricks": "Small Diorite Bricks",
+ "block.create.small_dripstone_brick_slab": "Small Dripstone Brick Slab",
+ "block.create.small_dripstone_brick_stairs": "Small Dripstone Brick Stairs",
+ "block.create.small_dripstone_brick_wall": "Small Dripstone Brick Wall",
+ "block.create.small_dripstone_bricks": "Small Dripstone Bricks",
+ "block.create.small_granite_brick_slab": "Small Granite Brick Slab",
+ "block.create.small_granite_brick_stairs": "Small Granite Brick Stairs",
+ "block.create.small_granite_brick_wall": "Small Granite Brick Wall",
+ "block.create.small_granite_bricks": "Small Granite Bricks",
+ "block.create.small_limestone_brick_slab": "Small Limestone Brick Slab",
+ "block.create.small_limestone_brick_stairs": "Small Limestone Brick Stairs",
+ "block.create.small_limestone_brick_wall": "Small Limestone Brick Wall",
+ "block.create.small_limestone_bricks": "Small Limestone Bricks",
+ "block.create.small_ochrum_brick_slab": "Small Ochrum Brick Slab",
+ "block.create.small_ochrum_brick_stairs": "Small Ochrum Brick Stairs",
+ "block.create.small_ochrum_brick_wall": "Small Ochrum Brick Wall",
+ "block.create.small_ochrum_bricks": "Small Ochrum Bricks",
+ "block.create.small_rose_quartz_tiles": "Small Rose Quartz Tiles",
+ "block.create.small_scorchia_brick_slab": "Small Scorchia Brick Slab",
+ "block.create.small_scorchia_brick_stairs": "Small Scorchia Brick Stairs",
+ "block.create.small_scorchia_brick_wall": "Small Scorchia Brick Wall",
+ "block.create.small_scorchia_bricks": "Small Scorchia Bricks",
+ "block.create.small_scoria_brick_slab": "Small Scoria Brick Slab",
+ "block.create.small_scoria_brick_stairs": "Small Scoria Brick Stairs",
+ "block.create.small_scoria_brick_wall": "Small Scoria Brick Wall",
+ "block.create.small_scoria_bricks": "Small Scoria Bricks",
+ "block.create.small_tuff_brick_slab": "Small Tuff Brick Slab",
+ "block.create.small_tuff_brick_stairs": "Small Tuff Brick Stairs",
+ "block.create.small_tuff_brick_wall": "Small Tuff Brick Wall",
+ "block.create.small_tuff_bricks": "Small Tuff Bricks",
+ "block.create.small_veridium_brick_slab": "Small Veridium Brick Slab",
+ "block.create.small_veridium_brick_stairs": "Small Veridium Brick Stairs",
+ "block.create.small_veridium_brick_wall": "Small Veridium Brick Wall",
+ "block.create.small_veridium_bricks": "Small Veridium Bricks",
+ "block.create.smart_chute": "Smart Chute",
+ "block.create.smart_fluid_pipe": "Smart Fluid Pipe",
+ "block.create.speedometer": "Speedometer",
+ "block.create.spout": "Spout",
+ "block.create.spruce_window": "Spruce Window",
+ "block.create.spruce_window_pane": "Spruce Window Pane",
+ "block.create.steam_engine": "Steam Engine",
+ "block.create.steam_whistle": "Steam Whistle",
+ "block.create.steam_whistle_extension": "Steam Whistle Extension",
+ "block.create.sticker": "Sticker",
+ "block.create.sticky_mechanical_piston": "Sticky Mechanical Piston",
+ "block.create.stock_link": "Stock Link",
+ "block.create.stock_ticker": "Stock Ticker",
+ "block.create.stockpile_switch": "Threshold Switch",
+ "block.create.stressometer": "Stressometer",
+ "block.create.tiled_glass": "Tiled Glass",
+ "block.create.tiled_glass_pane": "Tiled Glass Pane",
+ "block.create.toolbox.tooltip.behaviour1": "_Retains_ Inventory _Contents_.",
+ "block.create.toolbox.tooltip.behaviour2": "_Nearby_ _Players_ can hold the _Toolbox_ _Keybind_ to access its contents _Remotely_.",
+ "block.create.toolbox.tooltip.behaviour3": "Opens the _Container Interface_.",
+ "block.create.toolbox.tooltip.condition1": "When Picked Up",
+ "block.create.toolbox.tooltip.condition2": "When placed in Range",
+ "block.create.toolbox.tooltip.condition3": "When R-Clicked",
+ "block.create.toolbox.tooltip.summary": "Every Inventors' dearest Companion. Conveniently _holds_ a large amount of _8 Different_ item types.",
+ "block.create.track": "Train Track",
+ "block.create.track_observer": "Train Observer",
+ "block.create.track_signal": "Train Signal",
+ "block.create.track_station": "Train Station",
+ "block.create.train_door": "Train Door",
+ "block.create.train_trapdoor": "Train Trapdoor",
+ "block.create.tuff_pillar": "Tuff Pillar",
+ "block.create.turntable": "Turntable",
+ "block.create.turntable.tooltip.summary": "Uses _Rotational Force_ to create Motion Sickness.",
+ "block.create.veridium": "Veridium",
+ "block.create.veridium_pillar": "Veridium Pillar",
+ "block.create.vertical_framed_glass": "Vertical Framed Glass",
+ "block.create.vertical_framed_glass_pane": "Vertical Framed Glass Pane",
+ "block.create.warped_window": "Warped Window",
+ "block.create.warped_window_pane": "Warped Window Pane",
+ "block.create.water_wheel": "Water Wheel",
+ "block.create.water_wheel_structure": "Large Water Wheel",
+ "block.create.waxed_copper_shingle_slab": "Waxed Copper Shingle Slab",
+ "block.create.waxed_copper_shingle_stairs": "Waxed Copper Shingle Stairs",
+ "block.create.waxed_copper_shingles": "Waxed Copper Shingles",
+ "block.create.waxed_copper_tile_slab": "Waxed Copper Tile Slab",
+ "block.create.waxed_copper_tile_stairs": "Waxed Copper Tile Stairs",
+ "block.create.waxed_copper_tiles": "Waxed Copper Tiles",
+ "block.create.waxed_exposed_copper_shingle_slab": "Waxed Exposed Copper Shingle Slab",
+ "block.create.waxed_exposed_copper_shingle_stairs": "Waxed Exposed Copper Shingle Stairs",
+ "block.create.waxed_exposed_copper_shingles": "Waxed Exposed Copper Shingles",
+ "block.create.waxed_exposed_copper_tile_slab": "Waxed Exposed Copper Tile Slab",
+ "block.create.waxed_exposed_copper_tile_stairs": "Waxed Exposed Copper Tile Stairs",
+ "block.create.waxed_exposed_copper_tiles": "Waxed Exposed Copper Tiles",
+ "block.create.waxed_oxidized_copper_shingle_slab": "Waxed Oxidized Copper Shingle Slab",
+ "block.create.waxed_oxidized_copper_shingle_stairs": "Waxed Oxidized Copper Shingle Stairs",
+ "block.create.waxed_oxidized_copper_shingles": "Waxed Oxidized Copper Shingles",
+ "block.create.waxed_oxidized_copper_tile_slab": "Waxed Oxidized Copper Tile Slab",
+ "block.create.waxed_oxidized_copper_tile_stairs": "Waxed Oxidized Copper Tile Stairs",
+ "block.create.waxed_oxidized_copper_tiles": "Waxed Oxidized Copper Tiles",
+ "block.create.waxed_weathered_copper_shingle_slab": "Waxed Weathered Copper Shingle Slab",
+ "block.create.waxed_weathered_copper_shingle_stairs": "Waxed Weathered Copper Shingle Stairs",
+ "block.create.waxed_weathered_copper_shingles": "Waxed Weathered Copper Shingles",
+ "block.create.waxed_weathered_copper_tile_slab": "Waxed Weathered Copper Tile Slab",
+ "block.create.waxed_weathered_copper_tile_stairs": "Waxed Weathered Copper Tile Stairs",
+ "block.create.waxed_weathered_copper_tiles": "Waxed Weathered Copper Tiles",
+ "block.create.weathered_copper_shingle_slab": "Weathered Copper Shingle Slab",
+ "block.create.weathered_copper_shingle_stairs": "Weathered Copper Shingle Stairs",
+ "block.create.weathered_copper_shingles": "Weathered Copper Shingles",
+ "block.create.weathered_copper_tile_slab": "Weathered Copper Tile Slab",
+ "block.create.weathered_copper_tile_stairs": "Weathered Copper Tile Stairs",
+ "block.create.weathered_copper_tiles": "Weathered Copper Tiles",
+ "block.create.weathered_iron_block": "Block of Weathered Iron",
+ "block.create.weathered_iron_window": "Weathered Iron Window",
+ "block.create.weathered_iron_window_pane": "Weathered Iron Window Pane",
+ "block.create.weighted_ejector": "Weighted Ejector",
+ "block.create.white_nixie_tube": "White Nixie Tube",
+ "block.create.white_postbox": "White Postbox",
+ "block.create.white_sail": "Windmill Sail",
+ "block.create.white_seat": "White Seat",
+ "block.create.white_table_cloth": "White Table Cloth",
+ "block.create.white_toolbox": "White Toolbox",
+ "block.create.white_valve_handle": "White Valve Handle",
+ "block.create.windmill_bearing": "Windmill Bearing",
+ "block.create.wooden_bracket": "Wooden Bracket",
+ "block.create.wooden_bracket.tooltip.summary": "_Decorate_ your _Shafts, Cogwheels_ and _Pipes_ with a cozy and wooden bit of reinforcement.",
+ "block.create.yellow_nixie_tube": "Yellow Nixie Tube",
+ "block.create.yellow_postbox": "Yellow Postbox",
+ "block.create.yellow_sail": "Yellow Sail",
+ "block.create.yellow_seat": "Yellow Seat",
+ "block.create.yellow_table_cloth": "Yellow Table Cloth",
+ "block.create.yellow_toolbox": "Yellow Toolbox",
+ "block.create.yellow_valve_handle": "Yellow Valve Handle",
+ "block.create.zinc_block": "Block of Zinc",
+ "block.create.zinc_ore": "Zinc Ore",
+ "create.action.abort": "Abort",
+ "create.action.confirm": "Confirm",
+ "create.action.discard": "Discard",
+ "create.action.saveToFile": "Save",
+ "create.action.scroll": "Scroll",
+ "create.backtank.depleted": "Backtank pressure depleted",
+ "create.backtank.low": "Backtank pressure low",
+ "create.block.cart_assembler.invalid": "Place your Cart Assembler on a rail block",
+ "create.block.deployer.damage_source_name": "a rogue Deployer",
+ "create.bogey.style.invalid": "Unnamed style",
+ "create.bogey.style.no_other_sizes": "No other sizes",
+ "create.bogey.style.standard": "Standard",
+ "create.bogey.style.updated_style": "Updated style",
+ "create.bogey.style.updated_style_and_size": "Updated style and size",
+ "create.boiler.heat": "Heat",
+ "create.boiler.heat_dots": "...... ",
+ "create.boiler.idle": "Idle",
+ "create.boiler.lvl": "Lvl %1$s",
+ "create.boiler.max_lvl": "Max",
+ "create.boiler.passive": "Passive",
+ "create.boiler.per_tick": "%1$s per Tick",
+ "create.boiler.size": "Size",
+ "create.boiler.size_dots": "....... ",
+ "create.boiler.status": "Boiler Status: %1$s",
+ "create.boiler.status_short": "Boiler: %1$s",
+ "create.boiler.via_engines": "via %1$s engines",
+ "create.boiler.via_one_engine": "via 1 engine",
+ "create.boiler.water": "Water",
+ "create.boiler.water_dots": "... ",
+ "create.boiler.water_input_rate": "Water input rate",
+ "create.chain_conveyor.already_connected": "Conveyors are already connected",
+ "create.chain_conveyor.blocks_invalid": "Conveyor blocks invalid, Sneak-click to reset",
+ "create.chain_conveyor.cannot_add_more_connections": "Cannot add more connections to this Conveyor",
+ "create.chain_conveyor.cannot_connect_vertically": "Cannot connect vertically",
+ "create.chain_conveyor.not_enough_chains": "Not holding enough chains",
+ "create.chain_conveyor.select_second": "Select a second conveyor to connect",
+ "create.chain_conveyor.selection_cleared": "Selection Cleared",
+ "create.chain_conveyor.too_close": "Too close together",
+ "create.chain_conveyor.too_far": "Too far apart",
+ "create.chain_conveyor.too_steep": "Slope too steep",
+ "create.chain_conveyor.valid_connection": "Can Connect ✔",
+ "create.clipboard.actions": "Clipboard Actions",
+ "create.clipboard.address_added": "'%1$s' added to Clipboard",
+ "create.clipboard.copied_from": "Copying settings from %1$s",
+ "create.clipboard.copied_from_clipboard": "Added entries from Clipboard",
+ "create.clipboard.copy_other_clipboard": "%1$s to Copy entries from this Clipboard",
+ "create.clipboard.pasted_to": "Applied settings to %1$s",
+ "create.clipboard.to_copy": "%1$s to Copy settings",
+ "create.clipboard.to_paste": "%1$s to Paste settings",
+ "create.command.debuginfo.saved_to_clipboard": "Debug information has been copied to your clipboard.",
+ "create.command.debuginfo.sending": "Collecting debug information...",
+ "create.command.killTPSCommand": "killtps",
+ "create.command.killTPSCommand.argument.tickTime": "tickTime",
+ "create.command.killTPSCommand.status.slowed_by.0": "[Create]: Server tick is currently slowed by %s ms :o",
+ "create.command.killTPSCommand.status.slowed_by.1": "[Create]: Server tick is slowed by %s ms now >:)",
+ "create.command.killTPSCommand.status.slowed_by.2": "[Create]: Server tick is back to regular speed :D",
+ "create.command.killTPSCommand.status.usage.0": "[Create]: use /killtps stop to bring back server tick to regular speed",
+ "create.command.killTPSCommand.status.usage.1": "[Create]: use /killtps start to artificially slow down the server tick",
+ "create.contraption.controls.actor_toggle.off": "Off",
+ "create.contraption.controls.actor_toggle.on": "On",
+ "create.contraption.controls.all_actor_toggle": "All Actors: %1$s",
+ "create.contraption.controls.approach_station": "Hold %1$s to approach %2$s",
+ "create.contraption.controls.floor_unreachable": "Unreachable",
+ "create.contraption.controls.specific_actor_toggle": "%1$s Actors: %2$s",
+ "create.contraption.controls.start_controlling": "Now controlling: %1$s",
+ "create.contraption.controls.stop_controlling": "Stopped controlling contraption",
+ "create.contraption.door_control": "Onboard Door Control",
+ "create.contraption.door_control.all": "Open All Doors",
+ "create.contraption.door_control.all.short": "Open All",
+ "create.contraption.door_control.east": "East Side Only",
+ "create.contraption.door_control.east.short": "East",
+ "create.contraption.door_control.none": "Keep Doors Closed",
+ "create.contraption.door_control.none.short": "None",
+ "create.contraption.door_control.north": "North Side Only",
+ "create.contraption.door_control.north.short": "North",
+ "create.contraption.door_control.player_facing": "You are facing: %1$s",
+ "create.contraption.door_control.south": "South Side Only",
+ "create.contraption.door_control.south.short": "South",
+ "create.contraption.door_control.west": "West Side Only",
+ "create.contraption.door_control.west.short": "West",
+ "create.contraption.minecart_contraption_illegal_pickup": "A mystical force is binding this Cart Contraption to the world",
+ "create.contraption.minecart_contraption_too_big": "This Cart Contraption seems too big to pick up",
+ "create.contraptions.cart_movement_mode": "Cart Movement Mode",
+ "create.contraptions.cart_movement_mode.rotate": "Always face toward motion",
+ "create.contraptions.cart_movement_mode.rotate_paused": "Pause actors while rotating",
+ "create.contraptions.cart_movement_mode.rotation_locked": "Lock rotation",
+ "create.contraptions.chassis.distance": "Distance",
+ "create.contraptions.chassis.radius": "Radius when Sticky",
+ "create.contraptions.chassis.range": "Range of Sticky Sides",
+ "create.contraptions.clockwork.clock_hands": "Clock Hand Arrangement",
+ "create.contraptions.clockwork.hour_first": "Hour hand first",
+ "create.contraptions.clockwork.hour_first_24": "24-Hour hand first",
+ "create.contraptions.clockwork.minute_first": "Minute hand first",
+ "create.contraptions.contoller.target": "Targeted Component",
+ "create.contraptions.mechanical_roller.pave_material": "Paving Material",
+ "create.contraptions.movement_mode": "Movement Mode",
+ "create.contraptions.movement_mode.move_never_place": "Place only when Anchor Destroyed",
+ "create.contraptions.movement_mode.move_place": "Always Place when Stopped",
+ "create.contraptions.movement_mode.move_place_returned": "Place only in Starting Position",
+ "create.contraptions.movement_mode.rotate_never_place": "Only Place when Anchor Destroyed",
+ "create.contraptions.movement_mode.rotate_place": "Always Place when Stopped",
+ "create.contraptions.movement_mode.rotate_place_returned": "Only Place near Initial Angle",
+ "create.contraptions.moving_container": "Moving %1$s",
+ "create.contraptions.roller_mode": "Roller Mode",
+ "create.contraptions.roller_mode.straight_fill": "Straight Fill Below",
+ "create.contraptions.roller_mode.tunnel_pave": "Clear Blocks and Pave",
+ "create.contraptions.roller_mode.wide_fill": "Sloped Fill Below",
+ "create.contraptions.windmill.rotation_direction": "Rotation Direction",
+ "create.crafting_blueprint.crafting_slot": "Ingredient Slot",
+ "create.crafting_blueprint.display_slot": "Display Slot",
+ "create.crafting_blueprint.filter_items_viable": "Advanced filter items are viable",
+ "create.crafting_blueprint.inferred": "Inferred from recipe",
+ "create.crafting_blueprint.manually_assigned": "Manually assigned",
+ "create.crafting_blueprint.optional": "Optional",
+ "create.crafting_blueprint.secondary_display_slot": "Secondary Display Slot",
+ "create.display_link.attached_side": "Block on attached side",
+ "create.display_link.clear": "Cleared position selection",
+ "create.display_link.display_on": "Write data to:",
+ "create.display_link.display_on_multiline": "Start writing at:",
+ "create.display_link.information_type": "Type of Information",
+ "create.display_link.invalid": "Link has no valid target, try placing it again",
+ "create.display_link.no_source": "Not a Display Source",
+ "create.display_link.no_target": "Not a Display Target",
+ "create.display_link.reading_from": "Read from:",
+ "create.display_link.set": "Targeted position selected",
+ "create.display_link.success": "Successfully bound to targeted position",
+ "create.display_link.targeted_location": "Block in targeted location",
+ "create.display_link.title": "Display Link",
+ "create.display_link.too_far": "Targeted position is too far from here",
+ "create.display_link.view_compatible": "Click to view all Compatible",
+ "create.display_link.writing_to": "Send to:",
+ "create.display_source.accumulate_items": "Accumulate Item Count",
+ "create.display_source.boiler.for_boiler_status": "for Boiler Status",
+ "create.display_source.boiler.not_enough_space": "Not enough space ",
+ "create.display_source.boiler_status": "Boiler Status",
+ "create.display_source.combine_item_names": "Combine Item Names",
+ "create.display_source.computer": "From Computer",
+ "create.display_source.count_items": "Amount of matching Items",
+ "create.display_source.current_floor": "Elevator Location",
+ "create.display_source.entity_name": "Entity Name",
+ "create.display_source.fill_level": "Container Fill Level",
+ "create.display_source.fill_level.display": "Display Format",
+ "create.display_source.fill_level.percent": "Percent",
+ "create.display_source.fill_level.progress_bar": "Progress Bar",
+ "create.display_source.fluid_amount": "Amount of matching Fluids",
+ "create.display_source.gauge_status": "Factory gauge status",
+ "create.display_source.item_throughput": "Item Throughput",
+ "create.display_source.item_throughput.interval": "Interval",
+ "create.display_source.item_throughput.interval.hour": "per Hour",
+ "create.display_source.item_throughput.interval.minute": "per Minute",
+ "create.display_source.item_throughput.interval.second": "per Second",
+ "create.display_source.kinetic_speed": "Rotation Speed (RPM)",
+ "create.display_source.kinetic_speed.absolute": "Ignore Direction",
+ "create.display_source.kinetic_speed.directional": "Include Direction",
+ "create.display_source.kinetic_stress": "Network Stress",
+ "create.display_source.kinetic_stress.current": "Stress in SU",
+ "create.display_source.kinetic_stress.display": "Displayed Info",
+ "create.display_source.kinetic_stress.max": "Total Capacity",
+ "create.display_source.kinetic_stress.percent": "Percentage",
+ "create.display_source.kinetic_stress.progress_bar": "Progress Bar",
+ "create.display_source.kinetic_stress.remaining": "Remaining SU",
+ "create.display_source.label": "Attached Label",
+ "create.display_source.list_fluids": "List matching Fluids",
+ "create.display_source.list_items": "List matching Items",
+ "create.display_source.max_enchant_level": "Max Enchanting Cost",
+ "create.display_source.nixie_tube": "Copy Nixie Tubes",
+ "create.display_source.observed_train_name": "Detected Train Name",
+ "create.display_source.player_deaths": "Player Deaths",
+ "create.display_source.read_package_address": "Read Package Address",
+ "create.display_source.redstone_power": "Redstone Power",
+ "create.display_source.redstone_power.display": "Display Format",
+ "create.display_source.redstone_power.number": "Number",
+ "create.display_source.redstone_power.progress_bar": "Progress Bar",
+ "create.display_source.scoreboard": "Scoreboard",
+ "create.display_source.scoreboard.objective": "Objective ID",
+ "create.display_source.scoreboard.objective.deaths": "Player Deaths",
+ "create.display_source.scoreboard.objective_not_found": "'%1$s' not found",
+ "create.display_source.station_summary": "Train Station Summary",
+ "create.display_source.station_summary.filter": "Station name filter",
+ "create.display_source.station_summary.minutes": " min",
+ "create.display_source.station_summary.now": "now",
+ "create.display_source.station_summary.platform_column": "Platform column size",
+ "create.display_source.station_summary.seconds": "%1$ss",
+ "create.display_source.station_summary.train_name_column": "Train column size",
+ "create.display_source.stop_watch": "Stopwatch",
+ "create.display_source.time.12_hour": "12-hour",
+ "create.display_source.time.24_hour": "24-hour",
+ "create.display_source.time.format": "Time Format",
+ "create.display_source.time_of_day": "Time of Day",
+ "create.display_source.train_status": "Train Schedule Status",
+ "create.display_source.value_list.display": "Value Display",
+ "create.display_source.value_list.full_number": "Full Number",
+ "create.display_source.value_list.million": "m",
+ "create.display_source.value_list.shortened": "Shortened",
+ "create.display_source.value_list.thousand": "k",
+ "create.display_target.line": "Line %1$s",
+ "create.display_target.page": "Page %1$s",
+ "create.display_target.single_line": "Single Line",
+ "create.elevator_contact.floor_description": "Floor Description",
+ "create.elevator_contact.floor_identifier": "Floor Identifier",
+ "create.elevator_contact.title": "Elevator Contact",
+ "create.factory_panel.already_connected": "Gauges are already connected",
+ "create.factory_panel.cannot_add_more_inputs": "Cannot add more inputs to this gauge",
+ "create.factory_panel.click_second_panel": "Click a second gauge to connect...",
+ "create.factory_panel.click_to_configure": "Click to configure",
+ "create.factory_panel.click_to_relocate": "Click a spot to relocate this panel to...",
+ "create.factory_panel.connection_aborted": "Input connection aborted",
+ "create.factory_panel.cycled_arrow_path": "Cycled arrow pathing mode %1$s",
+ "create.factory_panel.hold_to_set_amount": "Click and hold for target amount",
+ "create.factory_panel.in_progress": "(In Progress)",
+ "create.factory_panel.input_in_restock_mode": "Input gauge cannot be in restock mode",
+ "create.factory_panel.link_connected": "Gauge connected to %1$s",
+ "create.factory_panel.new_factory_task": "New factory task",
+ "create.factory_panel.no_item": "Input gauge must have an item first",
+ "create.factory_panel.panels_connected": "Now using %1$s to create %2$s",
+ "create.factory_panel.redstone_paused": "(Redstone Paused)",
+ "create.factory_panel.relocated": "Gauge moved to new position",
+ "create.factory_panel.relocation_aborted": "Gauge relocation aborted",
+ "create.factory_panel.same_orientation": "Blocks must have the same orientation",
+ "create.factory_panel.same_surface": "Blocks must be on the same surface",
+ "create.factory_panel.some_links_unloaded": "Some links are not loaded",
+ "create.factory_panel.target_amount": "Target Amount in Storage",
+ "create.factory_panel.too_far_apart": "Blocks are too far away from each other",
+ "create.factory_panel.tune_before_placing": "Tune to a stock link before placing",
+ "create.flap_display.cycles.alphabet": " ;A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;X;Y;Z",
+ "create.flap_display.cycles.arrival_time": " ; min;now;15s;30s;45s",
+ "create.flap_display.cycles.fluid_units": "mB;B ",
+ "create.flap_display.cycles.instant": " ; ",
+ "create.flap_display.cycles.numeric": " ;0;1;2;3;4;5;6;7;8;9",
+ "create.flap_display.cycles.pixel": "â–ˆ;â–“;â–’",
+ "create.flap_display.cycles.shortened_numbers": " ;K;M",
+ "create.generic.angle": "Angle",
+ "create.generic.clockwise": "Clockwise",
+ "create.generic.counter_clockwise": "Counter-Clockwise",
+ "create.generic.daytime.am": "am",
+ "create.generic.daytime.hour": "Hour",
+ "create.generic.daytime.minute": "Minute",
+ "create.generic.daytime.pm": "pm",
+ "create.generic.daytime.second": "Second",
+ "create.generic.delay": "Delay",
+ "create.generic.duration": "Duration",
+ "create.generic.height": "Height",
+ "create.generic.in_quotes": "\"%1$s\"",
+ "create.generic.length": "Length",
+ "create.generic.notes": "F#;F;E;D#;D;C#;C;B;A#;A;G#;G",
+ "create.generic.pitch": "Pitch: %1$s",
+ "create.generic.radius": "Radius",
+ "create.generic.range": "Range",
+ "create.generic.speed": "Speed",
+ "create.generic.timeUnit": "Time Unit",
+ "create.generic.unit.buckets": "B",
+ "create.generic.unit.degrees": "°",
+ "create.generic.unit.millibuckets": "mB",
+ "create.generic.unit.minutes": "Minutes",
+ "create.generic.unit.rpm": "RPM",
+ "create.generic.unit.seconds": "Seconds",
+ "create.generic.unit.stress": "su",
+ "create.generic.unit.ticks": "Ticks",
+ "create.generic.width": "Width",
+ "create.gui.address_box.clipboard_tip": "Keeping Track",
+ "create.gui.address_box.clipboard_tip_1": "Entries starting with '#' on",
+ "create.gui.address_box.clipboard_tip_2": "held or nearby clipboards will",
+ "create.gui.address_box.clipboard_tip_3": "help auto-complete this input.",
+ "create.gui.address_box.clipboard_tip_4": "(e.g. #storage, #factory)",
+ "create.gui.adjustable_crate.storageSpace": "Storage Space",
+ "create.gui.adjustable_crate.title": "Adjustable Crate",
+ "create.gui.assembly.exception": "This Contraption was unable to assemble:",
+ "create.gui.assembly.exception.chunkNotLoaded": "The Block at [%1$s,%2$s,%3$s] was not in a loaded chunk",
+ "create.gui.assembly.exception.column_conflict": "Another Elevator is already targeting this column of contacts",
+ "create.gui.assembly.exception.noPistonPoles": "The Piston is missing some extension Poles",
+ "create.gui.assembly.exception.no_contacts": "Elevators require a horizontal _Redstone_ _Contact_ attached to the structure",
+ "create.gui.assembly.exception.not_enough_sails": "Attached structure does not include enough sail-like blocks: %1$s\nA minimum of %2$s are required",
+ "create.gui.assembly.exception.structureTooLarge": "There are too many Blocks included in the contraption.\nThe configured maximum is: %1$s",
+ "create.gui.assembly.exception.tooManyPistonPoles": "There are too many extension Poles attached to this Piston.\nThe configured maximum is: %1$s",
+ "create.gui.assembly.exception.too_many_contacts": "Cannot attach more than one _Redstone_ _Contact_ to Elevators",
+ "create.gui.assembly.exception.unmovableBlock": "Unmovable Block (%4$s) at [%1$s,%2$s,%3$s]",
+ "create.gui.attached_computer.controlled": "This device is being controlled by a computer",
+ "create.gui.attached_computer.hint": "To use device manually, disconnect all computers and modems",
+ "create.gui.attribute_filter.add_attribute": "Add attribute to List",
+ "create.gui.attribute_filter.add_inverted_attribute": "Add opposite attribute to List",
+ "create.gui.attribute_filter.add_reference_item": "Add Reference Item",
+ "create.gui.attribute_filter.allow_list_conjunctive": "Allow-List (All)",
+ "create.gui.attribute_filter.allow_list_conjunctive.description": "Items pass only if they have ALL of the selected attributes.",
+ "create.gui.attribute_filter.allow_list_disjunctive": "Allow-List (Any)",
+ "create.gui.attribute_filter.allow_list_disjunctive.description": "Items pass if they have any of the selected attributes.",
+ "create.gui.attribute_filter.deny_list": "Deny-List",
+ "create.gui.attribute_filter.deny_list.description": "Items pass if they do NOT have any of the selected attributes.",
+ "create.gui.attribute_filter.no_selected_attributes": "No attributes selected",
+ "create.gui.attribute_filter.selected_attributes": "Selected attributes:",
+ "create.gui.clipboard.erase_checked": "Erase checked items",
+ "create.gui.config.overlay1": "Hi :)",
+ "create.gui.config.overlay2": "This is a sample overlay",
+ "create.gui.config.overlay3": "Click or drag with your mouse",
+ "create.gui.config.overlay4": "to move this preview",
+ "create.gui.config.overlay5": "Press ESC to exit this screen",
+ "create.gui.config.overlay6": "and save the new position",
+ "create.gui.config.overlay7": "Run /create overlay reset",
+ "create.gui.config.overlay8": "to reset to the default position",
+ "create.gui.contraptions.network_overstressed": "It appears that this contraption is _overstressed_. Add more sources or _slow_ _down_ the components with a high _stress_ _impact_.",
+ "create.gui.contraptions.not_fast_enough": "It appears that this %1$s is _not_ rotating with _enough_ _speed_.",
+ "create.gui.factory_panel.activate_crafting": "Use Mechanical Crafting",
+ "create.gui.factory_panel.address_missing": "Inactive: missing a target address",
+ "create.gui.factory_panel.connect_input": "Add new connection",
+ "create.gui.factory_panel.crafting_input": "Packaged Ingredients",
+ "create.gui.factory_panel.crafting_input_tip": "Unpackage into connected",
+ "create.gui.factory_panel.crafting_input_tip_1": "mechanical crafters (3x3)",
+ "create.gui.factory_panel.empty_panel": "Empty panel",
+ "create.gui.factory_panel.expected_output": "Expecting %1$s",
+ "create.gui.factory_panel.expected_output_tip": "The amount of output returning",
+ "create.gui.factory_panel.expected_output_tip_1": "after each successful request",
+ "create.gui.factory_panel.expected_output_tip_2": "Scroll to change",
+ "create.gui.factory_panel.has_link_connections": "Gauge has connected links",
+ "create.gui.factory_panel.inactive": " Inactive ",
+ "create.gui.factory_panel.left_click_disconnect": "Left-Click to disconnect",
+ "create.gui.factory_panel.left_click_reset": "Left-Click to reset",
+ "create.gui.factory_panel.no_open_promises": "No open promises",
+ "create.gui.factory_panel.no_target_amount_set": "Inactive: no target amount set",
+ "create.gui.factory_panel.place_item_to_monitor": "Place Item to Monitor",
+ "create.gui.factory_panel.promise_prevents_oversending": "This prevents over-sending.",
+ "create.gui.factory_panel.promised_items": "Promised items",
+ "create.gui.factory_panel.promises_do_not_expire": "Promises do not expire",
+ "create.gui.factory_panel.promises_expire_title": "Promises expire after",
+ "create.gui.factory_panel.recipe_address": "Send inputs to...",
+ "create.gui.factory_panel.recipe_address_given": "Sending inputs to",
+ "create.gui.factory_panel.recipe_address_tip": "Enter the address where",
+ "create.gui.factory_panel.recipe_address_tip_1": "this recipe is crafted.",
+ "create.gui.factory_panel.recipe_promises_tip": "When inputs are sent, a promise",
+ "create.gui.factory_panel.recipe_promises_tip_1": "is held until outputs arrive.",
+ "create.gui.factory_panel.relocate": "Move this gauge",
+ "create.gui.factory_panel.reset": "Reset all settings",
+ "create.gui.factory_panel.restocker_address": "Send items to...",
+ "create.gui.factory_panel.restocker_address_given": "Sending to",
+ "create.gui.factory_panel.restocker_address_tip": "Enter the address that will",
+ "create.gui.factory_panel.restocker_address_tip_1": "cause packages to arrive here.",
+ "create.gui.factory_panel.restocker_promises_tip": "When items are sent, a promise",
+ "create.gui.factory_panel.restocker_promises_tip_1": "is held until they arrive.",
+ "create.gui.factory_panel.save_and_close": "Save and close",
+ "create.gui.factory_panel.scroll_to_change_amount": "Scroll to change amount",
+ "create.gui.factory_panel.send_item": "Send %1$s",
+ "create.gui.factory_panel.sending_item": "Sending %1$s",
+ "create.gui.factory_panel.sending_item_tip": "whenever local stock level",
+ "create.gui.factory_panel.sending_item_tip_1": "is below the target amount",
+ "create.gui.factory_panel.title_as_recipe": "Recipe Settings",
+ "create.gui.factory_panel.title_as_restocker": "Restocker Settings",
+ "create.gui.factory_panel.unconfigured_input": "Recipe ingredients",
+ "create.gui.factory_panel.unconfigured_input_tip": "Ingredients can be added by",
+ "create.gui.factory_panel.unconfigured_input_tip_1": "connecting other factory gauges",
+ "create.gui.filter.allow_list": "Allow-List",
+ "create.gui.filter.allow_list.description": "Items pass if they match any of the above. An empty Allow-List rejects everything.",
+ "create.gui.filter.deny_list": "Deny-List",
+ "create.gui.filter.deny_list.description": "Items pass if they do NOT match any of the above. An empty Deny-List accepts everything.",
+ "create.gui.filter.ignore_data": "Ignore Data",
+ "create.gui.filter.ignore_data.description": "Items match regardless of their attributes.",
+ "create.gui.filter.respect_data": "Respect Data",
+ "create.gui.filter.respect_data.description": "Items only match if their durability, enchantments, and other attributes match as well.",
+ "create.gui.gauge.info_header": "Gauge Information:",
+ "create.gui.goggles.at_current_speed": "at current speed",
+ "create.gui.goggles.basin_contents": "Basin Contents:",
+ "create.gui.goggles.fluid_container": "Fluid Container Info:",
+ "create.gui.goggles.fluid_container.capacity": "Capacity: ",
+ "create.gui.goggles.generator_stats": "Generator Stats:",
+ "create.gui.goggles.kinetic_stats": "Kinetic Stats:",
+ "create.gui.goggles.pole_length": "Pole Length:",
+ "create.gui.package_port.catch_packages": "Catch packages addressed to...",
+ "create.gui.package_port.catch_packages_empty": "Leave empty to match non-addressed",
+ "create.gui.package_port.catch_packages_wildcard": "Use * as a text wildcard",
+ "create.gui.package_port.not_targeting_anything": "No target selected",
+ "create.gui.package_port.send_and_receive": "Send and receive packages",
+ "create.gui.package_port.send_only": "Only send packages",
+ "create.gui.pojav.content": "Mobile devices have extremely low quality graphics drivers, which cause visual issues that are impossible for Create to fix. No support will be provided for any issues you encounter.",
+ "create.gui.pojav.continue": "Continue Anyway",
+ "create.gui.pojav.title": "Create is not supported on PojavLauncher.",
+ "create.gui.redstone_requester.allow_partial": "Allow partial orders",
+ "create.gui.redstone_requester.dont_allow_partial": "Must send all items",
+ "create.gui.redstone_requester.requester_address": "Send order to...",
+ "create.gui.redstone_requester.requester_address_given": "Sending order to",
+ "create.gui.redstone_requester.requester_address_tip": "Enter the address this",
+ "create.gui.redstone_requester.requester_address_tip_1": "order should be send to.",
+ "create.gui.schedule.add_condition": "Add Condition",
+ "create.gui.schedule.add_entry": "Add Action",
+ "create.gui.schedule.alternative_condition": "Alternative Condition",
+ "create.gui.schedule.duplicate": "Duplicate",
+ "create.gui.schedule.lmb_edit": "Left-Click to Edit",
+ "create.gui.schedule.move_down": "Move down",
+ "create.gui.schedule.move_up": "Move up",
+ "create.gui.schedule.remove_entry": "Remove Action",
+ "create.gui.schedule.rmb_remove": "Right-Click to Remove",
+ "create.gui.schematicTable.availableSchematics": "Available Schematics",
+ "create.gui.schematicTable.finished": "Upload Finished!",
+ "create.gui.schematicTable.noSchematics": "No Schematics Saved",
+ "create.gui.schematicTable.open_folder": "Open Folder",
+ "create.gui.schematicTable.refresh": "Refresh Files",
+ "create.gui.schematicTable.title": "Schematic Table",
+ "create.gui.schematicTable.uploading": "Uploading...",
+ "create.gui.schematicannon.gunpowderLevel": "Gunpowder at %1$s%%",
+ "create.gui.schematicannon.listPrinter": "Checklist Printer",
+ "create.gui.schematicannon.option.dontReplaceSolid": "Don't Replace Solid Blocks",
+ "create.gui.schematicannon.option.dontReplaceSolid.description": "The cannon will never replace any Solid blocks in its working area, only non-Solid and Air.",
+ "create.gui.schematicannon.option.replaceWithAny": "Replace Solid with Any",
+ "create.gui.schematicannon.option.replaceWithAny.description": "The cannon will replace Solid blocks in its working area if the Schematic contains any Block at the Location.",
+ "create.gui.schematicannon.option.replaceWithEmpty": "Replace Solid with Empty",
+ "create.gui.schematicannon.option.replaceWithEmpty.description": "The cannon will clear out all blocks in its working area, including those replaced by Air.",
+ "create.gui.schematicannon.option.replaceWithSolid": "Replace Solid with Solid",
+ "create.gui.schematicannon.option.replaceWithSolid.description": "The cannon will only replace Solid blocks in its working area if the Schematic contains a solid Block at the Location.",
+ "create.gui.schematicannon.option.skipBlockEntities": "Protect Block Entities",
+ "create.gui.schematicannon.option.skipBlockEntities.description": "The cannon will avoid replacing data holding blocks such as Chests.",
+ "create.gui.schematicannon.option.skipMissing": "Skip missing Blocks",
+ "create.gui.schematicannon.option.skipMissing.description": "If the cannon cannot find a required Block for placement, it will continue at the next Location.",
+ "create.gui.schematicannon.optionDisabled": "Currently Disabled",
+ "create.gui.schematicannon.optionEnabled": "Currently Enabled",
+ "create.gui.schematicannon.shotsRemaining": "Shots left: %1$s",
+ "create.gui.schematicannon.shotsRemainingWithBackup": "With backup: %1$s",
+ "create.gui.schematicannon.showOptions": "Show Printer Settings",
+ "create.gui.schematicannon.slot.gunpowder": "Add gunpowder to fuel the cannon",
+ "create.gui.schematicannon.slot.listPrinter": "Place a Clipboard or Book here to print a Checklist for your Schematic",
+ "create.gui.schematicannon.slot.schematic": "Add your Schematic here. Make sure it is deployed at a specific location.",
+ "create.gui.schematicannon.title": "Schematicannon",
+ "create.gui.scrollInput.defaultTitle": "Choose an Option:",
+ "create.gui.scrollInput.scrollToAdjustAmount": "Scroll to Adjust Amount",
+ "create.gui.scrollInput.scrollToModify": "Scroll to Modify",
+ "create.gui.scrollInput.scrollToSelect": "Scroll to Select",
+ "create.gui.scrollInput.shiftScrollsFaster": "Shift to Scroll Faster",
+ "create.gui.sequenced_gearshift.instruction": "Instruction",
+ "create.gui.sequenced_gearshift.instruction.await": "Await",
+ "create.gui.sequenced_gearshift.instruction.await.descriptive": "Await new Redstone Pulse",
+ "create.gui.sequenced_gearshift.instruction.delay": "Delay",
+ "create.gui.sequenced_gearshift.instruction.delay.descriptive": "Timed Delay",
+ "create.gui.sequenced_gearshift.instruction.delay.duration": "Duration",
+ "create.gui.sequenced_gearshift.instruction.end": "End",
+ "create.gui.sequenced_gearshift.instruction.end.descriptive": "End",
+ "create.gui.sequenced_gearshift.instruction.turn_angle": "Turn",
+ "create.gui.sequenced_gearshift.instruction.turn_angle.angle": "Angle",
+ "create.gui.sequenced_gearshift.instruction.turn_angle.descriptive": "Turn by angle",
+ "create.gui.sequenced_gearshift.instruction.turn_distance": "Piston",
+ "create.gui.sequenced_gearshift.instruction.turn_distance.descriptive": "Turn to move Piston/Pulley/Gantry",
+ "create.gui.sequenced_gearshift.instruction.turn_distance.distance": "Distance",
+ "create.gui.sequenced_gearshift.speed": "Speed, Direction",
+ "create.gui.sequenced_gearshift.speed.back": "Input speed, Reversed",
+ "create.gui.sequenced_gearshift.speed.back_fast": "Double speed, Reversed",
+ "create.gui.sequenced_gearshift.speed.forward": "Input speed, Forwards",
+ "create.gui.sequenced_gearshift.speed.forward_fast": "Double speed, Forwards",
+ "create.gui.sequenced_gearshift.title": "Sequenced Gearshift",
+ "create.gui.speedometer.title": "Rotation Speed",
+ "create.gui.stock_keeper.already_ordering_recipe": "Already ordering this recipe",
+ "create.gui.stock_keeper.checking_stocks": "Checking stocks...",
+ "create.gui.stock_keeper.configure": "Configure",
+ "create.gui.stock_keeper.confirm_order": "Confirm Order",
+ "create.gui.stock_keeper.craft": "Craft %1$s",
+ "create.gui.stock_keeper.inventories_empty": "Linked Inventories are Empty",
+ "create.gui.stock_keeper.network_lock_tip": "Locking prevents other Players",
+ "create.gui.stock_keeper.network_lock_tip_1": "from ordering items directly",
+ "create.gui.stock_keeper.network_lock_tip_2": "Click to toggle",
+ "create.gui.stock_keeper.network_locked": "Network is locked",
+ "create.gui.stock_keeper.network_open": "Network is open",
+ "create.gui.stock_keeper.no_packagers_linked": "No Packagers Linked",
+ "create.gui.stock_keeper.no_search_results": "No matching items found",
+ "create.gui.stock_keeper.not_in_stock": "Required items are not in Stock",
+ "create.gui.stock_keeper.package_adress": "Package Address",
+ "create.gui.stock_keeper.program_requester": "Program Requester",
+ "create.gui.stock_keeper.recipe_result_empty": "This recipe has a empty result, this is not allowed and should be reported to the mod or datapack that adds this recipe.",
+ "create.gui.stock_keeper.request_sent": "Request Sent!",
+ "create.gui.stock_keeper.schematic_list.no_results": "No items from your checklist are in stock",
+ "create.gui.stock_keeper.schematic_list.requesting": "Click 'Send' to request all available items from your checklist",
+ "create.gui.stock_keeper.search_items": "Search Items",
+ "create.gui.stock_keeper.send": "Send",
+ "create.gui.stock_keeper.slots_full": "Order slots already full",
+ "create.gui.stock_keeper.title": "Stock Keeper",
+ "create.gui.stock_keeper.unsorted_category": "Unsorted",
+ "create.gui.stock_ticker.category_editor": "Category Editor",
+ "create.gui.stock_ticker.category_filter": "Category Filter",
+ "create.gui.stock_ticker.category_filter_tip": "Place a List or Attribute Filter",
+ "create.gui.stock_ticker.category_filter_tip_1": "to specify which items are included",
+ "create.gui.stock_ticker.category_name": "Category Name",
+ "create.gui.stock_ticker.delete_category": "Delete Category",
+ "create.gui.stock_ticker.empty_category_name_placeholder": "(Empty)",
+ "create.gui.stock_ticker.new_category": "New Category",
+ "create.gui.stock_ticker.shift_moves_bottom": "Shift moves to bottom",
+ "create.gui.stock_ticker.shift_moves_top": "Shift moves to top",
+ "create.gui.stressometer.capacity": "Remaining Capacity",
+ "create.gui.stressometer.no_rotation": "No Rotation",
+ "create.gui.stressometer.overstressed": "Overstressed",
+ "create.gui.stressometer.title": "Network Stress",
+ "create.gui.symmetryWand.mirrorType": "Mirror",
+ "create.gui.symmetryWand.orientation": "Orientation",
+ "create.gui.terrainzapper.brush": "Brush",
+ "create.gui.terrainzapper.brush.cluster": "Cluster",
+ "create.gui.terrainzapper.brush.cuboid": "Cuboid",
+ "create.gui.terrainzapper.brush.cylinder": "Cylinder",
+ "create.gui.terrainzapper.brush.sphere": "Sphere",
+ "create.gui.terrainzapper.brush.surface": "Surface",
+ "create.gui.terrainzapper.pattern.chance25": "25% Roll",
+ "create.gui.terrainzapper.pattern.chance50": "50% Roll",
+ "create.gui.terrainzapper.pattern.chance75": "75% Roll",
+ "create.gui.terrainzapper.pattern.checkered": "Checkerboard",
+ "create.gui.terrainzapper.pattern.inversecheckered": "Inverted Checkerboard",
+ "create.gui.terrainzapper.pattern.solid": "Solid",
+ "create.gui.terrainzapper.patternSection": "Pattern",
+ "create.gui.terrainzapper.placement": "Placement",
+ "create.gui.terrainzapper.placement.attached": "Attached",
+ "create.gui.terrainzapper.placement.inserted": "Inserted",
+ "create.gui.terrainzapper.placement.merged": "Merged",
+ "create.gui.terrainzapper.searchDiagonal": "Follow Diagonals",
+ "create.gui.terrainzapper.searchFuzzy": "Ignore Material Borders",
+ "create.gui.terrainzapper.title": "Handheld Blockzapper",
+ "create.gui.terrainzapper.tool": "Tool",
+ "create.gui.terrainzapper.tool.clear": "Clear",
+ "create.gui.terrainzapper.tool.fill": "Fill",
+ "create.gui.terrainzapper.tool.flatten": "Flatten",
+ "create.gui.terrainzapper.tool.overlay": "Overlay",
+ "create.gui.terrainzapper.tool.place": "Place",
+ "create.gui.terrainzapper.tool.replace": "Replace",
+ "create.gui.threshold_switch.currently": "Currently %1$s",
+ "create.gui.threshold_switch.incompatible": "Incompatible block",
+ "create.gui.threshold_switch.invert_signal": "Invert Signal",
+ "create.gui.threshold_switch.lower_threshold": "...at or below:",
+ "create.gui.threshold_switch.not_attached": "Not attached to a block",
+ "create.gui.threshold_switch.power_off_when": "Power off when...",
+ "create.gui.threshold_switch.power_on_when": "Power on when...",
+ "create.gui.threshold_switch.pulley_y_level": "%1$sy",
+ "create.gui.threshold_switch.range": "(%1$s to %2$s)",
+ "create.gui.threshold_switch.range_max": "(max. %1$s)",
+ "create.gui.threshold_switch.title": "Threshold Switch",
+ "create.gui.threshold_switch.upper_threshold": "...at or above:",
+ "create.gui.toolmenu.cycle": "[SCROLL] to Cycle",
+ "create.gui.toolmenu.focusKey": "Hold [%1$s] to Focus",
+ "create.gui.value_settings.hold_to_edit": "Click and hold to edit",
+ "create.gui.value_settings.release_to_confirm": "Release %1$s to Confirm",
+ "create.hint.blocked_frogport": "A _Package_ caught by this _Frogport_ could not be transferred to the _inventory_ _below._ Could it be full?",
+ "create.hint.blocked_frogport.title": "Contains Packages",
+ "create.hint.derailed_train": "It appears this _Train_ is no longer sitting on a connected track piece. _Right-Click_ using a _wrench_ in order to relocate it to a nearby track.",
+ "create.hint.derailed_train.title": "Derailed Train",
+ "create.hint.empty_bearing": "_Right-click_ the bearing with an _empty_ _hand_ to _attach_ the structure you just built in front of it.",
+ "create.hint.empty_bearing.title": "Update Bearing",
+ "create.hint.full_deployer": "It appears this _Deployer_ contains _excess_ _items_ that need to be _extracted._ Use a _hopper,_ _funnel_ or other means to free it from its overflow.",
+ "create.hint.full_deployer.title": "Deployer cannot activate",
+ "create.hint.hose_pulley": "The targeted body of fluid is considered infinite.",
+ "create.hint.hose_pulley.title": "Bottomless Supply",
+ "create.hint.mechanical_arm_no_targets": "It appears this _Mechanical_ _Arm_ has not been assigned any _targets._ Select belts, depots, funnels and other blocks by _right-clicking_ them while _holding_ the _Mechanical_ _Arm_ in your _hand_.",
+ "create.hint.mechanical_arm_no_targets.title": "No Targets",
+ "create.item_attributes.added_by": "was added by %1$s",
+ "create.item_attributes.added_by.inverted": "was not added by %1$s",
+ "create.item_attributes.astralsorcery_amulet": "improves %1$s",
+ "create.item_attributes.astralsorcery_amulet.inverted": "does not improve %1$s",
+ "create.item_attributes.astralsorcery_constellation": "is attuned to %1$s",
+ "create.item_attributes.astralsorcery_constellation.inverted": "is not attuned to %1$s",
+ "create.item_attributes.astralsorcery_crystal": "has crystal attribute %1$s",
+ "create.item_attributes.astralsorcery_crystal.inverted": "does not have crystal attribute %1$s",
+ "create.item_attributes.astralsorcery_perk_gem": "has perk attribute %1$s",
+ "create.item_attributes.astralsorcery_perk_gem.inverted": "does not have perk attribute %1$s",
+ "create.item_attributes.badly_damaged": "is heavily damaged",
+ "create.item_attributes.badly_damaged.inverted": "is not heavily damaged",
+ "create.item_attributes.blastable": "can be Smelted in a Blast Furnace",
+ "create.item_attributes.blastable.inverted": "cannot be Smelted in a Blast Furnace",
+ "create.item_attributes.book_author": "was authored by %1$s",
+ "create.item_attributes.book_author.inverted": "was not authored by %1$s",
+ "create.item_attributes.book_copy_first": "is a first-generation copy",
+ "create.item_attributes.book_copy_first.inverted": "is not a first-generation copy",
+ "create.item_attributes.book_copy_original": "is an original",
+ "create.item_attributes.book_copy_original.inverted": "is not an original",
+ "create.item_attributes.book_copy_second": "is a second-generation copy",
+ "create.item_attributes.book_copy_second.inverted": "is not a second-generation copy",
+ "create.item_attributes.book_copy_tattered": "is a tattered mess",
+ "create.item_attributes.book_copy_tattered.inverted": "is not a tattered mess",
+ "create.item_attributes.color": "is dyed %1$s",
+ "create.item_attributes.color.inverted": "is not dyed %1$s",
+ "create.item_attributes.compostable": "can be composted",
+ "create.item_attributes.compostable.inverted": "cannot be composted",
+ "create.item_attributes.consumable": "can be eaten",
+ "create.item_attributes.consumable.inverted": "cannot be eaten",
+ "create.item_attributes.crushable": "can be Crushed",
+ "create.item_attributes.crushable.inverted": "cannot be Crushed",
+ "create.item_attributes.damaged": "is damaged",
+ "create.item_attributes.damaged.inverted": "is not damaged",
+ "create.item_attributes.enchanted": "is enchanted",
+ "create.item_attributes.enchanted.inverted": "is unenchanted",
+ "create.item_attributes.equipable": "can be equipped",
+ "create.item_attributes.equipable.inverted": "cannot be equipped",
+ "create.item_attributes.fluid_container": "can store fluids",
+ "create.item_attributes.fluid_container.inverted": "cannot store fluids",
+ "create.item_attributes.furnace_fuel": "is furnace fuel",
+ "create.item_attributes.furnace_fuel.inverted": "is not furnace fuel",
+ "create.item_attributes.has_enchant": "is enchanted with %1$s",
+ "create.item_attributes.has_enchant.inverted": "is not enchanted with %1$s",
+ "create.item_attributes.has_fluid": "contains %1$s",
+ "create.item_attributes.has_fluid.inverted": "does not contain %1$s",
+ "create.item_attributes.has_name": "has the custom name %1$s",
+ "create.item_attributes.has_name.inverted": "does not have the custom name %1$s",
+ "create.item_attributes.hauntable": "can be Haunted",
+ "create.item_attributes.hauntable.inverted": "cannot be Haunted",
+ "create.item_attributes.in_item_group": "is in group '%1$s'",
+ "create.item_attributes.in_item_group.inverted": "is not in group '%1$s'",
+ "create.item_attributes.in_tag": "is tagged %1$s",
+ "create.item_attributes.in_tag.inverted": "is not tagged %1$s",
+ "create.item_attributes.max_enchanted": "is enchanted at max level",
+ "create.item_attributes.max_enchanted.inverted": "is not enchanted at max level",
+ "create.item_attributes.not_stackable": "cannot stack",
+ "create.item_attributes.not_stackable.inverted": "can be stacked",
+ "create.item_attributes.placeable": "is placeable",
+ "create.item_attributes.placeable.inverted": "is not placeable",
+ "create.item_attributes.renamed": "has a custom name",
+ "create.item_attributes.renamed.inverted": "does not have a custom name",
+ "create.item_attributes.shulker_level": "is shulker %1$s",
+ "create.item_attributes.shulker_level.empty": "empty",
+ "create.item_attributes.shulker_level.full": "full",
+ "create.item_attributes.shulker_level.inverted": "is shulker not %1$s",
+ "create.item_attributes.shulker_level.partial": "partially filled",
+ "create.item_attributes.smeltable": "can be Smelted",
+ "create.item_attributes.smeltable.inverted": "cannot be Smelted",
+ "create.item_attributes.smokable": "can be Smoked",
+ "create.item_attributes.smokable.inverted": "cannot be Smoked",
+ "create.item_attributes.washable": "can be Washed",
+ "create.item_attributes.washable.inverted": "cannot be Washed",
+ "create.item_hatch.deposit_inventory": "Inventory items deposited",
+ "create.item_hatch.deposit_item": "Held item deposited",
+ "create.keyinfo.rotate_menu": "Open Block Rotation Menu",
+ "create.keyinfo.toolbelt": "Access Nearby Toolboxes",
+ "create.keyinfo.toolmenu": "Focus Schematic Overlay",
+ "create.kinetics.creative_motor.rotation_speed": "Generated Speed in RPM",
+ "create.kinetics.speed_controller.rotation_speed": "Targeted Speed in RPM",
+ "create.kinetics.valve_handle.rotated_angle": "Rotation when used",
+ "create.large_water_wheel.not_enough_space": "Clear Blocks for Placement",
+ "create.linked_controller.bind_mode": "Bind mode active",
+ "create.linked_controller.frequency_slot_1": "Keybind: %1$s, Freq. #1",
+ "create.linked_controller.frequency_slot_2": "Keybind: %1$s, Freq. #2",
+ "create.linked_controller.key_bound": "Frequency bound to %1$s",
+ "create.linked_controller.press_keybind": "Press %1$s, %2$s, %3$s, %4$s, %5$s or %6$s, to bind this frequency to the respective key",
+ "create.logistically_linked.cleared": "Cleared connection to network",
+ "create.logistically_linked.connected": "Connected to existing network successfully",
+ "create.logistically_linked.new_network_started": "New link network started",
+ "create.logistically_linked.protected": "Logistics Network is Protected",
+ "create.logistically_linked.tooltip": "Frequency configured",
+ "create.logistically_linked.tooltip_clear": "Place in crafting grid to reset",
+ "create.logistically_linked.tuned": "Tuned to this network",
+ "create.logistics.crafter.click_to_merge": "Click to merge Inventories",
+ "create.logistics.crafter.click_to_separate": "Click to separate Inventories",
+ "create.logistics.crafter.connected": "Connected Crafters",
+ "create.logistics.creative_crate.supply": "Infinite Supply",
+ "create.logistics.filter": "Filter",
+ "create.logistics.filter.any_amount_short": "Any",
+ "create.logistics.filter.click_to_replace": "Click with item to replace",
+ "create.logistics.filter.click_to_set": "Click with item to set",
+ "create.logistics.filter.exactly": "Exactly",
+ "create.logistics.filter.extracted_amount": "Extracted Amount",
+ "create.logistics.filter.hold_to_set_amount": "Click and hold for amount",
+ "create.logistics.filter.invalid_item": "Cannot use this item here.",
+ "create.logistics.filter.requires_item_in_inventory": "Requires %1$s item in Inventory",
+ "create.logistics.filter.up_to": "Up to",
+ "create.logistics.firstFrequency": "Frequency #1",
+ "create.logistics.fluid_filter": "Fluid Filter",
+ "create.logistics.recipe_filter": "Recipe Filter",
+ "create.logistics.redstone_interval": "Redstone Interval",
+ "create.logistics.secondFrequency": "Frequency #2",
+ "create.logistics.train_observer.cargo_filter": "Cargo Filter",
+ "create.logistics.when_multiple_outputs_available": "Distribution Method",
+ "create.materialChecklist": "Material Checklist",
+ "create.materialChecklist.blocksNotLoaded": "* Disclaimer *\n\nMaterial List may be inaccurate due to relevant chunks not being loaded.",
+ "create.mechanical_arm.deposit_to": "Deposit items to %1$s",
+ "create.mechanical_arm.extract_from": "Take items from %1$s",
+ "create.mechanical_arm.points_outside_range": "%1$s selected interaction point(s) removed due to range limitations.",
+ "create.mechanical_arm.selection_mode.forced_round_robin": "Forced Round Robin",
+ "create.mechanical_arm.selection_mode.prefer_first": "Prefer First Target",
+ "create.mechanical_arm.selection_mode.round_robin": "Round Robin",
+ "create.mechanical_arm.summary": "Mechanical Arm has %1$s input(s) and %2$s output(s).",
+ "create.menu.configure": "Configure...",
+ "create.menu.only_ingame": "Available in the Pause Menu",
+ "create.menu.ponder_index": "Ponder Index",
+ "create.menu.report_bugs": "Report Issues",
+ "create.menu.return": "Return to Menu",
+ "create.menu.support": "Support Us",
+ "create.minecart_coupling.no_loops": "Couplings cannot form a loop",
+ "create.minecart_coupling.removed": "Removed all couplings from minecart",
+ "create.minecart_coupling.too_far": "Minecarts are too far apart",
+ "create.minecart_coupling.two_couplings_max": "Minecarts cannot have more than two couplings each",
+ "create.minecart_coupling.unloaded": "Parts of your train seem to be in unloaded chunks",
+ "create.orientation.alongX": "Along X",
+ "create.orientation.alongZ": "Along Z",
+ "create.orientation.diagonal": "Diagonal",
+ "create.orientation.horizontal": "Horizontal",
+ "create.orientation.orthogonal": "Orthogonal",
+ "create.package_port.cannot_reach_down": "Cannot connect downward",
+ "create.package_port.too_far": "Too far away",
+ "create.package_port.valid": "Can Connect ✔",
+ "create.packager.no_portable_storage": "Packagers cannot interface with Portable Storage directly",
+ "create.packager_link.clear": "Cleared selection",
+ "create.packager_link.set": "Target selected",
+ "create.packager_link.success": "Successfully bound to target",
+ "create.ponder.analog_lever.header": "Controlling signals using the Analog Lever",
+ "create.ponder.analog_lever.text_1": "Analog Levers make for a compact and precise source of redstone power",
+ "create.ponder.analog_lever.text_2": "Right-click to increase its analog power output",
+ "create.ponder.analog_lever.text_3": "Right-click while Sneaking to decrease the power output again",
+ "create.ponder.andesite_tunnel.header": "Using Andesite Tunnels",
+ "create.ponder.andesite_tunnel.text_1": "Andesite Tunnels can be used to cover up your belts",
+ "create.ponder.andesite_tunnel.text_2": "Whenever an Andesite Tunnel has connections to the sides...",
+ "create.ponder.andesite_tunnel.text_3": "...they will split exactly one item off of any passing stacks",
+ "create.ponder.andesite_tunnel.text_4": "The remainder will continue on its path",
+ "create.ponder.auto_schedule.header": "Stations & Scheduling",
+ "create.ponder.auto_schedule.text_1": "Schedules can be used to provide drivers with a destination",
+ "create.ponder.auto_schedule.text_2": "Comparators will receive a signal whenever a Train is present",
+ "create.ponder.auto_schedule.text_3": "Mind that a Station can only be approached from the indicated direction",
+ "create.ponder.auto_schedule.text_4": "Stations can also be used to assign new Schedules automatically",
+ "create.ponder.auto_schedule.text_5": "The Schedule placed on a station will automatically copy itself to present trains",
+ "create.ponder.auto_schedule.text_6": "As opposed to manual scheduling, drivers will not take the item with them",
+ "create.ponder.basin.header": "Processing Items in the Basin",
+ "create.ponder.basin.text_1": "A Basin can hold Items and Fluids for Processing",
+ "create.ponder.basin.text_2": "After a processing step, basins try to output below to the side of them",
+ "create.ponder.basin.text_3": "When a valid component is present, the Basin will show an output faucet",
+ "create.ponder.basin.text_4": "A number of options are applicable here",
+ "create.ponder.basin.text_5": "Outputs will be caught by the inventory below",
+ "create.ponder.basin.text_6": "Without output faucet, the Basin will retain items created in its processing",
+ "create.ponder.basin.text_7": "This can be useful if outputs should be re-used as ingredients",
+ "create.ponder.basin.text_8": "Desired outputs will then have to be extracted from the basin",
+ "create.ponder.basin.text_9": "A Filter might be necessary to avoid pulling out un-processed items",
+ "create.ponder.bearing_modes.header": "Movement Modes of the Mechanical Bearing",
+ "create.ponder.bearing_modes.text_1": "When Stopped, the Bearing will place the structure at the nearest grid-aligned Angle",
+ "create.ponder.bearing_modes.text_2": "It can be configured never to revert to solid blocks, or only near the angle it started at",
+ "create.ponder.belt_casing.header": "Encasing Belts",
+ "create.ponder.belt_casing.text_1": "Brass or Andesite Casing can be used to decorate Mechanical Belts",
+ "create.ponder.belt_casing.text_2": "A wrench can be used to remove the casing",
+ "create.ponder.belt_connector.header": "Using Mechanical Belts",
+ "create.ponder.belt_connector.text_1": "Right-Clicking two shafts with a belt item will connect them together",
+ "create.ponder.belt_connector.text_2": "Accidental selections can be canceled with Right-Click while Sneaking",
+ "create.ponder.belt_connector.text_3": "Additional Shafts can be added throughout the Belt",
+ "create.ponder.belt_connector.text_4": "Shafts connected via Belts will rotate with Identical Speed and Direction",
+ "create.ponder.belt_connector.text_5": "Added shafts can be removed using the wrench",
+ "create.ponder.belt_connector.text_6": "Mechanical Belts can be dyed for aesthetic purposes",
+ "create.ponder.belt_directions.header": "Valid Orientations for Mechanical Belts",
+ "create.ponder.belt_directions.text_1": "Belts cannot connect in arbitrary directions",
+ "create.ponder.belt_directions.text_2": "1. They can connect horizontally",
+ "create.ponder.belt_directions.text_3": "2. They can connect diagonally",
+ "create.ponder.belt_directions.text_4": "3. They can connect vertically",
+ "create.ponder.belt_directions.text_5": "4. And they can connect vertical shafts horizontally",
+ "create.ponder.belt_directions.text_6": "These are all possible directions. Belts can span any Length between 2 and 20 blocks",
+ "create.ponder.belt_transport.header": "Using Mechanical Belts for Logistics",
+ "create.ponder.belt_transport.text_1": "Moving belts will transport Items and other Entities",
+ "create.ponder.belt_transport.text_2": "Right-Click with an empty hand to take items off a belt",
+ "create.ponder.blaze_burner.header": "Feeding Blaze Burners",
+ "create.ponder.blaze_burner.text_1": "Blaze Burners can provide Heat to Items processed in a Basin",
+ "create.ponder.blaze_burner.text_2": "For this, the Blaze has to be fed with flammable items",
+ "create.ponder.blaze_burner.text_3": "With a Blaze Cake, the Burner can reach an even stronger level of heat",
+ "create.ponder.blaze_burner.text_4": "The feeding process can be automated using Deployers or Mechanical Arms",
+ "create.ponder.brass_funnel.header": "The Brass Funnel",
+ "create.ponder.brass_funnel.text_1": "Andesite Funnels can only ever extract single items.",
+ "create.ponder.brass_funnel.text_2": "Brass Funnels can extract up to a full stack.",
+ "create.ponder.brass_funnel.text_3": "The value panel allows for precise control over the extracted stack size.",
+ "create.ponder.brass_funnel.text_4": "Using items on the filter slot will restrict the funnel to only transfer matching stacks.",
+ "create.ponder.brass_tunnel.header": "Using Brass Tunnels",
+ "create.ponder.brass_tunnel.text_1": "Brass Tunnels can be used to cover up your belts",
+ "create.ponder.brass_tunnel.text_2": "Brass Tunnels have filter slots on each open side",
+ "create.ponder.brass_tunnel.text_3": "Filters on inbound connections simply block non-matching items",
+ "create.ponder.brass_tunnel.text_4": "Filters on outbound connections can be used to sort items by type",
+ "create.ponder.brass_tunnel.text_5": "Whenever a passing item has multiple valid exits, the distribution mode will decide how to handle it",
+ "create.ponder.brass_tunnel.text_6": "Brass Tunnels on parallel belts will form a group",
+ "create.ponder.brass_tunnel.text_7": "Incoming Items will now be distributed across all connected exits",
+ "create.ponder.brass_tunnel.text_8": "For this, items can also be inserted into the Tunnel block directly",
+ "create.ponder.brass_tunnel_modes.header": "Distribution Modes of the Brass Tunnel",
+ "create.ponder.brass_tunnel_modes.text_1": "The distribution behaviour of Brass Tunnels can be configured",
+ "create.ponder.brass_tunnel_modes.text_10": "'Synchronize Inputs' is a unique setting for Brass Tunnels",
+ "create.ponder.brass_tunnel_modes.text_11": "Items are only allowed past if every tunnel in the group has one waiting",
+ "create.ponder.brass_tunnel_modes.text_12": "This ensures that all affected belts supply items at the same rate",
+ "create.ponder.brass_tunnel_modes.text_2": "'Split' will attempt to distribute the stack evenly between available outputs",
+ "create.ponder.brass_tunnel_modes.text_3": "If an output is unable to take more items, it will be skipped",
+ "create.ponder.brass_tunnel_modes.text_4": "'Forced Split' will never skip outputs, and instead wait until they are free",
+ "create.ponder.brass_tunnel_modes.text_5": "'Round Robin' keeps stacks whole, and cycles through outputs iteratively",
+ "create.ponder.brass_tunnel_modes.text_6": "Once Again, if an output is unable to take more items, it will be skipped",
+ "create.ponder.brass_tunnel_modes.text_7": "'Forced Round Robin' never skips outputs",
+ "create.ponder.brass_tunnel_modes.text_8": "'Prefer Nearest' prioritizes the outputs closest to the items' input location",
+ "create.ponder.brass_tunnel_modes.text_9": "'Randomize' will distribute whole stacks to randomly picked outputs",
+ "create.ponder.cart_assembler.header": "Moving Structures using Cart Assemblers",
+ "create.ponder.cart_assembler.text_1": "Powered Cart Assemblers mount attached structures to passing Minecarts",
+ "create.ponder.cart_assembler.text_2": "Without a redstone signal, it disassembles passing cart contraptions back into blocks",
+ "create.ponder.cart_assembler.text_3": "Using a Wrench on the Minecart will let you carry the Contraption elsewhere",
+ "create.ponder.cart_assembler_dual.header": "Assembling Carriage Contraptions",
+ "create.ponder.cart_assembler_dual.text_1": "Whenever two Cart Assembers share an attached structure...",
+ "create.ponder.cart_assembler_dual.text_2": "Powering either of them will create a Carriage Contraption",
+ "create.ponder.cart_assembler_dual.text_3": "The carts will behave like those connected via Minecart Coupling",
+ "create.ponder.cart_assembler_modes.header": "Orientation Settings for Minecart Contraptions",
+ "create.ponder.cart_assembler_modes.text_1": "Cart Contraptions will rotate to face towards their carts' motion",
+ "create.ponder.cart_assembler_modes.text_2": "This Arrow indicates which side of the Structure will be considered the front",
+ "create.ponder.cart_assembler_modes.text_3": "If the Assembler is set to Lock Rotation, the contraptions' orientation will never change",
+ "create.ponder.cart_assembler_rails.header": "Other types of Minecarts and Rails",
+ "create.ponder.cart_assembler_rails.text_1": "Cart Assemblers on Regular Tracks will not affect the passing carts' motion",
+ "create.ponder.cart_assembler_rails.text_2": "When on Powered or Controller Rail, the carts will be held in place until it's Powered",
+ "create.ponder.cart_assembler_rails.text_3": "Other types of Minecarts can be used as the anchor",
+ "create.ponder.cart_assembler_rails.text_4": "Furnace Carts will keep themselves powered, pulling fuel from any attached inventories",
+ "create.ponder.chain_conveyor.header": "Relaying rotational force using Chain Conveyors",
+ "create.ponder.chain_conveyor.text_1": "Right-click two conveyors with chains to connect them",
+ "create.ponder.chain_conveyor.text_2": "Chain conveyors relay rotational power between each other..",
+ "create.ponder.chain_conveyor.text_3": "..and connect to shafts above or below them",
+ "create.ponder.chain_conveyor.text_4": "Right-click holding a wrench to start travelling on the chain",
+ "create.ponder.chain_conveyor.text_5": "At a junction, face towards a chain to follow it",
+ "create.ponder.chain_drive.header": "Relaying rotational force with Chain Drives",
+ "create.ponder.chain_drive.text_1": "Chain Drives relay rotation to each other in a row",
+ "create.ponder.chain_drive.text_2": "All shafts connected like this will rotate in the same direction",
+ "create.ponder.chain_drive.text_3": "Any part of the row can be rotated by 90 degrees",
+ "create.ponder.chain_gearshift.header": "Controlling rotational speed with Chain Gearshifts",
+ "create.ponder.chain_gearshift.text_1": "Unpowered Chain Gearshifts behave exactly like Chain Drives",
+ "create.ponder.chain_gearshift.text_2": "When Powered, the speed transmitted to other Chain Drives in the row is doubled",
+ "create.ponder.chain_gearshift.text_3": "Whenever the Powered Gearshift is not at the source, its speed will be halved instead",
+ "create.ponder.chain_gearshift.text_4": "In both cases, Chain Drives in the row always run at 2x the speed of the Powered Gearshift",
+ "create.ponder.chain_gearshift.text_5": "Using analog signals, the ratio can be adjusted more precisely between 1 and 2",
+ "create.ponder.chain_gearshift.text_6": "12 RPM",
+ "create.ponder.chute.header": "Transporting Items downward via Chutes",
+ "create.ponder.chute.text_1": "Chutes can transport items vertically from and to inventories",
+ "create.ponder.chute.text_2": "Using the Wrench, a window can be created",
+ "create.ponder.chute.text_3": "Using Industrial Iron Blocks, chutes can be encased",
+ "create.ponder.chute.text_4": "Placing chutes targeting the side faces of another will make it diagonal",
+ "create.ponder.chute_upward.header": "Transporting Items upward via Chutes",
+ "create.ponder.chute_upward.text_1": "Using Encased Fans at the top or bottom, a Chute can move items upward",
+ "create.ponder.chute_upward.text_2": "Inspecting chutes with Engineers' Goggles reveals information about the movement direction",
+ "create.ponder.chute_upward.text_3": "On the 'blocked' end, items will have to be inserted/taken from the sides",
+ "create.ponder.clockwork_bearing.header": "Animating Structures using Clockwork Bearings",
+ "create.ponder.clockwork_bearing.text_1": "Clockwork Bearings attach to blocks in front of them",
+ "create.ponder.clockwork_bearing.text_2": "Upon receiving Rotational Force, the structure will be rotated according to the hour of the day",
+ "create.ponder.clockwork_bearing.text_3": "3:00",
+ "create.ponder.clockwork_bearing.text_4": "4:00",
+ "create.ponder.clockwork_bearing.text_5": "Right-Click the bearing to start or stop animating the structure",
+ "create.ponder.clockwork_bearing.text_6": "In front of the Hour Hand, a second structure can be added",
+ "create.ponder.clockwork_bearing.text_7": "Ensure that the two Structures are not glued to each other",
+ "create.ponder.clockwork_bearing.text_8": "The Second Structure will now rotate as the Minute Hand",
+ "create.ponder.clutch.header": "Controlling rotational force using a Clutch",
+ "create.ponder.clutch.text_1": "Clutches will relay rotation in a straight line",
+ "create.ponder.clutch.text_2": "When powered by Redstone, it breaks the connection",
+ "create.ponder.cog_speedup.header": "Gearshifting with Cogs",
+ "create.ponder.cog_speedup.text_1": "Large and Small cogs can be connected diagonally",
+ "create.ponder.cog_speedup.text_2": "Shifting from large to small cogs, the conveyed speed will be doubled",
+ "create.ponder.cog_speedup.text_3": "Shifting the opposite way, the conveyed speed will be halved",
+ "create.ponder.cogwheel.header": "Relaying rotational force using Cogwheels",
+ "create.ponder.cogwheel.text_1": "Cogwheels will relay rotation to other adjacent cogwheels",
+ "create.ponder.cogwheel.text_2": "Neighbouring shafts connected like this will rotate in opposite directions",
+ "create.ponder.cogwheel_casing.header": "Encasing Cogwheels",
+ "create.ponder.cogwheel_casing.text_1": "Brass or Andesite Casing can be used to decorate Cogwheels",
+ "create.ponder.cogwheel_casing.text_2": "Components added after encasing will not connect to the shaft outputs",
+ "create.ponder.cogwheel_casing.text_3": "The Wrench can be used to toggle connections",
+ "create.ponder.contraption_controls.header": "Using Contraption Controls",
+ "create.ponder.contraption_controls.text_1": "Actors on moving contraptions are always active by default",
+ "create.ponder.contraption_controls.text_2": "Contraption Controls can be used to toggle them on the fly",
+ "create.ponder.contraption_controls.text_3": "They can be attached anywhere on the contraption",
+ "create.ponder.contraption_controls.text_4": "While disassembled, the filter can be changed to target specific types of actors",
+ "create.ponder.contraption_controls.text_5": "If it is redstone-activated during assembly...",
+ "create.ponder.contraption_controls.text_6": "...targeted actors will be turned off from the start",
+ "create.ponder.creative_fluid_tank.header": "Creative Fluid Tanks",
+ "create.ponder.creative_fluid_tank.text_1": "Creative Fluid Tanks can be used to provide a bottomless supply of fluid",
+ "create.ponder.creative_fluid_tank.text_2": "Right-Click with a fluid containing item to configure it",
+ "create.ponder.creative_fluid_tank.text_3": "Pipe Networks can now endlessly draw the assigned fluid from the tank",
+ "create.ponder.creative_fluid_tank.text_4": "Any Fluids pushed back into a Creative Fluid Tank will be voided",
+ "create.ponder.creative_motor.header": "Generating Rotational Force using Creative Motors",
+ "create.ponder.creative_motor.text_1": "Creative motors are a compact and configurable source of Rotational Force",
+ "create.ponder.creative_motor.text_2": "The generated speed can be configured on its input panels",
+ "create.ponder.creative_motor_mojang.header": "Mojang's Enigma",
+ "create.ponder.crushing_wheels.header": "Processing Items with Crushing Wheels",
+ "create.ponder.crushing_wheels.text_1": "A pair of Crushing Wheels can grind items very effectively",
+ "create.ponder.crushing_wheels.text_2": "Their Rotational Input has to make them spin into each other",
+ "create.ponder.crushing_wheels.text_3": "Items thrown or inserted into the top will get processed",
+ "create.ponder.crushing_wheels.text_4": "Items can be inserted and picked up through automated means as well",
+ "create.ponder.deployer.header": "Using the Deployer",
+ "create.ponder.deployer.text_1": "Given Rotational Force, a Deployer can imitate player interactions",
+ "create.ponder.deployer.text_10": "Right-click the front to give it an Item to use",
+ "create.ponder.deployer.text_11": "Items can also be inserted automatically",
+ "create.ponder.deployer.text_12": "Deployers carry a filter slot",
+ "create.ponder.deployer.text_13": "When a filter is set, it activates only while holding a matching item",
+ "create.ponder.deployer.text_14": "Only items matching the filter can now be inserted...",
+ "create.ponder.deployer.text_15": "...and only non-matching items will be extracted",
+ "create.ponder.deployer.text_2": "It will always interact with the position 2 blocks in front of itself",
+ "create.ponder.deployer.text_3": "Blocks directly in front will not obstruct it",
+ "create.ponder.deployer.text_4": "Deployers can:",
+ "create.ponder.deployer.text_5": "Place Blocks,",
+ "create.ponder.deployer.text_6": "Use Items,",
+ "create.ponder.deployer.text_7": "Activate Blocks,",
+ "create.ponder.deployer.text_8": "Harvest blocks",
+ "create.ponder.deployer.text_9": "and Attack Mobs",
+ "create.ponder.deployer_contraption.header": "Using Deployers on Contraptions",
+ "create.ponder.deployer_contraption.text_1": "Whenever Deployers are moved as part of an animated Contraption...",
+ "create.ponder.deployer_contraption.text_2": "They activate at each visited location, using items from inventories anywhere on the contraption",
+ "create.ponder.deployer_contraption.text_3": "The Filter slot can be used to specify which items to pull",
+ "create.ponder.deployer_modes.header": "Modes of the Deployer",
+ "create.ponder.deployer_modes.text_1": "By default, a Deployer imitates a Right-click interaction",
+ "create.ponder.deployer_modes.text_2": "Using a Wrench, it can be set to imitate a Left-click instead",
+ "create.ponder.deployer_processing.header": "Processing Items using Deployers",
+ "create.ponder.deployer_processing.text_1": "With a fitting held item, Deployers can process items provided beneath them",
+ "create.ponder.deployer_processing.text_2": "The Input items can be dropped or placed on a Depot under the Deployer",
+ "create.ponder.deployer_processing.text_3": "When items are provided on a belt...",
+ "create.ponder.deployer_processing.text_4": "The Deployer will hold and process them automatically",
+ "create.ponder.deployer_redstone.header": "Controlling Deployers with Redstone",
+ "create.ponder.deployer_redstone.text_1": "When powered by Redstone, Deployers will not activate",
+ "create.ponder.deployer_redstone.text_2": "Before stopping, the Deployer will finish any started cycles",
+ "create.ponder.deployer_redstone.text_3": "Thus, a negative pulse can be used to trigger exactly one activation cycle",
+ "create.ponder.depot.header": "Using Depots",
+ "create.ponder.depot.text_1": "Depots can serve as 'stationary' belt elements",
+ "create.ponder.depot.text_2": "Right-Click to manually place or remove Items from it",
+ "create.ponder.depot.text_3": "Just like Mechanical Belts, it can provide items to processing",
+ "create.ponder.depot.text_4": "...as well as provide Items to Mechanical Arms",
+ "create.ponder.display_board.header": "Using Display Boards",
+ "create.ponder.display_board.text_1": "Display Boards are a scalable alternative to the sign",
+ "create.ponder.display_board.text_2": "They require Rotational Force to operate",
+ "create.ponder.display_board.text_3": "Static text can be applied using written Clipboards",
+ "create.ponder.display_board.text_4": "And dynamic text through the use of Display Links",
+ "create.ponder.display_board.text_5": "Dyes can be applied to individual lines of the board",
+ "create.ponder.display_board.text_6": "Lines can be reset by clicking them with an empty hand",
+ "create.ponder.display_link.header": "Setting up Display Links",
+ "create.ponder.display_link.text_1": "Display Links can be used to visualise dynamic information",
+ "create.ponder.display_link.text_2": "First, right-click the target display...",
+ "create.ponder.display_link.text_3": "...then attach it to the block to read from",
+ "create.ponder.display_link.text_4": "Open the Interface to select and configure what is sent",
+ "create.ponder.display_link.text_5": "The display will now receive information from the link",
+ "create.ponder.display_link.text_6": "Not every block can act as a source",
+ "create.ponder.display_link.text_7": "Each compatible block provides unique information",
+ "create.ponder.display_link.text_8": "The Display Link can work with several different displays",
+ "create.ponder.display_link_redstone.header": "Redstone Control",
+ "create.ponder.display_link_redstone.text_1": "When powered by Redstone, Display Links stop sending updates",
+ "create.ponder.display_link_redstone.text_2": "Once unpowered, the Timer is reset and new info is sent immediately",
+ "create.ponder.display_link_redstone.text_3": "Signals emitted from the source do not affect the Link",
+ "create.ponder.elevator_pulley.header": "Using the Elevator Pulley",
+ "create.ponder.elevator_pulley.text_1": "Elevator Pulleys can move structures vertically between marked locations",
+ "create.ponder.elevator_pulley.text_10": "Any redstone contact sharing this column will be converted",
+ "create.ponder.elevator_pulley.text_11": "Supply a redstone pulse to call the elevator to the contact",
+ "create.ponder.elevator_pulley.text_12": "The movement speed depends on the rotation input on the pulley",
+ "create.ponder.elevator_pulley.text_13": "Scroll and click on the controls block to choose a floor while on-board",
+ "create.ponder.elevator_pulley.text_14": "Right-Clicking the assembled pulley will turn the cabin back into blocks",
+ "create.ponder.elevator_pulley.text_15": "Sliding doors attached to the cabin will open and close automatically",
+ "create.ponder.elevator_pulley.text_16": "Elevator Contacts emit a signal while the cabin is on their floor",
+ "create.ponder.elevator_pulley.text_17": "This can be useful to trigger doors or special effects upon arrival",
+ "create.ponder.elevator_pulley.text_18": "Display Links on any of the contacts can show the current floor of the elevator",
+ "create.ponder.elevator_pulley.text_2": "Start by constructing a cabin",
+ "create.ponder.elevator_pulley.text_3": "Place a pair of Redstone Contacts facing each other...",
+ "create.ponder.elevator_pulley.text_4": "...and glue one of them to your moving structure",
+ "create.ponder.elevator_pulley.text_5": "Contraption Controls can be attached to make floor selection easier",
+ "create.ponder.elevator_pulley.text_6": "Ensure that the pulley is supplied with Rotational Power",
+ "create.ponder.elevator_pulley.text_7": "Right-Clicking the pulley assembles the elevator",
+ "create.ponder.elevator_pulley.text_8": "The stationary contact now turns into an Elevator Contact",
+ "create.ponder.elevator_pulley.text_9": "Elevator Contacts represent a 'floor' and can be configured",
+ "create.ponder.elevator_pulley_multi_rope.header": "Synchronised Pulley Movement",
+ "create.ponder.elevator_pulley_multi_rope.text_1": "Whenever a pulley assembles a contraption...",
+ "create.ponder.elevator_pulley_multi_rope.text_2": "...other pulleys on the same layer will connect to the structure",
+ "create.ponder.elevator_pulley_multi_rope.text_3": "They do not require to be powered, the effect is purely cosmetic",
+ "create.ponder.empty_blaze_burner.header": "Using Empty Blaze Burners",
+ "create.ponder.empty_blaze_burner.text_1": "Right-click a Blaze with the empty burner to capture it",
+ "create.ponder.empty_blaze_burner.text_2": "Alternatively, Blazes can be collected from their Spawners directly",
+ "create.ponder.empty_blaze_burner.text_3": "You now have an ideal heat source for various machines",
+ "create.ponder.empty_blaze_burner.text_4": "For Aesthetic purposes, Empty Blaze Burners can also be lit using Flint and Steel",
+ "create.ponder.empty_blaze_burner.text_5": "The flame can be transformed using a soul-infused item",
+ "create.ponder.empty_blaze_burner.text_6": "However, without a blaze they are not suitable for industrial heating",
+ "create.ponder.encased_fluid_pipe.header": "Encasing Fluid Pipes",
+ "create.ponder.encased_fluid_pipe.text_1": "Copper Casing can be used to decorate Fluid Pipes",
+ "create.ponder.encased_fluid_pipe.text_2": "Aside from being concealed, Encased Pipes are locked into their connectivity state",
+ "create.ponder.encased_fluid_pipe.text_3": "It will no longer react to any neighbouring blocks being added or removed",
+ "create.ponder.factory_gauge_crafting.header": "Automatic Crafting with Factory Gauges",
+ "create.ponder.factory_gauge_crafting.text_1": "Factory gauges provide auto-arrangement for crafting table recipes",
+ "create.ponder.factory_gauge_crafting.text_2": "Connect the required ingredients as before",
+ "create.ponder.factory_gauge_crafting.text_3": "When a valid recipe is detected, a new button appears in the UI",
+ "create.ponder.factory_gauge_crafting.text_4": "With auto-arrangement active, the boxes can be unwrapped into crafters directly",
+ "create.ponder.factory_gauge_crafting.text_5": "The setup must be 3x3 and the crafters have to be connected via wrench",
+ "create.ponder.factory_gauge_crafting.text_6": "Hold Right-click on the gauge to set the target amount",
+ "create.ponder.factory_gauge_crafting.text_7": "This crafter can now be used universally, by more gauges with different recipes",
+ "create.ponder.factory_gauge_crafting.text_8": "Outputs should be sent back to a linked inventory to close the loop",
+ "create.ponder.factory_gauge_crafting.text_9": "Using a Re-packager is recommended to prevent fragmentation of input packages",
+ "create.ponder.factory_gauge_links.header": "Connecting Gauges to other Blocks",
+ "create.ponder.factory_gauge_links.text_1": "When adding a new connection from the UI...",
+ "create.ponder.factory_gauge_links.text_2": "...the gauge also accepts Redstone and Display Links",
+ "create.ponder.factory_gauge_links.text_3": "Redstone links will be powered when the stock level is at or above the target amount",
+ "create.ponder.factory_gauge_links.text_4": "In receiver mode, links can stop the gauge from sending requests",
+ "create.ponder.factory_gauge_links.text_5": "Display links can provide a status overview of connected gauges",
+ "create.ponder.factory_gauge_recipe.header": "Automated Recipes with Factory Gauges",
+ "create.ponder.factory_gauge_recipe.text_1": "Whenever gauges are not placed on a packager...",
+ "create.ponder.factory_gauge_recipe.text_10": "Specify the address that ingredients should be sent to",
+ "create.ponder.factory_gauge_recipe.text_11": "The target amount to maintain can now be set by holding Right-click on the gauge",
+ "create.ponder.factory_gauge_recipe.text_12": "Whenever the network has fewer items than the amount...",
+ "create.ponder.factory_gauge_recipe.text_13": "...it will send new ingredients to the specified address",
+ "create.ponder.factory_gauge_recipe.text_14": "The outputs then need to return to any of the linked inventories",
+ "create.ponder.factory_gauge_recipe.text_15": "Green connections indicate that the target amount has been reached",
+ "create.ponder.factory_gauge_recipe.text_16": "The board of gauges can expand to include more recipe steps",
+ "create.ponder.factory_gauge_recipe.text_17": "Each gauge maintains the stock level of its item independently",
+ "create.ponder.factory_gauge_recipe.text_2": "They will instead monitor stock levels of all linked inventories",
+ "create.ponder.factory_gauge_recipe.text_3": "Right-click it with the item that should be monitored",
+ "create.ponder.factory_gauge_recipe.text_4": "It will now display the total amount present on the network",
+ "create.ponder.factory_gauge_recipe.text_5": "The gauge can replenish stock levels by sending other items to be processed",
+ "create.ponder.factory_gauge_recipe.text_6": "First, add the required ingredients as new factory gauges",
+ "create.ponder.factory_gauge_recipe.text_7": "From the target's UI, new connections can be made",
+ "create.ponder.factory_gauge_recipe.text_8": "For aesthetics, input panels can be wrenched to change the pathing",
+ "create.ponder.factory_gauge_recipe.text_9": "In the UI, review the inputs and specify how much of the output gets made per batch",
+ "create.ponder.factory_gauge_restocker.header": "Restocking with Factory Gauges",
+ "create.ponder.factory_gauge_restocker.text_1": "Right-click a Stock link before placement to connect to its network",
+ "create.ponder.factory_gauge_restocker.text_10": "...the logistics network sends more, with the specified address",
+ "create.ponder.factory_gauge_restocker.text_11": "From there, they can be routed to the packager",
+ "create.ponder.factory_gauge_restocker.text_2": "When placed on a packager, factory gauges can monitor items inside the inventory",
+ "create.ponder.factory_gauge_restocker.text_3": "Right-click it with the item that should be monitored",
+ "create.ponder.factory_gauge_restocker.text_4": "It will now display the amount present in the inventory",
+ "create.ponder.factory_gauge_restocker.text_5": "The gauge can refill this inventory from the logistics network",
+ "create.ponder.factory_gauge_restocker.text_6": "Right-click it again to open its configuration UI",
+ "create.ponder.factory_gauge_restocker.text_7": "Set an address that should be used for the requested items",
+ "create.ponder.factory_gauge_restocker.text_8": "The target amount to maintain can now be set by holding Right-click on the gauge",
+ "create.ponder.factory_gauge_restocker.text_9": "Whenever the chest has fewer items than this amount...",
+ "create.ponder.fan_direction.header": "Air flow of Encased Fans",
+ "create.ponder.fan_direction.text_1": "Encased Fans use Rotational Force to create an Air Current",
+ "create.ponder.fan_direction.text_2": "Strength and Direction of Flow depends on the Rotational Input",
+ "create.ponder.fan_processing.header": "Processing Items using Encased Fans",
+ "create.ponder.fan_processing.text_1": "When passing through lava, the Air Flow becomes Heated",
+ "create.ponder.fan_processing.text_2": "Items caught in the area will be smelted",
+ "create.ponder.fan_processing.text_3": "Food items thrown here would be incinerated",
+ "create.ponder.fan_processing.text_4": "Instead, a setup for Smoking using Fire should be used for them",
+ "create.ponder.fan_processing.text_5": "Air Flows passing through water create a Washing Setup",
+ "create.ponder.fan_processing.text_6": "Some interesting new processing can be done with it",
+ "create.ponder.fan_processing.text_7": "The Speed of the Fan does NOT affect the processing speed, only its range",
+ "create.ponder.fan_processing.text_8": "Fan Processing can also be applied to Items on Depots and Belts",
+ "create.ponder.fluid_pipe_flow.header": "Moving Fluids using Copper Pipes",
+ "create.ponder.fluid_pipe_flow.text_1": "Fluid Pipes can connect two or more fluid sources and targets",
+ "create.ponder.fluid_pipe_flow.text_2": "Using a wrench, a straight pipe segment can be given a window",
+ "create.ponder.fluid_pipe_flow.text_3": "Windowed pipes will not connect to any other adjacent pipe segments",
+ "create.ponder.fluid_pipe_flow.text_4": "Powered by Mechanical Pumps, the Pipes can transport Fluids",
+ "create.ponder.fluid_pipe_flow.text_5": "No fluid is being extracted at first",
+ "create.ponder.fluid_pipe_flow.text_6": "Once the flow connects them, the endpoints gradually transfer their contents",
+ "create.ponder.fluid_pipe_flow.text_7": "Thus, the Pipe blocks themselves never 'physically' contain any fluid",
+ "create.ponder.fluid_pipe_interaction.header": "Draining and Filling fluid containers",
+ "create.ponder.fluid_pipe_interaction.text_1": "Endpoints of a pipe network can interact with a variety of blocks",
+ "create.ponder.fluid_pipe_interaction.text_2": "Any block with fluid storage capabilities can be filled or drained",
+ "create.ponder.fluid_pipe_interaction.text_3": "Source blocks right in front of an open end can be picked up...",
+ "create.ponder.fluid_pipe_interaction.text_4": "...while spilling into empty spaces can create fluid sources",
+ "create.ponder.fluid_pipe_interaction.text_5": "Pipes can also extract fluids from a handful of other blocks directly",
+ "create.ponder.fluid_tank_sizes.header": "Dimensions of a Fluid tank",
+ "create.ponder.fluid_tank_sizes.text_1": "Fluid Tanks can be combined to increase the total capacity",
+ "create.ponder.fluid_tank_sizes.text_2": "Their base square can be up to 3 blocks wide...",
+ "create.ponder.fluid_tank_sizes.text_3": "...and grow in height by more than 30 additional layers",
+ "create.ponder.fluid_tank_sizes.text_4": "Using a Wrench, a tanks' window can be toggled",
+ "create.ponder.fluid_tank_storage.header": "Storing Fluids in Fluid Tanks",
+ "create.ponder.fluid_tank_storage.text_1": "Fluid Tanks can be used to store large amounts of fluid",
+ "create.ponder.fluid_tank_storage.text_2": "Pipe networks can push and pull fluids from any side",
+ "create.ponder.fluid_tank_storage.text_3": "The contained fluid can be measured by a Comparator",
+ "create.ponder.fluid_tank_storage.text_4": "However, in Survival Mode Fluids cannot be added or taken manually",
+ "create.ponder.fluid_tank_storage.text_5": "You can use Basins, Item Drains and Spouts to drain or fill fluid containing items",
+ "create.ponder.funnel_compat.header": "Funnel compatibility",
+ "create.ponder.funnel_compat.text_1": "Funnels should also interact nicely with a handful of other components.",
+ "create.ponder.funnel_compat.text_2": "Vertical Saws",
+ "create.ponder.funnel_compat.text_3": "Depots",
+ "create.ponder.funnel_compat.text_4": "Item Drains",
+ "create.ponder.funnel_direction.header": "Direction of Transfer",
+ "create.ponder.funnel_direction.text_1": "Placed normally, it pulls items from the inventory.",
+ "create.ponder.funnel_direction.text_2": "Placed while sneaking, it puts items into the inventory.",
+ "create.ponder.funnel_direction.text_3": "Using a wrench, the funnel can be flipped after placement.",
+ "create.ponder.funnel_direction.text_4": "Same rules will apply for most orientations.",
+ "create.ponder.funnel_direction.text_5": "Funnels on belts will extract/insert depending on its movement direction.",
+ "create.ponder.funnel_intro.header": "Using funnels",
+ "create.ponder.funnel_intro.text_1": "Funnels are ideal for transferring items from and to inventories.",
+ "create.ponder.funnel_redstone.header": "Redstone control",
+ "create.ponder.funnel_redstone.text_1": "Redstone power will prevent any funnel from acting",
+ "create.ponder.funnel_transfer.header": "Direct transfer",
+ "create.ponder.funnel_transfer.text_1": "Funnels cannot ever transfer between closed inventories directly.",
+ "create.ponder.funnel_transfer.text_2": "Chutes or Smart chutes might be more suitable for such purposes.",
+ "create.ponder.funnel_transfer.text_3": "Same applies for horizontal movement. A mechanical belt should help here.",
+ "create.ponder.gantry_carriage.header": "Using Gantry Carriages",
+ "create.ponder.gantry_carriage.text_1": "Gantry Carriages can mount to and slide along a Gantry Shaft.",
+ "create.ponder.gantry_carriage.text_2": "Gantry setups can move attached Blocks.",
+ "create.ponder.gantry_cascaded.header": "Cascaded Gantries",
+ "create.ponder.gantry_cascaded.text_1": "Gantry shafts attach to a carriage without the need of super glue",
+ "create.ponder.gantry_cascaded.text_2": "Same applies for carriages on moved Gantry Shafts",
+ "create.ponder.gantry_cascaded.text_3": "Thus, a gantry system can be cascaded to cover multiple axes of movement",
+ "create.ponder.gantry_direction.header": "Gantry Movement Direction",
+ "create.ponder.gantry_direction.text_1": "Gantry Shafts can have opposite orientations",
+ "create.ponder.gantry_direction.text_2": "The movement direction of carriages depend on their shafts' orientation",
+ "create.ponder.gantry_direction.text_3": "...as well as the rotation direction of the shaft",
+ "create.ponder.gantry_direction.text_4": "Same rules apply for the propagated rotation",
+ "create.ponder.gantry_redstone.header": "Gantry Power Propagation",
+ "create.ponder.gantry_redstone.text_1": "Redstone-powered gantry shafts stop moving their carriages",
+ "create.ponder.gantry_redstone.text_2": "Instead, its rotational force is relayed to the carriages' output shaft",
+ "create.ponder.gantry_shaft.header": "Using Gantry Shafts",
+ "create.ponder.gantry_shaft.text_1": "Gantry Shafts form the basis of a gantry setup. Attached Carriages will move along them.",
+ "create.ponder.gantry_shaft.text_2": "Gantry setups can move attached Blocks.",
+ "create.ponder.gearbox.header": "Relaying rotational force using Gearboxes",
+ "create.ponder.gearbox.text_1": "Jumping between axes of rotation can get bulky quickly",
+ "create.ponder.gearbox.text_2": "A gearbox is the more compact equivalent of this setup",
+ "create.ponder.gearbox.text_3": "Shafts around corners rotate in mirrored directions",
+ "create.ponder.gearbox.text_4": "Straight connections will be reversed",
+ "create.ponder.gearshift.header": "Controlling rotational force using a Gearshift",
+ "create.ponder.gearshift.text_1": "Gearshifts will relay rotation in a straight line",
+ "create.ponder.gearshift.text_2": "When powered by Redstone, it reverses the transmission",
+ "create.ponder.hand_crank.header": "Generating Rotational Force using Hand Cranks",
+ "create.ponder.hand_crank.text_1": "Hand Cranks can be used by players to apply rotational force manually",
+ "create.ponder.hand_crank.text_2": "Hold Right-Click to rotate it Counter-Clockwise",
+ "create.ponder.hand_crank.text_3": "Sneak and Hold Right-Click to rotate it Clockwise",
+ "create.ponder.hose_pulley.header": "Source Filling and Draining using Hose Pulleys",
+ "create.ponder.hose_pulley.text_1": "Hose Pulleys can be used to fill or drain large bodies of Fluid",
+ "create.ponder.hose_pulley.text_2": "With the Kinetic Input, the height of the pulleys' hose can be controlled",
+ "create.ponder.hose_pulley.text_3": "The Pulley retracts while the input rotation is inverted",
+ "create.ponder.hose_pulley.text_4": "On the opposite side, pipes can be connected",
+ "create.ponder.hose_pulley.text_5": "Attached pipe networks can either provide fluid to the hose...",
+ "create.ponder.hose_pulley.text_6": "...or pull from it, draining the pool instead",
+ "create.ponder.hose_pulley.text_7": "Fill and Drain speed of the pulley depends entirely on the fluid networks' throughput",
+ "create.ponder.hose_pulley_infinite.header": "Passively Filling and Draining large bodies of Fluid",
+ "create.ponder.hose_pulley_infinite.text_1": "When deploying the Hose Pulley into a large enough ocean...",
+ "create.ponder.hose_pulley_infinite.text_2": "It will provide/dispose fluids without affecting the source",
+ "create.ponder.hose_pulley_infinite.text_3": "Pipe networks can limitlessly take fluids from/to such pulleys",
+ "create.ponder.hose_pulley_level.header": "Fill and Drain level of Hose Pulleys",
+ "create.ponder.hose_pulley_level.text_1": "While fully retracted, the Hose Pulley cannot operate",
+ "create.ponder.hose_pulley_level.text_2": "Draining runs from top to bottom",
+ "create.ponder.hose_pulley_level.text_3": "The surface level will end up just below where the hose ends",
+ "create.ponder.hose_pulley_level.text_4": "Filling runs from bottom to top",
+ "create.ponder.hose_pulley_level.text_5": "The filled pool will not grow beyond the layer above the hose end",
+ "create.ponder.item_drain.header": "Emptying Fluid Containers using Item Drains",
+ "create.ponder.item_drain.text_1": "Item Drains can extract fluids from items",
+ "create.ponder.item_drain.text_2": "Right-click it to pour fluids from your held item into it",
+ "create.ponder.item_drain.text_3": "When items are inserted from the side...",
+ "create.ponder.item_drain.text_4": "...they roll across, emptying out their contained fluid",
+ "create.ponder.item_drain.text_5": "Pipe Networks can now pull the fluid from the drains' internal buffer",
+ "create.ponder.item_vault_sizes.header": "Dimensions of an Item Vault",
+ "create.ponder.item_vault_sizes.text_1": "Item Vaults can be combined to increase the total capacity",
+ "create.ponder.item_vault_sizes.text_2": "Their base square can be up to 3 blocks wide...",
+ "create.ponder.item_vault_sizes.text_3": "...and grow in length up to 3x their diameter",
+ "create.ponder.item_vault_storage.header": "Storing Items in Vaults",
+ "create.ponder.item_vault_storage.text_1": "Item Vaults can be used to store large amounts of items",
+ "create.ponder.item_vault_storage.text_2": "However, contents cannot be added or taken manually",
+ "create.ponder.item_vault_storage.text_3": "Any components for item transfer can both insert...",
+ "create.ponder.item_vault_storage.text_4": "...and take contents from this container",
+ "create.ponder.large_cogwheel.header": "Relaying rotational force using Large Cogwheels",
+ "create.ponder.large_cogwheel.text_1": "Large cogwheels can connect to each other at right angles",
+ "create.ponder.large_cogwheel.text_2": "It will help relaying conveyed speed to other axes of rotation",
+ "create.ponder.large_water_wheel.header": "Generating Rotational Force using Large Water Wheels",
+ "create.ponder.large_water_wheel.text_1": "Large Water Wheels draw force from adjacent Water Currents",
+ "create.ponder.large_water_wheel.text_2": "Covering additional sides will not improve its kinetic output further",
+ "create.ponder.large_water_wheel.text_3": "These rotate only at half the speed of regular water wheels...",
+ "create.ponder.large_water_wheel.text_4": "...but provide a substantially higher stress capacity",
+ "create.ponder.large_water_wheel.text_5": "Use wood planks on the wheel to change its appearance",
+ "create.ponder.linear_chassis_attachment.header": "Attaching blocks using Linear Chassis",
+ "create.ponder.linear_chassis_attachment.text_1": "The open faces of a Linear Chassis can be made Sticky",
+ "create.ponder.linear_chassis_attachment.text_2": "Click again to make the opposite side sticky",
+ "create.ponder.linear_chassis_attachment.text_3": "Sneak and Right-Click with an empty hand to remove the slime",
+ "create.ponder.linear_chassis_attachment.text_4": "Stickied faces of the Linear Chassis will attach a line of blocks in front of it",
+ "create.ponder.linear_chassis_attachment.text_5": "Using a Wrench, a precise Range can be specified for this chassis",
+ "create.ponder.linear_chassis_attachment.text_6": "Holding CTRL adjusts the range of all connected Chassis Blocks",
+ "create.ponder.linear_chassis_attachment.text_7": "Attaching blocks to any other side requires the use of Super Glue",
+ "create.ponder.linear_chassis_attachment.text_8": "Using these mechanics, structures of any shape can move as a Contraption",
+ "create.ponder.linear_chassis_group.header": "Moving Linear Chassis in groups",
+ "create.ponder.linear_chassis_group.text_1": "Linear Chassis connect to identical Chassis blocks next to them",
+ "create.ponder.linear_chassis_group.text_2": "When one is moved by a Contraption, the others are dragged with it",
+ "create.ponder.linear_chassis_group.text_3": "Chassis of a different type or facing another direction will not attach",
+ "create.ponder.mechanical_arm.header": "Setting up Mechanical Arms",
+ "create.ponder.mechanical_arm.text_1": "Mechanical Arms have to be assigned their in- and outputs before they are placed",
+ "create.ponder.mechanical_arm.text_2": "Right-Click inventories while holding the Arm to assign them as Targets",
+ "create.ponder.mechanical_arm.text_3": "Right-Click again to toggle between Input (Blue) and Output (Yellow)",
+ "create.ponder.mechanical_arm.text_4": "Left-Click components to remove their Selection",
+ "create.ponder.mechanical_arm.text_5": "Once placed, the Mechanical Arm will target the blocks selected previously",
+ "create.ponder.mechanical_arm.text_6": "They can have any amount of in- and outputs within their range",
+ "create.ponder.mechanical_arm.text_7": "However, not every type of Inventory can be interacted with directly",
+ "create.ponder.mechanical_arm.text_8": "Funnels and Depots can help to Bridge that gap",
+ "create.ponder.mechanical_arm_filtering.header": "Filtering Outputs of the Mechanical Arm",
+ "create.ponder.mechanical_arm_filtering.text_1": "Inputs",
+ "create.ponder.mechanical_arm_filtering.text_2": "Outputs",
+ "create.ponder.mechanical_arm_filtering.text_3": "Sometimes it is desirable to restrict targets of the Arm by matching a filter",
+ "create.ponder.mechanical_arm_filtering.text_4": "Mechanical Arms by themselves do not provide any options for filtering",
+ "create.ponder.mechanical_arm_filtering.text_5": "Brass Funnels as Targets do however communicate their own filter to the Arm",
+ "create.ponder.mechanical_arm_filtering.text_6": "The Arm is smart enough not to pick up items it couldn't distribute",
+ "create.ponder.mechanical_arm_modes.header": "Distribution modes of the Mechanical Arm",
+ "create.ponder.mechanical_arm_modes.text_1": "Input",
+ "create.ponder.mechanical_arm_modes.text_2": "Outputs",
+ "create.ponder.mechanical_arm_modes.text_3": "Whenever an Arm has to choose between multiple valid outputs...",
+ "create.ponder.mechanical_arm_modes.text_4": "...it will act according to its setting",
+ "create.ponder.mechanical_arm_modes.text_5": "The value panel will allow you to configure it",
+ "create.ponder.mechanical_arm_modes.text_6": "Round Robin mode simply cycles through all outputs that are available",
+ "create.ponder.mechanical_arm_modes.text_7": "If an output is unable to take more items, it will be skipped",
+ "create.ponder.mechanical_arm_modes.text_8": "Forced Round Robin mode will never skip outputs, and instead wait until they are free",
+ "create.ponder.mechanical_arm_modes.text_9": "Prefer First prioritizes the outputs selected earliest when configuring this Arm",
+ "create.ponder.mechanical_arm_redstone.header": "Controlling Mechanical Arms with Redstone",
+ "create.ponder.mechanical_arm_redstone.text_1": "When powered by Redstone, Mechanical Arms will not activate",
+ "create.ponder.mechanical_arm_redstone.text_2": "Before stopping, it will finish any started cycles",
+ "create.ponder.mechanical_arm_redstone.text_3": "Thus, a negative pulse can be used to trigger exactly one activation cycle",
+ "create.ponder.mechanical_bearing.header": "Moving Structures using the Mechanical Bearing",
+ "create.ponder.mechanical_bearing.text_1": "Mechanical Bearings attach to the block in front of them",
+ "create.ponder.mechanical_bearing.text_2": "Upon receiving Rotational Force, it will assemble it into a Rotating Contraption",
+ "create.ponder.mechanical_crafter.header": "Setting up Mechanical Crafters",
+ "create.ponder.mechanical_crafter.text_1": "An array of Mechanical Crafters can be used to automate any Crafting Recipe",
+ "create.ponder.mechanical_crafter.text_2": "Using a Wrench, the Crafters' paths can be arranged",
+ "create.ponder.mechanical_crafter.text_3": "For a valid setup, all paths have to converge into one exit at any side",
+ "create.ponder.mechanical_crafter.text_4": "The outputs will be placed into the inventory at the exit",
+ "create.ponder.mechanical_crafter.text_5": "Mechanical Crafters require Rotational Force to operate",
+ "create.ponder.mechanical_crafter.text_6": "Right-Click the front to insert Items manually",
+ "create.ponder.mechanical_crafter.text_7": "Once every slot of a path contains an Item, the crafting process will begin",
+ "create.ponder.mechanical_crafter.text_8": "For recipes not fully occupying the crafter setup, the start can be forced using a Redstone Pulse",
+ "create.ponder.mechanical_crafter_connect.header": "Connecting Inventories of Crafters",
+ "create.ponder.mechanical_crafter_connect.text_1": "Items can be inserted to Crafters automatically",
+ "create.ponder.mechanical_crafter_connect.text_2": "Using the Wrench at their backs, Mechanical Crafter inputs can be combined",
+ "create.ponder.mechanical_crafter_connect.text_3": "All connected Crafters can now be accessed by the same input location",
+ "create.ponder.mechanical_crafter_covers.header": "Covering slots of Mechanical Crafters",
+ "create.ponder.mechanical_crafter_covers.text_1": "Some recipes will require additional Crafters to bridge gaps in the path",
+ "create.ponder.mechanical_crafter_covers.text_2": "Using Slot Covers, Crafters can be set to act as an Empty Slot in the arrangement",
+ "create.ponder.mechanical_crafter_covers.text_3": "Shared Inputs created with the Wrench at the back can also reach across covered Crafters",
+ "create.ponder.mechanical_drill.header": "Breaking Blocks with the Mechanical Drill",
+ "create.ponder.mechanical_drill.text_1": "When given Rotational Force, a Mechanical Drill will break blocks directly in front of it",
+ "create.ponder.mechanical_drill.text_2": "Its mining speed depends on the Rotational Input",
+ "create.ponder.mechanical_drill_contraption.header": "Using Mechanical Drills on Contraptions",
+ "create.ponder.mechanical_drill_contraption.text_1": "Whenever Drills are moved as part of an animated Contraption...",
+ "create.ponder.mechanical_drill_contraption.text_2": "...they will break blocks the contraption runs them into",
+ "create.ponder.mechanical_harvester.header": "Using Mechanical Harvesters on Contraptions",
+ "create.ponder.mechanical_harvester.text_1": "Whenever Harvesters are moved as part of an animated Contraption...",
+ "create.ponder.mechanical_harvester.text_2": "They will harvest and reset any mature crops on their way",
+ "create.ponder.mechanical_mixer.header": "Processing Items with the Mechanical Mixer",
+ "create.ponder.mechanical_mixer.text_1": "With a Mixer and Basin, some Crafting Recipes can be automated",
+ "create.ponder.mechanical_mixer.text_2": "Available recipes include any Shapeless Crafting Recipe, plus a couple extra ones",
+ "create.ponder.mechanical_mixer.text_3": "Some of those recipes may require the heat of a Blaze Burner",
+ "create.ponder.mechanical_mixer.text_4": "The filter slot can be used in case two recipes are conflicting.",
+ "create.ponder.mechanical_piston.header": "Moving Structures using Mechanical Pistons",
+ "create.ponder.mechanical_piston.text_1": "Mechanical Pistons can move blocks in front of them",
+ "create.ponder.mechanical_piston.text_2": "Speed and direction of movement depend on the Rotational Input",
+ "create.ponder.mechanical_piston.text_3": "Sticky Mechanical Pistons can pull the attached blocks back",
+ "create.ponder.mechanical_piston_modes.header": "Movement Modes of the Mechanical Piston",
+ "create.ponder.mechanical_piston_modes.text_1": "Whenever Pistons stop moving, the moved structure reverts to blocks",
+ "create.ponder.mechanical_piston_modes.text_2": "It can be configured never to revert to solid blocks, or only at the location it started at",
+ "create.ponder.mechanical_plough.header": "Using Mechanical Ploughs on Contraptions",
+ "create.ponder.mechanical_plough.text_1": "Whenever Ploughs are moved as part of an animated Contraption...",
+ "create.ponder.mechanical_plough.text_2": "...they will break blocks without a solid collision hitbox",
+ "create.ponder.mechanical_plough.text_3": "Additionally, ploughs can create farmland",
+ "create.ponder.mechanical_plough.text_4": "...they can also launch entities without hurting them",
+ "create.ponder.mechanical_press.header": "Processing Items with the Mechanical Press",
+ "create.ponder.mechanical_press.text_1": "The Mechanical Press can process items provided beneath it",
+ "create.ponder.mechanical_press.text_2": "The Input items can be dropped or placed on a Depot under the Press",
+ "create.ponder.mechanical_press.text_3": "When items are provided on a belt...",
+ "create.ponder.mechanical_press.text_4": "The Press will hold and process them automatically",
+ "create.ponder.mechanical_press_compacting.header": "Compacting items with the Mechanical Press",
+ "create.ponder.mechanical_press_compacting.text_1": "Pressing items held in a Basin will cause them to be Compacted",
+ "create.ponder.mechanical_press_compacting.text_2": "Compacting includes any filled 2x2 or 3x3 Crafting Recipe, plus a couple extra ones",
+ "create.ponder.mechanical_press_compacting.text_3": "Some of those recipes may require the heat of a Blaze Burner",
+ "create.ponder.mechanical_press_compacting.text_4": "The filter slot can be used in case two recipes are conflicting.",
+ "create.ponder.mechanical_pump_flow.header": "Fluid Transportation using Mechanical Pumps",
+ "create.ponder.mechanical_pump_flow.text_1": "Mechanical Pumps govern the flow of their attached pipe networks",
+ "create.ponder.mechanical_pump_flow.text_2": "Their arrow indicates the direction of flow",
+ "create.ponder.mechanical_pump_flow.text_3": "The network behind is now pulling fluids...",
+ "create.ponder.mechanical_pump_flow.text_4": "...while the network in front is transferring it outward",
+ "create.ponder.mechanical_pump_flow.text_5": "The pumps direction is unaffected by the input rotation",
+ "create.ponder.mechanical_pump_flow.text_6": "Instead, a Wrench can be used to reverse the direction",
+ "create.ponder.mechanical_pump_speed.header": "Throughput of Mechanical Pumps",
+ "create.ponder.mechanical_pump_speed.text_1": "Regardless of speed, Mechanical Pumps affect pipes connected up to 16 blocks away",
+ "create.ponder.mechanical_pump_speed.text_2": "Speeding up the input rotation changes the speed of flow propagation...",
+ "create.ponder.mechanical_pump_speed.text_3": "...and the speed at which fluids are transferred",
+ "create.ponder.mechanical_pump_speed.text_4": "Pumps can combine their throughputs within shared pipe networks",
+ "create.ponder.mechanical_pump_speed.text_5": "Ensure that all of them are facing in the same direction",
+ "create.ponder.mechanical_roller_fill.header": "Filling terrain with the Roller",
+ "create.ponder.mechanical_roller_fill.text_1": "While disassembled, rollers can be set to other modes",
+ "create.ponder.mechanical_roller_fill.text_2": "The 'fill' modes can help to bridge gaps between pavement and terrain",
+ "create.ponder.mechanical_roller_fill.text_3": "On 'straight fill', they will place simple columns down to the surface",
+ "create.ponder.mechanical_roller_fill.text_4": "On 'sloped fill', layers placed further down will increase in size",
+ "create.ponder.mechanical_roller_fill.text_5": "As opposed to 'clear & pave', neither of these modes will cause the rollers to break existing blocks",
+ "create.ponder.mechanical_roller_pave.header": "Clearing and Paving with the Roller",
+ "create.ponder.mechanical_roller_pave.text_1": "Mechanical rollers help to clean up terrain around tracks or paths",
+ "create.ponder.mechanical_roller_pave.text_2": "In its default mode, without a material set, it will simply clear blocks like a Drill",
+ "create.ponder.mechanical_roller_pave.text_3": "While disassembled, a suitable paving material can be specified",
+ "create.ponder.mechanical_roller_pave.text_4": "Materials can be supplied via chests or barrels attached to the structure",
+ "create.ponder.mechanical_roller_pave.text_5": "In addition to breaking blocks, it will now replace the layer beneath them",
+ "create.ponder.mechanical_roller_pave.text_6": "Note that any block destroyed by a roller has a chance not to yield drops",
+ "create.ponder.mechanical_roller_pave.text_7": "Rollers are especially useful on Trains, but can also be used on most other types of moving contraptions",
+ "create.ponder.mechanical_saw_breaker.header": "Cutting Trees with the Mechanical Saw",
+ "create.ponder.mechanical_saw_breaker.text_1": "When given Rotational Force, a Mechanical Saw will cut trees directly in front of it",
+ "create.ponder.mechanical_saw_breaker.text_2": "In order to cut the tree fully, the Saw has to break the last block connecting it to the ground",
+ "create.ponder.mechanical_saw_contraption.header": "Using Mechanical Saws on Contraptions",
+ "create.ponder.mechanical_saw_contraption.text_1": "Whenever Saws are moved as part of an animated Contraption...",
+ "create.ponder.mechanical_saw_contraption.text_2": "...they will cut any trees the contraption runs them into",
+ "create.ponder.mechanical_saw_processing.header": "Processing Items on the Mechanical Saw",
+ "create.ponder.mechanical_saw_processing.text_1": "Upward facing Mechanical Saws can process a variety of items",
+ "create.ponder.mechanical_saw_processing.text_2": "The processed item always moves against the rotational input to the saw",
+ "create.ponder.mechanical_saw_processing.text_3": "Saws can work in-line with Mechanical Belts",
+ "create.ponder.mechanical_saw_processing.text_4": "When an ingredient has multiple possible outcomes, the filter slot can specify it",
+ "create.ponder.mechanical_saw_processing.text_5": "Without filter, the Saw would cycle through all outcomes instead",
+ "create.ponder.millstone.header": "Processing Items in the Millstone",
+ "create.ponder.millstone.text_1": "Millstones process items by grinding them",
+ "create.ponder.millstone.text_2": "They can be powered from the side using cogwheels",
+ "create.ponder.millstone.text_3": "Throw or Insert items at the top",
+ "create.ponder.millstone.text_4": "After some time, the result can be obtained via Right-click",
+ "create.ponder.millstone.text_5": "The outputs can also be extracted by automation",
+ "create.ponder.mod_name": "Create",
+ "create.ponder.nixie_tube.header": "Using Nixie Tubes",
+ "create.ponder.nixie_tube.text_1": "When powered by Redstone, Nixie Tubes will display the signal strength",
+ "create.ponder.nixie_tube.text_2": "Using written Clipboards, custom text can be displayed",
+ "create.ponder.nixie_tube.text_3": "Right-Click with Dye to change their display colour",
+ "create.ponder.package_frogport.header": "Transporting packages between Frogports",
+ "create.ponder.package_frogport.text_1": "Right-click a Chain Conveyor and place the Frogport nearby",
+ "create.ponder.package_frogport.text_10": "Packages find their path to a matching frog on the chain network",
+ "create.ponder.package_frogport.text_11": "Peter",
+ "create.ponder.package_frogport.text_12": "→ Peter",
+ "create.ponder.package_frogport.text_13": "Frogports can directly interface with inventories below them",
+ "create.ponder.package_frogport.text_14": "This also works with packagers. Items can be packed and shipped directly",
+ "create.ponder.package_frogport.text_15": "Albert",
+ "create.ponder.package_frogport.text_16": "Peter",
+ "create.ponder.package_frogport.text_17": "Addresses packages to 'Peter'",
+ "create.ponder.package_frogport.text_18": "Right-click Frogports with a clipboard to collect their address",
+ "create.ponder.package_frogport.text_19": "Clipboards with collected names can help auto-complete address inputs in other UIs",
+ "create.ponder.package_frogport.text_2": "Assign it an address in the inventory UI",
+ "create.ponder.package_frogport.text_3": "If the address of an inserted package does not match it..",
+ "create.ponder.package_frogport.text_4": "Albert",
+ "create.ponder.package_frogport.text_5": "→ Peter",
+ "create.ponder.package_frogport.text_6": "..the Frogport will place the package on the conveyor",
+ "create.ponder.package_frogport.text_7": "Packages spin in place if they have no valid destination",
+ "create.ponder.package_frogport.text_8": "More Frogports can be added anywhere on the chain network",
+ "create.ponder.package_frogport.text_9": "Peter",
+ "create.ponder.packager.header": "Creating and unwrapping packages",
+ "create.ponder.packager.text_1": "Attach packagers to the inventory they should target",
+ "create.ponder.packager.text_2": "Given redstone power, it will pack items from the inventory into a package",
+ "create.ponder.packager.text_3": "These can be picked up and transported like any other item",
+ "create.ponder.packager.text_4": "Packages inserted will be destroyed, unpacking the contents into the inventory",
+ "create.ponder.packager.text_5": "Full",
+ "create.ponder.packager.text_6": "Packagers will not accept packages they cannot fully unpack",
+ "create.ponder.packager_address.header": "Routing packages with an address",
+ "create.ponder.packager_address.text_1": "Warehouse",
+ "create.ponder.packager_address.text_10": "For compactness, mechanical saws can unpack straight onto a belt",
+ "create.ponder.packager_address.text_11": "Aside from filters, Frogports and Postboxes have package routing abilities",
+ "create.ponder.packager_address.text_12": "Inspect them to find out more about their behaviour",
+ "create.ponder.packager_address.text_2": "When a sign is placed on a packager..",
+ "create.ponder.packager_address.text_3": "→ Warehouse",
+ "create.ponder.packager_address.text_4": "Created packages will carry the written lines of text as their address",
+ "create.ponder.packager_address.text_5": "Package filters route packages based on their address",
+ "create.ponder.packager_address.text_6": "→ Warehouse",
+ "create.ponder.packager_address.text_7": "Factory",
+ "create.ponder.packager_address.text_8": "→ Factory",
+ "create.ponder.packager_address.text_9": "Factory",
+ "create.ponder.piston_pole.header": "Piston Extension Poles",
+ "create.ponder.piston_pole.text_1": "Without attached Poles, a Mechanical Piston cannot move",
+ "create.ponder.piston_pole.text_2": "The Length of pole added at its back determines the Extension Range",
+ "create.ponder.portable_fluid_interface.header": "Contraption Fluid Exchange",
+ "create.ponder.portable_fluid_interface.text_1": "Fluid Tanks on moving contraptions cannot be accessed by any pipes",
+ "create.ponder.portable_fluid_interface.text_2": "This component can interact with fluid tanks without the need to stop the contraption",
+ "create.ponder.portable_fluid_interface.text_3": "Place a second one with a gap of 1 or 2 blocks inbetween",
+ "create.ponder.portable_fluid_interface.text_4": "Whenever they pass by each other, they will engage in a connection",
+ "create.ponder.portable_fluid_interface.text_5": "While engaged, the stationary interface will represent ALL Tanks on the contraption",
+ "create.ponder.portable_fluid_interface.text_6": "Fluid can now be inserted...",
+ "create.ponder.portable_fluid_interface.text_7": "...or extracted from the contraption",
+ "create.ponder.portable_fluid_interface.text_8": "After no contents have been exchanged for a while, the contraption will continue on its way",
+ "create.ponder.portable_storage_interface.header": "Contraption Storage Exchange",
+ "create.ponder.portable_storage_interface.text_1": "Moving inventories can be tricky to access with automation.",
+ "create.ponder.portable_storage_interface.text_2": "This component can interact with storage without the need to stop the contraption.",
+ "create.ponder.portable_storage_interface.text_3": "Place a second one with a gap of 1 or 2 blocks inbetween",
+ "create.ponder.portable_storage_interface.text_4": "Whenever they pass by each other, they will engage in a connection",
+ "create.ponder.portable_storage_interface.text_5": "While engaged, the stationary interface will represent ALL inventories on the contraption",
+ "create.ponder.portable_storage_interface.text_6": "Items can now be inserted...",
+ "create.ponder.portable_storage_interface.text_7": "...or extracted from the contraption",
+ "create.ponder.portable_storage_interface.text_8": "After no items have been exchanged for a while, the contraption will continue on its way",
+ "create.ponder.portable_storage_interface_redstone.header": "Redstone Control",
+ "create.ponder.portable_storage_interface_redstone.text_1": "Redstone power will prevent the stationary interface from engaging",
+ "create.ponder.postbox.header": "Transporting packages between Postboxes",
+ "create.ponder.postbox.text_1": "Right-click a Train Station and place the Postbox nearby",
+ "create.ponder.postbox.text_10": "→ Warehouse",
+ "create.ponder.postbox.text_11": "Just like trains, Postboxes maintain their behaviour in unloaded chunks",
+ "create.ponder.postbox.text_12": "Packages can still be delivered from or to their inventory",
+ "create.ponder.postbox.text_2": "Assign it an address in the inventory UI",
+ "create.ponder.postbox.text_3": "If the address of an inserted package does not match it..",
+ "create.ponder.postbox.text_4": "Warehouse",
+ "create.ponder.postbox.text_5": "→ Outpost",
+ "create.ponder.postbox.text_6": "..trains stopping at the station will collect it as cargo",
+ "create.ponder.postbox.text_7": "Conversely, packages matching the address will be dropped off",
+ "create.ponder.postbox.text_8": "Packages that arrived by train can be extracted from the Postbox",
+ "create.ponder.postbox.text_9": "Warehouse",
+ "create.ponder.powered_latch.header": "Controlling signals using the Powered Latch",
+ "create.ponder.powered_latch.text_1": "Powered Latches are redstone controllable Levers",
+ "create.ponder.powered_latch.text_2": "Signals at the back switch it on",
+ "create.ponder.powered_latch.text_3": "Signals from the side switch it back off",
+ "create.ponder.powered_latch.text_4": "Powered latches can also be toggled manually",
+ "create.ponder.powered_toggle_latch.header": "Controlling signals using the Powered Toggle Latch",
+ "create.ponder.powered_toggle_latch.text_1": "Powered Toggle Latches are redstone controllable Levers",
+ "create.ponder.powered_toggle_latch.text_2": "Signals at the back will toggle its state",
+ "create.ponder.powered_toggle_latch.text_3": "...on and back off",
+ "create.ponder.powered_toggle_latch.text_4": "Powered toggle latches can also be toggled manually",
+ "create.ponder.pulse_extender.header": "Controlling signals using Pulse Extenders",
+ "create.ponder.pulse_extender.text_1": "Pulse Extenders can lengthen a signal passing through",
+ "create.ponder.pulse_extender.text_2": "They activate after a short delay...",
+ "create.ponder.pulse_extender.text_3": "...and cool down for the configured duration",
+ "create.ponder.pulse_extender.text_4": "Using the value panel, the discharge time can be configured",
+ "create.ponder.pulse_extender.text_5": "The configured duration can range up to an hour",
+ "create.ponder.pulse_repeater.header": "Controlling signals using Pulse Repeaters",
+ "create.ponder.pulse_repeater.text_1": "Pulse Repeaters emit a short pulse after a delay",
+ "create.ponder.pulse_repeater.text_2": "Using the value panel, the charge time can be configured",
+ "create.ponder.pulse_repeater.text_3": "Configured delays can range up to an hour",
+ "create.ponder.pulse_timer.header": "Redstone output of the Pulse Timer",
+ "create.ponder.pulse_timer.text_1": "Pulse Timers repeatedly emit short pulses",
+ "create.ponder.pulse_timer.text_2": "Using the value panel, the time interval can be configured",
+ "create.ponder.pulse_timer.text_3": "Powering the input side will pause and reset them",
+ "create.ponder.pulse_timer.text_4": "Right-click the circuit base to invert the output",
+ "create.ponder.pulse_timer.text_5": "This helps trigger mechanisms that activate only without a redstone signal",
+ "create.ponder.radial_chassis.header": "Attaching blocks using Radial Chassis",
+ "create.ponder.radial_chassis.text_1": "Radial Chassis connect to identical Chassis blocks in a row",
+ "create.ponder.radial_chassis.text_2": "When one is moved by a Contraption, the others are dragged with it",
+ "create.ponder.radial_chassis.text_3": "The side faces of a Radial Chassis can be made Sticky",
+ "create.ponder.radial_chassis.text_4": "Click again to make all other sides sticky",
+ "create.ponder.radial_chassis.text_5": "Sneak and Right-Click with an empty hand to remove the slime",
+ "create.ponder.radial_chassis.text_6": "Whenever a Block is next to a sticky face...",
+ "create.ponder.radial_chassis.text_7": "...it will attach all reachable blocks within a radius on that layer",
+ "create.ponder.radial_chassis.text_8": "Using a Wrench, a precise Radius can be specified for this chassis",
+ "create.ponder.radial_chassis.text_9": "Blocks not reachable by any sticky face will not attach",
+ "create.ponder.redstone_contact.header": "Redstone Contacts",
+ "create.ponder.redstone_contact.text_1": "Redstone Contacts facing each other will emit a redstone signal",
+ "create.ponder.redstone_contact.text_2": "This still applies when one of them is part of a moving Contraption",
+ "create.ponder.redstone_link.header": "Using Redstone Links",
+ "create.ponder.redstone_link.text_1": "Redstone Links can transmit redstone signals wirelessly",
+ "create.ponder.redstone_link.text_2": "Right-click while Sneaking to toggle receive mode",
+ "create.ponder.redstone_link.text_3": "A simple Right-click with a Wrench can do the same",
+ "create.ponder.redstone_link.text_4": "Receivers emit the redstone power of transmitters within 256 blocks",
+ "create.ponder.redstone_link.text_5": "Placing items in the two slots can specify a Frequency",
+ "create.ponder.redstone_link.text_6": "Only the links with matching Frequencies will communicate",
+ "create.ponder.redstone_requester.header": "Automated orders with Redstone Requesters",
+ "create.ponder.redstone_requester.text_1": "Right-click a Stock link before placement to connect to its network",
+ "create.ponder.redstone_requester.text_2": "Just like Stock tickers, Redstone requesters can order items from the logistics network",
+ "create.ponder.redstone_requester.text_3": "Right-click the requester to open its configuration UI",
+ "create.ponder.redstone_requester.text_4": "The order set in the UI will be requested on every redstone pulse",
+ "create.ponder.redstone_requester.text_5": "Alternatively, the requester can be fully configured before placement",
+ "create.ponder.redstone_requester.text_6": "Right-click a Stock keeper with it and set the desired order there",
+ "create.ponder.repackager.header": "Merging packages from a request",
+ "create.ponder.repackager.text_1": "Sometimes, it is crucial for logistical requests to arrive as a single package",
+ "create.ponder.repackager.text_2": "Order A",
+ "create.ponder.repackager.text_3": "Order B",
+ "create.ponder.repackager.text_4": "Otherwise, other packages could arrive inbetween",
+ "create.ponder.repackager.text_5": "When this is the case, redirect packages to an inventory",
+ "create.ponder.repackager.text_6": "Attach a re-packager, and power it with redstone",
+ "create.ponder.repackager.text_7": "Once all fragments arrived, they will be exported as a new package",
+ "create.ponder.repackager.text_8": "Now, requested items arrive together and in a predictable order",
+ "create.ponder.rope_pulley.header": "Moving Structures using Rope Pulleys",
+ "create.ponder.rope_pulley.text_1": "Rope Pulleys can move blocks vertically when given Rotational Force",
+ "create.ponder.rope_pulley.text_2": "Direction and Speed of movement depend on the Rotational Input",
+ "create.ponder.rope_pulley_attachment.header": "Moving Pulleys as part of a Contraption",
+ "create.ponder.rope_pulley_attachment.text_1": "Whenever Pulleys are themselves being moved by a Contraption...",
+ "create.ponder.rope_pulley_attachment.text_2": "...its attached structure will be dragged with it",
+ "create.ponder.rope_pulley_attachment.text_3": "Mind that pulleys are only movable while stopped",
+ "create.ponder.rope_pulley_modes.header": "Movement Modes of the Rope Pulley",
+ "create.ponder.rope_pulley_modes.text_1": "Whenever Pulleys stop moving, the moved structure reverts to blocks",
+ "create.ponder.rope_pulley_modes.text_2": "It can be configured never to revert to solid blocks, or only at the location it started at",
+ "create.ponder.rope_pulley_multi_rope.header": "Synchronised Pulley Movement",
+ "create.ponder.rope_pulley_multi_rope.text_1": "Whenever a pulley assembles a contraption...",
+ "create.ponder.rope_pulley_multi_rope.text_2": "...other pulleys on the same layer will connect to the structure",
+ "create.ponder.rope_pulley_multi_rope.text_3": "They do not require to be powered, the effect is purely cosmetic",
+ "create.ponder.rose_quartz_lamp.header": "Rose Quartz Lamps",
+ "create.ponder.rose_quartz_lamp.text_1": "Rose Quartz Lamps activate on a Redstone signal",
+ "create.ponder.rose_quartz_lamp.text_2": "They will continue to emit redstone power afterwards",
+ "create.ponder.rose_quartz_lamp.text_3": "When multiple lamps are arranged in a group...",
+ "create.ponder.rose_quartz_lamp.text_4": "...activating a Lamp will focus the signal to it, deactivating all others",
+ "create.ponder.rose_quartz_lamp.text_5": "Comparators output based on the distance to a powered lamp",
+ "create.ponder.rose_quartz_lamp.text_6": "The Lamps can also be toggled manually using a Wrench",
+ "create.ponder.rotation_speed_controller.header": "Using the Rotational Speed Controller",
+ "create.ponder.rotation_speed_controller.text_1": "Rot. Speed Controllers relay rotation from their axis to a Large Cogwheel above them",
+ "create.ponder.rotation_speed_controller.text_2": "Using the value panel on its side, the conveyed speed can be configured",
+ "create.ponder.sail.header": "Assembling Windmills using Sails",
+ "create.ponder.sail.text_1": "Sails are handy blocks to create Windmills with",
+ "create.ponder.sail.text_2": "They will attach to blocks and each other without the need of Super Glue or Chassis Blocks",
+ "create.ponder.sail.text_3": "Right-Click with Dye to paint them",
+ "create.ponder.sail.text_4": "Right-Click with Shears to turn them back into frames",
+ "create.ponder.sail_frame.header": "Assembling Windmills using Sail Frames",
+ "create.ponder.sail_frame.text_1": "Sail Frames are handy blocks to create Windmills with",
+ "create.ponder.sail_frame.text_2": "They will attach to blocks and each other without the need of Super Glue or Chassis Blocks",
+ "create.ponder.sequenced_gearshift.header": "Controlling Rotational Speed using Sequenced Gearshifts",
+ "create.ponder.sequenced_gearshift.text_1": "Seq. Gearshifts relay rotation by following a timed list of instructions",
+ "create.ponder.sequenced_gearshift.text_2": "Right-click it to open the Configuration UI",
+ "create.ponder.sequenced_gearshift.text_3": "Upon receiving a Redstone Signal, it will start running its configured sequence",
+ "create.ponder.sequenced_gearshift.text_4": "Once finished, it waits for the next Redstone Signal and starts over",
+ "create.ponder.sequenced_gearshift.text_5": "A redstone comparator can be used to read the current progress",
+ "create.ponder.shaft.header": "Relaying rotational force using Shafts",
+ "create.ponder.shaft.text_1": "Shafts will relay rotation in a straight line.",
+ "create.ponder.shaft_casing.header": "Encasing Shafts",
+ "create.ponder.shaft_casing.text_1": "Brass or Andesite Casing can be used to decorate Shafts",
+ "create.ponder.shared.behaviour_modify_value_panel": "This behaviour can be modified using the value panel",
+ "create.ponder.shared.movement_anchors": "With the help of Super Glue, larger structures can be moved.",
+ "create.ponder.shared.rpm16": "16 RPM",
+ "create.ponder.shared.rpm16_source": "Source: 16 RPM",
+ "create.ponder.shared.rpm32": "32 RPM",
+ "create.ponder.shared.rpm8": "8 RPM",
+ "create.ponder.shared.storage_on_contraption": "Inventories attached to the Contraption will pick up their drops automatically",
+ "create.ponder.smart_chute.header": "Filtering Items using Smart Chutes",
+ "create.ponder.smart_chute.text_1": "Smart Chutes are vertical chutes with additional control",
+ "create.ponder.smart_chute.text_2": "Items in the filter slot specify what to extract or transfer",
+ "create.ponder.smart_chute.text_3": "Use the value panel to specify the extracted stack size",
+ "create.ponder.smart_chute.text_4": "Redstone power will prevent Smart Chutes from acting.",
+ "create.ponder.smart_observer.header": "Advanced detection with Smart Observers",
+ "create.ponder.smart_observer.text_1": "Smart Observers can be used to detect a variety of events",
+ "create.ponder.smart_observer.text_2": "It can detect items or fluids inside of generic containers",
+ "create.ponder.smart_observer.text_3": "The filter slot can be used to look for specific contents only",
+ "create.ponder.smart_observer.text_4": "It also activates when the block itself matches the filter",
+ "create.ponder.smart_observer.text_5": "Additionally, smart observers can monitor belts, chutes and pipes",
+ "create.ponder.smart_observer.text_6": "...and will emit a pulse, if an item enters or exits a funnel",
+ "create.ponder.smart_pipe.header": "Controlling Fluid flow using Smart Pipes",
+ "create.ponder.smart_pipe.text_1": "Smart pipes can help control flows by fluid type",
+ "create.ponder.smart_pipe.text_2": "When placed directly at the source, they can specify the type of fluid to extract",
+ "create.ponder.smart_pipe.text_3": "Simply Right-Click their filter slot with any item containing the desired fluid",
+ "create.ponder.smart_pipe.text_4": "When placed further down a pipe network, smart pipes will only let matching fluids continue",
+ "create.ponder.speedometer.header": "Monitoring Kinetic information using the Speedometer",
+ "create.ponder.speedometer.text_1": "The Speedometer displays the current Speed of attached components",
+ "create.ponder.speedometer.text_2": "When wearing Engineers' Goggles, the player can get more detailed information from the Gauge",
+ "create.ponder.speedometer.text_3": "Comparators can emit analog Restone Signals relative to the Speedometer's measurements",
+ "create.ponder.spout_filling.header": "Filling Items using a Spout",
+ "create.ponder.spout_filling.text_1": "The Spout can fill fluid holding items provided beneath it",
+ "create.ponder.spout_filling.text_2": "The content of a Spout cannot be accessed manually",
+ "create.ponder.spout_filling.text_3": "Instead, Pipes can be used to supply it with fluids",
+ "create.ponder.spout_filling.text_4": "The Input items can be placed on a Depot under the Spout",
+ "create.ponder.spout_filling.text_5": "When items are provided on a belt...",
+ "create.ponder.spout_filling.text_6": "The Spout will hold and process them automatically",
+ "create.ponder.stabilized_bearings.header": "Stabilized Contraptions",
+ "create.ponder.stabilized_bearings.text_1": "Whenever Mechanical Bearings are themselves part of a moving Structure..",
+ "create.ponder.stabilized_bearings.text_2": "..they will attempt to keep themselves upright",
+ "create.ponder.stabilized_bearings.text_3": "Once again, the bearing will attach to the block in front of it",
+ "create.ponder.stabilized_bearings.text_4": "As a result, the entire sub-Contraption will stay upright",
+ "create.ponder.steam_engine.header": "Setting up Steam Engines",
+ "create.ponder.steam_engine.text_1": "Steam Engines can be placed on a Fluid Tank",
+ "create.ponder.steam_engine.text_10": "Lvl 4",
+ "create.ponder.steam_engine.text_11": "4 Engines",
+ "create.ponder.steam_engine.text_12": "Lvl 8",
+ "create.ponder.steam_engine.text_13": "8 Engines",
+ "create.ponder.steam_engine.text_2": "Clicking the engine with a Shaft creates the Kinetic Output",
+ "create.ponder.steam_engine.text_3": "With sufficient Heat, Water and Boiler space...",
+ "create.ponder.steam_engine.text_4": "...they will generate Rotational Force",
+ "create.ponder.steam_engine.text_5": "The minimal setup requires 4 Fluid Tanks",
+ "create.ponder.steam_engine.text_6": "With the help of Blaze Burners, the power output can be increased",
+ "create.ponder.steam_engine.text_7": "Higher power levels require more Water, Size and Heat",
+ "create.ponder.steam_engine.text_8": "The boiler's current power level can be inspected with Engineer's Goggles",
+ "create.ponder.steam_engine.text_9": "With each added power level, an additional Engine can output at full capacity",
+ "create.ponder.steam_whistle.header": "Setting up Steam Whistles",
+ "create.ponder.steam_whistle.text_1": "Steam Whistles can be placed on a Fluid Tank",
+ "create.ponder.steam_whistle.text_2": "If the tank receives sufficient heat...",
+ "create.ponder.steam_whistle.text_3": "...the Whistle will play a note when activated",
+ "create.ponder.steam_whistle.text_4": "Use a Whistle item on the block to lower its pitch",
+ "create.ponder.steam_whistle.text_5": "Cycle between three different octaves using a Wrench",
+ "create.ponder.steam_whistle.text_6": "Engineer's Goggles can help to find out the current pitch of a Whistle",
+ "create.ponder.sticker.header": "Attaching blocks using the Sticker",
+ "create.ponder.sticker.text_1": "Stickers are ideal for Redstone-controlled block attachment",
+ "create.ponder.sticker.text_2": "Upon receiving a signal, it will toggle its state",
+ "create.ponder.sticker.text_3": "If it is now moved in a contraption, the block will move with it",
+ "create.ponder.sticker.text_4": "Toggled once again, the block is no longer attached",
+ "create.ponder.stock_link.header": "Logistics Networks and the Stock Link",
+ "create.ponder.stock_link.text_1": "When placed, Stock Links create a new stock network",
+ "create.ponder.stock_link.text_2": "Right-click an existing link before placing it to bind them",
+ "create.ponder.stock_link.text_3": "Stock-linked packagers make their inventory available to the network",
+ "create.ponder.stock_link.text_4": "Other components on the network can now find and request their items",
+ "create.ponder.stock_link.text_5": "On request, items from the inventories will be placed into packages",
+ "create.ponder.stock_link.text_6": "Stock Link signals have unlimited range, but packages require transportation",
+ "create.ponder.stock_link.text_7": "Full redstone power will stop a link from broadcasting",
+ "create.ponder.stock_link.text_8": "Analog power lowers the priority of a link, causing others to act first",
+ "create.ponder.stock_ticker.header": "Ordering items with Stock tickers",
+ "create.ponder.stock_ticker.text_1": "Right-click a Stock link before placement to connect to its network",
+ "create.ponder.stock_ticker.text_10": "Stock tickers can also order blocks required for the schematicannon",
+ "create.ponder.stock_ticker.text_11": "Simply hand the printed clipboard it generated to the Stock keeper",
+ "create.ponder.stock_ticker.text_2": "Stock-linked packagers make their attached inventory available to the network",
+ "create.ponder.stock_ticker.text_3": "Stock tickers can order items from these inventories",
+ "create.ponder.stock_ticker.text_4": "Seated mobs or blaze burners in front of it act as the Stock Keeper",
+ "create.ponder.stock_ticker.text_5": "Right-click the keeper to start ordering items",
+ "create.ponder.stock_ticker.text_6": "When an order is submitted, the items will be placed into packages",
+ "create.ponder.stock_ticker.text_7": "From there, they can be transported to the request point",
+ "create.ponder.stock_ticker.text_8": "Using attribute or list filters, categories can be added to the item listings",
+ "create.ponder.stock_ticker.text_9": "Right-click the Stock ticker to open the category editor",
+ "create.ponder.stock_ticker_address.header": "Addressing a Stock ticker order",
+ "create.ponder.stock_ticker_address.text_1": "When ordering items, a target address can be set in the request",
+ "create.ponder.stock_ticker_address.text_10": "Aside from filters, Frogports and Postboxes have package routing abilities",
+ "create.ponder.stock_ticker_address.text_11": "Inspect them to find out more about their behaviour",
+ "create.ponder.stock_ticker_address.text_2": "→ Workshop",
+ "create.ponder.stock_ticker_address.text_3": "This address will be on all packages created for the request",
+ "create.ponder.stock_ticker_address.text_4": "Using package filters, this can control where the packages will go",
+ "create.ponder.stock_ticker_address.text_5": "→ Workshop",
+ "create.ponder.stock_ticker_address.text_6": "Workshop",
+ "create.ponder.stock_ticker_address.text_7": "→ Factory",
+ "create.ponder.stock_ticker_address.text_8": "→ Factory",
+ "create.ponder.stock_ticker_address.text_9": "Workshop",
+ "create.ponder.stressometer.header": "Monitoring Kinetic information using the Stressometer",
+ "create.ponder.stressometer.text_1": "The Stressometer displays the current Stress Capacity of the attached kinetic network",
+ "create.ponder.stressometer.text_2": "When wearing Engineers' Goggles, the player can get more detailed information from the Gauge",
+ "create.ponder.stressometer.text_3": "Comparators can emit analog Restone Signals relative to the Stressometer's measurements",
+ "create.ponder.super_glue.header": "Attaching blocks using Super Glue",
+ "create.ponder.super_glue.text_1": "Super Glue groups blocks together into moving contraptions",
+ "create.ponder.super_glue.text_2": "Clicking two endpoints creates a new 'glued' area",
+ "create.ponder.super_glue.text_3": "To remove a box, punch it with the glue item in hand",
+ "create.ponder.super_glue.text_4": "Adjacent blocks sharing an area will pull each other along",
+ "create.ponder.super_glue.text_5": "Overlapping glue volumes will move together",
+ "create.ponder.super_glue.text_6": "Blocks hanging on others usually do not require glue",
+ "create.ponder.table_cloth.header": "Selling items with Table Cloths",
+ "create.ponder.table_cloth.text_1": "Table cloths can be used to display items",
+ "create.ponder.table_cloth.text_10": "When checking out at the cashier, the bought items will be placed into a package",
+ "create.ponder.table_cloth.text_11": "From there, they can be transported to the shop front",
+ "create.ponder.table_cloth.text_12": "The payments will be stored inside the stock ticker block",
+ "create.ponder.table_cloth.text_2": "With the help of a logistics system, they can also be used to sell items",
+ "create.ponder.table_cloth.text_3": "Start with the shop's inventory, a Packager and Stock link",
+ "create.ponder.table_cloth.text_4": "Bind a Stock ticker to the link and place it in the shop",
+ "create.ponder.table_cloth.text_5": "Employ a mob or blaze burner as the shop keeper",
+ "create.ponder.table_cloth.text_6": "Fill the shop inventory with items to be sold",
+ "create.ponder.table_cloth.text_7": "To create a new trade, interact with the shop keeper while holding a table cloth",
+ "create.ponder.table_cloth.text_8": "Once placed, set a price in the item slot on the side",
+ "create.ponder.table_cloth.text_9": "Other players can now interact with the shop",
+ "create.ponder.tag.arm_targets": "Targets for Mechanical Arms",
+ "create.ponder.tag.arm_targets.description": "Components which can be selected as inputs or outputs to the Mechanical Arm",
+ "create.ponder.tag.contraption_actor": "Contraption Actors",
+ "create.ponder.tag.contraption_actor.description": "Components which expose special behaviour when attached to a moving contraption",
+ "create.ponder.tag.contraption_assembly": "Block Attachment Utility",
+ "create.ponder.tag.contraption_assembly.description": "Tools and Components used to assemble structures moved as an animated Contraption",
+ "create.ponder.tag.creative": "Creative Mode",
+ "create.ponder.tag.creative.description": "Components not usually available for Survival Mode",
+ "create.ponder.tag.decoration": "Aesthetics",
+ "create.ponder.tag.decoration.description": "Components used mostly for decorative purposes",
+ "create.ponder.tag.display_sources": "Sources for Display Links",
+ "create.ponder.tag.display_sources.description": "Components or Blocks which offer some data that can be read with a Display Link",
+ "create.ponder.tag.display_targets": "Targets for Display Links",
+ "create.ponder.tag.display_targets.description": "Components or Blocks which can process and display the data received from a Display Link",
+ "create.ponder.tag.fluids": "Fluid Manipulators",
+ "create.ponder.tag.fluids.description": "Components which help relaying and making use of Fluids",
+ "create.ponder.tag.high_logistics": "High Logistics",
+ "create.ponder.tag.high_logistics.description": "Components which help manage distributed item storage and automated requests around your factory",
+ "create.ponder.tag.kinetic_appliances": "Kinetic Appliances",
+ "create.ponder.tag.kinetic_appliances.description": "Components which make use of Rotational Force",
+ "create.ponder.tag.kinetic_relays": "Kinetic Blocks",
+ "create.ponder.tag.kinetic_relays.description": "Components which help relaying Rotational Force elsewhere",
+ "create.ponder.tag.kinetic_sources": "Kinetic Sources",
+ "create.ponder.tag.kinetic_sources.description": "Components which generate Rotational Force",
+ "create.ponder.tag.logistics": "Item Transportation",
+ "create.ponder.tag.logistics.description": "Components which help moving items around",
+ "create.ponder.tag.movement_anchor": "Movement Anchors",
+ "create.ponder.tag.movement_anchor.description": "Components which allow the creation of moving contraptions, animating an attached structure in a variety of ways",
+ "create.ponder.tag.redstone": "Logic Components",
+ "create.ponder.tag.redstone.description": "Components which help with redstone engineering",
+ "create.ponder.tag.threshold_switch_targets": "Targets for Threshold Switches",
+ "create.ponder.tag.threshold_switch_targets.description": "Threshold Switches can read from these blocks, as well as most item and fluid containers.",
+ "create.ponder.tag.train_related": "Railway Equipment",
+ "create.ponder.tag.train_related.description": "Components used in the construction or management of Train Contraptions",
+ "create.ponder.tag.windmill_sails": "Sails for Windmill Bearings",
+ "create.ponder.tag.windmill_sails.description": "Blocks that count towards the strength of a Windmill Contraption when assembled. Each of these have equal efficiency in doing so.",
+ "create.ponder.threshold_switch.header": "Monitoring with the Threshold Switch",
+ "create.ponder.threshold_switch.text_1": "Threshold Switches monitor the fill level of containers",
+ "create.ponder.threshold_switch.text_2": "When the inventory content exceeds the upper threshold...",
+ "create.ponder.threshold_switch.text_3": "...the switch will change its redstone output",
+ "create.ponder.threshold_switch.text_4": "The signal stays until the lower threshold is reached",
+ "create.ponder.threshold_switch.text_5": "The redstone output can now be used to control item supply, keeping the buffer filled",
+ "create.ponder.threshold_switch.text_6": "The specific thresholds can be changed in the UI",
+ "create.ponder.threshold_switch.text_7": "A filter can help to only count specific contents toward the total",
+ "create.ponder.threshold_switch.text_8": "Fluid buffers can be monitored in a similar fashion",
+ "create.ponder.threshold_switch.text_9": "...as well as, curiously, the length of an extended rope pulley",
+ "create.ponder.track_chunks.header": "Traversing unloaded Chunks",
+ "create.ponder.track_chunks.text_1": "Tracks stay functional outside of loaded chunks",
+ "create.ponder.track_chunks.text_2": "Trains will travel through inactive sections of the world without issue",
+ "create.ponder.track_chunks.text_3": "They will still stop at stations or red signals",
+ "create.ponder.track_chunks.text_4": "However, Drills and other on-board machines will not operate",
+ "create.ponder.track_chunks.text_5": "Once near a Player, the train will re-appear",
+ "create.ponder.track_observer.header": "Detecting Trains",
+ "create.ponder.track_observer.text_1": "Select a Train Track then place the Observer nearby",
+ "create.ponder.track_observer.text_2": "The Observer will detect any Trains passing over the marker",
+ "create.ponder.track_observer.text_3": "Observers can be filtered to activate for matching cargo",
+ "create.ponder.track_placement.header": "Placing Train Tracks",
+ "create.ponder.track_placement.text_1": "A new type of rail designed for Train Contraptions",
+ "create.ponder.track_placement.text_2": "To place rows of track in bulk, click on an existing track",
+ "create.ponder.track_placement.text_3": "Then place or select a second track",
+ "create.ponder.track_placement.text_4": "Tracks can also be placed as turns or slopes",
+ "create.ponder.track_placement.text_5": "When connecting, tracks will try to make each turn equally sized",
+ "create.ponder.track_placement.text_6": "Holding the sprint key while connecting...",
+ "create.ponder.track_placement.text_7": "...will create the longest fitting bend instead",
+ "create.ponder.track_placement.text_8": "Materials in the off-hand will be paved under tracks automatically",
+ "create.ponder.track_portal.header": "Tracks and the Nether",
+ "create.ponder.track_portal.text_1": "Tracks placed up against a nether portal...",
+ "create.ponder.track_portal.text_2": "...will attempt to create a paired track on the other side",
+ "create.ponder.track_portal.text_3": "Trains on this track are now able to travel across dimensions",
+ "create.ponder.train_assembly.header": "Assembling Trains",
+ "create.ponder.train_assembly.text_1": "Select a Train Track then place the Station nearby",
+ "create.ponder.train_assembly.text_10": "Every Train requires Train Controls on board",
+ "create.ponder.train_assembly.text_11": "An optional second one allows departure from Stations in both directions",
+ "create.ponder.train_assembly.text_12": "Open the Station UI and confirm the Assembly process",
+ "create.ponder.train_assembly.text_13": "Trains can be disassembled back into blocks at stations only",
+ "create.ponder.train_assembly.text_14": "When used on a station, maps will add a labeled marker at the location",
+ "create.ponder.train_assembly.text_15": "Assembled Trains can be relocated to nearby Tracks using the Wrench",
+ "create.ponder.train_assembly.text_2": "Stations are the Waypoints of your Track Network",
+ "create.ponder.train_assembly.text_3": "To create a new Train, open the UI and switch to Assembly Mode",
+ "create.ponder.train_assembly.text_4": "During Assembly no scheduled trains will approach this station",
+ "create.ponder.train_assembly.text_5": "Create new bogeys by using Train Casing on Tracks",
+ "create.ponder.train_assembly.text_6": "Click the track again to cycle between bogey designs",
+ "create.ponder.train_assembly.text_7": "Attach blocks with the help of Super Glue",
+ "create.ponder.train_assembly.text_8": "Assembled Trains will move faster if they can find fuel in assembled chests or barrels",
+ "create.ponder.train_assembly.text_9": "Fuel stored in Vaults will not be consumed by the train",
+ "create.ponder.train_controls.header": "Controlling Trains",
+ "create.ponder.train_controls.text_1": "Train Controls are required on every train contraption",
+ "create.ponder.train_controls.text_2": "Once assembled, right-click the block to start driving",
+ "create.ponder.train_controls.text_3": "Accelerate and steer the Train using movement keybinds",
+ "create.ponder.train_controls.text_4": "If desired, the top speed can be fine-tuned using the mouse wheel",
+ "create.ponder.train_controls.text_5": "Hold space to approach a nearby Station",
+ "create.ponder.train_controls.text_6": "Trains can only be disassembled back into blocks at Stations",
+ "create.ponder.train_controls.text_7": "Assembled Whistles can be activated with the sprint key",
+ "create.ponder.train_controls.text_8": "Sneak or click again to stop controlling the Train",
+ "create.ponder.train_schedule.header": "Using Train Schedules",
+ "create.ponder.train_schedule.text_1": "Schedules allow Trains to be controlled by other Drivers",
+ "create.ponder.train_schedule.text_2": "Right-click with the item in hand to open its Interface",
+ "create.ponder.train_schedule.text_3": "Once programmed, the Schedule can be handed off to a Train Driver",
+ "create.ponder.train_schedule.text_4": "Any mob or blaze burner sitting in front of Train Controls is an eligible conductor",
+ "create.ponder.train_schedule.text_5": "Creatures on a lead can be given their seat more conveniently",
+ "create.ponder.train_schedule.text_6": "Schedules can be retrieved from Drivers at any moment",
+ "create.ponder.train_signal_placement.header": "Placing Train Signals",
+ "create.ponder.train_signal_placement.text_1": "Select a Train Track then place the Signal nearby",
+ "create.ponder.train_signal_placement.text_2": "Signals control the flow of Trains not driven by players",
+ "create.ponder.train_signal_placement.text_3": "Scheduled Trains will never cross signals in the opposite direction",
+ "create.ponder.train_signal_placement.text_4": "...unless a second signal is added facing the opposite way.",
+ "create.ponder.train_signal_placement.text_5": "Nixie tubes can be attached to make a signal's lights more visible",
+ "create.ponder.train_signal_redstone.header": "Signals & Redstone",
+ "create.ponder.train_signal_redstone.text_1": "Signals can be forced red by a redstone signal",
+ "create.ponder.train_signal_redstone.text_2": "Conversely, red signals emit a comparator output",
+ "create.ponder.train_signal_signaling.header": "Collision Prevention with Signals",
+ "create.ponder.train_signal_signaling.text_1": "Train Signals divide a track into segments",
+ "create.ponder.train_signal_signaling.text_2": "If a Segment is occupied, no other Trains will be allowed entry",
+ "create.ponder.train_signal_signaling.text_3": "Thus, each Segment will contain only one Train at a time",
+ "create.ponder.train_signal_signaling.text_4": "A second Signal mode is available via the Wrench",
+ "create.ponder.train_signal_signaling.text_5": "Segments of a brass signal usually lead into standard signals",
+ "create.ponder.train_signal_signaling.text_6": "This special Signal can stop trains under a second condition",
+ "create.ponder.train_signal_signaling.text_7": "It will stop Trains, which, upon entering...",
+ "create.ponder.train_signal_signaling.text_8": "...would not be able to leave the Segment immediately",
+ "create.ponder.train_signal_signaling.text_9": "This helps keeping queued Trains out of a busy Segment",
+ "create.ponder.valve_handle.header": "Precise rotation using Valve Handles",
+ "create.ponder.valve_handle.text_1": "Valve handles can be used to rotate components by a precise angle",
+ "create.ponder.valve_handle.text_2": "The angle can be configured on the input panel",
+ "create.ponder.valve_handle.text_3": "Right-Click to activate one rotation",
+ "create.ponder.valve_handle.text_4": "Sneak-Right-Click to activate it in the opposite direction",
+ "create.ponder.valve_handle.text_5": "Mind that Bearings have to be specifically told not to disassemble",
+ "create.ponder.valve_handle.text_6": "Valve handles can be dyed for aesthetic purposes",
+ "create.ponder.valve_pipe.header": "Controlling Fluid flow using Valves",
+ "create.ponder.valve_pipe.text_1": "Valve pipes help control fluids propagating through pipe networks",
+ "create.ponder.valve_pipe.text_2": "Their shaft input controls whether fluid is currently allowed through",
+ "create.ponder.valve_pipe.text_3": "Given Rotational Force in the opening direction, the valve will open up",
+ "create.ponder.valve_pipe.text_4": "It can be closed again by reversing the input rotation",
+ "create.ponder.water_wheel.header": "Generating Rotational Force using Water Wheels",
+ "create.ponder.water_wheel.text_1": "Water Wheels draw force from adjacent Water Currents",
+ "create.ponder.water_wheel.text_2": "Covering additional sides will not improve its kinetic output further",
+ "create.ponder.water_wheel.text_3": "Use wood planks on the wheel to change its appearance",
+ "create.ponder.weighted_ejector.header": "Using Weighted Ejectors",
+ "create.ponder.weighted_ejector.text_1": "Sneak and Right-Click holding an Ejector to select its target location",
+ "create.ponder.weighted_ejector.text_10": "It is now limited to this stack size, and only activates when its held stack reaches this amount",
+ "create.ponder.weighted_ejector.text_11": "Mobs and Players will always trigger an Ejector when stepping on it",
+ "create.ponder.weighted_ejector.text_2": "The placed ejector will now launch objects to the marked location",
+ "create.ponder.weighted_ejector.text_3": "A valid target can be at any height or distance within range",
+ "create.ponder.weighted_ejector.text_4": "They cannot however be off to a side",
+ "create.ponder.weighted_ejector.text_5": "If no valid Target was selected, it will simply target the block directly in front",
+ "create.ponder.weighted_ejector.text_6": "Supply Rotational Force in order to charge it up",
+ "create.ponder.weighted_ejector.text_7": "Items placed on the ejector cause it to trigger",
+ "create.ponder.weighted_ejector.text_8": "If Inventories are targeted, the ejector will wait until there is space",
+ "create.ponder.weighted_ejector.text_9": "Using the value panel, a required Stack Size can be configured",
+ "create.ponder.weighted_ejector_redstone.header": "Controlling Weighted Ejectors with Redstone",
+ "create.ponder.weighted_ejector_redstone.text_1": "When powered by Redstone, Ejectors will not activate",
+ "create.ponder.weighted_ejector_redstone.text_2": "Observers can detect when Ejectors activate",
+ "create.ponder.weighted_ejector_tunnel.header": "Splitting item stacks using Weighted Ejectors",
+ "create.ponder.weighted_ejector_tunnel.text_1": "Combined with Brass Tunnels, Ejectors can split item stacks by specific amounts",
+ "create.ponder.weighted_ejector_tunnel.text_2": "First, configure the Brass Tunnel to 'Prefer Nearest', in order to prioritize its side output",
+ "create.ponder.weighted_ejector_tunnel.text_3": "The Stack Size set on the Ejector now determines the amount to be split off",
+ "create.ponder.weighted_ejector_tunnel.text_4": "While a new stack of the configured size exits the side output...",
+ "create.ponder.weighted_ejector_tunnel.text_5": "...the remainder will continue on its path",
+ "create.ponder.windmill_source.header": "Generating Rotational Force using Windmill Bearings",
+ "create.ponder.windmill_source.text_1": "Windmill Bearings attach to the block in front of them",
+ "create.ponder.windmill_source.text_2": "Create a movable structure with the help of Super Glue",
+ "create.ponder.windmill_source.text_3": "If enough Sail-like blocks are included, this can act as a Windmill",
+ "create.ponder.windmill_source.text_4": "Activated with Right-Click, the Windmill Bearing will start providing Rotational Force",
+ "create.ponder.windmill_source.text_5": "The Amount of Sail Blocks determine its Rotation Speed",
+ "create.ponder.windmill_source.text_6": "Use the value panel to configure its rotation direction",
+ "create.ponder.windmill_source.text_7": "Right-click the Bearing anytime to stop and edit the Structure again",
+ "create.ponder.windmill_structure.header": "Windmill Contraptions",
+ "create.ponder.windmill_structure.text_1": "Any Structure can count as a valid Windmill, as long as it contains at least 8 sail-like Blocks.",
+ "create.portal_track.blocked": "Target location blocked (%1$s,%2$s,%3$s)",
+ "create.portal_track.failed": "Cannot place portal track:",
+ "create.portal_track.missing": "Target portal not generated yet",
+ "create.potato_cannon.ammo.attack_damage": "%1$s Attack Damage",
+ "create.potato_cannon.ammo.knockback": "%1$s Knockback",
+ "create.potato_cannon.ammo.reload_ticks": "%1$s Reload Ticks",
+ "create.recipe.assembly.cutting": "Cut with Saw",
+ "create.recipe.assembly.deploying_item": "Deploy %1$s",
+ "create.recipe.assembly.junk": "Random salvage",
+ "create.recipe.assembly.next": "Next: %1$s",
+ "create.recipe.assembly.pressing": "Process in Press",
+ "create.recipe.assembly.progress": "Progress: %1$s/%2$s",
+ "create.recipe.assembly.repeat": "Repeat Sequence %1$s Times",
+ "create.recipe.assembly.spout_filling_fluid": "Spout %1$s",
+ "create.recipe.assembly.step": "Step %1$s:",
+ "create.recipe.automatic_brewing": "Automated Brewing",
+ "create.recipe.automatic_packing": "Automated Packing",
+ "create.recipe.automatic_shaped": "Automated Shaped Crafting",
+ "create.recipe.automatic_shapeless": "Automated Shapeless Crafting",
+ "create.recipe.block_cutting": "Block Cutting",
+ "create.recipe.crushing": "Crushing",
+ "create.recipe.deploying": "Deploying",
+ "create.recipe.deploying.not_consumed": "Not Consumed",
+ "create.recipe.draining": "Item Draining",
+ "create.recipe.fan_blasting": "Bulk Blasting",
+ "create.recipe.fan_blasting.fan": "Fan behind Lava",
+ "create.recipe.fan_haunting": "Bulk Haunting",
+ "create.recipe.fan_haunting.fan": "Fan behind Soul Fire",
+ "create.recipe.fan_smoking": "Bulk Smoking",
+ "create.recipe.fan_smoking.fan": "Fan behind Fire",
+ "create.recipe.fan_washing": "Bulk Washing",
+ "create.recipe.fan_washing.fan": "Fan behind Flowing Water",
+ "create.recipe.heat_requirement.heated": "Heated",
+ "create.recipe.heat_requirement.none": "No Heating Required",
+ "create.recipe.heat_requirement.superheated": "Super-Heated",
+ "create.recipe.item_application": "Manual Item Application",
+ "create.recipe.item_application.any_axe": "Any Axe",
+ "create.recipe.mechanical_crafting": "Mechanical Crafting",
+ "create.recipe.milling": "Milling",
+ "create.recipe.mixing": "Mixing",
+ "create.recipe.mystery_conversion": "Mysterious Conversion",
+ "create.recipe.packing": "Compacting",
+ "create.recipe.pressing": "Pressing",
+ "create.recipe.processing.chance": "%1$s%% Chance",
+ "create.recipe.sandpaper_polishing": "Sandpaper Polishing",
+ "create.recipe.sawing": "Sawing",
+ "create.recipe.sequenced_assembly": "Recipe Sequence",
+ "create.recipe.spout_filling": "Filling by Spout",
+ "create.recipe.wood_cutting": "Wood Cutting",
+ "create.redstone_requester.keeper_connected": "Connected to Stock keeper successfully",
+ "create.redstone_requester.keeper_too_far_away": "Requester too far away from Stock keeper",
+ "create.schedule.applied_to_train": "Train is now following this Schedule",
+ "create.schedule.auto_removed_from_train": "Auto-Schedule discarded",
+ "create.schedule.condition.delay": "Scheduled Delay",
+ "create.schedule.condition.delay.status": "Departs in %1$s",
+ "create.schedule.condition.delay_short": "Wait: %1$s",
+ "create.schedule.condition.editor": "Condition Editor",
+ "create.schedule.condition.fluid_threshold": "Fluid Cargo Condition",
+ "create.schedule.condition.for_x_time": "for %1$s",
+ "create.schedule.condition.idle": "Cargo Inactivity",
+ "create.schedule.condition.idle.status": "Cargo Idle for %1$s",
+ "create.schedule.condition.idle_short": "Cargo Idle: %1$s",
+ "create.schedule.condition.item_threshold": "Item Cargo Condition",
+ "create.schedule.condition.player_count": "Players Seated",
+ "create.schedule.condition.player_count.condition": "Conditional",
+ "create.schedule.condition.player_count.exactly": "Exactly",
+ "create.schedule.condition.player_count.or_above": "Or above",
+ "create.schedule.condition.player_count.players": "Players",
+ "create.schedule.condition.player_count.seated": "%1$s seated",
+ "create.schedule.condition.player_count.status": "Passengers: %1$s/%2$s",
+ "create.schedule.condition.player_count.summary": "%1$s Player",
+ "create.schedule.condition.player_count.summary_plural": "%1$s Players",
+ "create.schedule.condition.powered": "Station Powered",
+ "create.schedule.condition.powered.status": "Waiting for redstone",
+ "create.schedule.condition.redstone_link": "Redstone Link",
+ "create.schedule.condition.redstone_link.frequency_powered": "Frequency powered:",
+ "create.schedule.condition.redstone_link.frequency_state": "Frequency state:",
+ "create.schedule.condition.redstone_link.frequency_unpowered": "Frequency not powered:",
+ "create.schedule.condition.redstone_link.powered": "Powered",
+ "create.schedule.condition.redstone_link.status": "Waiting for redstone link",
+ "create.schedule.condition.redstone_link.unpowered": "Not powered",
+ "create.schedule.condition.redstone_link_off": "Link Off",
+ "create.schedule.condition.redstone_link_on": "Link On",
+ "create.schedule.condition.threshold.anything": "Anything",
+ "create.schedule.condition.threshold.buckets": "Buckets",
+ "create.schedule.condition.threshold.equal": "exactly",
+ "create.schedule.condition.threshold.greater": "more than",
+ "create.schedule.condition.threshold.item_measure": "Item Measure",
+ "create.schedule.condition.threshold.items": "Items",
+ "create.schedule.condition.threshold.less": "less than",
+ "create.schedule.condition.threshold.matching_content": "Matching Content",
+ "create.schedule.condition.threshold.place_item": "Reference Item",
+ "create.schedule.condition.threshold.place_item_2": "Filters can be used",
+ "create.schedule.condition.threshold.place_item_3": "Leave empty to match any",
+ "create.schedule.condition.threshold.stacks": "Stacks",
+ "create.schedule.condition.threshold.status": "Cargo: %1$s/%2$s %3$s",
+ "create.schedule.condition.threshold.train_holds": "Train holds %1$s",
+ "create.schedule.condition.threshold.x_units_of_item": "%1$s %2$s of %3$s",
+ "create.schedule.condition.time_of_day": "Time of Day",
+ "create.schedule.condition.time_of_day.digital_format": "%1$s:%3$s %4$s",
+ "create.schedule.condition.time_of_day.rotation": "Rotation",
+ "create.schedule.condition.time_of_day.rotation.every_0_15": "Every 0:15",
+ "create.schedule.condition.time_of_day.rotation.every_0_30": "Every 0:30",
+ "create.schedule.condition.time_of_day.rotation.every_0_45": "Every 0:45",
+ "create.schedule.condition.time_of_day.rotation.every_1": "Every 1:00",
+ "create.schedule.condition.time_of_day.rotation.every_12": "Every 12:00",
+ "create.schedule.condition.time_of_day.rotation.every_2": "Every 2:00",
+ "create.schedule.condition.time_of_day.rotation.every_24": "Every Day",
+ "create.schedule.condition.time_of_day.rotation.every_3": "Every 3:00",
+ "create.schedule.condition.time_of_day.rotation.every_4": "Every 4:00",
+ "create.schedule.condition.time_of_day.rotation.every_6": "Every 6:00",
+ "create.schedule.condition.time_of_day.scheduled": "Scheduled Time: %1$s",
+ "create.schedule.condition.time_of_day.status": "Departs at ",
+ "create.schedule.condition.unloaded": "Chunk Unloaded",
+ "create.schedule.condition.unloaded.status": "Waiting for chunk unload",
+ "create.schedule.condition_type": "Continue if/after:",
+ "create.schedule.continued": "Schedule resumed",
+ "create.schedule.instruction.address_filter_edit_box": "Retrieve Packages with this Address",
+ "create.schedule.instruction.address_filter_edit_box_1": "Use * as a text wildcard",
+ "create.schedule.instruction.address_filter_edit_box_2": "Train may navigate anywhere to collect it",
+ "create.schedule.instruction.address_filter_edit_box_3": "Add another instruction to make it return",
+ "create.schedule.instruction.destination": "Travel to Station",
+ "create.schedule.instruction.destination.summary": "Next Stop:",
+ "create.schedule.instruction.editor": "Instruction Editor",
+ "create.schedule.instruction.filter_edit_box": "Station Name",
+ "create.schedule.instruction.filter_edit_box_1": "Use * as a text wildcard",
+ "create.schedule.instruction.filter_edit_box_2": "Example: 'My Station, Platform *'",
+ "create.schedule.instruction.filter_edit_box_3": "Train picks nearest unoccupied match",
+ "create.schedule.instruction.name_edit_box": "Schedule Title",
+ "create.schedule.instruction.name_edit_box_1": "Affects text shown on displays",
+ "create.schedule.instruction.name_edit_box_2": "Defaults to next destination's name",
+ "create.schedule.instruction.package_delivery": "Deliver Package",
+ "create.schedule.instruction.package_delivery.summary": "Deliver first package in cargo",
+ "create.schedule.instruction.package_delivery.summary_1": "Drives to the station with",
+ "create.schedule.instruction.package_delivery.summary_2": "the correct postbox",
+ "create.schedule.instruction.package_retrieval": "Retrieve Package",
+ "create.schedule.instruction.package_retrieval.summary": "Fetch a package addressed to:",
+ "create.schedule.instruction.package_retrieval.summary_1": "Drives to a postbox with",
+ "create.schedule.instruction.package_retrieval.summary_2": "an undelivered package",
+ "create.schedule.instruction.rename": "Update Schedule Title",
+ "create.schedule.instruction.rename.summary": "New Title:",
+ "create.schedule.instruction.throttle": "Limit Max Speed",
+ "create.schedule.instruction.throttle.summary": "Change Max Speed to %1$s",
+ "create.schedule.instruction.throttle_edit_box": "Throttle",
+ "create.schedule.instruction.throttle_edit_box_1": "Affects the top speed of the Train",
+ "create.schedule.instruction_type": "Next Action:",
+ "create.schedule.loop": "Loop Forever",
+ "create.schedule.loop1": "Schedule starts over",
+ "create.schedule.loop2": "when completed",
+ "create.schedule.no_stops": "This Schedule does not have any Stops yet",
+ "create.schedule.non_controlling_seat": "Conductor needs to sit in front of a Controls block",
+ "create.schedule.remove_with_empty_hand": "Remove current Schedule with an Empty Hand",
+ "create.schedule.removed_from_train": "Schedule retrieved from Train",
+ "create.schedule.reset": "Reset Progress",
+ "create.schedule.skip": "Skip current Stop",
+ "create.schematic.error": "Schematic failed to Load - Check Game Logs",
+ "create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead",
+ "create.schematic.mirror": "Mirror",
+ "create.schematic.mirror.frontBack": "Front-Back",
+ "create.schematic.mirror.leftRight": "Left-Right",
+ "create.schematic.mirror.none": "None",
+ "create.schematic.position": "Position",
+ "create.schematic.rotation": "Rotation",
+ "create.schematic.rotation.cw180": "Clockwise 180",
+ "create.schematic.rotation.cw270": "Clockwise 270",
+ "create.schematic.rotation.cw90": "Clockwise 90",
+ "create.schematic.rotation.none": "None",
+ "create.schematic.tool.deploy": "Position",
+ "create.schematic.tool.deploy.description.0": "Moves the structure to a location.",
+ "create.schematic.tool.deploy.description.1": "Right-Click on the ground to place.",
+ "create.schematic.tool.deploy.description.2": "Hold [Ctrl] to select at a fixed distance.",
+ "create.schematic.tool.deploy.description.3": "[Ctrl]-Scroll to change the distance.",
+ "create.schematic.tool.flip": "Mirror",
+ "create.schematic.tool.flip.description.0": "Flips the Schematic along the face you select.",
+ "create.schematic.tool.flip.description.1": "Point at the Schematic and [CTRL]-Scroll to flip it.",
+ "create.schematic.tool.flip.description.2": "",
+ "create.schematic.tool.flip.description.3": "",
+ "create.schematic.tool.move": "Move XZ",
+ "create.schematic.tool.move.description.0": "Shifts the Schematic Horizontally.",
+ "create.schematic.tool.move.description.1": "Point at the Schematic and [CTRL]-Scroll to push it.",
+ "create.schematic.tool.move.description.2": "",
+ "create.schematic.tool.move.description.3": "",
+ "create.schematic.tool.move_y": "Move Y",
+ "create.schematic.tool.move_y.description.0": "Shifts the Schematic Vertically.",
+ "create.schematic.tool.move_y.description.1": "[CTRL]-Scroll to move it up/down.",
+ "create.schematic.tool.move_y.description.2": "",
+ "create.schematic.tool.move_y.description.3": "",
+ "create.schematic.tool.print": "Print",
+ "create.schematic.tool.print.description.0": "Instantly places the structure in the world.",
+ "create.schematic.tool.print.description.1": "[Right-Click] to confirm placement at the current location.",
+ "create.schematic.tool.print.description.2": "This tool is for Creative Mode only.",
+ "create.schematic.tool.print.description.3": "",
+ "create.schematic.tool.rotate": "Rotate",
+ "create.schematic.tool.rotate.description.0": "Rotates the Schematic around its center.",
+ "create.schematic.tool.rotate.description.1": "[CTRL]-Scroll to rotate by 90 Degrees.",
+ "create.schematic.tool.rotate.description.2": "",
+ "create.schematic.tool.rotate.description.3": "",
+ "create.schematicAndQuill.abort": "Removed selection.",
+ "create.schematicAndQuill.convert": "Save and Upload Immediately",
+ "create.schematicAndQuill.dimensions": "Schematic Size: %1$sx%2$sx%3$s",
+ "create.schematicAndQuill.failed": "Failed to save schematic, check logs for details",
+ "create.schematicAndQuill.fallbackName": "My Schematic",
+ "create.schematicAndQuill.firstPos": "First position set.",
+ "create.schematicAndQuill.instant_failed": "Schematic instant-upload failed, check logs for details",
+ "create.schematicAndQuill.noTarget": "Hold [Ctrl] to select Air blocks.",
+ "create.schematicAndQuill.saved": "Saved as %1$s",
+ "create.schematicAndQuill.secondPos": "Second position set.",
+ "create.schematicAndQuill.title": "Schematic Name:",
+ "create.schematicannon.status.clearing": "Clearing Blocks",
+ "create.schematicannon.status.finished": "Finished",
+ "create.schematicannon.status.idle": "Idle",
+ "create.schematicannon.status.missingBlock": "Missing Item:",
+ "create.schematicannon.status.noGunpowder": "Out of Gunpowder",
+ "create.schematicannon.status.paused": "Paused",
+ "create.schematicannon.status.placing": "Placing",
+ "create.schematicannon.status.ready": "Ready",
+ "create.schematicannon.status.running": "Running",
+ "create.schematicannon.status.schematicErrored": "Incompatible Blocks",
+ "create.schematicannon.status.schematicErroredCheckLogs": "Check Server Logs",
+ "create.schematicannon.status.schematicExpired": "Schematic File Expired",
+ "create.schematicannon.status.schematicInvalid": "Schematic Invalid",
+ "create.schematicannon.status.schematicNotPlaced": "Schematic not Positioned",
+ "create.schematicannon.status.searching": "Searching",
+ "create.schematicannon.status.skipping": "Skipping",
+ "create.schematicannon.status.stopped": "Stopped",
+ "create.schematicannon.status.targetNotLoaded": "Target is not loaded",
+ "create.schematicannon.status.targetOutsideRange": "Target too far away",
+ "create.schematics.maxAllowedSize": "The maximum allowed schematic file size is:",
+ "create.schematics.synchronizing": "Syncing...",
+ "create.schematics.uploadTooLarge": "Your schematic exceeds limitations specified by the server.",
+ "create.schematics.wrongFormat": "The schematic you are trying to upload is either corrupt or encoded wrongly! It should be encoded with GZIP compatible format.",
+ "create.station.assemble_train": "Assemble Train",
+ "create.station.assembly_title": "Train Assembly",
+ "create.station.cancel": "Cancel Assembly",
+ "create.station.carriage_number": "Carriage %1$s:",
+ "create.station.close": "Close Window",
+ "create.station.create_train": "Create new Train",
+ "create.station.disassemble_train": "Disassemble Train",
+ "create.station.failed": "Assembly Failed",
+ "create.station.how_to": "Use Train Casing on highlighted Tracks to create bogeys.",
+ "create.station.how_to_1": "Remove bogeys by breaking the block on top.",
+ "create.station.how_to_2": "Build carriages attached to one or two bogeys each.",
+ "create.station.icon_type": "Icon Type",
+ "create.station.idle": "Station is Idle",
+ "create.station.more_bogeys": "%1$s Bogeys",
+ "create.station.no_assembly_curve": "Cannot build trains",
+ "create.station.no_assembly_curve_1": "on curved tracks",
+ "create.station.no_assembly_diagonal": "Cannot build trains",
+ "create.station.no_assembly_diagonal_1": "on diagonal tracks",
+ "create.station.no_bogeys": "No Bogeys",
+ "create.station.one_bogey": "1 Bogey",
+ "create.station.remove_auto_schedule": "Discard Auto-Schedule",
+ "create.station.remove_schedule": "Retrieve Schedule",
+ "create.station.retry": "Resolve this and retry",
+ "create.station.train_map_color": "Color on Maps",
+ "create.station.train_not_aligned": "Cannot disassemble,",
+ "create.station.train_not_aligned_1": "not all carriages aligned",
+ "create.stock_keeper.cash_register_full": "Purchase cancelled: Not enough space in Cash Register",
+ "create.stock_keeper.keeper_missing": "Stock keeper missing",
+ "create.stock_keeper.limited_stock": "Stock limit reached",
+ "create.stock_keeper.locked": "Interact using a Shopping list, direct access has been locked",
+ "create.stock_keeper.no_price_set": "Shop owner must set a price first",
+ "create.stock_keeper.out_of_stock": "Out of Stock",
+ "create.stock_keeper.shopping_list_empty_hand": "Empty hand required to start a shopping list",
+ "create.stock_keeper.stock_level_too_low": "Purchase cancelled: Stock levels lower than expected",
+ "create.stock_keeper.too_broke": "You cannot afford this purchase",
+ "create.stock_keeper.use_list_to_add_purchases": "Use this list to add more to your purchase",
+ "create.stock_keeper.wrong_network": "This list is for another shop",
+ "create.stock_ticker.click_to_retrieve": "Click to retrieve",
+ "create.stock_ticker.contains_payments": "Contains payments:",
+ "create.stock_ticker.keeper_missing": "Seat a mob or blaze burner next to this block to use it",
+ "create.subtitle.blaze_munch": "Blaze Burner munches",
+ "create.subtitle.cardboard_bonk": "Resonant bonk",
+ "create.subtitle.cogs": "Cogwheels rumble",
+ "create.subtitle.confirm": "Affirmative ding",
+ "create.subtitle.confirm_2": "Affirmative ding",
+ "create.subtitle.contraption_assemble": "Contraption moves",
+ "create.subtitle.contraption_disassemble": "Contraption stops",
+ "create.subtitle.controller_click": "Controller clicks",
+ "create.subtitle.controller_put": "Controller thumps",
+ "create.subtitle.controller_take": "Lectern empties",
+ "create.subtitle.copper_armor_equip": "Diving equipment clinks",
+ "create.subtitle.crafter_click": "Crafter clicks",
+ "create.subtitle.crafter_craft": "Crafter crafts",
+ "create.subtitle.cranking": "Hand Crank turns",
+ "create.subtitle.crushing_1": "Crushing noises",
+ "create.subtitle.deny": "Declining boop",
+ "create.subtitle.depot_plop": "Item lands",
+ "create.subtitle.depot_slide": "Item slides",
+ "create.subtitle.desk_bell": "Reception bell dings",
+ "create.subtitle.frogport_catch": "Frogport catches package",
+ "create.subtitle.frogport_close": "Frogport shuts",
+ "create.subtitle.frogport_deposit": "Frogport places package",
+ "create.subtitle.frogport_open": "Frogport opens",
+ "create.subtitle.funnel_flap": "Funnel flaps",
+ "create.subtitle.fwoomp": "Resonant fwoomp",
+ "create.subtitle.haunted_bell_convert": "Haunted Bell awakens",
+ "create.subtitle.haunted_bell_use": "Haunted Bell tolls",
+ "create.subtitle.item_hatch": "Item Hatch opens",
+ "create.subtitle.mechanical_press_activation": "Mechanical Press clangs",
+ "create.subtitle.mechanical_press_activation_belt": "Mechanical Press bonks",
+ "create.subtitle.mixing": "Mixing noises",
+ "create.subtitle.package_pop": "Package breaks",
+ "create.subtitle.packager": "Packager packages",
+ "create.subtitle.peculiar_bell_use": "Peculiar Bell tolls",
+ "create.subtitle.potato_hit": "Vegetable impacts",
+ "create.subtitle.sanding_long": "Sanding noises",
+ "create.subtitle.sanding_short": "Sanding noises",
+ "create.subtitle.saw_activate_stone": "Mechanical Saw activates",
+ "create.subtitle.saw_activate_wood": "Mechanical Saw activates",
+ "create.subtitle.schematicannon_finish": "Schematicannon dings",
+ "create.subtitle.schematicannon_launch_block": "Schematicannon fires",
+ "create.subtitle.scroll_value": "Scroll-input clicks",
+ "create.subtitle.slime_added": "Slime squishes",
+ "create.subtitle.spout": "Spout spurts",
+ "create.subtitle.steam": "Steam noises",
+ "create.subtitle.stock_link": "Stock link reacts",
+ "create.subtitle.stock_ticker_request": "Stock ticker requests",
+ "create.subtitle.stock_ticker_trade": "Stock ticker goes 'ka-ching!'",
+ "create.subtitle.train": "Bogey wheels rumble",
+ "create.subtitle.train3": "Bogey wheels rumble muffled",
+ "create.subtitle.whistle": "Whistling",
+ "create.subtitle.whistle_high": "High whistling",
+ "create.subtitle.whistle_low": "Low whistling",
+ "create.subtitle.whistle_train": "Whistling",
+ "create.subtitle.whistle_train_low": "Low whistling",
+ "create.subtitle.whistle_train_manual": "Train honks",
+ "create.subtitle.whistle_train_manual_low": "Train honks",
+ "create.subtitle.worldshaper_place": "Worldshaper zaps",
+ "create.subtitle.wrench_remove": "Component breaks",
+ "create.subtitle.wrench_rotate": "Wrench used",
+ "create.super_glue.abort": "Selection discarded",
+ "create.super_glue.cannot_reach": "Selected blocks must be connected",
+ "create.super_glue.click_to_confirm": "Click again to confirm",
+ "create.super_glue.click_to_discard": "Sneak-click to discard selection",
+ "create.super_glue.first_pos": "First position selected",
+ "create.super_glue.not_enough": "Not enough glue in inventory",
+ "create.super_glue.success": "Applying Glue...",
+ "create.super_glue.too_far": "Selected area is too big",
+ "create.symmetry.mirror.doublePlane": "Rectangular",
+ "create.symmetry.mirror.plane": "Mirror Once",
+ "create.symmetry.mirror.triplePlane": "Octagonal",
+ "create.table_cloth.amount": "Amount",
+ "create.table_cloth.hand_to_shop_keeper": "Hand this to a shop keeper",
+ "create.table_cloth.price_per_order": "Price per Order",
+ "create.table_cloth.shop_configured": "Item shop configured",
+ "create.table_cloth.shopping_list_discarded": "Shopping list discarded",
+ "create.table_cloth.sneak_click_discard": "Sneak-Click to discard",
+ "create.table_cloth.total_cost": "Total cost: ",
+ "create.terrainzapper.leftClickToSet": "Left-Click a Block to set Material",
+ "create.terrainzapper.shiftRightClickToSet": "Shift-Right-Click to Select a Shape",
+ "create.terrainzapper.usingBlock": "Using: %1$s",
+ "create.toolbox.depositAll": "Return items to nearby Toolboxes",
+ "create.toolbox.depositBox": "Return items to Toolbox",
+ "create.toolbox.detach": "Stop tracking and keep item",
+ "create.toolbox.outOfRange": "Toolbox of held item not in Range",
+ "create.toolbox.unequip": "Unequip: %1$s",
+ "create.tooltip.analogStrength": "Analog Strength: %1$s/15",
+ "create.tooltip.brass_tunnel.contains": "Currently distributing:",
+ "create.tooltip.brass_tunnel.contains_entry": "> %1$s x%2$s",
+ "create.tooltip.brass_tunnel.retrieve": "Right-Click to retrieve",
+ "create.tooltip.capacityProvided": "Kinetic Stress Capacity:",
+ "create.tooltip.capacityProvided.high": "Large",
+ "create.tooltip.capacityProvided.low": "Small",
+ "create.tooltip.capacityProvided.medium": "Medium",
+ "create.tooltip.chute.contains": "Contains: %1$s x%2$s",
+ "create.tooltip.chute.fans_pull_down": "Fans pull from Below",
+ "create.tooltip.chute.fans_pull_up": "Fans pull from Above",
+ "create.tooltip.chute.fans_push_down": "Fans push from Above",
+ "create.tooltip.chute.fans_push_up": "Fans push from Below",
+ "create.tooltip.chute.header": "Chute Information",
+ "create.tooltip.chute.items_move_down": "Items move Downward",
+ "create.tooltip.chute.items_move_up": "Items move Upward",
+ "create.tooltip.chute.no_fans_attached": "No attached fans",
+ "create.tooltip.deployer.contains": "Item: %1$s x%2$s",
+ "create.tooltip.deployer.header": "Deployer Information",
+ "create.tooltip.deployer.punching": "Mode: Attack",
+ "create.tooltip.deployer.using": "Mode: Use",
+ "create.tooltip.generationSpeed": "Generates at %1$s %2$s",
+ "create.tooltip.holdForControls": "Hold [%1$s] for Controls",
+ "create.tooltip.holdForDescription": "Hold [%1$s] for Summary",
+ "create.tooltip.keyCtrl": "Ctrl",
+ "create.tooltip.keyShift": "Shift",
+ "create.tooltip.speedRequirement": "Speed Requirement:",
+ "create.tooltip.speedRequirement.fast": "Fast",
+ "create.tooltip.speedRequirement.medium": "Moderate",
+ "create.tooltip.speedRequirement.none": "None",
+ "create.tooltip.speedRequirement.slow": "Slow",
+ "create.tooltip.stressImpact": "Kinetic Stress Impact:",
+ "create.tooltip.stressImpact.high": "High",
+ "create.tooltip.stressImpact.low": "Low",
+ "create.tooltip.stressImpact.medium": "Moderate",
+ "create.tooltip.stressImpact.overstressed": "Overstressed",
+ "create.tooltip.up_to": "Up to %1$s",
+ "create.track.ascending_s_curve": "Cannot create sloped S-Bends",
+ "create.track.hold_for_smooth_curve": "Hold %1$s for maximized turn",
+ "create.track.junction_start": "Cannot start connection from a Junction",
+ "create.track.leave_slope_ascending": "Cannot leave this slope while ascending",
+ "create.track.leave_slope_descending": "Cannot leave this slope while descending",
+ "create.track.not_enough_pavement": "Not holding enough pavement blocks",
+ "create.track.not_enough_tracks": "Not holding enough tracks",
+ "create.track.opposing_slopes": "Cannot connect opposing slopes",
+ "create.track.original_missing": "Original block removed, Sneak-click to reset",
+ "create.track.perpendicular": "Cannot connect perpendicularly",
+ "create.track.second_point": "Place track or select a second point",
+ "create.track.selection_cleared": "Selection Cleared",
+ "create.track.slope_turn": "Cannot enter or leave slope on a turn",
+ "create.track.too_far": "Too far away",
+ "create.track.too_sharp": "Turn too sharp",
+ "create.track.too_steep": "Slope too steep",
+ "create.track.turn_90": "Can only turn up to 90 Degrees",
+ "create.track.turn_start": "Cannot start connection from a Turn",
+ "create.track.valid_connection": "Can Connect ✔",
+ "create.track_signal.cannot_change_mode": "Unable to switch mode of this Signal",
+ "create.track_signal.mode_change.cross_signal": "-> Allow passage if section fully traversable",
+ "create.track_signal.mode_change.entry_signal": "-> Allow passage if section unoccupied",
+ "create.track_signal.mode_controlled_by_computer": "Signal mode is controlled by computer",
+ "create.track_target.clear": "Cleared track selection",
+ "create.track_target.invalid": "Cannot target this track here",
+ "create.track_target.missing": "Right-click the targeted train track first",
+ "create.track_target.no_junctions": "Targeted track cannot be an intersection",
+ "create.track_target.occupied": "Targeted track is occupied",
+ "create.track_target.set": "Targeted track selected",
+ "create.track_target.success": "Successfully bound to targeted track",
+ "create.track_target.too_far": "Targeted track is too far from here",
+ "create.train.arrived_at": "Arrived at %1$s",
+ "create.train.cannot_relocate_moving": "Cannot relocate a moving Train",
+ "create.train.departing_from": "Departing from %1$s",
+ "create.train.relocate": "Click a Track to Relocate %1$s to. Sneak-Click to abort",
+ "create.train.relocate.abort": "Relocation aborted",
+ "create.train.relocate.invalid": "Cannot relocate Train to here",
+ "create.train.relocate.success": "Relocation successful",
+ "create.train.relocate.too_far": "Cannot relocate Train this far away",
+ "create.train.relocate.valid": "Can relocate to here, Click to Confirm",
+ "create.train.status": " Information about Train: %1$s",
+ "create.train.status.back_on_track": "Train is back on Track",
+ "create.train.status.collision": "Collision with other Train",
+ "create.train.status.collision.where": "In %1$s near [%2$s]",
+ "create.train.status.coupling_stress": "Forced stop due to Stress on Couplings",
+ "create.train.status.double_portal": "A Carriage cannot enter a portal whilst leaving another",
+ "create.train.status.end_of_track": "A Carriage has reached the end of its Track",
+ "create.train.status.found_driver": "A new driver has been found",
+ "create.train.status.missing_driver": "Driver has gone missing",
+ "create.train.status.navigation_success": "Navigation succeeded",
+ "create.train.status.no_match": "No station on graph matches '%1$s'",
+ "create.train.status.no_package_target": "A package in cargo has no postbox that matches its address",
+ "create.train.status.no_path": "No suitable path to the next Scheduled destination could be found",
+ "create.train.status.opposite_driver": "Path requires a driver facing the opposite direction",
+ "create.train.status.paused_for_manual": "Schedule paused for manual controls",
+ "create.train.status.track_missing": "Tracks are missing beneath the Train",
+ "create.train.unnamed": "Unnamed Train",
+ "create.train_assembly.bogey_created": "Bogey created. Click again to cycle type",
+ "create.train_assembly.bogeys_too_close": "Bogeys %1$s and %2$s are too close to each other",
+ "create.train_assembly.frontmost_bogey_at_station": "Frontmost Bogey must be at Station Marker",
+ "create.train_assembly.no_bogeys": "No Bogeys Found",
+ "create.train_assembly.no_controls": "Attach at least one forward-facing Train Controls block (Are you missing Super Glue?)",
+ "create.train_assembly.not_connected_in_order": "Bogeys are not connected in order",
+ "create.train_assembly.nothing_attached": "No structure attached to Bogey %1$s",
+ "create.train_assembly.requires_casing": "Use Train Casing to create bogeys on tracks",
+ "create.train_assembly.sideways_controls": "Train Controls cannot face sideways",
+ "create.train_assembly.single_bogey_carriage": "This Bogey type cannot support a carriage on its own",
+ "create.train_assembly.too_many_bogeys": "Too many Bogeys attached: %1$s",
+ "create.train_map.cannot_traverse_section": " Cannot fully traverse",
+ "create.train_map.conductor_missing": " Conductor Missing",
+ "create.train_map.derailed": " Derailed",
+ "create.train_map.for_other_train": " for %1$s",
+ "create.train_map.fuel_boosted": " Fuel boosted ✔",
+ "create.train_map.navigation_failed": " Navigation Failed",
+ "create.train_map.player_controlled": " -> Controlled by Player",
+ "create.train_map.redstone_powered": " Redstone Powered",
+ "create.train_map.schedule_interrupted": " Schedule Interrupted",
+ "create.train_map.section_reserved": " Section reserved",
+ "create.train_map.toggle": "Train network overlay",
+ "create.train_map.train_at_station": " >| %1$s",
+ "create.train_map.train_moving_to_station": " >> %1$s (%2$sm)",
+ "create.train_map.train_owned_by": "by %1$s",
+ "create.train_map.waiting_at_signal": " Waiting at Signal",
+ "create.tunnel.selection_mode.forced_round_robin": "Forced Round Robin",
+ "create.tunnel.selection_mode.forced_split": "Forced Split",
+ "create.tunnel.selection_mode.prefer_nearest": "Prefer Nearest",
+ "create.tunnel.selection_mode.randomize": "Randomize",
+ "create.tunnel.selection_mode.round_robin": "Round Robin",
+ "create.tunnel.selection_mode.split": "Split",
+ "create.tunnel.selection_mode.synchronize": "Synchronize Inputs",
+ "create.weighted_ejector.no_target": "Ejecting to Adjacent block (No Target was Selected)",
+ "create.weighted_ejector.stack_size": "Ejected Stack Size",
+ "create.weighted_ejector.target_not_valid": "Ejecting to Adjacent block (Target was not Valid)",
+ "create.weighted_ejector.target_set": "Target Selected",
+ "create.weighted_ejector.targeting": "Ejecting to [%1$s,%2$s,%3$s]",
+ "death.attack.create.crush": "%1$s was processed by Crushing Wheels",
+ "death.attack.create.crush.player": "%1$s was thrown into Crushing Wheels by %2$s",
+ "death.attack.create.cuckoo_surprise": "%1$s was blown up by tampered cuckoo clock",
+ "death.attack.create.cuckoo_surprise.player": "%1$s was blown up by tampered cuckoo clock",
+ "death.attack.create.fan_fire": "%1$s got smoked by an Encased Fan",
+ "death.attack.create.fan_fire.player": "%1$s was thrown into a smoker by %2$s",
+ "death.attack.create.fan_lava": "%1$s was incinerated by an Encased Fan",
+ "death.attack.create.fan_lava.player": "%1$s was thrown into a smelter by %2$s",
+ "death.attack.create.mechanical_drill": "%1$s was impaled by a Mechanical Drill",
+ "death.attack.create.mechanical_drill.player": "%1$s was thrown in front of a Drill by %2$s",
+ "death.attack.create.mechanical_roller": "%1$s was flattened by a Mechanical Roller",
+ "death.attack.create.mechanical_roller.player": "%1$s was thrown in front of a Mechanical Roller by %2$s",
+ "death.attack.create.mechanical_saw": "%1$s got cut in half by a Mechanical Saw",
+ "death.attack.create.mechanical_saw.player": "%1$s was thrown into a Saw by %2$s",
+ "death.attack.create.potato_cannon": "%1$s was shot by %2$s's Potato Cannon",
+ "death.attack.create.potato_cannon.item": "%1$s was shot by %2$s using %3$s",
+ "death.attack.create.run_over": "%1$s was run over by %2$s",
+ "enchantment.create.capacity": "Capacity",
+ "enchantment.create.capacity.desc": "Increases Backtank air capacity.",
+ "enchantment.create.potato_recovery": "Potato Recovery",
+ "enchantment.create.potato_recovery.desc": "Potato Cannon projectiles have a chance to be reused.",
+ "entity.create.carriage_contraption": "Carriage Contraption",
+ "entity.create.contraption": "Contraption",
+ "entity.create.crafting_blueprint": "Crafting Blueprint",
+ "entity.create.gantry_contraption": "Gantry Contraption",
+ "entity.create.package": "Package",
+ "entity.create.potato_projectile": "Potato Projectile",
+ "entity.create.seat": "Seat",
+ "entity.create.stationary_contraption": "Stationary Contraption",
+ "entity.create.super_glue": "Super Glue",
+ "fluid.create.chocolate": "Chocolate",
+ "fluid.create.honey": "Honey",
+ "fluid.create.potion": "Potion",
+ "fluid.create.tea": "Builder's Tea",
+ "item.create.andesite_alloy": "Andesite Alloy",
+ "item.create.attribute_filter": "Attribute Filter",
+ "item.create.attribute_filter.tooltip.behaviour1": "Opens the _configuration interface_.",
+ "item.create.attribute_filter.tooltip.condition1": "When R-Clicked",
+ "item.create.attribute_filter.tooltip.summary": "_Matches items_ against a set of _attributes_ or _categories_. Can be used in _Filter Slots_ of Create's Components",
+ "item.create.bar_of_chocolate": "Bar of Chocolate",
+ "item.create.belt_connector": "Mechanical Belt",
+ "item.create.blaze_cake": "Blaze Cake",
+ "item.create.blaze_cake.tooltip.summary": "A Delicious treat for your hard-working _Blaze Burners_. Gets them all fired up!",
+ "item.create.blaze_cake_base": "Blaze Cake Base",
+ "item.create.brass_hand": "Brass Hand",
+ "item.create.brass_ingot": "Brass Ingot",
+ "item.create.brass_nugget": "Brass Nugget",
+ "item.create.brass_sheet": "Brass Sheet",
+ "item.create.builders_tea": "Builder's Tea",
+ "item.create.builders_tea.tooltip.summary": "The perfect drink to get the day started- _Motivating_ and _Saturating._",
+ "item.create.cardboard": "Cardboard",
+ "item.create.cardboard_armor.tooltip.behaviour1": "_Sneak_ to activate an inconspicuous _disguise_.",
+ "item.create.cardboard_armor.tooltip.condition1": "When full set equipped",
+ "item.create.cardboard_armor.tooltip.summary": "_Wearable_ _equipment_ for increased _style_ and _stealth_.",
+ "item.create.cardboard_boots": "Cardboard Boots",
+ "item.create.cardboard_chestplate": "Cardboard Chestplate",
+ "item.create.cardboard_helmet": "Cardboard Helmet",
+ "item.create.cardboard_leggings": "Cardboard Leggings",
+ "item.create.cardboard_sword": "Cardboard Sword",
+ "item.create.cardboard_sword.tooltip.summary": "Bonk. A _mostly_ _harmless_, yet powerful weapon of choice.",
+ "item.create.chest_minecart_contraption": "Chest Minecart Contraption",
+ "item.create.chocolate_bucket": "Chocolate Bucket",
+ "item.create.chocolate_glazed_berries": "Chocolate Glazed Berries",
+ "item.create.chromatic_compound": "Chromatic Compound",
+ "item.create.cinder_flour": "Cinder Flour",
+ "item.create.copper_backtank": "Copper Backtank",
+ "item.create.copper_backtank.tooltip.behaviour1": "Provides _Pressurized_ _Air_ to Equipment that requires it.",
+ "item.create.copper_backtank.tooltip.behaviour2": "_Collects_ _Pressurized_ _Air_ at a rate depending on the Rotational Speed.",
+ "item.create.copper_backtank.tooltip.condition1": "When Worn",
+ "item.create.copper_backtank.tooltip.condition2": "When placed, Powered by Kinetics",
+ "item.create.copper_backtank.tooltip.summary": "A _Wearable_ _Tank_ for carrying Pressurized Air.",
+ "item.create.copper_backtank_placeable": "Copper Backtank Placeable",
+ "item.create.copper_diving_boots": "Copper Diving Boots",
+ "item.create.copper_diving_boots.tooltip.behaviour1": "Wielder _descends_ more _quickly_ in liquids. Grants the ability to _walk_ and _jump_ underwater. Wielder also is no longer affected by _Mechanical_ _Belts_.",
+ "item.create.copper_diving_boots.tooltip.condition1": "When Worn",
+ "item.create.copper_diving_boots.tooltip.summary": "A pair of _heavy_ _boots_, allowing for better traversal of the Ocean floor.",
+ "item.create.copper_diving_helmet": "Copper Diving Helmet",
+ "item.create.copper_diving_helmet.tooltip.behaviour1": "Provides the _Water Breathing_ effect, slowly draining _Air Pressure_ from the Backtank.",
+ "item.create.copper_diving_helmet.tooltip.condition1": "When Worn",
+ "item.create.copper_diving_helmet.tooltip.summary": "Together with a _Backtank_, allows the wielder to _breathe_ _underwater_ for an extended amount of time.",
+ "item.create.copper_nugget": "Copper Nugget",
+ "item.create.copper_sheet": "Copper Sheet",
+ "item.create.crafter_slot_cover": "Crafter Slot Cover",
+ "item.create.crafting_blueprint": "Crafting Blueprint",
+ "item.create.crafting_blueprint.behaviour1": "Opens a _Crafting_ _menu_ allowing you to _configure_ a _recipe_ and items to display.",
+ "item.create.crafting_blueprint.behaviour2": "_Applies_ the _configured_ _recipe_ with matching Ingredients found in your _Inventory_. _Sneak_ to craft up to a _Stack_ of items.",
+ "item.create.crafting_blueprint.condition1": "R-Click empty Slot",
+ "item.create.crafting_blueprint.condition2": "R-Click configured Slot",
+ "item.create.crafting_blueprint.tooltip.summary": "_Placed_ on a wall, it can be used to _specify_ _ingredient_ _arrangements_ for easier manual crafting. Each slot represents a Recipe.",
+ "item.create.creative_blaze_cake": "Creative Blaze Cake",
+ "item.create.creative_blaze_cake.tooltip.behaviour1": "_Cycles_ a Blaze Burner's heat level.",
+ "item.create.creative_blaze_cake.tooltip.condition1": "When Used",
+ "item.create.creative_blaze_cake.tooltip.summary": "A very special treat for your _Blaze Burners_. After eating this cake, Blaze Burners will _never run out of fuel_.",
+ "item.create.crushed_raw_aluminum": "Crushed Raw Aluminum",
+ "item.create.crushed_raw_copper": "Crushed Raw Copper",
+ "item.create.crushed_raw_gold": "Crushed Raw Gold",
+ "item.create.crushed_raw_iron": "Crushed Raw Iron",
+ "item.create.crushed_raw_lead": "Crushed Raw Lead",
+ "item.create.crushed_raw_nickel": "Crushed Raw Nickel",
+ "item.create.crushed_raw_osmium": "Crushed Raw Osmium",
+ "item.create.crushed_raw_platinum": "Crushed Raw Platinum",
+ "item.create.crushed_raw_quicksilver": "Crushed Raw Quicksilver",
+ "item.create.crushed_raw_silver": "Crushed Raw Silver",
+ "item.create.crushed_raw_tin": "Crushed Raw Tin",
+ "item.create.crushed_raw_uranium": "Crushed Raw Uranium",
+ "item.create.crushed_raw_zinc": "Crushed Raw Zinc",
+ "item.create.dough": "Dough",
+ "item.create.electron_tube": "Electron Tube",
+ "item.create.empty_blaze_burner": "Empty Blaze Burner",
+ "item.create.empty_schematic": "Empty Schematic",
+ "item.create.empty_schematic.tooltip.summary": "Used as a recipe ingredient and for writing at the _Schematic Table_.",
+ "item.create.experience_nugget": "Nugget of Experience",
+ "item.create.experience_nugget.tooltip.behaviour1": "_Redeems_ _Experience_ points contained within.",
+ "item.create.experience_nugget.tooltip.condition1": "When Used",
+ "item.create.experience_nugget.tooltip.summary": "A speck of _inspiration_ from your fantastic inventions.",
+ "item.create.extendo_grip": "Extendo Grip",
+ "item.create.extendo_grip.tooltip.behaviour1": "Increases _reach distance_ of items used in the _Main-Hand_.",
+ "item.create.extendo_grip.tooltip.behaviour2": "_No_ _Durability_ will be used. Instead, _Air_ _pressure_ is drained from the Tank",
+ "item.create.extendo_grip.tooltip.condition1": "When in Off-Hand",
+ "item.create.extendo_grip.tooltip.condition2": "While wearing Backtank",
+ "item.create.extendo_grip.tooltip.summary": "Greatly _increases reach distance_ of the wielder. Can be powered with _Air_ _Pressure_ from a _Backtank_",
+ "item.create.filter": "List Filter",
+ "item.create.filter.tooltip.behaviour1": "Opens the _configuration interface_.",
+ "item.create.filter.tooltip.condition1": "When R-Clicked",
+ "item.create.filter.tooltip.summary": "_Matches items_ against a collection of _items_ or _other filters_. Can be used in _Filter Slots_ of Create's Components",
+ "item.create.furnace_minecart_contraption": "Furnace Minecart Contraption",
+ "item.create.goggles": "Engineer's Goggles",
+ "item.create.goggles.tooltip.behaviour1": "_Kinetic components_ show added _Stress Impact_ or _Capacity_. _Stressometers_ show statistics of their _attached kinetic network_. Some other blocks reveal information such as item and fluid content.",
+ "item.create.goggles.tooltip.condition1": "When looking at blocks",
+ "item.create.goggles.tooltip.summary": "Augments your HUD with _miscellaneous information_ about placed components.",
+ "item.create.golden_sheet": "Golden Sheet",
+ "item.create.handheld_worldshaper": "Creative Worldshaper",
+ "item.create.handheld_worldshaper.tooltip.behaviour1": "_Targeted block_ will become the _material_ placed by the shaper.",
+ "item.create.handheld_worldshaper.tooltip.behaviour2": "Applies currently selected _Brush_ and _Tool_ at the targeted location.",
+ "item.create.handheld_worldshaper.tooltip.behaviour3": "Opens the _Configuration Interface_",
+ "item.create.handheld_worldshaper.tooltip.condition1": "L-Click at Block",
+ "item.create.handheld_worldshaper.tooltip.condition2": "R-Click at Block",
+ "item.create.handheld_worldshaper.tooltip.condition3": "R-Click while Sneaking",
+ "item.create.handheld_worldshaper.tooltip.summary": "_Creative mode_ tool for large-scale _landscaping_ from a distance.",
+ "item.create.honey_bucket": "Honey Bucket",
+ "item.create.honeyed_apple": "Honeyed Apple",
+ "item.create.incomplete_precision_mechanism": "Incomplete Precision Mechanism",
+ "item.create.incomplete_track": "Incomplete Track",
+ "item.create.iron_sheet": "Iron Sheet",
+ "item.create.linked_controller": "Linked Controller",
+ "item.create.linked_controller.tooltip.behaviour1": "_Toggles_ the controller. _Movement_ _controls_ are taken over while its active.",
+ "item.create.linked_controller.tooltip.behaviour2": "Opens the manual _Configuration Interface_.",
+ "item.create.linked_controller.tooltip.behaviour3": "Enables _Bind Mode_, press one of the _six controls_ to bind it to the _Links' Frequency_.",
+ "item.create.linked_controller.tooltip.behaviour4": "Places the Controller into the Lectern for easy activation. (R-Click while Sneaking to retrieve it)",
+ "item.create.linked_controller.tooltip.condition1": "R-Click",
+ "item.create.linked_controller.tooltip.condition2": "R-Click while Sneaking",
+ "item.create.linked_controller.tooltip.condition3": "R-Click on Redstone Link Receiver",
+ "item.create.linked_controller.tooltip.condition4": "R-Click on Lectern",
+ "item.create.linked_controller.tooltip.summary": "Grants _handheld_ _control_ over _Redstone Link_ frequencies assigned to its _six_ _buttons_.",
+ "item.create.minecart_contraption": "Minecart Contraption",
+ "item.create.minecart_coupling": "Minecart Coupling",
+ "item.create.minecart_coupling.tooltip.summary": "_Chains_ together individual _Minecarts_, causing them to move as a group.",
+ "item.create.netherite_backtank": "Netherite Backtank",
+ "item.create.netherite_backtank.tooltip.behaviour1": "Provides _Pressurized_ _Air_ to Equipment that requires it.",
+ "item.create.netherite_backtank.tooltip.behaviour2": "_Collects_ _Pressurized_ _Air_ at a rate depending on the Rotational Speed.",
+ "item.create.netherite_backtank.tooltip.condition1": "When Worn",
+ "item.create.netherite_backtank.tooltip.condition2": "When placed, Powered by Kinetics",
+ "item.create.netherite_backtank.tooltip.summary": "A _Wearable_ _Tank_ for carrying Pressurized Air.",
+ "item.create.netherite_backtank_placeable": "Netherite Backtank Placeable",
+ "item.create.netherite_diving_boots": "Netherite Diving Boots",
+ "item.create.netherite_diving_boots.tooltip.behaviour1": "Wielder _descends_ more _quickly_ in liquids. Grants the ability to _walk_ and _jump_ underwater. Wielder also is no longer affected by _Mechanical_ _Belts_.",
+ "item.create.netherite_diving_boots.tooltip.condition1": "When Worn",
+ "item.create.netherite_diving_boots.tooltip.summary": "A pair of _heavy_ _boots_, allowing for better traversal of the Ocean floor, including Oceans made of Lava.",
+ "item.create.netherite_diving_helmet": "Netherite Diving Helmet",
+ "item.create.netherite_diving_helmet.tooltip.behaviour1": "Provides immunity to _Fire_ and _Lava_, slowly draining _Air Pressure_ from the Backtank. Also grants _improved vision_ in Lava",
+ "item.create.netherite_diving_helmet.tooltip.condition1": "When Worn (Full set)",
+ "item.create.netherite_diving_helmet.tooltip.summary": "Together with a _Netherite Backtank_, this can protect you from _extreme heat_. To be effective, _Legs and Feet_ have to be covered in _Netherite_, too.",
+ "item.create.package": "Cardboard Package",
+ "item.create.package_filter": "Package Filter",
+ "item.create.package_filter.tooltip.behaviour1": "Opens the _configuration interface_.",
+ "item.create.package_filter.tooltip.condition1": "When R-Clicked",
+ "item.create.package_filter.tooltip.summary": "_Matches packaged items_ by their targeted _address_. Can be used in _Filter Slots_ of Create's Components",
+ "item.create.polished_rose_quartz": "Polished Rose Quartz",
+ "item.create.potato_cannon": "Potato Cannon",
+ "item.create.potato_cannon.tooltip.behaviour1": "_Shoots_ a suitable item from your _Inventory_.",
+ "item.create.potato_cannon.tooltip.behaviour2": "_No_ _Durability_ will be used. Instead, _Air_ _pressure_ is drained from the Tank",
+ "item.create.potato_cannon.tooltip.condition1": "When R-Clicked",
+ "item.create.potato_cannon.tooltip.condition2": "While wearing Backtank",
+ "item.create.potato_cannon.tooltip.summary": "Launches your home-grown vegetables at Enemies. Can be powered with _Air_ _Pressure_ from a _Backtank_",
+ "item.create.powdered_obsidian": "Powdered Obsidian",
+ "item.create.precision_mechanism": "Precision Mechanism",
+ "item.create.propeller": "Propeller",
+ "item.create.pulp": "Pulp",
+ "item.create.rare_package": "Rare Package",
+ "item.create.raw_zinc": "Raw Zinc",
+ "item.create.red_sand_paper": "Red Sand Paper",
+ "item.create.refined_radiance": "Refined Radiance",
+ "item.create.rose_quartz": "Rose Quartz",
+ "item.create.sand_paper": "Sand Paper",
+ "item.create.sand_paper.tooltip.behaviour1": "Applies polish to items held in the _offhand_ or lying on the _floor_ when _looking at them_",
+ "item.create.sand_paper.tooltip.condition1": "When Used",
+ "item.create.sand_paper.tooltip.summary": "Can be used to _refine materials_. The process can be automated with a Deployer.",
+ "item.create.schedule": "Train Schedule",
+ "item.create.schematic": "Schematic",
+ "item.create.schematic.tooltip.behaviour1": "Can be positioned using the Tools on Screen.",
+ "item.create.schematic.tooltip.behaviour2": "Opens an _Interface_ for entering exact _Coordinates_.",
+ "item.create.schematic.tooltip.condition1": "When Held",
+ "item.create.schematic.tooltip.condition2": "R-Click while Sneaking",
+ "item.create.schematic.tooltip.summary": "Holds a structure to be positioned and placed into the world. Position the Hologram as desired and use a _Schematicannon_ to build it.",
+ "item.create.schematic_and_quill": "Schematic And Quill",
+ "item.create.schematic_and_quill.tooltip.behaviour1": "Select two _corner points_ using _R-Click_. Hold _Ctrl_ and Scroll to select locations mid-air.",
+ "item.create.schematic_and_quill.tooltip.behaviour2": "_Ctrl-Scroll_ on the faces to adjust the size, then R-Click again to Save.",
+ "item.create.schematic_and_quill.tooltip.condition1": "Creating a selection",
+ "item.create.schematic_and_quill.tooltip.condition2": "Adjusting and Saving",
+ "item.create.schematic_and_quill.tooltip.summary": "Used for _saving a Structure_ in your world to a _.nbt file_.",
+ "item.create.shadow_steel": "Shadow Steel",
+ "item.create.shopping_list": "Shopping List",
+ "item.create.sturdy_sheet": "Sturdy Sheet",
+ "item.create.super_glue": "Super Glue",
+ "item.create.sweet_roll": "Sweet Roll",
+ "item.create.transmitter": "Transmitter",
+ "item.create.tree_fertilizer": "Tree Fertilizer",
+ "item.create.tree_fertilizer.tooltip.behaviour1": "Creates a tree _regardless_ of its _spacing conditions_",
+ "item.create.tree_fertilizer.tooltip.condition1": "When used on a Sapling",
+ "item.create.tree_fertilizer.tooltip.summary": "A powerful combination of minerals useful for _growing saplings_ in tight spaces.",
+ "item.create.unprocessed_obsidian_sheet": "Unprocessed Obsidian Sheet",
+ "item.create.vertical_gearbox": "Vertical Gearbox",
+ "item.create.wand_of_symmetry": "Wand Of Symmetry",
+ "item.create.wand_of_symmetry.tooltip.behaviour1": "_Creates_ or _Moves_ its Mirror. _Sneak_ while using to open the _Configuration Interface_",
+ "item.create.wand_of_symmetry.tooltip.behaviour2": "_Removes_ the active Mirror",
+ "item.create.wand_of_symmetry.tooltip.condition1": "When used on a Surface",
+ "item.create.wand_of_symmetry.tooltip.condition2": "When used with no target",
+ "item.create.wand_of_symmetry.tooltip.summary": "_Mirrors Block placement_ while present in any _Hotbar Slot_.",
+ "item.create.wheat_flour": "Wheat Flour",
+ "item.create.whisk": "Whisk",
+ "item.create.wrench": "Wrench",
+ "item.create.wrench.tooltip.behaviour1": "_Rotates components_ around the clicked face. _Sneak_ while interacting to _dismantle_ components.",
+ "item.create.wrench.tooltip.behaviour2": "_Relocates_ assembled minecart- and train-based contraptions.",
+ "item.create.wrench.tooltip.condition1": "When used on Blocks",
+ "item.create.wrench.tooltip.condition2": "When used on Contraptions",
+ "item.create.wrench.tooltip.summary": "Multi-purpose tool for working with your kinetic contraptions.",
+ "item.create.zinc_ingot": "Zinc Ingot",
+ "item.create.zinc_nugget": "Zinc Nugget",
+ "itemGroup.create.base": "Create",
+ "itemGroup.create.palettes": "Create's Building Blocks",
+ "tag.block.create.brittle": "Brittle",
+ "tag.block.create.casing": "Casings",
+ "tag.block.create.chest_mounted_storage": "Mounted Chests",
+ "tag.block.create.copycat_allow": "Copycat Copyable",
+ "tag.block.create.copycat_deny": "Not Copycat Copyable",
+ "tag.block.create.corals": "Corals",
+ "tag.block.create.fallback_mounted_storage_blacklist": "Non-mountable Storages",
+ "tag.block.create.fan_processing_catalysts.blasting": "Blasting Catalysts",
+ "tag.block.create.fan_processing_catalysts.haunting": "Haunting Catalysts",
+ "tag.block.create.fan_processing_catalysts.smoking": "Smoking Catalysts",
+ "tag.block.create.fan_processing_catalysts.splashing": "Splashing Catalysts",
+ "tag.block.create.fan_transparent": "Fan Transparent",
+ "tag.block.create.girdable_tracks": "Girdable Tracks",
+ "tag.block.create.movable_empty_collider": "Movable Empty Colliders",
+ "tag.block.create.non_breakable": "Non-breakable",
+ "tag.block.create.non_harvestable": "Non-harvestable",
+ "tag.block.create.non_movable": "Non-movable",
+ "tag.block.create.passive_boiler_heaters": "Passive Boiler Heaters",
+ "tag.block.create.postboxes": "Postboxes",
+ "tag.block.create.roots": "Roots",
+ "tag.block.create.safe_nbt": "Safe NBT",
+ "tag.block.create.seats": "Seats",
+ "tag.block.create.simple_mounted_storage": "Simple Mounted Storages",
+ "tag.block.create.single_block_inventories": "Single-block Inventories",
+ "tag.block.create.sugar_cane_variants": "Sugarcane-like",
+ "tag.block.create.table_cloths": "Table Cloths",
+ "tag.block.create.toolboxes": "Toolboxes",
+ "tag.block.create.tracks": "Tracks",
+ "tag.block.create.tree_attachments": "Tree Attachments",
+ "tag.block.create.valve_handles": "Valve Handles",
+ "tag.block.create.windmill_sails": "Windmill Sails",
+ "tag.block.create.wrench_pickup": "Wrench-pickupable",
+ "tag.block.forge.ores.aluminum": "Aluminum Ores",
+ "tag.block.forge.ores.copper": "Copper Ores",
+ "tag.block.forge.ores.gold": "Gold Ores",
+ "tag.block.forge.ores.iron": "Iron Ores",
+ "tag.block.forge.ores.lead": "Lead Ores",
+ "tag.block.forge.ores.nickel": "Nickel Ores",
+ "tag.block.forge.ores.osmium": "Osmium Ores",
+ "tag.block.forge.ores.platinum": "Platinum Ores",
+ "tag.block.forge.ores.quicksilver": "Quicksilver Ores",
+ "tag.block.forge.ores.silver": "Silver Ores",
+ "tag.block.forge.ores.tin": "Tin Ores",
+ "tag.block.forge.ores.uranium": "Uranium Ores",
+ "tag.block.forge.ores.zinc": "Zinc Ores",
+ "tag.block.forge.ores_in_ground.deepslate": "Deepslate Ores",
+ "tag.block.forge.ores_in_ground.stone": "Stone Ores",
+ "tag.block.forge.relocation_not_supported": "Non-relocatable",
+ "tag.block.forge.storage_blocks.aluminum": "Aluminum Storage Blocks",
+ "tag.block.forge.storage_blocks.andesite_alloy": "Andesite Alloy Storage Blocks",
+ "tag.block.forge.storage_blocks.brass": "Brass Storage Blocks",
+ "tag.block.forge.storage_blocks.cardboard": "Cardboard Storage Blocks",
+ "tag.block.forge.storage_blocks.constantan": "Constantan Storage Blocks",
+ "tag.block.forge.storage_blocks.copper": "Copper Storage Blocks",
+ "tag.block.forge.storage_blocks.electrum": "Electrum Storage Blocks",
+ "tag.block.forge.storage_blocks.gold": "Gold Storage Blocks",
+ "tag.block.forge.storage_blocks.iron": "Iron Storage Blocks",
+ "tag.block.forge.storage_blocks.lead": "Lead Storage Blocks",
+ "tag.block.forge.storage_blocks.nickel": "Nickel Storage Blocks",
+ "tag.block.forge.storage_blocks.osmium": "Osmium Storage Blocks",
+ "tag.block.forge.storage_blocks.platinum": "Platinum Storage Blocks",
+ "tag.block.forge.storage_blocks.quicksilver": "Quicksilver Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_aluminum": "Raw Aluminum Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_copper": "Raw Copper Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_gold": "Raw Gold Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_iron": "Raw Iron Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_lead": "Raw Lead Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_nickel": "Raw Nickel Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_osmium": "Raw Osmium Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_platinum": "Raw Platinum Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_quicksilver": "Raw Quicksilver Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_silver": "Raw Silver Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_tin": "Raw Tin Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_uranium": "Raw Uranium Storage Blocks",
+ "tag.block.forge.storage_blocks.raw_zinc": "Raw Zinc Storage Blocks",
+ "tag.block.forge.storage_blocks.silver": "Silver Storage Blocks",
+ "tag.block.forge.storage_blocks.steel": "Steel Storage Blocks",
+ "tag.block.forge.storage_blocks.tin": "Tin Storage Blocks",
+ "tag.block.forge.storage_blocks.uranium": "Uranium Storage Blocks",
+ "tag.block.forge.storage_blocks.zinc": "Zinc Storage Blocks",
+ "tag.create.contraption_type.create.opens_controls": "Opens Contraption Controls",
+ "tag.create.contraption_type.create.requires_vehicle_for_render": "Requires a Vehicle to Render",
+ "tag.create.mounted_item_storage_type.create.fuel_blacklist": "Doesn't Provide Fuel",
+ "tag.create.mounted_item_storage_type.create.internal": "Internal",
+ "tag.fluid.create.bottomless.allow": "Potentially Bottomless Fluids",
+ "tag.fluid.create.bottomless.deny": "Non-bottomless Fluids",
+ "tag.fluid.create.fan_processing_catalysts.blasting": "Blasting Catalysts",
+ "tag.fluid.create.fan_processing_catalysts.haunting": "Haunting Catalysts",
+ "tag.fluid.create.fan_processing_catalysts.smoking": "Smoking Catalysts",
+ "tag.fluid.create.fan_processing_catalysts.splashing": "Splashing Catalysts",
+ "tag.fluid.forge.chocolate": "Chocolate",
+ "tag.fluid.forge.creosote": "Creosote",
+ "tag.fluid.forge.honey": "Honey",
+ "tag.fluid.forge.tea": "Teas",
+ "tag.item.create.blaze_burner_fuel.regular": "Regular Blaze Burner Fuel",
+ "tag.item.create.blaze_burner_fuel.special": "Special Blaze Burner Fuel",
+ "tag.item.create.casing": "Casings",
+ "tag.item.create.chain_rideable": "Can Ride Chains",
+ "tag.item.create.contraption_controlled": "Contraption-controllable",
+ "tag.item.create.create_ingots": "Create's Ingots",
+ "tag.item.create.crushed_raw_materials": "Crushed Raw Materials",
+ "tag.item.create.deployable_drink": "Deployable Drink",
+ "tag.item.create.dispense_behavior_wrap_blacklist": "Dispense Behavior Wrap Blacklist",
+ "tag.item.create.dyed_table_cloths": "Dyed Table Cloths",
+ "tag.item.create.invalid_for_track_paving": "Track Paving Blacklist",
+ "tag.item.create.modded_stripped_logs": "Modded Stripped Logs",
+ "tag.item.create.modded_stripped_wood": "Modded Stripped Wood",
+ "tag.item.create.not_upright_on_belt": "Not Upright on Belts",
+ "tag.item.create.packages": "Packages",
+ "tag.item.create.postboxes": "Postboxes",
+ "tag.item.create.pressurized_air_sources": "Pressurized Air Sources",
+ "tag.item.create.pulpifiable": "Pulpifiable",
+ "tag.item.create.sandpaper": "Sandpaper",
+ "tag.item.create.seats": "Seats",
+ "tag.item.create.sleepers": "Sleepers",
+ "tag.item.create.stone_types.andesite": "Andesite",
+ "tag.item.create.stone_types.asurine": "Asurine",
+ "tag.item.create.stone_types.calcite": "Calcite",
+ "tag.item.create.stone_types.crimsite": "Crimsite",
+ "tag.item.create.stone_types.deepslate": "Deepslate",
+ "tag.item.create.stone_types.diorite": "Diorite",
+ "tag.item.create.stone_types.dripstone": "Dripstone",
+ "tag.item.create.stone_types.galosphere.allurite": "Allurite",
+ "tag.item.create.stone_types.galosphere.amethyst": "Amethust",
+ "tag.item.create.stone_types.galosphere.lumiere": "Lumiere",
+ "tag.item.create.stone_types.granite": "Granite",
+ "tag.item.create.stone_types.limestone": "Limestone",
+ "tag.item.create.stone_types.ochrum": "Ochrum",
+ "tag.item.create.stone_types.scorchia": "Scorchia",
+ "tag.item.create.stone_types.scoria": "Scoria",
+ "tag.item.create.stone_types.tuff": "Tuff",
+ "tag.item.create.stone_types.veridium": "Veridium",
+ "tag.item.create.table_cloths": "Table Cloths",
+ "tag.item.create.toolboxes": "Toolboxes",
+ "tag.item.create.tracks": "Tracks",
+ "tag.item.create.upgrade_aquatic.coral": "Upgrade Aquatic Coral",
+ "tag.item.create.upright_on_belt": "Upright on Belts",
+ "tag.item.create.valve_handles": "Valve Handles",
+ "tag.item.create.vanilla_stripped_logs": "Vanilla Stripped Logs",
+ "tag.item.create.vanilla_stripped_wood": "Vanilla Stripped Wood",
+ "tag.item.forge.armors.boots": "Boots",
+ "tag.item.forge.armors.chestplates": "Chestplates",
+ "tag.item.forge.armors.helmets": "Helmets",
+ "tag.item.forge.armors.leggings": "Leggings",
+ "tag.item.forge.buckets.honey": "Honey Buckets",
+ "tag.item.forge.dough": "Dough",
+ "tag.item.forge.dough.wheat": "Wheat Dough",
+ "tag.item.forge.dusts.obsidian": "Obsidian Dust",
+ "tag.item.forge.flour": "Flour",
+ "tag.item.forge.flour.wheat": "Wheat Flour",
+ "tag.item.forge.gems.certus_quartz": "Certus Quartz",
+ "tag.item.forge.ingots.aluminum": "Aluminum Ingots",
+ "tag.item.forge.ingots.brass": "Brass Ingots",
+ "tag.item.forge.ingots.constantan": "Constantan Ingots",
+ "tag.item.forge.ingots.copper": "Copper Ingots",
+ "tag.item.forge.ingots.electrum": "Electrum Ingots",
+ "tag.item.forge.ingots.gold": "Gold Ingots",
+ "tag.item.forge.ingots.iron": "Iron Ingots",
+ "tag.item.forge.ingots.lead": "Lead Ingots",
+ "tag.item.forge.ingots.nickel": "Nickel Ingots",
+ "tag.item.forge.ingots.osmium": "Osmium Ingots",
+ "tag.item.forge.ingots.platinum": "Platinum Ingots",
+ "tag.item.forge.ingots.quicksilver": "Quicksilver Ingots",
+ "tag.item.forge.ingots.silver": "Silver Ingots",
+ "tag.item.forge.ingots.steel": "Steel Ingots",
+ "tag.item.forge.ingots.tin": "Tin Ingots",
+ "tag.item.forge.ingots.uranium": "Uranium Ingots",
+ "tag.item.forge.ingots.zinc": "Zinc Ingots",
+ "tag.item.forge.nuggets.aluminum": "Aluminum Nuggets",
+ "tag.item.forge.nuggets.brass": "Brass Nuggets",
+ "tag.item.forge.nuggets.constantan": "Constantan Nuggets",
+ "tag.item.forge.nuggets.copper": "Copper Nuggets",
+ "tag.item.forge.nuggets.electrum": "Electrum Nuggets",
+ "tag.item.forge.nuggets.gold": "Gold Nuggets",
+ "tag.item.forge.nuggets.iron": "Iron Nuggets",
+ "tag.item.forge.nuggets.lead": "Lead Nuggets",
+ "tag.item.forge.nuggets.nickel": "Nickel Nuggets",
+ "tag.item.forge.nuggets.osmium": "Osmium Nuggets",
+ "tag.item.forge.nuggets.platinum": "Platinum Nuggets",
+ "tag.item.forge.nuggets.quicksilver": "Quicksilver Nuggets",
+ "tag.item.forge.nuggets.silver": "Silver Nuggets",
+ "tag.item.forge.nuggets.steel": "Steel Nuggets",
+ "tag.item.forge.nuggets.tin": "Tin Nuggets",
+ "tag.item.forge.nuggets.uranium": "Uranium Nuggets",
+ "tag.item.forge.nuggets.zinc": "Zinc Nuggets",
+ "tag.item.forge.ores.aluminum": "Aluminum Ores",
+ "tag.item.forge.ores.ametrine": "Ametrine Ores",
+ "tag.item.forge.ores.anthracite": "Anthracite Ores",
+ "tag.item.forge.ores.copper": "Copper Ores",
+ "tag.item.forge.ores.emeraldite": "Emeraldite Ores",
+ "tag.item.forge.ores.gold": "Gold Ores",
+ "tag.item.forge.ores.iron": "Iron Ores",
+ "tag.item.forge.ores.lead": "Lead Ores",
+ "tag.item.forge.ores.lignite": "Lignite Ores",
+ "tag.item.forge.ores.nickel": "Nickel Ores",
+ "tag.item.forge.ores.osmium": "Osmium Ores",
+ "tag.item.forge.ores.platinum": "Platinum Ores",
+ "tag.item.forge.ores.quicksilver": "Quicksilver Ores",
+ "tag.item.forge.ores.silver": "Silver Ores",
+ "tag.item.forge.ores.tin": "Tin Ores",
+ "tag.item.forge.ores.uranium": "Uranium Ores",
+ "tag.item.forge.ores.zinc": "Zinc Ores",
+ "tag.item.forge.ores_in_ground.deepslate": "Deepslate Ores",
+ "tag.item.forge.ores_in_ground.stone": "Stone Ores",
+ "tag.item.forge.plates": "Plates",
+ "tag.item.forge.plates.aluminum": "Aluminum Plates",
+ "tag.item.forge.plates.brass": "Brass Plates",
+ "tag.item.forge.plates.cardboard": "Cardboard Plates",
+ "tag.item.forge.plates.constantan": "Constantan Plates",
+ "tag.item.forge.plates.copper": "Copper Plates",
+ "tag.item.forge.plates.electrum": "Electrum Plates",
+ "tag.item.forge.plates.gold": "Gold Plates",
+ "tag.item.forge.plates.iron": "Iron Plates",
+ "tag.item.forge.plates.lead": "Lead Plates",
+ "tag.item.forge.plates.nickel": "Nickel Plates",
+ "tag.item.forge.plates.obsidian": "Obsidian Plates",
+ "tag.item.forge.plates.osmium": "Osmium Plates",
+ "tag.item.forge.plates.platinum": "Platinum Plates",
+ "tag.item.forge.plates.quicksilver": "Quicksilver Plates",
+ "tag.item.forge.plates.silver": "Silver Plates",
+ "tag.item.forge.plates.steel": "Steel Plates",
+ "tag.item.forge.plates.tin": "Tin Plates",
+ "tag.item.forge.plates.uranium": "Uranium Plates",
+ "tag.item.forge.plates.zinc": "Zinc Plates",
+ "tag.item.forge.raw_materials": "Raw Materials",
+ "tag.item.forge.raw_materials.aluminum": "Raw Aluminum Ores",
+ "tag.item.forge.raw_materials.copper": "Raw Copper Ores",
+ "tag.item.forge.raw_materials.gold": "Raw Gold Ores",
+ "tag.item.forge.raw_materials.iron": "Raw Iron Ores",
+ "tag.item.forge.raw_materials.lead": "Raw Lead Ores",
+ "tag.item.forge.raw_materials.nickel": "Raw Nickel Ores",
+ "tag.item.forge.raw_materials.osmium": "Raw Osmium Ores",
+ "tag.item.forge.raw_materials.platinum": "Raw Platinum Ores",
+ "tag.item.forge.raw_materials.quicksilver": "Raw Quicksilver Ores",
+ "tag.item.forge.raw_materials.silver": "Raw Silver Ores",
+ "tag.item.forge.raw_materials.tin": "Raw Tin Ores",
+ "tag.item.forge.raw_materials.uranium": "Raw Uranium Ores",
+ "tag.item.forge.raw_materials.zinc": "Raw Zinc Ores",
+ "tag.item.forge.storage_blocks.aluminum": "Aluminum Storage Blocks",
+ "tag.item.forge.storage_blocks.andesite_alloy": "Andesite Alloy Storage Blocks",
+ "tag.item.forge.storage_blocks.brass": "Brass Storage Blocks",
+ "tag.item.forge.storage_blocks.cardboard": "Cardboard Storage Blocks",
+ "tag.item.forge.storage_blocks.constantan": "Constantan Storage Blocks",
+ "tag.item.forge.storage_blocks.copper": "Copper Storage Blocks",
+ "tag.item.forge.storage_blocks.electrum": "Electrum Storage Blocks",
+ "tag.item.forge.storage_blocks.gold": "Gold Storage Blocks",
+ "tag.item.forge.storage_blocks.iron": "Iron Storage Blocks",
+ "tag.item.forge.storage_blocks.lead": "Lead Storage Blocks",
+ "tag.item.forge.storage_blocks.nickel": "Nickel Storage Blocks",
+ "tag.item.forge.storage_blocks.osmium": "Osmium Storage Blocks",
+ "tag.item.forge.storage_blocks.platinum": "Platinum Storage Blocks",
+ "tag.item.forge.storage_blocks.quicksilver": "Quicksilver Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_aluminum": "Raw Aluminum Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_copper": "Raw Copper Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_gold": "Raw Gold Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_iron": "Raw Iron Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_lead": "Raw Lead Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_nickel": "Raw Nickel Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_osmium": "Raw Osmium Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_platinum": "Raw Platinum Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_quicksilver": "Raw Quicksilver Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_silver": "Raw Silver Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_tin": "Raw Tin Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_uranium": "Raw Uranium Storage Blocks",
+ "tag.item.forge.storage_blocks.raw_zinc": "Raw Zinc Storage Blocks",
+ "tag.item.forge.storage_blocks.silver": "Silver Storage Blocks",
+ "tag.item.forge.storage_blocks.steel": "Steel Storage Blocks",
+ "tag.item.forge.storage_blocks.tin": "Tin Storage Blocks",
+ "tag.item.forge.storage_blocks.uranium": "Uranium Storage Blocks",
+ "tag.item.forge.storage_blocks.zinc": "Zinc Storage Blocks",
+ "tag.item.forge.stripped_logs": "Stripped Logs",
+ "tag.item.forge.stripped_wood": "Stripped Wood",
+ "tag.item.forge.tools.wrench": "Wrenches",
+ "tag.recipe_serializer.create.automation_ignore": "Non-automatable"
+}
\ No newline at end of file
diff --git a/modules/lang/test/resources/1.20.1/default/additional/assets/farmersdelight/lang/en_us.json b/modules/lang/test/resources/1.20.1/default/additional/assets/farmersdelight/lang/en_us.json
new file mode 100644
index 0000000..09c8f27
--- /dev/null
+++ b/modules/lang/test/resources/1.20.1/default/additional/assets/farmersdelight/lang/en_us.json
@@ -0,0 +1,468 @@
+{
+ "block.farmersdelight.stove": "Stove",
+ "block.farmersdelight.cooking_pot": "Cooking Pot",
+ "block.farmersdelight.cutting_board": "Cutting Board",
+ "block.farmersdelight.skillet": "Skillet",
+ "block.farmersdelight.wooden_basket": "Basket",
+ "block.farmersdelight.bamboo_basket": "Bamboo Basket",
+ "block.farmersdelight.oak_cabinet": "Oak Cabinet",
+ "block.farmersdelight.birch_cabinet": "Birch Cabinet",
+ "block.farmersdelight.spruce_cabinet": "Spruce Cabinet",
+ "block.farmersdelight.jungle_cabinet": "Jungle Cabinet",
+ "block.farmersdelight.acacia_cabinet": "Acacia Cabinet",
+ "block.farmersdelight.dark_oak_cabinet": "Dark Oak Cabinet",
+ "block.farmersdelight.mangrove_cabinet": "Mangrove Cabinet",
+ "block.farmersdelight.cherry_cabinet": "Cherry Cabinet",
+ "block.farmersdelight.bamboo_cabinet": "Bamboo Cabinet",
+ "block.farmersdelight.crimson_cabinet": "Crimson Cabinet",
+ "block.farmersdelight.warped_cabinet": "Warped Cabinet",
+ "block.farmersdelight.rope": "Rope",
+ "block.farmersdelight.rope_fence": "Rope Fence",
+ "block.farmersdelight.rope_fence_gate": "Rope Fence Gate",
+ "block.farmersdelight.safety_net": "Safety Net",
+ "block.farmersdelight.canvas_rug": "Canvas Rug",
+ "block.farmersdelight.tatami": "Tatami Block",
+ "block.farmersdelight.full_tatami_mat": "Full Tatami Mat",
+ "block.farmersdelight.half_tatami_mat": "Half Tatami Mat",
+ "block.farmersdelight.organic_compost": "Organic Compost",
+ "block.farmersdelight.rich_soil": "Rich Soil",
+ "block.farmersdelight.rich_soil_farmland": "Rich Soil Farmland",
+ "block.farmersdelight.carrot_crate": "Carrot Crate",
+ "block.farmersdelight.potato_crate": "Potato Crate",
+ "block.farmersdelight.beetroot_crate": "Beetroot Crate",
+ "block.farmersdelight.cabbage_crate": "Cabbage Crate",
+ "block.farmersdelight.tomato_crate": "Tomato Crate",
+ "block.farmersdelight.onion_crate": "Onion Crate",
+ "block.farmersdelight.rice_bale": "Rice Bale",
+ "block.farmersdelight.rice_bag": "Bag of Rice",
+ "block.farmersdelight.straw_bale": "Straw Bale",
+
+ "block.farmersdelight.canvas_sign": "Canvas Sign",
+ "block.farmersdelight.white_canvas_sign": "White Canvas Sign",
+ "block.farmersdelight.orange_canvas_sign": "Orange Canvas Sign",
+ "block.farmersdelight.magenta_canvas_sign": "Magenta Canvas Sign",
+ "block.farmersdelight.light_blue_canvas_sign": "Light Blue Canvas Sign",
+ "block.farmersdelight.yellow_canvas_sign": "Yellow Canvas Sign",
+ "block.farmersdelight.lime_canvas_sign": "Lime Canvas Sign",
+ "block.farmersdelight.pink_canvas_sign": "Pink Canvas Sign",
+ "block.farmersdelight.gray_canvas_sign": "Gray Canvas Sign",
+ "block.farmersdelight.light_gray_canvas_sign": "Light Gray Canvas Sign",
+ "block.farmersdelight.cyan_canvas_sign": "Cyan Canvas Sign",
+ "block.farmersdelight.purple_canvas_sign": "Purple Canvas Sign",
+ "block.farmersdelight.blue_canvas_sign": "Blue Canvas Sign",
+ "block.farmersdelight.brown_canvas_sign": "Brown Canvas Sign",
+ "block.farmersdelight.green_canvas_sign": "Green Canvas Sign",
+ "block.farmersdelight.red_canvas_sign": "Red Canvas Sign",
+ "block.farmersdelight.black_canvas_sign": "Black Canvas Sign",
+
+ "block.farmersdelight.canvas_wall_sign": "Canvas Wall Sign",
+ "block.farmersdelight.white_canvas_wall_sign": "White Canvas Wall Sign",
+ "block.farmersdelight.orange_canvas_wall_sign": "Orange Canvas Wall Sign",
+ "block.farmersdelight.magenta_canvas_wall_sign": "Magenta Canvas Wall Sign",
+ "block.farmersdelight.light_blue_canvas_wall_sign": "Light Blue Canvas Wall Sign",
+ "block.farmersdelight.yellow_canvas_wall_sign": "Yellow Canvas Wall Sign",
+ "block.farmersdelight.lime_canvas_wall_sign": "Lime Canvas Wall Sign",
+ "block.farmersdelight.pink_canvas_wall_sign": "Pink Canvas Wall Sign",
+ "block.farmersdelight.gray_canvas_wall_sign": "Gray Canvas Wall Sign",
+ "block.farmersdelight.light_gray_canvas_wall_sign": "Light Gray Canvas Wall Sign",
+ "block.farmersdelight.cyan_canvas_wall_sign": "Cyan Canvas Wall Sign",
+ "block.farmersdelight.purple_canvas_wall_sign": "Purple Canvas Wall Sign",
+ "block.farmersdelight.blue_canvas_wall_sign": "Blue Canvas Wall Sign",
+ "block.farmersdelight.brown_canvas_wall_sign": "Brown Canvas Wall Sign",
+ "block.farmersdelight.green_canvas_wall_sign": "Green Canvas Wall Sign",
+ "block.farmersdelight.red_canvas_wall_sign": "Red Canvas Wall Sign",
+ "block.farmersdelight.black_canvas_wall_sign": "Black Canvas Wall Sign",
+
+ "block.farmersdelight.hanging_canvas_sign": "Hanging Canvas Sign",
+ "block.farmersdelight.white_hanging_canvas_sign": "White Hanging Canvas Sign",
+ "block.farmersdelight.orange_hanging_canvas_sign": "Orange Hanging Canvas Sign",
+ "block.farmersdelight.magenta_hanging_canvas_sign": "Magenta Hanging Canvas Sign",
+ "block.farmersdelight.light_blue_hanging_canvas_sign": "Light Blue Hanging Canvas Sign",
+ "block.farmersdelight.yellow_hanging_canvas_sign": "Yellow Hanging Canvas Sign",
+ "block.farmersdelight.lime_hanging_canvas_sign": "Lime Hanging Canvas Sign",
+ "block.farmersdelight.pink_hanging_canvas_sign": "Pink Hanging Canvas Sign",
+ "block.farmersdelight.gray_hanging_canvas_sign": "Gray Hanging Canvas Sign",
+ "block.farmersdelight.light_gray_hanging_canvas_sign": "Light Gray Hanging Canvas Sign",
+ "block.farmersdelight.cyan_hanging_canvas_sign": "Cyan Hanging Canvas Sign",
+ "block.farmersdelight.purple_hanging_canvas_sign": "Purple Hanging Canvas Sign",
+ "block.farmersdelight.blue_hanging_canvas_sign": "Blue Hanging Canvas Sign",
+ "block.farmersdelight.brown_hanging_canvas_sign": "Brown Hanging Canvas Sign",
+ "block.farmersdelight.green_hanging_canvas_sign": "Green Hanging Canvas Sign",
+ "block.farmersdelight.red_hanging_canvas_sign": "Red Hanging Canvas Sign",
+ "block.farmersdelight.black_hanging_canvas_sign": "Black Hanging Canvas Sign",
+
+ "block.farmersdelight.wall_hanging_canvas_sign": "Wall Hanging Canvas Sign",
+ "block.farmersdelight.white_wall_hanging_canvas_sign": "White Wall Hanging Canvas Sign",
+ "block.farmersdelight.orange_wall_hanging_canvas_sign": "Orange Wall Hanging Canvas Sign",
+ "block.farmersdelight.magenta_wall_hanging_canvas_sign": "Magenta Wall Hanging Canvas Sign",
+ "block.farmersdelight.light_blue_wall_hanging_canvas_sign": "Light Blue Wall Hanging Canvas Sign",
+ "block.farmersdelight.yellow_wall_hanging_canvas_sign": "Yellow Wall Hanging Canvas Sign",
+ "block.farmersdelight.lime_wall_hanging_canvas_sign": "Lime Wall Hanging Canvas Sign",
+ "block.farmersdelight.pink_wall_hanging_canvas_sign": "Pink Wall Hanging Canvas Sign",
+ "block.farmersdelight.gray_wall_hanging_canvas_sign": "Gray Wall Hanging Canvas Sign",
+ "block.farmersdelight.light_gray_wall_hanging_canvas_sign": "Light Gray Wall Hanging Canvas Sign",
+ "block.farmersdelight.cyan_wall_hanging_canvas_sign": "Cyan Wall Hanging Canvas Sign",
+ "block.farmersdelight.purple_wall_hanging_canvas_sign": "Purple Wall Hanging Canvas Sign",
+ "block.farmersdelight.blue_wall_hanging_canvas_sign": "Blue Wall Hanging Canvas Sign",
+ "block.farmersdelight.brown_wall_hanging_canvas_sign": "Brown Wall Hanging Canvas Sign",
+ "block.farmersdelight.green_wall_hanging_canvas_sign": "Green Wall Hanging Canvas Sign",
+ "block.farmersdelight.red_wall_hanging_canvas_sign": "Red Wall Hanging Canvas Sign",
+ "block.farmersdelight.black_wall_hanging_canvas_sign": "Black Wall Hanging Canvas Sign",
+
+ "block.farmersdelight.budding_tomatoes": "Budding Tomato Vine",
+ "block.farmersdelight.tomatoes": "Tomato Vine",
+ "block.farmersdelight.tomatoes_on_rope": "Tomato Vine on Rope",
+ "block.farmersdelight.cabbages": "Cabbage",
+ "block.farmersdelight.onions": "Onions",
+ "block.farmersdelight.rice": "Rice Crops",
+ "block.farmersdelight.rice_panicles": "Rice Panicles",
+ "block.farmersdelight.brown_mushroom_colony": "Brown Mushroom Colony",
+ "block.farmersdelight.red_mushroom_colony": "Red Mushroom Colony",
+
+ "block.farmersdelight.sandy_shrub": "Sandy Shrub",
+ "block.farmersdelight.wild_cabbages": "Wild Cabbage",
+ "block.farmersdelight.wild_tomatoes": "Tomato Shrub",
+ "block.farmersdelight.wild_onions": "Wild Onion",
+ "block.farmersdelight.wild_carrots": "Wild Carrot",
+ "block.farmersdelight.wild_potatoes": "Wild Potato",
+ "block.farmersdelight.wild_beetroots": "Sea Beet",
+ "block.farmersdelight.wild_rice": "Wild Rice",
+
+ "block.farmersdelight.apple_pie": "Apple Pie",
+ "block.farmersdelight.sweet_berry_cheesecake": "Sweet Berry Cheesecake",
+ "block.farmersdelight.chocolate_pie": "Chocolate Pie",
+ "block.farmersdelight.pumpkin_pie": "Pumpkin Pie",
+
+ "item.farmersdelight.tomato": "Tomato",
+ "item.farmersdelight.cabbage": "Cabbage",
+ "item.farmersdelight.onion": "Onion",
+ "item.farmersdelight.rice": "Rice",
+ "item.farmersdelight.cabbage_seeds": "Cabbage Seeds",
+ "item.farmersdelight.tomato_seeds": "Tomato Seeds",
+ "item.farmersdelight.rice_panicle": "Rice Panicle",
+ "item.farmersdelight.rotten_tomato": "Rotten Tomato",
+
+ "item.farmersdelight.straw": "Straw",
+ "item.farmersdelight.canvas": "Canvas",
+ "item.farmersdelight.tree_bark": "Tree Bark",
+
+ "item.farmersdelight.tomato_sauce": "Tomato Sauce",
+ "item.farmersdelight.fried_egg": "Fried Egg",
+ "item.farmersdelight.milk_bottle": "Milk Bottle",
+ "item.farmersdelight.hot_cocoa": "Hot Cocoa",
+ "item.farmersdelight.apple_cider": "Apple Cider",
+ "item.farmersdelight.melon_juice": "Melon Juice",
+ "item.farmersdelight.wheat_dough": "Wheat Dough",
+ "item.farmersdelight.raw_pasta": "Raw Pasta",
+ "item.farmersdelight.pumpkin_slice": "Pumpkin Slice",
+ "item.farmersdelight.cabbage_leaf": "Cabbage Leaf",
+ "item.farmersdelight.minced_beef": "Minced Beef",
+ "item.farmersdelight.beef_patty": "Beef Patty",
+ "item.farmersdelight.chicken_cuts": "Raw Chicken Cuts",
+ "item.farmersdelight.cooked_chicken_cuts": "Cooked Chicken Cuts",
+ "item.farmersdelight.bacon": "Raw Bacon",
+ "item.farmersdelight.cooked_bacon": "Cooked Bacon",
+ "item.farmersdelight.cod_slice": "Raw Cod Slice",
+ "item.farmersdelight.cooked_cod_slice": "Cooked Cod Slice",
+ "item.farmersdelight.salmon_slice": "Raw Salmon Slice",
+ "item.farmersdelight.cooked_salmon_slice": "Cooked Salmon Slice",
+ "item.farmersdelight.mutton_chops": "Raw Mutton Chops",
+ "item.farmersdelight.cooked_mutton_chops": "Cooked Mutton Chops",
+ "item.farmersdelight.ham": "Ham",
+ "item.farmersdelight.smoked_ham": "Smoked Ham",
+
+ "item.farmersdelight.pie_crust": "Pie Crust",
+ "item.farmersdelight.cake_slice": "Slice of Cake",
+ "item.farmersdelight.apple_pie_slice": "Slice of Apple Pie",
+ "item.farmersdelight.sweet_berry_cheesecake_slice": "Slice of Sweet Berry Cheesecake",
+ "item.farmersdelight.chocolate_pie_slice": "Slice of Chocolate Pie",
+ "item.farmersdelight.pumpkin_pie_slice": "Slice of Pumpkin Pie",
+ "item.farmersdelight.fruit_salad": "Fruit Salad",
+ "item.farmersdelight.sweet_berry_cookie": "Sweet Berry Cookie",
+ "item.farmersdelight.honey_cookie": "Honey Cookie",
+ "item.farmersdelight.melon_popsicle": "Melon Popsicle",
+ "item.farmersdelight.glow_berry_custard": "Glow Berry Custard",
+
+ "item.farmersdelight.earthworm": "Earthworm",
+
+ "item.farmersdelight.flint_knife": "Flint Knife",
+ "item.farmersdelight.iron_knife": "Iron Knife",
+ "item.farmersdelight.golden_knife": "Golden Knife",
+ "item.farmersdelight.diamond_knife": "Diamond Knife",
+ "item.farmersdelight.netherite_knife": "Netherite Knife",
+
+ "item.farmersdelight.barbecue_stick": "Barbecue on a Stick",
+ "item.farmersdelight.egg_sandwich": "Egg Sandwich",
+ "item.farmersdelight.chicken_sandwich": "Chicken Sandwich",
+ "item.farmersdelight.hamburger": "Hamburger",
+ "item.farmersdelight.bacon_sandwich": "Bacon Sandwich",
+ "item.farmersdelight.mutton_wrap": "Mutton Wrap",
+ "item.farmersdelight.dumplings": "Dumplings",
+ "item.farmersdelight.stuffed_potato": "Stuffed Potato",
+ "item.farmersdelight.cabbage_rolls": "Cabbage Rolls",
+ "item.farmersdelight.salmon_roll": "Salmon Roll",
+ "item.farmersdelight.cod_roll": "Cod Roll",
+ "item.farmersdelight.kelp_roll": "Kelp Roll",
+ "item.farmersdelight.kelp_roll_slice": "Kelp Roll Slice",
+
+ "item.farmersdelight.cooked_rice": "Cooked Rice",
+ "item.farmersdelight.mixed_salad": "Mixed Salad",
+ "item.farmersdelight.nether_salad": "Nether Salad",
+ "item.farmersdelight.beef_stew": "Beef Stew",
+ "item.farmersdelight.chicken_soup": "Chicken Soup",
+ "item.farmersdelight.vegetable_soup": "Vegetable Soup",
+ "item.farmersdelight.fish_stew": "Fish Stew",
+ "item.farmersdelight.fried_rice": "Fried Rice",
+ "item.farmersdelight.pumpkin_soup": "Pumpkin Soup",
+ "item.farmersdelight.baked_cod_stew": "Baked Cod Stew",
+ "item.farmersdelight.noodle_soup": "Noodle Soup",
+ "item.farmersdelight.onion_soup": "Onion Soup",
+ "item.farmersdelight.bone_broth": "Bone Broth",
+
+ "item.farmersdelight.bacon_and_eggs": "Bacon and Eggs",
+ "item.farmersdelight.pasta_with_meatballs": "Pasta with Meatballs",
+ "item.farmersdelight.pasta_with_mutton_chop": "Pasta with Mutton Chop",
+ "item.farmersdelight.roasted_mutton_chops": "Roasted Mutton Chops",
+ "item.farmersdelight.steak_and_potatoes": "Steak and Potatoes",
+ "item.farmersdelight.vegetable_noodles": "Vegetable Noodles",
+ "item.farmersdelight.ratatouille": "Ratatouille",
+ "item.farmersdelight.squid_ink_pasta": "Squid Ink Pasta",
+ "item.farmersdelight.grilled_salmon": "Grilled Salmon",
+ "item.farmersdelight.mushroom_rice": "Mushroom Rice",
+
+ "block.farmersdelight.roast_chicken_block": "Roast Chicken",
+ "item.farmersdelight.roast_chicken": "Plate of Roast Chicken",
+ "block.farmersdelight.stuffed_pumpkin_block": "Stuffed Pumpkin",
+ "item.farmersdelight.stuffed_pumpkin": "Bowl of Stuffed Pumpkin",
+ "block.farmersdelight.honey_glazed_ham_block": "Honey Glazed Ham",
+ "item.farmersdelight.honey_glazed_ham": "Plate of Honey Glazed Ham",
+ "block.farmersdelight.shepherds_pie_block": "Shepherd's Pie",
+ "item.farmersdelight.shepherds_pie": "Plate of Shepherd's Pie",
+ "block.farmersdelight.gleaming_salad_block": "Gleaming Salad",
+ "item.farmersdelight.gleaming_salad": "Bowl of Gleaming Salad",
+ "block.farmersdelight.rice_roll_medley_block": "Rice Roll Medley",
+
+ "item.farmersdelight.dog_food": "Dog Food",
+ "item.farmersdelight.horse_feed": "Horse Feed",
+
+ "entity.farmersdelight.rotten_tomato": "Rotten Tomato",
+
+ "effect.farmersdelight.nourishment": "Nourishment",
+ "effect.farmersdelight.nourishment.description": "Prevents the food meter from decreasing, except when healing with saturation",
+ "effect.farmersdelight.comfort": "Comfort",
+ "effect.farmersdelight.comfort.description": "Provides natural regeneration, regardless of how hungry you are.",
+
+ "enchantment.farmersdelight.backstabbing": "Backstabbing",
+ "enchantment.farmersdelight.backstabbing.description": "Amplifies damage when striking a target from behind.",
+
+ "itemGroup.farmersdelight": "Farmer's Delight",
+
+ "container.farmersdelight.recipe_book.cookable": "Showing Cookable",
+ "container.farmersdelight.cooking_pot": "Cooking Pot",
+ "container.farmersdelight.cooking_pot.served_on": "Served on: %s",
+ "container.farmersdelight.cooking_pot.not_heated": "Needs heat from below",
+ "container.farmersdelight.cooking_pot.heated": "Heated",
+ "container.farmersdelight.basket": "Basket",
+ "container.farmersdelight.cabinet": "Cabinet",
+
+ "tooltip.farmersdelight.cooking_pot.empty": "Empty",
+ "tooltip.farmersdelight.cooking_pot.single_serving": "Holds 1 serving of:",
+ "tooltip.farmersdelight.cooking_pot.many_servings": "Holds %s servings of:",
+ "tooltip.farmersdelight.dog_food.when_feeding": "When fed to a tamed Wolf:",
+ "tooltip.farmersdelight.horse_feed.when_feeding": "When fed to a tamed mount:",
+ "tooltip.farmersdelight.milk_bottle": "Clears 1 Effect",
+ "tooltip.farmersdelight.hot_cocoa": "Clears 1 Harmful Effect",
+ "tooltip.farmersdelight.melon_juice": "Minor Instant Health",
+ "tooltip.farmersdelight.placeable": "Placeable",
+ "tooltip.farmersdelight.placeable_sneaking": "Placeable when sneaking",
+ "tooltip.farmersdelight.debug_item": "Debug item - Not meant for gameplay",
+
+ "advancements.farmersdelight.root.title": "Farmer's Delight",
+ "advancements.farmersdelight.root.description": "A world of flavor awaits you!",
+
+ "advancements.farmersdelight.craft_knife.title": "Hunt and Gather",
+ "advancements.farmersdelight.craft_knife.description": "Craft a Knife to scavenge extra goods from plants and animals",
+ "advancements.farmersdelight.harvest_straw.title": "Grasping at Straws",
+ "advancements.farmersdelight.harvest_straw.description": "Harvest grass, wheat or rice with a Knife to collect Straw",
+ "advancements.farmersdelight.place_organic_compost.title": "Advanced Composting",
+ "advancements.farmersdelight.place_organic_compost.description": "Place down some Organic Compost. It composts better with sun, water and mushrooms!",
+ "advancements.farmersdelight.get_rich_soil.title": "Plant Food",
+ "advancements.farmersdelight.get_rich_soil.description": "Organic Compost slowly decays into Rich Soil, an upgrade for your farms!",
+ "advancements.farmersdelight.get_ham.title": "Wild Butcher",
+ "advancements.farmersdelight.get_ham.description": "Use a Knife to extract Ham from Pigs or Hoglins",
+ "advancements.farmersdelight.use_cutting_board.title": "Watch Your Fingers",
+ "advancements.farmersdelight.use_cutting_board.description": "With a tool in hand, use a Cutting Board to break down an item",
+ "advancements.farmersdelight.obtain_netherite_knife.title": "If You Can't Take the Heat...",
+ "advancements.farmersdelight.obtain_netherite_knife.description": "Spend a whole Netherite Ingot to upgrade your knife! Or get out of the kitchen.",
+
+ "advancements.farmersdelight.get_fd_seed.title": "Crops of the Wild",
+ "advancements.farmersdelight.get_fd_seed.description": "Four new crops may be found in the wild, across many climates... or maybe in a chest somewhere.",
+ "advancements.farmersdelight.plant_rice.title": "Dipping Your Roots",
+ "advancements.farmersdelight.plant_rice.description": "Plant grains of Rice in a shallow water puddle",
+ "advancements.farmersdelight.harvest_ropelogged_tomato.title": "Tall-mato",
+ "advancements.farmersdelight.harvest_ropelogged_tomato.description": "Hang some rope above a tomato crop to make it grow taller",
+ "advancements.farmersdelight.hit_raider_with_rotten_tomato.title": "Boo! Hiss!",
+ "advancements.farmersdelight.hit_raider_with_rotten_tomato.description": "Throw a Rotten Tomato at one of these pesky raiders!",
+ "advancements.farmersdelight.get_mushroom_colony.title": "Fungus Among Us",
+ "advancements.farmersdelight.get_mushroom_colony.description": "Shear a fully mature Mushroom Colony. To grow them like this, you'll need a very rich soil...",
+ "advancements.farmersdelight.plant_all_crops.title": "Crop Rotation",
+ "advancements.farmersdelight.plant_all_crops.description": "Cultivate every food-related plant you can find, such as vegetables, fruits, fungi or roots!",
+
+ "advancements.farmersdelight.place_campfire.title": "Bonfire Lit",
+ "advancements.farmersdelight.place_campfire.description": "Place down a Campfire to cook some food",
+ "advancements.farmersdelight.use_skillet.title": "Portable Cooking",
+ "advancements.farmersdelight.use_skillet.description": "Skillets let you cook on the go. Stand near heat, then hold food in your other hand!",
+ "advancements.farmersdelight.place_skillet.title": "Sizzling Hot!",
+ "advancements.farmersdelight.place_skillet.description": "Sneak to place your Skillet down as a block",
+ "advancements.farmersdelight.place_cooking_pot.title": "Dinner's Served!",
+ "advancements.farmersdelight.place_cooking_pot.description": "Put down a Cooking Pot and start preparing meals!",
+ "advancements.farmersdelight.eat_nourishing_food.title": "Nourishing!",
+ "advancements.farmersdelight.eat_nourishing_food.description": "A balanced and diverse meal will keep you fed and healthy for a long time!",
+ "advancements.farmersdelight.place_feast.title": "A Glorious Feast",
+ "advancements.farmersdelight.place_feast.description": "Some meals are big enough to be placed down and shared with friends!",
+ "advancements.farmersdelight.master_chef.title": "Master Chef",
+ "advancements.farmersdelight.master_chef.description": "Eat a course of every meal available!",
+
+ "subtitles.farmersdelight.block.cooking_pot.boil": "Cooking Pot boils",
+ "subtitles.farmersdelight.block.cutting_board.place_item": "Item placed on board",
+ "subtitles.farmersdelight.block.cutting_board.remove_item": "Item removed from board",
+ "subtitles.farmersdelight.block.cutting_board.carve_tool": "Tool carved on board",
+ "subtitles.farmersdelight.block.cutting_board.knife_cut": "Knife cuts",
+ "subtitles.farmersdelight.block.stove.crackle": "Stove crackles",
+ "subtitles.farmersdelight.block.skillet.sizzle": "Skillet sizzles",
+ "subtitles.farmersdelight.block.skillet.add_food": "Skillet hisses",
+ "subtitles.farmersdelight.block.rope_fence_gate.open": "Rope Fence Gate unties",
+ "subtitles.farmersdelight.block.rope_fence_gate.close": "Rope Fence Gate ties",
+ "subtitles.farmersdelight.block.cabinet.open": "Cabinet opens",
+ "subtitles.farmersdelight.block.cabinet.close": "Cabinet closes",
+ "subtitles.farmersdelight.block.tomatoes.pick_tomatoes": "Tomatoes pop",
+ "subtitles.farmersdelight.block.food.take_portion": "Portion served",
+ "subtitles.farmersdelight.block.food.slice": "Food sliced",
+ "subtitles.farmersdelight.entity.rotten_tomato.throw": "Rotten Tomato flies",
+ "subtitles.farmersdelight.entity.rotten_tomato.hit": "Rotten Tomato splatters",
+
+ "block.farmersdelight.feast.use_container": "You need a %s to eat this.",
+ "block.farmersdelight.rice.invalid_placement": "This seed must be planted in shallow water.",
+ "block.farmersdelight.cutting_board.invalid_item": "This doesn't seem cuttable...",
+ "block.farmersdelight.cutting_board.invalid_tool": "Maybe with a different tool...",
+ "block.farmersdelight.cutting_board.remaining_items": "%s remaining...",
+ "block.farmersdelight.skillet.invalid_item": "This can't be cooked...",
+ "block.farmersdelight.skillet.underwater": "Can't cook while flooded...",
+ "item.farmersdelight.skillet.how_to_cook": "Use your other hand to hold ingredients!",
+ "item.farmersdelight.skillet.underwater": "Can't cook underwater...",
+
+ "death.attack.farmersdelight.stove": "%1$s was grilled to perfection",
+ "death.attack.farmersdelight.stove.player": "%1$s was thrown on the grill by Chef %2$s",
+
+ "jei.farmersdelight.cooking": "Cooking",
+ "jei.farmersdelight.cutting": "Cutting Board",
+ "jei.farmersdelight.chance": "%1$s%% chance",
+ "jei.farmersdelight.decomposition": "Decomposition",
+ "jei.farmersdelight.decomposition.light": "Sped up by sunlight",
+ "jei.farmersdelight.decomposition.fluid": "Sped up by adjacent water",
+ "jei.farmersdelight.decomposition.accelerators": "Sped up by adjacent activators (see below)",
+
+ "jei.farmersdelight.info.dough": "Wheat can be turned into Dough by crafting it with a Water Bucket. But don't worry, the bucket's water won't be depleted!",
+ "jei.farmersdelight.info.straw": "A dry plant fiber used in crafting. Obtained by cutting grassy crops and plants with a Knife.",
+ "jei.farmersdelight.info.knife": "A lightweight melee weapon, with fast swings and gentle damage.\n\nThey can harvest Straw from grasses, and guarantee secondary drops from animals.",
+ "jei.farmersdelight.info.ham": "A large cut of meat from a Pig or Hoglin's leg.\n\nYou will need something lighter than a Sword to cut one...",
+ "jei.farmersdelight.info.wild_cabbages": "Cabbages can be commonly found as a wild plant in beach coasts.",
+ "jei.farmersdelight.info.wild_beetroots": "Beetroot can be commonly found as a wild plant in beach coasts.",
+ "jei.farmersdelight.info.wild_carrots": "Carrots can be commonly found as a wild plant in temperate lands, such as plains and forests.",
+ "jei.farmersdelight.info.wild_onions": "Onions can be commonly found as a wild plant in temperate lands, such as plains and forests.",
+ "jei.farmersdelight.info.wild_potatoes": "Potatoes can be commonly found as a wild plant in cold lands, such as mountains and taigas.",
+ "jei.farmersdelight.info.wild_tomatoes": "Tomatoes can be commonly found as a wild plant in arid lands, such as savannas and deserts.",
+ "jei.farmersdelight.info.wild_rice": "Rice can be commonly found as a wild plant in the ponds of wet lands, such as swamps and jungles.",
+
+ "emi.category.farmersdelight.cooking": "Cooking",
+ "emi.category.farmersdelight.cutting": "Cutting Board",
+ "emi.category.farmersdelight.decomposition": "Decomposition",
+
+ "tag.item.forge.berries": "Berries",
+ "tag.item.forge.bread": "Bread",
+ "tag.item.forge.bread.wheat": "Wheat Bread",
+ "tag.item.forge.cooked_bacon": "Cooked Bacon",
+ "tag.item.forge.cooked_beef": "Cooked Beef",
+ "tag.item.forge.cooked_chicken": "Cooked Chicken",
+ "tag.item.forge.cooked_eggs": "Cooked Eggs",
+ "tag.item.forge.cooked_fishes": "Cooked Fishes",
+ "tag.item.forge.cooked_fishes.cod": "Cooked Cod",
+ "tag.item.forge.cooked_fishes.salmon": "Cooked Salmon",
+ "tag.item.forge.cooked_mutton": "Cooked Mutton",
+ "tag.item.forge.cooked_pork": "Cooked Pork",
+ "tag.item.forge.crops.cabbage": "Cabbage Crops",
+ "tag.item.forge.crops.onion": "Onion Crops",
+ "tag.item.forge.crops.rice": "Rice Crops",
+ "tag.item.forge.crops.tomato": "Tomato Crops",
+ "tag.item.forge.dough": "Dough",
+ "tag.item.forge.dough.wheat": "Wheat Dough",
+ "tag.item.forge.eggs": "Eggs",
+ "tag.item.forge.grain": "Grains",
+ "tag.item.forge.grain.rice": "Rice Grains",
+ "tag.item.forge.grain.wheat": "Wheat Grains",
+ "tag.item.forge.milk": "Milk",
+ "tag.item.forge.milk.milk": "Milk Buckets",
+ "tag.item.forge.milk.milk_bottle": "Milk Bottles",
+ "tag.item.forge.pasta": "Pasta",
+ "tag.item.forge.pasta.raw_pasta": "Raw Pasta",
+ "tag.item.forge.raw_meat": "Raw Meat",
+ "tag.item.forge.raw_bacon": "Raw Bacon",
+ "tag.item.forge.raw_beef": "Raw Beef",
+ "tag.item.forge.raw_chicken": "Raw Chicken",
+ "tag.item.forge.raw_eggs": "Raw Eggs",
+ "tag.item.forge.raw_fishes": "Raw Fishes",
+ "tag.item.forge.raw_fishes.cod": "Raw Cod",
+ "tag.item.forge.raw_fishes.salmon": "Raw Salmon",
+ "tag.item.forge.raw_fishes.tropical_fish": "Tropical Fish",
+ "tag.item.forge.raw_mutton": "Raw Mutton",
+ "tag.item.forge.raw_pork": "Raw Pork",
+ "tag.item.forge.salad_ingredients": "Salad Ingredients",
+ "tag.item.forge.salad_ingredients.cabbage": "Cabbage Salad Ingredients",
+ "tag.item.forge.seeds.cabbage": "Cabbage Seeds",
+ "tag.item.forge.seeds.rice": "Rice Seeds",
+ "tag.item.forge.seeds.tomato": "Tomato Seeds",
+ "tag.item.forge.tools.knives": "Knives",
+ "tag.item.forge.vegetables": "Vegetables",
+ "tag.item.forge.vegetables.beetroot": "Beetroots",
+ "tag.item.forge.vegetables.carrot": "Carrots",
+ "tag.item.forge.vegetables.onion": "Onions",
+ "tag.item.forge.vegetables.potato": "Potatoes",
+ "tag.item.forge.vegetables.tomato": "Tomatoes",
+
+ "tag.block.farmersdelight.cabinets": "Cabinets",
+ "tag.block.farmersdelight.cabinets/wooden": "Wooden Cabinets",
+ "tag.block.farmersdelight.campfire_signal_smoke": "Campfire Signal Smoke",
+ "tag.block.farmersdelight.compost_activators": "Compost Activators",
+ "tag.block.farmersdelight.drops_cake_slice": "Drops Cake Slices",
+ "tag.block.farmersdelight.feasts": "Feasts",
+ "tag.block.farmersdelight.heat_conductors": "Heat Conductors",
+ "tag.block.farmersdelight.heat_sources": "Heat Sources",
+ "tag.block.farmersdelight.mineable/knife": "Mineable with Knife",
+ "tag.block.farmersdelight.mushroom_colonies": "Mushroom Colonies",
+ "tag.block.farmersdelight.mushroom_colony_growable_on": "Mushroom Colonies Can Grow On",
+ "tag.block.farmersdelight.pies": "Pies",
+ "tag.block.farmersdelight.planted_from_below": "Planted from Below",
+ "tag.block.farmersdelight.ropes": "Ropes",
+ "tag.block.farmersdelight.straw_blocks": "Straw Blocks",
+ "tag.block.farmersdelight.terrain": "Terrain",
+ "tag.block.farmersdelight.tray_heat_sources": "Heat Sources with a Tray",
+ "tag.block.farmersdelight.unaffected_by_rich_soil": "Unaffected by Rich Soil",
+ "tag.block.farmersdelight.wild_crops": "Wild Crops",
+
+ "tag.item.farmersdelight.cabinets": "Cabinets",
+ "tag.item.farmersdelight.cabinets.wooden": "Wooden Cabinets",
+ "tag.item.farmersdelight.canvas_signs": "Canvas Signs",
+ "tag.item.farmersdelight.drinks": "Drinks",
+ "tag.item.farmersdelight.feasts": "Feasts",
+ "tag.item.farmersdelight.flat_on_cutting_board": "Flat on Cutting Board",
+ "tag.item.farmersdelight.hanging_canvas_signs": "Hanging Canvas Signs",
+ "tag.item.farmersdelight.meals": "Meals",
+ "tag.item.farmersdelight.mushroom_colonies": "Mushroom Colonies",
+ "tag.item.farmersdelight.pies": "Pies",
+ "tag.item.farmersdelight.serving_containers": "Serving Containers",
+ "tag.item.farmersdelight.snacks": "Snacks",
+ "tag.item.farmersdelight.straw_harvesters": "Straw Harvesters",
+ "tag.item.farmersdelight.sweets": "Sweets",
+ "tag.item.farmersdelight.tools.knives": "Knives",
+ "tag.item.farmersdelight.wild_crops": "Wild Crops"
+}
diff --git a/modules/loot/package.json b/modules/loot/package.json
new file mode 100644
index 0000000..e826d6e
--- /dev/null
+++ b/modules/loot/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@adeficior/data-modifier-loot",
+ "version": "1.0.0",
+ "exports": {
+ ".": "./dist/index.js",
+ "./testing": "./dist/testing/index.js"
+ },
+ "types": "./dist/index.d.ts",
+ "typesVersions": {
+ "*": {
+ "testing": [
+ "./dist/testing/index.d.ts"
+ ]
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "scripts": {
+ "build": "tsc --project tsconfig.build.json",
+ "check": "tsc --noEmit",
+ "dev": "bun run build --watch",
+ "lint": "eslint {src,test}",
+ "test": "bun test --timeout 10000",
+ "prune": "bun run datamod-dev --prune",
+ "prepare": "datamod-dev --configs && datamod-dev --types",
+ "test:ci": "bun run lint && bun run check && bun run test --coverage"
+ },
+ "devDependencies": {
+ "@adeficior/configs": "workspace:*",
+ "@adeficior/testing": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*"
+ },
+ "peerDependencies": {
+ "@adeficior/pack-resolver": "catalog:",
+ "typescript": "catalog:",
+ "zod": "catalog:",
+ "@adeficior/data-modifier-core": "workspace:*",
+ "@adeficior/data-modifier-ingredients": "workspace:*",
+ "@adeficior/data-modifier-tags": "workspace:*"
+ }
+}
\ No newline at end of file
diff --git a/src/emit/data/loot.ts b/modules/loot/src/emitter.ts
similarity index 66%
rename from src/emit/data/loot.ts
rename to modules/loot/src/emitter.ts
index 84529b3..b2e97cf 100644
--- a/src/emit/data/loot.ts
+++ b/modules/loot/src/emitter.ts
@@ -1,24 +1,37 @@
-import { combineResolvers, type BaseContext } from "@adeficior/pack-resolver";
import {
- resolveIDTest,
+ type ClearableEmitter,
+ type Id,
+ type IdInput,
+ type LoaderContext,
+ type NormalizedId,
+ type RegistryLookup,
+ type RegistryProvider,
+ type SemVerInput,
+ CustomEmitter,
+ encodeId,
+ prefix,
+ RuledEmitter,
+} from "@adeficior/data-modifier-core";
+import {
type CommonFilter,
type Predicate,
-} from "../../common/filters.js";
-import type { Id, IdInput, NormalizedId } from "../../common/id.js";
-import { encodeId, prefix } from "../../common/id.js";
-import type { IngredientFilter } from "../../common/ingredient/filter.js";
-import createIngredientPredicate from "../../common/ingredient/filter.js";
-import type { Ingredient } from "../../common/ingredient/index.js";
-import type { PackContext } from "../../loader/context.js";
-import { lootTableFolder } from "../../packFormat.js";
-import type { LootItemInput } from "../../parser/lootTable.js";
-import { createLootEntry, replaceItemInTable } from "../../parser/lootTable.js";
-import type { LootModifier, LootTable } from "../../schema/data/loot.js";
-import { EmptyLootEntry, LootTableSchema } from "../../schema/data/loot.js";
-import CustomEmitter from "../custom.js";
-import type { ClearableEmitter, RegistryProvider } from "../index.js";
-import LootTableRule from "../rule/lootTable.js";
-import RuledEmitter from "../ruled.js";
+} from "@adeficior/data-modifier-core/serializer";
+import {
+ type Ingredient,
+ type IngredientFilter,
+ type Predicates,
+} from "@adeficior/data-modifier-ingredients";
+import { combineResolvers } from "@adeficior/pack-resolver";
+import { lootTablePath } from "./helper";
+import { LootTableRule } from "./rule";
+import {
+ type LootItemInput,
+ type LootModifier,
+ type LootTable,
+ EmptyLootEntry,
+ LootTableSchema,
+} from "./schema";
+import { createLootEntry, replaceItemInTable } from "./serializer";
export const EMPTY_LOOT_TABLE: LootTable = {
type: "minecraft:empty",
@@ -54,9 +67,9 @@ export interface LootRules {
disabledModifier(id: IdInput): void;
}
-export default class LootTableEmitter implements LootRules, ClearableEmitter {
+export class LootTableEmitter implements LootRules, ClearableEmitter {
private readonly customTables = new CustomEmitter((it) =>
- this.tablePath(it),
+ lootTablePath(this.packFormat, it),
);
private readonly customModifiers = new CustomEmitter