diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 2c28acbb..00000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,140 +0,0 @@
-{
- "env": {
- "browser": true,
- "es2021": true,
- "node": true
- },
- "extends": [
- "eslint:recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:@typescript-eslint/recommended-requiring-type-checking"
- ],
- "ignorePatterns": [
- "node_modules/",
- "dist/",
- "exporters/",
- "tools/",
- "svelte.config.ts",
- "vitest.config.ts",
- "src/util/bufferGeometryUtils.ts",
- "tests/**/*"
- ],
- "overrides": [
- {
- "files": ["*.js", "*.d.ts"],
- "parserOptions": {
- "project": "./tsconfig.json",
- "tsconfigRootDir": "./"
- },
- "rules": {}
- }
- ],
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "project": ["./tsconfig.json"],
- "tsconfigRootDir": "./"
- },
- "plugins": ["@typescript-eslint"],
- "rules": {
- "semi": ["warn", "never"],
- "prefer-const": "warn",
- "no-fallthrough": "off",
- "no-mixed-spaces-and-tabs": "off",
- "@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/no-floating-promises": ["error", { "ignoreVoid": true }],
- "@typescript-eslint/array-type": ["warn", { "default": "array-simple" }],
- "@typescript-eslint/ban-types": "warn",
- "@typescript-eslint/consistent-indexed-object-style": ["warn", "record"],
- "@typescript-eslint/consistent-generic-constructors": "warn",
- "@typescript-eslint/no-namespace": "off",
- "@typescript-eslint/restrict-template-expressions": "off",
- "@typescript-eslint/naming-convention": [
- "warn",
- {
- "selector": "class",
- "format": ["PascalCase"]
- },
- {
- "selector": ["classProperty", "classMethod"],
- "format": ["camelCase"]
- },
- {
- "selector": ["classProperty", "classMethod"],
- "filter": {
- "regex": "^_.*$",
- "match": true
- },
- "prefix": ["_"],
- "format": ["camelCase"]
- },
- {
- "selector": "typeProperty",
- "format": null
- },
- {
- "selector": "variable",
- "modifiers": ["const"],
- "format": null
- },
- {
- "selector": "variable",
- "modifiers": ["const", "destructured"],
- "format": ["camelCase", "PascalCase", "UPPER_CASE"]
- },
- {
- "selector": "variable",
- "modifiers": ["const", "global"],
- "format": ["UPPER_CASE"]
- },
- {
- "selector": "variable",
- "modifiers": ["const", "global"],
- "filter": {
- "regex": "^_.*$",
- "match": true
- },
- "prefix": ["_"],
- "format": ["UPPER_CASE"]
- },
- {
- "selector": "variable",
- "modifiers": ["const", "global"],
- "types": ["function"],
- "format": ["camelCase", "UPPER_CASE"]
- },
- {
- "selector": "variable",
- "modifiers": ["const", "global", "exported"],
- "format": ["camelCase", "UPPER_CASE"]
- },
- { "selector": "variableLike", "format": ["camelCase"] },
- { "selector": "interface", "format": ["PascalCase"] },
- {
- "selector": "interface",
- "modifiers": ["exported"],
- "format": ["PascalCase"],
- "prefix": ["I"]
- },
- { "selector": "typeLike", "format": ["PascalCase"] },
- { "selector": "objectLiteralProperty", "format": null },
- { "selector": "default", "format": ["camelCase"] },
- {
- "selector": "parameter",
- "modifiers": ["unused"],
- "format": null
- },
- {
- "selector": "enumMember",
- "format": ["UPPER_CASE"]
- }
- ],
- "@typescript-eslint/no-unsafe-member-access": "off",
- "@typescript-eslint/no-unsafe-assignment": "off",
- "@typescript-eslint/ban-ts-comment": "off",
- "@typescript-eslint/require-await": "warn",
- "@typescript-eslint/no-unsafe-call": "off",
- "@typescript-eslint/unbound-method": "off",
- "@typescript-eslint/no-non-null-assertion": "off",
- "@typescript-eslint/triple-slash-reference": "off"
- }
-}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 9395d6ca..4dddd09b 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,3 +1,9 @@
{
- "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "svelte.svelte-vscode"]
+ "recommendations": [
+ "esbenp.prettier-vscode",
+ "dbaeumer.vscode-eslint",
+ "svelte.svelte-vscode",
+ "ExodiusStudios.comment-anchors",
+ "SuperAnt.mc-dp-icons"
+ ]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index efe4f7ef..7c823c2f 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -24,6 +24,11 @@
"**/Thumbs.db": true,
"**/node_modules": true
},
+ "[yaml]": {
+ "editor.tabSize": 2,
+ "editor.insertSpaces": true,
+ "editor.detectIndentation": false
+ },
"npm.exclude": ["**/node_modules"],
"errorLens.excludePatterns": ["**/node_modules/**/*"],
"eslint.validate": ["javascript", "typescript", "svelte"],
diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz
new file mode 100644
index 00000000..08f8fe41
Binary files /dev/null and b/.yarn/install-state.gz differ
diff --git a/.yarn/patches/deepslate-npm-0.19.2-f859599b0a.patch b/.yarn/patches/deepslate-npm-0.19.2-f859599b0a.patch
new file mode 100644
index 00000000..7c5cf247
--- /dev/null
+++ b/.yarn/patches/deepslate-npm-0.19.2-f859599b0a.patch
@@ -0,0 +1,36 @@
+diff --git a/lib/nbt/index.d.ts b/lib/nbt/index.d.ts
+index 69843e7fc1b7c47c89bcd58270289736d2ef1a71..e3bbf5213144a5249a22b351ac6feeee6dd66972 100644
+--- a/lib/nbt/index.d.ts
++++ b/lib/nbt/index.d.ts
+@@ -1,7 +1,7 @@
+-export * from './io/DataInput.js';
+-export * from './io/DataOutput.js';
+-export * from './NbtChunk.js';
+-export * from './NbtFile.js';
+-export * from './NbtRegion.js';
+-export * from './tags/index.js';
++export * from './io/DataInput.js'
++export * from './io/DataOutput.js'
++// export * from './NbtChunk.js';
++// export * from './NbtFile.js';
++// export * from './NbtRegion.js';
++export * from './tags/index.js'
+ //# sourceMappingURL=index.d.ts.map
+diff --git a/lib/nbt/index.js b/lib/nbt/index.js
+index 5906e500f45d93439fc3c683a4cd11135980535b..15de3352ccbfb6f09b266edb0250e7b349324d8a 100644
+--- a/lib/nbt/index.js
++++ b/lib/nbt/index.js
+@@ -1,7 +1,7 @@
+-export * from './io/DataInput.js';
+-export * from './io/DataOutput.js';
+-export * from './NbtChunk.js';
+-export * from './NbtFile.js';
+-export * from './NbtRegion.js';
+-export * from './tags/index.js';
++export * from './io/DataInput.js'
++export * from './io/DataOutput.js'
++// export * from './NbtChunk.js';
++// export * from './NbtFile.js';
++// export * from './NbtRegion.js';
++export * from './tags/index.js'
+ //# sourceMappingURL=index.js.map
diff --git a/.yarnrc.yml b/.yarnrc.yml
new file mode 100644
index 00000000..3186f3f0
--- /dev/null
+++ b/.yarnrc.yml
@@ -0,0 +1 @@
+nodeLinker: node-modules
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..b8f300ee
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,75 @@
+
+
+
🧑💻 Contributing to Animated Java
+
+
+Contributions are always welcome, but please consult @SnaveSutit before starting to avoid duplicates or misalignment of goals. I don't want you to waste all that time and effort on a PR that gets refused!
+
+
+ Thank you! ❤️
+
+
+
+
+💻 Setting up the Development Environment
+
+### 🛠️ Prerequisites
+
+- #### Required
+
+ - [Node.js](https://nodejs.org/en/)
+ - [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#windows-stable)
+ - [Git](https://git-scm.com/)
+
+- #### Recommended
+
+ - [VSCode](https://code.visualstudio.com/)
+ > (or any other code editor, but this project has configurations for VSCode)
+ - [Blockbench](https://www.blockbench.net/)
+ > The repository includes [Envbench](https://github.com/SnaveSutit/envbench) to create and manage a dev instance of Blockbench, So installing Blockbench separately is not strictly required.
+ - [SnaveSutit's Blockbench Types](https://github.com/SnaveSutit/blockbench-types)
+ > Bleeding edge types for Blockbench plugins. Install via `yarn add -D https://github.com/SnaveSutit/blockbench-types.git`
+ - [GitButler](https://gitbutler.com/)
+ > A Git client for simultaneous branches on top of your existing workflow.
+
+## 🖇️ Cloning the Repository
+
+1. Clone the repository via git
+
+ > [How do I clone a repository?](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository)
+
+2. Run `yarn install` to install dependencies.
+
+3. Open up `src/blockbenchTypes.d.ts` and replace the first line with the following:
+
+ ```ts
+ ///
+ ```
+
+ > By default this references my local fork of the Blockbench types to quickly add / adjust types as needed. So you need to adjust this to the official Blockbench types, or your own fork, if you're not me.
+
+4. Run `yarn dev` to start the development environment, which will watch for changes and recompile the plugin.
+
+5. Open a new terminal, or click `Split` if you're in VSCode's terminal, and run `yarn start` to start the development instance of Blockbench.
+
+6. That's it! You're ready to start developing.
+
+> [!IMPORTANT]
+> Note that whenever you make changes, reloading just the plugins won't be enough, you must fully reload Blockbench for the plugin to function as expected.
+> You can use CTRL + SHIFT + R
+> to reload Blockbench, or run `Blockbench.reload()` in the dev-tools console.
+
+## 💬 Adding Localizations
+
+1. Follow the instructions for [Cloning the Repository](#🖇️-cloning-the-repository).
+
+2. Duplicate `src/lang/en.yml` and rename it to match the language you're translating to. (e.g. `fr.yml` for French).
+
+ > See [this page](https://github.com/JannisX11/blockbench/tree/master/lang) for the list of languages Blockbench supports.
+
+3. Start Translating! Make sure to check your changes in Blockbench to ensure they work correctly.
+
+ > [!IMPORTANT]
+ > You will see strings that have curly brackets surrounding a number (`{0}`) in them, these are placeholders that indicate where variables should inserted into a string. Make sure to keep them in your translations!
+
+4. Once you're done translating, open a pull request with your changes.
diff --git a/README.md b/README.md
index be731cc3..d4af7cd0 100644
--- a/README.md
+++ b/README.md
@@ -1,80 +1,111 @@
-
-
Animated Java
-
-
-
- A Blockbench plugin that makes complex animation a breeze in Minecraft: Java Edition.
+ Effortlessly craft complex animations in Minecraft: Java Edition
-# What is Animated Java?
+
+
+
+---
+
+
+
+# ❔ What is Animated Java?
+
+Animated Java is a cutting-edge Blockbench plugin designed for Minecraft: Java Edition mapmakers and Data Pack developers. By leveraging Blockbench's familiar interface, and the power of Java Edition's Data Pack and Resource Pack systems, Animated Java allows you to create complex animations with ease!
+
+
+
+# ✨ Key Features
+
+- **Function API** - Simple, yet powerful, API for summoning, and controlling animated models.
+
+- **Variants** - Swap between different textures in-game.
+
+- **Text Displays** - Preview, edit, and animate text displays in Blockbench.
+
+- **Keyframe Easing Curves** - Create smooth animations with ease.
+
+- **Locators** - Execute commands using Command Keyframes, teleport entities in an animation, and more.
+
+- **Animation Tweening** - Smoothly transition between animations.
+
+- **Camera Support** - Install the [Official Camera Plugin](https://www.blockbench.net/plugins/cameras) to create cinematic camera paths with ease.
-Animated Java is a plugin for Blockbench that allows you to create custom animations and models
-for Vanilla Minecraft. It uses the power of Java Edition's Data Pack and Resource Pack systems
-to bring your creations to life!
+- **Well Optimized** - Hours of effort have been poured into making Animated Java's Data Pack as low-impact as possible.
-## Features
+- **Resource Packs are Optional** - Animate Vanilla block and item models using Item and Block Display entities.
-- Variants: Swap between different textures in-game.
-- Highly optimized: Hours of performance tests and optimization tweaks have gone into Animated
- Java's Exported Data Pack to make sure it has as little performance impact as possible.
-- Custom easing functions for keyframes.
-- Limited Molang support. If Blockbench can render it, Animated Java can bake it.
-- Text Display previewing and animation support.
-- Resource Pack-less exporting. Animate Vanilla block and item models!
-- Locators: Run commands relative to a locators position via keyframes.
-- Camera Plugin Support: Install the [Official Camera Plugin](https://www.blockbench.net/plugins/cameras) to create cinematic camera paths with ease!
-- Animation Tweening: Create smooth transitions between animations.
-- Many different configuration options.
-- Complete Documentation at https://animated-java.dev
+- **Molang Support** - If Blockbench can render your Molang expressions in the preview, you can use it in Animated Java.
-And much more!
+
-# How to Install
+# 📦 Installation
-Follow our guide [here](https://animated-java.dev/docs/getting-started/installing-animated-java) for detailed instructions on how to install the latest release of Animated Java.
+- ❔ [Getting Started](https://animated-java.dev/docs/getting-started/using-animated-java)
+- 📚 [Documentation](https://animated-java.dev/docs)
+- 🕸️ [Website](https://animated-java.dev)
+- 🗣️ [Discord](https://animated-java.dev/discord)
-# Getting Started
+
-Check out the [Getting Started](https://animated-java.dev/docs/getting-started/installing-animated-java) page of our documentation to learn how to use Animated Java.
+# 💬 Testimonials
-# Contributing to Animated Java
+> _I love AJ 3000!_
+> — [MrMakistein](https://www.youtube.com/@McMakistein)
-We welcome contributions to Animated Java! If you're interested in contributing, please make sure to run your ideas by us in our [Discord server](https://discord.com/invite/jFgY4PXZfp) before starting work on them. So that we can ensure that your contributions align with the goals of the project.
+> _Wonderful tool, thank you for contributing to the creative possibilities in this game!_
+> — [Leroidesafk](https://www.curseforge.com/members/leroidesafk/projects)
-## Prerequisites
+> _Super thankful for AJ Snave! Its allowed me to create some insane sh\*\*_
+> — [phiac](https://www.youtube.com/channel/UCh2OK3oqxy-_azT-iwcSCag)
-Things you'll need installed before you can setup the development environment
+> _Thank you for the lovely tool!_
+> — [LeCarbonator](https://github.com/LeCarbonator)
-- [Blockbench](https://www.blockbench.net/)
-- [Node.js](https://nodejs.org/en/)
-- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#windows-stable)
+
-## Setting up the Development Environment
+# 🧑💻 Contributing
-1. Clone the repository.
-2. Run `yarn install` to install dependencies.
-3. Open up `types/blockbench-types.d.ts` and replace the content of the file with the following:
- ```ts
- ///
- ```
-4. Run `yarn dev` to start the development environment, which will watch for changes and recompile the plugin.
-5. Open Blockbench, then go to `File > Plugins > Load Plugin From File` and select the `animated_java.js` file from your local repo (`dist/pluginPackage/animated-java.js`).
-6. That's it! You're ready to start developing.
+Contributions are always welcome! Check out [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
-> [!NOTE]
-> Note that whenever you make changes, you must reload Blockbench as a whole for the changes to function as expected.
-> You can use `Ctrl + Shift + R` to reload Blockbench.
+
-## Adding Localizations
+# ⭐ Star History
-1. Follow the steps above to set up the development environment.
-2. Duplicate `src/lang/en.yml` and rename it to match the language you're translating to. (e.g. `fr.yml` for French)
-3. Start Translating! Make sure to check your changes in Blockbench to ensure they display correctly.
-4. Once you're done translating, open a pull request with your changes.
+
+
+
+
+
+
+
diff --git a/TODO.md b/TODO.md
index 06f09465..ad419336 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,146 +1,33 @@
+# Misc.
+
+- [ ] Remove direct NBT editing in favor of on-summon commands with the `data` command.
+
# Blockbench
-- [x] ~~Add a root NBT blueprint setting~~ Add a custom summon commands setting.
-- [x] Custom animation properties dialog
-- [x] Prevent the user setting their export namespace to 'global' or 'minecraft'
-- [x] Add affected bones list to variant, and animation properties.
- - [x] Variant Properties
- - [x] Animation Properties
-- [x] Implement .ajmodel to blueprint conversion.
-- [x] Change .ajmeta to use relative file paths.
-- [x] Resolve env variables in blueprint settings.
-- [x] Add renderbox options to the blueprint settings.
-- [x] Render and handle invalid cubes with a red outline.
-- [x] Force animations to be at least 1 tick long
-- [x] Respect Variant inheritance when applying variants in the animation preview.
-- [x] Add a transparency option to the variant texture map selection. (And don't export completely transparent bones)
-- [x] Locators
- - [x] Custom command keyframes.
- - [x] Locator config.
- - [x] Data Pack Compiler support.
-- [x] Natively support step keyframes.
-- [x] When upgrading old ajmodels, if they have command keyframes in the effect animator, create a locator at 0 0 0 with those keyframes.
-- [x] Add support for Text Displays
- - [x] Basic rendering
- - [x] Word wrapping - Thanks Fetchbot!
- - [x] Italic
- - [x] Bold
- - [x] Underline
- - [x] Strikethrough
- - [x] Array style inheritance
- - [x] Support vanilla fonts
- - [x] minecraft:default
- - [x] minecraft:alt
- - [x] minecraft:illageralt
- - [x] User interface
- - [x] Figure out a nice way to configure text displays...
- - [x] Include an option to change the text.
- - [x] Include an option to change the max line width.
- - [x] Animation
- - [x] Make sure the text display is animatable.
- - [x] Add a TextDisplay config.
- - [x] Add support for billboarding to TextDisplays.
- - [ ] Add an option to change the alignment of the text.
-- [x] Respect inheritance in the bone config.
-- [x] Change font rendering to use a geometry for each character instead of a single plane for the entire text display. This will open the possibility of loading custom fonts and spacing.
-- [x] Add vanilla block displays
- - [x] Create a custom element type for block displays.
- - [x] Add rendering for vanilla block models.
- - [x] Use Blockstates to select models.
- - [x] Parent model inheritance
- - [x] block/block
- - [x] Add overrides for entity-based block models.
- - [x] chest
- - [x] ender_chest
- - [x] mob heads
- - [x] shulker boxes
- - [x] beds
- - [x] multi-parts like walls throw an intneral error if they don't have any elements.
-- [x] Add an option to Locators to use the old entity-based functionality.
-- [x] Add an about page.
-- [x] Camera Plugin Support
- - [x] Data Pack Compiler support.
-- [x] Add vanilla item displays
- - [x] Create a custom element type for item displays.
- - [x] Add rendering for vanilla item models.
- - [x] Parent model inheritance
- - [x] item/generated
- - [x] item/handheld
- - [x] item/handheld_rod
- - [x] item/handheld_mace
- - [ ] Add overrides for entity-based models.
- - [x] conduit
- - [x] decorated_pot
- - [x] template_banner
- - [x] template_shulker_box
- - [x] template_skull
- - [x] banners
- - [x] shield
- - [x] trident
-- [ ] Change the Collection setting type to allow single-click swapping of items between lists.
-- [ ] Look into adding a color picker for tintable vanilla items.
-- [ ] Add Variants to the UndoSystem (Blocked by vanilla Blockbench not supporting custom undo actions).
-- [ ] Remove `easingArgs` and `easingMode` from saved keyframes if `easingType` is `linear`.
-- [ ] Add an option to generate a `damage_flash` variant for mob-type entities.
-- [ ] Add a fix for 360 rotation snap by using `set_frame` instead of `apply_frame` for the first frame of the animation.
+- [ ] Change the Collection setting type to allow single-click swapping of items between lists.
+- [ ] Look into adding a color picker for tintable vanilla items.
+- [ ] Add Variants to the UndoSystem (Blocked by vanilla Blockbench not supporting custom undo actions).
+- [ ] Remove `easingArgs` and `easingMode` from saved keyframes if `easingType` is `linear`.
+- [ ] Add an option to generate a `damage_flash` variant for mob-type entities.
+- [ ] Add a fix for 360 rotation snap by using `set_frame` instead of `apply_frame` for the first frame of the animation.
+- [ ] Add an `Interaction` Outliner Element.
# Data Pack Compiler
-- [x] Merge on_tick and on_load function tags
-- [x] When merging the new minecraft:tick tag with old one, try and find any old style function references (AKA animated_java:my_project/zzzzzz/tick), and remove them.
-- [x] Animation Tweening
-- [x] Implement animation loop mode tech.
-- [x] Write files after compilaion is done by using a queue system.
-- [x] Make data pack compiler as async as possible.
-- [x] Actually respect variant config options.
-- [x] Warn the user when a previously summoned rig needs to be re-summoned due to changes in the blueprint.
-- [x] Figure out how to add repeating functionality to command keyframes.
-- [x] Add toggles to command keyframes to allow continuously running the commands in the keyframe instead of only once when the keyframe is reached.
-- [x] Teleport the rig to the execution location of the summon command.
-- [x] Rotate the bones with the root entity.
-- [x] Add default saved Locator positions to the summoned rig.
-- [x] Add support for text displays.
-- [x] Add support for vanilla item displays.
-- [x] Add support for vanilla block displays.
-- [x] Locator rotation inheritance support - looks like they've supported it all this time...
-- [x] Apply variant keyframes in animations.
-- [x] Figure out how cameras will work.
-- [x] See how much swapping to a static list of UUIDs for selecting bones effects performance.
-- [x] Split up animation storage data command to avoid command length limit.
-- [x] Check for references to non-existant functions in merged function tags, and remove them.
-- [ ] When applying variants, remove / replace any bones that have / had no elements with textured faces.
+- [ ] When applying variants, remove / replace any bones that have / had no elements with textured faces.
+- [ ] Add a system that detects the version of Minecraft that the data pack is being exported into. (Can probably use the level.dat of the world if it exists?)
-- [x] Add a toast notification for when the model has invalid rotations.
# Resource Pack
-- [x] Warn the user when they have custom elements in their model, but have disabled the resource pack export.
+- [x] Warn the user when they have custom elements in their model, but have disabled the resource pack export.
# Plugin Exporter
-- [x] Add an option to export a JSON file.
-
-# List of numbers to track
-
-- [ ] Total exports
- - Stored in amount per day
-- [ ] Total functions created by the data pack compiler
- - Stored in amount per day
-
-# Github
-
-- [x] Reorganize the repo's branches.
- - [x] Create a `release` branch.
- - [x] Create a `dev` branch.
- - [x] Create a `legacy-beta` tag.
- - [x] Create a `legacy-armorstands` tag.
-- [ ] Reorganize the repo's tags.
- - [ ] Create a `v1.0.0` tag.
- - [x] Create a `legacy-beta` tag.
- - [x] Create a `legacy-armorstands` tag.
+- [x] Add an option to export a JSON file.
# Post 1.0.0
-- [ ] Add support for [block-display.com's API](https://wiki.block-display.com/api/get-api)
-- [ ] Add support for custom fonts in TextDisplays.
-- [ ] Add support and previewing for interaction entity based locators.
-- [ ] Add support for previewing player skins on heads.
+- [ ] Add support for [block-display.com's API](https://wiki.block-display.com/api/get-api)
+- [ ] Add support for custom fonts in TextDisplays.
+- [ ] Add support and previewing for interaction entity based locators.
+- [ ] Add support for previewing player skins on heads.
diff --git a/eslint.config.ts b/eslint.config.ts
new file mode 100644
index 00000000..a87994e7
--- /dev/null
+++ b/eslint.config.ts
@@ -0,0 +1,268 @@
+import checkFile from 'eslint-plugin-check-file'
+import svelteEslint from 'eslint-plugin-svelte'
+import svelteParser from 'svelte-eslint-parser'
+import tsESLint, { type ConfigWithExtends } from 'typescript-eslint'
+import svelteConfig from './svelte.config'
+import type { NamingConventionRule } from './tools/tslintNamingConventionRule'
+
+console.log(`[${new Date().toLocaleTimeString()}] Loading ESLint config`)
+
+const IGNORE_PATTERNS = [
+ '.DS_Store',
+ '.env',
+ '.env.*',
+ '.github',
+ '.vscode',
+ '**/node_modules/**',
+
+ // Blockbench Plugin Template
+ 'dist/**/*',
+
+ // Ignore files for PNPM, NPM and YARN
+ 'pnpm-lock.yaml',
+ 'package-lock.json',
+ 'yarn.lock',
+]
+
+const CUSTOM_RULES: ConfigWithExtends['rules'] = {
+ // ESLint
+ semi: ['error', 'never'],
+ 'prefer-const': 'warn',
+ 'no-fallthrough': 'off',
+ 'no-mixed-spaces-and-tabs': 'off',
+ 'no-unreachable': 'warn',
+ '@typescript-eslint/no-unused-vars': [
+ 'warn',
+ {
+ vars: 'local',
+ args: 'after-used',
+ argsIgnorePattern: '^_',
+ ignoreRestSiblings: true,
+ },
+ ],
+ // Svelte
+ 'svelte/html-quotes': ['warn', { prefer: 'double' }],
+ 'svelte/block-lang': ['error', { script: ['ts', null], style: null }],
+ 'svelte/comment-directive': ['error', { reportUnusedDisableDirectives: true }],
+ // Check File
+ 'check-file/filename-naming-convention': [
+ 'error',
+ {
+ 'src/**/*.{ts.d.ts}': 'CAMEL_CASE',
+ 'tools/**/*.{ts.d.ts}': 'CAMEL_CASE',
+ },
+ ],
+ 'check-file/folder-naming-convention': [
+ 'error',
+ {
+ 'src/**': 'KEBAB_CASE',
+ 'tools/**': 'KEBAB_CASE',
+ },
+ ],
+ // TypeScript
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
+ '@typescript-eslint/array-type': ['warn', { default: 'array-simple' }],
+ '@typescript-eslint/consistent-indexed-object-style': ['warn', 'interface'],
+ '@typescript-eslint/consistent-generic-constructors': 'warn',
+ '@typescript-eslint/no-namespace': 'off',
+ '@typescript-eslint/restrict-template-expressions': 'off',
+ '@typescript-eslint/no-unsafe-member-access': 'off',
+ '@typescript-eslint/no-unsafe-assignment': 'off',
+ '@typescript-eslint/ban-ts-comment': 'off',
+ '@typescript-eslint/require-await': 'warn',
+ '@typescript-eslint/no-unsafe-call': 'off',
+ '@typescript-eslint/unbound-method': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'off',
+ '@typescript-eslint/triple-slash-reference': 'off',
+ // Naming conventions
+ '@typescript-eslint/naming-convention': [
+ 'warn',
+ {
+ // DFU Version imports
+ selector: ['import'],
+ modifiers: ['default'],
+ filter: {
+ regex: 'v\\d+_\\d+_\\d+$',
+ match: true,
+ },
+ custom: {
+ match: true,
+ regex: 'v\\d+_\\d+_\\d+$',
+ },
+ format: null,
+ },
+ {
+ selector: ['import'],
+ modifiers: ['default'],
+ format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
+ },
+ {
+ selector: 'class',
+ format: ['PascalCase'],
+ },
+ {
+ selector: ['classProperty', 'classMethod'],
+ format: ['camelCase'],
+ },
+ {
+ selector: ['classProperty', 'classMethod'],
+ leadingUnderscore: 'allow',
+ format: ['camelCase'],
+ },
+ {
+ selector: ['classProperty', 'classMethod'],
+ modifiers: ['private'],
+ leadingUnderscore: 'allowDouble',
+ trailingUnderscore: 'allowDouble',
+ format: ['camelCase'],
+ },
+ {
+ selector: 'typeProperty',
+ format: null,
+ },
+ {
+ selector: 'variable',
+ modifiers: ['const', 'destructured'],
+ format: null,
+ },
+ {
+ selector: 'variable',
+ modifiers: ['const', 'global'],
+ types: ['function'],
+ leadingUnderscore: 'allow',
+ format: ['UPPER_CASE', 'camelCase'],
+ },
+ {
+ selector: 'variable',
+ modifiers: ['const', 'global'],
+ leadingUnderscore: 'allow',
+ format: ['UPPER_CASE'],
+ },
+ {
+ selector: 'variable',
+ modifiers: ['const', 'exported'],
+ format: ['camelCase', 'UPPER_CASE'],
+ },
+ {
+ selector: 'variableLike',
+ format: ['camelCase'],
+ },
+ { selector: 'interface', format: ['PascalCase'] },
+ {
+ selector: 'interface',
+ modifiers: ['exported'],
+ format: ['PascalCase'],
+ prefix: ['I'],
+ },
+ { selector: 'typeLike', format: ['PascalCase'] },
+ { selector: 'objectLiteralProperty', format: null },
+ { selector: 'default', format: ['camelCase'] },
+ {
+ selector: 'parameter',
+ modifiers: ['unused'],
+ format: ['camelCase'],
+ leadingUnderscore: 'allow',
+ },
+ {
+ selector: 'parameter',
+ format: ['camelCase'],
+ },
+ {
+ selector: 'enumMember',
+ format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
+ },
+ {
+ selector: 'enum',
+ format: ['UPPER_CASE'],
+ },
+ ] satisfies NamingConventionRule,
+}
+
+export default tsESLint.config(
+ {
+ ignores: IGNORE_PATTERNS,
+ },
+ ...tsESLint.configs.stylisticTypeChecked,
+ ...svelteEslint.configs['flat/prettier'],
+ {
+ plugins: {
+ '@typescript-eslint': tsESLint.plugin,
+ svelte: svelteEslint,
+ 'check-file': checkFile,
+ },
+ },
+ {
+ rules: CUSTOM_RULES,
+ },
+ {
+ languageOptions: {
+ parser: tsESLint.parser,
+ parserOptions: {
+ project: './tsconfig.json',
+ extraFileExtensions: ['.svelte'],
+ },
+ globals: {
+ browser: true,
+ node: true,
+ },
+ },
+ },
+ {
+ files: ['**/*.svelte'],
+ rules: {
+ // Causes issues with Svelte and global types
+ 'no-undef': 'off',
+ '@typescript-eslint/naming-convention': [
+ 'warn',
+ {
+ selector: 'variable',
+ modifiers: ['exported'],
+ format: ['camelCase'],
+ },
+ {
+ selector: 'variable',
+ modifiers: ['const', 'global'],
+ format: ['UPPER_CASE'],
+ },
+ {
+ selector: 'variable',
+ modifiers: ['const', 'global'],
+ types: ['function'],
+ format: ['camelCase'],
+ },
+ {
+ selector: 'variable',
+ format: ['camelCase'],
+ leadingUnderscore: 'allow',
+ },
+ ] satisfies NamingConventionRule,
+ },
+ languageOptions: {
+ parser: svelteParser,
+ parserOptions: {
+ parser: tsESLint.parser,
+ svelteConfig: svelteConfig,
+ extraFileExtensions: ['.svelte'],
+ },
+ globals: {
+ browser: true,
+ node: true,
+ },
+ },
+ settings: {
+ ignoreWarnings: ['svelte/a11y-no-onchange', 'a11y-no-onchange'],
+ },
+ },
+ {
+ languageOptions: {
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: __dirname,
+ },
+ },
+ linterOptions: {
+ reportUnusedDisableDirectives: true,
+ },
+ }
+)
diff --git a/package.json b/package.json
index 08e53c6e..96ab8841 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
- "type": "module",
+ "type": "commonjs",
"name": "animated_java",
"title": "Animated Java",
"icon": "icon.svg",
- "description": "A Blockbench plugin that makes complex animation a breeze in Minecraft: Java Edition.",
+ "description": "Effortlessly craft complex animations in Minecraft: Java Edition",
"version": "1.7.3",
"min_blockbench_version": "4.12.0",
"variant": "desktop",
@@ -76,7 +76,8 @@
"scripts": {
"build:scripts": "esbuild --bundle --platform=node --outfile=dist/build.cjs --packages=external ./tools/esbuild.ts",
"dev": "yarn build:scripts && node ./dist/build.cjs --mode=dev",
- "prod": "node ./tools/cleanupDist.cjs && yarn build:scripts && node ./dist/build.cjs",
+ "prod": "yarn build:scripts && node ./dist/build.cjs",
+ "lint": "eslint . --max-warnings=0",
"format": "prettier --write .",
"test": "yarn build:scripts && vitest run",
"coverage": "yarn build:scripts && vitest run --coverage"
@@ -84,33 +85,40 @@
"devDependencies": {
"@novacbn/svelte-codejar": "^0.1.2",
"@types/download": "^8.0.5",
- "@types/eslint": "^8.21.1",
+ "@types/eslint": "^9.6.1",
+ "@types/find-cache-dir": "^5.0.2",
"@types/js-yaml": "^4.0.5",
- "@types/node": "^17.0.21",
+ "@types/node": "^22.13.5",
"@types/websocket": "^1.0.10",
- "@typescript-eslint/eslint-plugin": "^5.54.0",
- "@typescript-eslint/parser": "^5.54.0",
+ "@typescript-eslint/eslint-plugin": "^8.24.0",
+ "@typescript-eslint/parser": "^8.24.0",
"blockbench-types": "https://github.com/SnaveSutit/blockbench-types.git",
- "esbuild": "^0.17.10",
+ "envbench": "^3.0.2",
+ "esbuild": "^0.25.0",
"esbuild-plugin-import-glob": "^0.1.1",
"esbuild-plugin-inline-image": "^0.0.9",
"esbuild-plugin-inline-worker": "^0.1.1",
"esbuild-plugin-svelte": "^0.1.1",
- "eslint": "^8.35.0",
+ "eslint": "^9.20.0",
+ "eslint-plugin-check-file": "^3.0.0",
+ "eslint-plugin-svelte": "^2.46.1",
+ "find-cache-dir": "^5.0.0",
"firebase": "^9.19.0",
+ "jiti": "^2.4.2",
"js-yaml": "^4.1.0",
- "prettier": "^2.5.1",
- "svelte": "^3.55.1",
+ "node-modules-vscode-problems-patch": "^1.0.8",
+ "prettier": "^3.5.0",
+ "svelte": "3.59.2",
"svelte-awesome-color-picker": "^3.0.0-beta.7",
- "svelte-multiselect": "^11.0.0-rc.1",
+ "svelte-eslint-parser": "^0.43.0",
"svelte-preprocess": "^5.0.1",
"svelte-preprocess-esbuild": "^3.0.1",
- "typescript": "^4.5.5",
+ "typescript": "^5.7.3",
+ "typescript-eslint": "^8.24.0",
"vitest": "^2.1.8"
},
"dependencies": {
- "deepslate": "^0.19.2",
- "download": "^8.0.0",
+ "deepslate": "patch:deepslate@npm%3A0.19.2#~/.yarn/patches/deepslate-npm-0.19.2-f859599b0a.patch",
"fflate": "^0.8.2",
"generic-stream": "^1.2.6",
"marked": "^4.3.0",
@@ -118,7 +126,8 @@
"mc-build": "^3.5.1",
"request-progress": "^3.0.0",
"svelte-ace": "^1.0.21",
- "svelte-dnd-action": "^0.9.38"
+ "svelte-dnd-action": "^0.9.38",
+ "svelte-multiselect": "^11.1.1"
},
- "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
+ "packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728"
}
diff --git a/src/assets/banners/animated_java_title_banner.svg b/src/assets/banners/animated_java_title_banner.svg
new file mode 100644
index 00000000..bd2073b7
--- /dev/null
+++ b/src/assets/banners/animated_java_title_banner.svg
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/icons/animated_java_fancy_icon.svg b/src/assets/icons/animated_java_fancy_icon.svg
new file mode 100644
index 00000000..ecad65e5
--- /dev/null
+++ b/src/assets/icons/animated_java_fancy_icon.svg
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/animated_java_icon.svg b/src/assets/icons/animated_java_icon.svg
similarity index 100%
rename from src/assets/animated_java_icon.svg
rename to src/assets/icons/animated_java_icon.svg
diff --git a/src/assets/animated_java_icon.webp b/src/assets/icons/animated_java_icon.webp
similarity index 100%
rename from src/assets/animated_java_icon.webp
rename to src/assets/icons/animated_java_icon.webp
diff --git a/src/assets/animated_java_icon_no_background.svg b/src/assets/icons/animated_java_icon_no_background.svg
similarity index 100%
rename from src/assets/animated_java_icon_no_background.svg
rename to src/assets/icons/animated_java_icon_no_background.svg
diff --git a/src/assets/animated_paper_icon.svg b/src/assets/icons/animated_paper_icon.svg
similarity index 100%
rename from src/assets/animated_paper_icon.svg
rename to src/assets/icons/animated_paper_icon.svg
diff --git a/src/assets/easingIcons/Back.svg b/src/assets/icons/easings/Back.svg
similarity index 100%
rename from src/assets/easingIcons/Back.svg
rename to src/assets/icons/easings/Back.svg
diff --git a/src/assets/easingIcons/Bounce.svg b/src/assets/icons/easings/Bounce.svg
similarity index 100%
rename from src/assets/easingIcons/Bounce.svg
rename to src/assets/icons/easings/Bounce.svg
diff --git a/src/assets/easingIcons/Circ.svg b/src/assets/icons/easings/Circ.svg
similarity index 100%
rename from src/assets/easingIcons/Circ.svg
rename to src/assets/icons/easings/Circ.svg
diff --git a/src/assets/easingIcons/Cubic.svg b/src/assets/icons/easings/Cubic.svg
similarity index 100%
rename from src/assets/easingIcons/Cubic.svg
rename to src/assets/icons/easings/Cubic.svg
diff --git a/src/assets/easingIcons/Elastic.svg b/src/assets/icons/easings/Elastic.svg
similarity index 100%
rename from src/assets/easingIcons/Elastic.svg
rename to src/assets/icons/easings/Elastic.svg
diff --git a/src/assets/easingIcons/Expo.svg b/src/assets/icons/easings/Expo.svg
similarity index 100%
rename from src/assets/easingIcons/Expo.svg
rename to src/assets/icons/easings/Expo.svg
diff --git a/src/assets/easingIcons/InOut.svg b/src/assets/icons/easings/InOut.svg
similarity index 100%
rename from src/assets/easingIcons/InOut.svg
rename to src/assets/icons/easings/InOut.svg
diff --git a/src/assets/easingIcons/Linear.svg b/src/assets/icons/easings/Linear.svg
similarity index 100%
rename from src/assets/easingIcons/Linear.svg
rename to src/assets/icons/easings/Linear.svg
diff --git a/src/assets/easingIcons/Out.svg b/src/assets/icons/easings/Out.svg
similarity index 100%
rename from src/assets/easingIcons/Out.svg
rename to src/assets/icons/easings/Out.svg
diff --git a/src/assets/easingIcons/Quad.svg b/src/assets/icons/easings/Quad.svg
similarity index 100%
rename from src/assets/easingIcons/Quad.svg
rename to src/assets/icons/easings/Quad.svg
diff --git a/src/assets/easingIcons/Quart.svg b/src/assets/icons/easings/Quart.svg
similarity index 100%
rename from src/assets/easingIcons/Quart.svg
rename to src/assets/icons/easings/Quart.svg
diff --git a/src/assets/easingIcons/Quint.svg b/src/assets/icons/easings/Quint.svg
similarity index 100%
rename from src/assets/easingIcons/Quint.svg
rename to src/assets/icons/easings/Quint.svg
diff --git a/src/assets/easingIcons/Sine.svg b/src/assets/icons/easings/Sine.svg
similarity index 100%
rename from src/assets/easingIcons/Sine.svg
rename to src/assets/icons/easings/Sine.svg
diff --git a/src/assets/easingIcons/Step.svg b/src/assets/icons/easings/Step.svg
similarity index 100%
rename from src/assets/easingIcons/Step.svg
rename to src/assets/icons/easings/Step.svg
diff --git a/src/assets/impulse_command_block.png b/src/assets/impulse_command_block.png
new file mode 100644
index 00000000..6553b9c7
Binary files /dev/null and b/src/assets/impulse_command_block.png differ
diff --git a/src/assets/papermc.svg b/src/assets/papermc.svg
new file mode 100644
index 00000000..6b44eda9
--- /dev/null
+++ b/src/assets/papermc.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/blockbench-additions/class-properties/animation.ts b/src/blockbench-additions/class-properties/animation.ts
new file mode 100644
index 00000000..3b5e0ec7
--- /dev/null
+++ b/src/blockbench-additions/class-properties/animation.ts
@@ -0,0 +1,27 @@
+import { PACKAGE } from '../../constants'
+import { type ContextProperty, createBlockbenchMod } from '../../util/moddingTools'
+import { translate } from '../../util/translation'
+import { isCurrentFormat } from '../model-formats/ajblueprint'
+
+createBlockbenchMod(
+ `${PACKAGE.name}:additions/class-properties/animation`,
+ {
+ excludedNodesProperty: undefined as ContextProperty<'array'>,
+ },
+ context => {
+ context.excludedNodesProperty = new Property(
+ Blockbench.Animation,
+ 'array',
+ 'excluded_nodes',
+ {
+ condition: () => isCurrentFormat(),
+ label: translate('animation.excluded_nodes'),
+ default: [],
+ }
+ )
+ return context
+ },
+ context => {
+ context.excludedNodesProperty?.delete()
+ }
+)
diff --git a/src/mods/bonePropertiesMod.ts b/src/blockbench-additions/class-properties/group.ts
similarity index 75%
rename from src/mods/bonePropertiesMod.ts
rename to src/blockbench-additions/class-properties/group.ts
index 349640da..4cd59ce1 100644
--- a/src/mods/bonePropertiesMod.ts
+++ b/src/blockbench-additions/class-properties/group.ts
@@ -1,6 +1,6 @@
-import { isCurrentFormat as condition } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { type ContextProperty, createBlockbenchMod } from '../util/moddingTools'
+import { PACKAGE } from '../../constants'
+import { type ContextProperty, createBlockbenchMod } from '../../util/moddingTools'
+import { isCurrentFormat as condition } from '../model-formats/ajblueprint'
class DeepClonedObjectProperty extends Property<'object'> {
constructor(targetClass: any, name: string, options?: PropertyOptions) {
@@ -19,7 +19,7 @@ class DeepClonedObjectProperty extends Property<'object'> {
}
createBlockbenchMod(
- `${PACKAGE.name}:boneProperties`,
+ `${PACKAGE.name}:additions/class-properties/group`,
{
configs: undefined as ContextProperty<'object'>,
},
diff --git a/src/blockbench-additions/class-properties/keyframe.ts b/src/blockbench-additions/class-properties/keyframe.ts
new file mode 100644
index 00000000..95902e90
--- /dev/null
+++ b/src/blockbench-additions/class-properties/keyframe.ts
@@ -0,0 +1,28 @@
+import { PACKAGE } from '../../constants'
+import { type ContextProperty, createBlockbenchMod } from '../../util/moddingTools'
+import { isCurrentFormat } from '../model-formats/ajblueprint'
+
+import { EASING_DEFAULT } from '../../util/easing'
+
+createBlockbenchMod(
+ `${PACKAGE.name}:additions/class-properties/keyframe`,
+ {
+ easingProperty: undefined as ContextProperty<'string'>,
+ easingArgsProperty: undefined as ContextProperty<'array'>,
+ },
+ context => {
+ context.easingProperty = new Property(Blockbench.Keyframe, 'string', 'easing', {
+ default: EASING_DEFAULT,
+ condition: () => isCurrentFormat(),
+ })
+ context.easingArgsProperty = new Property(Blockbench.Keyframe, 'array', 'easingArgs', {
+ condition: () => isCurrentFormat(),
+ })
+
+ return context
+ },
+ context => {
+ context.easingProperty?.delete()
+ context.easingArgsProperty?.delete()
+ }
+)
diff --git a/src/mods/locatorPropertiesMod.ts b/src/blockbench-additions/class-properties/locator.ts
similarity index 51%
rename from src/mods/locatorPropertiesMod.ts
rename to src/blockbench-additions/class-properties/locator.ts
index 21806c88..49160d25 100644
--- a/src/mods/locatorPropertiesMod.ts
+++ b/src/blockbench-additions/class-properties/locator.ts
@@ -1,9 +1,9 @@
-import { isCurrentFormat as condition } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { type ContextProperty, createBlockbenchMod } from '../util/moddingTools'
+import { PACKAGE } from '../../constants'
+import { type ContextProperty, createBlockbenchMod } from '../../util/moddingTools'
+import { isCurrentFormat as condition } from '../model-formats/ajblueprint'
createBlockbenchMod(
- `${PACKAGE.name}:locatorProperties`,
+ `${PACKAGE.name}:additions/class-properties/locator`,
{
config: undefined as ContextProperty<'instance'>,
},
diff --git a/src/blockbench-additions/index.ts b/src/blockbench-additions/index.ts
new file mode 100644
index 00000000..442b4aa2
--- /dev/null
+++ b/src/blockbench-additions/index.ts
@@ -0,0 +1,11 @@
+// Class Properties
+import './class-properties/animation'
+import './class-properties/group'
+import './class-properties/keyframe'
+import './class-properties/locator'
+// Model Formats
+import './model-formats/ajblueprint'
+// Outliner Elements
+import './outliner-elements/blockDisplay'
+import './outliner-elements/itemDisplay'
+import './outliner-elements/textDisplay'
diff --git a/src/systems/modelDataFixerUpper.ts b/src/blockbench-additions/model-formats/ajblueprint/dfu.ts
similarity index 93%
rename from src/systems/modelDataFixerUpper.ts
rename to src/blockbench-additions/model-formats/ajblueprint/dfu.ts
index bd20252c..386cf91f 100644
--- a/src/systems/modelDataFixerUpper.ts
+++ b/src/blockbench-additions/model-formats/ajblueprint/dfu.ts
@@ -1,9 +1,9 @@
+import { CommonDisplayConfig } from '@aj/systems/node-configs'
+import TransparentTexture from '@assets/transparent.png'
import { NbtCompound, NbtList, NbtString, NbtTag } from 'deepslate/lib/nbt'
-import TransparentTexture from '../assets/transparent.png'
-import { IBlueprintFormatJSON, getDefaultProjectSettings } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { openUnexpectedErrorDialog } from '../interface/dialog/unexpectedError'
-import { BoneConfig } from '../nodeConfigs'
+import { type IBlueprintFormatJSON, getDefaultProjectSettings } from '.'
+import { PACKAGE } from '../../../constants'
+import { openUnexpectedErrorDialog } from '../../../ui/dialogs/unexpected-error'
export function process(model: any): any {
console.log('Running MDFU...', JSON.parse(JSON.stringify(model)))
@@ -105,8 +105,7 @@ function updateModelToOld1_0(model: any) {
}
if (
- model.animations &&
- model.animations.find((a: any) =>
+ model.animations?.find((a: any) =>
Object.keys(a.animators as Record).find(name => name === 'effects')
)
) {
@@ -197,7 +196,7 @@ function updateModelToOld1_4(model: any) {
}
// region v0.3.10
-// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
+// eslint-disable-next-line @typescript-eslint/naming-convention
function updateModelTo0_3_10(model: any) {
console.log('Processing model for AJ 0.3.10', JSON.parse(JSON.stringify(model)))
}
@@ -218,7 +217,7 @@ function updateModelTo1_0pre1(model: any) {
meta: {
format: 'animated_java_blueprint',
format_version: '0.5.0',
- uuid: model.meta.uuid || guid(),
+ uuid: model.meta.uuid ?? guid(),
last_used_export_namespace: model.animated_java.settings.project_namespace,
},
project_settings: {
@@ -258,9 +257,9 @@ function updateModelTo1_0pre1(model: any) {
variants: {
default: {
name: 'default',
- display_name: defaultVariant.name || 'Default',
- uuid: defaultVariant.uuid || guid(),
- texture_map: defaultVariant.textureMap || {},
+ display_name: defaultVariant.name ?? 'Default',
+ uuid: defaultVariant.uuid ?? guid(),
+ texture_map: defaultVariant.textureMap ?? {},
// @ts-ignore
excluded_bones: [],
},
@@ -280,7 +279,7 @@ function updateModelTo1_0pre1(model: any) {
if (typeof node === 'string') return
bones.push(node.uuid as string)
node.configs = {
- default: new BoneConfig().toJSON(),
+ default: new CommonDisplayConfig().toJSON(),
variants: {},
}
node.children.forEach((child: any) => {
@@ -304,13 +303,13 @@ function updateModelTo1_0pre1(model: any) {
}
if (element.entity_type) element.config.entity_type = element.entity_type
if (element.nbt) {
- const summon_commands: string[] = []
+ const summonCommands: string[] = []
const nbt = NbtTag.fromString(element.nbt as string) as NbtCompound
nbt.delete('Passengers')
const tags = (nbt.get('Tags') as NbtList)?.map(t => t.getAsString())
nbt.delete('Tags')
- summon_commands.push('data merge entity @s ' + nbt.toString())
- if (tags) summon_commands.push(...tags.map(t => `tag @s add ${t}`))
+ summonCommands.push('data merge entity @s ' + nbt.toString())
+ if (tags) summonCommands.push(...tags.map(t => `tag @s add ${t}`))
const recursePassengers = (stringNbt: string): string[] => {
const nbt = NbtTag.fromString(stringNbt) as NbtCompound
@@ -348,16 +347,16 @@ function updateModelTo1_0pre1(model: any) {
}
try {
- summon_commands.push(...recursePassengers(element.nbt as string))
+ summonCommands.push(...recursePassengers(element.nbt as string))
} catch (e) {
console.error('Failed to parse NBT', element.nbt)
console.error(e)
}
- if (summon_commands.length === 0) {
- summon_commands.push(`data merge entity @s ${element.nbt as string}`)
+ if (summonCommands.length === 0) {
+ summonCommands.push(`data merge entity @s ${element.nbt as string}`)
}
- element.config.summon_commands = summon_commands.join('\n')
+ element.config.summon_commands = summonCommands.join('\n')
}
}
}
@@ -413,14 +412,14 @@ function updateModelTo1_0pre6(model: any): IBlueprintFormatJSON {
delete defaultVariant.excluded_bones
}
- for (const variant of model?.variants?.list || []) {
+ for (const variant of model?.variants?.list ?? []) {
if (variant?.excluded_bones) {
variant.excluded_nodes = variant.excluded_bones
delete variant.excluded_bones
}
}
- for (const animation of model?.animations || []) {
+ for (const animation of model?.animations ?? []) {
if (animation?.excluded_bones) {
animation.excluded_nodes = animation.excluded_bones
delete animation.excluded_bones
@@ -499,7 +498,7 @@ function updateModelTo1_6_3(model: IBlueprintFormatJSON): IBlueprintFormatJSON {
{ name: 'transparent' },
'797174ae-5c58-4a83-a630-eefd51007c80'
).fromDataURL(TransparentTexture)
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+
model.textures.push(texture.getSaveCopy())
break
}
diff --git a/src/components/formatPage.svelte b/src/blockbench-additions/model-formats/ajblueprint/formatPage.svelte
similarity index 73%
rename from src/components/formatPage.svelte
rename to src/blockbench-additions/model-formats/ajblueprint/formatPage.svelte
index 6e5097ea..bd26afb5 100644
--- a/src/components/formatPage.svelte
+++ b/src/blockbench-additions/model-formats/ajblueprint/formatPage.svelte
@@ -1,33 +1,33 @@
-
{#if $pluginMode}
diff --git a/src/blockbench-additions/model-formats/ajblueprint/versions/v1.6.4.ts b/src/blockbench-additions/model-formats/ajblueprint/versions/v1.6.4.ts
new file mode 100644
index 00000000..464986ad
--- /dev/null
+++ b/src/blockbench-additions/model-formats/ajblueprint/versions/v1.6.4.ts
@@ -0,0 +1,110 @@
+import type { PACKAGE } from '@aj/constants'
+import type {
+ CameraConfig,
+ CommonDisplayConfig,
+ LocatorConfig,
+ Serialized,
+ TextDisplayConfig,
+} from '@aj/systems/node-configs'
+
+namespace v1_6_4 {
+ export type IBlueprintBoneConfigJSON = Serialized
+ export type IBlueprintLocatorConfigJSON = Serialized
+ export type IBlueprintCameraConfigJSON = Serialized
+ export type IBlueprintTextDisplayConfigJSON = Serialized
+
+ /**
+ * The serialized Variant
+ */
+ export interface IBlueprintVariantJSON {
+ /**
+ * The display name of the Variant. Only use in Blockbench and for error messages.
+ */
+ display_name: string
+ /**
+ * The name of the Variant
+ */
+ name: string
+ /**
+ * The uuid of the Variant
+ */
+ uuid: string
+ /**
+ * The texture map for the Variant
+ */
+ texture_map: Record
+ /**
+ * The list of bones that should be ignored when applying the Variant
+ */
+ excluded_nodes: string[]
+ /**
+ * Whether or not this is the default Variant
+ */
+ is_default?: true
+ }
+
+ /**
+ * The serialized Blueprint
+ */
+ export interface IBlueprintFormatJSON {
+ meta: {
+ format: 'animated_java_blueprint'
+ format_version: string
+ uuid: string
+ last_used_export_namespace: string
+ box_uv?: boolean
+ backup?: boolean
+ save_location?: string
+ }
+ /**
+ * The project settings of the Blueprint
+ */
+ blueprint_settings?: NonNullable['animated_java']
+ /**
+ * The variants of the Blueprint
+ */
+ variants: {
+ /**
+ * The default Variant of the Blueprint
+ */
+ default: IBlueprintVariantJSON
+ /**
+ * The list of variants of the Blueprint, excluding the default Variant
+ */
+ list: IBlueprintVariantJSON[]
+ }
+
+ resolution: {
+ width: number
+ height: number
+ }
+
+ elements: any[]
+ outliner: any[]
+ textures: Texture[]
+ animations: AnimationOptions[]
+ animation_controllers?: AnimationControllerOptions[]
+ animation_variable_placeholders: string
+ backgrounds?: Record
+ }
+
+ export interface IBlueprintFormat {
+ meta: {
+ format: `${typeof PACKAGE.name}:utility_model`
+ format_version: '0.0.5'
+ }
+ }
+}
+
+export default {
+ upgrade(model: any): v1_6_4.IBlueprintFormat {
+ console.groupCollapsed('Updating utility model to 1.6.4')
+
+ // As this is the first version the DFU knows of, there is nothing to upgrade.
+ // However, we should make sure the format version is correct.
+ model.meta.format_version = '1.6.4'
+
+ console.groupEnd()
+ return model as v1_6_4.IBlueprintFormat
+ },
+}
diff --git a/src/outliner/vanillaBlockDisplay.ts b/src/blockbench-additions/outliner-elements/blockDisplay.ts
similarity index 73%
rename from src/outliner/vanillaBlockDisplay.ts
rename to src/blockbench-additions/outliner-elements/blockDisplay.ts
index 254872f1..e1eb8260 100644
--- a/src/outliner/vanillaBlockDisplay.ts
+++ b/src/blockbench-additions/outliner-elements/blockDisplay.ts
@@ -1,23 +1,38 @@
-import { IBlueprintBoneConfigJSON, isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { VANILLA_BLOCK_DISPLAY_CONFIG_ACTION } from '../interface/dialog/vanillaBlockDisplayConfig'
-import { BoneConfig } from '../nodeConfigs'
-import { getBlockModel } from '../systems/minecraft/blockModelManager'
-import { BlockStateValue, getBlockState } from '../systems/minecraft/blockstateManager'
-import { MINECRAFT_REGISTRY } from '../systems/minecraft/registryManager'
-import { getCurrentVersion } from '../systems/minecraft/versionManager'
-import { events } from '../util/events'
-import { parseBlock } from '../util/minecraftUtil'
-import { createAction, createBlockbenchMod } from '../util/moddingTools'
-import { Valuable } from '../util/stores'
-import { translate } from '../util/translation'
+import { getBlockModel } from '@aj/systems/minecraft-temp/blockModelManager'
+import { type BlockStateValue, getBlockState } from '@aj/systems/minecraft-temp/blockstateManager'
+import { MINECRAFT_REGISTRY } from '@aj/systems/minecraft-temp/registryManager'
+import { getCurrentVersion } from '@aj/systems/minecraft-temp/versionManager'
+import { BlockDisplayConfig, CommonDisplayConfig, type Serialized } from '@aj/systems/node-configs'
+import EVENTS from '@events'
+import { PACKAGE } from '../../constants'
+import { VANILLA_BLOCK_DISPLAY_CONFIG_ACTION } from '../../ui/dialogs/block-display-config'
+import { parseBlock } from '../../util/minecraftUtil'
+import {
+ createAction,
+ createBlockbenchMod,
+ fixClassPropertyInheritance,
+} from '../../util/moddingTools'
+import { Syncable } from '../../util/stores'
+import { translate } from '../../util/translation'
+import { isCurrentFormat } from '../model-formats/ajblueprint'
import { ResizableOutlinerElement } from './resizableOutlinerElement'
import { sanitizeOutlinerElementName } from './util'
const ERROR_OUTLINE_MATERIAL = Canvas.outlineMaterial.clone()
ERROR_OUTLINE_MATERIAL.color.set('#ff0000')
-interface VanillaBlockDisplayOptions {
+export type ItemDisplayMode =
+ | 'none'
+ | 'thirdperson_lefthand'
+ | 'thirdperson_righthand'
+ | 'firstperson_lefthand'
+ | 'firstperson_righthand'
+ | 'head'
+ | 'gui'
+ | 'ground'
+ | 'fixed'
+
+interface BlockDisplayOptions {
name?: string
block?: string
position?: ArrayVector3
@@ -26,20 +41,23 @@ interface VanillaBlockDisplayOptions {
visibility?: boolean
}
-export class VanillaBlockDisplay extends ResizableOutlinerElement {
- static type = `${PACKAGE.name}:vanilla_block_display`
- static selected: VanillaBlockDisplay[] = []
- static all: VanillaBlockDisplay[] = []
+@fixClassPropertyInheritance
+export class BlockDisplay extends ResizableOutlinerElement {
+ static type = `${PACKAGE.name}:block_display`
+ static selected: BlockDisplay[] = []
+ static all: BlockDisplay[] = []
- public type = VanillaBlockDisplay.type
+ public type = BlockDisplay.type
public icon = 'deployed_code'
+ public title = translate('node.block_display.title')
public needsUniqueName = true
// Properties
- public _block = new Valuable('minecraft:stone')
- public config: IBlueprintBoneConfigJSON
+ private __block = new Syncable('minecraft:stone')
+ public config: Serialized
+ public commonConfig: Serialized
- public error = new Valuable('')
+ public error = new Syncable('')
public menu = new Menu([
...Outliner.control_menu_group,
@@ -54,12 +72,12 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
public ready = false
- constructor(data: VanillaBlockDisplayOptions, uuid = guid()) {
+ constructor(data: BlockDisplayOptions, uuid = guid()) {
super(data, uuid)
- VanillaBlockDisplay.all.push(this)
+ BlockDisplay.all.push(this)
- for (const key in VanillaBlockDisplay.properties) {
- VanillaBlockDisplay.properties[key].reset(this)
+ for (const key in BlockDisplay.properties) {
+ BlockDisplay.properties[key].reset(this)
}
this.name = 'block_display'
@@ -67,6 +85,7 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
this.block ??= 'minecraft:stone'
this.config ??= {}
+ this.commonConfig ??= {}
this.sanitizeName()
@@ -93,19 +112,19 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
}
}
- this._block.subscribe(value => {
+ this.__block.subscribe(value => {
void updateBlock(value)
})
}
get block() {
- if (this._block === undefined) return 'minecraft:stone'
- return this._block.get()
+ if (this.__block === undefined) return 'minecraft:stone'
+ return this.__block.get()
}
set block(value: string) {
- if (this._block === undefined) return
+ if (this.__block === undefined) return
if (this.block === value) return
- this._block.set(value)
+ this.__block.set(value)
}
async waitForReady() {
@@ -120,10 +139,10 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
}
getUndoCopy() {
- const copy = {} as VanillaBlockDisplayOptions & { uuid: string; type: string }
+ const copy = {} as BlockDisplayOptions & { uuid: string; type: string }
- for (const key in VanillaBlockDisplay.properties) {
- VanillaBlockDisplay.properties[key].copy(this, copy)
+ for (const key in BlockDisplay.properties) {
+ BlockDisplay.properties[key].copy(this, copy)
}
copy.uuid = this.uuid
@@ -133,19 +152,17 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
getSaveCopy() {
const el: any = {}
- for (const key in VanillaBlockDisplay.properties) {
- VanillaBlockDisplay.properties[key].copy(this, el)
+ for (const key in BlockDisplay.properties) {
+ BlockDisplay.properties[key].copy(this, el)
}
el.uuid = this.uuid
el.type = this.type
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return el
}
select() {
- if (Group.first_selected) {
- Group.first_selected.unselect()
- }
+ Group.all.forEachReverse(el => el.unselect())
+
if (!Pressing.ctrl && !Pressing.shift) {
if (Cube.selected.length) {
Cube.selected.forEachReverse(el => el.unselect())
@@ -155,7 +172,7 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
}
}
- VanillaBlockDisplay.selected.safePush(this)
+ BlockDisplay.selected.safePush(this)
this.selectLow()
this.showInOutliner()
updateSelection()
@@ -176,25 +193,25 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
Timeline.selected.empty()
}
Project!.selected_elements.remove(this)
- VanillaBlockDisplay.selected.remove(this)
+ BlockDisplay.selected.remove(this)
this.selected = false
TickUpdates.selection = true
this.preview_controller.updateHighlight(this)
}
}
-new Property(VanillaBlockDisplay, 'string', 'block', { default: 'minecraft:stone' })
-new Property(VanillaBlockDisplay, 'object', 'config', {
+new Property(BlockDisplay, 'string', 'block', { default: 'minecraft:stone' })
+new Property(BlockDisplay, 'object', 'config', {
get default() {
- return new BoneConfig().toJSON()
+ return new CommonDisplayConfig().toJSON()
},
})
-OutlinerElement.registerType(VanillaBlockDisplay, VanillaBlockDisplay.type)
+OutlinerElement.registerType(BlockDisplay, BlockDisplay.type)
-export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaBlockDisplay, {
- setup(el: VanillaBlockDisplay) {
+export const PREVIEW_CONTROLLER = new NodePreviewController(BlockDisplay, {
+ setup(el: BlockDisplay) {
ResizableOutlinerElement.prototype.preview_controller.setup(el)
},
- updateGeometry(el: VanillaBlockDisplay) {
+ updateGeometry(el: BlockDisplay) {
if (!el.mesh) return
void getBlockModel(el.block)
@@ -232,10 +249,10 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaBlockDisplay,
el.ready = true
})
},
- updateTransform(el: VanillaBlockDisplay) {
+ updateTransform(el: BlockDisplay) {
ResizableOutlinerElement.prototype.preview_controller.updateTransform(el)
},
- updateHighlight(el: VanillaBlockDisplay, force?: boolean | VanillaBlockDisplay) {
+ updateHighlight(el: BlockDisplay, force?: boolean | BlockDisplay) {
if (!isCurrentFormat() || !el?.mesh) return
const highlighted = Modes.edit && (force === true || force === el || el.selected) ? 1 : 0
@@ -254,20 +271,20 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaBlockDisplay,
},
})
-class VanillaBlockDisplayAnimator extends BoneAnimator {
- private _name: string
+class BlockDisplayAnimator extends BoneAnimator {
+ private __name: string
public uuid: string
- public element: VanillaBlockDisplay | undefined
+ public element: BlockDisplay | undefined
constructor(uuid: string, animation: _Animation, name: string) {
super(uuid, animation, name)
this.uuid = uuid
- this._name = name
+ this.__name = name
}
getElement() {
- this.element = OutlinerNode.uuids[this.uuid] as VanillaBlockDisplay
+ this.element = OutlinerNode.uuids[this.uuid] as BlockDisplay
return this.element
}
@@ -372,8 +389,8 @@ class VanillaBlockDisplayAnimator extends BoneAnimator {
return this
}
}
-VanillaBlockDisplayAnimator.prototype.type = VanillaBlockDisplay.type
-VanillaBlockDisplay.animator = VanillaBlockDisplayAnimator as any
+BlockDisplayAnimator.prototype.type = BlockDisplay.type
+BlockDisplay.animator = BlockDisplayAnimator as any
createBlockbenchMod(
`${PACKAGE.name}:vanillaBlockDisplay`,
@@ -386,14 +403,14 @@ createBlockbenchMod(
MenuBar.menus.edit.addAction(CREATE_ACTION, 8)
context.subscriptions.push(
- events.SELECT_PROJECT.subscribe(project => {
+ EVENTS.SELECT_PROJECT.subscribe(project => {
project.vanillaBlockDisplays ??= []
- VanillaBlockDisplay.all.empty()
- VanillaBlockDisplay.all.push(...project.vanillaBlockDisplays)
+ BlockDisplay.all.empty()
+ BlockDisplay.all.push(...project.vanillaBlockDisplays)
}),
- events.UNSELECT_PROJECT.subscribe(project => {
- project.vanillaBlockDisplays = [...VanillaBlockDisplay.all]
- VanillaBlockDisplay.all.empty()
+ EVENTS.UNSELECT_PROJECT.subscribe(project => {
+ project.vanillaBlockDisplays = [...BlockDisplay.all]
+ BlockDisplay.all.empty()
})
)
return context
@@ -407,8 +424,8 @@ createBlockbenchMod(
}
)
-export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_vanilla_block_display`, {
- name: translate('action.create_vanilla_block_display.title'),
+export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_block_display`, {
+ name: translate('action.create_block_display.title'),
icon: 'deployed_code',
category: 'animated_java',
condition() {
@@ -417,7 +434,7 @@ export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_vanilla_block_
click() {
Undo.initEdit({ outliner: true, elements: [], selection: true })
- const vanillaBlockDisplay = new VanillaBlockDisplay({}).init()
+ const vanillaBlockDisplay = new BlockDisplay({}).init()
const group = getCurrentGroup()
if (group instanceof Group) {
@@ -426,7 +443,7 @@ export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_vanilla_block_
}
selected.forEachReverse(el => el.unselect())
- Group.first_selected && Group.first_selected.unselect()
+ Group.all.forEachReverse(el => el.unselect())
vanillaBlockDisplay.select()
Undo.finishEdit('Create Vanilla Block Display', {
@@ -445,7 +462,7 @@ export function debugBlocks() {
const block = MINECRAFT_REGISTRY.block.items[i]
const x = (i % maxX) * 32
const y = Math.floor(i / maxX) * 32
- new VanillaBlockDisplay({ name: block, block, position: [x, 8, y] }).init()
+ new BlockDisplay({ name: block, block, position: [x, 8, y] }).init()
}
}
@@ -460,7 +477,7 @@ export async function debugBlockState(block: string) {
const x = (i % maxX) * 32
const y = Math.floor(i / maxX) * 32
const str = generateBlockStateString(permutations[i])
- new VanillaBlockDisplay({
+ new BlockDisplay({
name: block + str,
block: block + str,
position: [x, 8, y],
diff --git a/src/outliner/vanillaItemDisplay.ts b/src/blockbench-additions/outliner-elements/itemDisplay.ts
similarity index 65%
rename from src/outliner/vanillaItemDisplay.ts
rename to src/blockbench-additions/outliner-elements/itemDisplay.ts
index fcf7fc81..d3db3793 100644
--- a/src/outliner/vanillaItemDisplay.ts
+++ b/src/blockbench-additions/outliner-elements/itemDisplay.ts
@@ -1,57 +1,52 @@
-import { IBlueprintBoneConfigJSON, isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { VANILLA_ITEM_DISPLAY_CONFIG_ACTION } from '../interface/dialog/vanillaItemDisplayConfig'
-import { BoneConfig } from '../nodeConfigs'
-import { getItemModel } from '../systems/minecraft/itemModelManager'
-import { MINECRAFT_REGISTRY } from '../systems/minecraft/registryManager'
-import { getCurrentVersion } from '../systems/minecraft/versionManager'
-import { events } from '../util/events'
-import { createAction, createBlockbenchMod } from '../util/moddingTools'
-import { Valuable } from '../util/stores'
-import { translate } from '../util/translation'
+import { getItemModel } from '@aj/systems/minecraft-temp/itemModelManager'
+import { MINECRAFT_REGISTRY } from '@aj/systems/minecraft-temp/registryManager'
+import { getCurrentVersion } from '@aj/systems/minecraft-temp/versionManager'
+import { CommonDisplayConfig, ItemDisplayConfig, type Serialized } from '@aj/systems/node-configs'
+import EVENTS from '@events'
+import { PACKAGE } from '../../constants'
+import {
+ createAction,
+ createBlockbenchMod,
+ fixClassPropertyInheritance,
+} from '../../util/moddingTools'
+import { Syncable } from '../../util/stores'
+import { translate } from '../../util/translation'
+import { isCurrentFormat } from '../model-formats/ajblueprint'
import { ResizableOutlinerElement } from './resizableOutlinerElement'
import { sanitizeOutlinerElementName } from './util'
-export type ItemDisplayMode =
- | 'none'
- | 'thirdperson_lefthand'
- | 'thirdperson_righthand'
- | 'firstperson_lefthand'
- | 'firstperson_righthand'
- | 'head'
- | 'gui'
- | 'ground'
- | 'fixed'
-
-interface VanillaItemDisplayOptions {
+interface ItemDisplayOptions {
name?: string
item?: string
- itemDisplay?: ItemDisplayMode
+ item_display?: string
position?: ArrayVector3
rotation?: ArrayVector3
scale?: ArrayVector3
visibility?: boolean
}
-export class VanillaItemDisplay extends ResizableOutlinerElement {
- static type = `${PACKAGE.name}:vanilla_item_display`
- static selected: VanillaItemDisplay[] = []
- static all: VanillaItemDisplay[] = []
+@fixClassPropertyInheritance
+export class ItemDisplay extends ResizableOutlinerElement {
+ static type = `${PACKAGE.name}:item_display`
+ static selected: ItemDisplay[] = []
+ static all: ItemDisplay[] = []
- public type = VanillaItemDisplay.type
+ public type = ItemDisplay.type
public icon = 'icecream'
+ public title = translate('node.item_display.title')
public needsUniqueName = true
// Properties
- public _item = new Valuable('minecraft:diamond')
- public _itemDisplay = new Valuable('none')
- public config: IBlueprintBoneConfigJSON
+ private __item = new Syncable('minecraft:diamond')
+ private __itemDisplay = new Syncable('none')
+ public config: Serialized
+ public commonConfig: Serialized
- public error = new Valuable('')
+ public error = new Syncable('')
public menu = new Menu([
...Outliner.control_menu_group,
- VANILLA_ITEM_DISPLAY_CONFIG_ACTION,
+ ITEM_DISPLAY_CONFIG_ACTION,
'_',
'rename',
'delete',
@@ -62,12 +57,12 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
public ready = false
- constructor(data: VanillaItemDisplayOptions, uuid = guid()) {
+ constructor(data: ItemDisplayOptions, uuid = guid()) {
super(data, uuid)
- VanillaItemDisplay.all.push(this)
+ ItemDisplay.all.push(this)
- for (const key in VanillaItemDisplay.properties) {
- VanillaItemDisplay.properties[key].reset(this)
+ for (const key in ItemDisplay.properties) {
+ ItemDisplay.properties[key].reset(this)
}
this.name = 'item_display'
@@ -80,6 +75,7 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
this.scale ??= [1, 1, 1]
this.visibility ??= true
this.config ??= {}
+ this.commonConfig ??= {}
this.sanitizeName()
@@ -104,27 +100,27 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
}
}
- this._item.subscribe(value => {
+ this.__item.subscribe(value => {
updateItem(value)
})
}
get item() {
- if (this._item === undefined) return 'minecraft:diamond'
- return this._item.get()
+ if (this.__item === undefined) return 'minecraft:diamond'
+ return this.__item.get()
}
set item(value: string) {
- if (this._item === undefined) return
- this._item.set(value)
+ if (this.__item === undefined) return
+ this.__item.set(value)
}
get itemDisplay() {
- if (this._itemDisplay === undefined) return 'none'
- return this._itemDisplay.get()
+ if (this.__itemDisplay === undefined) return 'none'
+ return this.__itemDisplay.get()
}
- set itemDisplay(value: ItemDisplayMode) {
- if (this._itemDisplay === undefined) return
- this._itemDisplay.set(value)
+ set itemDisplay(value: string) {
+ if (this.__itemDisplay === undefined) return
+ this.__itemDisplay.set(value)
}
async waitForReady() {
@@ -139,10 +135,10 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
}
getUndoCopy() {
- const copy = {} as VanillaItemDisplayOptions & { uuid: string; type: string }
+ const copy = {} as ItemDisplayOptions & { uuid: string; type: string }
- for (const key in VanillaItemDisplay.properties) {
- VanillaItemDisplay.properties[key].copy(this, copy)
+ for (const key in ItemDisplay.properties) {
+ ItemDisplay.properties[key].copy(this, copy)
}
copy.uuid = this.uuid
@@ -152,19 +148,17 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
getSaveCopy() {
const el: any = {}
- for (const key in VanillaItemDisplay.properties) {
- VanillaItemDisplay.properties[key].copy(this, el)
+ for (const key in ItemDisplay.properties) {
+ ItemDisplay.properties[key].copy(this, el)
}
el.uuid = this.uuid
el.type = this.type
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return el
}
select() {
- if (Group.first_selected) {
- Group.first_selected.unselect()
- }
+ Group.all.forEachReverse(el => el.unselect())
+
if (!Pressing.ctrl && !Pressing.shift) {
if (Cube.selected.length) {
Cube.selected.forEachReverse(el => el.unselect())
@@ -174,7 +168,7 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
}
}
- VanillaItemDisplay.selected.safePush(this)
+ ItemDisplay.selected.safePush(this)
this.selectLow()
this.showInOutliner()
updateSelection()
@@ -195,26 +189,26 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
Timeline.selected.empty()
}
Project!.selected_elements.remove(this)
- VanillaItemDisplay.selected.remove(this)
+ ItemDisplay.selected.remove(this)
this.selected = false
TickUpdates.selection = true
this.preview_controller.updateHighlight(this)
}
}
-new Property(VanillaItemDisplay, 'string', 'item', { default: 'minecraft:diamond' })
-new Property(VanillaItemDisplay, 'string', 'itemDisplay', { default: 'none' })
-new Property(VanillaItemDisplay, 'object', 'config', {
+new Property(ItemDisplay, 'string', 'item', { default: 'minecraft:diamond' })
+new Property(ItemDisplay, 'string', 'item_display', { default: 'none' })
+new Property(ItemDisplay, 'object', 'config', {
get default() {
- return new BoneConfig().toJSON()
+ return new CommonDisplayConfig().toJSON()
},
})
-OutlinerElement.registerType(VanillaItemDisplay, VanillaItemDisplay.type)
+OutlinerElement.registerType(ItemDisplay, ItemDisplay.type)
-export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaItemDisplay, {
- setup(el: VanillaItemDisplay) {
+export const PREVIEW_CONTROLLER = new NodePreviewController(ItemDisplay, {
+ setup(el: ItemDisplay) {
ResizableOutlinerElement.prototype.preview_controller.setup(el)
},
- updateGeometry(el: VanillaItemDisplay) {
+ updateGeometry(el: ItemDisplay) {
if (!el.mesh) return
void getItemModel(el.item)
@@ -243,10 +237,10 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaItemDisplay,
el.ready = true
})
},
- updateTransform(el: VanillaItemDisplay) {
+ updateTransform(el: ItemDisplay) {
ResizableOutlinerElement.prototype.preview_controller.updateTransform(el)
},
- updateHighlight(el: VanillaItemDisplay, force?: boolean | VanillaItemDisplay) {
+ updateHighlight(el: ItemDisplay, force?: boolean | ItemDisplay) {
if (!isCurrentFormat() || !el?.mesh) return
const highlighted = Modes.edit && (force === true || force === el || el.selected) ? 1 : 0
@@ -265,20 +259,20 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaItemDisplay,
},
})
-class VanillaItemDisplayAnimator extends BoneAnimator {
- private _name: string
+class ItemDisplayAnimator extends BoneAnimator {
+ private __name: string
public uuid: string
- public element: VanillaItemDisplay | undefined
+ public element: ItemDisplay | undefined
constructor(uuid: string, animation: _Animation, name: string) {
super(uuid, animation, name)
this.uuid = uuid
- this._name = name
+ this.__name = name
}
getElement() {
- this.element = OutlinerNode.uuids[this.uuid] as VanillaItemDisplay
+ this.element = OutlinerNode.uuids[this.uuid] as ItemDisplay
return this.element
}
@@ -314,7 +308,7 @@ class VanillaItemDisplayAnimator extends BoneAnimator {
}
}
- if (this.element && this.element.parent && this.element.parent !== 'root') {
+ if (this.element?.parent && this.element.parent !== 'root') {
this.element.parent.openUp()
}
@@ -323,7 +317,7 @@ class VanillaItemDisplayAnimator extends BoneAnimator {
doRender() {
this.getElement()
- return !!(this.element && this.element.mesh)
+ return !!this.element?.mesh
}
displayRotation(arr: ArrayVector3 | ArrayVector4, multiplier = 1) {
@@ -335,13 +329,13 @@ class VanillaItemDisplayAnimator extends BoneAnimator {
if (arr) {
if (arr.length === 4) {
- const added_rotation = new THREE.Euler().setFromQuaternion(
+ const addedRotation = new THREE.Euler().setFromQuaternion(
new THREE.Quaternion().fromArray(arr),
'ZYX'
)
- bone.rotation.x -= added_rotation.x * multiplier
- bone.rotation.y -= added_rotation.y * multiplier
- bone.rotation.z += added_rotation.z * multiplier
+ bone.rotation.x -= addedRotation.x * multiplier
+ bone.rotation.y -= addedRotation.y * multiplier
+ bone.rotation.z += addedRotation.z * multiplier
} else {
bone.rotation.x += Math.degToRad(-arr[0]) * multiplier
bone.rotation.y += Math.degToRad(-arr[1]) * multiplier
@@ -382,8 +376,8 @@ class VanillaItemDisplayAnimator extends BoneAnimator {
return this
}
}
-VanillaItemDisplayAnimator.prototype.type = VanillaItemDisplay.type
-VanillaItemDisplay.animator = VanillaItemDisplayAnimator as any
+ItemDisplayAnimator.prototype.type = ItemDisplay.type
+ItemDisplay.animator = ItemDisplayAnimator as any
createBlockbenchMod(
`${PACKAGE.name}:vanillaItemDisplay`,
@@ -396,14 +390,14 @@ createBlockbenchMod(
MenuBar.menus.edit.addAction(CREATE_ACTION, 8)
context.subscriptions.push(
- events.SELECT_PROJECT.subscribe(project => {
+ EVENTS.SELECT_PROJECT.subscribe(project => {
project.vanillaItemDisplays ??= []
- VanillaItemDisplay.all.empty()
- VanillaItemDisplay.all.push(...project.vanillaItemDisplays)
+ ItemDisplay.all.empty()
+ ItemDisplay.all.push(...project.vanillaItemDisplays)
}),
- events.UNSELECT_PROJECT.subscribe(project => {
- project.vanillaItemDisplays = [...VanillaItemDisplay.all]
- VanillaItemDisplay.all.empty()
+ EVENTS.UNSELECT_PROJECT.subscribe(project => {
+ project.vanillaItemDisplays = [...ItemDisplay.all]
+ ItemDisplay.all.empty()
})
)
return context
@@ -417,8 +411,8 @@ createBlockbenchMod(
}
)
-export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_vanilla_item_display`, {
- name: translate('action.create_vanilla_item_display.title'),
+export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_item_display`, {
+ name: translate('action.create_item_display.title'),
icon: 'icecream',
category: 'animated_java',
condition() {
@@ -427,7 +421,7 @@ export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_vanilla_item_d
click() {
Undo.initEdit({ outliner: true, elements: [], selection: true })
- const vanillaItemDisplay = new VanillaItemDisplay({}).init()
+ const vanillaItemDisplay = new ItemDisplay({}).init()
const group = getCurrentGroup()
if (group instanceof Group) {
@@ -436,7 +430,7 @@ export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_vanilla_item_d
}
selected.forEachReverse(el => el.unselect())
- Group.first_selected && Group.first_selected.unselect()
+ Group.all.forEachReverse(el => el.unselect())
vanillaItemDisplay.select()
Undo.finishEdit('Create Vanilla Item Display', {
diff --git a/src/outliner/resizableOutlinerElement.ts b/src/blockbench-additions/outliner-elements/resizableOutlinerElement.ts
similarity index 95%
rename from src/outliner/resizableOutlinerElement.ts
rename to src/blockbench-additions/outliner-elements/resizableOutlinerElement.ts
index efc6f4df..d1e536a9 100644
--- a/src/outliner/resizableOutlinerElement.ts
+++ b/src/blockbench-additions/outliner-elements/resizableOutlinerElement.ts
@@ -1,4 +1,4 @@
-import { makeNotZero } from '../util/misc'
+import { makeNotZero } from '../../util/misc'
export class ResizableOutlinerElement extends OutlinerElement {
// Properties
@@ -93,7 +93,7 @@ export class ResizableOutlinerElement extends OutlinerElement {
// allowNegative: boolean,
// bidirectional: boolean
) {
- let before = this.oldScale !== undefined ? this.oldScale : this.size(axis)
+ let before = this.oldScale ?? this.size(axis)
if (before instanceof Array) before = before[axis]
// For some unknown reason scale is not inverted on the y axis
const sign = before < 0 && axis !== 1 ? -1 : 1
@@ -110,7 +110,7 @@ new Property(ResizableOutlinerElement, 'string', 'name', { default: 'resizable_o
new Property(ResizableOutlinerElement, 'vector', 'position', { default: [0, 0, 0] })
new Property(ResizableOutlinerElement, 'vector', 'rotation', { default: [0, 0, 0] })
new Property(ResizableOutlinerElement, 'vector', 'scale', { default: [1, 1, 1] })
-new Property(ResizableOutlinerElement, 'string', 'visibility', { default: true })
+new Property(ResizableOutlinerElement, 'boolean', 'visibility', { default: true })
export const PREVIEW_CONTROLLER = new NodePreviewController(ResizableOutlinerElement, {
setup(el: ResizableOutlinerElement) {
diff --git a/src/outliner/textDisplay.ts b/src/blockbench-additions/outliner-elements/textDisplay.ts
similarity index 54%
rename from src/outliner/textDisplay.ts
rename to src/blockbench-additions/outliner-elements/textDisplay.ts
index d376265b..bbc681be 100644
--- a/src/outliner/textDisplay.ts
+++ b/src/blockbench-additions/outliner-elements/textDisplay.ts
@@ -1,18 +1,19 @@
+import { PACKAGE } from '../../constants'
import {
- BLUEPRINT_FORMAT,
- IBlueprintTextDisplayConfigJSON,
- isCurrentFormat,
-} from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createAction, createBlockbenchMod } from '../util/moddingTools'
-// import * as MinecraftFull from '../assets/MinecraftFull.json'
-import { TEXT_DISPLAY_CONFIG_ACTION } from '../interface/dialog/textDisplayConfig'
-import { TextDisplayConfig } from '../nodeConfigs'
-import { getVanillaFont } from '../systems/minecraft/fontManager'
-import { JsonText } from '../systems/minecraft/jsonText'
-import { events } from '../util/events'
-import { Valuable } from '../util/stores'
-import { translate } from '../util/translation'
+ createAction,
+ createBlockbenchMod,
+ fixClassPropertyInheritance,
+ ObjectProperty,
+} from '../../util/moddingTools'
+import { BLUEPRINT_FORMAT, isCurrentFormat } from '../model-formats/ajblueprint'
+// import * as MinecraftFull from '@assets/MinecraftFull.json'
+import { getVanillaFont } from '@aj/systems/minecraft-temp/fontManager'
+import { CommonDisplayConfig, TextDisplayConfig, type Serialized } from '@aj/systems/node-configs'
+import EVENTS from '@events'
+
+import { TEXT_DISPLAY_CONFIG_ACTION } from '../../ui/dialogs/text-display-config'
+import { Syncable } from '../../util/stores'
+import { translate } from '../../util/translation'
import { ResizableOutlinerElement } from './resizableOutlinerElement'
import { sanitizeOutlinerElementName } from './util'
@@ -30,6 +31,7 @@ interface TextDisplayOptions {
}
export type Alignment = 'left' | 'center' | 'right'
+@fixClassPropertyInheritance
export class TextDisplay extends ResizableOutlinerElement {
static type = `${PACKAGE.name}:text_display`
static selected: TextDisplay[] = []
@@ -37,10 +39,12 @@ export class TextDisplay extends ResizableOutlinerElement {
public type = TextDisplay.type
public icon = 'text_fields'
+ public title = translate('node.text_display.title')
public needsUniqueName = true
// Properties
- public config: IBlueprintTextDisplayConfigJSON
+ public config: Serialized
+ public commonConfig: Serialized
public menu = new Menu([
...Outliner.control_menu_group,
@@ -54,26 +58,13 @@ export class TextDisplay extends ResizableOutlinerElement {
public preview_controller = PREVIEW_CONTROLLER
public ready = false
- public textError = new Valuable('')
-
- private _updating = false
- private _text = new Valuable('Hello World!')
- private _newText: string | undefined
- private _lineWidth = new Valuable(200)
- private _newLineWidth: number | undefined
- private _backgroundColor = new Valuable('#000000')
- private _newBackgroundColor: string | undefined
- private _backgroundAlpha = new Valuable(0.25)
- private _newBackgroundAlpha: number | undefined
- private _shadow = new Valuable(false)
- private _newShadow: boolean | undefined
- private _align = new Valuable('center')
- private _newAlign: Alignment | undefined
- public seeThrough = false
+ public textError = new Syncable('')
+
+ private __renderingTextComponent = false
constructor(data: TextDisplayOptions, uuid = guid()) {
super(data, uuid)
- TextDisplay.all.push(this)
+ TextDisplay.all.safePush(this)
for (const key in TextDisplay.properties) {
TextDisplay.properties[key].reset(this)
@@ -86,36 +77,11 @@ export class TextDisplay extends ResizableOutlinerElement {
this.position ??= [0, 0, 0]
this.rotation ??= [0, 0, 0]
this.scale ??= [1, 1, 1]
- this.align ??= 'center'
this.visibility ??= true
this.config ??= {}
+ this.commonConfig ??= {}
this.sanitizeName()
-
- this._text.subscribe(v => {
- this._newText = v
- void this.updateText()
- })
- this._lineWidth.subscribe(v => {
- this._newLineWidth = v
- void this.updateText()
- })
- this._backgroundColor.subscribe(v => {
- this._newBackgroundColor = v
- void this.updateText()
- })
- this._backgroundAlpha.subscribe(v => {
- this._newBackgroundAlpha = v
- void this.updateText()
- })
- this._shadow.subscribe(v => {
- this._newShadow = v
- void this.updateText()
- })
- this._align.subscribe(v => {
- this._newAlign = v
- void this.updateText()
- })
}
public sanitizeName(): string {
@@ -123,68 +89,11 @@ export class TextDisplay extends ResizableOutlinerElement {
return this.name
}
- get text() {
- if (this._text === undefined) return TextDisplay.properties['text'].default as string
- return this._text.get()
- }
-
- set text(value) {
- if (this._text === undefined) return
- if (value === this.text) return
- this._text.set(value)
- }
-
- get lineWidth() {
- if (this._lineWidth === undefined)
- return TextDisplay.properties['lineWidth'].default as number
- return this._lineWidth.get()
- }
-
- set lineWidth(value) {
- if (this._lineWidth === undefined) return
- this._lineWidth.set(value)
- }
-
- get backgroundColor() {
- if (this._backgroundColor === undefined)
- return TextDisplay.properties['backgroundColor'].default as string
- return this._backgroundColor.get()
- }
-
- set backgroundColor(value) {
- if (this._backgroundColor === undefined) return
- this._backgroundColor.set(value)
- }
-
- get backgroundAlpha() {
- if (this._backgroundAlpha === undefined)
- return TextDisplay.properties['backgroundAlpha'].default as number
- return this._backgroundAlpha.get()
- }
-
- set backgroundAlpha(value) {
- if (this._backgroundAlpha === undefined) return
- this._backgroundAlpha.set(value)
- }
-
- get shadow() {
- if (this._shadow === undefined) return TextDisplay.properties['shadow'].default as boolean
- return this._shadow.get()
- }
-
- set shadow(value) {
- if (this._shadow === undefined) return
- this._shadow.set(value)
- }
-
- get align() {
- if (this._align === undefined) return TextDisplay.properties['align'].default as Alignment
- return this._align.get()
- }
-
- set align(value) {
- if (this._align === undefined) return
- this._align.set(value)
+ public extend(data: any) {
+ for (const key in TextDisplay.properties) {
+ TextDisplay.properties[key].merge(this, data)
+ }
+ return this
}
getUndoCopy() {
@@ -207,14 +116,12 @@ export class TextDisplay extends ResizableOutlinerElement {
}
el.uuid = this.uuid
el.type = this.type
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+
return el
}
select() {
- if (Group.first_selected) {
- Group.first_selected.unselect()
- }
+ Group.all.forEachReverse(el => el.unselect())
if (!Pressing.ctrl && !Pressing.shift) {
if (Cube.selected.length) {
@@ -251,102 +158,68 @@ export class TextDisplay extends ResizableOutlinerElement {
TickUpdates.selection = true
}
- async updateText() {
- if (this._updating) return
- this._updating = true
- let latestMesh: THREE.Mesh | undefined
- while (
- this._newText !== undefined ||
- this._newLineWidth !== undefined ||
- this._newBackgroundColor !== undefined ||
- this._newBackgroundAlpha !== undefined ||
- this._newShadow !== undefined ||
- this._newAlign !== undefined
- ) {
- let text: JsonText | undefined
- this.textError.set('')
- try {
- text = JsonText.fromString(this.text)
- console.log(text)
- } catch (e: any) {
- console.error(e)
- this.textError.set(e.message as string)
- this._updating = false
- text = new JsonText({ text: 'Invalid JSON Text!', color: 'red' })
- }
- this._newText = undefined
- this._newLineWidth = undefined
- this._newBackgroundColor = undefined
- this._newBackgroundAlpha = undefined
- this._newShadow = undefined
- this._newAlign = undefined
- if (text === undefined) continue
- latestMesh = await this.setText(text)
- }
- this._updating = false
- return latestMesh
- }
-
async waitForReady() {
while (!this.ready) {
await new Promise(resolve => requestAnimationFrame(resolve))
}
}
- private async setText(jsonText: JsonText) {
- await this.waitForReady()
- const font = await getVanillaFont()
- // Hide the geo while rendering
-
- const { mesh: newMesh, outline } = await font.generateTextMesh({
- jsonText,
- maxLineWidth: this.lineWidth,
- backgroundColor: this.backgroundColor,
- backgroundAlpha: this.backgroundAlpha,
- shadow: this.shadow,
- alignment: this.align,
- })
- newMesh.name = this.uuid + '_text'
- const previousMesh = this.mesh.children.find(v => v.name === newMesh.name)
- if (previousMesh) this.mesh.remove(previousMesh)
-
- const mesh = this.mesh as THREE.Mesh
- mesh.name = this.uuid
- mesh.geometry = (newMesh.children[0] as THREE.Mesh).geometry.clone()
- mesh.geometry.translate(
- newMesh.children[0].position.x,
- newMesh.children[0].position.y,
- newMesh.children[0].position.z
- )
- mesh.geometry.rotateY(Math.PI)
- mesh.geometry.scale(newMesh.scale.x, newMesh.scale.y, newMesh.scale.z)
- mesh.material = Canvas.transparentMaterial
-
- mesh.add(newMesh)
-
- outline.name = this.uuid + '_outline'
- outline.visible = this.selected
- mesh.outline = outline
- const previousOutline = mesh.children.find(v => v.name === outline.name)
- if (previousOutline) mesh.remove(previousOutline)
- mesh.add(outline)
- mesh.visible = this.visibility
- return newMesh
+ async updateText() {
+ if (this.__renderingTextComponent) return
+ try {
+ this.__renderingTextComponent = true
+ await this.waitForReady()
+ const font = await getVanillaFont()
+ const config = new TextDisplayConfig().fromJSON(
+ this.config
+ ) as Required
+ const { mesh: newMesh, outline } = await font.generateTextMesh(config)
+ newMesh.name = this.uuid + '_text'
+ const previousMesh = this.mesh.children.find(v => v.name === newMesh.name)
+ if (previousMesh) this.mesh.remove(previousMesh)
+
+ const mesh = this.mesh as THREE.Mesh
+ mesh.name = this.uuid
+ mesh.geometry = (newMesh.children[0] as THREE.Mesh).geometry.clone()
+ mesh.geometry.translate(
+ newMesh.children[0].position.x,
+ newMesh.children[0].position.y,
+ newMesh.children[0].position.z
+ )
+ mesh.geometry.rotateY(Math.PI)
+ mesh.geometry.scale(newMesh.scale.x, newMesh.scale.y, newMesh.scale.z)
+ mesh.material = Canvas.transparentMaterial
+
+ mesh.add(newMesh)
+
+ outline.name = this.uuid + '_outline'
+ outline.visible = this.selected
+ mesh.outline = outline
+ const previousOutline = mesh.children.find(v => v.name === outline.name)
+ if (previousOutline) mesh.remove(previousOutline)
+ mesh.add(outline)
+ mesh.visible = this.visibility
+ return newMesh
+ } catch (err: any) {
+ console.error(err)
+ this.textError.set(err.message)
+ } finally {
+ this.__renderingTextComponent = false
+ }
}
}
-new Property(TextDisplay, 'string', 'text', { default: '"Hello World!"' })
-new Property(TextDisplay, 'number', 'lineWidth', { default: 200 })
-new Property(TextDisplay, 'string', 'backgroundColor', { default: '#000000' })
-new Property(TextDisplay, 'number', 'backgroundAlpha', { default: 0.25 })
-new Property(TextDisplay, 'string', 'align', { default: 'center' })
-new Property(TextDisplay, 'boolean', 'shadow', { default: false })
-new Property(TextDisplay, 'boolean', 'seeThrough', { default: false })
-new Property(TextDisplay, 'object', 'config', {
+new ObjectProperty(TextDisplay, 'config', {
get default() {
return new TextDisplayConfig().toJSON()
},
})
+new ObjectProperty(TextDisplay, 'commonConfig', {
+ get default() {
+ return new CommonDisplayConfig().toJSON()
+ },
+})
+
OutlinerElement.registerType(TextDisplay, TextDisplay.type)
export const PREVIEW_CONTROLLER = new NodePreviewController(TextDisplay, {
@@ -376,7 +249,7 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(TextDisplay, {
})
class TextDisplayAnimator extends BoneAnimator {
- private _name: string
+ private __name: string
public uuid: string
public element: TextDisplay | undefined
@@ -384,7 +257,7 @@ class TextDisplayAnimator extends BoneAnimator {
constructor(uuid: string, animation: _Animation, name: string) {
super(uuid, animation, name)
this.uuid = uuid
- this._name = name
+ this.__name = name
}
getElement() {
@@ -424,7 +297,7 @@ class TextDisplayAnimator extends BoneAnimator {
}
}
- if (this.element && this.element.parent && this.element.parent !== 'root') {
+ if (this.element?.parent && this.element.parent !== 'root') {
this.element.parent.openUp()
}
@@ -433,7 +306,7 @@ class TextDisplayAnimator extends BoneAnimator {
doRender() {
this.getElement()
- return !!(this.element && this.element.mesh)
+ return !!this.element?.mesh
}
displayRotation(arr: ArrayVector3 | ArrayVector4, multiplier = 1) {
@@ -445,13 +318,13 @@ class TextDisplayAnimator extends BoneAnimator {
if (arr) {
if (arr.length === 4) {
- const added_rotation = new THREE.Euler().setFromQuaternion(
+ const addedRotation = new THREE.Euler().setFromQuaternion(
new THREE.Quaternion().fromArray(arr),
'ZYX'
)
- bone.rotation.x -= added_rotation.x * multiplier
- bone.rotation.y -= added_rotation.y * multiplier
- bone.rotation.z += added_rotation.z * multiplier
+ bone.rotation.x -= addedRotation.x * multiplier
+ bone.rotation.y -= addedRotation.y * multiplier
+ bone.rotation.z += addedRotation.z * multiplier
} else {
bone.rotation.x -= Math.degToRad(arr[0]) * multiplier
bone.rotation.y -= Math.degToRad(arr[1]) * multiplier
@@ -506,13 +379,13 @@ createBlockbenchMod(
MenuBar.menus.edit.addAction(CREATE_ACTION, 8)
context.subscriptions.push(
- events.SELECT_PROJECT.subscribe(project => {
+ EVENTS.SELECT_PROJECT.subscribe(project => {
if (project.format.id !== BLUEPRINT_FORMAT.id) return
project.textDisplays ??= []
TextDisplay.all.empty()
TextDisplay.all.push(...project.textDisplays)
}),
- events.UNSELECT_PROJECT.subscribe(project => {
+ EVENTS.UNSELECT_PROJECT.subscribe(project => {
if (project.format.id !== BLUEPRINT_FORMAT.id) return
project.textDisplays = [...TextDisplay.all]
TextDisplay.all.empty()
@@ -548,7 +421,7 @@ export const CREATE_ACTION = createAction(`${PACKAGE.name}:create_text_display`,
}
selected.forEachReverse(el => el.unselect())
- Group.first_selected && Group.first_selected.unselect()
+ Group.all.forEachReverse(el => el.unselect())
textDisplay.select()
Undo.finishEdit('Create Text Display', {
diff --git a/src/outliner/util.ts b/src/blockbench-additions/outliner-elements/util.ts
similarity index 76%
rename from src/outliner/util.ts
rename to src/blockbench-additions/outliner-elements/util.ts
index 3b1582bf..d7c26c62 100644
--- a/src/outliner/util.ts
+++ b/src/blockbench-additions/outliner-elements/util.ts
@@ -1,15 +1,15 @@
-import { sanitizePathName } from '../util/minecraftUtil'
+import { sanitizePathName } from '@aj/util/minecraftUtil'
+import { BlockDisplay } from './blockDisplay'
+import { ItemDisplay } from './itemDisplay'
import { TextDisplay } from './textDisplay'
-import { VanillaBlockDisplay } from './vanillaBlockDisplay'
-import { VanillaItemDisplay } from './vanillaItemDisplay'
export function sanitizeOutlinerElementName(name: string, elementUUID: string): string {
name = sanitizePathName(name)
let otherNodes: OutlinerElement[] = [
- ...VanillaBlockDisplay.all,
+ ...BlockDisplay.all,
...Group.all,
...TextDisplay.all,
- ...VanillaItemDisplay.all,
+ ...ItemDisplay.all,
...Locator.all,
]
if (OutlinerElement.types.camera) {
@@ -24,7 +24,7 @@ export function sanitizeOutlinerElementName(name: string, elementUUID: string):
}
let i = 1
- const match = name.match(/\d+$/)
+ const match = /\d+$/.exec(name)
if (match) {
i = parseInt(match[0])
name = name.slice(0, -match[0].length)
diff --git a/src/mods/addLocatorActionMod.ts b/src/blockbench-mods/action/addLocator.ts
similarity index 65%
rename from src/mods/addLocatorActionMod.ts
rename to src/blockbench-mods/action/addLocator.ts
index 269277ad..69a00c8d 100644
--- a/src/mods/addLocatorActionMod.ts
+++ b/src/blockbench-mods/action/addLocator.ts
@@ -1,9 +1,9 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:addLocatorAction`,
+ `${PACKAGE.name}:mods/action/addLocator`,
{
action: BarItems.add_locator as Action,
originalCondition: (BarItems.add_locator as Action).condition,
diff --git a/src/mods/animationPropertiesAction.ts b/src/blockbench-mods/action/animationProperties.ts
similarity index 63%
rename from src/mods/animationPropertiesAction.ts
rename to src/blockbench-mods/action/animationProperties.ts
index 4384641f..ce268b75 100644
--- a/src/mods/animationPropertiesAction.ts
+++ b/src/blockbench-mods/action/animationProperties.ts
@@ -1,10 +1,10 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { openAnimationPropertiesDialog } from '../interface/dialog/animationProperties'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { openAnimationPropertiesDialog } from '../../ui/dialogs/animation-properties'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:animationPropertiesAction`,
+ `${PACKAGE.name}:mods/action/animationProperties`,
{
originalOpen: Blockbench.Animation.prototype.propertiesDialog,
},
diff --git a/src/mods/exportOverActionMod.ts b/src/blockbench-mods/action/exportOver.ts
similarity index 77%
rename from src/mods/exportOverActionMod.ts
rename to src/blockbench-mods/action/exportOver.ts
index fe37b5fe..2793d868 100644
--- a/src/mods/exportOverActionMod.ts
+++ b/src/blockbench-mods/action/exportOver.ts
@@ -1,9 +1,12 @@
-import { BLUEPRINT_CODEC, BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
+import {
+ BLUEPRINT_CODEC,
+ BLUEPRINT_FORMAT,
+} from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:exportOverAction`,
+ `${PACKAGE.name}:mods/action/exportOver`,
{
action: BarItems.export_over as Action,
originalClick: (BarItems.export_over as Action).click,
diff --git a/src/mods/projectSettingsActionOverride.ts b/src/blockbench-mods/action/projectSettings.ts
similarity index 56%
rename from src/mods/projectSettingsActionOverride.ts
rename to src/blockbench-mods/action/projectSettings.ts
index 2cdd50d8..6d4a82b4 100644
--- a/src/mods/projectSettingsActionOverride.ts
+++ b/src/blockbench-mods/action/projectSettings.ts
@@ -1,10 +1,10 @@
-import PACKAGE from '../../package.json'
-import { BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { openBlueprintSettingsDialog } from '../interface/dialog/blueprintSettings'
-import { createBlockbenchMod } from '../util/moddingTools'
+import PACKAGE from '../../../package.json'
+import { BLUEPRINT_FORMAT } from '../../blockbench-additions/model-formats/ajblueprint'
+import { openBlueprintSettingsDialog } from '../../ui/dialogs/blueprint-settings'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:projectSettingsActionOverride`,
+ `${PACKAGE.name}:mods/action/projectSettings`,
{
action: BarItems.project_window as Action,
oldClick: (BarItems.project_window as Action).click,
diff --git a/src/mods/saveAllAnimationsActionMod.ts b/src/blockbench-mods/action/saveAllAnimations.ts
similarity index 64%
rename from src/mods/saveAllAnimationsActionMod.ts
rename to src/blockbench-mods/action/saveAllAnimations.ts
index eb0b2c44..43606337 100644
--- a/src/mods/saveAllAnimationsActionMod.ts
+++ b/src/blockbench-mods/action/saveAllAnimations.ts
@@ -1,9 +1,9 @@
-import { BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { BLUEPRINT_FORMAT } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:saveAllAnimationsActionMod`,
+ `${PACKAGE.name}:mods/action/saveAllAnimations`,
{
action: BarItems.save_all_animations as Action,
},
diff --git a/src/mods/saveProjectActionMod.ts b/src/blockbench-mods/action/saveProject.ts
similarity index 63%
rename from src/mods/saveProjectActionMod.ts
rename to src/blockbench-mods/action/saveProject.ts
index 016ae13a..3f3bb2e3 100644
--- a/src/mods/saveProjectActionMod.ts
+++ b/src/blockbench-mods/action/saveProject.ts
@@ -1,9 +1,12 @@
-import { BLUEPRINT_FORMAT, saveBlueprint } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
+import {
+ BLUEPRINT_FORMAT,
+ saveBlueprint,
+} from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:save_project`,
+ `${PACKAGE.name}:mods/action/saveProject`,
{
action: BarItems.save_project as Action,
originalClick: (BarItems.save_project as Action).click,
diff --git a/src/mods/saveProjectAsActionMod.ts b/src/blockbench-mods/action/saveProjectAs.ts
similarity index 64%
rename from src/mods/saveProjectAsActionMod.ts
rename to src/blockbench-mods/action/saveProjectAs.ts
index 67c24faa..a8a24b9e 100644
--- a/src/mods/saveProjectAsActionMod.ts
+++ b/src/blockbench-mods/action/saveProjectAs.ts
@@ -1,9 +1,12 @@
-import { BLUEPRINT_CODEC, BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
+import {
+ BLUEPRINT_CODEC,
+ BLUEPRINT_FORMAT,
+} from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:save_project_as`,
+ `${PACKAGE.name}:mods/action/saveProjectAs`,
{
action: BarItems.save_project_as as Action,
originalClick: (BarItems.save_project_as as Action).click,
diff --git a/src/mods/animationPropertiesMod.ts b/src/blockbench-mods/class-properties/animationProperties.ts
similarity index 61%
rename from src/mods/animationPropertiesMod.ts
rename to src/blockbench-mods/class-properties/animationProperties.ts
index fd914745..8ea965e7 100644
--- a/src/mods/animationPropertiesMod.ts
+++ b/src/blockbench-mods/class-properties/animationProperties.ts
@@ -1,14 +1,13 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { roundToNth } from '../util/misc'
-import { ContextProperty, createBlockbenchMod } from '../util/moddingTools'
-import { translate } from '../util/translation'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { roundToNth } from '../../util/misc'
+import { createBlockbenchMod } from '../../util/moddingTools'
export const DEFAULT_SNAPPING_VALUE = 20
export const MINIMUM_ANIMATION_LENGTH = 0.05
createBlockbenchMod(
- `${PACKAGE.name}:animationDefaultPropertiesMod`,
+ `${PACKAGE.name}:mods/class-properties/animation`,
{
originalExtend: Blockbench.Animation.prototype.extend,
originalSetLength: Blockbench.Animation.prototype.setLength,
@@ -39,7 +38,7 @@ createBlockbenchMod(
Blockbench.Animation.prototype.setLength = function (this: _Animation, length?: number) {
if (isCurrentFormat()) {
- length = Math.max(length || this.length, MINIMUM_ANIMATION_LENGTH)
+ length = Math.max(length ?? this.length, MINIMUM_ANIMATION_LENGTH)
}
return context.originalSetLength.call(this, length)
}
@@ -51,26 +50,3 @@ createBlockbenchMod(
Blockbench.Animation.prototype.setLength = context.originalSetLength
}
)
-
-createBlockbenchMod(
- `${PACKAGE.name}:animationPropertiesMod`,
- {
- excludedNodesProperty: undefined as ContextProperty<'array'>,
- },
- context => {
- context.excludedNodesProperty = new Property(
- Blockbench.Animation,
- 'array',
- 'excluded_nodes',
- {
- condition: () => isCurrentFormat(),
- label: translate('animation.excluded_nodes'),
- default: [],
- }
- )
- return context
- },
- context => {
- context.excludedNodesProperty?.delete()
- }
-)
diff --git a/src/blockbench-mods/css/panelTitleTextWrap.ts b/src/blockbench-mods/css/panelTitleTextWrap.ts
new file mode 100644
index 00000000..f61a01ea
--- /dev/null
+++ b/src/blockbench-mods/css/panelTitleTextWrap.ts
@@ -0,0 +1,24 @@
+import { PACKAGE } from '@aj/constants'
+import { createBlockbenchMod } from '@aj/util/moddingTools'
+
+/**
+ * Makes the text in panel titles clip instead of wrapping
+ */
+createBlockbenchMod(
+ `${PACKAGE.name}:panelTitleTextWrap`,
+ {
+ css: undefined as Deletable | undefined,
+ },
+ ctx => {
+ ctx.css = Blockbench.addCSS(`
+ .panel_handle label {
+ text-overflow: ellipsis;
+ text-wrap-mode: nowrap;
+ }
+ `)
+ return ctx
+ },
+ ctx => {
+ ctx.css?.delete()
+ }
+)
diff --git a/src/mods/blockbenchReadMod.ts b/src/blockbench-mods/function-overwrites/blockbenchRead.ts
similarity index 68%
rename from src/mods/blockbenchReadMod.ts
rename to src/blockbench-mods/function-overwrites/blockbenchRead.ts
index 0a5bf5d4..b3e632a0 100644
--- a/src/mods/blockbenchReadMod.ts
+++ b/src/blockbench-mods/function-overwrites/blockbenchRead.ts
@@ -1,13 +1,13 @@
-import { PACKAGE } from '../constants'
-import {
- closeBlueprintLoadingDialog,
- openBlueprintLoadingDialog,
- PROGRESS,
-} from '../interface/popup/blueprintLoading'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { PACKAGE } from '../../constants'
+// import {
+// closeBlueprintLoadingDialog,
+// openBlueprintLoadingDialog,
+// PROGRESS,
+// } from '../../ui/popups/blueprint-loading'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:blockbenchReadMod`,
+ `${PACKAGE.name}:mods/function-overwrites/blockbenchRead`,
{
original: Blockbench.read,
},
@@ -19,9 +19,9 @@ createBlockbenchMod(
) {
for (const file of files) {
context.original([file], options, cb)
- await new Promise(r => {
+ await new Promise(resolve => {
if (Project?.loadingPromises) {
- openBlueprintLoadingDialog()
+ // openBlueprintLoadingDialog()
const promises: Array> = []
for (const promise of Project.loadingPromises) {
promises.push(
@@ -29,7 +29,7 @@ createBlockbenchMod(
promise
.catch((err: any) => console.error(err))
.finally(() => {
- PROGRESS.set(PROGRESS.get() + 1)
+ // PROGRESS.set(PROGRESS.get() + 1)
r()
})
})
@@ -41,12 +41,12 @@ createBlockbenchMod(
console.error(err)
})
.finally(() => {
- closeBlueprintLoadingDialog()
- r()
+ // closeBlueprintLoadingDialog()
+ resolve()
})
return
}
- r()
+ resolve()
})
}
}
diff --git a/src/mods/customKeyframeEasingsMod.ts b/src/blockbench-mods/function-overwrites/reverseKeyframes.ts
similarity index 54%
rename from src/mods/customKeyframeEasingsMod.ts
rename to src/blockbench-mods/function-overwrites/reverseKeyframes.ts
index 64180946..37378c45 100644
--- a/src/mods/customKeyframeEasingsMod.ts
+++ b/src/blockbench-mods/function-overwrites/reverseKeyframes.ts
@@ -1,81 +1,13 @@
-import { ContextProperty, createBlockbenchMod } from '../util/moddingTools'
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
-import {
- EASING_DEFAULT,
- EasingKey,
- easingFunctions,
- getEasingArgDefault,
- hasArgs,
-} from '../util/easing'
+import { type EasingKey } from '../../util/easing'
interface IEasingProperties {
easing?: EasingKey
easingArgs?: any[]
}
-function lerp(start: number, stop: number, amt: number): number {
- return amt * (stop - start) + start
-}
-
-createBlockbenchMod(
- `${PACKAGE.name}:keyframeEasingMod`,
- {
- originalGetLerp: Blockbench.Keyframe.prototype.getLerp,
- easingProperty: undefined as ContextProperty<'string'>,
- easingArgsProperty: undefined as ContextProperty<'array'>,
- },
- context => {
- context.easingProperty = new Property(Blockbench.Keyframe, 'string', 'easing', {
- default: EASING_DEFAULT,
- condition: isCurrentFormat(),
- })
- context.easingArgsProperty = new Property(Blockbench.Keyframe, 'array', 'easingArgs', {
- condition: isCurrentFormat(),
- })
-
- Blockbench.Keyframe.prototype.getLerp = function (
- this: _Keyframe,
- other,
- axis,
- amount,
- allowExpression
- ): number {
- const easing = other.easing || 'linear'
-
- if (!isCurrentFormat() || easing === 'linear')
- return context.originalGetLerp.call(this, other, axis, amount, allowExpression)
-
- let easingFunc = easingFunctions[easing]
- if (hasArgs(easing)) {
- const arg1 =
- Array.isArray(other.easingArgs) && other.easingArgs.length > 0
- ? other.easingArgs[0]
- : getEasingArgDefault(other)
-
- easingFunc = easingFunc.bind(null, arg1 || 0)
- }
- const easedAmount = easingFunc(amount)
- const start = this.calc(axis)
- const stop = other.calc(axis)
- const result = lerp(start, stop, easedAmount)
-
- if (Number.isNaN(result)) {
- throw new Error('Invalid easing function or arguments.')
- }
- return result
- }
-
- return context
- },
- context => {
- context.easingProperty?.delete()
- context.easingArgsProperty?.delete()
- Blockbench.Keyframe.prototype.getLerp = context.originalGetLerp
- }
-)
-
export function reverseEasing(easing?: EasingKey): EasingKey | undefined {
if (!easing) return easing
if (easing.startsWith('easeInOut')) return easing
@@ -85,7 +17,7 @@ export function reverseEasing(easing?: EasingKey): EasingKey | undefined {
}
createBlockbenchMod(
- `${PACKAGE.name}:reverseKeyframesMod`,
+ `${PACKAGE.name}:mods/function-overwrites/reverseKeyframes`,
{
action: BarItems.reverse_keyframes as Action,
originalClick: (BarItems.reverse_keyframes as Action).click,
diff --git a/src/mods/showDefaultPoseMod.ts b/src/blockbench-mods/function-overwrites/showDefaultPose.ts
similarity index 67%
rename from src/mods/showDefaultPoseMod.ts
rename to src/blockbench-mods/function-overwrites/showDefaultPose.ts
index 536061f4..6be31582 100644
--- a/src/mods/showDefaultPoseMod.ts
+++ b/src/blockbench-mods/function-overwrites/showDefaultPose.ts
@@ -1,9 +1,12 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
+/**
+ * Corrects the handling of fix_position, fix_rotation, and fix_scale in the showDefaultPose method.
+ */
createBlockbenchMod(
- `${PACKAGE.name}:showDefaultPose`,
+ `${PACKAGE.name}:mods/function-overwrites/showDefaultPose`,
{
original: Animator.showDefaultPose,
},
@@ -21,9 +24,7 @@ createBlockbenchMod(
if (mesh.fix_scale) mesh.scale.copy(mesh.fix_scale)
else if (
// @ts-expect-error
- node.constructor.animator.prototype.channels &&
- // @ts-expect-error
- node.constructor.animator.prototype.channels.scale
+ node.constructor.animator.prototype.channels?.scale
) {
mesh.scale.x = mesh.scale.y = mesh.scale.z = 1
}
diff --git a/src/blockbench-mods/index.ts b/src/blockbench-mods/index.ts
new file mode 100644
index 00000000..d67703eb
--- /dev/null
+++ b/src/blockbench-mods/index.ts
@@ -0,0 +1,38 @@
+// Action
+import './action/addLocator'
+import './action/animationProperties'
+import './action/exportOver'
+import './action/projectSettings'
+import './action/saveAllAnimations'
+import './action/saveProject'
+import './action/saveProjectAs'
+// Class Properties
+import './class-properties/animationProperties'
+// CSS
+import './css/panelTitleTextWrap'
+// Function Overwrites
+import './function-overwrites/blockbenchRead'
+import './function-overwrites/reverseKeyframes'
+import './function-overwrites/showDefaultPose'
+// Prototype
+import './prototype/animationController'
+import './prototype/boneInterpolation'
+import './prototype/cameraName'
+import './prototype/cubeOutline'
+// import './prototype/groupContextMenu'
+import './prototype/groupName'
+import './prototype/keyframeGetLerp'
+import './prototype/keyframeSelectEvents'
+// import './prototype/locatorContextMenu'
+import './prototype/modelFormatEvents'
+import './prototype/variantPreviewCubeFace'
+// UI
+import './ui/animationsPanel'
+import './ui/formatIcon'
+import './ui/formatOrder'
+import './ui/incompatiblePluginNotice'
+import './ui/structureNodeIcons'
+// Misc
+import './misc/customKeyframes'
+import './misc/locatorAnimator'
+import './misc/prismLanguages'
diff --git a/src/mods/customKeyframesMod.ts b/src/blockbench-mods/misc/customKeyframes.ts
similarity index 94%
rename from src/mods/customKeyframesMod.ts
rename to src/blockbench-mods/misc/customKeyframes.ts
index ba6ec78d..073fe6a8 100644
--- a/src/mods/customKeyframesMod.ts
+++ b/src/blockbench-mods/misc/customKeyframes.ts
@@ -1,7 +1,10 @@
-import { BLUEPRINT_FORMAT, isCurrentFormat } from '../blueprintFormat'
-import { events } from '../util/events'
-import { translate } from '../util/translation'
-import { Variant } from '../variants'
+import EVENTS from '@events'
+import {
+ BLUEPRINT_FORMAT,
+ isCurrentFormat,
+} from '../../blockbench-additions/model-formats/ajblueprint'
+import { translate } from '../../util/translation'
+import { Variant } from '../../variants'
const DEFAULT_CHANNELS = { ...EffectAnimator.prototype.channels }
const DEFAULT_EFFECT_DISPLAY_FRAME = EffectAnimator.prototype.displayFrame
@@ -9,6 +12,10 @@ export const CUSTOM_CHANNELS = ['variant', 'commands']
let installed = false
+/**
+ * This mod is ✨ special ✨, it has to be injected, and extracted when switching between formats.
+ */
+
function injectCustomKeyframes() {
if (installed) return
@@ -168,7 +175,7 @@ function extractCustomKeyframes() {
installed = false
}
-events.PRE_SELECT_PROJECT.subscribe(project => {
+EVENTS.PRE_SELECT_PROJECT.subscribe(project => {
if (project.format.id === BLUEPRINT_FORMAT.id) {
injectCustomKeyframes()
} else {
diff --git a/src/mods/locatorAnimatorMod.ts b/src/blockbench-mods/misc/locatorAnimator.ts
similarity index 91%
rename from src/mods/locatorAnimatorMod.ts
rename to src/blockbench-mods/misc/locatorAnimator.ts
index 94891074..92af13dd 100644
--- a/src/mods/locatorAnimatorMod.ts
+++ b/src/blockbench-mods/misc/locatorAnimator.ts
@@ -1,6 +1,6 @@
-import { BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { events } from '../util/events'
-import { translate } from '../util/translation'
+import EVENTS from '@events'
+import { BLUEPRINT_FORMAT } from '../../blockbench-additions/model-formats/ajblueprint'
+import { translate } from '../../util/translation'
const DEFAULT_SHOW_MOTION_TRAIL = Animator.showMotionTrail
const DEFAULT_PREVIEW = Animator.preview
@@ -56,7 +56,7 @@ export class LocatorAnimator extends BoneAnimator {
}
}
- if (this.element && this.element.parent && this.element.parent !== 'root') {
+ if (this.element?.parent && this.element.parent !== 'root') {
this.element.parent.openUp()
}
@@ -65,7 +65,7 @@ export class LocatorAnimator extends BoneAnimator {
doRender() {
this.getElement()
- return !!(this.element && this.element.mesh)
+ return !!this.element?.mesh
}
displayPosition(arr?: ArrayVector3, multiplier = 1) {
@@ -157,7 +157,7 @@ function extract() {
installed = false
}
-events.PRE_SELECT_PROJECT.subscribe(project => {
+EVENTS.PRE_SELECT_PROJECT.subscribe(project => {
if (project.format.id === BLUEPRINT_FORMAT.id) {
inject()
} else {
diff --git a/src/blockbench-mods/misc/prismLanguages.ts b/src/blockbench-mods/misc/prismLanguages.ts
new file mode 100644
index 00000000..6bf90185
--- /dev/null
+++ b/src/blockbench-mods/misc/prismLanguages.ts
@@ -0,0 +1,351 @@
+Prism.languages.clike = {
+ comment: [
+ {
+ pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,
+ lookbehind: true,
+ greedy: true,
+ },
+ {
+ pattern: /(^|[^\\:])\/\/.*/,
+ lookbehind: true,
+ greedy: true,
+ },
+ ],
+ string: {
+ pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
+ greedy: true,
+ },
+ 'class-name': {
+ pattern:
+ /(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,
+ lookbehind: true,
+ inside: {
+ punctuation: /[.\\]/,
+ },
+ },
+ keyword:
+ /\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,
+ boolean: /\b(?:false|true)\b/,
+ function: /\b\w+(?=\()/,
+ number: /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,
+ operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,
+ punctuation: /[{}[\];(),.:]/,
+}
+
+Prism.languages.javascript = Prism.languages.extend('clike', {
+ 'class-name': [
+ // @ts-expect-error
+ Prism.languages.clike['class-name'],
+ {
+ pattern:
+ /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,
+ lookbehind: true,
+ },
+ ],
+ keyword: [
+ {
+ pattern: /((?:^|\})\s*)catch\b/,
+ lookbehind: true,
+ },
+ {
+ pattern:
+ /(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,
+ lookbehind: true,
+ },
+ ],
+ // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
+ function:
+ /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,
+ number: {
+ pattern: RegExp(
+ /(^|[^\w$])/.source +
+ '(?:' +
+ // constant
+ (/NaN|Infinity/.source +
+ '|' +
+ // binary integer
+ /0[bB][01]+(?:_[01]+)*n?/.source +
+ '|' +
+ // octal integer
+ /0[oO][0-7]+(?:_[0-7]+)*n?/.source +
+ '|' +
+ // hexadecimal integer
+ /0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source +
+ '|' +
+ // decimal bigint
+ /\d+(?:_\d+)*n/.source +
+ '|' +
+ // decimal number (integer or float) but no bigint
+ /(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/
+ .source) +
+ ')' +
+ /(?![\w$])/.source
+ ),
+ lookbehind: true,
+ },
+ operator:
+ /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/,
+})
+
+// @ts-expect-error
+Prism.languages.javascript['class-name'][0].pattern =
+ /(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/
+
+Prism.languages.insertBefore('javascript', 'keyword', {
+ regex: {
+ pattern: RegExp(
+ // lookbehind
+ /((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source +
+ // Regex pattern:
+ // There are 2 regex patterns here. The RegExp set notation proposal added support for nested character
+ // classes if the `v` flag is present. Unfortunately, nested CCs are both context-free and incompatible
+ // with the only syntax, so we have to define 2 different regex patterns.
+ /\//.source +
+ '(?:' +
+ /(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\[\r\n])+\/[dgimyus]{0,7}/.source +
+ '|' +
+ // `v` flag syntax. This supports 3 levels of nested character classes.
+ /(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/
+ .source +
+ ')' +
+ // lookahead
+ /(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source
+ ),
+ lookbehind: true,
+ greedy: true,
+ inside: {
+ 'regex-source': {
+ pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/,
+ lookbehind: true,
+ alias: 'language-regex',
+ inside: Prism.languages.regex,
+ },
+ 'regex-delimiter': /^\/|\/$/,
+ 'regex-flags': /^[a-z]+$/,
+ },
+ },
+ // This must be declared before keyword because we use "function" inside the look-forward
+ 'function-variable': {
+ pattern:
+ /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,
+ alias: 'function',
+ },
+ parameter: [
+ {
+ pattern:
+ /(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,
+ lookbehind: true,
+ inside: Prism.languages.javascript,
+ },
+ {
+ pattern:
+ /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,
+ lookbehind: true,
+ inside: Prism.languages.javascript,
+ },
+ {
+ pattern: /(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,
+ lookbehind: true,
+ inside: Prism.languages.javascript,
+ },
+ {
+ pattern:
+ /((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,
+ lookbehind: true,
+ inside: Prism.languages.javascript,
+ },
+ ],
+ constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/,
+})
+
+Prism.languages.insertBefore('javascript', 'string', {
+ hashbang: {
+ pattern: /^#!.*/,
+ greedy: true,
+ alias: 'comment',
+ },
+ 'template-string': {
+ pattern: /`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,
+ greedy: true,
+ inside: {
+ 'template-punctuation': {
+ pattern: /^`|`$/,
+ alias: 'string',
+ },
+ interpolation: {
+ pattern: /((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,
+ lookbehind: true,
+ inside: {
+ 'interpolation-punctuation': {
+ pattern: /^\$\{|\}$/,
+ alias: 'punctuation',
+ },
+ rest: Prism.languages.javascript,
+ },
+ },
+ string: /[\s\S]+/,
+ },
+ },
+ 'string-property': {
+ pattern: /((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,
+ lookbehind: true,
+ greedy: true,
+ alias: 'property',
+ },
+})
+
+Prism.languages.insertBefore('javascript', 'operator', {
+ 'literal-property': {
+ pattern:
+ /((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,
+ lookbehind: true,
+ alias: 'property',
+ },
+})
+
+Prism.languages.mcfunction = {
+ comment: {
+ pattern: /^#.*/m,
+ greedy: true,
+ },
+ string: {
+ pattern: /"(?:\\.|[^\\"])*"/,
+ greedy: true,
+ },
+ number: {
+ pattern: /\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/,
+ greedy: true,
+ },
+ boolean: {
+ pattern: /\b(?:true|false)\b/,
+ greedy: true,
+ },
+ uuid: {
+ pattern: /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/,
+ greedy: true,
+ },
+ selector: {
+ pattern: /@[a-z]/,
+ greedy: true,
+ },
+ keyword: {
+ pattern:
+ /\b(?:execute|run|if|unless|function|schedule|tag|advancement|enchantment|item_modifier|loot_table|predicate|recipe|chat_type|damage_type|dimension|dimension_type|worldgen|block|tick|load|clock|REPEAT|IF|ELSE)\b/,
+ greedy: true,
+ },
+ operator: {
+ pattern: /[~%^?!+*<>\\/|&=.:,;-]/,
+ greedy: true,
+ },
+ 'macro-indicator': {
+ pattern: /^[ \t]*\$/,
+ alias: ['italic', 'operator'],
+ },
+ property: {
+ pattern: /[a-z_][a-z0-9_]*\s*:/,
+ greedy: true,
+ },
+ function: {
+ pattern: /\b[a-z_][a-z0-9_]*\b/,
+ greedy: true,
+ },
+}
+
+Prism.languages['mc-build'] = {
+ comment: {
+ pattern: /^[ \t]*#.*/m,
+ greedy: true,
+ },
+ 'inline-js': {
+ pattern: /<%[^%]+%>/,
+ inside: {
+ keyword: /<%|%>/,
+ js: {
+ pattern: /.+/,
+ inside: Prism.languages.javascript,
+ },
+ },
+ },
+ 'multiline-js': {
+ pattern: /<%%[\s\S]*?%%>/,
+ inside: {
+ keyword: /<%%|%%>/,
+ js: {
+ pattern: /[\s\S]+/,
+ inside: Prism.languages.javascript,
+ },
+ },
+ },
+ 'schedule-block': {
+ pattern: /^[ \t]*\$?schedule\s+(\d+(t|s|d))(?:\s+(replace|append))?\s*{[\s\S]*?^[ \t]*}$/m,
+ inside: {
+ operator: /\$(?=schedule)/,
+ keyword: /^[ \t]*\$?schedule/,
+ constant: /(\d+(t|s|d))/,
+ 'keyword-other': {
+ pattern: /\b(replace|append)\b/,
+ alias: 'keyword',
+ },
+ content: {
+ pattern: /{[\s\S]*}/,
+ get inside() {
+ return Prism.languages['mc-build']
+ },
+ },
+ },
+ },
+ 'function-block': {
+ pattern: /block\s*[a-z0-9_]*\s*{(?:$|({.+|with .+)$)/m,
+ inside: {
+ keyword: /^block/,
+ entity: /[a-z0-9_]+/,
+ content: {
+ pattern: /{[\s\S]*}/,
+ get inside() {
+ return Prism.languages['mc-build']
+ },
+ },
+ },
+ },
+ execute: {
+ pattern:
+ /((?: |^[ \t]*)(\$?execute\b)|(\belse \$?execute\b)|else)[\s\S]*?(?: |^[ \t]*)(\$?run\b)/m,
+ inside: {
+ keyword: /(\$?execute\b|\belse \$?execute\b|else|\$?run\b)/,
+ operator: /\$/,
+ content: {
+ pattern: /[\s\S]+/,
+ get inside() {
+ return Prism.languages['mc-build']
+ },
+ },
+ },
+ },
+ 'execute-if-function': {
+ pattern: /(if|unless)\s+function\s*{[\s\S]*?^[ \t]*}$/m,
+ inside: {
+ keyword: /(if|unless)/,
+ entity: /function/,
+ content: {
+ pattern: /{[\s\S]*}/,
+ get inside() {
+ return Prism.languages['mc-build']
+ },
+ },
+ },
+ },
+ 'special-function-call': {
+ pattern: /^[ \t]*function\s+#?(?:\*|\.?\.\/).+?(?: |$)/m,
+ inside: {
+ keyword: /^function/,
+ entity: /#?(?:\*|\.?\.\/).+?(?: |$)/,
+ content: {
+ pattern: /.+/,
+ get inside() {
+ return Prism.languages['mc-build']
+ },
+ },
+ },
+ },
+ ...Prism.languages.mcfunction,
+}
diff --git a/src/mods/animationControllerMod.ts b/src/blockbench-mods/prototype/animationController.ts
similarity index 64%
rename from src/mods/animationControllerMod.ts
rename to src/blockbench-mods/prototype/animationController.ts
index 91cc42be..23e9a4e3 100644
--- a/src/mods/animationControllerMod.ts
+++ b/src/blockbench-mods/prototype/animationController.ts
@@ -1,9 +1,9 @@
-import { BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod, createPropertySubscribable } from '../util/moddingTools'
+import { BLUEPRINT_FORMAT } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod, createPropertySubscribable } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:animationControllerMod`,
+ `${PACKAGE.name}:mods/prototype/animationController`,
undefined,
() => {
const [, set] = createPropertySubscribable(AnimationController.prototype, 'saved')
diff --git a/src/mods/boneInterpolationMod.ts b/src/blockbench-mods/prototype/boneInterpolation.ts
similarity index 86%
rename from src/mods/boneInterpolationMod.ts
rename to src/blockbench-mods/prototype/boneInterpolation.ts
index 22554d83..8060dc36 100644
--- a/src/mods/boneInterpolationMod.ts
+++ b/src/blockbench-mods/prototype/boneInterpolation.ts
@@ -1,10 +1,10 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { roundToNth } from '../util/misc'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { roundToNth } from '../../util/misc'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:boneInterpolationMod`,
+ `${PACKAGE.name}:mods/prototype/boneInterpolation`,
{
orignalInterpolate: BoneAnimator.prototype.interpolate,
},
@@ -54,7 +54,6 @@ createBlockbenchMod(
Math.lerp(before[1], after[1], diff),
Math.lerp(before[2], after[2], diff),
]
- // console.log(diff)
return result
diff --git a/src/mods/cameraNameMod.ts b/src/blockbench-mods/prototype/cameraName.ts
similarity index 73%
rename from src/mods/cameraNameMod.ts
rename to src/blockbench-mods/prototype/cameraName.ts
index f061da24..6e89dceb 100644
--- a/src/mods/cameraNameMod.ts
+++ b/src/blockbench-mods/prototype/cameraName.ts
@@ -1,10 +1,10 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { sanitizeOutlinerElementName } from '../outliner/util'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { sanitizeOutlinerElementName } from '../../blockbench-additions/outliner-elements/util'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:cameraNameMod`,
+ `${PACKAGE.name}:mods/prototype/cameraName`,
{
originalRename: OutlinerElement.types.camera?.prototype.saveName,
originalSanitize: OutlinerElement.types.camera?.prototype.sanitizeName,
diff --git a/src/mods/cubeOutlineMod.ts b/src/blockbench-mods/prototype/cubeOutline.ts
similarity index 87%
rename from src/mods/cubeOutlineMod.ts
rename to src/blockbench-mods/prototype/cubeOutline.ts
index 0654adf4..19aa3192 100644
--- a/src/mods/cubeOutlineMod.ts
+++ b/src/blockbench-mods/prototype/cubeOutline.ts
@@ -1,16 +1,16 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { isCubeValid } from '../systems/util'
-import { createBlockbenchMod, createPropertySubscribable } from '../util/moddingTools'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { isCubeValid } from '../../util/misc'
+import { createBlockbenchMod, createPropertySubscribable } from '../../util/moddingTools'
const ERROR_OUTLINE_MATERIAL = Canvas.outlineMaterial.clone()
ERROR_OUTLINE_MATERIAL.color.set('#ff0000')
createBlockbenchMod(
- `${PACKAGE.name}:cubeOutlineMod`,
+ `${PACKAGE.name}:mods/prototype/cubeOutline`,
{
- originalUpdateTransform: Cube.preview_controller.updateTransform,
originalInit: Cube.prototype.init,
+ originalUpdateTransform: Cube.preview_controller.updateTransform,
},
context => {
Cube.preview_controller.updateTransform = function (cube: Cube) {
diff --git a/src/mods/groupContextMenuMod.ts b/src/blockbench-mods/prototype/groupContextMenu.ts
similarity index 53%
rename from src/mods/groupContextMenuMod.ts
rename to src/blockbench-mods/prototype/groupContextMenu.ts
index 13024ca7..d1748322 100644
--- a/src/mods/groupContextMenuMod.ts
+++ b/src/blockbench-mods/prototype/groupContextMenu.ts
@@ -1,9 +1,12 @@
-import { PACKAGE } from '../constants'
-import { BONE_CONFIG_ACTION } from '../interface/dialog/boneConfig'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { PACKAGE } from '../../constants'
+import { BONE_CONFIG_ACTION } from '../../ui/dialogs/bone-config'
+import { createBlockbenchMod } from '../../util/moddingTools'
+/**
+ * Adds the bone config action to the group context menu
+ */
createBlockbenchMod(
- `${PACKAGE.name}:groupContextMenu`,
+ `${PACKAGE.name}:mods/prototype/groupContextMenu`,
{
menuStructure: Group.prototype.menu!.structure,
},
diff --git a/src/mods/groupNameMod.ts b/src/blockbench-mods/prototype/groupName.ts
similarity index 59%
rename from src/mods/groupNameMod.ts
rename to src/blockbench-mods/prototype/groupName.ts
index 6db69049..58943964 100644
--- a/src/mods/groupNameMod.ts
+++ b/src/blockbench-mods/prototype/groupName.ts
@@ -1,10 +1,13 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { sanitizeOutlinerElementName } from '../outliner/util'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { sanitizeOutlinerElementName } from '../../blockbench-additions/outliner-elements/util'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
+/**
+ * Changes the sanitization of group names to use the same method as all other outliner elements in a Blueprint
+ */
createBlockbenchMod(
- `${PACKAGE.name}:groupNameMod`,
+ `${PACKAGE.name}:mods/prototype/groupName`,
{
originalRename: Group.prototype.saveName,
originalSanitize: Group.prototype.sanitizeName,
diff --git a/src/blockbench-mods/prototype/keyframeGetLerp.ts b/src/blockbench-mods/prototype/keyframeGetLerp.ts
new file mode 100644
index 00000000..b5cb85ac
--- /dev/null
+++ b/src/blockbench-mods/prototype/keyframeGetLerp.ts
@@ -0,0 +1,54 @@
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
+
+import { easingFunctions, getEasingArgDefault, hasArgs } from '../../util/easing'
+
+function lerp(start: number, stop: number, amt: number): number {
+ return amt * (stop - start) + start
+}
+
+createBlockbenchMod(
+ `${PACKAGE.name}:mods/prototype/keyframeGetLerp`,
+ {
+ originalGetLerp: Blockbench.Keyframe.prototype.getLerp,
+ },
+ context => {
+ Blockbench.Keyframe.prototype.getLerp = function (
+ this: _Keyframe,
+ other,
+ axis,
+ amount,
+ allowExpression
+ ): number {
+ const easing = other.easing ?? 'linear'
+
+ if (!isCurrentFormat() || easing === 'linear')
+ return context.originalGetLerp.call(this, other, axis, amount, allowExpression)
+
+ let easingFunc = easingFunctions[easing]
+ if (hasArgs(easing)) {
+ const arg1 =
+ Array.isArray(other.easingArgs) && other.easingArgs.length > 0
+ ? other.easingArgs[0]
+ : getEasingArgDefault(other)
+
+ easingFunc = easingFunc.bind(null, arg1 ?? 0)
+ }
+ const easedAmount = easingFunc(amount)
+ const start = this.calc(axis)
+ const stop = other.calc(axis)
+ const result = lerp(start, stop, easedAmount)
+
+ if (Number.isNaN(result)) {
+ throw new Error('Invalid easing function or arguments.')
+ }
+ return result
+ }
+
+ return context
+ },
+ context => {
+ Blockbench.Keyframe.prototype.getLerp = context.originalGetLerp
+ }
+)
diff --git a/src/mods/keyframeMod.ts b/src/blockbench-mods/prototype/keyframeSelectEvents.ts
similarity index 75%
rename from src/mods/keyframeMod.ts
rename to src/blockbench-mods/prototype/keyframeSelectEvents.ts
index ae586d6f..8c9d07aa 100644
--- a/src/mods/keyframeMod.ts
+++ b/src/blockbench-mods/prototype/keyframeSelectEvents.ts
@@ -1,10 +1,13 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { events } from '../util/events'
-import { createBlockbenchMod } from '../util/moddingTools'
+import EVENTS from '@events'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
+/**
+ * Dispatches custom events when keyframes are selected or unselected
+ */
createBlockbenchMod(
- `${PACKAGE.name}:keyframeSelectEventMod`,
+ `${PACKAGE.name}:mods/prototype/keyframeSelectEvents`,
{
originalKeyframeSelect: Blockbench.Keyframe.prototype.select,
originalUpdateKeyframeSelection: updateKeyframeSelection,
@@ -15,7 +18,7 @@ createBlockbenchMod(
Blockbench.Keyframe.prototype.select = function (this: _Keyframe, event: any) {
if (!isCurrentFormat()) return context.originalKeyframeSelect.call(this, event)
const kf = context.originalKeyframeSelect.call(this, event)
- events.SELECT_KEYFRAME.dispatch(kf)
+ EVENTS.SELECT_KEYFRAME.dispatch(kf)
return kf
}
@@ -25,7 +28,7 @@ createBlockbenchMod(
Timeline.keyframes.forEach(kf => {
if (kf.selected && Timeline.selected && !Timeline.selected.includes(kf)) {
kf.selected = false
- events.UNSELECT_KEYFRAME.dispatch()
+ EVENTS.UNSELECT_KEYFRAME.dispatch()
}
let hasExpressions = false
if (kf.transform) {
@@ -43,8 +46,7 @@ createBlockbenchMod(
})
if (Timeline.selected) {
- console.log('Selected keyframe:', Timeline.selected[0])
- events.SELECT_KEYFRAME.dispatch(Timeline.selected[0])
+ EVENTS.SELECT_KEYFRAME.dispatch(Timeline.selected[0])
}
return context.originalUpdateKeyframeSelection()
@@ -55,9 +57,9 @@ createBlockbenchMod(
if (isCurrentFormat()) {
if (Timeline.selected && Timeline.selected.length > 0) {
- events.SELECT_KEYFRAME.dispatch(Timeline.selected[0])
+ EVENTS.SELECT_KEYFRAME.dispatch(Timeline.selected[0])
} else {
- events.UNSELECT_KEYFRAME.dispatch()
+ EVENTS.UNSELECT_KEYFRAME.dispatch()
}
}
diff --git a/src/mods/locatorContextMenuMod.ts b/src/blockbench-mods/prototype/locatorContextMenu.ts
similarity index 59%
rename from src/mods/locatorContextMenuMod.ts
rename to src/blockbench-mods/prototype/locatorContextMenu.ts
index c771e76d..e2fe2ebf 100644
--- a/src/mods/locatorContextMenuMod.ts
+++ b/src/blockbench-mods/prototype/locatorContextMenu.ts
@@ -1,9 +1,9 @@
-import { PACKAGE } from '../constants'
-import { LOCATOR_CONFIG_ACTION } from '../interface/dialog/locatorConfig'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { PACKAGE } from '../../constants'
+import { LOCATOR_CONFIG_ACTION } from '../../ui/dialogs/locator-config'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:groupContextMenu`,
+ `${PACKAGE.name}:mods/prototype/locatorContextMenu`,
{
menuStructure: Locator.prototype.menu!.structure,
},
diff --git a/src/mods/modelFormatMod.ts b/src/blockbench-mods/prototype/modelFormatEvents.ts
similarity index 52%
rename from src/mods/modelFormatMod.ts
rename to src/blockbench-mods/prototype/modelFormatEvents.ts
index 9dd42ec4..00af221c 100644
--- a/src/mods/modelFormatMod.ts
+++ b/src/blockbench-mods/prototype/modelFormatEvents.ts
@@ -1,17 +1,17 @@
-import { BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { events } from '../util/events'
-import { createBlockbenchMod } from '../util/moddingTools'
+import EVENTS from '@events'
+import { BLUEPRINT_FORMAT } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:modelFormatPreSelectProjectEvent`,
+ `${PACKAGE.name}:mods/prototype/modelFormatEvents`,
{
originalSelect: ModelProject.prototype.select,
},
context => {
ModelProject.prototype.select = function (this: ModelProject) {
if (this.format.id === BLUEPRINT_FORMAT.id) {
- events.PRE_SELECT_PROJECT.dispatch(this)
+ EVENTS.PRE_SELECT_PROJECT.dispatch(this)
}
return context.originalSelect.call(this)
}
diff --git a/src/mods/variantPreviewCubeFaceMod.ts b/src/blockbench-mods/prototype/variantPreviewCubeFace.ts
similarity index 70%
rename from src/mods/variantPreviewCubeFaceMod.ts
rename to src/blockbench-mods/prototype/variantPreviewCubeFace.ts
index 775285af..442f08ff 100644
--- a/src/mods/variantPreviewCubeFaceMod.ts
+++ b/src/blockbench-mods/prototype/variantPreviewCubeFace.ts
@@ -1,10 +1,13 @@
-import { isCurrentFormat } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
-import { Variant } from '../variants'
+import { isCurrentFormat } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
+import { Variant } from '../../variants'
+/**
+ * Swaps out the texture of a cube face with the variant texture based on the currently selected variant
+ */
createBlockbenchMod(
- `${PACKAGE.name}:variantPreviewCubeFace`,
+ `${PACKAGE.name}:mods/prototype/variantPreviewCubeFace`,
{
originalGetTexture: CubeFace.prototype.getTexture,
},
diff --git a/src/mods/panelMod.ts b/src/blockbench-mods/ui/animationsPanel.ts
similarity index 69%
rename from src/mods/panelMod.ts
rename to src/blockbench-mods/ui/animationsPanel.ts
index 65f54406..e8f1a902 100644
--- a/src/mods/panelMod.ts
+++ b/src/blockbench-mods/ui/animationsPanel.ts
@@ -1,14 +1,15 @@
-import { BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
+import { BLUEPRINT_FORMAT } from '../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
createBlockbenchMod(
- `${PACKAGE.name}:panelMod`,
+ `${PACKAGE.name}:mods/ui/animationsPanel`,
{
panel: Interface.Panels.animations,
},
context => {
- const originalFilesFunction = context.panel.inside_vue.$options.computed!.files as () => any
+ const originalFilesFunction = context.panel.inside_vue.$options.computed!
+ .files as () => Record
context.panel.inside_vue.$options.computed!.files = function (this) {
if (Format.id === BLUEPRINT_FORMAT.id) {
diff --git a/src/blockbench-mods/ui/formatIcon.ts b/src/blockbench-mods/ui/formatIcon.ts
new file mode 100644
index 00000000..1438ea7f
--- /dev/null
+++ b/src/blockbench-mods/ui/formatIcon.ts
@@ -0,0 +1,32 @@
+import Icon from '@svelte-components/icon.svelte'
+import { PACKAGE } from '../../constants'
+import { injectSvelteCompomponent } from '../../util/injectSvelteComponent'
+import { createBlockbenchMod } from '../../util/moddingTools'
+
+/**
+ * Adds an icon to the format category title.
+ */
+createBlockbenchMod(
+ `${PACKAGE.name}:mods/ui/formatIcon`,
+ undefined,
+ () => {
+ void injectSvelteCompomponent({
+ elementSelector: () => document.querySelector('[format=animated_java_blueprint]'),
+ component: Icon,
+ props: {},
+ prepend: true,
+ postMount: () => {
+ document
+ .querySelector('[format=animated_java_blueprint] span i')
+ ?.parentElement?.remove()
+ const duplicates = [...document.querySelectorAll('#animated_java\\:icon')]
+ if (duplicates.length > 1) {
+ duplicates.slice(1).forEach(d => d.remove())
+ }
+ },
+ })
+ },
+ () => {
+ document.querySelector('#animated_java\\:icon')?.remove()
+ }
+)
diff --git a/src/blockbench-mods/ui/formatOrder.ts b/src/blockbench-mods/ui/formatOrder.ts
new file mode 100644
index 00000000..2cbc903c
--- /dev/null
+++ b/src/blockbench-mods/ui/formatOrder.ts
@@ -0,0 +1,27 @@
+import { PACKAGE } from '../../constants'
+import { createBlockbenchMod } from '../../util/moddingTools'
+
+/**
+ * I want alphabetically ordered format categories.
+ */
+createBlockbenchMod(
+ `${PACKAGE.name}:mods/ui/formatOrder`,
+ undefined,
+ () => {
+ const interval = setInterval(() => {
+ const ajFormats = $("li.format_category > label:contains('Animated Java')")
+ .first()
+ .parent()
+ if (ajFormats.length === 0) return
+
+ const mcFormats = $("li.format_category > label:contains('General')").first().parent()
+
+ ajFormats.insertBefore(mcFormats)
+
+ clearInterval(interval)
+ }, 16)
+ },
+ () => {
+ // Pass
+ }
+)
diff --git a/src/mods/pluginsDialogMod.ts b/src/blockbench-mods/ui/incompatiblePluginNotice.ts
similarity index 57%
rename from src/mods/pluginsDialogMod.ts
rename to src/blockbench-mods/ui/incompatiblePluginNotice.ts
index 36d841c9..58160992 100644
--- a/src/mods/pluginsDialogMod.ts
+++ b/src/blockbench-mods/ui/incompatiblePluginNotice.ts
@@ -1,10 +1,10 @@
-import IncompatiblePluginNotice from '../components/incompatiblePluginNotice.svelte'
-import { PACKAGE } from '../constants'
-import { injectSvelteCompomponentMod } from '../util/injectSvelteComponent'
-import { createBlockbenchMod } from '../util/moddingTools'
-import { Valuable } from '../util/stores'
+import IncompatiblePluginNotice from '@svelte-components/incompatiblePluginNotice.svelte'
+import { PACKAGE } from '../../constants'
+import { injectSvelteCompomponentMod } from '../../util/injectSvelteComponent'
+import { createBlockbenchMod } from '../../util/moddingTools'
+import { Syncable } from '../../util/stores'
-const SELECTED_PLUGIN = new Valuable(null)
+const SELECTED_PLUGIN = new Syncable(null)
injectSvelteCompomponentMod({
component: IncompatiblePluginNotice,
@@ -17,9 +17,9 @@ injectSvelteCompomponentMod({
})
createBlockbenchMod(
- `${PACKAGE.name}:pluginsDialogMod`,
+ `${PACKAGE.name}:mods/ui/incompatiblePluginNotice`,
{
- // @ts-expect-error
+ // @ts-expect-error - Vue components default to having no methods
originalSelect: Plugins.dialog.component.methods.selectPlugin,
},
context => {
@@ -27,7 +27,6 @@ createBlockbenchMod(
Plugins.dialog.component.methods.selectPlugin = function (this, plugin: BBPlugin) {
const result = context.originalSelect.call(this, plugin)
SELECTED_PLUGIN.set(plugin)
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return result
}
diff --git a/src/blockbench-mods/ui/structureNodeIcons.ts b/src/blockbench-mods/ui/structureNodeIcons.ts
new file mode 100644
index 00000000..b9c7ccc6
--- /dev/null
+++ b/src/blockbench-mods/ui/structureNodeIcons.ts
@@ -0,0 +1,56 @@
+import { PACKAGE } from '@aj/constants'
+import { createBlockbenchMod } from '@aj/util/moddingTools'
+import { translate } from '@aj/util/translation'
+
+createBlockbenchMod(
+ `${PACKAGE.name}:structureNodeIcons`,
+ {
+ // @ts-expect-error
+ originalIcon: Group.prototype.icon,
+ // @ts-expect-error
+ originalTitle: Group.prototype.title,
+ },
+ ctx => {
+ Object.defineProperty(Group.prototype, 'icon', {
+ get() {
+ for (const child of this.children) {
+ if (child instanceof Cube) {
+ return ctx.originalIcon
+ }
+ }
+ return 'account_tree'
+ },
+ set() {
+ console.warn('Setting the default Group icon is not allowed!')
+ },
+ })
+
+ Object.defineProperty(Group.prototype, 'title', {
+ get() {
+ for (const child of this.children) {
+ if (child instanceof Cube) {
+ return ctx.originalTitle
+ }
+ }
+ return translate('node.structure.title').replaceAll(' ', '\n')
+ },
+ set() {
+ console.warn('Setting the default Group icon is not allowed!')
+ },
+ })
+
+ return ctx
+ },
+ ctx => {
+ Object.defineProperty(Group.prototype, 'icon', {
+ value: ctx.originalIcon,
+ get: undefined,
+ set: undefined,
+ })
+ Object.defineProperty(Group.prototype, 'title', {
+ value: ctx.originalTitle,
+ get: undefined,
+ set: undefined,
+ })
+ }
+)
diff --git a/src/blockbenchTypeMods.d.ts b/src/blockbenchTypeMods.d.ts
deleted file mode 100644
index aa2befcc..00000000
--- a/src/blockbenchTypeMods.d.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import type {
- BLUEPRINT_CODEC,
- BLUEPRINT_FORMAT,
- IBlueprintBoneConfigJSON,
- IBlueprintLocatorConfigJSON,
-} from './blueprintFormat'
-import { blueprintSettingErrors, defaultValues } from './blueprintSettings'
-import { openExportProgressDialog } from './interface/dialog/exportProgress'
-import { openUnexpectedErrorDialog } from './interface/dialog/unexpectedError'
-import { TextDisplay } from './outliner/textDisplay'
-import { VanillaBlockDisplay } from './outliner/vanillaBlockDisplay'
-import { VanillaItemDisplay } from './outliner/vanillaItemDisplay'
-import datapackCompiler from './systems/datapackCompiler'
-import { MINECRAFT_REGISTRY } from './systems/minecraft/registryManager'
-import resourcepackCompiler from './systems/resourcepackCompiler'
-import { EasingKey } from './util/easing'
-import { isDataPackPath, isResourcePackPath } from './util/minecraftUtil'
-import { Valuable } from './util/stores'
-import { type Variant } from './variants'
-
-declare module 'three' {
- interface Object3D {
- isVanillaItemModel?: boolean
- isVanillaBlockModel?: boolean
- isTextDisplayText?: boolean
- fix_scale?: THREE.Vector3
- }
-}
-
-declare global {
- interface ModelProject {
- animated_java: { [T in keyof typeof defaultValues]: (typeof defaultValues)[T] }
- last_used_export_namespace: string
- visualBoundingBox?: THREE.LineSegments
- pluginMode: Valuable
- transparentTexture: Texture
-
- showingInvalidCubeRotations: boolean
-
- variants: Variant[]
- textDisplays: TextDisplay[]
- vanillaItemDisplays: VanillaItemDisplay[]
- vanillaBlockDisplays: VanillaBlockDisplay[]
-
- loadingPromises?: Array>
- }
-
- // eslint-disable-next-line @typescript-eslint/naming-convention
- interface _Animation {
- excluded_nodes: CollectionItem[]
- }
-
- interface AnimationUndoCopy {
- excluded_nodes: string[]
- }
-
- interface AnimationOptions {
- excluded_nodes: string[]
- }
-
- // eslint-disable-next-line @typescript-eslint/naming-convention
- interface _Keyframe {
- easing?: EasingKey
- easingArgs?: number[]
- }
-
- interface Group {
- configs: {
- default: IBlueprintBoneConfigJSON
- /**
- * @key Variant UUID
- * @value Variant Bone Config
- */
- variants: Record
- }
- }
-
- interface Locator {
- config: IBlueprintLocatorConfigJSON
- }
-
- interface Cube {
- rotationInvalid: boolean
- }
-
- interface CubeFace {
- lastVariant: Variant | undefined
- }
-
- const AnimatedJava: {
- API: {
- compileDataPack: typeof datapackCompiler
- compileResourcePack: typeof resourcepackCompiler
- Variant: typeof Variant
- MINECRAFT_REGISTRY: typeof MINECRAFT_REGISTRY
- openExportProgressDialog: typeof openExportProgressDialog
- isResourcePackPath: typeof isResourcePackPath
- isDataPackPath: typeof isDataPackPath
- blueprintSettingErrors: typeof blueprintSettingErrors
- openUnexpectedErrorDialog: typeof openUnexpectedErrorDialog
- BLUEPRINT_FORMAT: typeof BLUEPRINT_FORMAT
- BLUEPRINT_CODEC: typeof BLUEPRINT_CODEC
- TextDisplay: typeof TextDisplay
- VanillaItemDisplay: typeof VanillaItemDisplay
- VanillaBlockDisplay: typeof VanillaBlockDisplay
- }
- }
-}
diff --git a/src/blockbenchTypes.d.ts b/src/blockbenchTypes.d.ts
new file mode 100644
index 00000000..7d6845f5
--- /dev/null
+++ b/src/blockbenchTypes.d.ts
@@ -0,0 +1,72 @@
+import { BlockDisplay } from './blockbench-additions/outliner-elements/blockDisplay'
+import { ItemDisplay } from './blockbench-additions/outliner-elements/itemDisplay'
+import { TextDisplay } from './blockbench-additions/outliner-elements/textDisplay'
+import { defaultValues } from './blueprintSettings'
+import type { CommonDisplayConfig, LocatorConfig, Serialized } from './systems/node-configs'
+import { EasingKey } from './util/easing'
+import { isDataPackPath, isResourcePackPath } from './util/minecraftUtil'
+import { Syncable } from './util/stores'
+import { type Variant } from './variants'
+
+declare module 'three' {
+ interface Object3D {
+ isVanillaItemModel?: boolean
+ isVanillaBlockModel?: boolean
+ isTextDisplayText?: boolean
+ fix_scale?: THREE.Vector3
+ }
+}
+
+declare global {
+ interface ModelProject {
+ animated_java: { [T in keyof typeof defaultValues]: (typeof defaultValues)[T] }
+ last_used_export_namespace: string
+ visualBoundingBox?: THREE.LineSegments
+ pluginMode: Syncable
+ transparentTexture: Texture
+
+ showingInvalidCubeRotations: boolean
+
+ variants: Variant[]
+ textDisplays: TextDisplay[]
+ vanillaItemDisplays: ItemDisplay[]
+ vanillaBlockDisplays: BlockDisplay[]
+
+ loadingPromises?: Array>
+ }
+
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ interface _Animation {
+ excluded_nodes: CollectionItem[]
+ }
+
+ interface AnimationUndoCopy {
+ excluded_nodes: string[]
+ }
+
+ interface AnimationOptions {
+ excluded_nodes: string[]
+ }
+
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ interface _Keyframe {
+ easing?: EasingKey
+ easingArgs?: number[]
+ }
+
+ interface Group {
+ commonConfig: Serialized
+ }
+
+ interface Locator {
+ config: Serialized
+ }
+
+ interface Cube {
+ rotationInvalid: boolean
+ }
+
+ interface CubeFace {
+ lastVariant: Variant | undefined
+ }
+}
diff --git a/src/blueprintFormat.ts b/src/blueprintFormat.ts
index 24515ee3..c97e3825 100644
--- a/src/blueprintFormat.ts
+++ b/src/blueprintFormat.ts
@@ -8,7 +8,7 @@ import { events } from './util/events'
import { injectSvelteCompomponent } from './util/injectSvelteComponent'
import { sanitizePathName } from './util/minecraftUtil'
import { addProjectToRecentProjects } from './util/misc'
-import { Valuable } from './util/stores'
+import { Syncable } from './util/stores'
import { translate } from './util/translation'
import { Variant } from './variants'
@@ -595,7 +595,7 @@ export const BLUEPRINT_FORMAT = new Blockbench.ModelFormat({
Project.loadingPromises.push(
new Promise(resolve => {
requestAnimationFrame(() => {
- thisProject.pluginMode = new Valuable(
+ thisProject.pluginMode = new Syncable(
thisProject.animated_java.enable_plugin_mode
)
// Remove the default title
diff --git a/src/blueprintSettings.ts b/src/blueprintSettings.ts
index f9f4e5e6..08f370db 100644
--- a/src/blueprintSettings.ts
+++ b/src/blueprintSettings.ts
@@ -1,23 +1,27 @@
import { MinecraftVersion } from './systems/global'
-import { Valuable } from './util/stores'
+import { Syncable } from './util/stores'
-export type ExportMode = 'raw' | 'zip' | 'none'
+export type ExportMode = 'folder' | 'zip' | 'none'
+export type ExportEnvironment = 'vanilla' | 'plugin'
+export type DataPackAnimationSystem = 'functions' | 'storage'
export const defaultValues = {
- export_namespace: 'blueprint',
+ id: 'animated_java:new_blueprint',
+ tag_prefix: `aj.new_blueprint`,
+ auto_generate_tag_prefix: true,
show_bounding_box: false,
auto_bounding_box: true,
bounding_box: [48, 48],
// Export Settings
- enable_plugin_mode: false,
- resource_pack_export_mode: 'raw' as ExportMode,
- data_pack_export_mode: 'raw' as ExportMode,
+ environment: 'vanilla' as ExportEnvironment,
+ resource_pack_export_mode: 'folder' as ExportMode,
+ data_pack_export_mode: 'folder' as ExportMode,
target_minecraft_versions: ['1.21.4'] as MinecraftVersion[],
// Resource Pack Settings
- display_item: 'minecraft:white_dye',
custom_model_data_offset: 0,
enable_advanced_resource_pack_settings: false,
resource_pack: '',
+ display_item: 'minecraft:white_dye',
// Data Pack Settings
enable_advanced_data_pack_settings: false,
data_pack: '',
@@ -26,12 +30,12 @@ export const defaultValues = {
ticking_commands: '',
interpolation_duration: 1,
teleportation_duration: 1,
- use_storage_for_animation: false,
+ animation_system: 'functions' as DataPackAnimationSystem,
show_function_errors: true,
show_outdated_warning: true,
// Plugin Settings
- baked_animations: true,
+ bake_animations: false,
json_file: '',
}
-export const blueprintSettingErrors = new Valuable>({})
+export const blueprintSettingErrors = new Syncable>({})
diff --git a/src/components/blueprintSettingsDialog.svelte b/src/components/blueprintSettingsDialog.svelte
deleted file mode 100644
index 9cc9fda1..00000000
--- a/src/components/blueprintSettingsDialog.svelte
+++ /dev/null
@@ -1,881 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {#if !$autoBoundingBox}
-
- {/if}
-
-
-
-
-
-
-
- {#if $enablePluginMode}
-
-
-
-
-
- {:else}
-
-
-
-
-
-
- {#if $resourcePackExportMode !== 'none'}
-
-
-
-
-
-
- {#if $enableAdvancedResourcePackSettings}
-
- {translate('dialog.blueprint_settings.advanced_settings_warning')}
-
-
-
- {/if}
-
- {#if $resourcePackExportMode === 'raw'}
-
- {:else if $resourcePackExportMode === 'zip'}
-
- {/if}
- {/if}
-
- {#if $dataPackExportMode !== 'none'}
-
-
- {#if $dataPackExportMode === 'raw'}
-
- {:else if $dataPackExportMode === 'zip'}
-
- {/if}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/if}
- {/if}
-
-
-{#if showSupportMePopup}
-
-{/if}
-
-
diff --git a/src/components/boneConfigDialog.svelte b/src/components/boneConfigDialog.svelte
deleted file mode 100644
index 38cfef2e..00000000
--- a/src/components/boneConfigDialog.svelte
+++ /dev/null
@@ -1,326 +0,0 @@
-
-
-
-
-
-
- {translate('dialog.bone_config.selected_variant', variant.displayName)}
-
- {#if variant.isDefault}
-
- {translate('dialog.bone_config.default_variant_subtitle')}
-
- {:else}
-
- {translate('dialog.bone_config.selected_variant_subtitle')}
-
- {/if}
-
- {#if pluginModeEnabled}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {:else}
-
-
-
-
-
-
-
-
- {#if $useNBT}
-
- {translate('dialog.bone_config.use_nbt.use_nbt_warning')}
-
-
- {:else}
-
-
-
-
-
-
- {#if $overrideGlowColor}
-
- {/if}
-
-
-
-
-
-
-
- {#if $overrideBrightness}
-
- {/if}
-
-
-
-
- {/if}
- {/if}
-
-
-
diff --git a/src/components/dialogItems/dialogItemUtil.d.ts b/src/components/dialogItems/dialogItemUtil.d.ts
deleted file mode 100644
index 4ff027dc..00000000
--- a/src/components/dialogItems/dialogItemUtil.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-type DialogItemValueChecker =
- | ((value: Value) => { type: string; message: string })
- | undefined
-
-type CollectionItem = { icon?: string; name: string; value: string }
diff --git a/src/components/dialogItems/numberSlider.svelte b/src/components/dialogItems/numberSlider.svelte
deleted file mode 100644
index 9195d5f7..00000000
--- a/src/components/dialogItems/numberSlider.svelte
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
diff --git a/src/components/keyframePanels/variantKeyframePanel.svelte b/src/components/keyframePanels/variantKeyframePanel.svelte
deleted file mode 100644
index ab3e6e00..00000000
--- a/src/components/keyframePanels/variantKeyframePanel.svelte
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
- {translate('panel.keyframe.variant.title')}
-
-
-
-
-
diff --git a/src/components/unexpectedErrorDialog.svelte b/src/components/unexpectedErrorDialog.svelte
deleted file mode 100644
index e70000e3..00000000
--- a/src/components/unexpectedErrorDialog.svelte
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
-
-
-
-
-
{@html pickRandomFlavorQuote()}
-
-
-
- {@html translate(
- 'dialog.unexpected_error.paragraph',
- 'Discord ',
- 'Github ',
- )}
-
-
-
-
-
-
-
-
-
diff --git a/src/components/vanillaBlockDisplayElementPanel.svelte b/src/components/vanillaBlockDisplayElementPanel.svelte
deleted file mode 100644
index 9d35b5f1..00000000
--- a/src/components/vanillaBlockDisplayElementPanel.svelte
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
- {translate('panel.vanilla_block_display.title')}
-
-
-
-
-
- {$error}
-
-
-
diff --git a/src/components/vanillaItemDisplayElementPanel.svelte b/src/components/vanillaItemDisplayElementPanel.svelte
deleted file mode 100644
index be37fc01..00000000
--- a/src/components/vanillaItemDisplayElementPanel.svelte
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
- {translate('panel.vanilla_item_display.title')}
-
-
-
-
-
- {$error}
-
-
-
diff --git a/src/global.d.ts b/src/global.d.ts
index e3c0641c..4b4f444d 100644
--- a/src/global.d.ts
+++ b/src/global.d.ts
@@ -1,4 +1,5 @@
-///
+////
+///
declare module '*.png' {
const value: string
@@ -47,3 +48,8 @@ declare module '*.molang' {
declare module 'fflate/browser' {
export * from 'fflate'
}
+
+/**
+ * Display entity billboard mode
+ */
+type BillboardMode = 'fixed' | 'vertical' | 'horizontal' | 'center'
diff --git a/src/index.ts b/src/index.ts
index db3e1408..67668fbb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,55 +1,63 @@
import { PACKAGE } from './constants'
-import { events } from './util/events'
import './util/translation'
// Blueprint Format
-import './blueprintFormat'
+import './blockbench-additions/model-formats/ajblueprint'
// Interface
-import './interface'
+import './ui'
// Blockbench Mods
-import './mods'
-// Outliner
-import './outliner/textDisplay'
-import './outliner/vanillaItemDisplay'
-import './outliner/vanillaBlockDisplay'
+import './blockbench-mods'
+// Blockbench Additions
+import './blockbench-additions'
// Compilers
-import datapackCompiler from './systems/datapackCompiler'
+import datapackCompiler from './systems/datapack-compiler'
// Minecraft Systems
-import './systems/minecraft/versionManager'
-import './systems/minecraft/registryManager'
-import './systems/minecraft/blockstateManager'
-import './systems/minecraft/assetManager'
-import './systems/minecraft/fontManager'
+import './systems/minecraft-temp/assetManager'
+import './systems/minecraft-temp/blockstateManager'
+import './systems/minecraft-temp/fontManager'
+import './systems/minecraft-temp/registryManager'
+import './systems/minecraft-temp/versionManager'
// Misc imports
-import { Variant } from './variants'
-import './systems/minecraft/registryManager'
-import { MINECRAFT_REGISTRY } from './systems/minecraft/registryManager'
-import resourcepackCompiler from './systems/resourcepackCompiler'
-import { openExportProgressDialog } from './interface/dialog/exportProgress'
-import { isDataPackPath, isResourcePackPath, parseResourcePackPath } from './util/minecraftUtil'
+import { BLUEPRINT_CODEC, BLUEPRINT_FORMAT } from './blockbench-additions/model-formats/ajblueprint'
+import {
+ BlockDisplay,
+ debugBlockState,
+ debugBlocks,
+} from './blockbench-additions/outliner-elements/blockDisplay'
+import { TextDisplay } from './blockbench-additions/outliner-elements/textDisplay'
import { blueprintSettingErrors } from './blueprintSettings'
-import { openUnexpectedErrorDialog } from './interface/dialog/unexpectedError'
-import { BLUEPRINT_CODEC, BLUEPRINT_FORMAT } from './blueprintFormat'
-import { TextDisplay } from './outliner/textDisplay'
-import { getLatestVersionClientDownloadUrl } from './systems/minecraft/assetManager'
-import { getVanillaFont } from './systems/minecraft/fontManager'
-import * as assetManager from './systems/minecraft/assetManager'
-import * as itemModelManager from './systems/minecraft/itemModelManager'
-import * as blockModelManager from './systems/minecraft/blockModelManager'
-import { VanillaItemDisplay } from './outliner/vanillaItemDisplay'
-import { VanillaBlockDisplay, debugBlockState, debugBlocks } from './outliner/vanillaBlockDisplay'
-import { BLOCKSTATE_REGISTRY } from './systems/minecraft/blockstateManager'
-import { exportProject } from './systems/exporter'
-import { openBlueprintLoadingDialog } from './interface/popup/blueprintLoading'
-import { openInstallPopup } from './interface/popup/installed'
import { cleanupExportedFiles } from './systems/cleaner'
-import mcbFiles from './systems/datapackCompiler/mcbFiles'
-import { openChangelogDialog } from './interface/changelogDialog'
-import { checkForIncompatabilities } from './interface/popup/incompatabilityPopup'
+import mcbFiles from './systems/datapack-compiler/versions'
+import { exportProject } from './systems/exporter'
+import * as assetManager from './systems/minecraft-temp/assetManager'
+import { getLatestVersionClientDownloadUrl } from './systems/minecraft-temp/assetManager'
+import * as blockModelManager from './systems/minecraft-temp/blockModelManager'
+import { BLOCKSTATE_REGISTRY } from './systems/minecraft-temp/blockstateManager'
+import { getVanillaFont } from './systems/minecraft-temp/fontManager'
+import * as itemModelManager from './systems/minecraft-temp/itemModelManager'
+import './systems/minecraft-temp/registryManager'
+import { MINECRAFT_REGISTRY } from './systems/minecraft-temp/registryManager'
+import resourcepackCompiler from './systems/resourcepack-compiler'
+import { openChangelogDialog } from './ui/dialogs/changelog'
+import { openExportProgressDialog } from './ui/dialogs/export-progress'
+import { openUnexpectedErrorDialog } from './ui/dialogs/unexpected-error'
+import { checkForIncompatabilities } from './ui/popups/incompatability'
+import { openInstallPopup } from './ui/popups/installed'
+import {
+ isDataPackPath,
+ isResourcePackPath,
+ parseResourceLocation,
+ parseResourcePackPath,
+} from './util/minecraftUtil'
+import { Variant } from './variants'
+
+import { ItemDisplay } from './blockbench-additions/outliner-elements/itemDisplay'
+import registerPlugin from './plugin'
+import { createBlockbenchMod } from './util/moddingTools'
-// @ts-ignore
-globalThis.AnimatedJava = {
+const PLUGIN_API = {
API: {
parseResourcePackPath,
+ parseResourceLocation,
datapackCompiler,
resourcepackCompiler,
Variant,
@@ -67,13 +75,12 @@ globalThis.AnimatedJava = {
assetManager,
itemModelManager,
blockModelManager,
- VanillaItemDisplay,
- VanillaBlockDisplay,
+ ItemDisplay,
+ BlockDisplay,
debugBlocks,
debugBlockState,
BLOCKSTATE_REGISTRY,
exportProject,
- openBlueprintLoadingDialog,
openInstallPopup,
removeCubesAssociatedWithTexture(texture: Texture) {
const cubes = Cube.all.filter(cube =>
@@ -91,6 +98,23 @@ globalThis.AnimatedJava = {
},
}
+declare global {
+ // eslint-disable-next-line @typescript-eslint/naming-convention, no-var
+ var AnimatedJava: typeof PLUGIN_API
+}
+
+createBlockbenchMod(
+ `${PACKAGE.name}:global/api`,
+ undefined,
+ () => {
+ globalThis.AnimatedJava = PLUGIN_API
+ },
+ () => {
+ // @ts-expect-error: AnimatedJava type is not optional, but we want to delete it when uninstalling
+ delete globalThis.AnimatedJava
+ }
+)
+
requestAnimationFrame(() => {
if (checkForIncompatabilities()) return
@@ -101,38 +125,4 @@ requestAnimationFrame(() => {
}
})
-// Uninstall events
-events.EXTRACT_MODS.subscribe(() => {
- // @ts-ignore
- globalThis.AnimatedJava = undefined
-})
-
-BBPlugin.register(PACKAGE.name, {
- title: PACKAGE.title,
- author: PACKAGE.author.name,
- description: PACKAGE.description,
- icon: 'icon.svg',
- variant: 'desktop',
- version: PACKAGE.version,
- min_version: PACKAGE.min_blockbench_version,
- tags: ['Minecraft: Java Edition', 'Animation', 'Display Entities'],
- await_loading: true,
- onload() {
- events.LOAD.dispatch()
- },
- onunload() {
- events.UNLOAD.dispatch()
- },
- oninstall() {
- events.INSTALL.dispatch()
- openInstallPopup()
- },
- onuninstall() {
- events.UNINSTALL.dispatch()
- Blockbench.showMessageBox({
- title: 'Animated Java has Been Uninstalled!',
- message: 'In order to fully uninstall Animated Java, please restart Blockbench.',
- buttons: ['OK'],
- })
- },
-})
+registerPlugin()
diff --git a/src/interface/animatedJavaBarItem.ts b/src/interface/animatedJavaBarItem.ts
deleted file mode 100644
index b09d3aa2..00000000
--- a/src/interface/animatedJavaBarItem.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import AnimatedJavaIcon from '../assets/animated_java_icon.svg'
-import { BLUEPRINT_FORMAT } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { cleanupExportedFiles } from '../systems/cleaner'
-import { exportProject } from '../systems/exporter'
-import { createAction, createBarMenu } from '../util/moddingTools'
-import { translate } from '../util/translation'
-import { openAboutDialog } from './dialog/about'
-import { openBlueprintSettingsDialog } from './dialog/blueprintSettings'
-import { openChangelogDialog } from './changelogDialog'
-
-function createIconImg() {
- const IMG = document.createElement('img')
- Object.assign(IMG, {
- src: AnimatedJavaIcon,
- width: 16,
- height: 16,
- })
- Object.assign(IMG.style, {
- position: 'relative',
- top: '2px',
- borderRadius: '2px',
- marginRight: '6px',
- boxShadow: '1px 1px 1px #000000aa',
- })
- return IMG
-}
-const MENU_ID = `${PACKAGE.name}:menu` as `animated_java:menu`
-const BLOCKBENCH_MENU_BAR = document.querySelector('#menu_bar') as HTMLDivElement
-export const MENU = createBarMenu(MENU_ID, [], () => Format === BLUEPRINT_FORMAT) as BarMenu & {
- label: HTMLDivElement
-}
-MENU.label.style.display = 'inline-block'
-MENU.label.innerHTML = 'Animated Java'
-MENU.label.prepend(createIconImg())
-BLOCKBENCH_MENU_BAR.appendChild(MENU.label)
-
-MenuBar.addAction(
- createAction(`${PACKAGE.name}:about`, {
- icon: 'info',
- category: 'animated_java',
- name: translate('action.open_about.name'),
- click() {
- openAboutDialog()
- },
- }),
- MENU.id
-)
-
-MenuBar.addAction(
- createAction(`${PACKAGE.name}:documentation`, {
- icon: 'find_in_page',
- category: 'animated_java',
- name: translate('action.open_documentation.name'),
- click() {
- Blockbench.openLink('https://animated-java.dev/docs')
- },
- }),
- MENU.id
-)
-
-MenuBar.addAction(
- createAction(`${PACKAGE.name}:changelog`, {
- icon: 'history',
- category: 'animated_java',
- name: translate('action.open_changelog.name'),
- click() {
- openChangelogDialog()
- },
- }),
- MENU.id
-)
-
-MENU.structure.push(new MenuSeparator())
-
-MenuBar.addAction(
- createAction(`${PACKAGE.name}:blueprint_settings`, {
- icon: 'settings',
- category: 'animated_java',
- name: translate('action.open_blueprint_settings.name'),
- condition() {
- return Format === BLUEPRINT_FORMAT
- },
- click() {
- openBlueprintSettingsDialog()
- },
- }),
- MENU.id
-)
-
-MenuBar.menus[MENU_ID].structure.push({
- id: 'animated_java:extract-open',
- name: translate('action.extract.name'),
- icon: 'fa-trash-can',
- searchable: false,
- children: [],
- condition() {
- return Format === BLUEPRINT_FORMAT
- },
-})
-
-MenuBar.addAction(
- createAction(`${PACKAGE.name}:extract`, {
- icon: 'fa-trash-can',
- category: 'animated_java',
- name: translate('action.extract.confirm'),
- condition() {
- return Format === BLUEPRINT_FORMAT
- },
- click() {
- void cleanupExportedFiles()
- },
- }),
- MENU_ID + '.animated_java:extract-open'
-)
-
-MenuBar.addAction(
- createAction(`${PACKAGE.name}:export`, {
- icon: 'insert_drive_file',
- category: 'animated_java',
- name: translate('action.export.name'),
- condition() {
- return Format === BLUEPRINT_FORMAT
- },
- click() {
- void exportProject()
- },
- }),
- MENU.id
-)
diff --git a/src/interface/dialog/blueprintSettings.ts b/src/interface/dialog/blueprintSettings.ts
deleted file mode 100644
index ba5fad0a..00000000
--- a/src/interface/dialog/blueprintSettings.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { updateBoundingBox } from '../../blueprintFormat'
-import { defaultValues, ExportMode } from '../../blueprintSettings'
-import BlueprintSettingsDialogSvelteComponent from '../../components/blueprintSettingsDialog.svelte'
-import { PACKAGE } from '../../constants'
-import { MinecraftVersion } from '../../systems/global'
-import { sanitizePathName } from '../../util/minecraftUtil'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
-
-function getSettings() {
- return {
- blueprintName: new Valuable(Project!.name, value => {
- if (!value) {
- return 'My Blueprint'
- }
- return value
- }),
- textureSizeX: new Valuable(Project!.texture_width),
- textureSizeY: new Valuable(Project!.texture_height),
- showBoundingBox: new Valuable(Project!.animated_java.show_bounding_box),
- autoBoundingBox: new Valuable(Project!.animated_java.auto_bounding_box),
- boundingBoxX: new Valuable(Project!.animated_java.bounding_box[0]),
- boundingBoxY: new Valuable(Project!.animated_java.bounding_box[1]),
- // Export Settings
- enablePluginMode: new Valuable(Project!.animated_java.enable_plugin_mode),
- exportNamespace: new Valuable(Project!.animated_java.export_namespace, value => {
- if (!value) {
- return defaultValues.export_namespace
- }
- return sanitizePathName(value)
- }),
- resourcePackExportMode: new Valuable(
- Project!.animated_java.resource_pack_export_mode as string
- ),
- dataPackExportMode: new Valuable(Project!.animated_java.data_pack_export_mode as string),
- targetMinecraftVersions: new Valuable(Project!.animated_java.target_minecraft_versions),
- // Resource Pack Settings
- displayItem: new Valuable(Project!.animated_java.display_item, value => {
- if (!value) {
- return defaultValues.display_item
- }
- return value
- }),
- customModelDataOffset: new Valuable(Project!.animated_java.custom_model_data_offset),
- enableAdvancedResourcePackSettings: new Valuable(
- Project!.animated_java.enable_advanced_resource_pack_settings
- ),
- resourcePack: new Valuable(Project!.animated_java.resource_pack),
- // Data Pack Settings
- enableAdvancedDataPackSettings: new Valuable(
- Project!.animated_java.enable_advanced_data_pack_settings
- ),
- dataPack: new Valuable(Project!.animated_java.data_pack),
- summonCommands: new Valuable(Project!.animated_java.summon_commands),
- removeCommands: new Valuable(Project!.animated_java.remove_commands),
- tickingCommands: new Valuable(Project!.animated_java.ticking_commands),
- interpolationDuration: new Valuable(Project!.animated_java.interpolation_duration),
- teleportationDuration: new Valuable(Project!.animated_java.teleportation_duration),
- useStorageForAnimation: new Valuable(Project!.animated_java.use_storage_for_animation),
- showFunctionErrors: new Valuable(Project!.animated_java.show_function_errors),
- showOutdatedWarning: new Valuable(Project!.animated_java.show_outdated_warning),
- // Plugin Settings
- bakedAnimations: new Valuable(Project!.animated_java.baked_animations),
- jsonFile: new Valuable(Project!.animated_java.json_file),
- }
-}
-
-function setSettings(settings: ReturnType) {
- if (!Project) return
- Project.name = settings.blueprintName.get()
-
- setProjectResolution(settings.textureSizeX.get(), settings.textureSizeY.get(), true)
-
- Project.animated_java.show_bounding_box = settings.showBoundingBox.get()
- Project.animated_java.auto_bounding_box = settings.autoBoundingBox.get()
- Project.animated_java.bounding_box = [settings.boundingBoxX.get(), settings.boundingBoxY.get()]
-
- // Export Settings
- Project.animated_java.enable_plugin_mode = settings.enablePluginMode.get()
- Project.pluginMode.set(settings.enablePluginMode.get()) // Required to update the project title.
- Project.animated_java.export_namespace = settings.exportNamespace.get()
- Project.animated_java.resource_pack_export_mode =
- settings.resourcePackExportMode.get() as ExportMode
- Project.animated_java.data_pack_export_mode = settings.dataPackExportMode.get() as ExportMode
- Project.animated_java.target_minecraft_versions =
- settings.targetMinecraftVersions.get() as MinecraftVersion[]
- // Resource Pack Settings
- Project.animated_java.display_item = settings.displayItem.get()
- Project.animated_java.custom_model_data_offset = settings.customModelDataOffset.get()
- Project.animated_java.enable_advanced_resource_pack_settings =
- settings.enableAdvancedResourcePackSettings.get()
- Project.animated_java.resource_pack = settings.resourcePack.get()
- // Data Pack Settings
- Project.animated_java.enable_advanced_data_pack_settings =
- settings.enableAdvancedDataPackSettings.get()
- Project.animated_java.data_pack = settings.dataPack.get()
- Project.animated_java.summon_commands = settings.summonCommands.get()
- Project.animated_java.remove_commands = settings.removeCommands.get()
- Project.animated_java.ticking_commands = settings.tickingCommands.get()
- Project.animated_java.interpolation_duration = settings.interpolationDuration.get()
- Project.animated_java.teleportation_duration = settings.teleportationDuration.get()
- Project.animated_java.use_storage_for_animation = settings.useStorageForAnimation.get()
- Project.animated_java.show_function_errors = settings.showFunctionErrors.get()
- Project.animated_java.show_outdated_warning = settings.showOutdatedWarning.get()
- // Plugin Settings
- Project.animated_java.baked_animations = settings.bakedAnimations.get()
- Project.animated_java.json_file = settings.jsonFile.get()
- console.log('Successfully saved project settings', Project)
-}
-
-export function openBlueprintSettingsDialog() {
- if (!Project) return
-
- const settings = getSettings()
- return new SvelteDialog({
- id: `${PACKAGE.name}:blueprintSettingsDialog`,
- title: translate('dialog.blueprint_settings.title'),
- width: 700,
- component: BlueprintSettingsDialogSvelteComponent,
- props: settings,
- preventKeybinds: true,
- onConfirm() {
- setSettings(settings)
- updateBoundingBox()
- },
- }).show()
-}
diff --git a/src/interface/dialog/boneConfig.ts b/src/interface/dialog/boneConfig.ts
deleted file mode 100644
index 94dacfa5..00000000
--- a/src/interface/dialog/boneConfig.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import { BLUEPRINT_FORMAT } from '../../blueprintFormat'
-import { BoneConfig } from '../../nodeConfigs'
-import BoneConfigDialogSvelteComponent from '../../components/boneConfigDialog.svelte'
-import { PACKAGE } from '../../constants'
-import { createAction } from '../../util/moddingTools'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
-import { Variant } from '../../variants'
-
-// TODO: These should probably be part of the BoneConfig class
-function propagateInheritanceUp(group: Group, config: BoneConfig, variant?: string): void {
- // Recurse to the topmost parent that doesn't have inherit_settings enabled, then inherit down from there
- if (group.parent instanceof Group) {
- const parentConfig = variant
- ? group.parent.configs.variants[variant]
- : group.parent.configs.default
- if (parentConfig) {
- const parentBoneConfig = BoneConfig.fromJSON(parentConfig)
- if (parentConfig.inherit_settings) {
- propagateInheritanceUp(group.parent, parentBoneConfig, variant)
- }
- config.inheritFrom(parentBoneConfig)
- if (variant) group.configs.variants[variant] = config.toJSON()
- else group.configs.default = config.toJSON()
- }
- }
-}
-
-function propagateInheritanceDown(group: Group, config: BoneConfig, variant?: string) {
- for (const child of group.children) {
- if (!(child instanceof Group)) continue
- const childConfig = variant ? child.configs.variants[variant] : child.configs.default
- if (childConfig && childConfig.inherit_settings) {
- const childBoneConfig = BoneConfig.fromJSON(childConfig)
- childBoneConfig.inheritFrom(config)
- if (variant) child.configs.variants[variant] = childBoneConfig.toJSON()
- else child.configs.default = childBoneConfig.toJSON()
- propagateInheritanceDown(child, childBoneConfig, variant)
- }
- }
-}
-
-export function openBoneConfigDialog(bone: Group) {
- // Blockbench's JSON stringifier doesn't handle custom toJSON functions, so I'm storing the config JSON in the bone instead of the actual BoneConfig object
- let boneConfigJSON = (bone.configs.default ??= new BoneConfig().toJSON())
- let parentConfigJSON =
- bone.parent instanceof Group
- ? (bone.parent.configs.default ??= new BoneConfig().toJSON())
- : undefined
-
- if (Variant.selected && !Variant.selected.isDefault) {
- // Get the variant's config, or create a new one if it doesn't exist
- boneConfigJSON = bone.configs.variants[Variant.selected.uuid] ??= new BoneConfig().toJSON()
- parentConfigJSON =
- bone.parent instanceof Group
- ? (bone.parent.configs.variants[Variant.selected.uuid] ??=
- new BoneConfig().toJSON())
- : undefined
- }
-
- const parentConfig = parentConfigJSON
- ? BoneConfig.fromJSON(parentConfigJSON)
- : BoneConfig.getDefault()
-
- const oldConfig = BoneConfig.fromJSON(boneConfigJSON)
-
- const customName = new Valuable(oldConfig.customName)
- const customNameVisible = new Valuable(oldConfig.customNameVisible)
- const billboard = new Valuable(oldConfig.billboard as string)
- const overrideBrightness = new Valuable(oldConfig.overrideBrightness)
- const brightnessOverride = new Valuable(oldConfig.brightnessOverride)
- const enchanted = new Valuable(oldConfig.enchanted)
- const glowing = new Valuable(oldConfig.glowing)
- const overrideGlowColor = new Valuable(oldConfig.overrideGlowColor)
- const glowColor = new Valuable(oldConfig.glowColor)
- const inheritSettings = new Valuable(oldConfig.inheritSettings)
- const invisible = new Valuable(oldConfig.invisible)
- const nbt = new Valuable(oldConfig.nbt)
- const shadowRadius = new Valuable(oldConfig.shadowRadius)
- const shadowStrength = new Valuable(oldConfig.shadowStrength)
- const useNBT = new Valuable(oldConfig.useNBT)
-
- new SvelteDialog({
- id: `${PACKAGE.name}:boneConfig`,
- title: translate('dialog.bone_config.title'),
- width: 400,
- component: BoneConfigDialogSvelteComponent,
- props: {
- variant: Variant.selected!,
- customName,
- customNameVisible,
- billboard,
- overrideBrightness,
- brightnessOverride,
- enchanted,
- glowing,
- overrideGlowColor,
- glowColor,
- inheritSettings,
- invisible,
- nbt,
- shadowRadius,
- shadowStrength,
- useNBT,
- },
- preventKeybinds: true,
- onConfirm() {
- const newConfig = new BoneConfig()
-
- newConfig.customName = customName.get()
- newConfig.customNameVisible = customNameVisible.get()
- newConfig.billboard = billboard.get() as any
- newConfig.overrideBrightness = overrideBrightness.get()
- newConfig.brightnessOverride = brightnessOverride.get()
- newConfig.enchanted = enchanted.get()
- newConfig.glowing = glowing.get()
- newConfig.overrideGlowColor = overrideGlowColor.get()
- newConfig.glowColor = glowColor.get()
- newConfig.inheritSettings = inheritSettings.get()
- newConfig.invisible = invisible.get()
- newConfig.nbt = nbt.get()
- newConfig.shadowRadius = shadowRadius.get()
- newConfig.shadowStrength = shadowStrength.get()
- newConfig.useNBT = useNBT.get()
-
- // Remove properties that are the same as the parent's
- newConfig.customName === parentConfig.customName && (newConfig.customName = undefined)
- newConfig.customNameVisible === parentConfig.customNameVisible &&
- (newConfig.customNameVisible = undefined)
- newConfig.billboard === parentConfig.billboard && (newConfig.billboard = undefined)
- newConfig.overrideBrightness === parentConfig.overrideBrightness &&
- (newConfig.overrideBrightness = undefined)
- newConfig.brightnessOverride === parentConfig.brightnessOverride &&
- (newConfig.brightnessOverride = undefined)
- newConfig.enchanted === parentConfig.enchanted && (newConfig.enchanted = undefined)
- newConfig.glowing === parentConfig.glowing && (newConfig.glowing = undefined)
- newConfig.overrideGlowColor === parentConfig.overrideGlowColor &&
- (newConfig.overrideGlowColor = undefined)
- newConfig.glowColor === parentConfig.glowColor && (newConfig.glowColor = undefined)
- newConfig.invisible === parentConfig.invisible && (newConfig.invisible = undefined)
- newConfig.nbt === parentConfig.nbt && (newConfig.nbt = undefined)
- newConfig.shadowRadius === parentConfig.shadowRadius &&
- (newConfig.shadowRadius = undefined)
- newConfig.shadowStrength === parentConfig.shadowStrength &&
- (newConfig.shadowStrength = undefined)
- newConfig.useNBT === parentConfig.useNBT && (newConfig.useNBT = undefined)
-
- if (newConfig.checkIfEqual(BoneConfig.fromJSON(bone.configs.default))) {
- // Don't save the variant config if it's the same as the default
- delete bone.configs.variants[Variant.selected!.uuid]
- return
- }
-
- if (Variant.selected && !Variant.selected.isDefault) {
- if (newConfig.inheritSettings) {
- propagateInheritanceUp(bone, newConfig, Variant.selected.uuid)
- }
- bone.configs.variants[Variant.selected.uuid] = newConfig.toJSON()
- propagateInheritanceDown(bone, newConfig, Variant.selected.uuid)
- } else {
- if (newConfig.inheritSettings) {
- propagateInheritanceUp(bone, newConfig)
- }
- bone.configs.default = newConfig.toJSON()
- propagateInheritanceDown(bone, newConfig)
- }
- },
- }).show()
-}
-
-export const BONE_CONFIG_ACTION = createAction(`${PACKAGE.name}:bone_config`, {
- icon: 'settings',
- name: translate('action.open_bone_config.name'),
- condition: () => Format === BLUEPRINT_FORMAT,
- click: () => {
- if (!Group.first_selected) return
- openBoneConfigDialog(Group.first_selected)
- },
-})
diff --git a/src/interface/dialog/changelog.ts b/src/interface/dialog/changelog.ts
deleted file mode 100644
index de6aa3b6..00000000
--- a/src/interface/dialog/changelog.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import ChangelogDialog from '../../components/changelogDialog.svelte'
-import { PACKAGE } from '../../constants'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
-
-export const DIALOG_ID = `${PACKAGE.name}:animationPropertiesDialog`
-
-export function openChangelogDialog() {
- new SvelteDialog({
- id: DIALOG_ID,
- title: translate('dialog.changelog_dialog.title'),
- width: 600,
- component: ChangelogDialog,
- props: {},
- buttons: ['OK!'],
- preventKeybinds: true,
- }).show()
-}
diff --git a/src/interface/dialog/textDisplayConfig.ts b/src/interface/dialog/textDisplayConfig.ts
deleted file mode 100644
index 2042ed9e..00000000
--- a/src/interface/dialog/textDisplayConfig.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import { isCurrentFormat } from '../../blueprintFormat'
-import { TextDisplayConfig } from '../../nodeConfigs'
-import { PACKAGE } from '../../constants'
-import { createAction } from '../../util/moddingTools'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
-import { Variant } from '../../variants'
-import TextDisplayConfigDialog from '../../components/textDisplayConfigDialog.svelte'
-import { TextDisplay } from '../../outliner/textDisplay'
-
-export function openBoneConfigDialog(bone: TextDisplay) {
- // Blockbench's JSON stringifier doesn't handle custom toJSON functions, so I'm storing the config JSON in the bone instead of the actual BoneConfig object
- const oldConfig = TextDisplayConfig.fromJSON((bone.config ??= new TextDisplayConfig().toJSON()))
-
- const billboard = new Valuable(oldConfig.billboard as string)
- const overrideBrightness = new Valuable(oldConfig.overrideBrightness)
- const brightnessOverride = new Valuable(oldConfig.brightnessOverride)
- const glowing = new Valuable(oldConfig.glowing)
- const overrideGlowColor = new Valuable(oldConfig.overrideGlowColor)
- const glowColor = new Valuable(oldConfig.glowColor)
- const invisible = new Valuable(oldConfig.invisible)
- const nbt = new Valuable(oldConfig.nbt)
- const shadowRadius = new Valuable(oldConfig.shadowRadius)
- const shadowStrength = new Valuable(oldConfig.shadowStrength)
- const useNBT = new Valuable(oldConfig.useNBT)
-
- new SvelteDialog({
- id: `${PACKAGE.name}:textDisplayConfigDialog`,
- title: translate('dialog.text_display_config.title'),
- width: 400,
- component: TextDisplayConfigDialog,
- props: {
- variant: Variant.selected,
- billboard,
- overrideBrightness,
- brightnessOverride,
- glowing,
- overrideGlowColor,
- glowColor,
- invisible,
- nbt,
- shadowRadius,
- shadowStrength,
- useNBT,
- },
- preventKeybinds: true,
- onConfirm() {
- const newConfig = new TextDisplayConfig()
-
- newConfig.billboard = billboard.get() as any
- newConfig.overrideBrightness = overrideBrightness.get()
- newConfig.brightnessOverride = brightnessOverride.get()
- newConfig.glowing = glowing.get()
- newConfig.overrideGlowColor = overrideGlowColor.get()
- newConfig.glowColor = glowColor.get()
- newConfig.invisible = invisible.get()
- newConfig.nbt = nbt.get()
- newConfig.shadowRadius = shadowRadius.get()
- newConfig.shadowStrength = shadowStrength.get()
- newConfig.useNBT = useNBT.get()
-
- const defaultConfig = TextDisplayConfig.getDefault()
-
- newConfig.billboard === defaultConfig.billboard && (newConfig.billboard = undefined)
- newConfig.overrideBrightness === defaultConfig.overrideBrightness &&
- (newConfig.overrideBrightness = undefined)
- newConfig.brightnessOverride === defaultConfig.brightnessOverride &&
- (newConfig.brightnessOverride = undefined)
- newConfig.glowing === defaultConfig.glowing && (newConfig.glowing = undefined)
- newConfig.overrideGlowColor === defaultConfig.overrideGlowColor &&
- (newConfig.overrideGlowColor = undefined)
- newConfig.glowColor === defaultConfig.glowColor && (newConfig.glowColor = undefined)
- newConfig.invisible === defaultConfig.invisible && (newConfig.invisible = undefined)
- newConfig.nbt === defaultConfig.nbt && (newConfig.nbt = undefined)
- newConfig.shadowRadius === defaultConfig.shadowRadius &&
- (newConfig.shadowRadius = undefined)
- newConfig.shadowStrength === defaultConfig.shadowStrength &&
- (newConfig.shadowStrength = undefined)
- newConfig.useNBT === defaultConfig.useNBT && (newConfig.useNBT = undefined)
-
- bone.config = newConfig.toJSON()
- },
- }).show()
-}
-
-export const TEXT_DISPLAY_CONFIG_ACTION = createAction(`${PACKAGE.name}:text_display_config`, {
- icon: 'settings',
- name: translate('action.open_text_display_config.name'),
- condition: () => isCurrentFormat(),
- click: () => {
- if (TextDisplay.selected.length === 0) return
- openBoneConfigDialog(TextDisplay.selected[0])
- },
-})
diff --git a/src/interface/dialog/unexpectedError.ts b/src/interface/dialog/unexpectedError.ts
deleted file mode 100644
index c15aa402..00000000
--- a/src/interface/dialog/unexpectedError.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import UnexpectedErrorDialog from '../../components/unexpectedErrorDialog.svelte'
-import { PACKAGE } from '../../constants'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
-
-export function openUnexpectedErrorDialog(error: Error) {
- new SvelteDialog({
- id: `${PACKAGE.name}:unexpectedError`,
- title: translate('dialog.unexpected_error.title'),
- width: 600,
- component: UnexpectedErrorDialog,
- props: {
- error,
- },
- preventKeybinds: true,
- buttons: [translate('dialog.unexpected_error.close_button')],
- }).show()
-}
diff --git a/src/interface/dialog/vanillaItemDisplayConfig.ts b/src/interface/dialog/vanillaItemDisplayConfig.ts
deleted file mode 100644
index 1cf0ae90..00000000
--- a/src/interface/dialog/vanillaItemDisplayConfig.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { isCurrentFormat } from '../../blueprintFormat'
-import { BoneConfig } from '../../nodeConfigs'
-import { PACKAGE } from '../../constants'
-import { createAction } from '../../util/moddingTools'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
-import { Variant } from '../../variants'
-import { VanillaItemDisplay } from '../../outliner/vanillaItemDisplay'
-import VanillaItemDisplayConfigDialog from '../../components/vanillaItemDisplayConfigDialog.svelte'
-
-export function openVanillaItemDisplayConfigDialog(display: VanillaItemDisplay) {
- // Blockbench's JSON stringifier doesn't handle custom toJSON functions, so I'm storing the config JSON in the bone instead of the actual BoneConfig object
- const oldConfig = BoneConfig.fromJSON((display.config ??= new BoneConfig().toJSON()))
-
- const customName = new Valuable(oldConfig.customName)
- const customNameVisible = new Valuable(oldConfig.customNameVisible)
- const billboard = new Valuable(oldConfig.billboard as string)
- const overrideBrightness = new Valuable(oldConfig.overrideBrightness)
- const brightnessOverride = new Valuable(oldConfig.brightnessOverride)
- const glowing = new Valuable(oldConfig.glowing)
- const overrideGlowColor = new Valuable(oldConfig.overrideGlowColor)
- const glowColor = new Valuable(oldConfig.glowColor)
- const invisible = new Valuable(oldConfig.invisible)
- const nbt = new Valuable(oldConfig.nbt)
- const shadowRadius = new Valuable(oldConfig.shadowRadius)
- const shadowStrength = new Valuable(oldConfig.shadowStrength)
- const useNBT = new Valuable(oldConfig.useNBT)
-
- new SvelteDialog({
- id: `${PACKAGE.name}:vanillaItemDisplayConfigDialog`,
- title: translate('dialog.vanilla_item_display_config.title'),
- width: 400,
- component: VanillaItemDisplayConfigDialog,
- props: {
- variant: Variant.selected,
- customName,
- customNameVisible,
- billboard,
- overrideBrightness,
- brightnessOverride,
- glowing,
- overrideGlowColor,
- glowColor,
- invisible,
- nbt,
- shadowRadius,
- shadowStrength,
- useNBT,
- },
- preventKeybinds: true,
- onConfirm() {
- const newConfig = new BoneConfig()
-
- newConfig.customName = customName.get()
- newConfig.customNameVisible = customNameVisible.get()
- newConfig.billboard = billboard.get() as any
- newConfig.overrideBrightness = overrideBrightness.get()
- newConfig.brightnessOverride = brightnessOverride.get()
- newConfig.glowing = glowing.get()
- newConfig.overrideGlowColor = overrideGlowColor.get()
- newConfig.glowColor = glowColor.get()
- newConfig.invisible = invisible.get()
- newConfig.nbt = nbt.get()
- newConfig.shadowRadius = shadowRadius.get()
- newConfig.shadowStrength = shadowStrength.get()
- newConfig.useNBT = useNBT.get()
-
- const defaultConfig = BoneConfig.getDefault()
-
- newConfig.customName === defaultConfig.customName && (newConfig.customName = undefined)
- newConfig.customNameVisible === defaultConfig.customNameVisible &&
- (newConfig.customNameVisible = undefined)
- newConfig.billboard === defaultConfig.billboard && (newConfig.billboard = undefined)
- newConfig.overrideBrightness === defaultConfig.overrideBrightness &&
- (newConfig.overrideBrightness = undefined)
- newConfig.brightnessOverride === defaultConfig.brightnessOverride &&
- (newConfig.brightnessOverride = undefined)
- newConfig.glowing === defaultConfig.glowing && (newConfig.glowing = undefined)
- newConfig.overrideGlowColor === defaultConfig.overrideGlowColor &&
- (newConfig.overrideGlowColor = undefined)
- newConfig.glowColor === defaultConfig.glowColor && (newConfig.glowColor = undefined)
- newConfig.invisible === defaultConfig.invisible && (newConfig.invisible = undefined)
- newConfig.nbt === defaultConfig.nbt && (newConfig.nbt = undefined)
- newConfig.shadowRadius === defaultConfig.shadowRadius &&
- (newConfig.shadowRadius = undefined)
- newConfig.shadowStrength === defaultConfig.shadowStrength &&
- (newConfig.shadowStrength = undefined)
- newConfig.useNBT === defaultConfig.useNBT && (newConfig.useNBT = undefined)
-
- display.config = newConfig.toJSON()
- },
- }).show()
-}
-
-export const VANILLA_ITEM_DISPLAY_CONFIG_ACTION = createAction(
- `${PACKAGE.name}:open_vanilla_item_display_config`,
- {
- icon: 'settings',
- name: translate('action.open_vanilla_item_display_config.name'),
- condition: () => isCurrentFormat(),
- click: () => {
- if (VanillaItemDisplay.selected.length === 0) return
- openVanillaItemDisplayConfigDialog(VanillaItemDisplay.selected[0])
- },
- }
-)
diff --git a/src/interface/index.ts b/src/interface/index.ts
deleted file mode 100644
index ddad7aca..00000000
--- a/src/interface/index.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import './dialog/about'
-import './dialog/animationProperties'
-import './dialog/blueprintSettings'
-import './dialog/boneConfig'
-import './dialog/changelog'
-import './dialog/exportProgress'
-import './dialog/locatorConfig'
-import './dialog/textDisplayConfig'
-import './dialog/unexpectedError'
-import './dialog/vanillaBlockDisplayConfig'
-import './dialog/vanillaItemDisplayConfig'
-import './dialog/variantConfig'
-
-import './panel/customKeyframe'
-import './panel/textDisplayElement'
-import './panel/vanillaBlockDisplayElement'
-import './panel/vanillaItemDisplayElement'
-import './panel/variants'
-
-import './popup/animatedJavaLoading'
-import './popup/blueprintLoading'
-import './popup/incompatabilityPopup'
-import './popup/installed'
-
-import './importAJModelLoader'
-import './animatedJavaBarItem'
-import './keyframeEasings'
diff --git a/src/interface/panel/vanillaBlockDisplayElement.ts b/src/interface/panel/vanillaBlockDisplayElement.ts
deleted file mode 100644
index f55ba202..00000000
--- a/src/interface/panel/vanillaBlockDisplayElement.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import VanillaBlockDisplayElementPanel from '../../components/vanillaBlockDisplayElementPanel.svelte'
-import { injectSvelteCompomponentMod } from '../../util/injectSvelteComponent'
-
-injectSvelteCompomponentMod({
- component: VanillaBlockDisplayElementPanel,
- props: {},
- elementSelector() {
- return document.querySelector('#panel_element')
- },
-})
diff --git a/src/lang/en.yml b/src/lang/en.yml
index 2d7a9cee..e4b4e4a4 100644
--- a/src/lang/en.yml
+++ b/src/lang/en.yml
@@ -1,3 +1,150 @@
+# ----------------------------------------
+# NEW TRANSLATIONS
+# ----------------------------------------
+dialog:
+ error:
+ # Generic Errors
+ function_file.no_leading_slash: Commands inside of function files cannot start with a leading slash!
+ no_folder_selected: No folder selected!
+ folder_does_not_exist: The selected folder does not exist!
+ not_a_folder: The selected path is not a folder!
+ no_file_selected: No file selected!
+ file_does_not_exist: The selected file does not exist!
+ not_a_file: The selected path is not a file!
+ not_a_zip_file: The selected file is not a `.zip` file!
+
+ placeholder:
+ select_a_folder: Select a folder...
+ select_a_file: Select a file...
+
+ blueprint_settings:
+ title: Blueprint Settings
+ general:
+ plugin:
+
+ export:
+ target_minecraft_version:
+ label: Target Minecraft Version
+ description: |-
+ Choose the version of Minecraft you are exporting this project for. If your exact version is not listed, choose the next oldest version. For example, if you are using Minecraft `1.21.3`, choose `1.21.2`.
+ tag_prefix:
+ label: Tag Prefix
+ description: The prefix to use for all tags in the exported Data Pack.
+ auto_generate_tag_prefix:
+ label: Auto-Generate Tag Prefix
+ description: Automatically generate a tag prefix based on the BlueprintID.
+
+ resourcepack:
+ error:
+ missing_assets_folder: The selected Resource Pack is missing an assets folder!
+
+ datapack:
+ error:
+ missing_pack_mcmeta: The selected folder is missing a pack.mcmeta file!
+ warning:
+ missing_data_folder: The selected Data Pack is missing a data folder!
+
+ export_mode.folder:
+ label: Export to Folder
+ description: Export the generated Data Pack to a folder.
+ export_mode.zip:
+ label: Export to Zip
+ description: Export the generated Data Pack to a `.zip` file.
+ export_mode.none:
+ label: Do Not Export
+ description: Do not export the generated Data Pack.
+
+ export_folder.label: Export Folder
+ export_folder.description: Choose the folder to export the Data Pack into.
+
+ export_zip.label: Export Zip
+ export_zip.description: Choose the `.zip` file to export the Data Pack into.
+
+ on_summon_commands.label: On-Summon Commands
+ on_summon_commands.description: |-
+ Commands that will be run as the root entity when the model is summoned.
+ This input is treated as a function file, and supports [MC-Build](https://mcbuild.dev/) syntax.
+
+ on_tick_commands.label: On-Tick Commands
+ on_tick_commands.description: |-
+ Commands that will be run as the root entity every tick.
+ This input is treated as a function file, and supports [MC-Build](https://mcbuild.dev/) syntax.
+
+ interpolation_duration.label: Interpolation Duration
+ interpolation_duration.description: |-
+ How much time it takes for the model to transition from one frame to the next. Higher values will cause animations to lose precision.
+ Usually you want this to have a value of 1 or 2.
+
+ teleportation_duration.label: Teleport Duration
+ teleportation_duration.description: |-
+ How much time over which the model will visually interpolate between it's old position to it's new position when teleported.
+
+ animation_system.label: Animation System
+ animation_system.description: |-
+ Choose the animation system to use for the project.
+ animation_system.functions:
+ label: Function-based
+ description: Fast and simple, but creates a lot of function files.
+ animation_system.storage:
+ label: Storage-based
+ description: 42% Slower than function-based, but creates far fewer function files.
+
+config:
+ common:
+ label: Common
+ description: Properties common to all nodes.
+ options:
+ billboard: Billboard
+ overrideBrightness: Override Brightness
+ brightness: Brightness
+ glowing: Glowing
+ overrideGlowColor: Override Glow Color
+ glowColor: Glow Color
+ invisible: Invisible
+ shadowRadius: Shadow Radius
+ shadowStrength: Shadow Strength
+
+ animated_java:text_display:
+ label: Text Display
+ description: Properties specific to Text Displays.
+ options:
+ alignment: Alignment
+ backgroundColor: Background Color
+ lineWidth: Line Width
+ seeThrough: See Through
+ shadow: Shadow
+ textComponent: Text Component
+
+ animated_java:item_display:
+ label: Item Display
+ description: Properties specific to Item Displays.
+
+ animated_java:block_display:
+ label: Block Display
+ description: Properties specific to Block Displays.
+
+panel:
+ display:
+ title: Display
+ linked.tooltip: Linked - This display option will be inherited from the parent node.
+ unlinked.tooltip: Unlinked - This display option is unique to this node.
+ set_all_linked.tooltip: Set All Linked - Set all display options to inherit the display properties from the parent node.
+ set_all_unlinked.tooltip: Set All Unlinked - Set all display options to be unique to this node.
+ reset_all.tooltip: Reset All - Reset all display options to their default values.
+
+node:
+ structure.title: |-
+ Structure - (Animated Java)
+ A Group will become a Structure if it doesn't have Cube children.
+ Structures will not create an entity in-game, but can be used to organize your model, and effect the animation.
+ item_display.title: Item Display
+ block_display.title: Block Display
+ text_display.title: Text Display
+
+# ----------------------------------------
+# OLD TRANSLATIONS
+# ----------------------------------------
+
### Actions
animated_java.action.open_blueprint_settings.name: Blueprint Settings
animated_java.action.open_documentation.name: Documentation
@@ -10,10 +157,10 @@ animated_java.action.export.name: Export
animated_java.action.extract.name: Extract
animated_java.action.extract.confirm: Confirm Extraction
animated_java.action.create_text_display.title: Add Text Display
-animated_java.action.create_vanilla_item_display.title: Add Item Display
-animated_java.action.create_vanilla_block_display.title: Add Block Display
-animated_java.action.open_vanilla_item_display_config.name: Item Display Config
-animated_java.action.open_vanilla_block_display_config.name: Block Display Config
+animated_java.action.create_item_display.title: Add Item Display
+animated_java.action.create_block_display.title: Add Block Display
+animated_java.action.open_item_display_config.name: Item Display Config
+animated_java.action.open_block_display_config.name: Block Display Config
### Popups
animated_java.popup.loading.loading: Loading Animated Java...
@@ -328,8 +475,8 @@ animated_java.dialog.locator_config.ticking_commands.description: |-
## Text Display Config Dialog
animated_java.dialog.text_display_config.title: Text Display Config
-animated_java.dialog.bone_config.vanilla_item_model.title: Vanilla Item Model
-animated_java.dialog.bone_config.vanilla_item_model.description: |-
+animated_java.dialog.bone_config.item_model.title: Vanilla Item Model
+animated_java.dialog.bone_config.item_model.description: |-
If set, the bone will render as a vanilla item model.
This will overwrite the bone's existing cubes.
@@ -381,26 +528,26 @@ animated_java.dialog.text_display_config.billboard.options.horizontal: Horizonta
animated_java.dialog.text_display_config.billboard.options.center: Center
## Block Display Config Dialog
-animated_java.dialog.vanilla_block_display_config.title: Block Display Config
-animated_java.dialog.vanilla_block_display.custom_name.title: Custom Name
-animated_java.dialog.vanilla_block_display.custom_name.description: The custom name of the block display.
-animated_java.dialog.vanilla_block_display.custom_name.invalid_json.error: |-
+animated_java.dialog.block_display_config.title: Block Display Config
+animated_java.dialog.block_display.custom_name.title: Custom Name
+animated_java.dialog.block_display.custom_name.description: The custom name of the block display.
+animated_java.dialog.block_display.custom_name.invalid_json.error: |-
Invalid JSON Text!
{0}
-animated_java.dialog.vanilla_block_display.custom_name_visible.title: Custom Name Visible
-animated_java.dialog.vanilla_block_display.custom_name_visible.description: Whether or not the custom name should always be visible.
+animated_java.dialog.block_display.custom_name_visible.title: Custom Name Visible
+animated_java.dialog.block_display.custom_name_visible.description: Whether or not the custom name should always be visible.
## Item Display Config Dialog
-animated_java.dialog.vanilla_item_display_config.title: Item Display Config
-animated_java.dialog.vanilla_item_display.custom_name.title: Custom Name
-animated_java.dialog.vanilla_item_display.custom_name.description: The custom name of the item display.
-animated_java.dialog.vanilla_item_display.custom_name.invalid_json.error: |-
+animated_java.dialog.item_display_config.title: Item Display Config
+animated_java.dialog.item_display.custom_name.title: Custom Name
+animated_java.dialog.item_display.custom_name.description: The custom name of the item display.
+animated_java.dialog.item_display.custom_name.invalid_json.error: |-
Invalid JSON Text!
{0}
-animated_java.dialog.vanilla_item_display.custom_name_visible.title: Custom Name Visible
-animated_java.dialog.vanilla_item_display.custom_name_visible.description: Whether or not the custom name should always be visible.
+animated_java.dialog.item_display.custom_name_visible.title: Custom Name Visible
+animated_java.dialog.item_display.custom_name_visible.description: Whether or not the custom name should always be visible.
## Variant Config Dialog
animated_java.dialog.variant_config.title: Variant Config
@@ -576,8 +723,8 @@ animated_java.tool.text_display.see_through.title: See Through
animated_java.tool.text_display.see_through.description: Whether or not the text display should be visible through blocks.
# Item Display Panel
-animated_java.panel.vanilla_item_display.title: Displayed Item
-animated_java.panel.vanilla_item_display.description: The item to display.
+animated_java.panel.item_display.title: Displayed Item
+animated_java.panel.item_display.description: The item to display.
animated_java.tool.item_display.item_display.title: Item Display Mode
animated_java.tool.item_display.item_display.description: Which item model transform to apply to the item (as defined in display field in model JSON).
animated_java.tool.item_display.item_display.options.none: None
@@ -591,12 +738,12 @@ animated_java.tool.item_display.item_display.options.ground: Ground
animated_java.tool.item_display.item_display.options.fixed: Fixed
# Block Display Panel
-animated_java.panel.vanilla_block_display.title: Displayed Block
-animated_java.panel.vanilla_block_display.description: The block to display. Supports block states!
+animated_java.panel.block_display.title: Displayed Block
+animated_java.panel.block_display.description: The block to display. Supports block states!
### Custom Elements
## Item Display
-animated_java.vanilla_item_display.title: Item Display
+animated_java.item_display.title: Item Display
## Block Display
diff --git a/src/mods/assetSetupMod.ts b/src/mods/assetSetupMod.ts
deleted file mode 100644
index c3f55817..00000000
--- a/src/mods/assetSetupMod.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { PACKAGE } from '../constants'
-import {
- hideLoadingPopup,
- showLoadingPopup,
- showOfflineError,
-} from '../interface/popup/animatedJavaLoading'
-import { events } from '../util/events'
-import { createBlockbenchMod } from '../util/moddingTools'
-
-createBlockbenchMod(
- `${PACKAGE.name}:assetLoading`,
- undefined,
- () => {
- // Show loading popup
- void showLoadingPopup().then(async () => {
- if (!window.navigator.onLine) {
- showOfflineError()
- // return
- }
- events.NETWORK_CONNECTED.dispatch()
-
- await Promise.all([
- new Promise(resolve =>
- events.MINECRAFT_ASSETS_LOADED.subscribe(() => resolve())
- ),
- new Promise(resolve =>
- events.MINECRAFT_REGISTRY_LOADED.subscribe(() => resolve())
- ),
- new Promise(resolve =>
- events.MINECRAFT_FONTS_LOADED.subscribe(() => resolve())
- ),
- new Promise(resolve =>
- events.BLOCKSTATE_REGISTRY_LOADED.subscribe(() => resolve())
- ),
- ])
- .then(() => {
- hideLoadingPopup()
- })
- .catch(error => {
- console.error(error)
- Blockbench.showToastNotification({
- text: 'Animated Java failed to load! Please restart Blockbench',
- color: 'var(--color-error)',
- })
- })
- })
- },
- () => {
- //
- }
-)
diff --git a/src/mods/formatIconMod.ts b/src/mods/formatIconMod.ts
deleted file mode 100644
index 4a855724..00000000
--- a/src/mods/formatIconMod.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import Icon from '../components/icon.svelte'
-import { PACKAGE } from '../constants'
-import { injectSvelteCompomponent } from '../util/injectSvelteComponent'
-import { createBlockbenchMod } from '../util/moddingTools'
-
-createBlockbenchMod(
- `${PACKAGE.name}:formatIconMod`,
- undefined,
- () => {
- void injectSvelteCompomponent({
- elementSelector: () => document.querySelector('[format=animated_java_blueprint]'),
- component: Icon,
- props: {},
- prepend: true,
- postMount: () => {
- document
- .querySelector('[format=animated_java_blueprint] span i')
- ?.parentElement?.remove()
- const duplicates = [...document.querySelectorAll('#animated_java\\:icon')]
- if (duplicates.length > 1) {
- duplicates.slice(1).forEach(d => d.remove())
- }
- },
- })
- },
- () => {
- document.querySelector('#animated_java\\:icon')?.remove()
- }
-)
-
-createBlockbenchMod(
- `${PACKAGE.name}:prioritizeAnimatedJavaFormats`,
- undefined,
- () => {
- const interval = setInterval(() => {
- const ajFormats = $("li.format_category > label:contains('Animated Java')")
- .first()
- .parent()
- if (ajFormats.length === 0) return
-
- const mcFormats = $("li.format_category > label:contains('General')").first().parent()
-
- ajFormats.insertBefore(mcFormats)
-
- clearInterval(interval)
- }, 16)
- },
- () => {
- // Pass
- }
-)
diff --git a/src/mods/functions.molang b/src/mods/functions.molang
deleted file mode 100644
index 3bab9c8d..00000000
--- a/src/mods/functions.molang
+++ /dev/null
@@ -1,165 +0,0 @@
-smoothmin(a, b, k): |-
- v.h = math.clamp(0.5 + (0.5 * (a - b) / k), 0, 1);
- return math.lerp(a, b, v.h) - (k * v.h * (1 - v.h));
-
-smoothclamp(value, min, max, k): |-
- return lunar.smoothmin(lunar.smoothmin(value, min, -k), max, k);
-
-lopsided_wave(value, lopside_mag): |-
- return math.sin(value + math.cos(value) * lopside_mag);
-
-easeinsine(progress): |-
- return 1 - math.cos((progress * 180) / 2);
-
-easeoutsine(progress): |-
- return math.sin((progress * 180) / 2);
-
-easeinoutsine(progress): |-
- return(math.cos(180 * progress) - 1) * -1 / 2;
-
-easeinquad(progress): |-
- return progress * progress;
-
-easeoutquad(progress): |-
- return 1 - (1 - progress) * (1 - progress);
-
-easeinoutquad(progress): |-
- return progress < 0.5
- ? 2 * progress * progress
- : 1 - math.pow(-2 * progress + 2, 2) / 2;
-
-easeincubic(progress): |-
- return progress * progress * progress;
-
-easeoutcubic(progress): |-
- return 1 - math.pow(1 - progress, 3);
-
-easeinoutcubic(progress): |-
- return progress < 0.5
- ? 4 * progress * progress * progress
- : 1 - math.pow(-2 * progress + 2, 3) / 2;
-
-easeinquart(progress): |-
- return progress * progress * progress * progress;
-
-easeoutquart(progress): |-
- return 1 - math.pow(1 - progress, 4);
-
-easeinoutquart(progress): |-
- return progress < 0.5
- ? 8 * progress * progress * progress * progress
- : 1 - math.pow(-2 * progress + 2, 4) / 2;
-
-easeinquint(progress): |-
- return progress * progress * progress * progress * progress;
-
-easeoutquint(progress): |-
- return 1 - math.pow(1 - progress, 5);
-
-easeinoutquint(progress): |-
- return progress < 0.5
- ? 16 * progress * progress * progress * progress * progress
- : 1 - math.pow(-2 * progress + 2, 5) / 2;
-
-easeinexpo(progress): |-
- return progress == 0
- ? 0
- : math.pow(2, 10 * progress - 10);
-
-easeoutexpo(progress): |-
- return progress == 1
- ? 1
- : 1 - math.pow(2, -10 * progress);
-
-easeinoutexpo(progress): |-
- return progress == 0
- ? 0
- : progress == 1
- ? 1
- : progress < 0.5
- ? math.pow(2, 20 * progress - 10) / 2
- : (2 - math.pow(2, -20 * progress + 10)) / 2;
-
-easeincirc(progress): |-
- return math.sqrt(1 - math.pow(progress - 1, 2));
-
-easeoutcirc(progress): |-
- return math.sqrt(1 - math.pow(progress - 1, 2));
-
-easeinoutcirc(progress): |-
- return progress < 0.5
- ? (1 - math.sqrt(1 - math.pow(2 * progress, 2))) / 2
- : (math.sqrt(1 - math.pow(-2 * progress + 2, 2)) + 1) / 2;
-
-easeinback(progress, overshoot): |-
- t.overshoot=1.70158 * (overshoot ?? 1);
- return (t.overshoot + 1) * progress * progress * progress - t.overshoot * progress * progress;
-
-easeoutback(progress, overshoot): |-
- t.overshoot=1.70158 * (overshoot ?? 1);
- return 1 + (t.overshoot + 1) * math.pow(progress - 1, 3) + t.overshoot * math.pow(progress - 1, 2);
-
-easeinoutback(progress, overshoot): |-
- t.overshoot=1.70158 * (overshoot ?? 1);
- t.c2 = t.overshoot + 1;
- return progress < 0.5
- ? (math.pow(2 * progress, 2) * ((t.c2 + 1) * 2 * progress - t.c2)) / 2
- : (math.pow(2 * progress - 2, 2) * ((t.c2 + 1) * (progress * 2 - 2) + t.c2) + 2) / 2;
-
-easeinelastic(progress): |-
- return progress == 0
- ? 0
- : progress == 1
- ? 1
- : -math.pow(2, 10 * progress - 10) * math.sin((progress * 10 - 10.75) * 90);
-
-easeoutelastic(progress): |-
- return progress == 0
- ? 0
- : progress == 1
- ? 1
- : math.pow(2, -10 * progress) * math.sin((progress * 10 - 0.75) * 90) + 1;
-
-easeinoutelastic(progress): |-
- return progress == 0
- ? 0
- : progress == 1
- ? 1
- : progress < 0.5
- ? (math.pow(2, 20 * progress - 10) * math.sin((20 * progress - 11.125) * 90) * -1) / 2
- : (math.pow(2, -20 * progress + 10) * math.sin((20 * progress - 11.125) * 90)) / 2 + 1;
-
-easeinbounce(progress): |-
- return 1 - easeoutbounce(1 - progress);
-
-easeoutbounce(progress): |-
- t.n1=7.5625;
- t.d1=2.75;
- return progress < 1 / t.d1
- ? (t.n1 * progress * progress)
- : progress < 2 / t.d1
- ? {
- progress = progress - 1.5 / t.d1;
- t.n1 * progress * progress + 0.75;
- }
- : progress < 2.5 / t.d1
- ? {
- progress = progress - 2.25 / t.d1;
- t.n1 * progress * progress + 0.9375;
- }
- : {
- progress = progress - 2.625 / t.d1;
- t.n1 * progress * progress + 0.984375;
- };
-
-easeinoutbounce(progress): |-
- return progress < 0.5
- ? 1-easeinbounce(1-2 * progress) / 2
- : 1 + easeoutbounce(2 * progress - 1) / 2;
-
-linear_wave(progress, hang): |-
- t.progress = progress * (math.pi / 180) + math.pi * 600;
- t.hang = hang * (math.pi / 180);
- return math.mod(math.abs(t.progress), 2 * math.pi + t.hang * 2) > math.pi + t.hang
- ? math.clamp(-math.mod(math.abs(t.progress), math.pi + t.hang) / math.pi + 1, 0, 1)
- : math.clamp(math.mod(math.abs(t.progress), math.pi + t.hang) / math.pi, 0, 1);
\ No newline at end of file
diff --git a/src/mods/index.ts b/src/mods/index.ts
deleted file mode 100644
index 7776807d..00000000
--- a/src/mods/index.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import './assetSetupMod'
-import './addLocatorActionMod'
-import './animationControllerMod'
-import './animationPropertiesAction'
-import './animationPropertiesMod'
-import './blockbenchReadMod'
-import './boneInterpolationMod'
-import './bonePropertiesMod'
-import './cubeOutlineMod'
-import './customKeyframeEasingsMod'
-import './customKeyframesMod'
-import './exportOverActionMod'
-import './formatIconMod'
-import './groupContextMenuMod'
-import './groupNameMod'
-import './keyframeMod'
-import './locatorAnimatorMod'
-import './locatorContextMenuMod'
-import './locatorPropertiesMod'
-import './modelFormatConvertToMod'
-import './modelFormatMod'
-import './molangMod'
-import './panelMod'
-import './projectSettingsActionOverride'
-import './saveAllAnimationsActionMod'
-import './saveProjectActionMod'
-import './saveProjectAsActionMod'
-import './showDefaultPoseMod'
-import './variantPreviewCubeFaceMod'
-import './cameraNameMod'
-import './pluginsDialogMod'
diff --git a/src/mods/modelFormatConvertToMod.ts b/src/mods/modelFormatConvertToMod.ts
deleted file mode 100644
index 6b27cc5b..00000000
--- a/src/mods/modelFormatConvertToMod.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { BLUEPRINT_FORMAT, convertToBlueprint } from '../blueprintFormat'
-import { PACKAGE } from '../constants'
-import { createBlockbenchMod } from '../util/moddingTools'
-
-createBlockbenchMod(
- `${PACKAGE.name}:modelFormatConvertToMod`,
- {
- original: ModelFormat.prototype.convertTo,
- },
- context => {
- ModelFormat.prototype.convertTo = function (this: ModelFormat) {
- const result = context.original.call(this)
- if (this === BLUEPRINT_FORMAT) convertToBlueprint()
- return result
- }
- return context
- },
- context => {
- ModelFormat.prototype.convertTo = context.original
- }
-)
diff --git a/src/mods/molangMod.ts b/src/mods/molangMod.ts
deleted file mode 100644
index 81c227f8..00000000
--- a/src/mods/molangMod.ts
+++ /dev/null
@@ -1,482 +0,0 @@
-import { PACKAGE } from '../constants'
-import { events } from '../util/events'
-import { createBlockbenchMod } from '../util/moddingTools'
-import MolangFunctionFile from './functions.molang'
-
-const GLOBAL_VARIABLES = Animator.MolangParser.global_variables
-
-const ROOT_TOKENS = [
- 'true',
- 'false',
- 'math.',
- 'query.', //'q.',
- 'variable.', //'v.',
- 'temp.', //'t.',
- 'context.', //'c.',
- 'this',
- 'loop()',
- 'return',
- 'break',
- 'continue',
-]
-const MOLANG_QUERIES = [
- // common
- 'all_animations_finished',
- 'any_animation_finished',
- 'anim_time',
- 'life_time',
- 'yaw_speed',
- 'ground_speed',
- 'vertical_speed',
- 'property',
- 'has_property()',
- 'variant',
- 'mark_variant',
- 'skin_id',
-
- 'above_top_solid',
- 'actor_count',
- 'all()',
- 'all_tags',
- 'anger_level',
- 'any()',
- 'any_tag',
- 'approx_eq()',
- 'armor_color_slot',
- 'armor_material_slot',
- 'armor_texture_slot',
- 'average_frame_time',
- 'blocking',
- 'body_x_rotation',
- 'body_y_rotation',
- 'bone_aabb',
- 'bone_origin',
- 'bone_rotation',
- 'camera_distance_range_lerp',
- 'camera_rotation()',
- 'can_climb',
- 'can_damage_nearby_mobs',
- 'can_dash',
- 'can_fly',
- 'can_power_jump',
- 'can_swim',
- 'can_walk',
- 'cape_flap_amount',
- 'cardinal_facing',
- 'cardinal_facing_2d',
- 'cardinal_player_facing',
- 'combine_entities()',
- 'count',
- 'current_squish_value',
- 'dash_cooldown_progress',
- 'day',
- 'death_ticks',
- 'debug_output',
- 'delta_time',
- 'distance_from_camera',
- 'effect_emitter_count',
- 'effect_particle_count',
- 'equipment_count',
- 'equipped_item_all_tags',
- 'equipped_item_any_tag()',
- 'equipped_item_is_attachable',
- 'eye_target_x_rotation',
- 'eye_target_y_rotation',
- 'facing_target_to_range_attack',
- 'frame_alpha',
- 'get_actor_info_id',
- 'get_animation_frame',
- 'get_default_bone_pivot',
- 'get_locator_offset',
- 'get_root_locator_offset',
- 'had_component_group()',
- 'has_any_family()',
- 'has_armor_slot',
- 'has_biome_tag',
- 'has_block_property',
- 'has_cape',
- 'has_collision',
- 'has_dash_cooldown',
- 'has_gravity',
- 'has_owner',
- 'has_rider',
- 'has_target',
- 'head_roll_angle',
- 'head_x_rotation',
- 'head_y_rotation',
- 'health',
- 'heartbeat_interval',
- 'heartbeat_phase',
- 'heightmap',
- 'hurt_direction',
- 'hurt_time',
- 'in_range()',
- 'invulnerable_ticks',
- 'is_admiring',
- 'is_alive',
- 'is_angry',
- 'is_attached_to_entity',
- 'is_avoiding_block',
- 'is_avoiding_mobs',
- 'is_baby',
- 'is_breathing',
- 'is_bribed',
- 'is_carrying_block',
- 'is_casting',
- 'is_celebrating',
- 'is_celebrating_special',
- 'is_charged',
- 'is_charging',
- 'is_chested',
- 'is_critical',
- 'is_croaking',
- 'is_dancing',
- 'is_delayed_attacking',
- 'is_digging',
- 'is_eating',
- 'is_eating_mob',
- 'is_elder',
- 'is_emerging',
- 'is_emoting',
- 'is_enchanted',
- 'is_fire_immune',
- 'is_first_person',
- 'is_ghost',
- 'is_gliding',
- 'is_grazing',
- 'is_idling',
- 'is_ignited',
- 'is_illager_captain',
- 'is_in_contact_with_water',
- 'is_in_love',
- 'is_in_ui',
- 'is_in_water',
- 'is_in_water_or_rain',
- 'is_interested',
- 'is_invisible',
- 'is_item_equipped',
- 'is_item_name_any()',
- 'is_jump_goal_jumping',
- 'is_jumping',
- 'is_laying_down',
- 'is_laying_egg',
- 'is_leashed',
- 'is_levitating',
- 'is_lingering',
- 'is_moving',
- 'is_name_any()',
- 'is_on_fire',
- 'is_on_ground',
- 'is_on_screen',
- 'is_onfire',
- 'is_orphaned',
- 'is_owner_identifier_any()',
- 'is_persona_or_premium_skin',
- 'is_playing_dead',
- 'is_powered',
- 'is_pregnant',
- 'is_ram_attacking',
- 'is_resting',
- 'is_riding',
- 'is_roaring',
- 'is_rolling',
- 'is_saddled',
- 'is_scared',
- 'is_selected_item',
- 'is_shaking',
- 'is_shaking_wetness',
- 'is_sheared',
- 'is_shield_powered',
- 'is_silent',
- 'is_sitting',
- 'is_sleeping',
- 'is_sneaking',
- 'is_sneezing',
- 'is_sniffing',
- 'is_sonic_boom',
- 'is_spectator',
- 'is_sprinting',
- 'is_stackable',
- 'is_stalking',
- 'is_standing',
- 'is_stunned',
- 'is_swimming',
- 'is_tamed',
- 'is_transforming',
- 'is_using_item',
- 'is_wall_climbing',
- 'item_in_use_duration',
- 'item_is_charged',
- 'item_max_use_duration',
- 'item_remaining_use_duration',
- 'item_slot_to_bone_name()',
- 'key_frame_lerp_time',
- 'last_frame_time',
- 'last_hit_by_player',
- 'lie_amount',
- 'life_span',
- 'lod_index',
- 'log',
- 'main_hand_item_max_duration',
- 'main_hand_item_use_duration',
- 'max_durability',
- 'max_health',
- 'max_trade_tier',
- 'maximum_frame_time',
- 'minimum_frame_time',
- 'model_scale',
- 'modified_distance_moved',
- 'modified_move_speed',
- 'moon_brightness',
- 'moon_phase',
- 'movement_direction',
- 'noise',
- 'on_fire_time',
- 'out_of_control',
- 'player_level',
- 'position()',
- 'position_delta()',
- 'previous_squish_value',
- 'remaining_durability',
- 'roll_counter',
- 'rotation_to_camera()',
- 'shake_angle',
- 'shake_time',
- 'shield_blocking_bob',
- 'show_bottom',
- 'sit_amount',
- 'sleep_rotation',
- 'sneeze_counter',
- 'spellcolor',
- 'standing_scale',
- 'structural_integrity',
- 'surface_particle_color',
- 'surface_particle_texture_coordinate',
- 'surface_particle_texture_size',
- 'swell_amount',
- 'swelling_dir',
- 'swim_amount',
- 'tail_angle',
- 'target_x_rotation',
- 'target_y_rotation',
- 'texture_frame_index',
- 'time_of_day',
- 'time_since_last_vibration_detection',
- 'time_stamp',
- 'total_emitter_count',
- 'total_particle_count',
- 'trade_tier',
- 'unhappy_counter',
- 'walk_distance',
- 'wing_flap_position',
- 'wing_flap_speed',
-]
-const MOLANG_QUERY_LABELS = {
- 'in_range()': 'in_range( value, min, max )',
- 'all()': 'in_range( value, values... )',
- 'any()': 'in_range( value, values... )',
- 'approx_eq()': 'in_range( value, values... )',
-}
-const DEFAULT_CONTEXT = [
- 'item_slot',
- 'block_face',
- 'cardinal_block_face_placed_on',
- 'is_first_person',
- 'owning_entity',
- 'player_offhand_arm_height',
- 'other',
- 'count',
-]
-const DEFAULT_VARIABLES = ['attack_time', 'is_first_person']
-const MATH_FUNCTIONS = [
- 'sin()',
- 'cos()',
- 'abs()',
- 'clamp()',
- 'pow()',
- 'sqrt()',
- 'random()',
- 'ceil()',
- 'round()',
- 'trunc()',
- 'floor()',
- 'mod()',
- 'min()',
- 'max()',
- 'exp()',
- 'ln()',
- 'lerp()',
- 'lerprotate()',
- 'pi',
- 'asin()',
- 'acos()',
- 'atan()',
- 'atan2()',
- 'die_roll()',
- 'die_roll_integer()',
- 'hermite_blend()',
- 'random_integer()',
-]
-const MATH_FUNCTION_LABELS = {
- 'clamp()': 'clamp( value, min, max )',
- 'pow()': 'pow( base, exponent )',
- 'random()': 'random( low, high )',
- 'mod()': 'mod( value, denominator )',
- 'min()': 'min( A, B )',
- 'max()': 'max( A, B )',
- 'lerp()': 'lerp( start, end, 0_to_1 )',
- 'lerprotate()': 'lerprotate( start, end, 0_to_1 )',
- 'atan2()': 'atan2( y, x )',
- 'die_roll()': 'die_roll( num, low, high )',
- 'die_roll_integer()': 'die_roll_integer( num, low, high )',
- 'random_integer()': 'random_integer( low, high )',
- 'hermite_blend()': 'hermite_blend( 0_to_1 )',
-}
-
-const CUSTOM_FUNCTIONS: Record number> = {}
-const CUSTOM_FUNCTION_LABELS: Record = {}
-
-for (const [call, body] of Object.entries(MolangFunctionFile)) {
- const match = call.match(/^(.+?)\((.*?)\)$/)
- if (!match) continue
- const name = match[1]
- const argList = match[2].split(',').map(v => v.trim())
- CUSTOM_FUNCTIONS[name] = (...args: number[]) => {
- const variables: Record = {}
- for (let i = 0; i < args.length; i++) {
- variables[argList[i]] = args[i] || 0
- }
- return Animator.MolangParser.parse(body, variables)
- }
- CUSTOM_FUNCTION_LABELS[name] = name + '( ' + argList.join(', ') + ' )'
-}
-
-function getProjectVariables(current: string) {
- const set = new Set()
- const expressions = getAllMolangExpressions()
- expressions.forEach((exp: MolangExpression) => {
- if (!exp.value) return
- const matches = exp.value.match(/(v|variable)\.\w+/gi)
- if (!matches) return
- matches.forEach(match => {
- const name = match.substring(match.indexOf('.') + 1)
- if (name !== current) set.add(name)
- })
- })
- return set
-}
-
-function filterAndSortList(
- list: string[],
- match: string,
- blacklist: string[] | false | undefined = false,
- labels: Record | undefined = undefined
-) {
- const result = list.filter(f => f.startsWith(match) && f.length != match.length)
- list.forEach(f => {
- if (!result.includes(f) && f.includes(match) && f.length != match.length) result.push(f)
- })
- if (blacklist) blacklist.forEach(black => result.remove(black))
- return result.map(text => {
- return { text, label: labels && labels[text], overlap: match.length }
- })
-}
-
-createBlockbenchMod(
- `${PACKAGE.name}:molangMod`,
- {
- originalAutocompleteMolang: Animator.autocompleteMolang,
- unsubscribeSelectAjProject: undefined as (() => void) | undefined,
- unsuscribeUnselectAjProject: undefined as (() => void) | undefined,
- },
- context => {
- context.unsubscribeSelectAjProject = events.SELECT_AJ_PROJECT.subscribe(() => {
- Object.assign(GLOBAL_VARIABLES, CUSTOM_FUNCTIONS)
-
- Animator.autocompleteMolang = function (text, position, type) {
- let beginning = text
- .substring(0, position)
- .split(/[^a-zA-Z_.]\.*/g)
- .last()
- if (!beginning) return []
-
- beginning = beginning.toLowerCase()
- if (beginning.includes('.')) {
- const [namespace, dir] = beginning.split('.')
- if (namespace == 'math') {
- return filterAndSortList(
- MATH_FUNCTIONS,
- dir,
- undefined,
- MATH_FUNCTION_LABELS
- )
- }
- if (namespace == 'query' || namespace == 'q') {
- return filterAndSortList(
- MOLANG_QUERIES,
- dir,
- type !== 'controller' && [
- 'all_animations_finished',
- 'any_animation_finished',
- ],
- MOLANG_QUERY_LABELS
- )
- }
- if (namespace == 'temp' || namespace == 't') {
- const temps = text.match(/([^a-z]|^)t(emp)?\.\w+/gi)
- if (temps) {
- const temps2 = temps.map(t => t.split('.')[1])
- const temps3 = temps2.filter(
- (t, i) => t !== dir && temps2.indexOf(t) === i
- )
- return filterAndSortList(temps3, dir)
- }
- }
- if (namespace == 'context' || namespace == 'c') {
- return filterAndSortList([...DEFAULT_CONTEXT], dir)
- }
- if (namespace == 'variable' || namespace == 'v') {
- const options = [...getProjectVariables(dir)]
- options.safePush(...DEFAULT_VARIABLES)
- return filterAndSortList(options, dir)
- }
- } else {
- const root_tokens = ROOT_TOKENS.slice()
- let labels = {}
- if (type === 'placeholders') {
- labels = {
- 'toggle()': 'toggle( name )',
- 'slider()': 'slider( name, step?, min?, max? )',
- 'impulse()': 'impulse( name, duration )',
- }
- root_tokens.push(...Object.keys(labels))
- }
- return filterAndSortList(
- [...root_tokens, ...Object.keys(CUSTOM_FUNCTION_LABELS)],
- beginning,
- undefined,
- { ...labels, ...CUSTOM_FUNCTION_LABELS }
- )
- }
- return []
- }
- })
-
- context.unsuscribeUnselectAjProject = events.UNSELECT_AJ_PROJECT.subscribe(() => {
- for (const key of Object.keys(CUSTOM_FUNCTIONS)) {
- delete GLOBAL_VARIABLES[key]
- }
- Animator.autocompleteMolang = context.originalAutocompleteMolang
- })
-
- return context
- },
- context => {
- for (const key of Object.keys(CUSTOM_FUNCTIONS)) {
- delete GLOBAL_VARIABLES[key]
- }
- Animator.autocompleteMolang = context.originalAutocompleteMolang
- context.unsubscribeSelectAjProject?.()
- context.unsuscribeUnselectAjProject?.()
- }
-)
diff --git a/src/nodeConfigs.ts b/src/nodeConfigs.ts
deleted file mode 100644
index 06ad0760..00000000
--- a/src/nodeConfigs.ts
+++ /dev/null
@@ -1,759 +0,0 @@
-import { NbtByte, NbtCompound, NbtFloat, NbtInt, NbtString, NbtTag } from 'deepslate/lib/nbt'
-import {
- IBlueprintBoneConfigJSON,
- IBlueprintCameraConfigJSON,
- IBlueprintLocatorConfigJSON,
- IBlueprintTextDisplayConfigJSON,
-} from './blueprintFormat'
-
-export type BillboardMode = 'fixed' | 'vertical' | 'horizontal' | 'center'
-
-// TODO: Refactor these configs to inherit from a base class
-export class BoneConfig {
- private _customName?: string
- private _customNameVisible?: boolean
- private _billboard?: BillboardMode
- private _overrideBrightness?: boolean
- private _brightnessOverride?: number
- private _enchanted?: boolean
- private _glowing?: boolean
- private _overrideGlowColor?: boolean
- private _glowColor?: string
- private _inheritSettings?: boolean
- private _invisible?: boolean
- private _nbt?: string
- private _shadowRadius?: number
- private _shadowStrength?: number
- private _useNBT?: boolean
-
- static getDefault(): BoneConfig {
- return BoneConfig.fromJSON({
- custom_name: '',
- custom_name_visible: false,
- billboard: 'fixed',
- override_brightness: false,
- brightness_override: 0,
- enchanted: false,
- glowing: false,
- override_glow_color: false,
- glow_color: '#ffffff',
- inherit_settings: true,
- invisible: false,
- nbt: '{}',
- shadow_radius: 0,
- shadow_strength: 1,
- use_nbt: false,
- })
- }
-
- get customName(): NonNullable {
- if (this._customName !== undefined) return this._customName
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.customName
- }
- set customName(value: BoneConfig['_customName']) {
- this._customName = value
- }
-
- get customNameVisible(): NonNullable {
- if (this._customNameVisible !== undefined) return this._customNameVisible
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.customNameVisible
- }
- set customNameVisible(value: BoneConfig['_customNameVisible']) {
- this._customNameVisible = value
- }
-
- get billboard(): NonNullable {
- if (this._billboard !== undefined) return this._billboard
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.billboard
- }
- set billboard(value: BoneConfig['_billboard']) {
- this._billboard = value
- }
-
- get overrideBrightness(): NonNullable {
- if (this._overrideBrightness !== undefined) return this._overrideBrightness
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.overrideBrightness
- }
- set overrideBrightness(value: BoneConfig['_overrideBrightness']) {
- this._overrideBrightness = value
- }
-
- get brightnessOverride(): NonNullable {
- if (this._brightnessOverride !== undefined) return this._brightnessOverride
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.brightnessOverride
- }
- set brightnessOverride(value: BoneConfig['_brightnessOverride']) {
- this._brightnessOverride = value
- }
-
- get enchanted(): NonNullable {
- if (this._enchanted !== undefined) return this._enchanted
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.enchanted
- }
- set enchanted(value: BoneConfig['_enchanted']) {
- this._enchanted = value
- }
-
- get glowing(): NonNullable {
- if (this._glowing !== undefined) return this._glowing
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.glowing
- }
- set glowing(value: BoneConfig['_glowing']) {
- this._glowing = value
- }
-
- get overrideGlowColor(): NonNullable {
- if (this._overrideGlowColor !== undefined) return this._overrideGlowColor
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.overrideGlowColor
- }
- set overrideGlowColor(value: BoneConfig['_overrideGlowColor']) {
- this._overrideGlowColor = value
- }
-
- get glowColor(): NonNullable {
- if (this._glowColor !== undefined) return this._glowColor
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.glowColor
- }
- set glowColor(value: BoneConfig['_glowColor']) {
- this._glowColor = value
- }
-
- get inheritSettings(): NonNullable {
- if (this._inheritSettings !== undefined) return this._inheritSettings
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.inheritSettings
- }
- set inheritSettings(value: BoneConfig['_inheritSettings']) {
- this._inheritSettings = value
- }
-
- get invisible(): NonNullable {
- if (this._invisible !== undefined) return this._invisible
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.invisible
- }
- set invisible(value: BoneConfig['_invisible']) {
- this._invisible = value
- }
-
- get nbt(): NonNullable {
- if (this._nbt !== undefined) return this._nbt
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.nbt
- }
- set nbt(value: BoneConfig['_nbt']) {
- this._nbt = value
- }
-
- get shadowRadius(): NonNullable {
- if (this._shadowRadius !== undefined) return this._shadowRadius
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.shadowRadius
- }
- set shadowRadius(value: BoneConfig['_shadowRadius']) {
- this._shadowRadius = value
- }
-
- get shadowStrength(): NonNullable {
- if (this._shadowStrength !== undefined) return this._shadowStrength
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.shadowStrength
- }
- set shadowStrength(value: BoneConfig['_shadowStrength']) {
- this._shadowStrength = value
- }
-
- get useNBT(): NonNullable {
- if (this._useNBT !== undefined) return this._useNBT
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.useNBT
- }
- set useNBT(value: BoneConfig['_useNBT']) {
- this._useNBT = value
- }
-
- public checkIfEqual(other: BoneConfig) {
- return (
- this._customName === other._customName &&
- this._customNameVisible === other._customNameVisible &&
- this._billboard === other._billboard &&
- this._overrideBrightness === other._overrideBrightness &&
- this._brightnessOverride === other._brightnessOverride &&
- this._enchanted === other._enchanted &&
- this._glowing === other._glowing &&
- this._overrideGlowColor === other._overrideGlowColor &&
- this._glowColor === other._glowColor &&
- this._inheritSettings === other._inheritSettings &&
- this._invisible === other._invisible &&
- this._nbt === other._nbt &&
- this._shadowRadius === other._shadowRadius &&
- this._shadowStrength === other._shadowStrength &&
- this._useNBT === other._useNBT
- )
- }
-
- public isDefault(): boolean {
- return this.checkIfEqual(BoneConfig.getDefault())
- }
-
- public toJSON(): IBlueprintBoneConfigJSON {
- return {
- custom_name: this._customName,
- custom_name_visible: this._customNameVisible,
- billboard: this._billboard,
- override_brightness: this._overrideBrightness,
- brightness_override: this._brightnessOverride,
- enchanted: this._enchanted,
- glowing: this._glowing,
- override_glow_color: this._overrideGlowColor,
- glow_color: this._glowColor,
- inherit_settings: this._inheritSettings,
- invisible: this._invisible,
- nbt: this._nbt,
- shadow_radius: this._shadowRadius,
- shadow_strength: this._shadowStrength,
- use_nbt: this._useNBT,
- }
- }
-
- inheritFrom(other: BoneConfig) {
- if (other._customName !== undefined) this.customName = other.customName
- if (other._customNameVisible !== undefined) this.customNameVisible = other.customNameVisible
- if (other._billboard !== undefined) this.billboard = other.billboard
- if (other._overrideBrightness !== undefined)
- this.overrideBrightness = other.overrideBrightness
- if (other._brightnessOverride !== undefined)
- this.brightnessOverride = other.brightnessOverride
- if (other._enchanted !== undefined) this.enchanted = other.enchanted
- if (other._glowing !== undefined) this.glowing = other.glowing
- if (other._overrideGlowColor !== undefined) this.overrideGlowColor = other.overrideGlowColor
- if (other._glowColor !== undefined) this.glowColor = other.glowColor
- if (other._inheritSettings !== undefined) this.inheritSettings = other.inheritSettings
- if (other._invisible !== undefined) this.invisible = other.invisible
- if (other._nbt !== undefined) this.nbt = other.nbt
- if (other._shadowRadius !== undefined) this.shadowRadius = other.shadowRadius
- if (other._shadowStrength !== undefined) this.shadowStrength = other.shadowStrength
- if (other._useNBT !== undefined) this.useNBT = other.useNBT
- }
-
- public static fromJSON(json: IBlueprintBoneConfigJSON): BoneConfig {
- const config = new BoneConfig()
- if (json.custom_name !== undefined) config._customName = json.custom_name
- if (json.custom_name_visible !== undefined)
- config._customNameVisible = json.custom_name_visible
- if (json.billboard !== undefined) config._billboard = json.billboard
- if (json.override_brightness !== undefined)
- config._overrideBrightness = json.override_brightness
- if (json.brightness_override !== undefined)
- config._brightnessOverride = json.brightness_override
- if (json.enchanted !== undefined) config._enchanted = json.enchanted
- if (json.glowing !== undefined) config._glowing = json.glowing
- if (json.override_glow_color !== undefined)
- config._overrideGlowColor = json.override_glow_color
- if (json.glow_color !== undefined) config._glowColor = json.glow_color
- if (json.inherit_settings !== undefined) config._inheritSettings = json.inherit_settings
- if (json.invisible !== undefined) config._invisible = json.invisible
- if (json.nbt !== undefined) config._nbt = json.nbt
- if (json.shadow_radius !== undefined) config._shadowRadius = json.shadow_radius
- if (json.shadow_strength !== undefined) config._shadowStrength = json.shadow_strength
- if (json.use_nbt !== undefined) config._useNBT = json.use_nbt
- return config
- }
-
- public toNBT(compound: NbtCompound = new NbtCompound()): NbtCompound {
- if (this.useNBT) {
- const newData = NbtTag.fromString(this.nbt) as NbtCompound
- for (const key of newData.keys()) {
- compound.set(key, newData.get(key)!)
- }
- return compound
- }
-
- if (this._customName) {
- compound.set('CustomName', new NbtString(this.customName))
- }
-
- if (this._customNameVisible) {
- compound.set('CustomNameVisible', new NbtByte(Number(this.customNameVisible)))
- }
-
- if (this._billboard) {
- compound.set('billboard', new NbtString(this.billboard))
- }
-
- if (this.overrideBrightness) {
- compound.set(
- 'brightness',
- new NbtCompound()
- .set('block', new NbtFloat(this.brightnessOverride))
- .set('sky', new NbtFloat(this.brightnessOverride))
- )
- }
-
- if (this.enchanted) {
- const item = (compound.get('item') as NbtCompound) || new NbtCompound()
- compound.set(
- 'item',
- item.set(
- 'components',
- new NbtCompound().set(
- 'minecraft:enchantments',
- new NbtCompound().set(
- 'levels',
- new NbtCompound().set('minecraft:infinity', new NbtInt(1))
- )
- )
- )
- )
- }
-
- if (this.glowing) {
- compound.set('Glowing', new NbtByte(Number(this.glowing)))
- }
- if (this.overrideGlowColor) {
- compound.set(
- 'glow_color_override',
- new NbtInt(Number(this.glowColor.replace('#', '0x')))
- )
- }
-
- // TODO Figure out a good solution for toggling a bone's visibility...
- // if (force || config.invisible !== defaultConfig.invisible) {
- // compound.set('invisible', new NbtByte(1))
- // }
-
- if (this._shadowRadius) {
- compound.set('shadow_radius', new NbtFloat(this.shadowRadius))
- }
-
- if (this._shadowStrength) {
- compound.set('shadow_strength', new NbtFloat(this.shadowStrength))
- }
-
- return compound
- }
-}
-
-export class LocatorConfig {
- private _useEntity?: boolean
- private _entityType?: string
- private _syncPassengerRotation?: boolean
- private _summonCommands?: string
- private _tickingCommands?: string
-
- getDefault(): LocatorConfig {
- return LocatorConfig.fromJSON({
- use_entity: false,
- entity_type: 'minecraft:pig',
- sync_passenger_rotation: false,
- summon_commands: '',
- ticking_commands: '',
- })
- }
-
- get useEntity(): NonNullable {
- if (this._useEntity !== undefined) return this._useEntity
- const defaultConfig = this.getDefault()
- return defaultConfig.useEntity
- }
- set useEntity(value: NonNullable) {
- this._useEntity = value
- }
-
- get entityType(): NonNullable {
- if (this._entityType !== undefined) return this._entityType
- const defaultConfig = this.getDefault()
- return defaultConfig.entityType
- }
- set entityType(value: NonNullable) {
- this._entityType = value
- }
-
- get syncPassengerRotation(): NonNullable {
- if (this._syncPassengerRotation !== undefined) return this._syncPassengerRotation
- const defaultConfig = this.getDefault()
- return defaultConfig.syncPassengerRotation
- }
- set syncPassengerRotation(value: NonNullable) {
- this._syncPassengerRotation = value
- }
-
- get summonCommands(): NonNullable {
- if (this._summonCommands !== undefined) return this._summonCommands
- const defaultConfig = this.getDefault()
- return defaultConfig.summonCommands
- }
- set summonCommands(value: NonNullable) {
- this._summonCommands = value
- }
-
- get tickingCommands(): NonNullable {
- if (this._tickingCommands !== undefined) return this._tickingCommands
- const defaultConfig = this.getDefault()
- return defaultConfig.tickingCommands
- }
- set tickingCommands(value: NonNullable) {
- this._tickingCommands = value
- }
-
- public toJSON(): IBlueprintLocatorConfigJSON {
- return {
- use_entity: this._useEntity,
- entity_type: this._entityType,
- sync_passenger_rotation: this._syncPassengerRotation,
- summon_commands: this._summonCommands,
- ticking_commands: this._tickingCommands,
- }
- }
-
- public static fromJSON(json: IBlueprintLocatorConfigJSON): LocatorConfig {
- const config = new LocatorConfig()
- if (json.use_entity !== undefined) config._useEntity = json.use_entity
- if (json.entity_type !== undefined) config._entityType = json.entity_type
- if (json.sync_passenger_rotation !== undefined)
- config._syncPassengerRotation = json.sync_passenger_rotation
- if (json.summon_commands !== undefined) config._summonCommands = json.summon_commands
- if (json.ticking_commands !== undefined) config._tickingCommands = json.ticking_commands
- return config
- }
-
- public isDefault(): boolean {
- return this.checkIfEqual(new LocatorConfig())
- }
-
- public checkIfEqual(other: LocatorConfig) {
- return (
- this.useEntity === other.useEntity &&
- this.entityType === other.entityType &&
- this.syncPassengerRotation === other.syncPassengerRotation &&
- this.summonCommands === other.summonCommands &&
- this.tickingCommands === other.tickingCommands
- )
- }
-}
-
-export class CameraConfig {
- private _entityType?: string
- private _nbt?: string
- private _tickingCommands?: string
-
- getDefault(): CameraConfig {
- return CameraConfig.fromJSON({
- entity_type: 'minecraft:item_display',
- nbt: '{}',
- ticking_commands: '',
- })
- }
-
- get entityType(): NonNullable {
- if (this._entityType !== undefined) return this._entityType
- const defaultConfig = this.getDefault()
- return defaultConfig.entityType
- }
- set entityType(value: NonNullable) {
- this._entityType = value
- }
-
- get nbt(): NonNullable {
- if (this._nbt !== undefined) return this._nbt
- const defaultConfig = this.getDefault()
- return defaultConfig.nbt
- }
- set nbt(value: NonNullable) {
- this._nbt = value
- }
-
- get tickingCommands(): NonNullable {
- if (this._tickingCommands !== undefined) return this._tickingCommands
- const defaultConfig = this.getDefault()
- return defaultConfig.tickingCommands
- }
- set tickingCommands(value: NonNullable) {
- this._tickingCommands = value
- }
-
- public toJSON(): IBlueprintCameraConfigJSON {
- return {
- entity_type: this.entityType,
- nbt: this.nbt,
- ticking_commands: this.tickingCommands,
- }
- }
-
- public static fromJSON(json: IBlueprintCameraConfigJSON): CameraConfig {
- const config = new CameraConfig()
- if (json.entity_type != undefined) config.entityType = json.entity_type
- if (json.nbt != undefined) config.nbt = json.nbt
- if (json.ticking_commands != undefined) config.tickingCommands = json.ticking_commands
- return config
- }
-
- public isDefault(): boolean {
- return this.checkIfEqual(new CameraConfig())
- }
-
- public checkIfEqual(other: CameraConfig) {
- return (
- this.entityType === other.entityType &&
- this.nbt === other.nbt &&
- this.tickingCommands === other.tickingCommands
- )
- }
-}
-
-export class TextDisplayConfig {
- private _billboard?: BillboardMode
- private _overrideBrightness?: boolean
- private _brightnessOverride?: number
- private _glowing?: boolean
- private _overrideGlowColor?: boolean
- private _glowColor?: string
- private _invisible?: boolean
- private _shadowRadius?: number
- private _shadowStrength?: number
- private _useNBT?: boolean
- private _nbt?: string
-
- static getDefault(): TextDisplayConfig {
- return TextDisplayConfig.fromJSON({
- billboard: 'fixed',
- override_brightness: false,
- brightness_override: 0,
- glowing: false,
- override_glow_color: false,
- glow_color: '#ffffff',
- invisible: false,
- nbt: '{}',
- shadow_radius: 0,
- shadow_strength: 1,
- use_nbt: false,
- })
- }
-
- get billboard(): NonNullable {
- if (this._billboard !== undefined) return this._billboard
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.billboard
- }
- set billboard(value: BoneConfig['_billboard']) {
- this._billboard = value
- }
-
- get overrideBrightness(): NonNullable {
- if (this._overrideBrightness !== undefined) return this._overrideBrightness
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.overrideBrightness
- }
- set overrideBrightness(value: BoneConfig['_overrideBrightness']) {
- this._overrideBrightness = value
- }
-
- get brightnessOverride(): NonNullable {
- if (this._brightnessOverride !== undefined) return this._brightnessOverride
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.brightnessOverride
- }
- set brightnessOverride(value: BoneConfig['_brightnessOverride']) {
- this._brightnessOverride = value
- }
-
- get glowing(): NonNullable {
- if (this._glowing !== undefined) return this._glowing
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.glowing
- }
- set glowing(value: BoneConfig['_glowing']) {
- this._glowing = value
- }
-
- get overrideGlowColor(): NonNullable {
- if (this._overrideGlowColor !== undefined) return this._overrideGlowColor
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.overrideGlowColor
- }
- set overrideGlowColor(value: BoneConfig['_overrideGlowColor']) {
- this._overrideGlowColor = value
- }
-
- get glowColor(): NonNullable {
- if (this._glowColor !== undefined) return this._glowColor
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.glowColor
- }
- set glowColor(value: BoneConfig['_glowColor']) {
- this._glowColor = value
- }
-
- get invisible(): NonNullable {
- if (this._invisible !== undefined) return this._invisible
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.invisible
- }
- set invisible(value: BoneConfig['_invisible']) {
- this._invisible = value
- }
-
- get nbt(): NonNullable {
- if (this._nbt !== undefined) return this._nbt
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.nbt
- }
- set nbt(value: BoneConfig['_nbt']) {
- this._nbt = value
- }
-
- get shadowRadius(): NonNullable {
- if (this._shadowRadius !== undefined) return this._shadowRadius
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.shadowRadius
- }
- set shadowRadius(value: BoneConfig['_shadowRadius']) {
- this._shadowRadius = value
- }
-
- get shadowStrength(): NonNullable {
- if (this._shadowStrength !== undefined) return this._shadowStrength
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.shadowStrength
- }
- set shadowStrength(value: BoneConfig['_shadowStrength']) {
- this._shadowStrength = value
- }
-
- get useNBT(): NonNullable {
- if (this._useNBT !== undefined) return this._useNBT
- const defaultConfig = BoneConfig.getDefault()
- return defaultConfig.useNBT
- }
- set useNBT(value: BoneConfig['_useNBT']) {
- this._useNBT = value
- }
-
- getDefault(): TextDisplayConfig {
- return TextDisplayConfig.fromJSON({
- billboard: 'center',
- })
- }
-
- get tickingCommands(): NonNullable {
- if (this._billboard !== undefined) return this._billboard
- const defaultConfig = this.getDefault()
- return defaultConfig.tickingCommands
- }
- set tickingCommands(value: NonNullable) {
- this._billboard = value
- }
-
- public toJSON(): IBlueprintTextDisplayConfigJSON {
- return {
- billboard: this._billboard,
- override_brightness: this._overrideBrightness,
- brightness_override: this._brightnessOverride,
- glowing: this._glowing,
- override_glow_color: this._overrideGlowColor,
- glow_color: this._glowColor,
- invisible: this._invisible,
- nbt: this._nbt,
- shadow_radius: this._shadowRadius,
- shadow_strength: this._shadowStrength,
- use_nbt: this._useNBT,
- }
- }
-
- public static fromJSON(json: IBlueprintTextDisplayConfigJSON): TextDisplayConfig {
- const config = new TextDisplayConfig()
- if (json.billboard !== undefined) config._billboard = json.billboard
- if (json.override_brightness !== undefined)
- config._overrideBrightness = json.override_brightness
- if (json.brightness_override !== undefined)
- config._brightnessOverride = json.brightness_override
- if (json.glowing !== undefined) config._glowing = json.glowing
- if (json.override_glow_color !== undefined)
- config._overrideGlowColor = json.override_glow_color
- if (json.glow_color !== undefined) config._glowColor = json.glow_color
- if (json.invisible !== undefined) config._invisible = json.invisible
- if (json.nbt !== undefined) config._nbt = json.nbt
- if (json.shadow_radius !== undefined) config._shadowRadius = json.shadow_radius
- if (json.shadow_strength !== undefined) config._shadowStrength = json.shadow_strength
- if (json.use_nbt !== undefined) config._useNBT = json.use_nbt
-
- return config
- }
-
- public toNBT(compound = new NbtCompound()) {
- if (this.useNBT) {
- const newData = NbtTag.fromString(this.nbt) as NbtCompound
- for (const key of newData.keys()) {
- compound.set(key, newData.get(key)!)
- }
- return compound
- }
-
- if (this._billboard) {
- compound.set('billboard', new NbtString(this.billboard))
- }
-
- if (this.overrideBrightness) {
- compound.set(
- 'brightness',
- new NbtCompound()
- .set('block', new NbtFloat(this.brightnessOverride))
- .set('sky', new NbtFloat(this.brightnessOverride))
- )
- }
-
- if (this.glowing) {
- compound.set('Glowing', new NbtByte(Number(this.glowing)))
- }
- if (this.overrideGlowColor) {
- compound.set(
- 'glow_color_override',
- new NbtInt(Number(this.glowColor.replace('#', '0x')))
- )
- }
-
- // TODO Figure out a good solution for toggling a bone's visibility...
- // if (force || config.invisible !== defaultConfig.invisible) {
- // compound.set('invisible', new NbtByte(1))
- // }
-
- if (this._shadowRadius) {
- compound.set('shadow_radius', new NbtFloat(this.shadowRadius))
- }
-
- if (this._shadowStrength) {
- compound.set('shadow_strength', new NbtFloat(this.shadowStrength))
- }
-
- return compound
- }
-
- public isDefault(): boolean {
- return this.checkIfEqual(new TextDisplayConfig())
- }
-
- public checkIfEqual(other: TextDisplayConfig) {
- return (
- this._billboard === other._billboard &&
- this._overrideBrightness === other._overrideBrightness &&
- this._brightnessOverride === other._brightnessOverride &&
- this._glowing === other._glowing &&
- this._overrideGlowColor === other._overrideGlowColor &&
- this._glowColor === other._glowColor &&
- this._invisible === other._invisible &&
- this._nbt === other._nbt &&
- this._shadowRadius === other._shadowRadius &&
- this._shadowStrength === other._shadowStrength &&
- this._useNBT === other._useNBT
- )
- }
-}
diff --git a/src/plugin/index.ts b/src/plugin/index.ts
new file mode 100644
index 00000000..3d2ea92a
--- /dev/null
+++ b/src/plugin/index.ts
@@ -0,0 +1,35 @@
+import EVENTS from '@events'
+import { PACKAGE } from '../constants'
+import { openInstallPopup } from '../ui/popups/installed'
+
+export default function register() {
+ BBPlugin.register(PACKAGE.name, {
+ title: PACKAGE.title,
+ author: PACKAGE.author.name,
+ description: PACKAGE.description,
+ icon: 'icon.svg',
+ variant: 'desktop',
+ version: PACKAGE.version,
+ min_version: PACKAGE.min_blockbench_version,
+ tags: ['Minecraft: Java Edition', 'Animation', 'Display Entities'],
+ await_loading: true,
+ onload() {
+ EVENTS.LOAD.dispatch()
+ },
+ onunload() {
+ EVENTS.UNLOAD.dispatch()
+ },
+ oninstall() {
+ EVENTS.INSTALL.dispatch()
+ openInstallPopup()
+ },
+ onuninstall() {
+ EVENTS.UNINSTALL.dispatch()
+ Blockbench.showMessageBox({
+ title: 'Animated Java has Been Uninstalled!',
+ message: 'In order to fully uninstall Animated Java, please restart Blockbench.',
+ buttons: ['OK'],
+ })
+ },
+ })
+}
diff --git a/src/pluginPackage/about.svelte b/src/plugin/package/about.svelte
similarity index 70%
rename from src/pluginPackage/about.svelte
rename to src/plugin/package/about.svelte
index 23d4fc6a..24d203b8 100644
--- a/src/pluginPackage/about.svelte
+++ b/src/plugin/package/about.svelte
@@ -39,28 +39,34 @@
Features
- Variants: Swap between different textures in-game.
- Highly optimized: Hours of performance tests and optimization tweaks have gone into Animated
- Java's Exported Data Pack to make sure it has as little performance impact as possible.
+ Function API - Simple, yet powerful, API for summoning, and controlling animated models.
- Limited Molang support. If Blockbench can render it, Animated Java can animate it.
- Text Display previewing and animation support.
- Resource Pack-less exporting. Animate Vanilla block and item models!
- Locators: Run commands relative to a locators position via keyframes.
+ Variants - Swap between different textures in-game.
+ Text Displays - Preview, edit, and animate text displays in Blockbench.
+ Keyframe Easing Curves - Create smooth animations with ease.
- Camera Plugin Support: Install the Official Camera Plugin to create cinematic camera paths with ease!
+ Locators - Execute commands using Command Keyframes, teleport entities in an animation,
+ and more.
- Animation Tweening: Create smooth transitions between animations.
- Many different configuration options.
+ Animation Tweening - Smoothly transition between animations.
- Complete Documentation at animated-java.dev/documentation/
+ Camera Support - Install the
+ Official Camera Plugin to create cinematic
+ camera paths with ease.
+
+
+ Well Optimized - Hours of effort have been poured into making Animated Java's Data Pack
+ as low-impact as possible.
+
+
+ Resource Packs are Optional - Animate Vanilla block and item models using Item and Block
+ Display entities.
+
+
+ Molang Support - If Blockbench can render your Molang expressions in the preview, you
+ can use it in Animated Java.
- And much more!
Getting started
diff --git a/src/pluginPackage/changelog.json b/src/plugin/package/changelog.json
similarity index 100%
rename from src/pluginPackage/changelog.json
rename to src/plugin/package/changelog.json
diff --git a/src/plugin/package/icon.svg b/src/plugin/package/icon.svg
new file mode 100644
index 00000000..7e3ff7ea
--- /dev/null
+++ b/src/plugin/package/icon.svg
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/plugin/package/members.yml b/src/plugin/package/members.yml
new file mode 100644
index 00000000..aa47fe81
--- /dev/null
+++ b/src/plugin/package/members.yml
@@ -0,0 +1,2 @@
+maintainers:
+ - SnaveSutit
diff --git a/src/components/dialogItems/baseDialogItem.svelte b/src/svelte-components/dialog-items/baseDialogItem.svelte
similarity index 62%
rename from src/components/dialogItems/baseDialogItem.svelte
rename to src/svelte-components/dialog-items/baseDialogItem.svelte
index c570590f..ebed76b6 100644
--- a/src/components/dialogItems/baseDialogItem.svelte
+++ b/src/svelte-components/dialog-items/baseDialogItem.svelte
@@ -1,17 +1,18 @@
-
-
-
+
+
+
+ {#if tooltip}
+
+
+ {:else}
+
+ {/if}
- {#if error_text}
+ {#if errorText}
- {#each error_text.split('\n') as text}
+ {#each errorText.split('\n') as text}
{text}
{/each}
- {:else if warning_text}
+ {:else if warningText}
- {#each warning_text.split('\n') as text}
+ {#each warningText.split('\n') as text}
{text}
{/each}
{/if}
- {#if tooltip}
-
- {tooltip}
-
- {/if}
diff --git a/src/svelte-components/sidebar-dialog-items/boxSelect.svelte b/src/svelte-components/sidebar-dialog-items/boxSelect.svelte
new file mode 100644
index 00000000..20d0d874
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/boxSelect.svelte
@@ -0,0 +1,95 @@
+
+
+
+
+ {#each Object.entries(options) as [key, option]}
+
+
{
+ $selected = key
+ }}
+ >
+
{@html option.label}
+ {#if option.type === 'image'}
+
+ {:else}
+
{@html option.description}
+ {/if}
+
+ {/each}
+
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/checkbox.svelte b/src/svelte-components/sidebar-dialog-items/checkbox.svelte
new file mode 100644
index 00000000..a37bc193
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/checkbox.svelte
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/codeBox.svelte b/src/svelte-components/sidebar-dialog-items/codeBox.svelte
new file mode 100644
index 00000000..faeb1d42
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/codeBox.svelte
@@ -0,0 +1,136 @@
+
+
+
+
+ forceNoWrap()}
+ syntax="mc-build"
+ {highlight}
+ bind:this={codeJar}
+ bind:value={$value}
+ onUpdate={value => {
+ console.log('Updated value:', value)
+ }}
+ preserveIdent
+ withLineNumbers
+ catchTab
+ addClosing
+ tab={'\t'}
+ />
+
+ {#if defaultValue !== '' && $value !== defaultValue}
+
+ ($value = defaultValue)}
+ />
+ {/if}
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/fileSelect.svelte b/src/svelte-components/sidebar-dialog-items/fileSelect.svelte
new file mode 100644
index 00000000..43eb67db
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/fileSelect.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+
+ {#if defaultValue !== '' && $value !== defaultValue}
+
+ ($value = defaultValue)} />
+ {/if}
+
+ selectFile()} />
+
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/folderSelect.svelte b/src/svelte-components/sidebar-dialog-items/folderSelect.svelte
new file mode 100644
index 00000000..cdc2a053
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/folderSelect.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+
+ {#if defaultValue !== '' && $value !== defaultValue}
+
+ ($value = defaultValue)} />
+ {/if}
+
+ selectFile()} />
+
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/lineEdit.svelte b/src/svelte-components/sidebar-dialog-items/lineEdit.svelte
new file mode 100644
index 00000000..5cbcbc17
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/lineEdit.svelte
@@ -0,0 +1,90 @@
+
+
+
+
+
+ {#if !disabled && defaultValue !== '' && $value !== defaultValue}
+
+ ($value = defaultValue)}
+ />
+ {/if}
+
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/numberSlider.svelte b/src/svelte-components/sidebar-dialog-items/numberSlider.svelte
new file mode 100644
index 00000000..073c7027
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/numberSlider.svelte
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/optionSelect.svelte b/src/svelte-components/sidebar-dialog-items/optionSelect.svelte
new file mode 100644
index 00000000..1f8628fc
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/optionSelect.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/requiredAsteriskInform.svelte b/src/svelte-components/sidebar-dialog-items/requiredAsteriskInform.svelte
new file mode 100644
index 00000000..59025adf
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/requiredAsteriskInform.svelte
@@ -0,0 +1,12 @@
+
* = Required
+
+
diff --git a/src/svelte-components/sidebar-dialog-items/vector2d.svelte b/src/svelte-components/sidebar-dialog-items/vector2d.svelte
new file mode 100644
index 00000000..ef9ef713
--- /dev/null
+++ b/src/svelte-components/sidebar-dialog-items/vector2d.svelte
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
diff --git a/src/systems/ajmeta.ts b/src/systems/ajmeta.ts
new file mode 100644
index 00000000..bc8908c0
--- /dev/null
+++ b/src/systems/ajmeta.ts
@@ -0,0 +1,101 @@
+import { normalizePath } from '@aj/util/fileUtil'
+import { sortObjectKeys } from '../util/misc'
+import { IntentionalExportError } from './exporter'
+
+interface OldSerializedAJMeta {
+ [key: string]: {
+ files?: string[]
+ }
+}
+
+interface SerializedAJMeta {
+ formatVersion?: string
+ rigs?: {
+ [key: string]: {
+ coreFiles?: string[]
+ versionedFiles?: string[]
+ }
+ }
+}
+
+export class AJMeta {
+ public coreFiles = new Set
()
+ public previousCoreFiles = new Set()
+
+ public versionedFiles = new Set()
+ public previousVersionedFiles = new Set()
+
+ private previousAJMeta: SerializedAJMeta = {}
+
+ constructor(
+ public path: string,
+ public exportNamespace: string,
+ public lastUsedExportNamespace: string,
+ public rootFolder: string
+ ) {}
+
+ read() {
+ if (!fs.existsSync(this.path)) return
+
+ try {
+ this.previousAJMeta = JSON.parse(fs.readFileSync(this.path, 'utf-8'))
+ } catch (e) {
+ throw new IntentionalExportError(`Failed to read existing AJMeta file: ${e}`)
+ }
+
+ if (this.previousAJMeta.formatVersion !== '1.0.0') {
+ // TODO - Use our new standardized solution to handle file versioning.
+ // Assume the file is outdated, and update it.
+ const outdated = this.previousAJMeta as OldSerializedAJMeta
+ this.previousAJMeta = {
+ formatVersion: '1.0.0',
+ rigs: {},
+ }
+ for (const [key, value] of Object.entries(outdated)) {
+ this.previousAJMeta.rigs![key] = {
+ versionedFiles: value.files,
+ }
+ }
+ }
+
+ this.previousAJMeta.rigs ??= {}
+
+ const lastNamespaceData = this.previousAJMeta.rigs[this.lastUsedExportNamespace]
+ if (lastNamespaceData) {
+ if (!Array.isArray(lastNamespaceData.versionedFiles))
+ lastNamespaceData.versionedFiles = []
+ for (const file of lastNamespaceData.versionedFiles) {
+ this.previousVersionedFiles.add(PathModule.join(this.rootFolder, file))
+ }
+ delete this.previousAJMeta.rigs[this.lastUsedExportNamespace]
+ }
+
+ const namespaceData = this.previousAJMeta.rigs[this.exportNamespace]
+ if (namespaceData) {
+ if (!Array.isArray(namespaceData.versionedFiles)) namespaceData.versionedFiles = []
+ for (const file of namespaceData.versionedFiles) {
+ this.previousVersionedFiles.add(PathModule.join(this.rootFolder, file))
+ }
+ delete this.previousAJMeta.rigs[this.exportNamespace]
+ }
+ }
+
+ write() {
+ const resourcePackFolder = PathModule.dirname(this.path)
+ const content: SerializedAJMeta = {
+ formatVersion: '1.0.0',
+ rigs: sortObjectKeys({
+ ...this.previousAJMeta.rigs,
+ [this.exportNamespace]: {
+ coreFiles: Array.from(this.coreFiles)
+ .map(v => normalizePath(PathModule.relative(resourcePackFolder, v)))
+ .sort(),
+ versionedFiles: Array.from(this.versionedFiles)
+ .map(v => normalizePath(PathModule.relative(resourcePackFolder, v)))
+ .sort(),
+ },
+ }),
+ }
+ fs.writeFileSync(this.path, autoStringify(content))
+ }
+}
diff --git a/src/systems/animationRenderer.ts b/src/systems/animation-renderer/index.ts
similarity index 93%
rename from src/systems/animationRenderer.ts
rename to src/systems/animation-renderer/index.ts
index 10e919d7..30da8bad 100644
--- a/src/systems/animationRenderer.ts
+++ b/src/systems/animation-renderer/index.ts
@@ -1,19 +1,19 @@
-import * as crypto from 'crypto'
-import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '../interface/dialog/exportProgress'
import {
getKeyframeCommands,
getKeyframeExecuteCondition,
getKeyframeRepeat,
getKeyframeRepeatFrequency,
getKeyframeVariant,
-} from '../mods/customKeyframesMod'
-import { TextDisplay } from '../outliner/textDisplay'
-import { VanillaBlockDisplay } from '../outliner/vanillaBlockDisplay'
-import { VanillaItemDisplay } from '../outliner/vanillaItemDisplay'
-import { sanitizePathName, sanitizeStorageKey } from '../util/minecraftUtil'
-import { eulerFromQuaternion, roundToNth } from '../util/misc'
-import { AnyRenderedNode, IRenderedRig } from './rigRenderer'
-import { sleepForAnimationFrame } from './util'
+} from '@aj/blockbench-mods/misc/customKeyframes'
+import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '@aj/ui/dialogs/export-progress'
+import { sanitizePathName, sanitizeStorageKey } from '@aj/util/minecraftUtil'
+import { eulerFromQuaternion, roundToNth } from '@aj/util/misc'
+import * as crypto from 'crypto'
+import { BlockDisplay } from '../../blockbench-additions/outliner-elements/blockDisplay'
+import { ItemDisplay } from '../../blockbench-additions/outliner-elements/itemDisplay'
+import { TextDisplay } from '../../blockbench-additions/outliner-elements/textDisplay'
+import type { AnyRenderedNode, IRenderedRig } from '../rig-renderer'
+import { sleepForAnimationFrame } from '../util'
export function correctSceneAngle() {
main_preview.controls.rotateLeft(Math.PI)
@@ -44,10 +44,10 @@ function getNodeMatrix(node: OutlinerElement, scale: number) {
function getDecomposedTransformation(matrix: THREE.Matrix4) {
const translation = new THREE.Vector3()
- const left_rotation = new THREE.Quaternion()
+ const leftRotation = new THREE.Quaternion()
const scale = new THREE.Vector3()
- matrix.decompose(translation, left_rotation, scale)
- return { translation, left_rotation, scale }
+ matrix.decompose(translation, leftRotation, scale)
+ return { translation, left_rotation: leftRotation, scale }
}
function threeAxisRotationToTwoAxisRotation(rot: THREE.Quaternion): ArrayVector2 {
@@ -230,7 +230,7 @@ export function getFrame(
case 'struct': {
matrix = getNodeMatrix(outlinerNode, 1)
// Only add the frame if the matrix has changed, or this is the first frame
- if (lastFrame && lastFrame.matrix.equals(matrix)) continue
+ if (lastFrame?.matrix.equals(matrix)) continue
lastFrameCache.set(uuid, { matrix, keyframe })
break
}
@@ -311,8 +311,8 @@ export function updatePreview(animation: _Animation, time: number) {
...NullObject.all,
...Locator.all,
...TextDisplay.all,
- ...VanillaBlockDisplay.all,
- ...VanillaItemDisplay.all,
+ ...BlockDisplay.all,
+ ...ItemDisplay.all,
]
if (OutlinerElement.types.camera) {
nodes.push(...OutlinerElement.types.camera.all)
@@ -395,8 +395,8 @@ export function getAnimatableNodes(): OutlinerElement[] {
...Group.all,
...Locator.all,
...TextDisplay.all,
- ...VanillaBlockDisplay.all,
- ...VanillaItemDisplay.all,
+ ...BlockDisplay.all,
+ ...ItemDisplay.all,
...(OutlinerElement.types.camera ? OutlinerElement.types.camera.all : []),
]
}
diff --git a/src/systems/cleaner.ts b/src/systems/cleaner.ts
index 5e8692f8..c15c6236 100644
--- a/src/systems/cleaner.ts
+++ b/src/systems/cleaner.ts
@@ -1,5 +1,5 @@
import { isFunctionTagPath } from '../util/fileUtil'
-import { IFunctionTag, parseDataPackPath } from '../util/minecraftUtil'
+import { type IFunctionTag, parseDataPackPath } from '../util/minecraftUtil'
import { getExportPaths } from './exporter'
import { AJMeta } from './global'
import { replacePathPart } from './util'
@@ -14,11 +14,11 @@ export async function cleanupExportedFiles() {
// displayItemPath,
} = getExportPaths()
- if (aj.resource_pack_export_mode === 'raw') {
+ if (aj.resource_pack_export_mode === 'folder') {
const assetsMetaPath = PathModule.join(resourcePackFolder, 'assets.ajmeta')
const assetsMeta = new AJMeta(
assetsMetaPath,
- aj.export_namespace,
+ aj.id,
Project!.last_used_export_namespace,
resourcePackFolder
)
@@ -31,7 +31,7 @@ export async function cleanupExportedFiles() {
for (const file of assetsMeta.previousVersionedFiles) {
if (!isFunctionTagPath(file)) {
if (fs.existsSync(file)) await fs.promises.unlink(file)
- } else if (aj.export_namespace !== Project!.last_used_export_namespace) {
+ } else if (aj.id !== Project!.last_used_export_namespace) {
const resourceLocation = parseDataPackPath(file)!.resourceLocation
if (
resourceLocation.startsWith(
@@ -42,7 +42,7 @@ export async function cleanupExportedFiles() {
const newPath = replacePathPart(
file,
Project!.last_used_export_namespace,
- aj.export_namespace
+ aj.id
)
await fs.promises.mkdir(PathModule.dirname(newPath), { recursive: true })
await fs.promises.copyFile(file, newPath)
@@ -66,11 +66,11 @@ export async function cleanupExportedFiles() {
assetsMeta.write()
}
- if (aj.data_pack_export_mode === 'raw') {
+ if (aj.data_pack_export_mode === 'folder') {
const dataMetaPath = PathModule.join(dataPackFolder, 'data.ajmeta')
const dataMeta = new AJMeta(
dataMetaPath,
- aj.export_namespace,
+ aj.id,
Project!.last_used_export_namespace,
dataPackFolder
)
@@ -82,7 +82,7 @@ export async function cleanupExportedFiles() {
const removedFolders = new Set()
for (const file of [...dataMeta.previousCoreFiles, ...dataMeta.previousVersionedFiles]) {
if (isFunctionTagPath(file) && fs.existsSync(file)) {
- if (aj.export_namespace !== Project!.last_used_export_namespace) {
+ if (aj.id !== Project!.last_used_export_namespace) {
const resourceLocation = parseDataPackPath(file)!.resourceLocation
if (
resourceLocation.startsWith(
@@ -92,7 +92,7 @@ export async function cleanupExportedFiles() {
const newPath = replacePathPart(
file,
Project!.last_used_export_namespace,
- aj.export_namespace
+ aj.id
)
await fs.promises.mkdir(PathModule.dirname(newPath), { recursive: true })
await fs.promises.copyFile(file, newPath)
@@ -106,7 +106,7 @@ export async function cleanupExportedFiles() {
content.values = content.values.filter(
v =>
typeof v === 'string' &&
- (!v.startsWith(`animated_java:${aj.export_namespace}/`) ||
+ (!v.startsWith(`animated_java:${aj.id}/`) ||
!v.startsWith(`animated_java:${Project!.last_used_export_namespace}/`))
)
await fs.promises.writeFile(file, autoStringify(content))
diff --git a/src/systems/datapackCompiler/compiler.ts b/src/systems/datapack-compiler/compiler.ts
similarity index 94%
rename from src/systems/datapackCompiler/compiler.ts
rename to src/systems/datapack-compiler/compiler.ts
index 4648a4e3..b0b2e88f 100644
--- a/src/systems/datapackCompiler/compiler.ts
+++ b/src/systems/datapack-compiler/compiler.ts
@@ -1,8 +1,8 @@
import { Parser, SyncIo, Tokenizer } from 'mc-build'
import { Compiler, VariableMap } from 'mc-build/dist/mcl/Compiler'
import { getDataPackFormat } from '../../util/minecraftUtil'
-import { MinecraftVersion } from '../global'
-import { ExportedFile } from '../util'
+import { type MinecraftVersion } from '../global'
+import { type ExportedFile } from '../util'
interface CompilerOptions {
path: string
diff --git a/src/systems/datapackCompiler/index.ts b/src/systems/datapack-compiler/index.ts
similarity index 54%
rename from src/systems/datapackCompiler/index.ts
rename to src/systems/datapack-compiler/index.ts
index 7765baa1..50a63e18 100644
--- a/src/systems/datapackCompiler/index.ts
+++ b/src/systems/datapack-compiler/index.ts
@@ -7,46 +7,39 @@ import {
NbtString,
NbtTag,
} from 'deepslate/lib/nbt'
-import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '../../interface/dialog/exportProgress'
-import { BoneConfig, TextDisplayConfig } from '../../nodeConfigs'
+import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '../../ui/dialogs/export-progress'
import { isFunctionTagPath } from '../../util/fileUtil'
import {
getDataPackFormat,
- IFunctionTag,
mergeTag,
parseBlock,
parseDataPackPath,
parseResourceLocation,
+ type IFunctionTag,
} from '../../util/minecraftUtil'
import { eulerFromQuaternion, floatToHex, roundTo, tinycolorToDecimal } from '../../util/misc'
import { MSLimiter } from '../../util/msLimiter'
import { Variant } from '../../variants'
-import { IRenderedAnimation } from '../animationRenderer'
-import mcbFiles from '../datapackCompiler/mcbFiles'
+import { AJMeta } from '../ajmeta'
+import type { IRenderedAnimation } from '../animation-renderer'
import { IntentionalExportError } from '../exporter'
-import { AJMeta, MinecraftVersion, PackMeta, PackMetaFormats } from '../global'
-import { JsonText } from '../minecraft/jsonText'
-import { AnyRenderedNode, IRenderedRig, IRenderedVariant } from '../rigRenderer'
+import { PackMeta, type MinecraftVersion, type PackMetaFormats } from '../global'
+import { BoneConfig, CommonDisplayConfig, TextDisplayConfig } from '../node-configs'
+import type { AnyRenderedNode, IRenderedRig } from '../rig-renderer'
import {
- arrayToNbtFloatArray,
- ExportedFile,
matrixToNbtFloatArray,
replacePathPart,
transformationToNbt,
+ type ExportedFile,
} from '../util'
import { compile } from './compiler'
-import { TAGS } from './tags'
+import { OBJECTIVES } from './objectives'
+import { getNodeTags, TAGS } from './tags'
+import { TELLRAW } from './tellraw'
+import mcbFiles from './versions'
const BONE_TYPES = ['bone', 'text_display', 'item_display', 'block_display']
-namespace OBJECTIVES {
- export const I = () => 'aj.i'
- export const ID = () => 'aj.id'
- export const FRAME = (animationName: string) => `aj.${animationName}.frame`
- export const IS_RIG_LOADED = () => 'aj.is_rig_loaded'
- export const TWEEN_DURATION = () => 'aj.tween_duration'
-}
-
// 🗡 🏹 🔱 🧪 ⚗ 🎣 🛡 🪓 ⛏ ☠ ☮ ☯ ♠ Ω ♤ ♣ ♧ ❤ ♥ ♡
// ♦ ♢ ★ ☆ ☄ ☽ ☀ ☁ ☂ ☃ ◎ ☺ ☻ ☹ ☜ ☞ ♪ ♩ ♫ ♬ ✂ ✉ ∞ ♂ ♀ ❤ ™ ®
// © ✘ ■ □ ▲ △ ▼ ▽ ◆ ◇ ○ ◎ ● Δ ʊ ღ ₪ ½ ⅓ ⅔ ¼ ¾ ⅛ ⅜ ⅝ ⅞ ∧ ∨ ∩
@@ -55,598 +48,8 @@ namespace OBJECTIVES {
// ▓ □〓≡ ╝╚╔╗╬ ╓ ╩┌ ┐└ ┘ ↑ ↓ → ← ↔ ▀▐ ░ ▒▬ ♦ ◘
// → ✎ ❣ ✚ ✔ ✖ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ⊻ ⊼ ⊽ ⋃ ⌀ ⌂
-function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList {
- const tags: string[] = []
-
- const parentNames: Array<{ name: string; type: string }> = []
-
- function recurseParents(n: AnyRenderedNode) {
- if (n.parent === 'root') {
- // Root is ignored
- } else if (n.parent) {
- parentNames.push({
- name: rig.nodes[n.parent].path_name,
- type: rig.nodes[n.parent].type,
- })
- recurseParents(rig.nodes[n.parent])
- }
- }
- recurseParents(node)
-
- const hasParent = node.parent && node.parent !== 'root'
-
- tags.push(
- // Global
- TAGS.NEW(),
- TAGS.GLOBAL_ENTITY(),
- TAGS.GLOBAL_NODE(),
- TAGS.GLOBAL_NODE_NAMED(node.path_name),
- // Project
- TAGS.PROJECT_ENTITY(Project!.animated_java.export_namespace),
- TAGS.PROJECT_NODE(Project!.animated_java.export_namespace),
- TAGS.PROJECT_NODE_NAMED(Project!.animated_java.export_namespace, node.path_name)
- )
-
- if (!hasParent) {
- tags.push(TAGS.GLOBAL_ROOT_CHILD())
- }
- switch (node.type) {
- case 'bone': {
- tags.push(
- // Global
- TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
- TAGS.GLOBAL_BONE(),
- TAGS.GLOBAL_BONE_TREE(node.path_name), // Tree includes self
- TAGS.GLOBAL_BONE_TREE_BONE(node.path_name), // Tree includes self
- // Project
- TAGS.PROJECT_DISPLAY_NODE_NAMED(
- Project!.animated_java.export_namespace,
- node.path_name
- ),
- TAGS.PROJECT_BONE(Project!.animated_java.export_namespace),
- TAGS.PROJECT_BONE_NAMED(Project!.animated_java.export_namespace, node.path_name),
- TAGS.PROJECT_BONE_TREE(Project!.animated_java.export_namespace, node.path_name), // Tree includes self
- TAGS.PROJECT_BONE_TREE_BONE(Project!.animated_java.export_namespace, node.path_name) // Tree includes self
- )
- if (!hasParent) {
- // Nodes without parents are assumed to be root nodes
- tags.push(TAGS.GLOBAL_ROOT_CHILD_BONE())
- } else {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
- TAGS.GLOBAL_BONE_CHILD_BONE(parentNames[0].name),
- // Project
- TAGS.PROJECT_BONE_CHILD(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- ),
- TAGS.PROJECT_BONE_CHILD_BONE(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- )
- )
- }
- for (const { name } of parentNames) {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_DECENDANT(name),
- TAGS.GLOBAL_BONE_DECENDANT_BONE(name),
- TAGS.GLOBAL_BONE_TREE(name),
- TAGS.GLOBAL_BONE_TREE_BONE(name),
- // Project
- TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_DECENDANT_BONE(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_TREE(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_TREE_BONE(Project!.animated_java.export_namespace, name)
- )
- }
- break
- }
- case 'item_display': {
- tags.push(
- // Global
- TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
- TAGS.GLOBAL_ITEM_DISPLAY(),
- // Project
- TAGS.PROJECT_DISPLAY_NODE_NAMED(
- Project!.animated_java.export_namespace,
- node.path_name
- ),
- TAGS.PROJECT_ITEM_DISPLAY(Project!.animated_java.export_namespace),
- TAGS.PROJECT_ITEM_DISPLAY_NAMED(
- Project!.animated_java.export_namespace,
- node.path_name
- )
- )
- if (!hasParent) {
- // Nodes without parents are assumed to be root nodes
- tags.push(TAGS.GLOBAL_ROOT_CHILD_ITEM_DISPLAY())
- } else {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
- TAGS.GLOBAL_BONE_CHILD_ITEM_DISPLAY(parentNames[0].name),
- // Project
- TAGS.PROJECT_BONE_CHILD(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- ),
- TAGS.PROJECT_BONE_CHILD_ITEM_DISPLAY(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- )
- )
- }
- for (const { name } of parentNames) {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_DECENDANT(name),
- TAGS.GLOBAL_BONE_DECENDANT_ITEM_DISPLAY(name),
- TAGS.GLOBAL_BONE_TREE(name),
- // Project
- TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_DECENDANT_ITEM_DISPLAY(
- Project!.animated_java.export_namespace,
- name
- ),
- TAGS.PROJECT_BONE_TREE(Project!.animated_java.export_namespace, name)
- )
- }
- break
- }
- case 'block_display': {
- tags.push(
- // Global
- TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
- TAGS.GLOBAL_BLOCK_DISPLAY(),
- // Project
- TAGS.PROJECT_DISPLAY_NODE_NAMED(
- Project!.animated_java.export_namespace,
- node.path_name
- ),
- TAGS.PROJECT_BLOCK_DISPLAY(Project!.animated_java.export_namespace),
- TAGS.PROJECT_BLOCK_DISPLAY_NAMED(
- Project!.animated_java.export_namespace,
- node.path_name
- )
- )
- if (!hasParent) {
- // Nodes without parents are assumed to be root nodes
- tags.push(TAGS.GLOBAL_ROOT_CHILD_BLOCK_DISPLAY())
- } else {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
- TAGS.GLOBAL_BONE_CHILD_BLOCK_DISPLAY(parentNames[0].name),
- // Project
- TAGS.PROJECT_BONE_CHILD(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- ),
- TAGS.PROJECT_BONE_CHILD_BLOCK_DISPLAY(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- )
- )
- }
- for (const { name } of parentNames) {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_DECENDANT(name),
- TAGS.GLOBAL_BONE_DECENDANT_BLOCK_DISPLAY(name),
- TAGS.GLOBAL_BONE_TREE(name),
- // Project
- TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_DECENDANT_BLOCK_DISPLAY(
- Project!.animated_java.export_namespace,
- name
- ),
- TAGS.PROJECT_BONE_TREE(Project!.animated_java.export_namespace, name)
- )
- }
- break
- }
- case 'text_display': {
- tags.push(
- // Global
- TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
- TAGS.GLOBAL_TEXT_DISPLAY(),
- // Project
- TAGS.PROJECT_DISPLAY_NODE_NAMED(
- Project!.animated_java.export_namespace,
- node.path_name
- ),
- TAGS.PROJECT_TEXT_DISPLAY(Project!.animated_java.export_namespace),
- TAGS.PROJECT_TEXT_DISPLAY_NAMED(
- Project!.animated_java.export_namespace,
- node.path_name
- )
- )
- if (!hasParent) {
- // Nodes without parents are assumed to be root nodes
- tags.push(TAGS.GLOBAL_ROOT_CHILD_TEXT_DISPLAY())
- } else {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
- TAGS.GLOBAL_BONE_CHILD_TEXT_DISPLAY(parentNames[0].name),
- // Project
- TAGS.PROJECT_BONE_CHILD(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- ),
- TAGS.PROJECT_BONE_CHILD_TEXT_DISPLAY(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- )
- )
- }
- for (const { name } of parentNames) {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_DECENDANT(name),
- TAGS.GLOBAL_BONE_DECENDANT_TEXT_DISPLAY(name),
- TAGS.GLOBAL_BONE_TREE(name),
- // Project
- TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_DECENDANT_TEXT_DISPLAY(
- Project!.animated_java.export_namespace,
- name
- ),
- TAGS.PROJECT_BONE_TREE(Project!.animated_java.export_namespace, name)
- )
- }
- break
- }
- case 'locator': {
- tags.push(
- // Global
- TAGS.GLOBAL_LOCATOR(),
- // Project
- TAGS.PROJECT_LOCATOR(Project!.animated_java.export_namespace),
- TAGS.PROJECT_LOCATOR_NAMED(Project!.animated_java.export_namespace, node.path_name)
- )
- if (!hasParent) {
- // Nodes without parents are assumed to be root nodes
- tags.push(TAGS.GLOBAL_ROOT_CHILD_LOCATOR())
- } else {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
- TAGS.GLOBAL_BONE_CHILD_LOCATOR(parentNames[0].name),
- // Project
- TAGS.PROJECT_BONE_CHILD(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- ),
- TAGS.PROJECT_BONE_CHILD_LOCATOR(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- )
- )
- }
- for (const { name } of parentNames) {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_DECENDANT(name),
- TAGS.GLOBAL_BONE_DECENDANT_LOCATOR(name),
- TAGS.GLOBAL_BONE_TREE(name),
- // Project
- TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_DECENDANT_LOCATOR(
- Project!.animated_java.export_namespace,
- name
- ),
- TAGS.PROJECT_BONE_TREE(Project!.animated_java.export_namespace, name)
- )
- }
- break
- }
- case 'camera': {
- tags.push(
- // Global
- TAGS.GLOBAL_CAMERA(),
- // Project
- TAGS.PROJECT_CAMERA(Project!.animated_java.export_namespace),
- TAGS.PROJECT_CAMERA_NAMED(Project!.animated_java.export_namespace, node.path_name)
- )
- if (!hasParent) {
- // Nodes without parents are assumed to be root nodes
- tags.push(TAGS.GLOBAL_ROOT_CHILD_CAMERA())
- } else {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
- TAGS.GLOBAL_BONE_CHILD_CAMERA(parentNames[0].name),
- // Project
- TAGS.PROJECT_BONE_CHILD(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- ),
- TAGS.PROJECT_BONE_CHILD_CAMERA(
- Project!.animated_java.export_namespace,
- parentNames[0].name
- )
- )
- }
- for (const { name } of parentNames) {
- tags.push(
- // Global
- TAGS.GLOBAL_BONE_DECENDANT(name),
- TAGS.GLOBAL_BONE_DECENDANT_CAMERA(name),
- TAGS.GLOBAL_BONE_TREE(name),
- // Project
- TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.export_namespace, name),
- TAGS.PROJECT_BONE_DECENDANT_CAMERA(
- Project!.animated_java.export_namespace,
- name
- ),
- TAGS.PROJECT_BONE_TREE(Project!.animated_java.export_namespace, name)
- )
- }
- break
- }
- default: {
- throw new IntentionalExportError(
- `Attempted to get tags for an unknown node type: '${node.type}'!`
- )
- }
- }
-
- return new NbtList(tags.sort().map(v => new NbtString(v)))
-}
-
-const TELLRAW_PREFIX = () =>
- new JsonText([
- { text: '\n[', color: 'gray' },
- { text: 'AJ', color: 'aqua' },
- '] ',
- [
- { text: '(from ', color: 'gray', italic: true },
- Project!.animated_java.export_namespace,
- ')',
- ],
- ' -> ',
- ])
-
-const TELLRAW_ERROR_PREFIX = () =>
- new JsonText([TELLRAW_PREFIX(), { text: 'ERROR: ', color: 'red' }, '\n '])
-
-const TELLRAW_SUFFIX = () => new JsonText(['\n'])
-
-const TELLRAW_LEARN_MORE_LINK = (url: string) =>
- new JsonText([
- '\n ',
- {
- text: 'Click here to learn more',
- color: 'blue',
- underlined: true,
- italic: true,
- clickEvent: { action: 'open_url', value: url },
- },
- ])
-
-namespace TELLRAW {
- export const RIG_OUTDATED = () =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- { text: 'The ', color: 'red' },
- { text: Project!.animated_java.export_namespace, color: 'yellow' },
- { text: ' rig instance at', color: 'red' },
- [
- { text: ' [', color: 'yellow' },
- { score: { name: '#this.x', objective: OBJECTIVES.I() } },
- ', ',
- { score: { name: '#this.y', objective: OBJECTIVES.I() } },
- ', ',
- { score: { name: '#this.z', objective: OBJECTIVES.I() } },
- ']',
- ],
- {
- text: ' is outdated! It will not function correctly and should be removed or re-summoned.',
- color: 'red',
- },
- '\n ',
- {
- text: '[Click Here to Teleport to the Rig Instance]',
- clickEvent: {
- action: 'suggest_command',
- value: '/tp @s $(x) $(y) $(z)',
- },
- color: 'aqua',
- underlined: true,
- },
- TELLRAW_SUFFIX(),
- ])
- export const RIG_OUTDATED_TEXT_DISPLAY = () =>
- new JsonText([
- '',
- {
- text: 'This rig instance is outdated!\\nIt will not function correctly and should be removed or re-summoned.',
- color: 'red',
- },
- ])
- export const FUNCTION_NOT_EXECUTED_AS_ROOT_ERROR = (functionName: string) =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- {
- text: 'This function',
- color: 'blue',
- underlined: true,
- hoverEvent: {
- action: 'show_text',
- contents: [{ text: functionName, color: 'yellow' }],
- },
- },
- { text: " must be executed as the rig's root entity.", color: 'red' },
- '\n',
- TELLRAW_LEARN_MORE_LINK(
- 'https://animated-java.dev/docs/rigs/controlling-a-rig-instance'
- ),
- TELLRAW_SUFFIX(),
- ])
- // Summon Function
- export const VARIANT_CANNOT_BE_EMPTY = () =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- { text: 'variant', color: 'yellow' },
- { text: ' cannot be an empty string.', color: 'red' },
- TELLRAW_SUFFIX(),
- ])
- export const INVALID_VARIANT = (
- variantName: string,
- variants: Record
- ) =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- { text: 'The variant ', color: 'red' },
- { text: variantName, color: 'yellow' },
- { text: ' does not exist.', color: 'red' },
- '\n ',
- { text: ' ≡ ', color: 'white' },
- { text: 'Available Variants:', color: 'green' },
- ...Object.values(variants).map(
- variant =>
- new JsonText([
- '\n ',
- ' ',
- ' ',
- { text: ' ● ', color: 'gray' },
- { text: variant.name, color: 'yellow' },
- ])
- ),
- TELLRAW_SUFFIX(),
- ])
- export const ANIMATION_CANNOT_BE_EMPTY = () =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- { text: 'animation', color: 'yellow' },
- { text: ' cannot be an empty string.', color: 'red' },
- TELLRAW_SUFFIX(),
- ])
- export const FRAME_CANNOT_BE_NEGATIVE = () =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- { text: 'frame', color: 'yellow' },
- { text: ' must be a non-negative integer.', color: 'red' },
- TELLRAW_SUFFIX(),
- ])
- export const INVALID_ANIMATION = (animationName: string, animations: IRenderedAnimation[]) =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- { text: 'The animation ', color: 'red' },
- { text: animationName, color: 'yellow' },
- { text: ' does not exist.', color: 'red' },
- '\n ',
- { text: ' ≡ ', color: 'white' },
- { text: 'Available Animations:', color: 'green' },
- ...animations.map(
- anim =>
- new JsonText([
- '\n ',
- ' ',
- ' ',
- { text: ' ● ', color: 'gray' },
- { text: anim.path_name, color: 'yellow' },
- ])
- ),
- TELLRAW_SUFFIX(),
- ])
- export const NO_VARIANTS = () =>
- new JsonText([
- '',
- TELLRAW_ERROR_PREFIX(),
- { text: 'No variants are available.', color: 'red' },
- TELLRAW_SUFFIX(),
- ])
- export const INVALID_VERSION = () =>
- new JsonText([
- TELLRAW_ERROR_PREFIX(),
- [
- {
- text: 'Attempting to load an Animated Java Data Pack that was exported for ',
- color: 'red',
- },
- {
- text: `Minecraft ${Project!.animated_java.target_minecraft_versions}`,
- color: 'aqua',
- },
- { text: ' in the wrong version!', color: 'red' },
- {
- text: '\n Please ensure that the data pack is loaded in the correct version, or that your blueprint settings are configured to target the correct version(s) of Minecraft.',
- color: 'yellow',
- },
- ],
- TELLRAW_SUFFIX(),
- ])
- export const UNINSTALL = () =>
- new JsonText([
- TELLRAW_PREFIX(),
- { text: 'Successfully removed known animation scoreboard objectives.', color: 'red' },
- {
- text: "\nIf you have exported multiple times you may have to manually remove some objectives from previous exports manually, as Animated Java can only remove the latest export's objectives.",
- color: 'gray',
- italic: true,
- },
- TELLRAW_SUFFIX(),
- ])
- export const LOCATOR_NOT_FOUND = () =>
- new JsonText([
- TELLRAW_PREFIX(),
- { text: 'Locator ', color: 'red' },
- { nbt: 'args.name', storage: 'aj:temp', color: 'aqua' },
- { text: ' not found!', color: 'red' },
- {
- text: "\nPlease ensure that it's name is spelled correctly.",
- color: 'gray',
- italic: true,
- },
- TELLRAW_SUFFIX(),
- ])
- export const LOCATOR_NOT_FOUND_ENTITY = () =>
- new JsonText([
- TELLRAW_PREFIX(),
- { text: 'Locator ', color: 'red' },
- { nbt: 'args.name', storage: 'aj:temp', color: 'aqua' },
- { text: ' not found!', color: 'red' },
- {
- text: '\nPlease ensure that the locator has ',
- color: 'gray',
- italic: true,
- },
- {
- text: 'Use Entity',
- color: 'yellow',
- italic: true,
- },
- {
- text: " enabled in it's config, and it's name is spelled correctly.",
- color: 'gray',
- italic: true,
- },
- TELLRAW_SUFFIX(),
- ])
- export const CAMERA_NOT_FOUND_ENTITY = () =>
- new JsonText([
- TELLRAW_PREFIX(),
- { text: 'Camera ', color: 'red' },
- { nbt: 'args.name', storage: 'aj:temp', color: 'aqua' },
- { text: ' not found!', color: 'red' },
- {
- text: "\nPlease ensure that it's name is spelled correctly.",
- color: 'gray',
- italic: true,
- },
- TELLRAW_SUFFIX(),
- ])
+export function arrayToNbtFloatArray(array: number[]) {
+ return new NbtList(array.map(v => new NbtFloat(v)))
}
async function generateRootEntityPassengers(
@@ -680,8 +83,8 @@ async function generateRootEntityPassengers(
new NbtString(TAGS.NEW()),
new NbtString(TAGS.GLOBAL_ENTITY()),
new NbtString(TAGS.GLOBAL_DATA()),
- new NbtString(TAGS.PROJECT_ENTITY(aj.export_namespace)),
- new NbtString(TAGS.PROJECT_DATA(aj.export_namespace)),
+ new NbtString(TAGS.PROJECT_ENTITY()),
+ new NbtString(TAGS.PROJECT_DATA()),
])
)
.set(
@@ -772,15 +175,10 @@ async function generateRootEntityPassengers(
)
break
}
- default: {
- throw new Error(
- `Unsupported Minecraft version '${version}' for item display!`
- )
- }
}
if (node.configs?.default) {
- BoneConfig.fromJSON(node.configs.default).toNBT(passenger)
+ new CommonDisplayConfig().fromJSON(node.configs.default).toNBT(passenger)
}
passenger.set('height', new NbtFloat(aj.bounding_box[1]))
@@ -843,7 +241,7 @@ async function generateRootEntityPassengers(
passenger.set('alignment', new NbtString(node.align))
if (node.config) {
- TextDisplayConfig.fromJSON(node.config).toNBT(passenger)
+ new TextDisplayConfig().fromJSON(node.config).toNBT(passenger)
}
break
}
@@ -857,7 +255,7 @@ async function generateRootEntityPassengers(
)
if (node.config) {
- BoneConfig.fromJSON(node.config).toNBT(passenger)
+ new CommonDisplayConfig().fromJSON(node.config).toNBT(passenger)
}
break
}
@@ -884,7 +282,7 @@ async function generateRootEntityPassengers(
)
if (node.config) {
- BoneConfig.fromJSON(node.config).toNBT(passenger)
+ new CommonDisplayConfig().fromJSON(node.config).toNBT(passenger)
}
break
}
@@ -913,9 +311,9 @@ async function createAnimationStorage(rig: IRenderedRig, animations: IRenderedAn
PROGRESS_DESCRIPTION.set(`Creating Animation Storage for '${animation.storage_name}'`)
let frames = new NbtCompound()
const addFrameDataCommand = () => {
- const str = `data modify storage aj.${
- Project!.animated_java.export_namespace
- }:animations ${animation.storage_name} merge value ${frames.toString()}`
+ const str = `data modify storage aj.${Project!.animated_java.tag_prefix}:animations ${
+ animation.storage_name
+ } merge value ${frames.toString()}`
dataCommands.push(str)
frames = new NbtCompound()
}
@@ -1054,12 +452,12 @@ export default async function compileDataPack(
const ajmeta = new AJMeta(
PathModule.join(options.dataPackFolder, 'data.ajmeta'),
- aj.export_namespace,
+ aj.id,
Project!.last_used_export_namespace,
options.dataPackFolder
)
- if (aj.data_pack_export_mode === 'raw') {
+ if (aj.data_pack_export_mode === 'folder') {
ajmeta.read()
}
@@ -1077,7 +475,7 @@ export default async function compileDataPack(
? PathModule.join(
options.dataPackFolder,
`animated_java_${version.replaceAll('.', '_')}`
- )
+ )
: coreDataPackFolder
await dataPackCompiler({
@@ -1108,7 +506,7 @@ export default async function compileDataPack(
console.log('Exported Files:', globalCoreFiles.size + globalVersionSpecificFiles.size)
const packMetaPath = PathModule.join(options.dataPackFolder, 'pack.mcmeta')
- let packMeta = new PackMeta(
+ const packMeta = new PackMeta(
packMetaPath,
0,
[],
@@ -1120,7 +518,7 @@ export default async function compileDataPack(
if (targetVersions.length > 1) {
for (const version of targetVersions) {
- let format: PackMetaFormats = getDataPackFormat(version)
+ const format: PackMetaFormats = getDataPackFormat(version)
packMeta.supportedFormats.push(format)
const existingOverlay = [...packMeta.overlayEntries].find(
@@ -1142,7 +540,7 @@ export default async function compileDataPack(
includeInAJMeta: false,
})
- if (aj.data_pack_export_mode === 'raw') {
+ if (aj.data_pack_export_mode === 'folder') {
await removeFiles(ajmeta, options.dataPackFolder)
// Write new files
@@ -1166,14 +564,14 @@ export default async function compileDataPack(
async function removeFiles(ajmeta: AJMeta, dataPackFolder: string) {
console.time('Removing Files took')
const aj = Project!.animated_java
- if (aj.data_pack_export_mode === 'raw') {
+ if (aj.data_pack_export_mode === 'folder') {
PROGRESS_DESCRIPTION.set('Removing Old Data Pack Files...')
PROGRESS.set(0)
MAX_PROGRESS.set(ajmeta.previousVersionedFiles.size)
const removedFolders = new Set()
for (const file of ajmeta.previousVersionedFiles) {
if (isFunctionTagPath(file) && fs.existsSync(file)) {
- if (aj.export_namespace !== Project!.last_used_export_namespace) {
+ if (aj.id !== Project!.last_used_export_namespace) {
const resourceLocation = parseDataPackPath(file)!.resourceLocation
if (
resourceLocation.startsWith(
@@ -1183,7 +581,7 @@ async function removeFiles(ajmeta: AJMeta, dataPackFolder: string) {
const newPath = replacePathPart(
file,
Project!.last_used_export_namespace,
- aj.export_namespace
+ aj.id
)
await fs.promises.mkdir(PathModule.dirname(newPath), { recursive: true })
await fs.promises.copyFile(file, newPath)
@@ -1206,7 +604,7 @@ async function removeFiles(ajmeta: AJMeta, dataPackFolder: string) {
content.values = content.values.filter(
v =>
typeof v === 'string' &&
- (!v.startsWith(`animated_java:${aj.export_namespace}/`) ||
+ (!v.startsWith(`animated_java:${aj.id}/`) ||
!v.startsWith(`animated_java:${Project!.last_used_export_namespace}/`))
)
await fs.promises.writeFile(file, autoStringify(content))
@@ -1243,7 +641,7 @@ const dataPackCompiler: DataPackCompiler = async ({
const aj = Project!.animated_java
const is_static = animations.length === 0
const variables = {
- export_namespace: aj.export_namespace,
+ export_namespace: aj.id,
interpolation_duration: aj.interpolation_duration,
teleportation_duration: aj.teleportation_duration,
display_item: aj.display_item,
@@ -1258,10 +656,11 @@ const dataPackCompiler: DataPackCompiler = async ({
custom_remove_commands: aj.remove_commands,
matrixToNbtFloatArray,
transformationToNbt,
- use_storage_for_animation: aj.use_storage_for_animation,
- animationStorage: aj.use_storage_for_animation
- ? await createAnimationStorage(rig, animations)
- : null,
+ use_storage_for_animation: aj.animation_system === 'storage',
+ animationStorage:
+ aj.animation_system === 'storage'
+ ? await createAnimationStorage(rig, animations)
+ : null,
rigHash,
animationHash,
boundingBox: aj.bounding_box,
@@ -1347,7 +746,7 @@ async function writeFiles(exportedFiles: Map, dataPackFold
const oldFile: IFunctionTag = JSON.parse(fs.readFileSync(path, 'utf-8'))
const newFile: IFunctionTag = JSON.parse(file.content.toString())
const merged = mergeTag(oldFile, newFile)
- if (aj.export_namespace !== Project!.last_used_export_namespace) {
+ if (aj.id !== Project!.last_used_export_namespace) {
merged.values = merged.values.filter(v => {
const value = typeof v === 'string' ? v : v.id
return (
@@ -1377,13 +776,13 @@ async function writeFiles(exportedFiles: Map, dataPackFold
location.namespace,
'tags/functions',
location.path + '.json'
- )
+ )
: PathModule.join(
dataFolder,
location.namespace,
'functions',
location.path + '.mcfunction'
- )
+ )
console.log('Checking path:', path)
if (
!(
diff --git a/src/systems/datapack-compiler/objectives.ts b/src/systems/datapack-compiler/objectives.ts
new file mode 100644
index 00000000..9fd6c878
--- /dev/null
+++ b/src/systems/datapack-compiler/objectives.ts
@@ -0,0 +1,7 @@
+export namespace OBJECTIVES {
+ export const I = () => 'aj.i'
+ export const ID = () => 'aj.id'
+ export const FRAME = (animationName: string) => `aj.${animationName}.frame`
+ export const IS_RIG_LOADED = () => 'aj.is_rig_loaded'
+ export const TWEEN_DURATION = () => 'aj.tween_duration'
+}
diff --git a/src/systems/datapack-compiler/tags.ts b/src/systems/datapack-compiler/tags.ts
new file mode 100644
index 00000000..a507ecd0
--- /dev/null
+++ b/src/systems/datapack-compiler/tags.ts
@@ -0,0 +1,479 @@
+import { NbtList, NbtString } from 'deepslate/lib/nbt'
+import { createTagPrefixFromBlueprintID } from '../../util/minecraftUtil'
+import { IntentionalExportError } from '../exporter'
+import type { AnyRenderedNode, IRenderedRig } from '../rig-renderer'
+
+export namespace TAGS {
+ // --------------------------------
+ // region Global Tags
+ // --------------------------------
+ export const NEW = () => 'aj.new'
+ export const GLOBAL_ENTITY = () => 'aj.global.entity'
+
+ export const GLOBAL_ROOT = () => 'aj.global.root'
+ export const GLOBAL_ROOT_CHILD = () => 'aj.global.root.child'
+ export const GLOBAL_ROOT_CHILD_BONE = () => 'aj.global.root.child.bone'
+ export const GLOBAL_ROOT_CHILD_ITEM_DISPLAY = () => 'aj.global.root.child.item_display'
+ export const GLOBAL_ROOT_CHILD_BLOCK_DISPLAY = () => 'aj.global.root.child.block_display'
+ export const GLOBAL_ROOT_CHILD_TEXT_DISPLAY = () => 'aj.global.root.child.text_display'
+ export const GLOBAL_ROOT_CHILD_LOCATOR = () => 'aj.global.root.child.locator'
+ export const GLOBAL_ROOT_CHILD_CAMERA = () => 'aj.global.root.child.camera'
+ export const GLOBAL_ROOT_CHILD_DATA = () => 'aj.global.root.child.data'
+
+ export const GLOBAL_NODE = () => 'aj.global.node'
+ export const GLOBAL_DISPLAY_NODE = () => 'aj.global.display_node'
+ export const GLOBAL_VANILLA_DISPLAY_NODE = () => 'aj.global.display_node'
+ export const GLOBAL_BONE = () => 'aj.global.bone'
+ export const GLOBAL_ITEM_DISPLAY = () => 'aj.global.item_display'
+ export const GLOBAL_BLOCK_DISPLAY = () => 'aj.global.block_display'
+ export const GLOBAL_TEXT_DISPLAY = () => 'aj.global.text_display'
+ export const GLOBAL_CAMERA = () => 'aj.global.camera'
+ export const GLOBAL_LOCATOR = () => 'aj.global.locator'
+ export const GLOBAL_DATA = () => 'aj.global.data'
+
+ export const GLOBAL_NODE_NAMED = (nodeName: string) => `aj.global.node.${nodeName}`
+ export const GLOBAL_DISPLAY_NODE_NAMED = (nodeName: string) =>
+ `aj.global.display_node.${nodeName}`
+ export const GLOBAL_BONE_CHILD = (boneName: string) => `aj.global.bone.${boneName}.child`
+ export const GLOBAL_BONE_CHILD_BONE = (boneName: string) =>
+ `aj.global.bone.${boneName}.child.bone`
+ export const GLOBAL_BONE_CHILD_ITEM_DISPLAY = (boneName: string) =>
+ `aj.global.bone.${boneName}.child.item_display`
+ export const GLOBAL_BONE_CHILD_BLOCK_DISPLAY = (boneName: string) =>
+ `aj.global.bone.${boneName}.child.block_display`
+ export const GLOBAL_BONE_CHILD_TEXT_DISPLAY = (boneName: string) =>
+ `aj.global.bone.${boneName}.child.text_display`
+ export const GLOBAL_BONE_CHILD_LOCATOR = (boneName: string) =>
+ `aj.global.bone.${boneName}.child.locator`
+ export const GLOBAL_BONE_CHILD_CAMERA = (boneName: string) =>
+ `aj.global.bone.${boneName}.child.camera`
+
+ export const GLOBAL_BONE_DECENDANT = (boneName: string) =>
+ `aj.global.bone.${boneName}.decendant`
+ export const GLOBAL_BONE_DECENDANT_BONE = (boneName: string) =>
+ `aj.global.bone.${boneName}.decendant.bone`
+ export const GLOBAL_BONE_DECENDANT_ITEM_DISPLAY = (boneName: string) =>
+ `aj.global.bone.${boneName}.decendant.item_display`
+ export const GLOBAL_BONE_DECENDANT_BLOCK_DISPLAY = (boneName: string) =>
+ `aj.global.bone.${boneName}.decendant.block_display`
+ export const GLOBAL_BONE_DECENDANT_TEXT_DISPLAY = (boneName: string) =>
+ `aj.global.bone.${boneName}.decendant.text_display`
+ export const GLOBAL_BONE_DECENDANT_LOCATOR = (boneName: string) =>
+ `aj.global.bone.${boneName}.decendant.locator`
+ export const GLOBAL_BONE_DECENDANT_CAMERA = (boneName: string) =>
+ `aj.global.bone.${boneName}.decendant.camera`
+
+ export const GLOBAL_BONE_TREE = (boneName: string) => `aj.global.bone.${boneName}.tree`
+ export const GLOBAL_BONE_TREE_BONE = (boneName: string) =>
+ `aj.global.bone.${boneName}.tree.bone`
+
+ // --------------------------------
+ // region Project Tags
+ // --------------------------------
+ export const PROJECT_ENTITY = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.entity`
+
+ export const PROJECT_ROOT = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root`
+ export const PROJECT_ROOT_CHILD = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child`
+ export const PROJECT_ROOT_CHILD_BONE = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child.bone`
+ export const PROJECT_ROOT_CHILD_ITEM_DISPLAY = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child.item_display`
+ export const PROJECT_ROOT_CHILD_BLOCK_DISPLAY = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child.block_display`
+ export const PROJECT_ROOT_CHILD_TEXT_DISPLAY = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child.text_display`
+ export const PROJECT_ROOT_CHILD_LOCATOR = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child.locator`
+ export const PROJECT_ROOT_CHILD_CAMERA = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child.camera`
+ export const PROJECT_ROOT_CHILD_DATA = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.root.child.data`
+
+ export const PROJECT_NODE = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.node`
+ export const PROJECT_DISPLAY_NODE = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.display_node`
+ export const PROJECT_VANILLA_DISPLAY_NODE = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.display_node`
+ export const PROJECT_BONE = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.bone`
+ export const PROJECT_ITEM_DISPLAY = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.item_display`
+ export const PROJECT_BLOCK_DISPLAY = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.block_display`
+ export const PROJECT_TEXT_DISPLAY = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.text_display`
+ export const PROJECT_CAMERA = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.camera`
+ export const PROJECT_LOCATOR = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.locator`
+ export const PROJECT_DATA = () =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.data`
+
+ export const PROJECT_NODE_NAMED = (nodeName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.node.${nodeName}`
+ export const PROJECT_BONE_NAMED = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.bone.${boneName}`
+ export const PROJECT_DISPLAY_NODE_NAMED = (nodeName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.display_node.${nodeName}`
+ export const PROJECT_ITEM_DISPLAY_NAMED = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.item_display.${boneName}`
+ export const PROJECT_BLOCK_DISPLAY_NAMED = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.block_display.${boneName}`
+ export const PROJECT_TEXT_DISPLAY_NAMED = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.text_display.${boneName}`
+ export const PROJECT_CAMERA_NAMED = (cameraName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.camera.${cameraName}`
+ export const PROJECT_LOCATOR_NAMED = (locatorName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.locator.${locatorName}`
+
+ export const PROJECT_BONE_CHILD = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.bone.${boneName}.child`
+ export const PROJECT_BONE_CHILD_BONE = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.child.bone`
+ export const PROJECT_BONE_CHILD_ITEM_DISPLAY = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.child.item_display`
+ export const PROJECT_BONE_CHILD_BLOCK_DISPLAY = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.child.block_display`
+ export const PROJECT_BONE_CHILD_TEXT_DISPLAY = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.child.text_display`
+ export const PROJECT_BONE_CHILD_LOCATOR = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.child.locator`
+ export const PROJECT_BONE_CHILD_CAMERA = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.child.camera`
+
+ export const PROJECT_BONE_DECENDANT = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.bone.${boneName}.decendant`
+ export const PROJECT_BONE_DECENDANT_BONE = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.decendant.bone`
+ export const PROJECT_BONE_DECENDANT_ITEM_DISPLAY = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.decendant.item_display`
+ export const PROJECT_BONE_DECENDANT_BLOCK_DISPLAY = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.decendant.block_display`
+ export const PROJECT_BONE_DECENDANT_TEXT_DISPLAY = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.decendant.text_display`
+ export const PROJECT_BONE_DECENDANT_LOCATOR = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.decendant.locator`
+ export const PROJECT_BONE_DECENDANT_CAMERA = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.bone.${boneName}.decendant.camera`
+
+ export const PROJECT_BONE_TREE = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.bone.${boneName}.tree`
+ export const PROJECT_BONE_TREE_BONE = (boneName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(Project!.animated_java.id)}.bone.${boneName}.tree.bone`
+
+ // --------------------------------
+ // region Misc Tags
+ // --------------------------------
+ export const ANIMATION_PLAYING = (animationName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.animation.${animationName}.playing`
+ export const TWEENING = (animationName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.animation.${animationName}.tween_playing`
+ export const VARIANT_APPLIED = (variantName: string) =>
+ `aj.${createTagPrefixFromBlueprintID(
+ Project!.animated_java.id
+ )}.variant.${variantName}.applied`
+ // Used to tell the set and apply frame functions to only apply the bone transforms, and ignore command/variant keyframes
+ export const TRANSFORMS_ONLY = () => 'aj.transforms_only'
+ export const OUTDATED_RIG_TEXT_DISPLAY = () => 'aj.outdated_rig_text_display'
+}
+
+// --------------------------------
+// region Helper Functions
+// --------------------------------
+export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList {
+ const tags: string[] = []
+
+ const parentNames: Array<{ name: string; type: string }> = []
+
+ function recurseParents(node: AnyRenderedNode) {
+ if (node.parent) {
+ parentNames.push({
+ name: rig.nodes[node.parent].path_name,
+ type: rig.nodes[node.parent].type,
+ })
+ recurseParents(rig.nodes[node.parent])
+ }
+ }
+ recurseParents(node)
+
+ tags.push(
+ // Global
+ TAGS.NEW(),
+ TAGS.GLOBAL_ENTITY(),
+ TAGS.GLOBAL_NODE(),
+ TAGS.GLOBAL_NODE_NAMED(node.path_name),
+ // Project
+ TAGS.PROJECT_ENTITY(),
+ TAGS.PROJECT_NODE(),
+ TAGS.PROJECT_NODE_NAMED(node.path_name)
+ )
+
+ if (!node.parent) {
+ tags.push(TAGS.GLOBAL_ROOT_CHILD())
+ }
+ switch (node.type) {
+ case 'bone': {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.GLOBAL_BONE(),
+ TAGS.GLOBAL_BONE_TREE(node.path_name), // Tree includes self
+ TAGS.GLOBAL_BONE_TREE_BONE(node.path_name), // Tree includes self
+ // Project
+ TAGS.PROJECT_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.PROJECT_BONE(),
+ TAGS.PROJECT_BONE_NAMED(node.path_name),
+ TAGS.PROJECT_BONE_TREE(node.path_name), // Tree includes self
+ TAGS.PROJECT_BONE_TREE_BONE(node.path_name) // Tree includes self
+ )
+ if (!node.parent) {
+ // Nodes without parents are assumed to be root nodes
+ tags.push(TAGS.GLOBAL_ROOT_CHILD_BONE())
+ } else {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
+ TAGS.GLOBAL_BONE_CHILD_BONE(parentNames[0].name),
+ // Project
+ TAGS.PROJECT_BONE_CHILD(parentNames[0].name),
+ TAGS.PROJECT_BONE_CHILD_BONE(parentNames[0].name)
+ )
+ }
+ for (const { name } of parentNames) {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_DECENDANT(name),
+ TAGS.GLOBAL_BONE_DECENDANT_BONE(name),
+ TAGS.GLOBAL_BONE_TREE(name),
+ TAGS.GLOBAL_BONE_TREE_BONE(name),
+ // Project
+ TAGS.PROJECT_BONE_DECENDANT(name),
+ TAGS.PROJECT_BONE_DECENDANT_BONE(name),
+ TAGS.PROJECT_BONE_TREE(name),
+ TAGS.PROJECT_BONE_TREE_BONE(name)
+ )
+ }
+ break
+ }
+ case 'item_display': {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.GLOBAL_ITEM_DISPLAY(),
+ // Project
+ TAGS.PROJECT_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.PROJECT_ITEM_DISPLAY(),
+ TAGS.PROJECT_ITEM_DISPLAY_NAMED(node.path_name)
+ )
+ if (!node.parent) {
+ // Nodes without parents are assumed to be root nodes
+ tags.push(TAGS.GLOBAL_ROOT_CHILD_ITEM_DISPLAY())
+ } else {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
+ TAGS.GLOBAL_BONE_CHILD_ITEM_DISPLAY(parentNames[0].name),
+ // Project
+ TAGS.PROJECT_BONE_CHILD(parentNames[0].name),
+ TAGS.PROJECT_BONE_CHILD_ITEM_DISPLAY(parentNames[0].name)
+ )
+ }
+ for (const { name } of parentNames) {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_DECENDANT(name),
+ TAGS.GLOBAL_BONE_DECENDANT_ITEM_DISPLAY(name),
+ TAGS.GLOBAL_BONE_TREE(name),
+ // Project
+ TAGS.PROJECT_BONE_DECENDANT(name),
+ TAGS.PROJECT_BONE_DECENDANT_ITEM_DISPLAY(name),
+ TAGS.PROJECT_BONE_TREE(name)
+ )
+ }
+ break
+ }
+ case 'block_display': {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.GLOBAL_BLOCK_DISPLAY(),
+ // Project
+ TAGS.PROJECT_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.PROJECT_BLOCK_DISPLAY(),
+ TAGS.PROJECT_BLOCK_DISPLAY_NAMED(node.path_name)
+ )
+ if (!node.parent) {
+ // Nodes without parents are assumed to be root nodes
+ tags.push(TAGS.GLOBAL_ROOT_CHILD_BLOCK_DISPLAY())
+ } else {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
+ TAGS.GLOBAL_BONE_CHILD_BLOCK_DISPLAY(parentNames[0].name),
+ // Project
+ TAGS.PROJECT_BONE_CHILD(parentNames[0].name),
+ TAGS.PROJECT_BONE_CHILD_BLOCK_DISPLAY(parentNames[0].name)
+ )
+ }
+ for (const { name } of parentNames) {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_DECENDANT(name),
+ TAGS.GLOBAL_BONE_DECENDANT_BLOCK_DISPLAY(name),
+ TAGS.GLOBAL_BONE_TREE(name),
+ // Project
+ TAGS.PROJECT_BONE_DECENDANT(name),
+ TAGS.PROJECT_BONE_DECENDANT_BLOCK_DISPLAY(name),
+ TAGS.PROJECT_BONE_TREE(name)
+ )
+ }
+ break
+ }
+ case 'text_display': {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.GLOBAL_TEXT_DISPLAY(),
+ // Project
+ TAGS.PROJECT_DISPLAY_NODE_NAMED(node.path_name),
+ TAGS.PROJECT_TEXT_DISPLAY(),
+ TAGS.PROJECT_TEXT_DISPLAY_NAMED(node.path_name)
+ )
+ if (!node.parent) {
+ // Nodes without parents are assumed to be root nodes
+ tags.push(TAGS.GLOBAL_ROOT_CHILD_TEXT_DISPLAY())
+ } else {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
+ TAGS.GLOBAL_BONE_CHILD_TEXT_DISPLAY(parentNames[0].name),
+ // Project
+ TAGS.PROJECT_BONE_CHILD(parentNames[0].name),
+ TAGS.PROJECT_BONE_CHILD_TEXT_DISPLAY(parentNames[0].name)
+ )
+ }
+ for (const { name } of parentNames) {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_DECENDANT(name),
+ TAGS.GLOBAL_BONE_DECENDANT_TEXT_DISPLAY(name),
+ TAGS.GLOBAL_BONE_TREE(name),
+ // Project
+ TAGS.PROJECT_BONE_DECENDANT(name),
+ TAGS.PROJECT_BONE_DECENDANT_TEXT_DISPLAY(name),
+ TAGS.PROJECT_BONE_TREE(name)
+ )
+ }
+ break
+ }
+ case 'locator': {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_LOCATOR(),
+ // Project
+ TAGS.PROJECT_LOCATOR(),
+ TAGS.PROJECT_LOCATOR_NAMED(node.path_name)
+ )
+ if (!node.parent) {
+ // Nodes without parents are assumed to be root nodes
+ tags.push(TAGS.GLOBAL_ROOT_CHILD_LOCATOR())
+ } else {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
+ TAGS.GLOBAL_BONE_CHILD_LOCATOR(parentNames[0].name),
+ // Project
+ TAGS.PROJECT_BONE_CHILD(parentNames[0].name),
+ TAGS.PROJECT_BONE_CHILD_LOCATOR(parentNames[0].name)
+ )
+ }
+ for (const { name } of parentNames) {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_DECENDANT(name),
+ TAGS.GLOBAL_BONE_DECENDANT_LOCATOR(name),
+ TAGS.GLOBAL_BONE_TREE(name),
+ // Project
+ TAGS.PROJECT_BONE_DECENDANT(name),
+ TAGS.PROJECT_BONE_DECENDANT_LOCATOR(name),
+ TAGS.PROJECT_BONE_TREE(name)
+ )
+ }
+ break
+ }
+ case 'camera': {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_CAMERA(),
+ // Project
+ TAGS.PROJECT_CAMERA(),
+ TAGS.PROJECT_CAMERA_NAMED(node.path_name)
+ )
+ if (!node.parent) {
+ // Nodes without parents are assumed to be root nodes
+ tags.push(TAGS.GLOBAL_ROOT_CHILD_CAMERA())
+ } else {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_CHILD(parentNames[0].name),
+ TAGS.GLOBAL_BONE_CHILD_CAMERA(parentNames[0].name),
+ // Project
+ TAGS.PROJECT_BONE_CHILD(parentNames[0].name),
+ TAGS.PROJECT_BONE_CHILD_CAMERA(parentNames[0].name)
+ )
+ }
+ for (const { name } of parentNames) {
+ tags.push(
+ // Global
+ TAGS.GLOBAL_BONE_DECENDANT(name),
+ TAGS.GLOBAL_BONE_DECENDANT_CAMERA(name),
+ TAGS.GLOBAL_BONE_TREE(name),
+ // Project
+ TAGS.PROJECT_BONE_DECENDANT(name),
+ TAGS.PROJECT_BONE_DECENDANT_CAMERA(name),
+ TAGS.PROJECT_BONE_TREE(name)
+ )
+ }
+ break
+ }
+ default: {
+ throw new IntentionalExportError(
+ `Attempted to get tags for an unknown node type: '${node.type}'!`
+ )
+ }
+ }
+
+ return new NbtList(tags.sort().map(v => new NbtString(v)))
+}
diff --git a/src/systems/datapack-compiler/tellraw.ts b/src/systems/datapack-compiler/tellraw.ts
new file mode 100644
index 00000000..3ab3fba3
--- /dev/null
+++ b/src/systems/datapack-compiler/tellraw.ts
@@ -0,0 +1,201 @@
+import type { IRenderedAnimation } from '../animation-renderer'
+import { JsonText } from '../minecraft-temp/jsonText'
+import type { IRenderedVariant } from '../rig-renderer'
+import { OBJECTIVES } from './objectives'
+
+export namespace TELLRAW {
+ export const PREFIX = () =>
+ new JsonText([
+ { text: '\n[', color: 'gray' },
+ { text: 'AJ', color: 'aqua' },
+ '] ',
+ [{ text: '(from ', color: 'gray', italic: true }, Project!.animated_java.id, ')'],
+ ' -> ',
+ ])
+
+ export const ERROR_PREFIX = () =>
+ new JsonText([PREFIX(), { text: 'ERROR: ', color: 'red' }, '\n '])
+
+ export const SUFFIX = () => new JsonText(['\n'])
+
+ export const LEARN_MORE_LINK = (url: string) =>
+ new JsonText([
+ '\n ',
+ {
+ text: 'Click here to learn more',
+ color: 'blue',
+ underlined: true,
+ italic: true,
+ clickEvent: { action: 'open_url', value: url },
+ },
+ ])
+
+ export const RIG_OUTDATED = () =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ { text: 'The ', color: 'red' },
+ { text: Project!.animated_java.id, color: 'yellow' },
+ { text: ' rig instance at', color: 'red' },
+ [
+ { text: ' [', color: 'yellow' },
+ { score: { name: '#this.x', objective: OBJECTIVES.I() } },
+ ', ',
+ { score: { name: '#this.y', objective: OBJECTIVES.I() } },
+ ', ',
+ { score: { name: '#this.z', objective: OBJECTIVES.I() } },
+ ']',
+ ],
+ {
+ text: ' is outdated! It will not function correctly and should be removed or re-summoned.',
+ color: 'red',
+ },
+ '\n ',
+ {
+ text: '[Click Here to Teleport to the Rig Instance]',
+ clickEvent: {
+ action: 'suggest_command',
+ value: '/tp @s $(x) $(y) $(z)',
+ },
+ color: 'aqua',
+ underlined: true,
+ },
+ SUFFIX(),
+ ])
+
+ export const RIG_OUTDATED_TEXT_DISPLAY = () =>
+ new JsonText([
+ '',
+ {
+ text: 'This rig instance is outdated!\\nIt will not function correctly and should be removed or re-summoned.',
+ color: 'red',
+ },
+ ])
+
+ export const FUNCTION_NOT_EXECUTED_AS_ROOT_ERROR = (functionName: string) =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ {
+ text: 'This function',
+ color: 'blue',
+ underlined: true,
+ hoverEvent: {
+ action: 'show_text',
+ contents: [{ text: functionName, color: 'yellow' }],
+ },
+ },
+ { text: " must be executed as the rig's root entity.", color: 'red' },
+ '\n',
+ LEARN_MORE_LINK(
+ 'https://animated-java.dev/docs/exported-rigs/controlling-a-rig-instance'
+ ),
+ SUFFIX(),
+ ])
+
+ // Summon Function
+ export const VARIANT_CANNOT_BE_EMPTY = () =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ { text: 'variant', color: 'yellow' },
+ { text: ' cannot be an empty string.', color: 'red' },
+ SUFFIX(),
+ ])
+
+ export const INVALID_VARIANT = (
+ variantName: string,
+ variants: Record
+ ) =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ { text: 'The variant ', color: 'red' },
+ { text: variantName, color: 'yellow' },
+ { text: ' does not exist.', color: 'red' },
+ '\n ',
+ { text: ' ≡ ', color: 'white' },
+ { text: 'Available Variants:', color: 'green' },
+ ...Object.values(variants).map(
+ variant =>
+ new JsonText([
+ '\n ',
+ ' ',
+ ' ',
+ { text: ' ● ', color: 'gray' },
+ { text: variant.name, color: 'yellow' },
+ ])
+ ),
+ SUFFIX(),
+ ])
+
+ export const ANIMATION_CANNOT_BE_EMPTY = () =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ { text: 'animation', color: 'yellow' },
+ { text: ' cannot be an empty string.', color: 'red' },
+ SUFFIX(),
+ ])
+
+ export const FRAME_CANNOT_BE_NEGATIVE = () =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ { text: 'frame', color: 'yellow' },
+ { text: ' must be a non-negative integer.', color: 'red' },
+ SUFFIX(),
+ ])
+
+ export const INVALID_ANIMATION = (animationName: string, animations: IRenderedAnimation[]) =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ { text: 'The animation ', color: 'red' },
+ { text: animationName, color: 'yellow' },
+ { text: ' does not exist.', color: 'red' },
+ '\n ',
+ { text: ' ≡ ', color: 'white' },
+ { text: 'Available Animations:', color: 'green' },
+ ...animations.map(
+ anim =>
+ new JsonText([
+ '\n ',
+ ' ',
+ ' ',
+ { text: ' ● ', color: 'gray' },
+ { text: anim.path_name, color: 'yellow' },
+ ])
+ ),
+ SUFFIX(),
+ ])
+
+ export const NO_VARIANTS = () =>
+ new JsonText([
+ '',
+ ERROR_PREFIX(),
+ { text: 'No variants are available.', color: 'red' },
+ SUFFIX(),
+ ])
+
+ export const INVALID_VERSION = () =>
+ new JsonText([
+ ERROR_PREFIX(),
+ [
+ {
+ text: 'Attempting to load an Animated Java Data Pack that was exported for ',
+ color: 'red',
+ },
+ {
+ text: `Minecraft ${Project!.animated_java.target_minecraft_versions[0]}`,
+ color: 'aqua',
+ },
+ { text: ' in the wrong version!', color: 'red' },
+ {
+ text: '\n Please ensure that the data pack is loaded in the correct version, or that your blueprint settings are configured to target the correct version(s) of Minecraft.',
+ color: 'yellow',
+ },
+ ],
+ SUFFIX(),
+ ])
+}
diff --git a/src/systems/datapackCompiler/1.20.4/animation.mcb b/src/systems/datapack-compiler/versions/1.20.4/animation.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.20.4/animation.mcb
rename to src/systems/datapack-compiler/versions/1.20.4/animation.mcb
index f32e3b39..f8263feb 100644
--- a/src/systems/datapackCompiler/1.20.4/animation.mcb
+++ b/src/systems/datapack-compiler/versions/1.20.4/animation.mcb
@@ -899,7 +899,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapack-compiler/versions/1.20.4/core.mcb b/src/systems/datapack-compiler/versions/1.20.4/core.mcb
new file mode 100644
index 00000000..e0b1e6c1
--- /dev/null
+++ b/src/systems/datapack-compiler/versions/1.20.4/core.mcb
@@ -0,0 +1,129 @@
+dir global {
+
+ function on_load minecraft:load {
+ # Initialize Scoreboards
+ scoreboard objectives add <%OBJECTIVES.I()%> dummy
+ scoreboard objectives add <%OBJECTIVES.ID()%> dummy
+ scoreboard objectives add <%OBJECTIVES.IS_RIG_LOADED()%> dummy
+ scoreboard objectives add <%OBJECTIVES.TWEEN_DURATION()%> dummy
+
+ scoreboard players add aj.last_id <%OBJECTIVES.ID()%> 0
+
+ # Initialize Storage
+ data modify storage aj:temp args set value {}
+ IF (show_outdated_warning) {
+ # Initialize Rigs
+ scoreboard players reset * <%OBJECTIVES.IS_RIG_LOADED()%>
+ }
+ function #*global/on_load
+ }
+
+ IF (!is_static || show_outdated_warning) {
+ function on_tick minecraft:tick {
+ execute as @e[type=item_display,tag=<%TAGS.GLOBAL_ROOT()%>] at @s run function #*global/root/on_tick
+ }
+ }
+
+ tag functions on_load {
+ *<%export_namespace%>/on_load
+ }
+
+ dir root {
+ # TODO Maybe instead of merging tags, I should just generate it from the .ajmeta data every time a rig is exported?
+ # That way I can also check if the rig's files still exist, and remove tag entries if they don't.
+ # An entry will be added for each exported rig.
+ tag functions on_tick {
+ *<%export_namespace%>/root/on_tick
+ }
+ # An entry will be added for each exported rig.
+ IF (show_outdated_warning) {
+ tag functions on_load {
+ *<%export_namespace%>/root/on_load
+ }
+ }
+ }
+
+ IF (show_function_errors) {
+ dir errors {
+ function function_not_executed_as_root_entity {
+ #ARGS: {export_namespace: string, function_path: string}
+ $tellraw @a <%TELLRAW.FUNCTION_NOT_EXECUTED_AS_ROOT_ERROR('$(function_path)')%>
+ }
+ }
+ }
+
+ dir remove {
+ # Removes all instances of all rigs from the world.
+ function everything {
+ kill @e[tag=<%TAGS.GLOBAL_ENTITY()%>]
+ }
+ }
+
+ dir internal {
+ # Thanks Gibbsly for this code! https://github.com/gibbsly/gu
+ dir gu {
+ function load minecraft:load {
+ scoreboard players set 256 <%OBJECTIVES.I()%> 256
+ data modify storage aj:uuid main.hex_chars set value \
+ <%JSON.stringify([...Array(0x100).keys()].map(v => {const x = v.toString(16); return x.length > 1 ? x : '0' + x}))%>
+ }
+
+ function convert_uuid_array_to_string {
+ data modify storage aj:uuid temp set value {0:0,1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0,a:0,b:0,c:0,d:0,e:0,f:0}
+ data modify storage aj:uuid main.in set from entity @s UUID
+
+ execute store result score 0= <%OBJECTIVES.I()%> store result score 1= <%OBJECTIVES.I()%> run data get storage aj:uuid main.in[0]
+ execute store result storage aj:uuid temp.0 int 1 run scoreboard players operation 0= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 2= <%OBJECTIVES.I()%> run scoreboard players operation 1= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.1 int 1 run scoreboard players operation 1= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 3= <%OBJECTIVES.I()%> run scoreboard players operation 2= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.2 int 1 run scoreboard players operation 2= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.3 int 1 run scoreboard players operation 3= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+
+ execute store result score 0= <%OBJECTIVES.I()%> store result score 1= <%OBJECTIVES.I()%> run data get storage aj:uuid main.in[1]
+ execute store result storage aj:uuid temp.4 int 1 run scoreboard players operation 0= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 2= <%OBJECTIVES.I()%> run scoreboard players operation 1= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.5 int 1 run scoreboard players operation 1= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 3= <%OBJECTIVES.I()%> run scoreboard players operation 2= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.6 int 1 run scoreboard players operation 2= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.7 int 1 run scoreboard players operation 3= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+
+ execute store result score 0= <%OBJECTIVES.I()%> store result score 1= <%OBJECTIVES.I()%> run data get storage aj:uuid main.in[2]
+ execute store result storage aj:uuid temp.8 int 1 run scoreboard players operation 0= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 2= <%OBJECTIVES.I()%> run scoreboard players operation 1= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.9 int 1 run scoreboard players operation 1= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 3= <%OBJECTIVES.I()%> run scoreboard players operation 2= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.a int 1 run scoreboard players operation 2= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.b int 1 run scoreboard players operation 3= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+
+ execute store result score 0= <%OBJECTIVES.I()%> store result score 1= <%OBJECTIVES.I()%> run data get storage aj:uuid main.in[3]
+ execute store result storage aj:uuid temp.c int 1 run scoreboard players operation 0= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 2= <%OBJECTIVES.I()%> run scoreboard players operation 1= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.d int 1 run scoreboard players operation 1= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result score 3= <%OBJECTIVES.I()%> run scoreboard players operation 2= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.e int 1 run scoreboard players operation 2= <%OBJECTIVES.I()%> %= 256 <%OBJECTIVES.I()%>
+ execute store result storage aj:uuid temp.f int 1 run scoreboard players operation 3= <%OBJECTIVES.I()%> /= 256 <%OBJECTIVES.I()%>
+
+ block { with storage aj:uuid temp
+ REPEAT (0, 15) as i {
+ $data modify storage aj:uuid temp.<%i.toString(16)%> set from storage aj:uuid main.hex_chars[$(<%i.toString(16)%>)]
+ }
+ }
+
+ block { with storage aj:uuid temp
+ $data modify storage aj:uuid main.out set value "$(3)$(2)$(1)$(0)-$(7)$(6)-$(5)$(4)-$(b)$(a)-$(9)$(8)$(f)$(e)$(d)$(c)"
+ }
+ }
+ }
+ }
+}
+
+dir <%export_namespace%> {
+ function on_load {
+ function *<%export_namespace%>/invalid_version_warning
+ }
+
+ function invalid_version_warning {
+ tellraw @a <%TELLRAW.INVALID_VERSION()%>
+ }
+}
diff --git a/src/systems/datapackCompiler/1.20.4/core.mcbt b/src/systems/datapack-compiler/versions/1.20.4/core.mcbt
similarity index 100%
rename from src/systems/datapackCompiler/1.20.4/core.mcbt
rename to src/systems/datapack-compiler/versions/1.20.4/core.mcbt
diff --git a/src/systems/datapackCompiler/1.20.4/static.mcb b/src/systems/datapack-compiler/versions/1.20.4/static.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.20.4/static.mcb
rename to src/systems/datapack-compiler/versions/1.20.4/static.mcb
index a68f25e6..6d5d04cc 100644
--- a/src/systems/datapackCompiler/1.20.4/static.mcb
+++ b/src/systems/datapack-compiler/versions/1.20.4/static.mcb
@@ -453,7 +453,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapackCompiler/1.20.5/animation.mcb b/src/systems/datapack-compiler/versions/1.20.5/animation.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.20.5/animation.mcb
rename to src/systems/datapack-compiler/versions/1.20.5/animation.mcb
index 737c565c..6c6b8697 100644
--- a/src/systems/datapackCompiler/1.20.5/animation.mcb
+++ b/src/systems/datapack-compiler/versions/1.20.5/animation.mcb
@@ -899,7 +899,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapackCompiler/1.20.5/static.mcb b/src/systems/datapack-compiler/versions/1.20.5/static.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.20.5/static.mcb
rename to src/systems/datapack-compiler/versions/1.20.5/static.mcb
index 6b474dd2..13b6eb87 100644
--- a/src/systems/datapackCompiler/1.20.5/static.mcb
+++ b/src/systems/datapack-compiler/versions/1.20.5/static.mcb
@@ -453,7 +453,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapackCompiler/1.21.2/animation.mcb b/src/systems/datapack-compiler/versions/1.21.2/animation.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.21.2/animation.mcb
rename to src/systems/datapack-compiler/versions/1.21.2/animation.mcb
index 694adb9f..01e601a9 100644
--- a/src/systems/datapackCompiler/1.21.2/animation.mcb
+++ b/src/systems/datapack-compiler/versions/1.21.2/animation.mcb
@@ -899,7 +899,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapackCompiler/1.21.2/static.mcb b/src/systems/datapack-compiler/versions/1.21.2/static.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.21.2/static.mcb
rename to src/systems/datapack-compiler/versions/1.21.2/static.mcb
index 5e93d120..82fe8a6e 100644
--- a/src/systems/datapackCompiler/1.21.2/static.mcb
+++ b/src/systems/datapack-compiler/versions/1.21.2/static.mcb
@@ -453,7 +453,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapack-compiler/versions/1.21.4/animation.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.21.4/animation.mcb
rename to src/systems/datapack-compiler/versions/1.21.4/animation.mcb
index 4ad4b9b6..dd3a1142 100644
--- a/src/systems/datapackCompiler/1.21.4/animation.mcb
+++ b/src/systems/datapack-compiler/versions/1.21.4/animation.mcb
@@ -899,7 +899,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapackCompiler/1.21.5/static.mcb b/src/systems/datapack-compiler/versions/1.21.4/static.mcb
similarity index 99%
rename from src/systems/datapackCompiler/1.21.5/static.mcb
rename to src/systems/datapack-compiler/versions/1.21.4/static.mcb
index b611a990..b62d4008 100644
--- a/src/systems/datapackCompiler/1.21.5/static.mcb
+++ b/src/systems/datapack-compiler/versions/1.21.4/static.mcb
@@ -453,7 +453,7 @@ dir <%export_namespace%> {
}
IF (node.configs.variants[variant.uuid]) {
<%%
- global.config = BoneConfig.fromJSON(node.configs.variants[variant.uuid])
+ global.config = GenericDisplayConfig.fromJSON(node.configs.variants[variant.uuid])
%%>
IF (!global.config.isDefault()) {
data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%>
diff --git a/src/systems/datapackCompiler/1.21.5/animation.mcb b/src/systems/datapack-compiler/versions/1.21.5/animation.mcb
similarity index 100%
rename from src/systems/datapackCompiler/1.21.5/animation.mcb
rename to src/systems/datapack-compiler/versions/1.21.5/animation.mcb
diff --git a/src/systems/datapackCompiler/1.21.4/static.mcb b/src/systems/datapack-compiler/versions/1.21.5/static.mcb
similarity index 100%
rename from src/systems/datapackCompiler/1.21.4/static.mcb
rename to src/systems/datapack-compiler/versions/1.21.5/static.mcb
diff --git a/src/systems/datapack-compiler/versions/1.21.5/tests.mcb b/src/systems/datapack-compiler/versions/1.21.5/tests.mcb
new file mode 100644
index 00000000..e69de29b
diff --git a/src/systems/datapack-compiler/versions/index.ts b/src/systems/datapack-compiler/versions/index.ts
new file mode 100644
index 00000000..10dd5816
--- /dev/null
+++ b/src/systems/datapack-compiler/versions/index.ts
@@ -0,0 +1,45 @@
+import ANIMATION_1_20_4 from './1.20.4/animation.mcb'
+import CORE_1_20_4 from './1.20.4/core.mcb'
+import STATIC_1_20_4 from './1.20.4/static.mcb'
+
+import ANIMATION_1_20_5 from './1.20.5/animation.mcb'
+import STATIC_1_20_5 from './1.20.5/static.mcb'
+
+import ANIMATION_1_21_2 from './1.21.2/animation.mcb'
+import STATIC_1_21_2 from './1.21.2/static.mcb'
+
+import ANIMATION_1_21_4 from './1.21.4/animation.mcb'
+import STATIC_1_21_4 from './1.21.4/static.mcb'
+
+export type MinecraftVersion = '1.20.4' | '1.20.5' | '1.21.0' | '1.21.2' | '1.21.4' | '1.21.5'
+
+// The core is content that always goes in the `data` folder directly,
+// while other files are in the `animated_java/data` folder to be overlayed when the correct version is loaded.
+
+export default {
+ '1.21.4': {
+ animation: ANIMATION_1_21_4,
+ static: STATIC_1_21_4,
+ core: CORE_1_20_4,
+ },
+ '1.21.2': {
+ animation: ANIMATION_1_21_2,
+ static: STATIC_1_21_2,
+ core: CORE_1_20_4,
+ },
+ '1.21.0': {
+ animation: ANIMATION_1_20_5,
+ static: STATIC_1_20_5,
+ core: CORE_1_20_4,
+ },
+ '1.20.5': {
+ animation: ANIMATION_1_20_5,
+ static: STATIC_1_20_5,
+ core: CORE_1_20_4,
+ },
+ '1.20.4': {
+ animation: ANIMATION_1_20_4,
+ static: STATIC_1_20_4,
+ core: CORE_1_20_4,
+ },
+} as Record
diff --git a/src/systems/datapackCompiler/mcbFiles.ts b/src/systems/datapackCompiler/mcbFiles.ts
deleted file mode 100644
index 7a351419..00000000
--- a/src/systems/datapackCompiler/mcbFiles.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { MinecraftVersion } from '../global'
-import animation_1_20_4 from './1.20.4/animation.mcb'
-import core_1_20_4 from './1.20.4/core.mcb'
-import static_1_20_4 from './1.20.4/static.mcb'
-
-import animation_1_20_5 from './1.20.5/animation.mcb'
-import static_1_20_5 from './1.20.5/static.mcb'
-
-import animation_1_21_2 from './1.21.2/animation.mcb'
-import static_1_21_2 from './1.21.2/static.mcb'
-
-import animation_1_21_4 from './1.21.4/animation.mcb'
-import static_1_21_4 from './1.21.4/static.mcb'
-
-import animation_1_21_5 from './1.21.5/animation.mcb'
-import static_1_21_5 from './1.21.5/static.mcb'
-
-// The core is content that always goes in the `data` folder directly,
-// while other files are in the `animated_java/data` folder to be overlayed when the correct version is loaded.
-
-export default {
- '1.21.5': {
- animation: animation_1_21_5,
- static: static_1_21_5,
- core: core_1_20_4,
- },
- '1.21.4': {
- animation: animation_1_21_4,
- static: static_1_21_4,
- core: core_1_20_4,
- },
- '1.21.2': {
- animation: animation_1_21_2,
- static: static_1_21_2,
- core: core_1_20_4,
- },
- '1.21.0': {
- animation: animation_1_20_5,
- static: static_1_20_5,
- core: core_1_20_4,
- },
- '1.20.5': {
- animation: animation_1_20_5,
- static: static_1_20_5,
- core: core_1_20_4,
- },
- '1.20.4': {
- animation: animation_1_20_4,
- static: static_1_20_4,
- core: core_1_20_4,
- },
-} as Record
diff --git a/src/systems/datapackCompiler/tags.ts b/src/systems/datapackCompiler/tags.ts
deleted file mode 100644
index f9ffe411..00000000
--- a/src/systems/datapackCompiler/tags.ts
+++ /dev/null
@@ -1,174 +0,0 @@
-export namespace TAGS {
- // --------------------------------
- // region Global Tags
- // --------------------------------
- export const NEW = () => 'aj.new'
- export const GLOBAL_ENTITY = () => 'aj.global.entity'
-
- export const GLOBAL_ROOT = () => 'aj.global.root'
- export const GLOBAL_ROOT_CHILD = () => 'aj.global.root.child'
- export const GLOBAL_ROOT_CHILD_BONE = () => 'aj.global.root.child.bone'
- export const GLOBAL_ROOT_CHILD_ITEM_DISPLAY = () => 'aj.global.root.child.item_display'
- export const GLOBAL_ROOT_CHILD_BLOCK_DISPLAY = () => 'aj.global.root.child.block_display'
- export const GLOBAL_ROOT_CHILD_TEXT_DISPLAY = () => 'aj.global.root.child.text_display'
- export const GLOBAL_ROOT_CHILD_LOCATOR = () => 'aj.global.root.child.locator'
- export const GLOBAL_ROOT_CHILD_CAMERA = () => 'aj.global.root.child.camera'
- export const GLOBAL_ROOT_CHILD_DATA = () => 'aj.global.root.child.data'
-
- export const GLOBAL_NODE = () => 'aj.global.node'
- export const GLOBAL_DISPLAY_NODE = () => 'aj.global.display_node'
- export const GLOBAL_VANILLA_DISPLAY_NODE = () => 'aj.global.vanilla_display_node'
- export const GLOBAL_BONE = () => 'aj.global.bone'
- export const GLOBAL_ITEM_DISPLAY = () => 'aj.global.item_display'
- export const GLOBAL_BLOCK_DISPLAY = () => 'aj.global.block_display'
- export const GLOBAL_TEXT_DISPLAY = () => 'aj.global.text_display'
- export const GLOBAL_CAMERA = () => 'aj.global.camera'
- export const GLOBAL_LOCATOR = () => 'aj.global.locator'
- export const GLOBAL_DATA = () => 'aj.global.data'
-
- export const GLOBAL_NODE_NAMED = (nodeName: string) => `aj.global.node.${nodeName}`
- export const GLOBAL_DISPLAY_NODE_NAMED = (nodeName: string) =>
- `aj.global.display_node.${nodeName}`
- export const GLOBAL_BONE_CHILD = (boneName: string) => `aj.global.bone.${boneName}.child`
- export const GLOBAL_BONE_CHILD_BONE = (boneName: string) =>
- `aj.global.bone.${boneName}.child.bone`
- export const GLOBAL_BONE_CHILD_ITEM_DISPLAY = (boneName: string) =>
- `aj.global.bone.${boneName}.child.item_display`
- export const GLOBAL_BONE_CHILD_BLOCK_DISPLAY = (boneName: string) =>
- `aj.global.bone.${boneName}.child.block_display`
- export const GLOBAL_BONE_CHILD_TEXT_DISPLAY = (boneName: string) =>
- `aj.global.bone.${boneName}.child.text_display`
- export const GLOBAL_BONE_CHILD_LOCATOR = (boneName: string) =>
- `aj.global.bone.${boneName}.child.locator`
- export const GLOBAL_BONE_CHILD_CAMERA = (boneName: string) =>
- `aj.global.bone.${boneName}.child.camera`
-
- export const GLOBAL_BONE_DECENDANT = (boneName: string) =>
- `aj.global.bone.${boneName}.decendant`
- export const GLOBAL_BONE_DECENDANT_BONE = (boneName: string) =>
- `aj.global.bone.${boneName}.decendant.bone`
- export const GLOBAL_BONE_DECENDANT_ITEM_DISPLAY = (boneName: string) =>
- `aj.global.bone.${boneName}.decendant.item_display`
- export const GLOBAL_BONE_DECENDANT_BLOCK_DISPLAY = (boneName: string) =>
- `aj.global.bone.${boneName}.decendant.block_display`
- export const GLOBAL_BONE_DECENDANT_TEXT_DISPLAY = (boneName: string) =>
- `aj.global.bone.${boneName}.decendant.text_display`
- export const GLOBAL_BONE_DECENDANT_LOCATOR = (boneName: string) =>
- `aj.global.bone.${boneName}.decendant.locator`
- export const GLOBAL_BONE_DECENDANT_CAMERA = (boneName: string) =>
- `aj.global.bone.${boneName}.decendant.camera`
-
- export const GLOBAL_BONE_TREE = (boneName: string) => `aj.global.bone.${boneName}.tree`
- export const GLOBAL_BONE_TREE_BONE = (boneName: string) =>
- `aj.global.bone.${boneName}.tree.bone`
-
- // --------------------------------
- // region Project Tags
- // --------------------------------
- export const PROJECT_ENTITY = (exportNamespace: string) => `aj.${exportNamespace}.entity`
-
- export const PROJECT_ROOT = (exportNamespace: string) => `aj.${exportNamespace}.root`
- export const PROJECT_ROOT_CHILD = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child`
- export const PROJECT_ROOT_CHILD_BONE = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child.bone`
- export const PROJECT_ROOT_CHILD_ITEM_DISPLAY = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child.item_display`
- export const PROJECT_ROOT_CHILD_BLOCK_DISPLAY = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child.block_display`
- export const PROJECT_ROOT_CHILD_TEXT_DISPLAY = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child.text_display`
- export const PROJECT_ROOT_CHILD_LOCATOR = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child.locator`
- export const PROJECT_ROOT_CHILD_CAMERA = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child.camera`
- export const PROJECT_ROOT_CHILD_DATA = (exportNamespace: string) =>
- `aj.${exportNamespace}.root.child.data`
-
- export const PROJECT_NODE = (exportNamespace: string) => `aj.${exportNamespace}.node`
- export const PROJECT_DISPLAY_NODE = (exportNamespace: string) =>
- `aj.${exportNamespace}.display_node`
- export const PROJECT_VANILLA_DISPLAY_NODE = (exportNamespace: string) =>
- `aj.${exportNamespace}.vanilla_display_node`
- export const PROJECT_BONE = (exportNamespace: string) => `aj.${exportNamespace}.bone`
- export const PROJECT_ITEM_DISPLAY = (exportNamespace: string) =>
- `aj.${exportNamespace}.item_display`
- export const PROJECT_BLOCK_DISPLAY = (exportNamespace: string) =>
- `aj.${exportNamespace}.block_display`
- export const PROJECT_TEXT_DISPLAY = (exportNamespace: string) =>
- `aj.${exportNamespace}.text_display`
- export const PROJECT_CAMERA = (exportNamespace: string) => `aj.${exportNamespace}.camera`
- export const PROJECT_LOCATOR = (exportNamespace: string) => `aj.${exportNamespace}.locator`
- export const PROJECT_DATA = (exportNamespace: string) => `aj.${exportNamespace}.data`
-
- export const PROJECT_NODE_NAMED = (exportNamespace: string, nodeName: string) =>
- `aj.${exportNamespace}.node.${nodeName}`
- export const PROJECT_BONE_NAMED = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}`
- export const PROJECT_DISPLAY_NODE_NAMED = (exportNamespace: string, nodeName: string) =>
- `aj.${exportNamespace}.display_node.${nodeName}`
- export const PROJECT_ITEM_DISPLAY_NAMED = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.item_display.${boneName}`
- export const PROJECT_BLOCK_DISPLAY_NAMED = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.block_display.${boneName}`
- export const PROJECT_TEXT_DISPLAY_NAMED = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.text_display.${boneName}`
- export const PROJECT_CAMERA_NAMED = (exportNamespace: string, cameraName: string) =>
- `aj.${exportNamespace}.camera.${cameraName}`
- export const PROJECT_LOCATOR_NAMED = (exportNamespace: string, locatorName: string) =>
- `aj.${exportNamespace}.locator.${locatorName}`
-
- export const PROJECT_BONE_CHILD = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.child`
- export const PROJECT_BONE_CHILD_BONE = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.child.bone`
- export const PROJECT_BONE_CHILD_ITEM_DISPLAY = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.child.item_display`
- export const PROJECT_BONE_CHILD_BLOCK_DISPLAY = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.child.block_display`
- export const PROJECT_BONE_CHILD_TEXT_DISPLAY = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.child.text_display`
- export const PROJECT_BONE_CHILD_LOCATOR = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.child.locator`
- export const PROJECT_BONE_CHILD_CAMERA = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.child.camera`
-
- export const PROJECT_BONE_DECENDANT = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.decendant`
- export const PROJECT_BONE_DECENDANT_BONE = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.decendant.bone`
- export const PROJECT_BONE_DECENDANT_ITEM_DISPLAY = (
- exportNamespace: string,
- boneName: string
- ) => `aj.${exportNamespace}.bone.${boneName}.decendant.item_display`
- export const PROJECT_BONE_DECENDANT_BLOCK_DISPLAY = (
- exportNamespace: string,
- boneName: string
- ) => `aj.${exportNamespace}.bone.${boneName}.decendant.block_display`
- export const PROJECT_BONE_DECENDANT_TEXT_DISPLAY = (
- exportNamespace: string,
- boneName: string
- ) => `aj.${exportNamespace}.bone.${boneName}.decendant.text_display`
- export const PROJECT_BONE_DECENDANT_LOCATOR = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.decendant.locator`
- export const PROJECT_BONE_DECENDANT_CAMERA = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.decendant.camera`
-
- export const PROJECT_BONE_TREE = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.tree`
- export const PROJECT_BONE_TREE_BONE = (exportNamespace: string, boneName: string) =>
- `aj.${exportNamespace}.bone.${boneName}.tree.bone`
-
- // --------------------------------
- // region Misc Tags
- // --------------------------------
- export const ANIMATION_PLAYING = (exportNamespace: string, animationName: string) =>
- `aj.${exportNamespace}.animation.${animationName}.playing`
- export const TWEENING = (exportNamespace: string, animationName: string) =>
- `aj.${exportNamespace}.animation.${animationName}.tween_playing`
- export const VARIANT_APPLIED = (exportNamespace: string, variantName: string) =>
- `aj.${exportNamespace}.variant.${variantName}.applied`
- // Used to tell the set and apply frame functions to only apply the bone transforms, and ignore command/variant keyframes
- export const TRANSFORMS_ONLY = () => 'aj.transforms_only'
- export const OUTDATED_RIG_TEXT_DISPLAY = () => 'aj.outdated_rig_text_display'
-}
diff --git a/src/systems/exporter.ts b/src/systems/exporter.ts
index f6e905b7..e25d2c04 100644
--- a/src/systems/exporter.ts
+++ b/src/systems/exporter.ts
@@ -1,17 +1,17 @@
-import { saveBlueprint } from '../blueprintFormat'
+import { saveBlueprint } from '../blockbench-additions/model-formats/ajblueprint'
import { blueprintSettingErrors } from '../blueprintSettings'
-import { openBlueprintSettingsDialog } from '../interface/dialog/blueprintSettings'
-import { PROGRESS_DESCRIPTION, openExportProgressDialog } from '../interface/dialog/exportProgress'
-import { openUnexpectedErrorDialog } from '../interface/dialog/unexpectedError'
+import { openBlueprintSettingsDialog } from '../ui/dialogs/blueprint-settings'
+import { openExportProgressDialog, PROGRESS_DESCRIPTION } from '../ui/dialogs/export-progress'
+import { openUnexpectedErrorDialog } from '../ui/dialogs/unexpected-error'
import { resolvePath } from '../util/fileUtil'
import { isResourcePackPath, sortMCVersions } from '../util/minecraftUtil'
import { translate } from '../util/translation'
import { Variant } from '../variants'
-import { hashAnimations, renderProjectAnimations } from './animationRenderer'
-import compileDataPack from './datapackCompiler'
-import resourcepackCompiler from './resourcepackCompiler'
-import { hashRig, renderRig } from './rigRenderer'
-import { isCubeValid } from './util'
+import { hashAnimations, renderProjectAnimations } from './animation-renderer'
+import datapackCompiler from './datapack-compiler'
+import { exportJSON } from './plugin-json-compiler'
+import resourcepackCompiler from './resourcepack-compiler'
+import { hashRig, renderRig } from './rig-renderer'
export class IntentionalExportError extends Error {}
@@ -24,11 +24,11 @@ export function getExportPaths() {
// These paths are all relative to the resource pack folder
const modelExportFolder = PathModule.join(
'assets/animated_java/models/blueprint/',
- aj.export_namespace
+ aj.id
)
const textureExportFolder = PathModule.join(
'assets/animated_java/textures/blueprint/',
- aj.export_namespace
+ aj.id
)
const displayItemPath = PathModule.join(
'assets/minecraft/models/item/',
@@ -143,7 +143,7 @@ async function actuallyExportProject(forceSave = true) {
})
}
- Project!.last_used_export_namespace = aj.export_namespace
+ Project!.last_used_export_namespace = aj.id
console.timeEnd('Exporting project took')
if (forceSave) saveBlueprint()
@@ -187,7 +187,6 @@ export async function exportProject(forceSave = true) {
const settingsDialog = openBlueprintSettingsDialog()!
// Wait for the dialog to open
await new Promise(resolve => requestAnimationFrame(resolve))
- console.log('Blueprint Setting Errors', blueprintSettingErrors.get())
if (Object.keys(blueprintSettingErrors.get()).length > 0) {
Blockbench.showMessageBox({
title: translate('misc.failed_to_export.title'),
diff --git a/src/systems/global.ts b/src/systems/global.ts
index 34f782db..9d677b02 100644
--- a/src/systems/global.ts
+++ b/src/systems/global.ts
@@ -1,108 +1,8 @@
-import { normalizePath } from '../util/fileUtil'
import { getDataPackFormat } from '../util/minecraftUtil'
import { IntentionalExportError } from './exporter'
-import { sortObjectKeys } from './util'
export type MinecraftVersion = '1.20.4' | '1.20.5' | '1.21.0' | '1.21.2' | '1.21.4' | '1.21.5'
-interface OldSerializedAJMeta {
- [key: string]: {
- files?: string[]
- }
-}
-
-interface SerializedAJMeta {
- formatVersion?: string
- rigs?: {
- [key: string]: {
- coreFiles?: string[]
- versionedFiles?: string[]
- }
- }
-}
-
-export class AJMeta {
- public coreFiles = new Set()
- public previousCoreFiles = new Set()
-
- public versionedFiles = new Set()
- public previousVersionedFiles = new Set()
-
- private previousAJMeta: SerializedAJMeta = {}
-
- constructor(
- public path: string,
- public exportNamespace: string,
- public lastUsedExportNamespace: string,
- public rootFolder: string
- ) {}
-
- read() {
- if (!fs.existsSync(this.path)) return
-
- try {
- this.previousAJMeta = JSON.parse(fs.readFileSync(this.path, 'utf-8'))
- } catch (e) {
- throw new IntentionalExportError(`Failed to read existing AJMeta file: ${e}`)
- }
-
- if (this.previousAJMeta.formatVersion !== '1.0.0') {
- // TODO - Use our new standardized solution to handle file versioning.
- // Assume the file is outdated, and update it.
- const outdated = this.previousAJMeta as OldSerializedAJMeta
- this.previousAJMeta = {
- formatVersion: '1.0.0',
- rigs: {},
- }
- for (const [key, value] of Object.entries(outdated)) {
- this.previousAJMeta.rigs![key] = {
- versionedFiles: value.files,
- }
- }
- }
-
- this.previousAJMeta.rigs ??= {}
-
- const lastNamespaceData = this.previousAJMeta.rigs[this.lastUsedExportNamespace]
- if (lastNamespaceData) {
- if (!Array.isArray(lastNamespaceData.versionedFiles))
- lastNamespaceData.versionedFiles = []
- for (const file of lastNamespaceData.versionedFiles) {
- this.previousVersionedFiles.add(PathModule.join(this.rootFolder, file))
- }
- delete this.previousAJMeta.rigs[this.lastUsedExportNamespace]
- }
-
- const namespaceData = this.previousAJMeta.rigs[this.exportNamespace]
- if (namespaceData) {
- if (!Array.isArray(namespaceData.versionedFiles)) namespaceData.versionedFiles = []
- for (const file of namespaceData.versionedFiles) {
- this.previousVersionedFiles.add(PathModule.join(this.rootFolder, file))
- }
- delete this.previousAJMeta.rigs[this.exportNamespace]
- }
- }
-
- write() {
- const resourcePackFolder = PathModule.dirname(this.path)
- const content: SerializedAJMeta = {
- formatVersion: '1.0.0',
- rigs: sortObjectKeys({
- ...this.previousAJMeta.rigs,
- [this.exportNamespace]: {
- coreFiles: Array.from(this.coreFiles)
- .map(v => normalizePath(PathModule.relative(resourcePackFolder, v)))
- .sort(),
- versionedFiles: Array.from(this.versionedFiles)
- .map(v => normalizePath(PathModule.relative(resourcePackFolder, v)))
- .sort(),
- },
- }),
- }
- fs.writeFileSync(this.path, autoStringify(content))
- }
-}
-
export type PackMetaFormats = number | number[] | { min_inclusive: number; max_inclusive: number }
interface OverlayEntry {
diff --git a/src/systems/minecraft-assets/assetCacheHandler.ts b/src/systems/minecraft-assets/assetCacheHandler.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/systems/minecraft-assets/index.ts b/src/systems/minecraft-assets/index.ts
new file mode 100644
index 00000000..8ea2b8d1
--- /dev/null
+++ b/src/systems/minecraft-assets/index.ts
@@ -0,0 +1,38 @@
+import EVENTS from '@events'
+import {
+ hideLoadingPopup,
+ showLoadingPopup,
+ showOfflineError,
+} from '../../ui/popups/animated-java-loading'
+
+EVENTS.LOAD.subscribe(() => {
+ // Show loading popup
+ void showLoadingPopup().then(async () => {
+ if (!window.navigator.onLine) {
+ showOfflineError()
+ // return
+ }
+ EVENTS.NETWORK_CONNECTED.dispatch()
+
+ await Promise.all([
+ new Promise(resolve => EVENTS.MINECRAFT_ASSETS_LOADED.subscribe(() => resolve())),
+ new Promise(resolve =>
+ EVENTS.MINECRAFT_REGISTRY_LOADED.subscribe(() => resolve())
+ ),
+ new Promise(resolve => EVENTS.MINECRAFT_FONTS_LOADED.subscribe(() => resolve())),
+ new Promise(resolve =>
+ EVENTS.BLOCKSTATE_REGISTRY_LOADED.subscribe(() => resolve())
+ ),
+ ])
+ .then(() => {
+ hideLoadingPopup()
+ })
+ .catch(error => {
+ console.error(error)
+ Blockbench.showToastNotification({
+ text: 'Animated Java failed to load! Please restart Blockbench',
+ color: 'var(--color-error)',
+ })
+ })
+ })
+})
diff --git a/src/systems/minecraft-assets/versionAPI.ts b/src/systems/minecraft-assets/versionAPI.ts
new file mode 100644
index 00000000..e0ba00a0
--- /dev/null
+++ b/src/systems/minecraft-assets/versionAPI.ts
@@ -0,0 +1,80 @@
+type SingleLetter =
+ | 'a'
+ | 'b'
+ | 'c'
+ | 'd'
+ | 'e'
+ | 'f'
+ | 'g'
+ | 'h'
+ | 'i'
+ | 'j'
+ | 'k'
+ | 'l'
+ | 'm'
+ | 'n'
+ | 'o'
+ | 'p'
+ | 'q'
+ | 'r'
+ | 's'
+ | 't'
+ | 'u'
+ | 'v'
+ | 'w'
+ | 'x'
+ | 'y'
+ | 'z'
+
+namespace MinecraftVersion {
+ export type AlphaVersionString = `a${string}`
+ export type SnapshotVersionString = `${number}w${number}${SingleLetter}`
+ export type PreReleaseVersionString = `${number}.${number}.pre${number}`
+ export type ReleaseVersionString = `${number}.${number}.${number}`
+ export type VersionString =
+ | ReleaseVersionString
+ | SnapshotVersionString
+ | PreReleaseVersionString
+ | AlphaVersionString
+ export type VersionType = 'release' | 'snapshot' | 'old_beta' | 'old_alpha'
+
+ export type VersionData = {
+ url: string
+ time: string
+ releaseTime: string
+ sha1: string
+ complianceLevel: number
+ } & (
+ | {
+ id: AlphaVersionString
+ type: 'old_alpha'
+ }
+ | {
+ id: SnapshotVersionString
+ type: 'snapshot'
+ }
+ | {
+ id: PreReleaseVersionString
+ type: 'old_beta'
+ }
+ | {
+ id: ReleaseVersionString
+ type: 'release'
+ }
+ )
+
+ export interface IMinecraftVersionManifest {
+ latest: {
+ release: MinecraftVersion.ReleaseVersionString
+ snapshot: MinecraftVersion.SnapshotVersionString
+ }
+ versions: VersionData[]
+ }
+}
+
+class VersionManager {
+ get latestVersion() {
+ return
+ }
+ set latestVerison(value: MinecraftVersion.VersionString) {}
+}
diff --git a/src/systems/minecraft-temp/assetManager.ts b/src/systems/minecraft-temp/assetManager.ts
new file mode 100644
index 00000000..1af40636
--- /dev/null
+++ b/src/systems/minecraft-temp/assetManager.ts
@@ -0,0 +1,181 @@
+import { PACKAGE } from '../../constants'
+import { getCurrentVersion, getLatestVersion } from './versionManager'
+
+import EVENTS from '@aj/util/events'
+import { type Unzipped } from 'fflate'
+import {
+ showOfflineError,
+ updateLoadingProgress,
+ updateLoadingProgressLabel,
+} from '../../ui/popups/animated-java-loading'
+import { unzip } from '../util'
+const ASSET_OVERRIDES: Record = {}
+
+async function downloadJar(url: string, savePath: string) {
+ updateLoadingProgressLabel('Downloading Minecraft Assets...')
+
+ let attempts = 3
+ let failed = false
+ let sink: Uint8Array
+ do {
+ try {
+ const res = await fetch(url)
+ if (!res) throw new Error('Failed to fetch Minecraft client.')
+ const reader = res.body?.getReader()
+ if (!reader) throw new Error('Failed to get reader from Minecraft client response.')
+ const length = parseInt(res.headers.get('Content-Length') ?? '0')
+ sink = new Uint8Array(length)
+ let offset = 0
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+ if (!value) throw new Error('Failed to read Minecraft client response.')
+ sink.set(value, offset)
+ offset += value.length
+ updateLoadingProgress((offset / length) * 100)
+ }
+ failed = false
+ } catch (e) {
+ console.error('Failed to download Minecraft client:', e)
+ failed = true
+ } finally {
+ attempts--
+ }
+ } while (attempts > 0)
+ // const data = await download(url, { retry: { retries: 3 } })
+ // .on('downloadProgress', progress => {
+ // updateLoadingProgress(progress.percent * 100)
+ // })
+ // .catch((error: any) => {
+ // console.error('Failed to download Minecraft client:', error)
+ // })
+
+ if (failed) {
+ showOfflineError()
+ throw new Error('Failed to download Minecraft client after 3 retries.')
+ }
+
+ await fs.promises.writeFile(savePath, sink!)
+}
+
+export async function getLatestVersionClientDownloadUrl() {
+ let retries = 3
+ const version = await getLatestVersion()
+
+ retries = 3
+ while (retries-- >= 0) {
+ let response: Response | undefined
+ try {
+ response = await fetch(version.url)
+ } catch (error) {
+ console.error('Failed to fetch latest Minecraft version API:', error)
+ }
+ if (response?.ok) {
+ const result = await response.json()
+ if (!result?.downloads?.client) {
+ throw new Error(`Failed to find client download for ${version.id}`)
+ }
+ return result.downloads.client.url as string
+ }
+ }
+ throw new Error('Failed to fetch latest Minecraft version API after 3 retries.')
+}
+
+function getCachedJarFilePath() {
+ const userDataPath = electron.app.getPath('userData')
+ return PathModule.join(userDataPath, `${PACKAGE.name}/latest.jar`)
+}
+
+export async function updateAssets() {
+ localStorage.setItem('assetsLoaded', 'false')
+
+ const downloadUrl = await getLatestVersionClientDownloadUrl()
+ console.log('Downloading latest Minecraft client:', downloadUrl)
+
+ const cachedJarFilePath = getCachedJarFilePath()
+ await fs.promises.mkdir(PathModule.dirname(cachedJarFilePath), { recursive: true })
+ await downloadJar(downloadUrl, cachedJarFilePath)
+ console.log('Downloaded latest Minecraft client:', cachedJarFilePath)
+}
+
+export async function checkForAssetsUpdate() {
+ console.log('Checking for Minecraft assets update...')
+
+ const currentVersion = getCurrentVersion()
+ if (!currentVersion) {
+ console.log('No current Minecraft version found, updating assets...')
+ await updateAssets()
+ } else {
+ const latestVersion = await getLatestVersion()
+ if (currentVersion.id !== latestVersion.id) {
+ console.log('Minecraft assets are outdated, updating...')
+ await updateAssets()
+ }
+ }
+
+ const cachedJarFilePath = getCachedJarFilePath()
+ if (!fs.existsSync(cachedJarFilePath) || !(localStorage.getItem('assetsLoaded') === 'true')) {
+ console.log('No cached Minecraft client found, updating assets...')
+ await updateAssets()
+ }
+
+ console.log('Does file exist?', fs.existsSync(cachedJarFilePath))
+ console.log('Are assets loaded?', localStorage.getItem('assetsLoaded') === 'true')
+
+ await extractAssets()
+ console.log('Minecraft assets are up to date!')
+ localStorage.setItem('assetsLoaded', 'true')
+ requestAnimationFrame(() => EVENTS.MINECRAFT_ASSETS_LOADED.dispatch())
+}
+
+let loadedAssets: Unzipped | undefined
+export async function extractAssets() {
+ const cachedJarFilePath = getCachedJarFilePath()
+
+ loadedAssets = await unzip(new Uint8Array(await fs.promises.readFile(cachedJarFilePath)), {
+ filter: v => v.name.startsWith('assets/'),
+ })
+}
+
+export async function assetsLoaded() {
+ return new Promise(resolve => {
+ if (loadedAssets !== undefined) {
+ resolve()
+ } else {
+ EVENTS.MINECRAFT_ASSETS_LOADED.subscribe(() => resolve(), true)
+ }
+ })
+}
+
+export function hasAsset(path: string) {
+ if (!loadedAssets) throw new Error('Assets not loaded')
+ return !!loadedAssets[path]
+}
+
+export function getRawAsset(path: string) {
+ if (!loadedAssets) throw new Error('Assets not loaded')
+
+ if (ASSET_OVERRIDES[path]) {
+ if (path.endsWith('.png')) {
+ return Buffer.from(ASSET_OVERRIDES[path], 'base64')
+ }
+ return ASSET_OVERRIDES[path]
+ }
+
+ const asset = loadedAssets[path]
+ if (!asset) throw new Error(`Asset not found: ${path}`)
+ return asset
+}
+
+export function getPngAssetAsDataUrl(path: string) {
+ const asset = getRawAsset(path)
+ if (!asset) throw new Error(`Asset not found: ${path}`)
+ return `data:image/png;base64,${Buffer.from(asset).toString('base64')}`
+}
+
+export function getJSONAsset(path: string): any {
+ const asset = getRawAsset(path)
+ if (!asset) throw new Error(`Asset not found: ${path}`)
+ const json = JSON.parse(Buffer.from(asset).toString('utf-8'))
+ return json
+}
diff --git a/src/systems/minecraft-temp/blockModelManager.ts b/src/systems/minecraft-temp/blockModelManager.ts
new file mode 100644
index 00000000..72686b7e
--- /dev/null
+++ b/src/systems/minecraft-temp/blockModelManager.ts
@@ -0,0 +1,564 @@
+import { mergeGeometries } from '../../util/bufferGeometryUtils'
+import {
+ type IParsedBlock,
+ getPathFromResourceLocation,
+ parseBlock,
+ resolveBlockstateValueType,
+} from '../../util/minecraftUtil'
+import { translate } from '../../util/translation'
+import { assetsLoaded, getJSONAsset, getPngAssetAsDataUrl, hasAsset } from './assetManager'
+import { type BlockStateValue } from './blockstateManager'
+import type {
+ IBlockModel,
+ IBlockState,
+ IBlockStateMultipartCase,
+ IBlockStateMultipartCaseCondition,
+ IBlockStateVariant,
+} from './model'
+import { TEXTURE_FRAG_SHADER, TEXTURE_VERT_SHADER } from './textureShaders'
+
+interface BlockModelMesh {
+ mesh: THREE.Mesh
+ outline: THREE.LineSegments
+ boundingBox: THREE.BufferGeometry
+ isBlock: true
+}
+
+const LOADER = new THREE.TextureLoader()
+const BLOCK_MODEL_CACHE = new Map()
+
+const BLACKLISTED_BLOCKS = new Map([
+ ['water', translate('block_model_manager.fluid_warning')],
+ ['lava', translate('block_model_manager.fluid_warning')],
+
+ ['player_head', translate('block_model_manager.mob_head_warning')],
+ ['player_wall_head', translate('block_model_manager.mob_head_warning')],
+ ['skeleton_skull', translate('block_model_manager.mob_head_warning')],
+ ['skeleton_wall_skull', translate('block_model_manager.mob_head_warning')],
+ ['wither_skeleton_skull', translate('block_model_manager.mob_head_warning')],
+ ['wither_skeleton_wall_skull', translate('block_model_manager.mob_head_warning')],
+ ['creeper_head', translate('block_model_manager.mob_head_warning')],
+ ['creeper_wall_head', translate('block_model_manager.mob_head_warning')],
+ ['zombie_head', translate('block_model_manager.mob_head_warning')],
+ ['zombie_wall_head', translate('block_model_manager.mob_head_warning')],
+ ['dragon_head', translate('block_model_manager.mob_head_warning')],
+ ['dragon_wall_head', translate('block_model_manager.mob_head_warning')],
+ ['piglin_head', translate('block_model_manager.mob_head_warning')],
+ ['piglin_wall_head', translate('block_model_manager.mob_head_warning')],
+])
+
+export async function getBlockModel(block: string): Promise {
+ await assetsLoaded()
+ let result = BLOCK_MODEL_CACHE.get(block)
+ if (!result) {
+ // console.warn(`Found no cached item model mesh for '${block}'`)
+ const parsed = await parseBlock(block)
+ if (!parsed) return undefined
+ if (BLACKLISTED_BLOCKS.has(block)) {
+ throw new Error(BLACKLISTED_BLOCKS.get(block))
+ }
+ result = await parseBlockState(parsed)
+ BLOCK_MODEL_CACHE.set(block, result)
+ }
+ if (!result) return undefined
+ result = {
+ mesh: result.mesh.clone(true),
+ outline: result.outline.clone(true),
+ boundingBox: result.boundingBox.clone(),
+ isBlock: true,
+ }
+ for (const child of result.mesh.children as THREE.Mesh[]) {
+ child.geometry = child.geometry.clone()
+ }
+ result.mesh.geometry = result.mesh.geometry.clone()
+ result.mesh.name = block
+ result.mesh.isVanillaBlockModel = true
+ return result
+}
+
+export async function parseBlockModel(
+ variant: IBlockStateVariant,
+ childModel?: IBlockModel
+): Promise {
+ const modelPath = getPathFromResourceLocation(variant.model, 'models')
+ const model = getJSONAsset(modelPath + '.json') as IBlockModel
+
+ if (childModel) {
+ if (childModel.textures !== undefined) {
+ model.textures ??= {}
+ Object.assign(model.textures, childModel.textures)
+ }
+ // Interesting that elements aren't merged in vanilla...
+ if (childModel.elements !== undefined) model.elements = childModel.elements
+ if (childModel.display !== undefined)
+ model.display = Object.assign(model.display ?? {}, childModel.display)
+ if (childModel.ambientocclusion !== undefined)
+ model.ambientocclusion = childModel.ambientocclusion
+ }
+
+ if (model.parent) {
+ const parentVariant = { ...variant, model: model.parent }
+ return await parseBlockModel(parentVariant, model)
+ }
+
+ return await generateModelMesh(variant, model)
+}
+
+async function generateModelMesh(
+ variant: IBlockStateVariant,
+ model: IBlockModel
+): Promise {
+ console.log(`Generating block mesh for '${variant.model}' from `, variant, model)
+
+ if (!model.elements) {
+ throw new Error(`No elements defined in block model '${variant.model}'`)
+ }
+ if (!model.textures) {
+ throw new Error(`No textures defined in block model '${variant.model}'`)
+ }
+
+ const mesh: THREE.Mesh = new THREE.Mesh()
+ const boundingBoxes: THREE.BufferGeometry[] = []
+ const outlineGeos: THREE.BufferGeometry[] = []
+
+ for (const element of model.elements) {
+ const size: ArrayVector3 = [
+ element.to[0] - element.from[0],
+ element.to[1] - element.from[1],
+ element.to[2] - element.from[2],
+ ]
+ const position: ArrayVector3 = [
+ element.from[0] + (element.to[0] - element.from[0]) / 2,
+ element.from[1] + (element.to[1] - element.from[1]) / 2,
+ element.from[2] + (element.to[2] - element.from[2]) / 2,
+ ]
+ if (size[0] === 0) {
+ size[0] += 0.01
+ position[0] -= 0.005
+ }
+ if (size[1] === 0) {
+ size[1] += 0.01
+ position[1] -= 0.005
+ }
+ if (size[2] === 0) {
+ size[2] += 0.01
+ position[2] -= 0.005
+ }
+
+ const geometry = new THREE.BoxGeometry(...size)
+ geometry.translate(...position)
+
+ if (element.rotation) {
+ let factor: number | undefined
+ if (element.rotation.rescale) {
+ factor = getRescalingFactor(element.rotation.angle)
+ }
+
+ const origin = element.rotation.origin
+ if (origin) {
+ geometry.translate(...(origin.map(v => -v) as ArrayVector3))
+ }
+
+ switch (element.rotation.axis) {
+ case 'x':
+ geometry.rotateX(Math.degToRad(element.rotation.angle))
+ if (factor !== undefined) geometry.scale(1, factor, factor)
+ break
+ case 'y':
+ geometry.rotateY(Math.degToRad(element.rotation.angle))
+ if (factor !== undefined) geometry.scale(factor, 1, factor)
+ break
+ case 'z':
+ geometry.rotateZ(Math.degToRad(element.rotation.angle))
+ if (factor !== undefined) geometry.scale(factor, factor, 1)
+ break
+ }
+
+ if (origin) {
+ geometry.translate(...origin)
+ }
+ }
+
+ geometry.translate(-8, -8, -8)
+ // geometry.rotateY(Math.degToRad(180))
+ if (variant.x) geometry.rotateX(Math.degToRad(variant.x))
+ if (variant.y) geometry.rotateY(-Math.degToRad(variant.y))
+ if (variant.isItemModel) {
+ geometry.translate(0, 8, 0)
+ } else {
+ geometry.translate(8, 8, 8)
+ }
+
+ const indices = []
+ for (let i = 0; i < 6; i++) {
+ indices.push(0 + i * 4, 2 + i * 4, 1 + i * 4, 2 + i * 4, 3 + i * 4, 1 + i * 4)
+ geometry.addGroup(i * 6, 6, i)
+ }
+ geometry.setIndex(indices)
+ geometry.setAttribute(
+ 'highlight',
+ new THREE.BufferAttribute(new Uint8Array(geometry.attributes.position.count), 1)
+ )
+
+ if (!element.faces) {
+ throw new Error(`No faces defined in element for block model '${variant.model}'`)
+ }
+
+ const uvs: number[] = []
+ const materials: THREE.Material[] = []
+ for (const faceKey of Canvas.face_order) {
+ const face = element.faces[faceKey]
+ if (!face) {
+ materials.push(Canvas.transparentMaterial)
+ uvs.push(0, 0, 0, 0, 0, 0, 0, 0)
+ continue
+ }
+ const texture = (await loadTexture(model.textures, face.texture)).clone()
+ const material = new THREE.ShaderMaterial({
+ uniforms: {
+ map: new THREE.Uniform(texture),
+ // @ts-expect-error
+ SHADE: { type: 'bool', value: settings.shading.value },
+ LIGHTCOLOR: {
+ // @ts-expect-error
+ type: 'vec3',
+ value: new THREE.Color()
+ .copy(Canvas.global_light_color)
+ .multiplyScalar(settings.brightness.value / 50),
+ },
+ // @ts-expect-error
+ LIGHTSIDE: { type: 'int', value: Canvas.global_light_side },
+ // @ts-expect-error
+ EMISSIVE: { type: 'bool', value: false },
+ },
+ vertexShader: TEXTURE_VERT_SHADER,
+ fragmentShader: TEXTURE_FRAG_SHADER,
+ blending: THREE.NormalBlending,
+ side: Canvas.getRenderSide(),
+ transparent: true,
+ })
+ // @ts-expect-error
+ material.map = texture
+ material.name = variant.model
+ materials.push(material)
+
+ // UVs are always based on 16x16 texture width, not the actual texture size
+ const tw = 16
+ const th = 16
+ // const tw = texture.image.width
+ // const th = texture.image.height
+ if (face.uv) {
+ const [x, y, w, h] = face.uv
+ // The THREE face UV has a strange order.
+ // upper-left, upper-right, lower-left, lower-right
+ // The MC input UV is only upper-left, and lower-right coords.
+ // prettier-ignore
+ const uv = [
+ [x / tw, y / th],
+ [w / tw, y / th],
+ [x / tw, h / th],
+ [w / tw, h / th],
+ ]
+ if (face.rotation) {
+ let rot = face.rotation + 0
+ while (rot > 0) {
+ const a = uv[0]
+ uv[0] = uv[2]
+ uv[2] = uv[3]
+ uv[3] = uv[1]
+ uv[1] = a
+ rot -= 90
+ }
+ }
+ texture.flipY = false
+
+ uvs.push(...uv.flat())
+ } else {
+ // Create uvs based on the element's position and size
+ const [x, y, z] = element.from
+ const [w, h, d] = size
+ switch (faceKey) {
+ // Up and down faces are flipped on both axes
+ case 'down':
+ // prettier-ignore
+ uvs.push(
+ x / tw, z / th,
+ (x + w) / tw, z / th,
+ x / tw, (z + d) / th,
+ (x + w) / tw, (z + d) / th,
+ )
+ break
+ case 'up':
+ // prettier-ignore
+ uvs.push(
+ x / tw, z / th,
+ (x + w) / tw, z / th,
+ x / tw, (z + d) / th,
+ (x + w) / tw, (z + d) / th,
+ )
+ break
+ case 'north':
+ // prettier-ignore
+ uvs.push(
+ (x + w) / tw, (y + h) / th,
+ x / tw, (y + h) / th,
+ (x + w) / tw, y / th,
+ x / tw, y / th,
+ )
+ break
+ case 'south':
+ // prettier-ignore
+ uvs.push(
+ (x + w) / tw, (y + h) / th,
+ x / tw, (y + h) / th,
+ (x + w) / tw, y / th,
+ x / tw, y / th,
+ )
+ break
+ case 'west':
+ // prettier-ignore
+ uvs.push(
+ (z + d) / tw, (y + h) / th,
+ z / tw, (y + h) / th,
+ (z + d) / tw, y / th,
+ z / tw, y / th,
+ )
+ break
+ case 'east':
+ // prettier-ignore
+ uvs.push(
+ (z + d) / tw, (y + h) / th,
+ z / tw, (y + h) / th,
+ (z + d) / tw, y / th,
+ z / tw, y / th,
+ )
+ break
+ }
+ }
+ texture.needsUpdate = true
+ }
+
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2))
+ geometry.attributes.uv.needsUpdate = true
+
+ boundingBoxes.push(geometry.clone())
+ const outlineGeo = new THREE.EdgesGeometry(geometry)
+ outlineGeos.push(outlineGeo)
+ const elementMesh = new THREE.Mesh(geometry, materials)
+ mesh.add(elementMesh)
+ }
+
+ const outlineGeo = mergeGeometries(outlineGeos)!
+ const outline = new THREE.LineSegments(outlineGeo, Canvas.outlineMaterial)
+ const boundingBox = mergeGeometries(boundingBoxes)!
+
+ outline.no_export = true
+ outline.renderOrder = 2
+ outline.frustumCulled = false
+
+ return { mesh, outline, boundingBox, isBlock: true }
+}
+
+const TEXTURE_CACHE = new Map()
+async function loadTexture(textures: IBlockModel['textures'], key: string): Promise {
+ if (key.at(0) === '#') key = key.slice(1)
+ const resourceLocation = textures[key]
+ if (resourceLocation?.at(0) === '#') {
+ return await loadTexture(textures, resourceLocation.slice(1))
+ }
+ const texturePath = getPathFromResourceLocation(resourceLocation, 'textures') + '.png'
+ if (TEXTURE_CACHE.has(texturePath)) {
+ return TEXTURE_CACHE.get(texturePath)!
+ }
+ let texture: THREE.Texture
+ if (hasAsset(texturePath + '.mcmeta')) {
+ console.log(`Found mcmeta for texture '${texturePath}'`)
+
+ const img = new Image()
+ img.src = getPngAssetAsDataUrl(texturePath)
+ const canvas = document.createElement('canvas')
+ const ctx = canvas.getContext('2d')!
+ await new Promise(resolve => {
+ img.onload = () => {
+ canvas.width = img.width
+ canvas.height = img.width
+ ctx.drawImage(img, 0, 0)
+ resolve()
+ }
+ })
+ texture = new THREE.CanvasTexture(canvas)
+ } else {
+ texture = await LOADER.loadAsync(getPngAssetAsDataUrl(texturePath))
+ }
+
+ texture.magFilter = THREE.NearestFilter
+ texture.minFilter = THREE.NearestFilter
+
+ TEXTURE_CACHE.set(texturePath, texture)
+
+ return texture
+}
+
+export async function parseBlockState(block: IParsedBlock): Promise {
+ const path = getPathFromResourceLocation(block.resourceLocation, 'blockstates')
+ const blockstate = (await getJSONAsset(path + '.json')) as IBlockState
+ if (!block.blockStateRegistryEntry) {
+ throw new Error(`Block state registry entry not found for '${block.resource.name}'`)
+ }
+ // Make sure the block has all the default states
+ block.states = Object.assign({}, block.blockStateRegistryEntry.defaultStates, block.states)
+
+ for (const [k, v] of Object.entries(block.states)) {
+ if (!block.blockStateRegistryEntry.stateValues[k]) {
+ throw new Error(
+ `Invalid block state '${k}' for '${block.resource.name}'` +
+ ` Expected one of: ${Object.keys(
+ block.blockStateRegistryEntry.stateValues
+ ).join(', ')}`
+ )
+ } else if (!block.blockStateRegistryEntry.stateValues[k].includes(v)) {
+ throw new Error(
+ `Invalid block state value '${v.toString()}' for '${k}'.` +
+ ` Expected one of: ${block.blockStateRegistryEntry.stateValues[k].join(', ')}`
+ )
+ }
+ }
+
+ if (blockstate.variants) {
+ const singleVariant = blockstate.variants['']
+ if (singleVariant) {
+ if (Array.isArray(singleVariant)) {
+ return await parseBlockModel(singleVariant[0])
+ } else {
+ return await parseBlockModel(singleVariant)
+ }
+ }
+
+ for (const [name, variant] of Object.entries(blockstate.variants)) {
+ const variantStates: Record> = {}
+ const args = name.split(',')
+ for (const arg of args) {
+ const [key, value] = arg.trim().split('=')
+ const parsedValue = resolveBlockstateValueType(value, false)
+ variantStates[key] = parsedValue
+ }
+
+ const matchesState = Object.entries(variantStates).allAre(([k, v]) =>
+ checkIfBlockStateMatches(block, k, v, false)
+ )
+
+ if (!matchesState) {
+ continue
+ }
+
+ let model: BlockModelMesh
+ if (Array.isArray(variant)) {
+ model = await parseBlockModel(variant[0])
+ } else {
+ model = await parseBlockModel(variant)
+ }
+ return model
+ }
+ } else if (blockstate.multipart) {
+ // throw new Error(`Multipart block states are not supported yet`)
+ const mesh = new THREE.Mesh()
+ const boundingBoxes: THREE.BufferGeometry[] = []
+ const outlines: THREE.BufferGeometry[] = []
+ for (const c of blockstate.multipart) {
+ const result = await parseMultipartCase(block, c)
+ if (!result) continue
+ for (const child of result.mesh.children as THREE.Mesh[]) {
+ const newChild = child.clone()
+ newChild.geometry = newChild.geometry.clone()
+ newChild.rotateY(result.mesh.rotation.y)
+ newChild.rotateX(result.mesh.rotation.x)
+ mesh.add(newChild)
+ const boundingBox = result.boundingBox.clone()
+ boundingBox.rotateY(result.mesh.rotation.y)
+ boundingBox.rotateX(result.mesh.rotation.x)
+ boundingBoxes.push(boundingBox)
+ }
+ const outlineGeo = result.outline.geometry.clone()
+ outlineGeo.rotateY(result.mesh.rotation.y)
+ outlineGeo.rotateX(result.mesh.rotation.x)
+ outlines.push(outlineGeo)
+ }
+
+ if (outlines.length === 0) {
+ throw new Error(
+ `The selected block state for '${block.resourceLocation}' has no model!`
+ )
+ }
+
+ const outlineGeo = mergeGeometries(outlines)!
+ const outline = new THREE.LineSegments(outlineGeo, Canvas.outlineMaterial)
+ const boundingBox = mergeGeometries(boundingBoxes)!
+
+ outline.no_export = true
+ outline.renderOrder = 2
+ outline.frustumCulled = false
+
+ return { mesh, outline, boundingBox, isBlock: true }
+ }
+
+ throw new Error(`Unsupported block state '${block.resourceLocation}'`)
+}
+
+async function parseMultipartCase(
+ block: IParsedBlock,
+ mpCase: IBlockStateMultipartCase
+): Promise {
+ if (mpCase.when) {
+ const recurse = (c: IBlockStateMultipartCaseCondition): boolean => {
+ if (c.OR && c.AND) {
+ throw new Error(`Cannot have both OR and AND in a multipart case condition`)
+ } else if (c.OR) {
+ return c.OR.some(v => recurse(v))
+ } else if (c.AND) {
+ return c.AND.every(v => recurse(v))
+ }
+
+ let matchesState = true
+ for (const [k, v] of Object.entries(c) as Array<[string, string]>) {
+ const parsedValue = resolveBlockstateValueType(v, true)
+ matchesState = checkIfBlockStateMatches(block, k, parsedValue, true)
+ if (!matchesState) break
+ }
+ return matchesState
+ }
+ const result = recurse(mpCase.when)
+ if (!result) return
+ }
+ if (Array.isArray(mpCase.apply)) {
+ return await parseBlockModel(mpCase.apply[0])
+ } else {
+ return await parseBlockModel(mpCase.apply)
+ }
+}
+
+function checkIfBlockStateMatches(
+ block: IParsedBlock,
+ key: string,
+ value: BlockStateValue,
+ allowArray: boolean
+) {
+ if (typeof value === 'string' && value.includes('|')) {
+ if (!allowArray)
+ throw new Error(`Unsupported OR condition in block state '${key}': '${value}'`)
+ value = value.split('|')
+ }
+
+ if (typeof value === 'boolean') {
+ return !!block.states[key] === value
+ } else if (typeof value === 'string') {
+ return block.states[key] === value
+ } else if (typeof value === 'number') {
+ return value === 0
+ ? block.states[key] === value || block.states[key] === undefined
+ : block.states[key] === value
+ } else if (allowArray) {
+ return value.includes(block.states[key] as string | number | boolean)
+ } else {
+ throw new Error(`Unsupported variant state type '${typeof value}'`)
+ }
+}
diff --git a/src/systems/minecraft-temp/blockstateManager.ts b/src/systems/minecraft-temp/blockstateManager.ts
new file mode 100644
index 00000000..989123a1
--- /dev/null
+++ b/src/systems/minecraft-temp/blockstateManager.ts
@@ -0,0 +1,109 @@
+import EVENTS from '@events'
+import { resolveBlockstateValueType } from '../../util/minecraftUtil'
+import { getLatestVersion } from './versionManager'
+
+type BlockStateRegistryJSON = Record
+
+type BlockStateRegistryData = [Record, Record]
+
+const REGISTRIES_URL = 'https://raw.githubusercontent.com/misode/mcmeta/summary/blocks/data.json'
+
+export type BlockStateValue = string | number | boolean | Array
+
+export class BlockStateRegistryEntry {
+ public defaultStates: Record = {}
+ public stateValues: Record = {}
+
+ constructor(blockstate: BlockStateRegistryData) {
+ for (const [k, v] of Object.entries(blockstate[1])) {
+ this.defaultStates[k] = resolveBlockstateValueType(v, false)
+ }
+ for (const [k, v] of Object.entries(blockstate[0])) {
+ this.stateValues[k] = v.map(v => resolveBlockstateValueType(v, false))
+ }
+ }
+}
+
+type BlockStateRegistry = Record
+export const BLOCKSTATE_REGISTRY = {} as BlockStateRegistry
+
+function updateMemoryRegistry() {
+ const registryString = localStorage.getItem('animated_java:blockStateRegistry')
+ if (!registryString) {
+ console.error('BlockState Registry not found in local storage')
+ return
+ }
+ const registry = JSON.parse(registryString) as BlockStateRegistryJSON
+ for (const key in registry) {
+ BLOCKSTATE_REGISTRY[key] = new BlockStateRegistryEntry(registry[key])
+ }
+}
+
+async function updateLocalRegistry() {
+ console.log('Updating BlockState Registry...')
+ let retries = 3
+ while (retries-- >= 0) {
+ let response
+ try {
+ response = await fetch(REGISTRIES_URL)
+ } catch (error) {
+ console.error('Failed to fetch latest BlockState registry:', error)
+ }
+ if (response?.ok) {
+ const newRegistry = (await response.json()) as BlockStateRegistryJSON
+ localStorage.setItem('animated_java:blockStateRegistry', JSON.stringify(newRegistry))
+ const latestVersion = await getLatestVersion()
+ localStorage.setItem(
+ 'animated_java:blockStateRegistryVersion',
+ JSON.stringify(latestVersion)
+ )
+ console.log('BlockState Registry updated!')
+ return
+ }
+ }
+ throw new Error('Failed to fetch latest BlockState registry after 3 retries.')
+}
+
+export async function checkForRegistryUpdate() {
+ console.log('Checking if BlockState Registry update...')
+ const currentValueString = localStorage.getItem('animated_java:blockStateRegistry')
+ if (!currentValueString) {
+ console.log('No BlockState Registry found. Updating...')
+ await updateLocalRegistry()
+ return
+ }
+ const currentVersionString = localStorage.getItem('animated_java:blockStateRegistryVersion')
+ if (!currentVersionString) {
+ console.log('No BlockState Registry version found. Updating...')
+ await updateLocalRegistry()
+ return
+ }
+ const currentVersion = JSON.parse(currentVersionString)
+ const latestVersion = await getLatestVersion()
+ if (currentVersion.id !== latestVersion.id) {
+ console.log('BlockState Registry is outdated. Updating...')
+ await updateLocalRegistry()
+ return
+ }
+
+ console.log('BlockState Registry is up to date!')
+ updateMemoryRegistry()
+ requestAnimationFrame(() => EVENTS.BLOCKSTATE_REGISTRY_LOADED.dispatch())
+}
+
+export async function getBlockState(block: string) {
+ if (Object.keys(BLOCKSTATE_REGISTRY).length === 0) {
+ return new Promise(resolve => {
+ EVENTS.BLOCKSTATE_REGISTRY_LOADED.subscribe(() => {
+ resolve(BLOCKSTATE_REGISTRY[block])
+ }, true)
+ })
+ }
+ return BLOCKSTATE_REGISTRY[block]
+}
+
+EVENTS.LOAD.subscribe(() => {
+ void checkForRegistryUpdate().catch(err => {
+ console.error(err)
+ })
+})
diff --git a/src/systems/minecraft-temp/fontManager.ts b/src/systems/minecraft-temp/fontManager.ts
new file mode 100644
index 00000000..1a44bc0d
--- /dev/null
+++ b/src/systems/minecraft-temp/fontManager.ts
@@ -0,0 +1,773 @@
+import MissingCharacter from '@assets/missing_character.png'
+import EVENTS from '@events'
+import { createHash } from 'crypto'
+import { mergeGeometries } from '../../util/bufferGeometryUtils'
+import { getPathFromResourceLocation } from '../../util/minecraftUtil'
+import { UnicodeString } from '../../util/unicodeString'
+import type { TextDisplayConfig } from '../node-configs'
+import * as assets from './assetManager'
+import { COLOR_MAP, JsonText } from './jsonText'
+import {
+ computeTextWrapping,
+ getComponentWords,
+ type IComponentWord,
+ type IStyleSpan,
+ type StyleRecord,
+} from './textWrapping'
+
+interface IFontProviderBitmap {
+ type: 'bitmap'
+ file: string
+ height?: number
+ // FIXME This isn't actually used for anything yet...
+ ascent: number
+ chars: string[]
+}
+
+interface IFontProviderReference {
+ type: 'reference'
+ id: string
+ filter?: {
+ uniform?: boolean
+ }
+}
+
+interface IFontProviderSpace {
+ type: 'space'
+ advances: Record
+}
+
+type IFontProvider = IFontProviderBitmap | IFontProviderReference | IFontProviderSpace
+
+interface IFont {
+ providers: IFontProvider[]
+}
+
+interface ICachedBitmapChar {
+ type: 'bitmap'
+ ascent: number
+ width: number
+ atlas: THREE.Texture
+ pixelUV: [number, number, number, number]
+ uv: [number, number, number, number]
+}
+
+interface ICachedSpaceChar {
+ type: 'space'
+ width: number
+}
+
+type ICachedChar = ICachedBitmapChar | ICachedSpaceChar
+
+interface ICachedCharMesh {
+ geo?: THREE.BufferGeometry
+ width: number
+}
+
+class FontProvider {
+ public type: 'bitmap' | 'reference' | 'space'
+ public loaded = false
+
+ constructor(providerJSON: IFontProvider) {
+ this.type = providerJSON.type
+ }
+
+ // eslint-disable-next-line @typescript-eslint/require-await
+ async load() {
+ if (this.loaded) return this
+ this.loaded = true
+ return this
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ getChar(_char: string, top = true): ICachedChar | undefined {
+ return undefined
+ }
+
+ static fromAssetPath(assetPath: string) {
+ if (!assetPath.endsWith('.json')) assetPath += '.json'
+ const providerJSON = assets.getJSONAsset(assetPath) as IFontProvider
+ switch (providerJSON.type) {
+ case 'bitmap':
+ return new BitmapFontProvider(providerJSON)
+ case 'reference':
+ return new ReferenceFontProvider(providerJSON)
+ case 'space':
+ return new SpaceFontProvider(providerJSON)
+ default:
+ throw new Error(
+ `Unsupported font provider type: ${(providerJSON as any).type as string}`
+ )
+ }
+ }
+}
+
+class ReferenceFontProvider extends FontProvider {
+ public reference: MinecraftFont
+
+ constructor(providerJSON: IFontProviderReference) {
+ super(providerJSON)
+ const path = getPathFromResourceLocation(providerJSON.id, 'font')
+ this.reference = new MinecraftFont(providerJSON.id, path + '.json')
+ }
+
+ async load() {
+ if (this.loaded) return this
+ await this.reference.load()
+ this.loaded = true
+ return this
+ }
+
+ getChar(char: string, top = true) {
+ return this.reference.getChar(char, top)
+ }
+}
+
+class SpaceFontProvider extends FontProvider {
+ public advances: Record
+
+ constructor(providerJSON: IFontProviderSpace) {
+ super(providerJSON)
+ this.advances = providerJSON.advances
+ }
+
+ getChar(char: string): ICachedChar | undefined {
+ if (this.advances[char] !== undefined) {
+ return {
+ type: 'space',
+ width: this.advances[char],
+ }
+ }
+ }
+}
+
+class BitmapFontProvider extends FontProvider {
+ public bitmapPath: string
+ public charHeight: number
+ public charWidth: number
+ public ascent: number
+ public chars: UnicodeString[] = []
+
+ public atlas: THREE.Texture = THREE.Texture.DEFAULT_IMAGE
+ public canvas: HTMLCanvasElement = document.createElement('canvas')
+
+ private charCache = new Map()
+
+ constructor(providerJSON: IFontProviderBitmap) {
+ super(providerJSON)
+ this.type = providerJSON.type
+ this.bitmapPath = getPathFromResourceLocation(providerJSON.file, 'textures')
+ this.charHeight = providerJSON.height ?? 8
+ this.charWidth = 8
+ this.ascent = providerJSON.ascent
+ for (const row of providerJSON.chars) {
+ this.chars.push(new UnicodeString(row))
+ }
+ }
+
+ async load() {
+ if (this.loaded) return this
+ const dataUrl = assets.getPngAssetAsDataUrl(this.bitmapPath)
+ const texture = await new THREE.TextureLoader().loadAsync(dataUrl)
+
+ this.atlas = texture
+ this.charHeight = texture.image.height / this.chars.length
+ this.charWidth = texture.image.width / this.chars[0].length
+ // Update canvas
+ this.canvas.width = texture.image.width
+ this.canvas.height = texture.image.height
+ const ctx = this.canvas.getContext('2d')!
+ ctx.drawImage(this.atlas.image as HTMLImageElement, 0, 0)
+ this.loaded = true
+ return this
+ }
+
+ private getCharIndex(char: string): [number, number] {
+ for (const row of this.chars) {
+ if (row.includes(char)) {
+ return [this.chars.indexOf(row), row.indexOf(char)]
+ }
+ }
+ return [-1, -1]
+ }
+
+ getChar(char: string) {
+ if (!this.charCache.has(char)) {
+ const charPos = this.getCharIndex(char)
+ if (charPos[0] === -1) return
+ // Figure out how wide the character is by checking for the last non-transparent pixel
+ const startX = charPos[1] * this.charWidth
+ const startY = charPos[0] * this.charHeight
+ const data = this.canvas
+ .getContext('2d')!
+ .getImageData(startX, startY, this.charWidth, this.charHeight)
+
+ let width = 0
+ for (let x = 0; x < this.charWidth; x++) {
+ for (let y = 0; y < this.charHeight; y++) {
+ const i = (y * this.charWidth + x) * 4
+ if (data.data[i + 3] > 0) {
+ width = x + 1
+ break
+ }
+ }
+ }
+
+ const scope = this
+ this.charCache.set(char, {
+ type: 'bitmap',
+ ascent: this.ascent,
+ width: width + 1, // Add 1 pixel of spacing between characters
+ get atlas() {
+ return scope.atlas
+ },
+ pixelUV: [startX, startY, width, this.charHeight],
+ uv: [
+ startX / scope.atlas.image.width,
+ startY / scope.atlas.image.height,
+ width / scope.atlas.image.width,
+ this.charHeight / scope.atlas.image.height,
+ ],
+ })
+ }
+ return this.charCache.get(char)!
+ }
+}
+
+export class MinecraftFont {
+ static all: MinecraftFont[] = []
+ static missingCharacterAtlas = new THREE.TextureLoader().load(MissingCharacter)
+
+ public id: string
+ public providers: FontProvider[] = []
+ public fallback: MinecraftFont | undefined
+
+ private charCache = new Map()
+ private loaded = false
+ private characterMeshCache = new Map()
+
+ constructor(id: string, assetPath: string, fallback?: MinecraftFont) {
+ this.id = id
+ this.fallback = fallback
+ const fontJSON = assets.getJSONAsset(assetPath) as IFont
+
+ for (const providerJSON of fontJSON.providers) {
+ switch (providerJSON.type) {
+ case 'bitmap':
+ this.providers.push(new BitmapFontProvider(providerJSON))
+ break
+ case 'reference':
+ this.providers.push(new ReferenceFontProvider(providerJSON))
+ break
+ case 'space':
+ this.providers.push(new SpaceFontProvider(providerJSON))
+ break
+ default:
+ throw new Error(
+ `Unsupported font provider type: ${(providerJSON as any).type as string}`
+ )
+ }
+ }
+
+ MinecraftFont.all.push(this)
+ }
+
+ static getById(id: string) {
+ return MinecraftFont.all.find(font => font.id === id)
+ }
+
+ async load() {
+ if (this.loaded) return this
+ await Promise.all(this.providers.map(provider => provider.load())).then(() => {
+ // // Cache commonly used characters
+ // for (const char of 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}\\|;:\'",.<>/?`~ ') {
+ // this.getChar(char)
+ // }
+ })
+ this.loaded = true
+ return this
+ }
+
+ /**
+ * @returns The character data for the given character, or undefined if the character is not found.
+ */
+ getChar(char: string, top = true): ICachedChar | undefined {
+ if (!this.charCache.has(char)) {
+ for (const provider of this.providers) {
+ const data = provider.getChar(char, false)
+ if (data) {
+ this.charCache.set(char, data)
+ return data
+ }
+ }
+ if (top) {
+ return {
+ type: 'bitmap',
+ ascent: 7,
+ width: 6,
+ atlas: MinecraftFont.missingCharacterAtlas,
+ pixelUV: [0, 0, 8, 8],
+ uv: [0, 0, (1 / 8) * 6, 1],
+ }
+ }
+ }
+ return this.charCache.get(char)
+ }
+
+ getTextWidth(text: UnicodeString, span: IStyleSpan) {
+ let width = 0
+ const boldExtra = span.style.bold ? 1 : 0
+
+ let font: MinecraftFont = this
+ if (span.style.font && span.style.font !== this.id) {
+ const newFont = MinecraftFont.getById(span.style.font as string)
+ if (newFont) font = newFont
+ }
+ for (const char of text) {
+ if (char === '\n') break
+ const charData = font.getChar(char)
+ // TODO: Handle missing characters better
+ if (!charData) {
+ console.warn(`Missing character: '${char}'`)
+ continue
+ }
+
+ width += charData.width + boldExtra
+ }
+ return Math.max(width, 0)
+ }
+
+ getWordWidth(word: IComponentWord) {
+ let width = 0
+
+ let font: MinecraftFont = this
+ for (const span of word.styles) {
+ if (span.style.font && span.style.font !== this.id) {
+ const newFont = MinecraftFont.getById(span.style.font as string)
+ if (newFont) font = newFont
+ }
+ const text = word.text.slice(span.start, span.end)
+ const textWidth = font.getTextWidth(text, span)
+ width += textWidth
+ }
+ return Math.max(width, 0)
+ }
+
+ async generateTextMesh({
+ textComponent,
+ lineWidth,
+ backgroundColor,
+ shadow,
+ alignment,
+ }: Required): Promise<{ mesh: THREE.Mesh; outline: THREE.LineSegments }> {
+ console.time('drawTextToMesh')
+ const mesh = new THREE.Mesh()
+
+ const jsonText = new JsonText(textComponent)
+ const words = getComponentWords(jsonText)
+ const { lines, backgroundWidth } = await computeTextWrapping(words, lineWidth)
+ const width = backgroundWidth + 1
+ const height = lines.length * 10 + 1
+
+ if (process.env.NODE_ENV === 'development') {
+ // Debug output
+ const wordWidths = words.map(word => this.getWordWidth(word))
+ for (const word of words) {
+ console.log(
+ `${words.indexOf(word)} '${word.text.toString()}' width: ${
+ wordWidths[words.indexOf(word)]
+ }`
+ )
+ for (const span of word.styles) {
+ console.log(
+ `'${word.text.slice(span.start, span.end).toString()}' ${span.start}-${
+ span.end
+ } = `,
+ span.style
+ )
+ }
+ }
+ console.log('Lines:', lines, 'CanvasWidth:', lineWidth)
+ for (const line of lines) {
+ console.log('Line', lines.indexOf(line), line.width)
+ for (const word of line.words) {
+ console.log(
+ 'Word',
+ line.words.indexOf(word),
+ `'${word.text.toString()}'`,
+ word.styles.map(span => span.style),
+ word.styles.map(
+ span =>
+ `${span.start}-${span.end} '${word.text
+ .slice(span.start, span.end)
+ .toString()}'`
+ )
+ )
+ }
+ }
+ }
+
+ const color = tinycolor(backgroundColor)
+ const backgroundHex = color.toHexString()
+ const backgroundAlpha = color.getAlpha()
+
+ const backgroundGeo = new THREE.PlaneBufferGeometry(width, height)
+ const backgroundMesh = new THREE.Mesh(
+ backgroundGeo,
+ new THREE.MeshBasicMaterial({
+ color: backgroundHex,
+ transparent: true,
+ opacity: backgroundAlpha,
+ })
+ )
+ .translateY(height / 2)
+ .translateZ(-0.05)
+ mesh.add(backgroundMesh)
+
+ const geos: THREE.BufferGeometry[] = []
+ const cursor = { x: 0, y: height - 9 }
+ for (const line of lines) {
+ switch (alignment) {
+ case 'center':
+ cursor.x = -width / 2 + Math.ceil((width - line.width) / 2)
+ break
+ case 'right':
+ cursor.x = -width / 2 + width - line.width
+ break
+ default:
+ cursor.x = -width / 2 + 1
+ }
+ for (const word of line.words) {
+ for (const span of word.styles) {
+ const text = word.text.slice(span.start, span.end)
+ for (const char of text) {
+ const charMesh = this.generateCharMesh(char, span.style, shadow)
+ if (!charMesh) continue
+ if (charMesh.geo) {
+ const clone = charMesh.geo.clone()
+ clone.translate(cursor.x, cursor.y, 0)
+ geos.push(clone)
+ }
+ cursor.x += charMesh.width
+ }
+ }
+ }
+ cursor.y -= 10
+ }
+
+ let charGeo: THREE.BufferGeometry | undefined
+ if (geos.length > 0) {
+ charGeo = mergeGeometries(geos)!
+ const charMesh = new THREE.Mesh(
+ charGeo,
+ new THREE.MeshBasicMaterial({ vertexColors: true })
+ )
+ mesh.add(charMesh)
+ }
+
+ mesh.scale.set(0.4, 0.4, 0.4)
+ mesh.rotateY(Math.PI)
+ mesh.translateX(1 / 5)
+
+ const outlineGeo = new THREE.EdgesGeometry(backgroundGeo.clone().scale(0.4, 0.4, 0.4))
+ const outline = new THREE.LineSegments(outlineGeo, Canvas.outlineMaterial)
+ const positions = Array.from(outlineGeo.getAttribute('position').array)
+ for (let i = 0; i < positions.length; i += 3) {
+ positions[i] -= 1 / 5
+ positions[i + 1] += (height / 2) * 0.4
+ }
+ outlineGeo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
+
+ outline.no_export = true
+ outline.renderOrder = 2
+ outline.frustumCulled = false
+
+ mesh.isTextDisplayText = true
+
+ console.timeEnd('drawTextToMesh')
+ return { mesh, outline }
+ }
+
+ generateCharMesh(
+ char: string,
+ style: StyleRecord,
+ shadow?: boolean
+ ): ICachedCharMesh | undefined {
+ let font: MinecraftFont = this
+ if (style.font) {
+ const newFont = MinecraftFont.getById(style.font as string)
+ if (newFont) font = newFont
+ }
+ const charData = font.getChar(char)
+ if (!charData) {
+ // Technically this should never happen, but just in case...
+ console.error('Unknown character:', char)
+ return
+ }
+
+ let color = new THREE.Color('#ffffff')
+ if (typeof style.color === 'string') {
+ color =
+ style.color.startsWith('#') && style.color.length === 7
+ ? new THREE.Color(style.color)
+ : new THREE.Color(COLOR_MAP[style.color]) || color
+ }
+
+ let shadowColor: THREE.Color
+ if (typeof style.shadow_color === 'string') {
+ shadowColor =
+ style.shadow_color.startsWith('#') && style.shadow_color.length === 7
+ ? new THREE.Color(style.shadow_color)
+ : new THREE.Color(COLOR_MAP[style.shadow_color]) || color
+ } else {
+ shadowColor = color.clone().multiplyScalar(0.25)
+ }
+
+ const boldExtra = style.bold ? 1 : 0
+
+ if (charData.type === 'bitmap') {
+ const hash = createHash('sha256')
+ hash.update(char)
+ hash.update(color.getHexString())
+ if (shadow) hash.update('shadow')
+ if (style.bold) hash.update('bold')
+ if (style.italic) hash.update('italic')
+ if (style.underlined) hash.update('underlined')
+ if (style.strikethrough) hash.update('strikethrough')
+ if (style.font) hash.update(';' + font.id)
+ // I'm not rendering this...
+ // if (style.obfuscated) hash.update('obfuscated')
+ const digest = hash.digest('hex')
+
+ let charMesh = this.characterMeshCache.get(digest)
+
+ if (charMesh === undefined) {
+ // If no mesh is found, create a new one
+ const canvas = document.createElement('canvas')
+ const ctx = canvas.getContext('2d', { willReadFrequently: true })!
+ canvas.width = charData.pixelUV[2]
+ canvas.height = charData.pixelUV[3]
+ ctx.imageSmoothingEnabled = false
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
+
+ ctx.drawImage(
+ charData.atlas.image as HTMLImageElement,
+ charData.pixelUV[0],
+ charData.pixelUV[1],
+ charData.pixelUV[2],
+ charData.pixelUV[3],
+ 0,
+ 0,
+ canvas.width,
+ canvas.height
+ )
+ const data = ctx.getImageData(0, 0, canvas.width, canvas.height)
+
+ const geo = new THREE.BufferGeometry()
+ let colors: number[] = []
+ let vertices: number[] = []
+ let indices: number[] = []
+
+ const createQuad = (x: number, y: number, w: number, h: number) => {
+ const vertIndex = vertices.length / 3
+ // prettier-ignore
+ vertices.push(
+ x, y, 0,
+ x + w, y, 0,
+ x + w, y + h, 0,
+ x, y + h, 0
+ )
+ indices.push(
+ vertIndex,
+ vertIndex + 1,
+ vertIndex + 2,
+ vertIndex,
+ vertIndex + 2,
+ vertIndex + 3
+ )
+ // prettier-ignore
+ colors.push(
+ color.r, color.g, color.b,
+ color.r, color.g, color.b,
+ color.r, color.g, color.b,
+ color.r, color.g, color.b
+ )
+ if (shadow) {
+ const shadowVertIndex = vertices.length / 3
+ x += 1
+ y -= 1
+ const z = -0.01
+ // prettier-ignore
+ vertices.push(
+ x, y, z,
+ x + w, y, z,
+ x + w, y + h, z,
+ x, y + h, z
+ )
+ indices.push(
+ shadowVertIndex,
+ shadowVertIndex + 1,
+ shadowVertIndex + 2,
+ shadowVertIndex,
+ shadowVertIndex + 2,
+ shadowVertIndex + 3
+ )
+ // prettier-ignore
+ colors.push(
+ shadowColor.r, shadowColor.g, shadowColor.b,
+ shadowColor.r, shadowColor.g, shadowColor.b,
+ shadowColor.r, shadowColor.g, shadowColor.b,
+ shadowColor.r, shadowColor.g, shadowColor.b
+ )
+ }
+ }
+
+ // Generate a quad for each pixel in the character
+ // This also attempts to make a single quad for each horizontal line of connected pixels
+ for (let y = 0; y < canvas.height; y++) {
+ const ascent = -y + charData.ascent
+ let width = 0
+ for (let x = 0; x < canvas.width; x++) {
+ const i = (y * canvas.width + x) * 4
+ const alpha = data.data[i + 3]
+ if (alpha === 0) {
+ if (width > 0) {
+ createQuad(x - width, ascent, width + boldExtra, 1)
+ width = 0
+ }
+ continue
+ } else {
+ width++
+ }
+ }
+ if (width > 0) {
+ createQuad(canvas.width - width, ascent, width + boldExtra, 1)
+ }
+ }
+
+ geo.setIndex(indices)
+ geo.setAttribute(
+ 'position',
+ new THREE.BufferAttribute(new Float32Array(vertices), 3)
+ )
+ geo.setAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3))
+
+ if (style.italic) {
+ geo.applyMatrix4(new THREE.Matrix4().makeShear(0, 0, 0.2, 0, 0, 0))
+ geo.translate(-1, 0, 0)
+ }
+
+ vertices = Array.from(geo.getAttribute('position').array)
+ colors = Array.from(geo.getAttribute('color').array)
+ indices = Array.from(geo.getIndex()!.array)
+
+ if (style.underlined) {
+ createQuad(-1, -1, canvas.width + 2, 1)
+ }
+
+ if (style.strikethrough) {
+ const ascent = charData.ascent / 2 + 1
+ createQuad(-1, ascent, canvas.width + 2, 1)
+ }
+
+ geo.setIndex(indices)
+ geo.setAttribute(
+ 'position',
+ new THREE.BufferAttribute(new Float32Array(vertices), 3)
+ )
+ geo.setAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3))
+
+ geo.attributes.position.needsUpdate = true
+ geo.attributes.color.needsUpdate = true
+ charMesh = {
+ geo,
+ width: charData.width + boldExtra,
+ }
+
+ this.characterMeshCache.set(digest, charMesh)
+ }
+ return charMesh
+ } else {
+ return {
+ width: charData.width,
+ }
+ }
+ }
+}
+
+let vanillaFont: MinecraftFont
+let illagerFont: MinecraftFont
+let standardGalacticAlphabetFont: MinecraftFont
+function loadMinecraftFonts() {
+ console.log('Loading Minecraft fonts...')
+ vanillaFont = new MinecraftFont('minecraft:default', 'assets/minecraft/font/default.json')
+ illagerFont = new MinecraftFont(
+ 'minecraft:illageralt',
+ 'assets/minecraft/font/illageralt.json',
+ vanillaFont
+ )
+ standardGalacticAlphabetFont = new MinecraftFont(
+ 'minecraft:alt',
+ 'assets/minecraft/font/alt.json',
+ vanillaFont
+ )
+
+ void Promise.all([
+ vanillaFont.load(),
+ illagerFont.load(),
+ standardGalacticAlphabetFont.load(),
+ ]).then(() => {
+ console.log('Minecraft fonts loaded!')
+ requestAnimationFrame(() => EVENTS.MINECRAFT_FONTS_LOADED.dispatch())
+ })
+}
+
+export async function getVanillaFont() {
+ if (!vanillaFont) {
+ await new Promise(resolve => {
+ EVENTS.MINECRAFT_FONTS_LOADED.subscribe(() => resolve())
+ })
+ }
+ return vanillaFont.load()
+}
+
+EVENTS.MINECRAFT_ASSETS_LOADED.subscribe(() => {
+ loadMinecraftFonts()
+})
+
+// EVENTS.SELECT_PROJECT.subscribe(() => {
+// void getVanillaFont().then(async font => {
+// await font.generateTextMesh({
+// jsonText: new JsonText([
+// '',
+// {
+// text: 'Sometimes ',
+// italic: true,
+// },
+// 'you ',
+// {
+// text: 'have',
+// bold: true,
+// },
+// ' to wear ',
+// {
+// text: 'stretchy',
+// color: 'yellow',
+// },
+// ' pants.\n',
+// {
+// text: "(It's for ",
+// },
+// {
+// text: 'fun!',
+// underlined: true,
+// color: 'blue',
+// },
+// ')',
+// ]),
+// maxLineWidth: 100,
+// backgroundColor: '#000000',
+// backgroundAlpha: 0.25,
+// })
+// })
+// })
diff --git a/src/systems/minecraft/itemDefinitions.ts b/src/systems/minecraft-temp/itemDefinitions.ts
similarity index 100%
rename from src/systems/minecraft/itemDefinitions.ts
rename to src/systems/minecraft-temp/itemDefinitions.ts
diff --git a/src/systems/minecraft-temp/itemModelManager.ts b/src/systems/minecraft-temp/itemModelManager.ts
new file mode 100644
index 00000000..b5fb722c
--- /dev/null
+++ b/src/systems/minecraft-temp/itemModelManager.ts
@@ -0,0 +1,311 @@
+import { mergeGeometries } from '../../util/bufferGeometryUtils'
+import { getPathFromResourceLocation, parseResourceLocation } from '../../util/minecraftUtil'
+import { assetsLoaded, getJSONAsset, getPngAssetAsDataUrl } from './assetManager'
+import { parseBlockModel } from './blockModelManager'
+import type { IItemModel } from './model'
+import { TEXTURE_FRAG_SHADER, TEXTURE_VERT_SHADER } from './textureShaders'
+
+interface ItemModelMesh {
+ mesh: THREE.Mesh
+ outline: THREE.LineSegments
+ boundingBox: THREE.BufferGeometry
+ isBlock?: boolean
+}
+
+const LOADER = new THREE.TextureLoader()
+const ITEM_MODEL_CACHE = new Map()
+
+export async function getItemModel(item: string): Promise {
+ await assetsLoaded()
+ let result = ITEM_MODEL_CACHE.get(item)
+ if (!result) {
+ // console.warn(`Found no cached item model mesh for '${item}'`)
+ result = await parseItemModel(getItemResourceLocation(item))
+ ITEM_MODEL_CACHE.set(item, result)
+ }
+ if (!result) return undefined
+ result = {
+ mesh: result.mesh.clone(true),
+ outline: result.outline.clone(true),
+ boundingBox: result.boundingBox.clone(),
+ isBlock: result.isBlock,
+ }
+
+ result.mesh.geometry = result.mesh.geometry.clone()
+ result.outline.geometry = result.outline.geometry.clone()
+ result.mesh.name = item
+ if (result.isBlock) {
+ result.mesh.isVanillaBlockModel = true
+ } else {
+ result.mesh.isVanillaItemModel = true
+ }
+
+ return result
+}
+
+function getItemResourceLocation(item: string) {
+ const resource = parseResourceLocation(item)
+ return resource.namespace + ':' + 'item/' + resource.subpath
+}
+
+async function parseItemModel(location: string, childModel?: IItemModel): Promise {
+ const modelPath = getPathFromResourceLocation(location, 'models')
+ const model = getJSONAsset(modelPath + '.json') as IItemModel
+
+ if (childModel) {
+ // if (childModel.ambientocclusion !== undefined)
+ // model.ambientocclusion = childModel.ambientocclusion
+ if (childModel.textures !== undefined) {
+ model.textures ??= {}
+ Object.assign(model.textures, childModel.textures)
+ }
+ // Interesting that elements aren't merged in vanilla...
+ if (childModel.elements !== undefined) model.elements = childModel.elements
+ if (childModel.display !== undefined)
+ Object.assign(model.display as any, childModel.display)
+ if (childModel.gui_light !== undefined) model.gui_light = childModel.gui_light
+ if (childModel.overrides !== undefined) model.overrides = childModel.overrides
+ }
+
+ if (model.parent) {
+ const resource = parseResourceLocation(model.parent)
+ if (resource.type === 'block') {
+ return await parseBlockModel({ model: model.parent, isItemModel: true }, model)
+ }
+ if (resource.subpath === 'item/generated') {
+ return await generateItemMesh(location, model)
+ } else {
+ return await parseItemModel(model.parent, model)
+ }
+ } else {
+ // The block model parser handles custom item models made from elements just fine, so we can use it here
+ return await parseBlockModel({ model: location, isItemModel: true }, model)
+ }
+
+ // throw new Error(`Unsupported item model '${location}'`)
+}
+
+async function generateItemMesh(location: string, model: IItemModel): Promise {
+ const masterMesh = new THREE.Mesh()
+ const boundingBoxes: THREE.BufferGeometry[] = []
+ const outlineGeos: THREE.BufferGeometry[] = []
+
+ for (const textureResourceLoc of Object.values(model.textures)) {
+ const texturePath = getPathFromResourceLocation(textureResourceLoc, 'textures') + '.png'
+ const textureUrl = getPngAssetAsDataUrl(texturePath)
+ const texture = await LOADER.loadAsync(textureUrl)
+ texture.magFilter = THREE.NearestFilter
+ texture.minFilter = THREE.NearestFilter
+
+ const mat = new THREE.ShaderMaterial({
+ uniforms: {
+ // @ts-ignore
+ map: { type: 't', value: texture },
+ // @ts-ignore
+ SHADE: { type: 'bool', value: settings.shading.value },
+ LIGHTCOLOR: {
+ // @ts-ignore
+ type: 'vec3',
+ value: new THREE.Color()
+ .copy(Canvas.global_light_color)
+ .multiplyScalar(settings.brightness.value / 50),
+ },
+ // @ts-ignore
+ LIGHTSIDE: { type: 'int', value: Canvas.global_light_side },
+ // @ts-ignore
+ EMISSIVE: { type: 'bool', value: false },
+ },
+ vertexShader: TEXTURE_VERT_SHADER,
+ fragmentShader: TEXTURE_FRAG_SHADER,
+ blending: THREE.NormalBlending,
+ side: Canvas.getRenderSide(),
+ transparent: true,
+ })
+ // @ts-ignore
+ mat.map = texture
+ mat.name = location
+
+ const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), mat)
+
+ const positionArray: number[] = []
+ const indices: number[] = []
+ const uvs: number[] = []
+ const normals: number[] = []
+ const colors: number[] = []
+ const addNormal = (x: number, y: number, z: number) => {
+ normals.push(x, y, z, x, y, z, x, y, z, x, y, z)
+ }
+
+ if (texture?.image.width) {
+ const canvas = document.createElement('canvas')
+ const ctx = canvas.getContext('2d')!
+ canvas.width = texture.image.width
+ canvas.height = texture.image.height
+ ctx.drawImage(texture.image as HTMLImageElement, 0, 0)
+
+ const addFace = (x: number, z: number, w: number, h: number, dir: number) => {
+ const s = positionArray.length / 3
+ const y = dir === 1 ? -1 : 0
+ // prettier-ignore
+ positionArray.push(
+ -x, y, z,
+ -x, y, z + 1,
+ -x - w, y, z + h,
+ -x - w, y, z + h - 1
+ )
+
+ if (dir === 1) {
+ indices.push(s + 0, s + 1, s + 2, s + 0, s + 2, s + 3)
+ } else if (dir === -1) {
+ indices.push(s + 0, s + 2, s + 1, s + 0, s + 3, s + 2)
+ }
+
+ addNormal(dir, 0, 0)
+ uvs.push(
+ (x + w) / canvas.width,
+ 1 - z / canvas.height,
+ (x + w) / canvas.width,
+ 1 - (z + h) / canvas.height,
+ x / canvas.width,
+ 1 - (z + h) / canvas.height,
+ x / canvas.width,
+ 1 - z / canvas.height
+ )
+ colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+ }
+
+ const addEdge = (
+ startX: number,
+ startY: number,
+ endX: number,
+ endY: number,
+ dir: number
+ ) => {
+ const s = positionArray.length / 3
+ // prettier-ignore
+ positionArray.push(
+ -startX, 0, startY,
+ -startX, -1, startY,
+ -endX, -1, endY,
+ -endX, 0, endY
+ )
+
+ if (dir === 1) {
+ indices.push(s + 0, s + 1, s + 2, s + 0, s + 2, s + 3)
+ } else if (dir === -1) {
+ indices.push(s + 0, s + 2, s + 1, s + 0, s + 3, s + 2)
+ }
+
+ if (startX == endX) {
+ startX += 0.1 * -dir
+ endX += 0.4 * -dir
+ startY += 0.1
+ endY -= 0.1
+ addNormal(-dir, 0, 0)
+ }
+ if (startY == endY) {
+ startY += 0.1 * dir
+ endY += 0.4 * dir
+ startX += 0.1
+ endX -= 0.1
+ addNormal(0, 0, -dir)
+ }
+ uvs.push(
+ endX / canvas.width,
+ 1 - startY / canvas.height,
+ endX / canvas.width,
+ 1 - endY / canvas.height,
+ startX / canvas.width,
+ 1 - endY / canvas.height,
+ startX / canvas.width,
+ 1 - startY / canvas.height
+ )
+ colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+ }
+
+ const result = ctx.getImageData(0, 0, canvas.width, canvas.height)
+
+ const matrix1 = []
+ for (let i = 0; i < result.data.length; i += 4) {
+ matrix1.push(result.data[i + 3] > 140 ? 1 : 0)
+ }
+ const matrix2 = matrix1.slice()
+
+ let pixel = 0
+ for (let y = 0; y < canvas.height; y++) {
+ for (let x = 0; x < canvas.width; x++) {
+ pixel = matrix1[y * canvas.width + x]
+ if (pixel) {
+ addFace(x, y, 1, 1, 1)
+ addFace(x, y, 1, 1, -1)
+ }
+ }
+ }
+
+ for (let y = 0; y < canvas.height; y++) {
+ for (let x = 0; x <= canvas.width; x++) {
+ const px0 = x == 0 ? 0 : matrix1[y * canvas.width + x - 1]
+ const px1 = x == canvas.width ? 0 : matrix1[y * canvas.width + x]
+ if (!px0 !== !px1) {
+ addEdge(x, y, x, y + 1, px0 ? 1 : -1)
+ }
+ }
+ }
+
+ for (let x = 0; x < canvas.width; x++) {
+ for (let y = 0; y <= canvas.height; y++) {
+ const px0 = y == 0 ? 0 : matrix2[(y - 1) * canvas.width + x]
+ const px1 = y == canvas.height ? 0 : matrix2[y * canvas.width + x]
+ if (!px0 !== !px1) {
+ addEdge(x, y, x + 1, y, px0 ? -1 : 1)
+ }
+ }
+ }
+ }
+
+ positionArray.forEach((n, i) => {
+ positionArray[i] = n + [8, 0.5, -8][i % 3]
+ })
+
+ mesh.geometry.setAttribute(
+ 'position',
+ new THREE.BufferAttribute(new Float32Array(positionArray), 3)
+ )
+ mesh.geometry.setAttribute(
+ 'highlight',
+ new THREE.BufferAttribute(new Uint8Array(mesh.geometry.attributes.position.count), 1)
+ )
+ mesh.geometry.setIndex(indices)
+ mesh.geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2))
+ mesh.geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3))
+ mesh.geometry.setAttribute(
+ 'normal',
+ new THREE.BufferAttribute(new Float32Array(normals), 3)
+ )
+ mesh.geometry.attributes.color.needsUpdate = true
+ mesh.geometry.attributes.normal.needsUpdate = true
+
+ mesh.geometry.rotateX(Math.PI / 2)
+
+ const outlineGeo = mesh.geometry.clone()
+ // Remove the front and back face planes
+ const outlineVerts = Array.from(outlineGeo.attributes.position.array)
+ outlineVerts.splice(0, 24)
+ outlineGeo.setAttribute(
+ 'position',
+ new THREE.BufferAttribute(new Float32Array(outlineVerts), 3)
+ )
+ outlineGeos.push(outlineGeo)
+ boundingBoxes.push(mesh.geometry.clone())
+ masterMesh.add(mesh)
+ }
+
+ const outlineGeo = mergeGeometries(outlineGeos)
+ const boundingBox = mergeGeometries(boundingBoxes)!
+ const outline = new THREE.LineSegments(
+ new THREE.EdgesGeometry(outlineGeo!),
+ Canvas.outlineMaterial
+ )
+
+ return { mesh: masterMesh, outline, boundingBox }
+}
diff --git a/src/systems/minecraft-temp/jsonText.ts b/src/systems/minecraft-temp/jsonText.ts
new file mode 100644
index 00000000..e4820203
--- /dev/null
+++ b/src/systems/minecraft-temp/jsonText.ts
@@ -0,0 +1,622 @@
+import { StringStream } from 'generic-stream'
+
+export const FONT = '16px MinecraftFull'
+
+export type JsonTextColor =
+ | 'dark_red'
+ | 'red'
+ | 'gold'
+ | 'yellow'
+ | 'dark_green'
+ | 'green'
+ | 'aqua'
+ | 'dark_aqua'
+ | 'dark_blue'
+ | 'blue'
+ | 'light_purple'
+ | 'dark_purple'
+ | 'white'
+ | 'gray'
+ | 'dark_gray'
+ | 'black'
+ | `#${string}`
+
+export const COLOR_MAP: Record = {
+ dark_red: '#AA0000',
+ red: '#FF5555',
+ gold: '#FFAA00',
+ yellow: '#FFFF55',
+ dark_green: '#00AA00',
+ green: '#55FF55',
+ aqua: '#55FFFF',
+ dark_aqua: '#00AAAA',
+ dark_blue: '#0000AA',
+ blue: '#5555FF',
+ light_purple: '#FF55FF',
+ dark_purple: '#AA00AA',
+ white: '#FFFFFF',
+ gray: '#AAAAAA',
+ dark_gray: '#555555',
+ black: '#000000',
+}
+
+export interface IJsonTextObject {
+ text?: string
+ font?: string
+ color?: JsonTextColor
+ shadow_color?: JsonTextColor
+ extra?: JsonTextArray
+ bold?: true | false
+ italic?: true | false
+ underlined?: true | false
+ strikethrough?: true | false
+ obfuscated?: true | false
+ insertion?: string
+ clickEvent?: {
+ action:
+ | 'open_url'
+ | 'open_file'
+ | 'run_command'
+ | 'suggest_command'
+ | 'change_page'
+ | 'copy_to_clipboard'
+ value: string
+ }
+ hoverEvent?: {
+ action: 'show_text' | 'show_item' | 'show_entity'
+ contents:
+ | JsonTextComponent
+ | {
+ type: string
+ id: string
+ name?: string
+ }
+ | {
+ id: string
+ count?: number
+ tag?: string
+ }
+ }
+ translate?: string
+ fallback?: string
+ with?: JsonTextArray
+ score?: {
+ name: string
+ objective: string
+ value?: number
+ }
+ selector?: string
+ separator?: string
+ keybind?: string
+ nbt?: string
+ block?: string
+ entity?: string
+ storage?: string
+}
+
+export type JsonTextComponent = string | JsonTextArray | IJsonTextObject | JsonText
+
+export type JsonTextArray = JsonTextComponent[]
+
+export class JsonText {
+ public isJsonTextClass = true
+ private text: JsonTextComponent
+
+ constructor(jsonText: JsonTextComponent) {
+ this.text = jsonText
+ }
+
+ toString() {
+ return JSON.stringify(this.text)
+ }
+
+ toJSON() {
+ return this.text
+ }
+
+ static fromString(str: string): JsonText | undefined {
+ // return new JsonText(JSON.parse(str) as JsonTextComponent)
+ const parser = new JsonTextParser(str)
+ return parser.parse()
+ }
+}
+
+class ParserError extends Error {
+ line: number
+ column: number
+ constructor(
+ message: string,
+ private stream: StringStream,
+ child?: Error,
+ line?: number,
+ column?: number
+ ) {
+ super(message)
+
+ this.line = line ?? stream.line
+ this.column = column ?? stream.column
+
+ if (child) {
+ this.message = `${message} at ${this.line}:${this.column}\n${child.message}`
+ return
+ }
+ this.setPointerMessage()
+ }
+
+ setPointerMessage() {
+ // Unexpected '}' at 1:5
+ // World!"}
+ // ^
+ const pointer = ' '.repeat(this.column - 1) + '^'
+ this.message = `${this.message} at ${this.line}:${this.column}\n${this.stream.lines[
+ this.line - 1
+ ].content.trimEnd()}\n${pointer}`
+ }
+}
+
+class JsonTextParser {
+ private s: StringStream
+ private numChars = '0123456789'
+ private whitespaceChars = ' \t\n\r'
+
+ constructor(private str: string) {
+ this.s = new StringStream(str)
+ }
+
+ parse(): JsonText {
+ let text: JsonTextComponent | undefined
+ try {
+ text = this.parseTextComponent(true)
+ } catch (e) {
+ throw new ParserError('Failed to parse JsonText', this.s, e as Error)
+ }
+ if (text) {
+ return new JsonText(text)
+ }
+ return new JsonText('')
+ }
+
+ consumeWhitespace() {
+ this.s.consumeWhile(s => !!s.item && this.whitespaceChars.includes(s.item))
+ }
+
+ parseTextComponent(single = false): JsonTextComponent {
+ let result: JsonTextComponent
+ this.consumeWhitespace()
+ if (this.s.item === '{') {
+ result = this.parseTextObject()
+ } else if (this.s.item === '[') {
+ result = this.parseArray()
+ } else if (this.s.item === '"') {
+ result = this.parseString()
+ } else {
+ throw new ParserError(`Unexpected '${this.s.item!}' in JsonTextComponent`, this.s)
+ }
+ this.consumeWhitespace()
+ if (single && this.s.item) {
+ throw new ParserError(
+ `Unexpected '${this.s.item as string}' in JsonTextComponent`,
+ this.s
+ )
+ }
+ return result
+ }
+
+ parseValue(): JsonTextComponent | boolean | string | number {
+ const { line, column } = this.s
+ this.consumeWhitespace()
+ if (this.s.item === '{') {
+ return this.parseTextObject()
+ } else if (this.s.item === '[') {
+ return this.parseArray()
+ } else if (this.s.item === '"') {
+ return this.parseString()
+ } else if (this.s.item === 't' || this.s.item === 'f') {
+ return this.parseBoolean()
+ } else if (
+ this.s.item === '-' ||
+ this.s.item === '.' ||
+ (this.s.item && this.numChars.includes(this.s.item))
+ ) {
+ return this.parseNumber()
+ } else {
+ throw new ParserError(`Unexpected ${this.s.item!}`, this.s, undefined, line, column)
+ }
+ }
+
+ parseObject(valueParser: (key: string, obj: any) => void, validator?: (obj: any) => void): any {
+ const { line, column } = this.s
+ try {
+ // TS attempts to incorrectly infer the type of this.item.s as '{' after this if statement.
+ // Casting it to string here fixes the issue.
+ if (this.s.item! !== '{') {
+ throw new ParserError(`Unexpected '${this.s.item!}' in JsonTextObject`, this.s)
+ }
+ this.s.consume() // {
+ this.consumeWhitespace()
+ const obj: any = {}
+ while ((this.s.item as string) !== '}') {
+ const key = this.parseString()
+ this.consumeWhitespace()
+ this.s.consume() // :
+ this.consumeWhitespace()
+ valueParser(key, obj)
+ this.consumeWhitespace()
+ if ((this.s.item as string) === ',') {
+ this.s.consume()
+ this.consumeWhitespace()
+ } else if ((this.s.item as string) === '}') {
+ break
+ } else if ((this.s.item as string) === undefined) {
+ throw new ParserError('Unexpected EOF in JsonTextObject', this.s)
+ } else {
+ throw new ParserError(`Unexpected '${this.s.item}' in JsonTextObject`, this.s)
+ }
+ }
+ this.s.consume() // }
+ if (validator) validator(obj)
+ return obj
+ } catch (e: any) {
+ throw new ParserError(
+ 'Failed to parse JsonTextObject',
+ this.s,
+ e as Error,
+ line,
+ column
+ )
+ }
+ }
+
+ parseTextObject() {
+ return this.parseObject(
+ (key, obj: IJsonTextObject) => {
+ switch (key) {
+ case 'block':
+ case 'entity':
+ case 'font':
+ case 'insertion':
+ case 'keybind':
+ case 'nbt':
+ case 'selector':
+ case 'separator':
+ case 'storage':
+ case 'text':
+ case 'translate':
+ case 'fallback':
+ obj[key] = this.parseString()
+ break
+ case 'color':
+ case 'shadow_color': {
+ const color = this.parseString() as JsonTextColor
+ if (!(color.startsWith('#') || COLOR_MAP[color])) {
+ throw new ParserError(`Unknown color '${color}'`, this.s)
+ }
+ if (key === 'color') {
+ obj.color = color
+ } else {
+ obj.shadow_color = color
+ }
+ break
+ }
+ case 'bold':
+ case 'italic':
+ case 'obfuscated':
+ case 'strikethrough':
+ case 'underlined':
+ obj[key] = this.parseBoolean()
+ break
+ case 'with':
+ case 'extra':
+ obj[key] = this.parseArray()
+ break
+ case 'score':
+ obj[key] = this.parseScoreObject()
+ break
+ case 'clickEvent':
+ obj[key] = this.parseClickEventObject()
+ break
+ case 'hoverEvent':
+ obj[key] = this.parseHoverEventObject()
+ break
+ default:
+ throw new ParserError(`Unknown key '${key}' in JsonTextObject`, this.s)
+ }
+ },
+ obj => {
+ // Make sure the object has at least one of the required keys
+ if (
+ obj.text === undefined &&
+ obj.translate === undefined &&
+ obj.score === undefined &&
+ obj.selector === undefined &&
+ obj.keybind === undefined &&
+ obj.nbt === undefined
+ ) {
+ throw new ParserError(
+ `JsonTextObject does not include one of 'text', 'translate', 'score', 'selector', 'keybind', or 'nbt'.`,
+ this.s
+ )
+ }
+
+ // Validate the NBT key
+ if (
+ obj.nbt !== undefined &&
+ obj.block === undefined &&
+ obj.entity === undefined &&
+ obj.storage === undefined
+ ) {
+ throw new ParserError(
+ `JsonTextObject includes 'nbt' but does not include one of 'block', 'entity', or 'storage'.`,
+ this.s
+ )
+ }
+ }
+ ) as IJsonTextObject
+ }
+
+ parseScoreObject() {
+ return this.parseObject(
+ (key, obj: NonNullable) => {
+ switch (key) {
+ case 'name':
+ case 'objective':
+ obj[key] = this.parseString()
+ break
+ case 'value':
+ obj[key] = this.parseNumber()
+ break
+ default:
+ throw new ParserError(
+ `Unknown key '${key}' in JsonTextObject.score`,
+ this.s
+ )
+ }
+ },
+ obj => {
+ if (obj.name === undefined || obj.objective === undefined) {
+ throw new ParserError(
+ `JsonTextObject.score must include 'name' and 'objective'`,
+ this.s
+ )
+ }
+ }
+ ) as IJsonTextObject['score']
+ }
+
+ parseClickEventObject() {
+ return this.parseObject(
+ (key, obj: NonNullable) => {
+ switch (key) {
+ case 'action':
+ obj[key] = this.parseString([
+ 'open_url',
+ 'open_file',
+ 'run_command',
+ 'suggest_command',
+ 'change_page',
+ 'copy_to_clipboard',
+ ])
+ break
+ case 'value':
+ obj[key] = this.parseString()
+ break
+ default:
+ throw new ParserError(
+ `Unknown key '${key}' in JsonTextObject.clickEvent`,
+ this.s
+ )
+ }
+ },
+ obj => {
+ if (obj.action === undefined) {
+ throw new ParserError(`JsonTextObject.clickEvent must include 'action'`, this.s)
+ } else if (obj.value === undefined) {
+ throw new ParserError(`JsonTextObject.clickEvent must include 'value'`, this.s)
+ }
+ }
+ ) as IJsonTextObject['clickEvent']
+ }
+
+ parseHoverEventObject() {
+ return this.parseObject(
+ (key, obj: NonNullable) => {
+ switch (key) {
+ case 'action':
+ obj[key] = this.parseString(['show_text', 'show_item', 'show_entity'])
+ break
+ case 'contents':
+ switch (obj.action) {
+ case undefined: {
+ throw new ParserError(
+ `HoverEvent 'action' is required, and must be defined before 'contents'.`,
+ this.s
+ )
+ }
+ case 'show_text': {
+ obj[key] = this.parseTextComponent()
+ break
+ }
+ case 'show_item': {
+ obj[key] = this.parseObject(
+ (key, item) => {
+ switch (key) {
+ case 'id':
+ item[key] = this.parseString()
+ break
+ case 'count':
+ item[key] = this.parseNumber()
+ break
+ case 'tag':
+ item[key] = this.parseString()
+ break
+ default:
+ throw new ParserError(
+ `Unknown key '${key}' in JsonTextObject.itemHoverEvent.contents`,
+ this.s
+ )
+ }
+ },
+ obj => {
+ if (obj.id === undefined) {
+ throw new ParserError(
+ `JsonTextObject.itemHoverEvent.contents must include 'id'`,
+ this.s
+ )
+ }
+ }
+ )
+ break
+ }
+ case 'show_entity': {
+ obj[key] = this.parseObject(
+ (key, entity) => {
+ switch (key) {
+ case 'type':
+ entity[key] = this.parseString()
+ break
+ case 'id':
+ entity[key] = this.parseString()
+ break
+ case 'name':
+ entity[key] = this.parseString()
+ break
+ default:
+ throw new ParserError(
+ `Unknown key '${key}' in JsonTextObject.entityHoverEvent.contents`,
+ this.s
+ )
+ }
+ },
+ obj => {
+ if (obj.type === undefined) {
+ throw new ParserError(
+ `JsonTextObject.entityHoverEvent.contents must include 'type'`,
+ this.s
+ )
+ }
+ }
+ )
+ break
+ }
+ }
+ break
+ default:
+ throw new ParserError(
+ `Unknown key '${key}' in JsonTextObject.hoverEvent`,
+ this.s
+ )
+ }
+ },
+ obj => {
+ if (obj.action === undefined) {
+ throw new ParserError(`JsonTextObject.hoverEvent must include 'action'`, this.s)
+ } else if (obj.contents === undefined) {
+ throw new ParserError(
+ `JsonTextObject.hoverEvent must include 'contents'`,
+ this.s
+ )
+ }
+ }
+ ) as IJsonTextObject['hoverEvent']
+ }
+
+ parseArray(): JsonTextArray {
+ this.s.consume() // [
+ this.consumeWhitespace()
+ const arr: JsonTextArray = []
+ while (this.s.item !== ']') {
+ this.consumeWhitespace()
+ const value = this.parseTextComponent()
+ arr.push(value)
+ if (this.s.item === ',') {
+ this.s.consume()
+ this.consumeWhitespace()
+ } else if (this.s.item === ']') {
+ break
+ } else {
+ throw new ParserError(`Unexpected '${this.s.item!}' in JsonTextArray`, this.s)
+ }
+ }
+ this.s.consume() // ]
+ return arr
+ }
+
+ parseString(): string
+ parseString(allowedValues: T[]): T
+ parseString(allowedValues?: string[]): string {
+ if (this.s.item !== '"') {
+ throw new ParserError(`Unexpected '${this.s.item!}' in string`, this.s)
+ }
+ this.s.consume() // "
+ let str = ''
+ while (this.s.item) {
+ if ((this.s.item as string) === '\\') {
+ if (this.s.look(1) === 'n') {
+ str += '\n'
+ this.s.consume() // \
+ this.s.consume() // n
+ continue
+ } else {
+ this.s.consume() // \
+ str += this.s.item
+ this.s.consume() // Escaped character
+ continue
+ }
+ }
+ if (this.s.item === '"') {
+ break
+ } else if (this.s.item === '\n') {
+ throw new ParserError('Unexpected newline in string', this.s)
+ }
+ str += this.s.item
+ this.s.consume()
+ }
+ if (!this.s.item) {
+ throw new ParserError('Unexpected EOF in string', this.s)
+ }
+ this.s.consume() // "
+ if (allowedValues && !allowedValues.includes(str)) {
+ throw new ParserError(
+ `Unexpected string value '${str}'. Expected one of ${allowedValues.join(', ')}`,
+ this.s
+ )
+ }
+ return str
+ }
+
+ parseBoolean(): boolean {
+ if (this.s.item === '"') {
+ const value = this.parseString()
+ if (value === 'true') {
+ return true
+ } else if (value === 'false') {
+ return false
+ }
+ throw new ParserError(`Unexpected incomplete string boolean`, this.s)
+ }
+ if (this.s.look(0, 4) === 'true') {
+ this.s.consumeN(4)
+ return true
+ } else if (this.s.look(0, 5) === 'false') {
+ this.s.consumeN(5)
+ return false
+ }
+ throw new ParserError(`Unexpected incomplete boolean`, this.s)
+ }
+
+ parseNumber(): number {
+ let num = ''
+ let hasDecimal = false
+ while (this.s.item) {
+ if (this.s.item === '.') {
+ if (hasDecimal) {
+ throw new ParserError('Unexpected second decimal point in number', this.s)
+ }
+ hasDecimal = true
+ }
+ num += this.s.item
+ this.s.consume()
+ }
+ return parseInt(num)
+ }
+}
diff --git a/src/systems/minecraft-temp/model.d.ts b/src/systems/minecraft-temp/model.d.ts
new file mode 100644
index 00000000..f1ee553b
--- /dev/null
+++ b/src/systems/minecraft-temp/model.d.ts
@@ -0,0 +1,111 @@
+export type ModelDisplaySlot =
+ | 'thirdperson_righthand'
+ | 'thirdperson_lefthand'
+ | 'firstperson_righthand'
+ | 'firstperson_lefthand'
+ | 'gui'
+ | 'head'
+ | 'ground'
+ | 'fixed'
+
+export type ItemModelFaceDirection = 'up' | 'down' | 'north' | 'south' | 'west' | 'east'
+
+type ItemModelPredicateTypes =
+ | 'angle'
+ | 'blocking'
+ | 'broken'
+ | 'cast'
+ | 'cooldown'
+ | 'damage'
+ | 'damaged'
+ | 'lefthanded'
+ | 'pull'
+ | 'pulling'
+ | 'charged'
+ | 'firework'
+ | 'throwing'
+ | 'time'
+ | 'custom_model_data'
+ | 'level'
+ | 'filled'
+ | 'tooting'
+ | 'trim_type'
+ | 'brushing'
+
+export interface IItemModelElementFace {
+ uv: [number, number, number, number]
+ texture: string
+ cullface?: ItemModelFaceDirection
+ rotation?: 0 | 90 | 180 | 270
+ tintindex?: number
+}
+
+export interface IModelELement {
+ from: [number, number, number]
+ to: [number, number, number]
+ rotation?: {
+ origin: [number, number, number]
+ axis: 'x' | 'y' | 'z'
+ angle: number
+ rescale: boolean
+ }
+ shade?: boolean
+ faces?: Record
+}
+
+export interface IItemModel {
+ parent?: string
+ textures: Record & Record<`layer${number}`, string> & { particle?: string }
+ display?: Record<
+ ModelDisplaySlot,
+ {
+ rotation: [number, number, number]
+ translation: [number, number, number]
+ scale: [number, number, number]
+ }
+ >
+ gui_light?: 'front' | 'side'
+ elements?: IModelELement[]
+ overrides?: {
+ predicate: Record
+ model: string
+ }
+}
+
+export interface IBlockModel {
+ parent?: string
+ textures: Record & Record<`layer${number}`, string> & { particle?: string }
+ display?: Record<
+ ModelDisplaySlot,
+ {
+ rotation: [number, number, number]
+ translation: [number, number, number]
+ scale: [number, number, number]
+ }
+ >
+ ambientocclusion?: boolean
+ elements?: IModelELement[]
+}
+
+export interface IBlockStateVariant {
+ model: string
+ uvlock?: boolean
+ x?: number
+ y?: number
+ isItemModel?: boolean // Internal use only
+}
+
+export type IBlockStateMultipartCaseCondition = {
+ OR: IBlockStateMultipartCaseCondition[]
+ AND: IBlockStateMultipartCaseCondition[]
+} & Record
+
+export interface IBlockStateMultipartCase {
+ when?: IBlockStateMultipartCaseCondition
+ apply: IBlockStateVariant | Array
+}
+
+export interface IBlockState {
+ variants?: Record>
+ multipart?: IBlockStateMultipartCase[]
+}
diff --git a/src/systems/minecraft-temp/registryManager.ts b/src/systems/minecraft-temp/registryManager.ts
new file mode 100644
index 00000000..bdcf3227
--- /dev/null
+++ b/src/systems/minecraft-temp/registryManager.ts
@@ -0,0 +1,185 @@
+import EVENTS from '@events'
+import { checkForAssetsUpdate } from './assetManager'
+import { getLatestVersion } from './versionManager'
+
+interface IRegistryJSON {
+ activity: string[]
+ advancement: string[]
+ attribute: string[]
+ block: string[]
+ block_definition: string[]
+ block_entity_type: string[]
+ block_predicate_type: string[]
+ chunk_status: string[]
+ custom_stat: string[]
+ dimension: string[]
+ dimension_type: string[]
+ enchantment: string[]
+ entity_type: string[]
+ float_provider_type: string[]
+ fluid: string[]
+ font: string[]
+ function: string[]
+ game_event: string[]
+ height_provider_type: string[]
+ int_provider_type: string[]
+ item: string[]
+ item_modifier: string[]
+ loot_condition_type: string[]
+ loot_function_type: string[]
+ loot_nbt_provider_type: string[]
+ loot_number_provider_type: string[]
+ loot_pool_entry_type: string[]
+ loot_score_provider_type: string[]
+ loot_table: string[]
+ memory_module_type: string[]
+ menu: string[]
+ mob_effect: string[]
+ model: string[]
+ motive: string[]
+ particle_type: string[]
+ point_of_interest_type: string[]
+ pos_rule_test: string[]
+ position_source_type: string[]
+ potion: string[]
+ predicate: string[]
+ recipe: string[]
+ recipe_serializer: string[]
+ recipe_type: string[]
+ rule_test: string[]
+ schedule: string[]
+ sensor_type: string[]
+ sound_event: string[]
+ stat_type: string[]
+ structure: string[]
+ 'tag/block': string[]
+ 'tag/entity_type': string[]
+ 'tag/fluid': string[]
+ 'tag/game_event': string[]
+ 'tag/item': string[]
+ texture: string[]
+ villager_profession: string[]
+ villager_type: string[]
+ 'worldgen/biome': string[]
+ 'worldgen/biome_source': string[]
+ 'worldgen/block_state_provider_type': string[]
+ 'worldgen/carver': string[]
+ 'worldgen/chunk_generator': string[]
+ 'worldgen/configured_carver': string[]
+ 'worldgen/configured_feature': string[]
+ 'worldgen/configured_structure_feature': string[]
+ 'worldgen/configured_surface_builder': string[]
+ 'worldgen/feature': string[]
+ 'worldgen/feature_size_type': string[]
+ 'worldgen/foliage_placer_type': string[]
+ 'worldgen/material_condition': string[]
+ 'worldgen/material_rule': string[]
+ 'worldgen/noise': string[]
+ 'worldgen/noise_settings': string[]
+ 'worldgen/placed_feature': string[]
+ 'worldgen/placement_modifier_type': string[]
+ 'worldgen/processor_list': string[]
+ 'worldgen/structure_feature': string[]
+ 'worldgen/structure_piece': string[]
+ 'worldgen/structure_placement': string[]
+ 'worldgen/structure_pool_element': string[]
+ 'worldgen/structure_processor': string[]
+ 'worldgen/template_pool': string[]
+ 'worldgen/tree_decorator_type': string[]
+ 'worldgen/trunk_placer_type': string[]
+}
+
+const REGISTRIES_URL =
+ 'https://raw.githubusercontent.com/misode/mcmeta/summary/registries/data.json'
+
+class MinecraftRegistryEntry {
+ public items: string[] = []
+
+ constructor(entries: string[]) {
+ this.items = entries
+ }
+
+ public has(item: string): boolean {
+ return this.items.includes(item)
+ }
+
+ public find(searchFunction: (item: string) => boolean): string | undefined {
+ return this.items.find(searchFunction)
+ }
+}
+
+type MinecraftRegistry = Record
+
+export const MINECRAFT_REGISTRY = {} as MinecraftRegistry
+
+function updateMemoryRegistry() {
+ const registryString = localStorage.getItem('animated_java:minecraftRegistry')
+ if (!registryString) {
+ console.error('Minecraft Registry not found in local storage')
+ return
+ }
+ const registry = JSON.parse(registryString) as IRegistryJSON
+ for (const key in registry) {
+ MINECRAFT_REGISTRY[key as keyof IRegistryJSON] = new MinecraftRegistryEntry(
+ registry[key as keyof IRegistryJSON]
+ )
+ }
+}
+
+async function updateLocalRegistry() {
+ console.log('Updating Minecraft Registry...')
+ let retries = 3
+ while (retries-- >= 0) {
+ let response
+ try {
+ response = await fetch(REGISTRIES_URL)
+ } catch (error) {
+ console.error('Failed to fetch latest Minecraft registry:', error)
+ }
+ if (response?.ok) {
+ const newRegistry = (await response.json()) as IRegistryJSON
+ localStorage.setItem('animated_java:minecraftRegistry', JSON.stringify(newRegistry))
+ const latestVersion = await getLatestVersion()
+ localStorage.setItem(
+ 'animated_java:minecraftRegistryVersion',
+ JSON.stringify(latestVersion)
+ )
+ console.log('Minecraft Registry updated!')
+ return
+ }
+ }
+ throw new Error('Failed to fetch latest Minecraft registry after 3 retries.')
+}
+
+export async function checkForRegistryUpdate() {
+ console.log('Checking if Minecraft Registry update...')
+ const currentValueString = localStorage.getItem('animated_java:minecraftRegistry')
+ if (!currentValueString) {
+ console.log('No Minecraft Registry found. Updating...')
+ await updateLocalRegistry()
+ return
+ }
+ const currentVersionString = localStorage.getItem('animated_java:minecraftRegistryVersion')
+ if (!currentVersionString) {
+ console.log('No Minecraft Registry version found. Updating...')
+ await updateLocalRegistry()
+ return
+ }
+ const currentVersion = JSON.parse(currentVersionString)
+ const latestVersion = await getLatestVersion()
+ if (currentVersion.id !== latestVersion.id) {
+ console.log('Minecraft Registry is outdated. Updating...')
+ await updateLocalRegistry()
+ return
+ }
+
+ console.log('Minecraft Registry is up to date!')
+ updateMemoryRegistry()
+ requestAnimationFrame(() => EVENTS.MINECRAFT_REGISTRY_LOADED.dispatch())
+}
+
+EVENTS.NETWORK_CONNECTED.subscribe(() => {
+ void checkForRegistryUpdate().then(async () => {
+ await checkForAssetsUpdate()
+ })
+})
diff --git a/src/systems/minecraft-temp/textWrapping.ts b/src/systems/minecraft-temp/textWrapping.ts
new file mode 100644
index 00000000..78cf28b9
--- /dev/null
+++ b/src/systems/minecraft-temp/textWrapping.ts
@@ -0,0 +1,327 @@
+import { UnicodeString } from '../../util/unicodeString'
+import { getVanillaFont } from './fontManager'
+import {
+ JsonText,
+ type IJsonTextObject,
+ type JsonTextArray,
+ type JsonTextComponent,
+} from './jsonText'
+
+// @ts-ignore
+// import TestWorker from './textWrapping.worker.ts'
+// const WORKER: Worker = new TestWorker()
+// WORKER.onmessage = ({ data }) => {
+// console.log(data)
+// }
+
+// Jumpstarted by @IanSSenne (FetchBot) and refactored by @SnaveSutit to do line wrapping on JSON Text Components.
+// THANK U IAN <3 - SnaveSutit
+const STYLE_KEYS = [
+ 'bold',
+ 'italic',
+ 'underlined',
+ 'strikethrough',
+ 'obfuscated',
+ 'color',
+ 'font',
+ 'shadow_color',
+] as const
+
+export type StyleRecord = Partial>
+function getStylesFromComponent(
+ component: IJsonTextObject,
+ parent: StyleRecord = { color: 'white' }
+): StyleRecord {
+ for (const key of STYLE_KEYS) {
+ if (component[key]) {
+ parent[key] = component[key]
+ }
+ }
+ return parent
+}
+
+function getFirstItemStyle(input: JsonTextArray): StyleRecord {
+ let item = input.at(0)
+ if (Array.isArray(item)) {
+ return getFirstItemStyle(item)
+ } else if (item instanceof JsonText) {
+ item = item.toJSON() as IJsonTextObject | JsonTextArray
+ if (Array.isArray(item)) return getFirstItemStyle(item)
+ else return getStylesFromComponent(item)
+ } else if (typeof item === 'object') {
+ return getStylesFromComponent(item)
+ }
+ return {}
+}
+
+function flattenTextComponent(input: JsonTextComponent): IJsonTextObject[] {
+ const output: IJsonTextObject[] = []
+ function flattenComponent(component: JsonTextComponent, parentStyle: StyleRecord = {}) {
+ if (Array.isArray(component)) {
+ // The items of an array inherit the first item's style
+ parentStyle = Object.assign({}, parentStyle, getFirstItemStyle(component))
+ for (const subcomponent of component) {
+ flattenComponent(subcomponent, parentStyle)
+ }
+ } else if (typeof component === 'string') {
+ output.push(
+ Object.assign({}, parentStyle, {
+ text: component,
+ }) as IJsonTextObject
+ )
+ } else if (component instanceof JsonText) {
+ flattenComponent(component.toJSON(), parentStyle)
+ } else if (typeof component === 'object') {
+ output.push(Object.assign({}, parentStyle, component, { extra: undefined }))
+ if (component.extra) {
+ const childStyles = getStylesFromComponent(component)
+ flattenComponent(component.extra, childStyles)
+ }
+ }
+ }
+ flattenComponent(input)
+ return output
+}
+
+function getText(component: IJsonTextObject) {
+ if (typeof component === 'string') return new UnicodeString(component)
+ else if (component.text) return new UnicodeString(component.text)
+ else if (component.translate) return new UnicodeString(`{${component.translate}}`)
+ else if (component.selector) return new UnicodeString(`{${component.selector}}`)
+ else if (component.score) {
+ if (component.score.value) return new UnicodeString(`{${component.score.value}}`)
+ return new UnicodeString(`{${component.score.name}:${component.score.objective}}`)
+ } else if (component.keybind) return new UnicodeString(`{${component.keybind}}`)
+ else if (component.nbt) {
+ if (component.block) return new UnicodeString(`{${component.block}:${component.nbt}}`)
+ else if (component.entity)
+ return new UnicodeString(`{${component.entity}:${component.nbt}}`)
+ else if (component.storage)
+ return new UnicodeString(`{${component.storage}:${component.nbt}}`)
+ return new UnicodeString(`{${component.nbt}}`)
+ }
+ return new UnicodeString('')
+}
+
+export interface IStyleSpan {
+ style: StyleRecord
+ start: number
+ end: number
+}
+
+export interface IComponentWord {
+ styles: IStyleSpan[]
+ text: UnicodeString
+ /**
+ * The width of the word in pixels.
+ */
+ width: number
+ forceWrap?: boolean
+}
+
+interface IComponentLine {
+ words: IComponentWord[]
+ width: number
+}
+/**
+ * Gets the words from a JSON Text Component, while keeping track of the styles applied to each word.
+ *
+ * WARNING: Word width is not calculated by this function.
+ */
+export function getComponentWords(input: JsonTextComponent) {
+ console.time('getComponentWords')
+ const flattenedComponents = flattenTextComponent(input)
+ if (!flattenedComponents.length) return []
+ const words: IComponentWord[] = []
+ let word: IComponentWord | undefined
+ let component: IJsonTextObject | undefined = flattenedComponents.shift()
+ let componentText = getText(component!)
+ let style: IStyleSpan = {
+ style: getStylesFromComponent(component!),
+ start: 0,
+ end: 0,
+ }
+
+ while (component) {
+ for (const char of componentText) {
+ if (char === ' ') {
+ // A group of multiple spaces is treated as a word.
+ if (word && !(word.text.at(-1) === ' ')) {
+ style.end++
+ if (Object.keys(style.style).length) {
+ word.styles.push({ ...style })
+ style.start = 0
+ style.end = 0
+ }
+ words.push(word)
+ word = undefined
+ }
+ } else if (char === '\n') {
+ if (word) {
+ if (Object.keys(style.style).length) {
+ word.styles.push({ ...style })
+ style.start = 0
+ style.end = 0
+ }
+ words.push(word)
+ }
+ words.push({
+ styles: [],
+ text: new UnicodeString(''),
+ width: 0,
+ forceWrap: true,
+ })
+ word = undefined
+ continue
+ } else if (char !== ' ' && word?.text.at(-1) === ' ') {
+ style.end++
+ if (Object.keys(style.style).length) {
+ word.styles.push({ ...style })
+ style.start = 0
+ style.end = 0
+ }
+ words.push(word)
+ word = undefined
+ }
+
+ if (!word) {
+ word = { styles: [], text: new UnicodeString(''), width: 0 }
+ }
+ word.text.append(char)
+ style.end++
+ }
+ component = flattenedComponents.shift()
+ if (component) {
+ componentText = getText(component)
+ if (word) {
+ word.styles.push(style)
+ style = {
+ style: getStylesFromComponent(component),
+ start: style.end,
+ end: style.end,
+ }
+ } else {
+ style = { style: getStylesFromComponent(component), start: 0, end: 0 }
+ }
+ }
+ }
+
+ if (word) {
+ if (Object.keys(style.style).length) {
+ word.styles.push(style)
+ }
+ words.push(word)
+ }
+
+ console.timeEnd('getComponentWords')
+ return words
+}
+
+export async function computeTextWrapping(words: IComponentWord[], maxLineWidth = 200) {
+ console.time('computeTextWrapping')
+ const lines: IComponentLine[] = []
+ const font = await getVanillaFont()
+
+ let backgroundWidth = 0
+ let currentLine: IComponentLine = { words: [], width: 0 }
+ for (const word of words) {
+ const wordWidth = font.getWordWidth(word)
+ const wordStyles = [...word.styles]
+ // If the word is longer than than the max line width, split it into multiple lines
+ if (wordWidth - 1 > maxLineWidth) {
+ if (currentLine.words.length) {
+ lines.push(currentLine)
+ backgroundWidth = Math.max(backgroundWidth, currentLine.width)
+ }
+ currentLine = { words: [], width: 0 }
+
+ let part = new UnicodeString('')
+ let partWidth = 0
+ let partStartIndex = 0
+ let style: IStyleSpan | undefined = wordStyles.shift()
+ if (!style) throw new Error(`No active style found for word '${word.text.toString()}'`)
+
+ for (let i = 0; i < word.text.length; i++) {
+ const char = word.text.at(i)!
+ if (wordStyles.length > 1 && i >= style.end) {
+ style = wordStyles.shift()!
+ }
+
+ const charWidth = font.getTextWidth(new UnicodeString(char), style)
+ if (part.length > 0 && partWidth + (charWidth - 1) > maxLineWidth) {
+ // Find all styles that apply to this part
+ // FIXME: Attempt to avoid filtering and maping the styles for each character
+ const partStyles = word.styles
+ .filter(
+ span =>
+ span.start < partStartIndex + part.length &&
+ span.end >= partStartIndex
+ )
+ .map(span => ({
+ ...span,
+ start: Math.max(span.start - partStartIndex, 0),
+ end: Math.min(span.end - partStartIndex, part.length),
+ }))
+ lines.push({
+ words: [{ text: part, styles: partStyles, width: wordWidth }],
+ width: partWidth,
+ })
+ backgroundWidth = Math.max(backgroundWidth, partWidth)
+ partStartIndex += part.length
+ part = new UnicodeString('')
+ partWidth = 0
+ }
+ part.append(char)
+ partWidth += charWidth
+ }
+ if (part) {
+ // Find all styles that apply to this part
+ // FIXME: Attempt to avoid filtering and maping the styles for each character
+ const partStyles = word.styles
+ .filter(
+ span =>
+ span.start < partStartIndex + part.length && span.end >= partStartIndex
+ )
+ .map(span => ({
+ ...span,
+ start: Math.max(span.start - partStartIndex, 0),
+ end: Math.min(span.end - partStartIndex, part.length),
+ }))
+ backgroundWidth = Math.max(backgroundWidth, partWidth)
+ currentLine = {
+ words: [{ text: part, styles: partStyles, width: wordWidth }],
+ width: partWidth,
+ }
+ }
+ continue
+ // If the word is a newline character, force a line break
+ } else if (word.forceWrap) {
+ if (currentLine.words.length) {
+ lines.push(currentLine)
+ backgroundWidth = Math.max(backgroundWidth, currentLine.width)
+ }
+ currentLine = { words: [], width: 0 }
+ // If the current line has words and adding the current word would exceed the max line width, start a new line
+ } else if (currentLine.words.length && currentLine.width + (wordWidth - 1) > maxLineWidth) {
+ const lastWord = currentLine.words.at(-1)
+ // This will only effect space "words"
+ if (lastWord?.text.at(-1) === ' ') {
+ currentLine.words.pop()
+ currentLine.width -= lastWord.width
+ }
+ lines.push(currentLine)
+ backgroundWidth = Math.max(backgroundWidth, currentLine.width)
+ currentLine = { words: [], width: 0 }
+ }
+ word.width = wordWidth
+ currentLine.words.push(word)
+ currentLine.width += wordWidth
+ }
+ if (currentLine.words.length) {
+ lines.push(currentLine)
+ backgroundWidth = Math.max(backgroundWidth, currentLine.width)
+ }
+
+ console.timeEnd('computeTextWrapping')
+ return { lines, backgroundWidth }
+}
diff --git a/src/systems/minecraft/textureAtlas.ts b/src/systems/minecraft-temp/textureAtlas.ts
similarity index 100%
rename from src/systems/minecraft/textureAtlas.ts
rename to src/systems/minecraft-temp/textureAtlas.ts
diff --git a/src/systems/minecraft/textureShaders.ts b/src/systems/minecraft-temp/textureShaders.ts
similarity index 100%
rename from src/systems/minecraft/textureShaders.ts
rename to src/systems/minecraft-temp/textureShaders.ts
diff --git a/src/systems/minecraft-temp/versionManager.ts b/src/systems/minecraft-temp/versionManager.ts
new file mode 100644
index 00000000..0cc7ede3
--- /dev/null
+++ b/src/systems/minecraft-temp/versionManager.ts
@@ -0,0 +1,59 @@
+export const VERSION_MANIFEST_URL =
+ 'https://launchermeta.mojang.com/mc/game/version_manifest_v2.json'
+
+interface IMinecraftVersion {
+ id: string
+ type: 'snapshot' | 'release'
+ url: string
+ time: string
+ releaseTime: string
+ sha1: string
+ complianceLevel: number
+}
+
+export interface IMinecraftVersionManifest {
+ latest: {
+ release: string
+ snapshot: string
+ }
+ versions: IMinecraftVersion[]
+}
+
+let latestMinecraftVersion: IMinecraftVersion | undefined
+export async function getLatestVersion() {
+ if (latestMinecraftVersion) return latestMinecraftVersion
+ if (!window.navigator.onLine) {
+ console.warn('Not connected to the internet! Using last known latest version.')
+ latestMinecraftVersion = getCurrentVersion()
+ if (!latestMinecraftVersion)
+ throw new Error('No internet connection, and no previous latest version cached!')
+ return latestMinecraftVersion
+ }
+ let response: Response | undefined
+ try {
+ response = await fetch(VERSION_MANIFEST_URL)
+ } catch (error: any) {
+ throw new Error(
+ `Failed to fetch latest Minecraft version manifest: ${error.message as string}`
+ )
+ }
+ if (response?.ok) {
+ const result: IMinecraftVersionManifest = await response.json()
+ const version = result.versions.find(
+ (v: IMinecraftVersion) => v.id === result.latest.snapshot
+ )
+ if (!version) {
+ throw new Error(`Failed to find version data for '${result.latest.snapshot}'`)
+ }
+ latestMinecraftVersion = version
+ localStorage.setItem('animated_java:minecraftVersion', JSON.stringify(version))
+ return version
+ }
+ throw new Error('Failed to fetch latest Minecraft version manifest.')
+}
+
+export function getCurrentVersion() {
+ const stringVersion = localStorage.getItem('animated_java:minecraftVersion')
+ if (!stringVersion) return undefined
+ return JSON.parse(stringVersion) as IMinecraftVersion
+}
diff --git a/src/systems/minecraft/assetManager.ts b/src/systems/minecraft/assetManager.ts
index 3526dc27..3b5cb730 100644
--- a/src/systems/minecraft/assetManager.ts
+++ b/src/systems/minecraft/assetManager.ts
@@ -1,22 +1,22 @@
import { PACKAGE } from '../../constants'
import { getCurrentVersion, getLatestVersion } from './versionManager'
-import { events } from '../../util/events'
-import index from '../../assets/vanillaAssetOverrides/index.json'
-import { Unzipped } from 'fflate'
-import { unzip } from '../util'
-import download from 'download'
+import index from '@aj/assets/vanillaAssetOverrides/index.json'
import {
showOfflineError,
updateLoadingProgress,
updateLoadingProgressLabel,
-} from '../../interface/popup/animatedJavaLoading'
+} from '@aj/ui/popups/animated-java-loading'
+import EVENTS from '@aj/util/events'
+import download from 'download'
+import type { Unzipped } from 'fflate'
+import { unzip } from '../util'
const ASSET_OVERRIDES = index as unknown as Record
async function downloadJar(url: string, savePath: string) {
updateLoadingProgressLabel('Downloading Minecraft Assets...')
- const data = await download(url, { retry: { retries: 3 } })
+ const data = await download(url, savePath)
.on('downloadProgress', progress => {
updateLoadingProgress(progress.percent * 100)
})
@@ -99,7 +99,7 @@ export async function checkForAssetsUpdate() {
await extractAssets()
console.log('Minecraft assets are up to date!')
localStorage.setItem('assetsLoaded', 'true')
- requestAnimationFrame(() => events.MINECRAFT_ASSETS_LOADED.dispatch())
+ requestAnimationFrame(() => EVENTS.MINECRAFT_ASSETS_LOADED.dispatch())
}
let loadedAssets: Unzipped | undefined
@@ -116,7 +116,7 @@ export async function assetsLoaded() {
if (loadedAssets !== undefined) {
resolve()
} else {
- events.MINECRAFT_ASSETS_LOADED.subscribe(() => resolve(), true)
+ EVENTS.MINECRAFT_ASSETS_LOADED.subscribe(() => resolve(), true)
}
})
}
diff --git a/src/systems/minecraft/itemModelManager.ts b/src/systems/minecraft/itemModelManager.ts
index d53be4e7..5def3711 100644
--- a/src/systems/minecraft/itemModelManager.ts
+++ b/src/systems/minecraft/itemModelManager.ts
@@ -45,7 +45,7 @@ export async function getItemModel(item: string): Promise {
@@ -72,7 +72,7 @@ async function parseItemModel(location: string, childModel?: IItemModel): Promis
if (resource.type === 'block') {
return await parseBlockModel({ model: model.parent, isItemModel: true }, model)
}
- if (resource.path === 'item/generated') {
+ if (resource.subpath === 'item/generated') {
return await generateItemMesh(location, model)
} else {
return await parseItemModel(model.parent, model)
diff --git a/src/systems/node-configs/index.ts b/src/systems/node-configs/index.ts
new file mode 100644
index 00000000..a1c1de26
--- /dev/null
+++ b/src/systems/node-configs/index.ts
@@ -0,0 +1,255 @@
+import type { Alignment } from '@aj/blockbench-additions/outliner-elements/textDisplay'
+import { translate } from '@aj/util/translation'
+import { NbtByte, NbtCompound, NbtFloat, NbtInt, NbtString } from 'deepslate/lib/nbt'
+import { SerializableConfig } from './serializableConfig'
+export type { Serialized } from './serializableConfig'
+
+@SerializableConfig.decorate
+export class CommonDisplayConfig extends SerializableConfig {
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.billboard')
+ },
+ displayMode: 'select',
+ options: ['fixed', 'vertical', 'horizontal', 'center'],
+ })
+ billboard?: BillboardMode = 'fixed'
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.overrideBrightness')
+ },
+ displayMode: 'checkbox',
+ })
+ overrideBrightness? = false
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.brightness')
+ },
+ displayMode: 'slider',
+ min: 0,
+ max: 15,
+ step: 1,
+ })
+ brightness? = 0
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.glowing')
+ },
+ displayMode: 'checkbox',
+ })
+ glowing? = false
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.overrideGlowColor')
+ },
+ displayMode: 'checkbox',
+ })
+ overrideGlowColor? = false
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.glowColor')
+ },
+ displayMode: 'color',
+ })
+ glowColor? = '#ffffff'
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.invisible')
+ },
+ displayMode: 'checkbox',
+ })
+ invisible? = false
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.shadowRadius')
+ },
+ displayMode: 'number',
+ min: 0,
+ max: 64,
+ step: 0.1,
+ })
+ shadowRadius? = 0
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.shadowStrength')
+ },
+ displayMode: 'number',
+ min: 0,
+ max: 1,
+ step: 0.1,
+ })
+ shadowStrength? = 1
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.common.options.onSummonCommands')
+ },
+ displayMode: 'code_editor',
+ syntax: 'mc-build',
+ })
+ onSummonCommands? = ''
+
+ public toNBT(compound: NbtCompound = new NbtCompound()): NbtCompound {
+ if (this.billboard) {
+ compound.set('billboard', new NbtString(this.billboard))
+ }
+
+ if (this.overrideBrightness && this.brightness != undefined) {
+ compound.set(
+ 'brightness',
+ new NbtCompound()
+ .set('block', new NbtFloat(this.brightness))
+ .set('sky', new NbtFloat(this.brightness))
+ )
+ }
+
+ if (this.glowing) {
+ compound.set('Glowing', new NbtByte(Number(this.glowing)))
+ }
+ if (this.overrideGlowColor && this.glowColor != undefined) {
+ compound.set(
+ 'glow_color_override',
+ new NbtInt(Number(this.glowColor.replace('#', '0x')))
+ )
+ }
+
+ // TODO Figure out a good solution for toggling a bone's visibility...
+ // if (force || config.invisible !== defaultConfig.invisible) {
+ // compound.set('invisible', new NbtByte(1))
+ // }
+
+ if (this.shadowRadius) {
+ compound.set('shadow_radius', new NbtFloat(this.shadowRadius))
+ }
+
+ if (this.shadowStrength) {
+ compound.set('shadow_strength', new NbtFloat(this.shadowStrength))
+ }
+
+ return compound
+ }
+}
+
+@SerializableConfig.decorate
+export class BoneConfig extends SerializableConfig {
+ customName? = ''
+ customNameVisible? = false
+ enchanted? = false
+
+ public toNBT(compound: NbtCompound = new NbtCompound()): NbtCompound {
+ if (this.customName) {
+ compound.set('CustomName', new NbtString(this.customName))
+ }
+
+ if (this.customNameVisible) {
+ compound.set('CustomNameVisible', new NbtByte(Number(this.customNameVisible)))
+ }
+
+ if (this.enchanted) {
+ const item = (compound.get('item') as NbtCompound) || new NbtCompound()
+ compound.set(
+ 'item',
+ item.set(
+ 'components',
+ new NbtCompound().set(
+ 'minecraft:enchantments',
+ new NbtCompound().set(
+ 'levels',
+ new NbtCompound().set('minecraft:infinity', new NbtInt(1))
+ )
+ )
+ )
+ )
+ }
+
+ return compound
+ }
+}
+
+@SerializableConfig.decorate
+export class LocatorConfig extends SerializableConfig {
+ useEntity?: boolean
+ entityType?: string
+ syncPassengerRotation?: boolean
+ summonCommands?: string
+ tickingCommands?: string
+}
+
+@SerializableConfig.decorate
+export class CameraConfig extends SerializableConfig {
+ entityType?: string
+ nbt?: string
+ tickingCommands?: string
+}
+
+@SerializableConfig.decorate
+export class ItemDisplayConfig extends SerializableConfig {}
+
+@SerializableConfig.decorate
+export class BlockDisplayConfig extends SerializableConfig {}
+
+@SerializableConfig.decorate
+export class TextDisplayConfig extends SerializableConfig {
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.animated_java:text_display.options.alignment')
+ },
+ displayMode: 'select',
+ options: ['center', 'left', 'right'],
+ })
+ alignment?: Alignment = 'center'
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.animated_java:text_display.options.backgroundColor')
+ },
+ displayMode: 'color',
+ })
+ backgroundColor = '#0000003f'
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.animated_java:text_display.options.lineWidth')
+ },
+ displayMode: 'color',
+ })
+ lineWidth? = 200
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.animated_java:text_display.options.seeThrough')
+ },
+ displayMode: 'checkbox',
+ })
+ seeThrough? = false
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.animated_java:text_display.options.shadow')
+ },
+ displayMode: 'checkbox',
+ })
+ shadow? = false
+
+ @SerializableConfig.configurePropertyDisplay({
+ get displayName() {
+ return translate('config.animated_java:text_display.options.textComponent')
+ },
+ displayMode: 'code_editor',
+ syntax: 'json',
+ })
+ textComponent? = ''
+
+ public toNBT(compound = new NbtCompound()) {
+ console.error('toNBT not implemented for TextDisplayConfig!')
+ return compound
+ }
+}
diff --git a/src/systems/node-configs/serializableConfig.ts b/src/systems/node-configs/serializableConfig.ts
new file mode 100644
index 00000000..0011bd3f
--- /dev/null
+++ b/src/systems/node-configs/serializableConfig.ts
@@ -0,0 +1,378 @@
+/**
+ * Returns the "serialized" version of a class.
+ *
+ * Strips all properties with keys with values of type {@link Function} from the {@link T}, and makes all properties optional.
+ */
+export type Serialized = {
+ [Key in keyof T as T[Key] extends Function ? never : Key]?: T[Key]
+}
+
+// interface Stupid extends SerializableConfig> {}
+
+/**
+ * A class that can be serialized to JSON.
+ *
+ * Any classes that inherit from this class require the {@link SerializableConfig.decorate} decorator to function properly!
+ *
+ * Any properties will be serialized to JSON. If a property is not intentionally set, it will be omitted from the JSON.
+ *
+ * @example
+ * ```ts
+ * // Ignore the ; in the next line, it's just to make the code block work
+ * ;@SerializableConfig.decorate
+ * class MyConfig extends SerializableConfig {
+ * foo? = 'string'
+ * bar?: number
+ * }
+ *
+ * const config = new MyConfig()
+ * console.log(config.toJSON()) // {} - Default values are excluded from the JSON
+ * console.log(config.foo) // 'string' - But they are still accessible when reading
+ * console.log(config.get('foo', 'local')) // undefined - Unless you explicitly ask for the local value
+ * // Only explicitly set values are included in the JSON
+ * config.foo = 'baz'
+ * console.log(config.toJSON()) // { foo: 'baz' }
+ * config.foo = undefined
+ * config.bar = 42
+ * console.log(config.toJSON()) // { bar: 42 }
+ * console.log(config.foo) // 'string'
+ * ```
+ */
+export class SerializableConfig<
+ const Config extends Record,
+ const Object extends Serialized = Serialized,
+ const JsonObject extends Object & {
+ __inheritedKeys__?: Array
+ } = Object & {
+ __inheritedKeys__?: Array
+ },
+> {
+ private static __propertyDisplayConfigs__: Partial> = {}
+ private __defaultValues__ = {} as Record
+ private __inheritedKeys__ = new Set()
+ private __getLocal__ = false
+ /** The actual instance of {@link Config} (No proxies) */
+ private __origin__: SerializableConfig
+ private __parent__?: Config
+ constructor() {
+ // @ts-expect-error
+ this.__origin__ = this
+ const origin = this
+ return new Proxy(this, {
+ get(target, key: string & keyof Object) {
+ // Return the value if it's set in this config
+ // @ts-expect-error
+ if (target[key] != undefined) {
+ return target[key]
+ }
+ // Check inheritance / default value if we're not expecting a local value.
+ if (!origin.__getLocal__) {
+ // If the key is inherited, return the value from the parent if it's set there
+ if (origin.__inheritedKeys__.has(key)) {
+ const parentValue = target.__parent__?.[key]
+ if (parentValue != undefined) {
+ return parentValue
+ }
+ }
+ // Default
+ return origin.__defaultValues__[key]
+ }
+ // Return undefined if non of the above
+ return undefined
+ },
+ set(target, key: string, newValue) {
+ // @ts-expect-error
+ target[key] = newValue
+ return true
+ },
+ })
+ }
+ private __postInitialize() {
+ for (const key of Object.getOwnPropertyNames(this) as Array) {
+ if (key.startsWith('_')) continue
+ // @ts-expect-error
+ this.__defaultValues__[key] = this[key]
+ // @ts-expect-error
+ this[key] = undefined
+ }
+ }
+ /**
+ * Serialize the config to a JSON object.
+ * @param metaData If true, include metadata such as inherited keys.
+ */
+ toJSON(metaData = true): JsonObject {
+ const result = {} as JsonObject
+ for (const key of Object.getOwnPropertyNames(this) as Array) {
+ if (key.startsWith('_')) continue
+ const value = this.get(key, 'local-inherited')
+ if (value != undefined) {
+ result[key] = value
+ }
+ }
+ if (metaData && this.__inheritedKeys__.size > 0) {
+ result.__inheritedKeys__ = Array.from(this.__inheritedKeys__) as Array<
+ string & keyof Object
+ >
+ }
+ return JSON.parse(JSON.stringify(result))
+ }
+ /**
+ * Initialize the config from a JSON object.
+ * @param json The JSON object to initialize the config from.
+ * @param partial If true, only set the properties that are present in the JSON object. Otherwise, clear all properties that are not present in the JSON object.
+ */
+ fromJSON(json: Partial, partial = false): this {
+ json = JSON.parse(JSON.stringify(json))
+ if (json.__inheritedKeys__) {
+ this.__inheritedKeys__ = new Set(json.__inheritedKeys__)
+ } else if (!partial) {
+ this.__inheritedKeys__ = new Set()
+ }
+ for (const key of Object.getOwnPropertyNames(this) as Array) {
+ if (key.startsWith('_')) continue
+ // Explicitly check for nullish, and use undefined if the key is not present in the JSON.
+ if (json[key] != undefined) {
+ // @ts-expect-error
+ this[key] = json[key]
+ } else if (!partial) {
+ // @ts-expect-error
+ this[key] = undefined
+ }
+ }
+ return this
+ }
+ isDefault(): boolean {
+ return Object.getOwnPropertyNames(this).every(key => {
+ if (key.startsWith('_')) return true
+ // @ts-expect-error
+ return this[key] == undefined
+ })
+ }
+ /**
+ * Explicitly set the value of {@link key} to its default.
+ *
+ * Note that this is different from setting it to `undefined`.
+ */
+ makeDefault(key: string & keyof Object): void {
+ // @ts-expect-error
+ this[key] = this.__defaultValues__[key]
+ }
+ /**
+ * Checks whether two configs are equal.
+ */
+ equalTo(other: Config): boolean {
+ for (const key of Object.getOwnPropertyNames(this)) {
+ if (key.startsWith('_')) continue
+ // @ts-expect-error
+ if (this[key] !== other[key]) return false
+ }
+ return true
+ }
+ /**
+ * Set the value of {@link key} to be (or not to be) inherited from the parent if it's not set locally.
+ * @param key The key to set the inheritance of.
+ * @param inherit Whether or not to inherit the value from the parent.
+ */
+ setKeyInheritance(key: string & keyof Object, inherit = true): this {
+ if (inherit) {
+ this.__inheritedKeys__.add(key)
+ } else {
+ this.__inheritedKeys__.delete(key)
+ }
+ return this
+ }
+ /**
+ * Get the inheritance status of {@link key}.
+ * @returns Whether or not the key is inherited from the parent.
+ */
+ getKeyInheritance(key: string & keyof Object): boolean {
+ return this.__inheritedKeys__.has(key)
+ }
+ /**
+ * Gets the value of {@link key} based on {@link getMode}
+ * @returns If {@link getMode} is `local`, returns the explicitly set value of property {@link key} on this instance. If {@link getMode} is `local-inherited`
+ * and the local value is `undefined`, it attempts to return the parent's explicitly set `local-inherited` value. If {@link getMode} is `default` it will return the `key`'s default value.
+ */
+ get(
+ key: string & keyof Object,
+ getMode: 'local' | 'local-inherited'
+ ): Object[string & keyof Object] | undefined
+ get(key: string & keyof Object, getMode: 'default'): Object[string & keyof Object]
+ get(
+ key: string & keyof Object,
+ getMode: 'local' | 'local-inherited' | 'default'
+ ): Object[string & keyof Object] | undefined {
+ if (getMode === 'default') {
+ return this.__defaultValues__[key]
+ }
+ // @ts-expect-error
+ const local = this.__origin__[key]
+ if (local != undefined) {
+ // Return the explicitly set local value
+ return local
+ }
+ if (getMode === 'local') return undefined
+ if (this.__inheritedKeys__.has(key) && this.__parent__) {
+ // Return the inherited value if it's explicitly set.
+ const inherited = this.__parent__.get(key)
+ if (inherited != undefined) {
+ return inherited
+ }
+ }
+ }
+ /**
+ * Set the value of {@link key} to {@link value}.
+ *
+ * Convenience method for `config[key] = value` typing issues.
+ */
+ set(key: string & keyof Object, value: Object[string & keyof Object]): this {
+ // @ts-expect-error
+ this[key] = value
+ return this
+ }
+ /**
+ * Set the parent of this config.
+ *
+ * Any properties that are not set in this config will be fetched from the parent.
+ */
+ setParent(parent: Config | undefined): this {
+ this.__parent__ = parent
+ return this
+ }
+ /**
+ * @param sort If true, sort the keys alphabetically. If a function, sort the keys using the function. The function
+ * uses the same implementation as {@link Array.prototype.sort}.
+ */
+ // FIXME - The Key type keeps reurning a union of string | number even though the only valid keys should be strings...
+ keys(sort?: boolean): Array
+ keys(sort?: (a: string, b: string) => number): Array
+ keys(sort?: boolean | ((a: string, b: string) => number)): Array {
+ const keys: Array = []
+ const names = Object.getOwnPropertyNames(this)
+ if (sort === true) {
+ names.sort()
+ } else if (typeof sort === 'function') {
+ names.sort(sort)
+ }
+ for (const key of names) {
+ if (key.startsWith('_')) continue
+ keys.push(key as string & keyof Object)
+ }
+ return keys
+ }
+ values(): Array {
+ const values: Array = []
+ for (const key of Object.getOwnPropertyNames(this)) {
+ if (key.startsWith('_')) continue
+ // @ts-expect-error
+ values.push(this[key])
+ }
+ return values
+ }
+ /**
+ * Get the linked status of a key.
+ *
+ * Indicates if {@link key} can be inherited from the parent.
+ * Returns false if the key is explicitly set locally.
+ */
+ getLinkedState(key: string & keyof Object): boolean {
+ return this.get(key, 'local') == undefined
+ }
+ /**
+ * Get the linked status of all keys.
+ *
+ * A key's linked status Indicates if that key can be inherited from the parent.
+ * Returns false if the key is explicitly set locally.
+ */
+ getAllLinkedStates(): Map {
+ const map = new Map()
+ for (const key of this.keys()) {
+ map.set(key, this.get(key, 'local') == undefined)
+ }
+ return map
+ }
+ /**
+ * @param sort If true, sort the entries alphabetically. If a function, sort the entries using the function. The function
+ * uses the same implementation as {@link Array.prototype.sort}.
+ */
+ entries(sort?: boolean): Array<[string & keyof Object, Object[string & keyof Object]]>
+ entries(
+ sort?: (a: string, b: string) => number
+ ): Array<[string & keyof Object, Object[string & keyof Object]]>
+ entries(
+ sort?: boolean | ((a: string, b: string) => number)
+ ): Array<[string & keyof Object, Object[string & keyof Object]]> {
+ const entries: Array<[string & keyof Object, Object[string & keyof Object]]> = []
+ const names = Object.getOwnPropertyNames(this)
+ if (sort === true) {
+ names.sort()
+ } else if (typeof sort === 'function') {
+ names.sort(sort)
+ }
+ for (const key of names) {
+ if (key.startsWith('_')) continue
+ // @ts-expect-error
+ entries.push([key, this[key]])
+ }
+ return entries
+ }
+ getPropertyDescription(key: string & keyof Object): PropertyDisplayConfig | undefined {
+ return this.constructor.prototype.__propertyDisplayConfigs__?.[key]
+ }
+ /**
+ * Decorator to make a class a serializable config.
+ */
+ // FetchBot is the GOAT 🐐
+ static decorate SerializableConfig>>(
+ settingClass: Class
+ ) {
+ return new Proxy(settingClass, {
+ construct(target, args) {
+ const instance = new target(...(args as []))
+ instance.__postInitialize()
+ return instance
+ },
+ })
+ }
+ static configurePropertyDisplay(options: PropertyDisplayConfig) {
+ return ((target, key) => {
+ target.constructor.prototype.__propertyDisplayConfigs__ ??= {}
+ target.constructor.prototype.__propertyDisplayConfigs__[key] = options
+ }) satisfies PropertyDecorator
+ }
+}
+
+interface IPropertyDisplayConfig {
+ displayName: string
+}
+
+interface IPropertyDisplayConfigs {
+ select: IPropertyDisplayConfig & {
+ displayMode: 'select'
+ options: string[]
+ }
+ color: IPropertyDisplayConfig & {
+ displayMode: 'color'
+ }
+ checkbox: IPropertyDisplayConfig & {
+ displayMode: 'checkbox'
+ }
+ code_editor: IPropertyDisplayConfig & {
+ displayMode: 'code_editor'
+ syntax: string
+ }
+ slider: IPropertyDisplayConfig & {
+ displayMode: 'slider'
+ min?: number
+ max?: number
+ step: number
+ }
+ number: IPropertyDisplayConfig & {
+ displayMode: 'number'
+ min?: number
+ max?: number
+ step: number
+ }
+}
+
+export type PropertyDisplayConfig = IPropertyDisplayConfigs[keyof IPropertyDisplayConfigs]
diff --git a/src/systems/plugin-json-compiler/index.ts b/src/systems/plugin-json-compiler/index.ts
new file mode 100644
index 00000000..0c06ca81
--- /dev/null
+++ b/src/systems/plugin-json-compiler/index.ts
@@ -0,0 +1,365 @@
+import {
+ getKeyframeCommands,
+ getKeyframeExecuteCondition,
+ getKeyframeRepeat,
+ getKeyframeRepeatFrequency,
+ getKeyframeVariant,
+} from '../../blockbench-mods/misc/customKeyframes'
+import { type defaultValues } from '../../blueprintSettings'
+import { type EasingKey } from '../../util/easing'
+import { resolvePath } from '../../util/fileUtil'
+import { detectCircularReferences, mapObjEntries, scrubUndefined } from '../../util/misc'
+import { Variant } from '../../variants'
+import type { INodeTransform, IRenderedAnimation, IRenderedFrame } from '../animation-renderer'
+import type {
+ AnyRenderedNode,
+ IRenderedModel,
+ IRenderedRig,
+ IRenderedVariant,
+ IRenderedVariantModel,
+} from '../rig-renderer'
+
+type ExportedNodetransform = Omit<
+ INodeTransform,
+ 'type' | 'name' | 'uuid' | 'node' | 'matrix' | 'decomposed' | 'executeCondition'
+> & {
+ matrix: number[]
+ decomposed: {
+ translation: ArrayVector3
+ left_rotation: ArrayVector4
+ scale: ArrayVector3
+ }
+ pos: ArrayVector3
+ rot: ArrayVector3
+ scale: ArrayVector3
+ execute_condition?: string
+}
+type ExportedRenderedNode = Omit<
+ AnyRenderedNode,
+ 'node' | 'parentNode' | 'model' | 'boundingBox' | 'configs' | 'baseScale' | 'safe_name'
+> & {
+ default_transform: ExportedNodetransform
+ bounding_box?: { min: ArrayVector3; max: ArrayVector3 }
+ configs?: Record
+}
+type ExportedAnimationFrame = Omit & {
+ node_transforms: Record
+}
+type ExportedBakedAnimation = Omit<
+ IRenderedAnimation,
+ 'uuid' | 'frames' | 'modified_nodes' | 'safe_name'
+> & {
+ frames: ExportedAnimationFrame[]
+ modified_nodes: string[]
+}
+interface ExportedKeyframe {
+ time: number
+ channel: string
+ value?: [string, string, string]
+ post?: [string, string, string]
+ interpolation?:
+ | {
+ type: 'linear'
+ easing: EasingKey
+ easingArgs?: number[]
+ }
+ | {
+ type: 'bezier'
+ bezier_linked?: boolean
+ bezier_left_time?: ArrayVector3
+ bezier_left_value?: ArrayVector3
+ bezier_right_time?: ArrayVector3
+ bezier_right_value?: ArrayVector3
+ }
+ | {
+ type: 'catmullrom'
+ }
+ | {
+ type: 'step'
+ }
+ commands?: string
+ variant?: string
+ execute_condition?: string
+ repeat?: boolean
+ repeat_frequency?: number
+}
+type ExportedAnimator = ExportedKeyframe[]
+interface ExportedDynamicAnimation {
+ name: string
+ loop_mode: 'once' | 'hold' | 'loop'
+ duration: number
+ excluded_nodes: string[]
+ animators: Record
+}
+interface ExportedTexture {
+ name: string
+ src: string
+}
+type ExportedVariantModel = Omit<
+ IRenderedVariantModel,
+ 'model_path' | 'resource_location' | 'item_model'
+> & {
+ model: IRenderedModel | null
+ custom_model_data: number
+}
+type ExportedVariant = Omit & {
+ /**
+ * A map of bone UUID -> IRenderedVariantModel
+ */
+ models: Record
+}
+
+export interface IExportedJSON {
+ /**
+ * The Blueprint's Settings
+ */
+ settings: {
+ export_namespace: (typeof defaultValues)['export_namespace']
+ bounding_box: (typeof defaultValues)['bounding_box']
+ // Resource Pack Settings
+ custom_model_data_offset: (typeof defaultValues)['custom_model_data_offset']
+ // Plugin Settings
+ baked_animations: (typeof defaultValues)['baked_animations']
+ }
+ textures: Record
+ nodes: Record
+ variants: Record
+ /**
+ * If `blueprint_settings.baked_animations` is true, this will be an array of `ExportedAnimation` objects. Otherwise, it will be an array of `AnimationUndoCopy` objects, just like the `.bbmodel`'s animation list.
+ */
+ animations: Record | Record
+}
+
+function transferKey(obj: any, oldKey: string, newKey: string) {
+ obj[newKey] = obj[oldKey]
+ delete obj[oldKey]
+}
+
+function serailizeKeyframe(kf: _Keyframe): ExportedKeyframe {
+ const json = {
+ time: kf.time,
+ channel: kf.channel,
+ commands: getKeyframeCommands(kf),
+ variant: getKeyframeVariant(kf),
+ execute_condition: getKeyframeExecuteCondition(kf),
+ repeat: getKeyframeRepeat(kf),
+ repeat_frequency: getKeyframeRepeatFrequency(kf),
+ } as ExportedKeyframe
+
+ switch (json.channel) {
+ case 'variant':
+ case 'commands':
+ break
+ default: {
+ json.value = [
+ kf.get('x', 0).toString(),
+ kf.get('y', 0).toString(),
+ kf.get('z', 0).toString(),
+ ]
+ json.interpolation = { type: kf.interpolation } as any
+ }
+ }
+
+ if (json.interpolation) {
+ switch (json.interpolation.type) {
+ case 'linear': {
+ json.interpolation.easing = kf.easing!
+ if (kf.easingArgs?.length) json.interpolation.easingArgs = kf.easingArgs
+ break
+ }
+ case 'bezier': {
+ json.interpolation.bezier_linked = kf.bezier_linked
+ json.interpolation.bezier_left_time = kf.bezier_left_time.slice() as ArrayVector3
+ json.interpolation.bezier_left_value = kf.bezier_left_value.slice() as ArrayVector3
+ json.interpolation.bezier_right_time = kf.bezier_right_time.slice() as ArrayVector3
+ json.interpolation.bezier_right_value =
+ kf.bezier_right_value.slice() as ArrayVector3
+ break
+ }
+ case 'catmullrom': {
+ break
+ }
+ case 'step': {
+ break
+ }
+ }
+ }
+
+ if (kf.data_points.length === 2) {
+ json.post = [
+ kf.get('x', 1).toString(),
+ kf.get('y', 1).toString(),
+ kf.get('z', 1).toString(),
+ ]
+ }
+
+ return json
+}
+
+function serializeVariant(rig: IRenderedRig, variant: IRenderedVariant): ExportedVariant {
+ const json: ExportedVariant = {
+ ...variant,
+ models: mapObjEntries(variant.models, (uuid, model) => {
+ const json: ExportedVariantModel = {
+ model: model.model,
+ custom_model_data: model.custom_model_data,
+ }
+ return [uuid, json]
+ }),
+ }
+ return json
+}
+
+export function exportJSON(options: {
+ rig: IRenderedRig
+ animations: IRenderedAnimation[]
+ displayItemPath: string
+ textureExportFolder: string
+ modelExportFolder: string
+}) {
+ const aj = Project!.animated_java
+ const { rig, animations } = options
+
+ console.log('Exporting JSON...', options)
+
+ function serializeTexture(texture: Texture): ExportedTexture {
+ return {
+ name: texture.name,
+ src: texture.getDataURL(),
+ }
+ }
+
+ const json: IExportedJSON = {
+ settings: {
+ export_namespace: aj.export_namespace,
+ bounding_box: aj.bounding_box,
+ custom_model_data_offset: aj.custom_model_data_offset,
+ baked_animations: aj.baked_animations,
+ },
+ textures: mapObjEntries(rig.textures, (_, texture) => [
+ texture.uuid,
+ serializeTexture(texture),
+ ]),
+ nodes: mapObjEntries(rig.nodes, (uuid, node) => [uuid, serailizeRenderedNode(node)]),
+ variants: mapObjEntries(rig.variants, (uuid, variant) => [
+ uuid,
+ serializeVariant(rig, variant),
+ ]),
+ animations: {},
+ }
+
+ if (aj.baked_animations) {
+ for (const animation of animations) {
+ json.animations[animation.uuid] = serializeAnimation(animation)
+ }
+ } else {
+ for (const animation of Blockbench.Animation.all) {
+ const animJSON: ExportedDynamicAnimation = {
+ name: animation.name,
+ loop_mode: animation.loop,
+ duration: animation.length,
+ excluded_nodes: animation.excluded_nodes.map(node => node.value),
+ animators: {},
+ }
+ for (const [uuid, animator] of Object.entries(animation.animators)) {
+ // Only include animators with keyframes
+ if (animator.keyframes.length === 0) continue
+ animJSON.animators[uuid] = animator.keyframes.map(serailizeKeyframe)
+ }
+ json.animations[animation.uuid] = animJSON
+ }
+ }
+
+ console.log('Exported JSON:', json)
+ if (detectCircularReferences(json)) {
+ throw new Error('Circular references detected in exported JSON.')
+ }
+ console.log('Scrubbed:', scrubUndefined(json))
+
+ let exportPath: string
+ try {
+ exportPath = resolvePath(aj.json_file)
+ } catch (e) {
+ console.log(`Failed to resolve export path '${aj.json_file}'`)
+ console.error(e)
+ return
+ }
+
+ fs.writeFileSync(exportPath, compileJSON(json).toString())
+}
+
+function serailizeNodeTransform(node: INodeTransform): ExportedNodetransform {
+ const json: ExportedNodetransform = {
+ matrix: node.matrix.elements,
+ decomposed: {
+ translation: node.decomposed.translation.toArray(),
+ left_rotation: node.decomposed.leftRotation.toArray() as ArrayVector4,
+ scale: node.decomposed.scale.toArray(),
+ },
+ pos: node.pos,
+ rot: node.rot,
+ head_rot: node.head_rot,
+ scale: node.scale,
+ interpolation: node.interpolation,
+ commands: node.commands,
+ execute_condition: node.execute_condition,
+ }
+ return json
+}
+
+function serailizeRenderedNode(node: AnyRenderedNode): ExportedRenderedNode {
+ const json: any = { ...node }
+ delete json.node
+ delete json.parentNode
+ delete json.safe_name
+ delete json.model
+ transferKey(json, 'lineWidth', 'line_width')
+ transferKey(json, 'backgroundColor', 'background_color')
+ transferKey(json, 'backgroundAlpha', 'background_alpha')
+
+ json.default_transform = serailizeNodeTransform(json.default_transform as INodeTransform)
+ switch (node.type) {
+ case 'bone': {
+ delete json.boundingBox
+ json.bounding_box = {
+ min: node.bounding_box.min.toArray(),
+ max: node.bounding_box.max.toArray(),
+ }
+ delete json.configs
+ json.configs = { ...node.configs?.variants }
+ const defaultVariant = Variant.getDefault()
+ if (node.configs?.default && defaultVariant) {
+ json.configs[defaultVariant.uuid] = node.configs.default
+ }
+ break
+ }
+ case 'text_display': {
+ json.text = node.text?.toJSON()
+ break
+ }
+ }
+ return json as ExportedRenderedNode
+}
+
+function serializeAnimation(animation: IRenderedAnimation): ExportedBakedAnimation {
+ const json: ExportedBakedAnimation = {
+ name: animation.name,
+ duration: animation.duration,
+ loop_delay: animation.loop_delay,
+ loop_mode: animation.loop_mode,
+ frames: [],
+ modified_nodes: Object.keys(animation.modified_nodes),
+ }
+
+ const frames: ExportedAnimationFrame[] = []
+ for (const frame of animation.frames) {
+ const nodeTransforms: Record = {}
+ for (const [uuid, nodeTransform] of Object.entries(frame.node_transforms)) {
+ nodeTransforms[uuid] = serailizeNodeTransform(nodeTransform)
+ }
+ frames.push({ ...frame, node_transforms: nodeTransforms })
+ }
+ json.frames = frames
+
+ return json
+}
diff --git a/src/systems/jsonCompiler.ts b/src/systems/plugin-json-compiler/jsonCompiler.ts
similarity index 93%
rename from src/systems/jsonCompiler.ts
rename to src/systems/plugin-json-compiler/jsonCompiler.ts
index 568d28e3..e783d5d1 100644
--- a/src/systems/jsonCompiler.ts
+++ b/src/systems/plugin-json-compiler/jsonCompiler.ts
@@ -1,28 +1,24 @@
-////
-///
-///
-
-import type { IBlueprintBoneConfigJSON } from '../blueprintFormat'
-import { type defaultValues } from '../blueprintSettings'
+import type { IBlueprintBoneConfigJSON } from '../../blockbench-additions/model-formats/ajblueprint'
import {
getKeyframeCommands,
getKeyframeExecuteCondition,
getKeyframeRepeat,
getKeyframeRepeatFrequency,
getKeyframeVariant,
-} from '../mods/customKeyframesMod'
-import { EasingKey } from '../util/easing'
-import { resolvePath } from '../util/fileUtil'
-import { detectCircularReferences, mapObjEntries, scrubUndefined } from '../util/misc'
-import { Variant } from '../variants'
-import type { INodeTransform, IRenderedAnimation, IRenderedFrame } from './animationRenderer'
+} from '../../blockbench-mods/misc/customKeyframes'
+import { type defaultValues } from '../../blueprintSettings'
+import { type EasingKey } from '../../util/easing'
+import { resolvePath } from '../../util/fileUtil'
+import { detectCircularReferences, mapObjEntries, scrubUndefined } from '../../util/misc'
+import { Variant } from '../../variants'
+import type { INodeTransform, IRenderedAnimation, IRenderedFrame } from '../animation-renderer'
import type {
AnyRenderedNode,
IRenderedModel,
IRenderedRig,
IRenderedVariant,
IRenderedVariantModel,
-} from './rigRenderer'
+} from '../rig-renderer'
type ExportedNodetransform = Omit<
INodeTransform,
@@ -64,7 +60,7 @@ type ExportedBakedAnimation = Omit<
frames: ExportedAnimationFrame[]
modified_nodes: string[]
}
-type ExportedKeyframe = {
+interface ExportedKeyframe {
time: number
channel: string
value?: [string, string, string]
@@ -96,7 +92,7 @@ type ExportedKeyframe = {
repeat_frequency?: number
}
type ExportedAnimator = ExportedKeyframe[]
-type ExportedDynamicAnimation = {
+interface ExportedDynamicAnimation {
name: string
loop_mode: 'once' | 'hold' | 'loop'
duration: number
@@ -305,7 +301,7 @@ function serailizeNodeTransform(node: INodeTransform): ExportedNodetransform {
matrix: node.matrix.elements,
decomposed: {
translation: node.decomposed.translation.toArray(),
- left_rotation: node.decomposed.left_rotation.toArray() as ArrayVector4,
+ left_rotation: node.decomposed.leftRotation.toArray() as ArrayVector4,
scale: node.decomposed.scale.toArray(),
},
pos: node.pos,
diff --git a/src/systems/resourcepackCompiler/1.20.4.ts b/src/systems/resourcepack-compiler/1.20.4.ts
similarity index 89%
rename from src/systems/resourcepackCompiler/1.20.4.ts
rename to src/systems/resourcepack-compiler/1.20.4.ts
index f8cd7a3d..a5e16b2e 100644
--- a/src/systems/resourcepackCompiler/1.20.4.ts
+++ b/src/systems/resourcepack-compiler/1.20.4.ts
@@ -1,9 +1,10 @@
-import type { ResourcePackCompiler } from '.'
-import { PROGRESS_DESCRIPTION } from '../../interface/dialog/exportProgress'
-import { isResourcePackPath, sanitizePathName } from '../../util/minecraftUtil'
-import { ITextureAtlas } from '../minecraft/textureAtlas'
-import { IRenderedNodes } from '../rigRenderer'
-import { sortObjectKeys } from '../util'
+import { sortObjectKeys } from '@aj/util/misc'
+import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '../../ui/dialogs/export-progress'
+import { isResourcePackPath, toSafeFunctionName } from '../../util/minecraftUtil'
+import { AJMeta } from '../ajmeta'
+import { type ITextureAtlas } from '../minecraft-temp/textureAtlas'
+import type { IRenderedNodes, IRenderedRig } from '../rig-renderer'
+import { zip } from '../util'
interface IPredicateItemModel {
parent?: string
@@ -76,10 +77,10 @@ class PredicateItemModel {
}
}
- file.animated_java[aj.export_namespace] ??= []
+ file.animated_java[aj.id] ??= []
for (const [name, ownedIds] of Object.entries(file.animated_java)) {
- const namespace = aj.export_namespace
+ const namespace = aj.id
const lastNamespace = Project!.last_used_export_namespace
if (name === namespace || name === lastNamespace) {
file.overrides = file.overrides.filter(
@@ -102,7 +103,7 @@ class PredicateItemModel {
toJSON(): IPredicateItemModel {
const [displayItemNamespace, displayItemName] =
Project!.animated_java.display_item.split(':')
- const exportNamespace = Project!.animated_java.export_namespace
+ const exportNamespace = Project!.animated_java.id
return {
parent: this.parent,
@@ -137,12 +138,19 @@ const compileResourcePack: ResourcePackCompiler = async ({
const aj = Project!.animated_java
PROGRESS_DESCRIPTION.set('Compiling Resource Pack...')
- console.log('Compiling resource pack...', {
- rig,
- displayItemPath,
- textureExportFolder,
- modelExportFolder,
- })
+ console.log('Compiling resource pack...', options)
+
+ const ajmeta = new AJMeta(
+ PathModule.join(options.resourcePackFolder, 'assets.ajmeta'),
+ aj.export_namespace,
+ lastUsedExportNamespace,
+ options.resourcePackFolder
+ )
+ if (aj.resource_pack_export_mode === 'raw') {
+ ajmeta.read()
+ }
+
+ const exportedFiles = new Map()
// Empty Model
versionedFiles.set(PathModule.join('assets/animated_java/models/empty.json'), { content: '{}' })
diff --git a/src/systems/resourcepackCompiler/1.21.2.ts b/src/systems/resourcepack-compiler/1.21.2.ts
similarity index 82%
rename from src/systems/resourcepackCompiler/1.21.2.ts
rename to src/systems/resourcepack-compiler/1.21.2.ts
index 9c79cb38..96179713 100644
--- a/src/systems/resourcepackCompiler/1.21.2.ts
+++ b/src/systems/resourcepack-compiler/1.21.2.ts
@@ -1,8 +1,18 @@
import type { ResourcePackCompiler } from '.'
-import { PROGRESS_DESCRIPTION } from '../../interface/dialog/exportProgress'
-import { safeReadSync } from '../../util/fileUtil'
+import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '../../interface/dialog/exportProgress'
import { isResourcePackPath, sanitizePathName } from '../../util/minecraftUtil'
+import { AJMeta } from '../ajmeta'
import { type ITextureAtlas } from '../minecraft/textureAtlas'
+import { IRenderedNodes, IRenderedRig } from '../rigRenderer'
+import { zip } from '../util'
+
+export default async function compileResourcePack(options: {
+ rig: IRenderedRig
+ resourcePackFolder: string
+ textureExportFolder: string
+ modelExportFolder: string
+}) {
+ const { rig, resourcePackFolder, textureExportFolder, modelExportFolder } = options
import { IRenderedNodes } from '../rigRenderer'
const compileResourcePack: ResourcePackCompiler = async ({
@@ -18,11 +28,19 @@ const compileResourcePack: ResourcePackCompiler = async ({
)
PROGRESS_DESCRIPTION.set('Compiling Resource Pack...')
- console.log('Compiling resource pack...', {
- rig,
- textureExportFolder,
- modelExportFolder,
- })
+ console.log('Compiling resource pack...', options)
+
+ const ajmeta = new AJMeta(
+ PathModule.join(options.resourcePackFolder, 'assets.ajmeta'),
+ aj.id,
+ lastUsedExportNamespace,
+ options.resourcePackFolder
+ )
+ if (aj.resource_pack_export_mode === 'raw') {
+ ajmeta.read()
+ }
+
+ const exportedFiles = new Map()
// Texture atlas
const blockAtlasPath = PathModule.join('assets/minecraft/atlases/blocks.json')
diff --git a/src/systems/resourcepackCompiler/1.21.4.ts b/src/systems/resourcepack-compiler/1.21.4.ts
similarity index 94%
rename from src/systems/resourcepackCompiler/1.21.4.ts
rename to src/systems/resourcepack-compiler/1.21.4.ts
index a1e4fb4e..e43d9a0f 100644
--- a/src/systems/resourcepackCompiler/1.21.4.ts
+++ b/src/systems/resourcepack-compiler/1.21.4.ts
@@ -2,6 +2,7 @@ import type { ResourcePackCompiler } from '.'
import { PROGRESS_DESCRIPTION } from '../../interface/dialog/exportProgress'
import { isResourcePackPath, sanitizePathName } from '../../util/minecraftUtil'
import { Variant } from '../../variants'
+import { AJMeta } from '../ajmeta'
import { IItemDefinition } from '../minecraft/itemDefinitions'
import { type ITextureAtlas } from '../minecraft/textureAtlas'
import { IRenderedNodes, IRenderedRig, IRenderedVariantModel } from '../rigRenderer'
@@ -16,11 +17,17 @@ const compileResourcePack: ResourcePackCompiler = async ({
const aj = Project!.animated_java
PROGRESS_DESCRIPTION.set('Compiling Resource Pack...')
- console.log('Compiling resource pack...', {
- rig,
- textureExportFolder,
- modelExportFolder,
- })
+ console.log('Compiling resource pack...', options)
+
+ const ajmeta = new AJMeta(
+ PathModule.join(options.resourcePackFolder, 'assets.ajmeta'),
+ aj.export_namespace,
+ lastUsedExportNamespace,
+ options.resourcePackFolder
+ )
+ ajmeta.read()
+
+ const exportedFiles = new Map()
const globalModelsFolder = PathModule.join('assets/animated_java/models/')
const itemModelDefinitionsFolder = PathModule.join(
diff --git a/src/systems/resourcepackCompiler/index.ts b/src/systems/resourcepack-compiler/index.ts
similarity index 100%
rename from src/systems/resourcepackCompiler/index.ts
rename to src/systems/resourcepack-compiler/index.ts
diff --git a/src/systems/rigRenderer.ts b/src/systems/rig-renderer/index.ts
similarity index 93%
rename from src/systems/rigRenderer.ts
rename to src/systems/rig-renderer/index.ts
index 7bdcfbb8..2e0a7106 100644
--- a/src/systems/rigRenderer.ts
+++ b/src/systems/rig-renderer/index.ts
@@ -1,31 +1,33 @@
+import type { IBlueprintVariantJSON } from '@aj/blockbench-additions/model-formats/ajblueprint'
+import { BlockDisplay } from '@aj/blockbench-additions/outliner-elements/blockDisplay'
+import { ItemDisplay } from '@aj/blockbench-additions/outliner-elements/itemDisplay'
import * as crypto from 'crypto'
import {
- IBlueprintCameraConfigJSON,
- IBlueprintLocatorConfigJSON,
- IBlueprintTextDisplayConfigJSON,
- IBlueprintVariantJSON,
- type IBlueprintBoneConfigJSON,
-} from '../blueprintFormat'
-import { BoneConfig } from '../nodeConfigs'
-import { Alignment, TextDisplay } from '../outliner/textDisplay'
-import { VanillaBlockDisplay } from '../outliner/vanillaBlockDisplay'
-import { VanillaItemDisplay } from '../outliner/vanillaItemDisplay'
+ type Alignment,
+ TextDisplay,
+} from '../../blockbench-additions/outliner-elements/textDisplay'
import {
- IMinecraftResourceLocation,
+ type IMinecraftResourceLocation,
parseResourcePackPath,
- sanitizePathName,
- sanitizeStorageKey,
-} from '../util/minecraftUtil'
-import { Variant } from '../variants'
+ toSafeFunctionName,
+} from '../../util/minecraftUtil'
+import { Variant } from '../../variants'
import {
correctSceneAngle,
getFrame,
+ type INodeTransform,
restoreSceneAngle,
updatePreview,
- type INodeTransform,
-} from './animationRenderer'
-import { IntentionalExportError } from './exporter'
-import { JsonText } from './minecraft/jsonText'
+} from '../animation-renderer'
+import { IntentionalExportError } from '../exporter'
+import { JsonText } from '../minecraft-temp/jsonText'
+import {
+ CameraConfig,
+ CommonDisplayConfig,
+ LocatorConfig,
+ type Serialized,
+ TextDisplayConfig,
+} from '../node-configs'
export interface IRenderedFace {
uv: number[]
@@ -92,7 +94,7 @@ export interface ICamera extends OutlinerElement {
linked_preview: string
camera_linked: boolean
visibility: boolean
- config: IBlueprintCameraConfigJSON
+ config: Serialized
preview_controller: NodePreviewController
}
@@ -105,8 +107,8 @@ export interface IRenderedNodes {
*/
base_scale: number
configs?: {
- default?: IBlueprintBoneConfigJSON
- variants: Record
+ default?: Serialized
+ variants: Record>
}
}
Struct: IRenderedNode & {
@@ -114,15 +116,15 @@ export interface IRenderedNodes {
}
Camera: IRenderedNode & {
type: 'camera'
- config?: IBlueprintCameraConfigJSON
+ config?: Serialized
/** The maximum distance this node travels away from the root entity while animating. */
max_distance: number
}
Locator: IRenderedNode & {
type: 'locator'
+ config?: Serialized
/** The maximum distance this node travels away from the root entity while animating. */
max_distance: number
- config?: IBlueprintLocatorConfigJSON
}
TextDisplay: IRenderedNode & {
type: 'text_display'
@@ -137,7 +139,7 @@ export interface IRenderedNodes {
* The base scale of the bone, used to offset any rescaling done to the bone's model due to exceeding the 3x3x3 model size limit.
*/
base_scale: number
- config?: IBlueprintTextDisplayConfigJSON
+ config?: Serialized
}
ItemDisplay: IRenderedNode & {
type: 'item_display'
@@ -147,7 +149,7 @@ export interface IRenderedNodes {
* The base scale of the bone, used to offset any rescaling done to the bone's model due to exceeding the 3x3x3 model size limit.
*/
base_scale: number
- config?: IBlueprintBoneConfigJSON
+ config?: Serialized
}
BlockDisplay: IRenderedNode & {
type: 'block_display'
@@ -156,7 +158,7 @@ export interface IRenderedNodes {
* The base scale of the bone, used to offset any rescaling done to the bone's model due to exceeding the 3x3x3 model size limit.
*/
base_scale: number
- config?: IBlueprintBoneConfigJSON
+ config?: Serialized
}
}
@@ -391,11 +393,11 @@ function renderGroup(
renderCamera(node as ICamera, rig)
break
}
- case node instanceof VanillaItemDisplay: {
+ case node instanceof ItemDisplay: {
renderItemDisplay(node, rig)
break
}
- case node instanceof VanillaBlockDisplay: {
+ case node instanceof BlockDisplay: {
renderBlockDisplay(node, rig)
break
}
@@ -443,7 +445,7 @@ function renderGroup(
rig.nodes[group.uuid] = renderedBone
}
-function renderItemDisplay(display: VanillaItemDisplay, rig: IRenderedRig) {
+function renderItemDisplay(display: ItemDisplay, rig: IRenderedRig) {
if (!display.export) return
const parentId = display.parent instanceof Group ? display.parent.uuid : undefined
@@ -472,7 +474,7 @@ function renderItemDisplay(display: VanillaItemDisplay, rig: IRenderedRig) {
rig.nodes[display.uuid] = renderedBone
}
-function renderBlockDisplay(display: VanillaBlockDisplay, rig: IRenderedRig) {
+function renderBlockDisplay(display: BlockDisplay, rig: IRenderedRig) {
if (!display.export) return
const parentId = display.parent instanceof Group ? display.parent.uuid : undefined
@@ -655,14 +657,14 @@ export function hashRig(rig: IRenderedRig) {
hash.update(';' + JSON.stringify(model) || '')
if (!node.configs) break // Skip if there are no configs
if (node.configs.default) {
- const defaultConfig = BoneConfig.fromJSON(node.configs.default)
+ const defaultConfig = new CommonDisplayConfig().fromJSON(node.configs.default)
if (!defaultConfig.isDefault()) {
hash.update('defaultconfig;')
hash.update(defaultConfig.toNBT().toString())
}
}
for (const [variantName, config] of Object.entries(node.configs.variants)) {
- const variantConfig = BoneConfig.fromJSON(config)
+ const variantConfig = new CommonDisplayConfig().fromJSON(config)
if (!variantConfig.isDefault()) {
hash.update('variantconfig;')
hash.update(variantName)
@@ -751,11 +753,11 @@ export function renderRig(modelExportFolder: string, textureExportFolder: string
renderCamera(node as ICamera, rig)
break
}
- case node instanceof VanillaItemDisplay: {
+ case node instanceof ItemDisplay: {
renderItemDisplay(node, rig)
break
}
- case node instanceof VanillaBlockDisplay: {
+ case node instanceof BlockDisplay: {
renderBlockDisplay(node, rig)
break
}
@@ -784,4 +786,4 @@ export function renderRig(modelExportFolder: string, textureExportFolder: string
console.timeEnd('Rendering rig took')
console.log('Rendered rig:', rig)
return rig
-}
+}
\ No newline at end of file
diff --git a/src/systems/util.ts b/src/systems/util.ts
index 6cf5e43f..7f26583f 100644
--- a/src/systems/util.ts
+++ b/src/systems/util.ts
@@ -1,10 +1,9 @@
-import { NbtCompound, NbtFloat, NbtList } from 'deepslate/lib/nbt'
import {
- AsyncZipOptions,
- AsyncZippable,
unzip as cbUnzip,
zip as cbZip,
type AsyncUnzipOptions,
+ type AsyncZipOptions,
+ type AsyncZippable,
type Unzipped,
} from 'fflate/browser'
import { roundTo } from '../util/misc'
@@ -44,19 +43,6 @@ export function replacePathPart(path: string, oldPart: string, newPart: string)
.join(PathModule.sep)
}
-/**
- * Returns a new object with the keys sorted alphabetically
- */
-export function sortObjectKeys>(obj: T): T {
- const sorted: Record = {}
- Object.keys(obj)
- .sort()
- .forEach(key => {
- sorted[key] = obj[key]
- })
- return sorted as T
-}
-
export function debounce(func: () => void, timeout = 300) {
let timer: NodeJS.Timeout
return () => {
diff --git a/src/ui/animatedJavaBarItem.ts b/src/ui/animatedJavaBarItem.ts
new file mode 100644
index 00000000..59e4a61e
--- /dev/null
+++ b/src/ui/animatedJavaBarItem.ts
@@ -0,0 +1,118 @@
+import { cleanupExportedFiles } from '@aj/systems/cleaner'
+import { exportProject } from '@aj/systems/exporter'
+import AnimatedJavaIcon from '@assets/icons/animated_java_icon.svg'
+import { BLUEPRINT_FORMAT } from '../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../constants'
+import { createAction, createBarMenu } from '../util/moddingTools'
+import { translate } from '../util/translation'
+import { openAboutDialog } from './dialogs/about'
+import { openBlueprintSettingsDialog } from './dialogs/blueprint-settings'
+import { openChangelogDialog } from './dialogs/changelog'
+
+function createIconImg() {
+ const img = document.createElement('img')
+ Object.assign(img, {
+ src: AnimatedJavaIcon,
+ width: 16,
+ height: 16,
+ })
+ Object.assign(img.style, {
+ position: 'relative',
+ top: '2px',
+ borderRadius: '2px',
+ marginRight: '6px',
+ boxShadow: '1px 1px 1px #000000aa',
+ })
+ return img
+}
+const MENU_ID = `${PACKAGE.name}:menu` as `animated_java:menu`
+const BLOCKBENCH_MENU_BAR = document.querySelector('#menu_bar')!
+export const MENU = createBarMenu(MENU_ID, [], () => Format === BLUEPRINT_FORMAT) as BarMenu & {
+ label: HTMLDivElement
+}
+MENU.label.style.display = 'inline-block'
+MENU.label.innerHTML = 'Animated Java'
+MENU.label.prepend(createIconImg())
+BLOCKBENCH_MENU_BAR.appendChild(MENU.label)
+
+export const OPEN_ABOUT_ACTION = createAction(`${PACKAGE.name}:about`, {
+ icon: 'info',
+ category: 'animated_java',
+ name: translate('action.open_about.name'),
+ click() {
+ openAboutDialog()
+ },
+ menu_path: MENU.id,
+})
+
+export const OPEN_DOCUMENTATION_ACTION = createAction(`${PACKAGE.name}:documentation`, {
+ icon: 'find_in_page',
+ category: 'animated_java',
+ name: translate('action.open_documentation.name'),
+ click() {
+ Blockbench.openLink('https://animated-java.dev/docs')
+ },
+ menu_path: MENU.id,
+})
+
+export const OPEN_CHANGELOG_ACTION = createAction(`${PACKAGE.name}:changelog`, {
+ icon: 'history',
+ category: 'animated_java',
+ name: translate('action.open_changelog.name'),
+ click() {
+ openChangelogDialog()
+ },
+ menu_path: MENU.id,
+})
+
+MENU.structure.push(new MenuSeparator())
+
+export const OPEN_BLUEPRINT_SETTINGS_ACTION = createAction(`${PACKAGE.name}:blueprint_settings`, {
+ icon: 'settings',
+ category: 'animated_java',
+ name: translate('action.open_blueprint_settings.name'),
+ condition() {
+ return Format === BLUEPRINT_FORMAT
+ },
+ click() {
+ openBlueprintSettingsDialog()
+ },
+ menu_path: MENU.id,
+})
+
+MenuBar.menus[MENU_ID].structure.push({
+ id: `${PACKAGE.name}:extract-open`,
+ name: translate('action.extract.name'),
+ icon: 'fa-trash-can',
+ searchable: false,
+ children: [],
+ condition() {
+ return Format === BLUEPRINT_FORMAT
+ },
+})
+
+export const EXTRACT_ACTION = createAction(`${PACKAGE.name}:extract`, {
+ icon: 'fa-trash-can',
+ category: `${PACKAGE.name}`,
+ name: translate('action.extract.confirm'),
+ condition() {
+ return Format === BLUEPRINT_FORMAT
+ },
+ click() {
+ void cleanupExportedFiles()
+ },
+ menu_path: MENU_ID + `.${PACKAGE.name}:extract-open`,
+})
+
+export const EXPORT_ACTION = createAction(`${PACKAGE.name}:export`, {
+ icon: 'insert_drive_file',
+ category: 'animated_java',
+ name: translate('action.export.name'),
+ condition() {
+ return Format === BLUEPRINT_FORMAT
+ },
+ click() {
+ void exportProject()
+ },
+ menu_path: MENU.id,
+})
diff --git a/src/components/about.svelte b/src/ui/dialogs/about/about.svelte
similarity index 94%
rename from src/components/about.svelte
rename to src/ui/dialogs/about/about.svelte
index 4e76f816..72b462fa 100644
--- a/src/components/about.svelte
+++ b/src/ui/dialogs/about/about.svelte
@@ -1,7 +1,7 @@
-
diff --git a/src/interface/dialog/about.ts b/src/ui/dialogs/about/index.ts
similarity index 57%
rename from src/interface/dialog/about.ts
rename to src/ui/dialogs/about/index.ts
index 050186e3..dfc46da4 100644
--- a/src/interface/dialog/about.ts
+++ b/src/ui/dialogs/about/index.ts
@@ -1,7 +1,7 @@
-import { PACKAGE } from '../../constants'
-import AboutSvelte from '../../components/about.svelte'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
+import { PACKAGE } from '../../../constants'
+import { SvelteDialog } from '../../../util/svelteDialog'
+import { translate } from '../../../util/translation'
+import AboutSvelte from './about.svelte'
export function openAboutDialog() {
new SvelteDialog({
diff --git a/src/components/animationProperties.svelte b/src/ui/dialogs/animation-properties/animationProperties.svelte
similarity index 63%
rename from src/components/animationProperties.svelte
rename to src/ui/dialogs/animation-properties/animationProperties.svelte
index 35fe7b57..ac23e948 100644
--- a/src/components/animationProperties.svelte
+++ b/src/ui/dialogs/animation-properties/animationProperties.svelte
@@ -1,34 +1,30 @@
-
-
-
- {#if pluginModeEnabled}
+ {#if IS_PLUGIN_MODE}
@@ -87,7 +85,7 @@
label={translate('dialog.bone_config.custom_name.title')}
tooltip={translate('dialog.bone_config.custom_name.description')}
bind:value={customName}
- defaultValue={BoneConfig.prototype.customName}
+ defaultValue={CommonDisplayConfig.prototype.customName}
valueChecker={customNameChecker}
/>
@@ -95,14 +93,14 @@
label={translate('dialog.bone_config.custom_name_visible.title')}
tooltip={translate('dialog.bone_config.custom_name_visible.description')}
bind:checked={customNameVisible}
- defaultValue={BoneConfig.prototype.customNameVisible}
+ defaultValue={CommonDisplayConfig.prototype.customNameVisible}
/>
@@ -124,7 +122,7 @@
label={translate('dialog.bone_config.shadow_strength.title')}
tooltip={translate('dialog.bone_config.shadow_strength.description')}
bind:value={shadowStrength}
- defaultValue={BoneConfig.prototype.shadowStrength}
+ defaultValue={CommonDisplayConfig.prototype.shadowStrength}
min={0}
/>
@@ -132,14 +130,14 @@
label={translate('dialog.bone_config.use_custom_brightness.title')}
tooltip={translate('dialog.bone_config.use_custom_brightness.description')}
bind:checked={overrideBrightness}
- defaultValue={BoneConfig.prototype.overrideBrightness}
+ defaultValue={CommonDisplayConfig.prototype.overrideBrightness}
/>
@@ -148,14 +146,14 @@
label={translate('dialog.bone_config.invisible.title')}
tooltip={translate('dialog.bone_config.invisible.description')}
bind:checked={invisible}
- defaultValue={BoneConfig.prototype.invisible}
+ defaultValue={CommonDisplayConfig.prototype.invisible}
/>
{:else}
{#if $useNBT}
@@ -166,7 +164,7 @@
label={translate('dialog.bone_config.nbt.title')}
tooltip={translate('dialog.bone_config.nbt.description')}
bind:value={nbt}
- defaultValue={BoneConfig.prototype.nbt}
+ defaultValue={CommonDisplayConfig.prototype.nbt}
valueChecker={nbtChecker}
/>
{:else}
@@ -174,7 +172,7 @@
label={translate('dialog.bone_config.custom_name.title')}
tooltip={translate('dialog.bone_config.custom_name.description')}
bind:value={customName}
- defaultValue={BoneConfig.prototype.customName}
+ defaultValue={CommonDisplayConfig.prototype.customName}
valueChecker={customNameChecker}
/>
@@ -182,14 +180,14 @@
label={translate('dialog.bone_config.custom_name_visible.title')}
tooltip={translate('dialog.bone_config.custom_name_visible.description')}
bind:checked={customNameVisible}
- defaultValue={BoneConfig.prototype.customNameVisible}
+ defaultValue={CommonDisplayConfig.prototype.customNameVisible}
/>
@@ -197,14 +195,14 @@
label={translate('dialog.bone_config.glowing.title')}
tooltip={translate('dialog.bone_config.glowing.description')}
bind:checked={glowing}
- defaultValue={BoneConfig.prototype.glowing}
+ defaultValue={CommonDisplayConfig.prototype.glowing}
/>
{#if $overrideGlowColor}
@@ -219,7 +217,7 @@
label={translate('dialog.bone_config.shadow_radius.title')}
tooltip={translate('dialog.bone_config.shadow_radius.description')}
bind:value={shadowRadius}
- defaultValue={BoneConfig.prototype.shadowRadius}
+ defaultValue={CommonDisplayConfig.prototype.shadowRadius}
min={0}
max={15}
/>
@@ -228,7 +226,7 @@
label={translate('dialog.bone_config.shadow_strength.title')}
tooltip={translate('dialog.bone_config.shadow_strength.description')}
bind:value={shadowStrength}
- defaultValue={BoneConfig.prototype.shadowStrength}
+ defaultValue={CommonDisplayConfig.prototype.shadowStrength}
min={0}
max={15}
/>
@@ -237,7 +235,7 @@
label={translate('dialog.bone_config.override_brightness.title')}
tooltip={translate('dialog.bone_config.override_brightness.description')}
bind:checked={overrideBrightness}
- defaultValue={BoneConfig.prototype.overrideBrightness}
+ defaultValue={CommonDisplayConfig.prototype.overrideBrightness}
/>
{#if $overrideBrightness}
@@ -245,7 +243,7 @@
label={translate('dialog.bone_config.brightness_override.title')}
tooltip={translate('dialog.bone_config.brightness_override.description')}
bind:value={brightnessOverride}
- defaultValue={BoneConfig.prototype.brightnessOverride}
+ defaultValue={CommonDisplayConfig.prototype.brightness}
min={0}
max={15}
/>
diff --git a/src/ui/dialogs/blueprint-settings/index.ts b/src/ui/dialogs/blueprint-settings/index.ts
new file mode 100644
index 00000000..3bfb6fb6
--- /dev/null
+++ b/src/ui/dialogs/blueprint-settings/index.ts
@@ -0,0 +1,118 @@
+import { updateBoundingBox } from '../../../blockbench-additions/model-formats/ajblueprint'
+import { defaultValues } from '../../../blueprintSettings'
+import { PACKAGE } from '../../../constants'
+import { makeSyncable, Syncable } from '../../../util/stores'
+import { SvelteSidebarDialog } from '../../../util/svelteDialog'
+import { translate } from '../../../util/translation'
+import {
+ EXPORT_ACTION,
+ OPEN_ABOUT_ACTION,
+ OPEN_DOCUMENTATION_ACTION,
+} from '../../animatedJavaBarItem'
+import Datapack from './pages/datapack.svelte'
+import Export from './pages/export.svelte'
+import General from './pages/general.svelte'
+import Plugin from './pages/plugin.svelte'
+import Resourcepack from './pages/resourcepack.svelte'
+import RequiredAsteriskInform from './requiredAsteriskInform.svelte'
+
+interface AdditionalSettings {
+ project_name: string
+ texture_width: number
+ texture_height: number
+}
+
+export type BlueprintSettings = typeof defaultValues & AdditionalSettings
+export type ValuableBlueprintSettings = {
+ [Key in keyof BlueprintSettings]: Syncable
+}
+
+export function openBlueprintSettingsDialog() {
+ if (!Project) return
+
+ const settings: ValuableBlueprintSettings = makeSyncable({
+ ...defaultValues,
+ project_name: Project.name,
+ texture_width: Project.texture_width,
+ texture_height: Project.texture_height,
+ })
+
+ return new SvelteSidebarDialog({
+ id: `${PACKAGE.name}:blueprintSettingsDialog`,
+ title: translate('dialog.blueprint_settings.title'),
+ sidebar: {
+ actions: [OPEN_DOCUMENTATION_ACTION, OPEN_ABOUT_ACTION, EXPORT_ACTION],
+ pages: {
+ general: {
+ icon: 'settings',
+ label: 'General',
+ component: General,
+ props: { settings },
+ },
+ export: {
+ icon: 'save',
+ label: 'Export',
+ component: Export,
+ props: { settings },
+ condition() {
+ return settings.environment.get() === 'vanilla'
+ },
+ },
+ datapack: {
+ icon: 'folder',
+ label: 'Data Pack',
+ component: Datapack,
+ props: { settings },
+ condition() {
+ return settings.environment.get() === 'vanilla'
+ },
+ },
+ resourcepack: {
+ icon: 'folder',
+ label: 'Resource Pack',
+ component: Resourcepack,
+ props: { settings },
+ condition() {
+ return settings.environment.get() === 'vanilla'
+ },
+ },
+ plugin: {
+ icon: 'extension',
+ label: 'Plugin',
+ component: Plugin,
+ props: { settings },
+ condition() {
+ return settings.environment.get() === 'plugin'
+ },
+ },
+ },
+ },
+ width: 1024,
+ preventKeybinds: true,
+ onOpen() {
+ const buttonBar = $(
+ "dialog[id='animated_java:blueprintSettingsDialog'] .dialog_bar.button_bar"
+ ).first()
+ const anchor = document.createComment('asterisk-mount') as unknown as HTMLElement
+ buttonBar.prepend(anchor)
+ new RequiredAsteriskInform({
+ target: buttonBar[0],
+ anchor,
+ })
+ const dialogContent = $(
+ '.dialog[id="animated_java:blueprintSettingsDialog"] .dialog_content'
+ )[0]
+ if (dialogContent) {
+ dialogContent.style.overflowY = 'scroll'
+ dialogContent.style.overflowX = 'hidden'
+ dialogContent.style.maxHeight = '60vh'
+ dialogContent.style.minHeight = '60vh'
+ dialogContent.style.maskImage =
+ 'linear-gradient(to bottom, black calc(100% - 16px), transparent 100%)'
+ }
+ },
+ onConfirm() {
+ updateBoundingBox()
+ },
+ }).show()
+}
diff --git a/src/ui/dialogs/blueprint-settings/pages/datapack.svelte b/src/ui/dialogs/blueprint-settings/pages/datapack.svelte
new file mode 100644
index 00000000..d8803002
--- /dev/null
+++ b/src/ui/dialogs/blueprint-settings/pages/datapack.svelte
@@ -0,0 +1,214 @@
+
+
+
+
+{#if $EXPORT_MODE !== 'none'}
+ {#if $EXPORT_MODE === 'folder'}
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+{/if}
diff --git a/src/ui/dialogs/blueprint-settings/pages/export.svelte b/src/ui/dialogs/blueprint-settings/pages/export.svelte
new file mode 100644
index 00000000..fb71f963
--- /dev/null
+++ b/src/ui/dialogs/blueprint-settings/pages/export.svelte
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
diff --git a/src/ui/dialogs/blueprint-settings/pages/general.svelte b/src/ui/dialogs/blueprint-settings/pages/general.svelte
new file mode 100644
index 00000000..fe4036f6
--- /dev/null
+++ b/src/ui/dialogs/blueprint-settings/pages/general.svelte
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/dialogs/blueprint-settings/pages/plugin.svelte b/src/ui/dialogs/blueprint-settings/pages/plugin.svelte
new file mode 100644
index 00000000..9ca65fa1
--- /dev/null
+++ b/src/ui/dialogs/blueprint-settings/pages/plugin.svelte
@@ -0,0 +1,45 @@
+
+
+
diff --git a/src/ui/dialogs/blueprint-settings/pages/resourcepack.svelte b/src/ui/dialogs/blueprint-settings/pages/resourcepack.svelte
new file mode 100644
index 00000000..97004374
--- /dev/null
+++ b/src/ui/dialogs/blueprint-settings/pages/resourcepack.svelte
@@ -0,0 +1,156 @@
+
+
+
+
+{#if $EXPORT_MODE !== 'none'}
+ {#if $EXPORT_MODE === 'folder'}
+
+ {:else if $EXPORT_MODE === 'zip'}
+
+ {/if}
+{/if}
+
+
+
+{#if compareVersions('1.21.4', $TARGET_MINECRAFT_VERSION)}
+
+{/if}
+
+
diff --git a/src/ui/dialogs/blueprint-settings/requiredAsteriskInform.svelte b/src/ui/dialogs/blueprint-settings/requiredAsteriskInform.svelte
new file mode 100644
index 00000000..59025adf
--- /dev/null
+++ b/src/ui/dialogs/blueprint-settings/requiredAsteriskInform.svelte
@@ -0,0 +1,12 @@
+* = Required
+
+
diff --git a/src/ui/dialogs/bone-config/boneConfig.svelte b/src/ui/dialogs/bone-config/boneConfig.svelte
new file mode 100644
index 00000000..66050fde
--- /dev/null
+++ b/src/ui/dialogs/bone-config/boneConfig.svelte
@@ -0,0 +1,74 @@
+
+
+Hello, World!
+
+
diff --git a/src/ui/dialogs/bone-config/index.ts b/src/ui/dialogs/bone-config/index.ts
new file mode 100644
index 00000000..fb136efb
--- /dev/null
+++ b/src/ui/dialogs/bone-config/index.ts
@@ -0,0 +1,36 @@
+import { CommonDisplayConfig } from '@aj/systems/node-configs'
+import { BLUEPRINT_FORMAT } from '../../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../../constants'
+import { createAction } from '../../../util/moddingTools'
+import { SvelteDialog } from '../../../util/svelteDialog'
+import { translate } from '../../../util/translation'
+import BoneConfigDialogSvelteComponent from './boneConfig.svelte'
+
+export function openBoneConfigDialog(bone: Group) {
+ // Blockbench's JSON stringifier doesn't handle custom toJSON functions, so I'm storing the config JSON in the bone instead of the actual GenericDisplayConfig object
+ const boneConfig = new CommonDisplayConfig().fromJSON(bone.configs.default ?? {})
+
+ new SvelteDialog({
+ id: `${PACKAGE.name}:boneConfig`,
+ title: translate('dialog.bone_config.title'),
+ width: 400,
+ component: BoneConfigDialogSvelteComponent,
+ props: {
+ boneConfig,
+ },
+ preventKeybinds: true,
+ onConfirm() {
+ bone.configs.default = boneConfig.toJSON()
+ },
+ }).show()
+}
+
+export const BONE_CONFIG_ACTION = createAction(`${PACKAGE.name}:bone_config`, {
+ icon: 'settings',
+ name: translate('action.open_bone_config.name'),
+ condition: () => Format === BLUEPRINT_FORMAT,
+ click: () => {
+ if (!Group.first_selected) return
+ openBoneConfigDialog(Group.first_selected)
+ },
+})
diff --git a/src/ui/dialogs/bone-config/nodeConfigOption.svelte b/src/ui/dialogs/bone-config/nodeConfigOption.svelte
new file mode 100644
index 00000000..f52429cb
--- /dev/null
+++ b/src/ui/dialogs/bone-config/nodeConfigOption.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/src/components/changelogDialog.svelte b/src/ui/dialogs/changelog/changelogDialog.svelte
similarity index 93%
rename from src/components/changelogDialog.svelte
rename to src/ui/dialogs/changelog/changelogDialog.svelte
index 128cdbfe..f7b94902 100644
--- a/src/components/changelogDialog.svelte
+++ b/src/ui/dialogs/changelog/changelogDialog.svelte
@@ -1,6 +1,6 @@
-
diff --git a/src/interface/dialog/exportProgress.ts b/src/ui/dialogs/export-progress/index.ts
similarity index 63%
rename from src/interface/dialog/exportProgress.ts
rename to src/ui/dialogs/export-progress/index.ts
index 38fa54da..ed8c1565 100644
--- a/src/interface/dialog/exportProgress.ts
+++ b/src/ui/dialogs/export-progress/index.ts
@@ -1,12 +1,12 @@
-import ExportProgressDialogSvelteComponent from '../../components/exportProgressDialog.svelte'
-import { PACKAGE } from '../../constants'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
+import { PACKAGE } from '../../../constants'
+import { Syncable } from '../../../util/stores'
+import { SvelteDialog } from '../../../util/svelteDialog'
+import { translate } from '../../../util/translation'
+import ExportProgressDialogSvelteComponent from './exportProgressDialog.svelte'
-export const PROGRESS = new Valuable(0)
-export const MAX_PROGRESS = new Valuable(1)
-export const PROGRESS_DESCRIPTION = new Valuable('')
+export const PROGRESS = new Syncable(0)
+export const MAX_PROGRESS = new Syncable(1)
+export const PROGRESS_DESCRIPTION = new Syncable('')
export function openExportProgressDialog(debug?: boolean) {
PROGRESS.set(0)
diff --git a/src/components/importAJModelLoaderDialog.svelte b/src/ui/dialogs/import-ajmodel/importAJModelLoaderDialog.svelte
similarity index 76%
rename from src/components/importAJModelLoaderDialog.svelte
rename to src/ui/dialogs/import-ajmodel/importAJModelLoaderDialog.svelte
index 3c55b1ec..9558a08f 100644
--- a/src/components/importAJModelLoaderDialog.svelte
+++ b/src/ui/dialogs/import-ajmodel/importAJModelLoaderDialog.svelte
@@ -1,12 +1,9 @@
-
-
-
- {#if pluginModeEnabled}
+ {#if IS_PLUGIN_MODE}
@@ -87,7 +85,7 @@
label={translate('dialog.bone_config.custom_name.title')}
tooltip={translate('dialog.bone_config.custom_name.description')}
bind:value={customName}
- defaultValue={BoneConfig.prototype.customName}
+ defaultValue={CommonDisplayConfig.prototype.customName}
valueChecker={customNameChecker}
/>
@@ -95,14 +93,14 @@
label={translate('dialog.bone_config.custom_name_visible.title')}
tooltip={translate('dialog.bone_config.custom_name_visible.description')}
bind:checked={customNameVisible}
- defaultValue={BoneConfig.prototype.customNameVisible}
+ defaultValue={CommonDisplayConfig.prototype.customNameVisible}
/>
@@ -124,7 +122,7 @@
label={translate('dialog.bone_config.shadow_strength.title')}
tooltip={translate('dialog.bone_config.shadow_strength.description')}
bind:value={shadowStrength}
- defaultValue={BoneConfig.prototype.shadowStrength}
+ defaultValue={CommonDisplayConfig.prototype.shadowStrength}
min={0}
/>
@@ -132,14 +130,14 @@
label={translate('dialog.bone_config.use_custom_brightness.title')}
tooltip={translate('dialog.bone_config.use_custom_brightness.description')}
bind:checked={overrideBrightness}
- defaultValue={BoneConfig.prototype.overrideBrightness}
+ defaultValue={CommonDisplayConfig.prototype.overrideBrightness}
/>
@@ -148,14 +146,14 @@
label={translate('dialog.bone_config.invisible.title')}
tooltip={translate('dialog.bone_config.invisible.description')}
bind:checked={invisible}
- defaultValue={BoneConfig.prototype.invisible}
+ defaultValue={CommonDisplayConfig.prototype.invisible}
/>
{:else}
{#if $useNBT}
@@ -166,7 +164,7 @@
label={translate('dialog.bone_config.nbt.title')}
tooltip={translate('dialog.bone_config.nbt.description')}
bind:value={nbt}
- defaultValue={BoneConfig.prototype.nbt}
+ defaultValue={CommonDisplayConfig.prototype.nbt}
valueChecker={nbtChecker}
/>
{:else}
@@ -174,7 +172,7 @@
label={translate('dialog.bone_config.custom_name.title')}
tooltip={translate('dialog.bone_config.custom_name.description')}
bind:value={customName}
- defaultValue={BoneConfig.prototype.customName}
+ defaultValue={CommonDisplayConfig.prototype.customName}
valueChecker={customNameChecker}
/>
@@ -182,14 +180,14 @@
label={translate('dialog.bone_config.custom_name_visible.title')}
tooltip={translate('dialog.bone_config.custom_name_visible.description')}
bind:checked={customNameVisible}
- defaultValue={BoneConfig.prototype.customNameVisible}
+ defaultValue={CommonDisplayConfig.prototype.customNameVisible}
/>
@@ -197,14 +195,14 @@
label={translate('dialog.bone_config.glowing.title')}
tooltip={translate('dialog.bone_config.glowing.description')}
bind:checked={glowing}
- defaultValue={BoneConfig.prototype.glowing}
+ defaultValue={CommonDisplayConfig.prototype.glowing}
/>
{#if $overrideGlowColor}
@@ -219,7 +217,7 @@
label={translate('dialog.bone_config.shadow_radius.title')}
tooltip={translate('dialog.bone_config.shadow_radius.description')}
bind:value={shadowRadius}
- defaultValue={BoneConfig.prototype.shadowRadius}
+ defaultValue={CommonDisplayConfig.prototype.shadowRadius}
min={0}
max={15}
/>
@@ -228,7 +226,7 @@
label={translate('dialog.bone_config.shadow_strength.title')}
tooltip={translate('dialog.bone_config.shadow_strength.description')}
bind:value={shadowStrength}
- defaultValue={BoneConfig.prototype.shadowStrength}
+ defaultValue={CommonDisplayConfig.prototype.shadowStrength}
min={0}
max={15}
/>
@@ -237,7 +235,7 @@
label={translate('dialog.bone_config.override_brightness.title')}
tooltip={translate('dialog.bone_config.override_brightness.description')}
bind:checked={overrideBrightness}
- defaultValue={BoneConfig.prototype.overrideBrightness}
+ defaultValue={CommonDisplayConfig.prototype.overrideBrightness}
/>
{#if $overrideBrightness}
@@ -245,7 +243,7 @@
label={translate('dialog.bone_config.brightness_override.title')}
tooltip={translate('dialog.bone_config.brightness_override.description')}
bind:value={brightnessOverride}
- defaultValue={BoneConfig.prototype.brightnessOverride}
+ defaultValue={CommonDisplayConfig.prototype.brightness}
min={0}
max={15}
/>
diff --git a/src/interface/dialog/locatorConfig.ts b/src/ui/dialogs/locator-config/index.ts
similarity index 58%
rename from src/interface/dialog/locatorConfig.ts
rename to src/ui/dialogs/locator-config/index.ts
index 5e75e6a2..8b0a8161 100644
--- a/src/interface/dialog/locatorConfig.ts
+++ b/src/ui/dialogs/locator-config/index.ts
@@ -1,21 +1,22 @@
-import { BLUEPRINT_FORMAT } from '../../blueprintFormat'
-import LocatorConfigDialog from '../../components/locatorConfigDialog.svelte'
-import { PACKAGE } from '../../constants'
-import { LocatorConfig } from '../../nodeConfigs'
-import { createAction } from '../../util/moddingTools'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
+import { LocatorConfig } from '@aj/systems/node-configs'
+import { BLUEPRINT_FORMAT } from '../../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../../constants'
+import { createAction } from '../../../util/moddingTools'
+import { Syncable } from '../../../util/stores'
+import { SvelteDialog } from '../../../util/svelteDialog'
+import { translate } from '../../../util/translation'
+import LocatorConfigDialog from './locatorConfigDialog.svelte'
export function openLocatorConfigDialog(locator: Locator) {
// Blockbench's JSON stringifier doesn't handle custom toJSON functions, so I'm storing the config JSON in the locator instead of the actual LocatorConfig object
- const locatorConfig = LocatorConfig.fromJSON((locator.config ??= new LocatorConfig().toJSON()))
+ const locatorConfig = new LocatorConfig().fromJSON(
+ (locator.config ??= new LocatorConfig().toJSON())
+ )
- const useEntity = new Valuable(locatorConfig.useEntity)
- const entityType = new Valuable(locatorConfig.entityType)
- const syncPassengerRotation = new Valuable(locatorConfig.syncPassengerRotation)
- const summonCommands = new Valuable(locatorConfig.summonCommands)
- const tickingCommands = new Valuable(locatorConfig.tickingCommands)
+ const useEntity = new Syncable(locatorConfig.useEntity!)
+ const entityType = new Syncable(locatorConfig.entityType!)
+ const summonCommands = new Syncable(locatorConfig.summonCommands!)
+ const tickingCommands = new Syncable(locatorConfig.tickingCommands!)
new SvelteDialog({
id: `${PACKAGE.name}:locatorConfig`,
diff --git a/src/components/locatorConfigDialog.svelte b/src/ui/dialogs/locator-config/locatorConfigDialog.svelte
similarity index 64%
rename from src/components/locatorConfigDialog.svelte
rename to src/ui/dialogs/locator-config/locatorConfigDialog.svelte
index 851a7763..cb70f934 100644
--- a/src/components/locatorConfigDialog.svelte
+++ b/src/ui/dialogs/locator-config/locatorConfigDialog.svelte
@@ -1,18 +1,18 @@
- {#if pluginModeEnabled}
+ {#if IS_PLUGIN_MODE}
{#each translate('dialog.locator_config.plugin_mode_warning').split('\n') as line}
{line}
{/each}
@@ -57,13 +57,6 @@
defaultValue="minecraft:item_display"
/>
-
-
, value:
return this
}
-export const TEXT_DISPLAY_SEE_THROUGH_TOGGLE = new Toggle(
- `${PACKAGE.name}:textDisplaySeeThroughToggle`,
- {
- name: translate('tool.text_display.see_through.title'),
- icon: 'check_box_outline_blank',
- description: translate('tool.text_display.see_through.description'),
- condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
- click() {
- //
- },
- onChange() {
- if (!Project) return
- const scope = TEXT_DISPLAY_SEE_THROUGH_TOGGLE
- scope.setIcon(scope.value ? 'check_box' : 'check_box_outline_blank')
- const selected = TextDisplay.selected[0]
- if (!selected) return
- if (selected.seeThrough === TEXT_DISPLAY_SEE_THROUGH_TOGGLE.value) return
- selected.seeThrough = TEXT_DISPLAY_SEE_THROUGH_TOGGLE.value
- Project!.saved = false
- },
- }
-)
-TEXT_DISPLAY_SEE_THROUGH_TOGGLE.set = function (value) {
- if (this.value === value) return this
- this.click()
- return this
-}
+export const TEXT_DISPLAY_CONFIG_ACTION = createAction(`${PACKAGE.name}:text_display_config`, {
+ icon: 'settings',
+ name: translate('action.open_text_display_config.name'),
+ condition: () => isCurrentFormat(),
+ click: () => {
+ if (TextDisplay.selected.length === 0) return
+ openBoneConfigDialog(TextDisplay.selected[0])
+ },
+})
diff --git a/src/components/textDisplayConfigDialog.svelte b/src/ui/dialogs/text-display-config/textDisplayConfigDialog.svelte
similarity index 79%
rename from src/components/textDisplayConfigDialog.svelte
rename to src/ui/dialogs/text-display-config/textDisplayConfigDialog.svelte
index 4925302f..ba50c5a6 100644
--- a/src/components/textDisplayConfigDialog.svelte
+++ b/src/ui/dialogs/text-display-config/textDisplayConfigDialog.svelte
@@ -1,31 +1,29 @@
-
-
- {#if pluginModeEnabled}
+ {#if IS_PLUGIN_MODE}
@@ -117,7 +115,7 @@
@@ -173,7 +171,7 @@
red button?`,
+ `Skill Issue.`,
+ `You have how many elements!?`,
+ `I'll export successfully some day!`,
+ `When I grow up, I wanna be just like Blender!`,
+ `Should'a seen that one comming...`,
+ `It's all Jannis' fault! :(`,
+ `Snaviewavie did an oopsie poopsie x3`,
+ `We to a little trolling`,
+ `execute run execute run execute run execute run say This is fine.`,
+ `This is why we can't have nice things. :(`,
+ `Have you tried turning it off and on again?`,
+ `What if I put my command block next to yours? Haha just kidding... Unless?`,
+ `If at first you don't succeed, try, try again!`,
+ `B:01010111 01100101 00100000 01100100 01101111 00100000 01100001 00100000 01101100 01101001 01110100 01110100 01101100 01100101 00100000 01110100 01110010 01101111 01101100 01101100 01101001 01101110 01100111`,
+ `
+
SnaveSutit would like to know your location
+
+ Allow
+ Deny
+
+
`,
+ `I've decided to stop working for today. Try again tomorrow!`,
+ `Every time you see this error message, a developer vanishes in a puff of binary.`,
+ `"Flavor Text"? I've never tasted text before...`,
+ `( ͡° ͜ʖ ͡°)`,
+ `That's a nice model you have there, it'd be a shame if something were to happen to it...`,
+ `Some day you'll learn. But until then, I control the cheese`,
+ `Please deposit 5 coins!`,
+ `Click here to find a solution! `,
+ ` `,
+ `Failed to find global 'pandemic'`,
+ `I'm sorry, Dave. I'm afraid I can't do that.`,
+]
+
+export function openUnexpectedErrorDialog(error: Error) {
+ new SvelteDialog({
+ id: `${PACKAGE.name}:unexpectedError`,
+ title: translate('dialog.unexpected_error.title'),
+ width: 600,
+ component: UnexpectedErrorDialog,
+ props: {
+ error,
+ },
+ preventKeybinds: true,
+ buttons: [translate('dialog.unexpected_error.close_button')],
+ }).show()
+}
diff --git a/src/ui/dialogs/unexpected-error/unexpectedErrorDialog.svelte b/src/ui/dialogs/unexpected-error/unexpectedErrorDialog.svelte
new file mode 100644
index 00000000..ed623a09
--- /dev/null
+++ b/src/ui/dialogs/unexpected-error/unexpectedErrorDialog.svelte
@@ -0,0 +1,75 @@
+
+
+
+
+
+
{@html pickRandomFlavorQuote()}
+
+
+
+ {@html translate(
+ 'dialog.unexpected_error.paragraph',
+ 'Discord ',
+ 'Github '
+ )}
+
+
+
+
+
+
+
+
+
diff --git a/src/interface/dialog/variantConfig.ts b/src/ui/dialogs/variant-config/index.ts
similarity index 53%
rename from src/interface/dialog/variantConfig.ts
rename to src/ui/dialogs/variant-config/index.ts
index afcec7dd..dccf0ab4 100644
--- a/src/interface/dialog/variantConfig.ts
+++ b/src/ui/dialogs/variant-config/index.ts
@@ -1,18 +1,18 @@
-import VariantConfigDialogSvelteComponent from '../../components/variantConfigDialog.svelte'
-import { PACKAGE } from '../../constants'
-import { events } from '../../util/events'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
-import { Variant } from '../../variants'
+import EVENTS from '@events'
+import { PACKAGE } from '../../../constants'
+import { Syncable } from '../../../util/stores'
+import { SvelteDialog } from '../../../util/svelteDialog'
+import { translate } from '../../../util/translation'
+import type { Variant } from '../../../variants'
+import VariantConfigDialogSvelteComponent from './variantConfigDialog.svelte'
export function openVariantConfigDialog(variant: Variant) {
- const displayName = new Valuable(variant.displayName)
- const name = new Valuable(variant.name)
- const uuid = new Valuable(variant.uuid)
+ const displayName = new Syncable(variant.displayName)
+ const name = new Syncable(variant.name)
+ const uuid = new Syncable(variant.uuid)
const textureMap = variant.textureMap.copy()
- const generateNameFromDisplayName = new Valuable(variant.generateNameFromDisplayName)
- const excludedNodes = new Valuable(variant.excludedNodes)
+ const generateNameFromDisplayName = new Syncable(variant.generateNameFromDisplayName)
+ const excludedNodes = new Syncable(variant.excludedNodes)
new SvelteDialog({
id: `${PACKAGE.name}:variantConfig`,
@@ -36,7 +36,7 @@ export function openVariantConfigDialog(variant: Variant) {
variant.textureMap = textureMap
variant.generateNameFromDisplayName = generateNameFromDisplayName.get()
variant.excludedNodes = excludedNodes.get()
- events.UPDATE_VARIANT.dispatch(variant)
+ EVENTS.UPDATE_VARIANT.dispatch(variant)
variant.select()
},
}).show()
diff --git a/src/components/variantConfigDialog.svelte b/src/ui/dialogs/variant-config/variantConfigDialog.svelte
similarity index 81%
rename from src/components/variantConfigDialog.svelte
rename to src/ui/dialogs/variant-config/variantConfigDialog.svelte
index 6f61209a..8f19e475 100644
--- a/src/components/variantConfigDialog.svelte
+++ b/src/ui/dialogs/variant-config/variantConfigDialog.svelte
@@ -1,29 +1,26 @@
-
-
@@ -143,7 +140,6 @@
{}}
>
createTextureMapping()}>add
@@ -160,7 +156,7 @@
{#key textureMapUpdated}
- {#each [...textureMap.map.entries()] as entry, index}
+ {#each [...textureMap.map.entries()] as entry, _index}
@@ -172,7 +168,7 @@
on:change={e => selectNewPrimaryTexture(e, entry[0])}
>
- {#each primaryTextures as texture}
+ {#each PRIMARY_TEXTURES as texture}
{texture.name}
@@ -191,7 +187,7 @@
on:change={e => selectNewSecondaryTexture(e, entry[0])}
>
- {#each secondaryTextures as texture}
+ {#each SECONDARY_TEXTURES as texture}
{texture.name}
@@ -217,16 +213,16 @@
tooltip={translate('dialog.variant_config.bone_lists.description')}
availableItemsColumnLable={translate('dialog.variant_config.included_nodes.title')}
availableItemsColumnTooltip={translate(
- 'dialog.variant_config.included_nodes.description',
+ 'dialog.variant_config.included_nodes.description'
)}
includedItemsColumnLable={translate('dialog.variant_config.excluded_nodes.title')}
includedItemsColumnTooltip={translate(
- 'dialog.variant_config.excluded_nodes.description',
+ 'dialog.variant_config.excluded_nodes.description'
)}
swapColumnsButtonTooltip={translate(
- 'dialog.variant_config.swap_columns_button.tooltip',
+ 'dialog.variant_config.swap_columns_button.tooltip'
)}
- availableItems={availableBones}
+ availableItems={AVAILABLE_BONES}
bind:includedItems={excludedNodes}
/>
{/if}
diff --git a/src/ui/index.ts b/src/ui/index.ts
new file mode 100644
index 00000000..63eee854
--- /dev/null
+++ b/src/ui/index.ts
@@ -0,0 +1,28 @@
+// import './dialogs/block-display-config'
+// import './dialogs/item-display-config'
+// import './dialogs/text-display-config'
+// import './dialogs/bone-config'
+import './dialogs/about'
+import './dialogs/animation-properties'
+import './dialogs/changelog'
+import './dialogs/export-progress'
+import './dialogs/import-ajmodel'
+import './dialogs/locator-config'
+import './dialogs/unexpected-error'
+import './dialogs/variant-config'
+
+// import './panels/block-display-element'
+// import './panels/item-display-element'
+// import './panels/text-display-element'
+import './panels/custom-keyframe'
+import './panels/display-panel'
+import './panels/variants'
+
+import './popups/animated-java-loading'
+// FIXME - Temporarily disabled for debugging purposes!
+// import './popups/blueprint-loading'
+import './popups/incompatability'
+import './popups/installed'
+
+import './animatedJavaBarItem'
+import './keyframeEasings'
diff --git a/src/interface/keyframeEasings.ts b/src/ui/keyframeEasings.ts
similarity index 73%
rename from src/interface/keyframeEasings.ts
rename to src/ui/keyframeEasings.ts
index 787d916b..a8b75d69 100644
--- a/src/interface/keyframeEasings.ts
+++ b/src/ui/keyframeEasings.ts
@@ -1,4 +1,4 @@
-import KeyframeEasingsSvelte from '../components/keyframeEasings.svelte'
+import KeyframeEasingsSvelte from '@svelte-components/keyframeEasings.svelte'
import { injectSvelteCompomponentMod } from '../util/injectSvelteComponent'
injectSvelteCompomponentMod({
diff --git a/src/ui/panels/block-display-element/index.ts b/src/ui/panels/block-display-element/index.ts
new file mode 100644
index 00000000..d74a2be3
--- /dev/null
+++ b/src/ui/panels/block-display-element/index.ts
@@ -0,0 +1,10 @@
+import { injectSvelteCompomponentMod } from '../../../util/injectSvelteComponent'
+import BlockDisplayElementPanel from './vanillaBlockDisplayElementPanel.svelte'
+
+injectSvelteCompomponentMod({
+ component: BlockDisplayElementPanel,
+ props: {},
+ elementSelector() {
+ return document.querySelector('#panel_element')
+ },
+})
diff --git a/src/ui/panels/block-display-element/vanillaBlockDisplayElementPanel.svelte b/src/ui/panels/block-display-element/vanillaBlockDisplayElementPanel.svelte
new file mode 100644
index 00000000..84143094
--- /dev/null
+++ b/src/ui/panels/block-display-element/vanillaBlockDisplayElementPanel.svelte
@@ -0,0 +1,71 @@
+
+
+
+ {translate('panel.block_display.title')}
+
+
+
+
+
+ {$error}
+
+
+
diff --git a/src/components/keyframePanels/commandsKeyframePanel.svelte b/src/ui/panels/custom-keyframe/commandsKeyframePanel.svelte
similarity index 71%
rename from src/components/keyframePanels/commandsKeyframePanel.svelte
rename to src/ui/panels/custom-keyframe/commandsKeyframePanel.svelte
index 4e91debe..b4bdfac7 100644
--- a/src/components/keyframePanels/commandsKeyframePanel.svelte
+++ b/src/ui/panels/custom-keyframe/commandsKeyframePanel.svelte
@@ -1,5 +1,4 @@
-
+ } from '../../../blockbench-mods/misc/customKeyframes'
+ import { Syncable } from '../../../util/stores'
+ import { translate } from '../../../util/translation'
-
@@ -43,7 +41,7 @@
@@ -60,7 +58,7 @@
id="repeat_input"
class="dark_bordered tab_target"
type="checkbox"
- bind:checked={$repeat}
+ bind:checked={$REPEAT}
/>
@@ -77,7 +75,7 @@
id="repeat_frequency_input"
class="dark_bordered tab_target"
type="number"
- bind:value={$repeatFrequency}
+ bind:value={$REPEAT_FREQUENCY}
/>
diff --git a/src/components/customKeyframePanel.svelte b/src/ui/panels/custom-keyframe/customKeyframePanel.svelte
similarity index 65%
rename from src/components/customKeyframePanel.svelte
rename to src/ui/panels/custom-keyframe/customKeyframePanel.svelte
index c06b77cb..b58322c3 100644
--- a/src/components/customKeyframePanel.svelte
+++ b/src/ui/panels/custom-keyframe/customKeyframePanel.svelte
@@ -1,22 +1,20 @@
-
+ } from '../../../blockbench-mods/misc/customKeyframes'
+ import { Syncable } from '../../../util/stores'
+ import { translate } from '../../../util/translation'
+ import CommandsKeyframePanel from './commandsKeyframePanel.svelte'
+ import VariantKeyframePanel from './variantKeyframePanel.svelte'
-
@@ -42,7 +40,7 @@
id="execute_condition"
type="text"
class="dark_bordered code keyframe_input tab_target"
- bind:value={$executeCondition}
+ bind:value={$EXECUTE_CONDITION}
/>
diff --git a/src/interface/panel/customKeyframe.ts b/src/ui/panels/custom-keyframe/index.ts
similarity index 72%
rename from src/interface/panel/customKeyframe.ts
rename to src/ui/panels/custom-keyframe/index.ts
index df918b17..dffdf209 100644
--- a/src/interface/panel/customKeyframe.ts
+++ b/src/ui/panels/custom-keyframe/index.ts
@@ -1,10 +1,10 @@
+import EVENTS from '@events'
import type { SvelteComponentDev } from 'svelte/internal'
-import { isCurrentFormat } from '../../blueprintFormat'
-import CustomKeyframePanelSvelteComponent from '../../components/customKeyframePanel.svelte'
-import { CUSTOM_CHANNELS } from '../../mods/customKeyframesMod'
-import { events } from '../../util/events'
-import { injectSvelteCompomponent } from '../../util/injectSvelteComponent'
-import { translate } from '../../util/translation'
+import { isCurrentFormat } from '../../../blockbench-additions/model-formats/ajblueprint'
+import { CUSTOM_CHANNELS } from '../../../blockbench-mods/misc/customKeyframes'
+import { injectSvelteCompomponent } from '../../../util/injectSvelteComponent'
+import { translate } from '../../../util/translation'
+import CustomKeyframePanelSvelteComponent from './customKeyframePanel.svelte'
let currentPanel: SvelteComponentDev | undefined = undefined
@@ -49,6 +49,6 @@ export function injectCustomKeyframePanel(selectedKeyframe: _Keyframe) {
})
}
-events.SELECT_KEYFRAME.subscribe(kf => {
+EVENTS.SELECT_KEYFRAME.subscribe(kf => {
injectCustomKeyframePanel(kf)
})
diff --git a/src/ui/panels/custom-keyframe/variantKeyframePanel.svelte b/src/ui/panels/custom-keyframe/variantKeyframePanel.svelte
new file mode 100644
index 00000000..55ab580e
--- /dev/null
+++ b/src/ui/panels/custom-keyframe/variantKeyframePanel.svelte
@@ -0,0 +1,54 @@
+
+
+
+
+ {translate('panel.keyframe.variant.title')}
+
+
+
+
+
diff --git a/src/ui/panels/display-panel/commonDisplayPage.svelte b/src/ui/panels/display-panel/commonDisplayPage.svelte
new file mode 100644
index 00000000..fcb67715
--- /dev/null
+++ b/src/ui/panels/display-panel/commonDisplayPage.svelte
@@ -0,0 +1,81 @@
+
+
+
+ {#each CONFIG.keys() as key}
+ {@const display = CONFIG.getPropertyDescription(key)}
+
+
+
+ {display?.displayName}
+
+
+
toggleLinked(key)}
+ class="material-icons notranslate icon option-mode-toggle"
+ >
+ {LINK_STATES.get(key) ? 'link' : 'link_off'}
+
+
+ {#if LINK_STATES.get(key) === false}
+ {#if display?.displayMode === 'checkbox'}
+
+
+
+ {:else if display?.displayMode === 'number'}
+
+
+
+ {:else}
+
+
+
+ {/if}
+ {/if}
+
+ {/each}
+
diff --git a/src/ui/panels/display-panel/displayPanel.svelte b/src/ui/panels/display-panel/displayPanel.svelte
new file mode 100644
index 00000000..301be077
--- /dev/null
+++ b/src/ui/panels/display-panel/displayPanel.svelte
@@ -0,0 +1,253 @@
+
+
+
+
+{#key $SELECTED_NODE}
+ {#if $SELECTED_NODE}
+
+
+ {#if isCommonTabActive}
+
+
toggleAllLinked()}
+ class="material-icons notranslate icon option-mode-toggle toggle-all-links"
+ title={getAverageLinkedState()
+ ? translate('panel.display.set_all_unlinked.tooltip')
+ : translate('panel.display.set_all_linked.tooltip')}
+ >
+ {getAverageLinkedState() ? 'link_off' : 'link'}
+
+ {:else}
+
+
resetUniqueOptions?.()}
+ class="material-icons notranslate icon option-mode-toggle toggle-all-links"
+ title={translate('panel.display.reset_all.tooltip')}
+ >
+ delete
+
+ {/if}
+
+
+
+
+ {#if isCommonTabActive}
+
+ {:else}
+
+ {#if $SELECTED_NODE instanceof Group}
+
+ {:else if $SELECTED_NODE instanceof TextDisplay}
+
+ {:else if $SELECTED_NODE instanceof BlockDisplay}
+
+ {:else if $SELECTED_NODE instanceof ItemDisplay}
+
+ {:else}
+
Selection has no Display Options
+ {/if}
+ {/if}
+
+
+ {/if}
+{/key}
+
+
diff --git a/src/ui/panels/display-panel/index.ts b/src/ui/panels/display-panel/index.ts
new file mode 100644
index 00000000..f179d2b4
--- /dev/null
+++ b/src/ui/panels/display-panel/index.ts
@@ -0,0 +1,180 @@
+import { isCurrentFormat } from '@aj/blockbench-additions/model-formats/ajblueprint'
+import { TextDisplay, type Alignment } from '@aj/blockbench-additions/outliner-elements/textDisplay'
+import { PACKAGE } from '@aj/constants'
+import { TextDisplayConfig } from '@aj/systems/node-configs'
+import { SveltePanel } from '@aj/util/sveltePanel'
+import { translate } from '@aj/util/translation'
+import DisplayPanel from './displayPanel.svelte'
+
+export const DISPLAY_PANEL = new SveltePanel({
+ id: `${PACKAGE.name}:display_panel`,
+ icon: 'visibility',
+ default_side: 'right',
+ default_position: {
+ slot: 'right_bar',
+ height: 200,
+ float_position: [0, 0],
+ float_size: [200, 200],
+ folded: false,
+ },
+ selection_only: true,
+ resizable: true,
+ insert_after: 'element',
+ name: translate('panel.display.title'),
+ component: DisplayPanel,
+ props: {},
+ expand_button: true,
+ condition: () => !!(isCurrentFormat() && Mode.selected?.id === 'edit'),
+})
+
+// Text Display Element Panel
+export const TEXT_DISPLAY_WIDTH_SLIDER = new NumSlider(
+ `${PACKAGE.name}:textDisplayLineWidthSlider`,
+ {
+ name: translate('tool.text_display.line_width.title'),
+ icon: 'format_size',
+ description: translate('tool.text_display.line_width.description'),
+ settings: {
+ min: 1,
+ max: 10000,
+ interval: 1,
+ },
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ get() {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return 0
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ return config.lineWidth!
+ },
+ change(value) {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ config.lineWidth = Math.clamp(value(config.lineWidth!), 1, 10000)
+ },
+ }
+)
+
+export const TEXT_DISPLAY_BACKGROUND_COLOR_PICKER = new ColorPicker(
+ `${PACKAGE.name}:textDisplayBackgroundColorPicker`,
+ {
+ name: translate('tool.text_display.background_color.title'),
+ icon: 'format_color_fill',
+ description: translate('tool.text_display.background_color.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ }
+)
+// @ts-expect-error
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.jq.spectrum('option', 'defaultColor', '#0000003f')
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.get = function () {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return new tinycolor('#0000003f')
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ return new tinycolor(config.backgroundColor)
+}
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.set = function (this: ColorPicker, color: string) {
+ this.value = new tinycolor(color)
+ // @ts-expect-error
+ this.jq.spectrum('set', this.value.toHex8String())
+
+ const selected = TextDisplay.selected[0]
+ if (!selected) return this
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ config.backgroundColor = this.value.toHex8String()
+ return this
+}
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.change = function (
+ this: ColorPicker,
+ color: InstanceType
+) {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return this
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ config.backgroundColor = color.toHex8String()
+ return this
+}
+
+export const TEXT_DISPLAY_SHADOW_TOGGLE = new Toggle(`${PACKAGE.name}:textDisplayShadowToggle`, {
+ name: translate('tool.text_display.text_shadow.title'),
+ icon: 'check_box_outline_blank',
+ description: translate('tool.text_display.text_shadow.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ click() {
+ //
+ },
+ onChange() {
+ const scope = TEXT_DISPLAY_SHADOW_TOGGLE
+ scope.setIcon(scope.value ? 'check_box' : 'check_box_outline_blank')
+ const selected = TextDisplay.selected[0]
+ if (!selected) return
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ config.shadow = TEXT_DISPLAY_SHADOW_TOGGLE.value
+ },
+})
+TEXT_DISPLAY_SHADOW_TOGGLE.set = function (value) {
+ if (this.value === value) return this
+ this.click()
+ return this
+}
+
+export const TEXT_DISPLAY_ALIGNMENT_SELECT = new BarSelect(
+ `${PACKAGE.name}:textDisplayAlignmentSelect`,
+ {
+ name: translate('tool.text_display.text_alignment.title'),
+ icon: 'format_align_left',
+ description: translate('tool.text_display.text_alignment.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ options: {
+ left: translate('tool.text_display.text_alignment.options.left'),
+ center: translate('tool.text_display.text_alignment.options.center'),
+ right: translate('tool.text_display.text_alignment.options.right'),
+ },
+ }
+)
+TEXT_DISPLAY_ALIGNMENT_SELECT.get = function () {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return 'left'
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ return config.alignment!
+}
+TEXT_DISPLAY_ALIGNMENT_SELECT.set = function (this: BarSelect, value: Alignment) {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return this
+ this.value = value
+ const name = this.getNameFor(value)
+ this.nodes.forEach(node => {
+ $(node).find('bb-select').text(name)
+ })
+ if (!this.nodes.includes(this.node)) {
+ $(this.node).find('bb-select').text(name)
+ }
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ config.alignment = value
+ return this
+}
+
+export const TEXT_DISPLAY_SEE_THROUGH_TOGGLE = new Toggle(
+ `${PACKAGE.name}:textDisplaySeeThroughToggle`,
+ {
+ name: translate('tool.text_display.see_through.title'),
+ icon: 'check_box_outline_blank',
+ description: translate('tool.text_display.see_through.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ click() {
+ //
+ },
+ onChange() {
+ const scope = TEXT_DISPLAY_SEE_THROUGH_TOGGLE
+ scope.setIcon(scope.value ? 'check_box' : 'check_box_outline_blank')
+ const selected = TextDisplay.selected[0]
+ if (!selected) return
+ const config = new TextDisplayConfig().fromJSON(selected.config)
+ config.seeThrough = TEXT_DISPLAY_SEE_THROUGH_TOGGLE.value
+ },
+ }
+)
+TEXT_DISPLAY_SEE_THROUGH_TOGGLE.set = function (value) {
+ if (this.value === value) return this
+ this.click()
+ return this
+}
diff --git a/src/ui/panels/display-panel/textDisplayPage.svelte b/src/ui/panels/display-panel/textDisplayPage.svelte
new file mode 100644
index 00000000..f9a58bd0
--- /dev/null
+++ b/src/ui/panels/display-panel/textDisplayPage.svelte
@@ -0,0 +1,182 @@
+
+
+
+ {#each CONFIG.keys() as key}
+ {@const display = CONFIG.getPropertyDescription(key)}
+
+
+
+ {display?.displayName}
+
+
+
toggleLinked(key)}
+ class="material-icons notranslate icon option-mode-toggle"
+ >
+ {LINK_STATES.get(key) ? 'edit' : 'delete'}
+
+
+ {#if LINK_STATES.get(key) === false}
+ {#if display?.displayMode === 'checkbox'}
+
+
+
+ {:else}
+
+
+
+ {/if}
+ {/if}
+
+ {/each}
+
+
+
+
+
diff --git a/src/ui/panels/item-display-element/index.ts b/src/ui/panels/item-display-element/index.ts
new file mode 100644
index 00000000..5d5c22bf
--- /dev/null
+++ b/src/ui/panels/item-display-element/index.ts
@@ -0,0 +1,62 @@
+import { isCurrentFormat } from '../../../blockbench-additions/model-formats/ajblueprint'
+import { ItemDisplay } from '../../../blockbench-additions/outliner-elements/itemDisplay'
+import { PACKAGE } from '../../../constants'
+import { injectSvelteCompomponentMod } from '../../../util/injectSvelteComponent'
+import { translate } from '../../../util/translation'
+import ItemDisplayElementPanel from './vanillaItemDisplayElementPanel.svelte'
+
+injectSvelteCompomponentMod({
+ component: ItemDisplayElementPanel,
+ props: {},
+ elementSelector() {
+ return document.querySelector('#panel_element')
+ },
+})
+
+export const ITEM_DISPLAY_ITEM_DISPLAY_SELECT = new BarSelect(
+ `${PACKAGE.name}:itemDisplayAlignmentSelect`,
+ {
+ name: translate('tool.item_display.item_display.title'),
+ icon: 'format_align_left',
+ description: translate('tool.item_display.item_display.description'),
+ condition: () => isCurrentFormat() && !!ItemDisplay.selected.length,
+ options: {
+ none: translate('tool.item_display.item_display.options.none'),
+ thirdperson_lefthand: translate(
+ 'tool.item_display.item_display.options.thirdperson_lefthand'
+ ),
+ thirdperson_righthand: translate(
+ 'tool.item_display.item_display.options.thirdperson_righthand'
+ ),
+ firstperson_lefthand: translate(
+ 'tool.item_display.item_display.options.firstperson_lefthand'
+ ),
+ firstperson_righthand: translate(
+ 'tool.item_display.item_display.options.firstperson_righthand'
+ ),
+ head: translate('tool.item_display.item_display.options.head'),
+ gui: translate('tool.item_display.item_display.options.gui'),
+ ground: translate('tool.item_display.item_display.options.ground'),
+ fixed: translate('tool.item_display.item_display.options.fixed'),
+ },
+ }
+)
+ITEM_DISPLAY_ITEM_DISPLAY_SELECT.get = function () {
+ const selected = ItemDisplay.selected[0]
+ if (!selected) return 'left'
+ return selected.itemDisplay
+}
+ITEM_DISPLAY_ITEM_DISPLAY_SELECT.set = function (this: BarSelect, value: string) {
+ const selected = ItemDisplay.selected[0]
+ if (!selected) return this
+ this.value = value
+ const name = this.getNameFor(value)
+ this.nodes.forEach(node => {
+ $(node).find('bb-select').text(name)
+ })
+ if (!this.nodes.includes(this.node)) {
+ $(this.node).find('bb-select').text(name)
+ }
+ selected.itemDisplay = value
+ return this
+}
diff --git a/src/ui/panels/item-display-element/vanillaItemDisplayElementPanel.svelte b/src/ui/panels/item-display-element/vanillaItemDisplayElementPanel.svelte
new file mode 100644
index 00000000..eb3260b0
--- /dev/null
+++ b/src/ui/panels/item-display-element/vanillaItemDisplayElementPanel.svelte
@@ -0,0 +1,82 @@
+
+
+
+ {translate('panel.item_display.title')}
+
+
+
+
+
+ {$error}
+
+
+
diff --git a/src/ui/panels/text-display-element/index.ts b/src/ui/panels/text-display-element/index.ts
new file mode 100644
index 00000000..166686a1
--- /dev/null
+++ b/src/ui/panels/text-display-element/index.ts
@@ -0,0 +1,162 @@
+import { isCurrentFormat } from '../../../blockbench-additions/model-formats/ajblueprint'
+import {
+ TextDisplay,
+ type Alignment,
+} from '../../../blockbench-additions/outliner-elements/textDisplay'
+import { PACKAGE } from '../../../constants'
+import { injectSvelteCompomponentMod } from '../../../util/injectSvelteComponent'
+import { floatToHex } from '../../../util/misc'
+import { translate } from '../../../util/translation'
+import TextDisplayElementPanel from './textDisplayElementPanel.svelte'
+
+injectSvelteCompomponentMod({
+ component: TextDisplayElementPanel,
+ props: {},
+ elementSelector() {
+ return document.querySelector('#panel_element')
+ },
+})
+
+export const TEXT_DISPLAY_WIDTH_SLIDER = new NumSlider(
+ `${PACKAGE.name}:textDisplayLineWidthSlider`,
+ {
+ name: translate('tool.text_display.line_width.title'),
+ icon: 'format_size',
+ description: translate('tool.text_display.line_width.description'),
+ settings: {
+ min: 1,
+ max: 10000,
+ interval: 1,
+ },
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ get() {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return 0
+ return selected.lineWidth
+ },
+ change(value) {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return
+ selected.lineWidth = Math.clamp(value(selected.lineWidth), 1, 10000)
+ },
+ }
+)
+
+export const TEXT_DISPLAY_BACKGROUND_COLOR_PICKER = new ColorPicker(
+ `${PACKAGE.name}:textDisplayBackgroundColorPicker`,
+ {
+ name: translate('tool.text_display.background_color.title'),
+ icon: 'format_color_fill',
+ description: translate('tool.text_display.background_color.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ }
+)
+// @ts-expect-error
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.jq.spectrum('option', 'defaultColor', '#0000003f')
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.get = function () {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return new tinycolor('#0000003f')
+ return new tinycolor(selected.backgroundColor + floatToHex(selected.backgroundAlpha))
+}
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.set = function (this: ColorPicker, color: string) {
+ this.value = new tinycolor(color)
+ // @ts-expect-error
+ this.jq.spectrum('set', this.value.toHex8String())
+
+ const selected = TextDisplay.selected[0]
+ if (!selected) return this
+ selected.backgroundColor = this.value.toHexString()
+ selected.backgroundAlpha = this.value.getAlpha()
+ return this
+}
+TEXT_DISPLAY_BACKGROUND_COLOR_PICKER.change = function (
+ this: ColorPicker,
+ color: InstanceType
+) {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return this
+ selected.backgroundColor = color.toHexString()
+ selected.backgroundAlpha = color.getAlpha()
+ return this
+}
+
+export const TEXT_DISPLAY_SHADOW_TOGGLE = new Toggle(`${PACKAGE.name}:textDisplayShadowToggle`, {
+ name: translate('tool.text_display.text_shadow.title'),
+ icon: 'check_box_outline_blank',
+ description: translate('tool.text_display.text_shadow.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ click() {
+ //
+ },
+ onChange() {
+ const scope = TEXT_DISPLAY_SHADOW_TOGGLE
+ scope.setIcon(scope.value ? 'check_box' : 'check_box_outline_blank')
+ const selected = TextDisplay.selected[0]
+ if (!selected) return
+ selected.shadow = TEXT_DISPLAY_SHADOW_TOGGLE.value
+ },
+})
+TEXT_DISPLAY_SHADOW_TOGGLE.set = function (value) {
+ if (this.value === value) return this
+ this.click()
+ return this
+}
+
+export const TEXT_DISPLAY_ALIGNMENT_SELECT = new BarSelect(
+ `${PACKAGE.name}:textDisplayAlignmentSelect`,
+ {
+ name: translate('tool.text_display.text_alignment.title'),
+ icon: 'format_align_left',
+ description: translate('tool.text_display.text_alignment.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ options: {
+ left: translate('tool.text_display.text_alignment.options.left'),
+ center: translate('tool.text_display.text_alignment.options.center'),
+ right: translate('tool.text_display.text_alignment.options.right'),
+ },
+ }
+)
+TEXT_DISPLAY_ALIGNMENT_SELECT.get = function () {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return 'left'
+ return selected.align
+}
+TEXT_DISPLAY_ALIGNMENT_SELECT.set = function (this: BarSelect, value: Alignment) {
+ const selected = TextDisplay.selected[0]
+ if (!selected) return this
+ this.value = value
+ const name = this.getNameFor(value)
+ this.nodes.forEach(node => {
+ $(node).find('bb-select').text(name)
+ })
+ if (!this.nodes.includes(this.node)) {
+ $(this.node).find('bb-select').text(name)
+ }
+ selected.align = value
+ return this
+}
+
+export const TEXT_DISPLAY_SEE_THROUGH_TOGGLE = new Toggle(
+ `${PACKAGE.name}:textDisplaySeeThroughToggle`,
+ {
+ name: translate('tool.text_display.see_through.title'),
+ icon: 'check_box_outline_blank',
+ description: translate('tool.text_display.see_through.description'),
+ condition: () => isCurrentFormat() && !!TextDisplay.selected.length,
+ click() {
+ //
+ },
+ onChange() {
+ const scope = TEXT_DISPLAY_SEE_THROUGH_TOGGLE
+ scope.setIcon(scope.value ? 'check_box' : 'check_box_outline_blank')
+ const selected = TextDisplay.selected[0]
+ if (!selected) return
+ selected.seeThrough = TEXT_DISPLAY_SEE_THROUGH_TOGGLE.value
+ },
+ }
+)
+TEXT_DISPLAY_SEE_THROUGH_TOGGLE.set = function (value) {
+ if (this.value === value) return this
+ this.click()
+ return this
+}
diff --git a/src/components/textDisplayElementPanel.svelte b/src/ui/panels/text-display-element/textDisplayElementPanel.svelte
similarity index 78%
rename from src/components/textDisplayElementPanel.svelte
rename to src/ui/panels/text-display-element/textDisplayElementPanel.svelte
index a59666c2..a0e945ce 100644
--- a/src/components/textDisplayElementPanel.svelte
+++ b/src/ui/panels/text-display-element/textDisplayElementPanel.svelte
@@ -1,33 +1,29 @@
diff --git a/src/interface/panel/variants.ts b/src/ui/panels/variants/index.ts
similarity index 74%
rename from src/interface/panel/variants.ts
rename to src/ui/panels/variants/index.ts
index 26ca68ff..d80d801c 100644
--- a/src/interface/panel/variants.ts
+++ b/src/ui/panels/variants/index.ts
@@ -1,11 +1,11 @@
-import { isCurrentFormat } from '../../blueprintFormat'
-import VariantsPanel from '../../components/variantsPanel.svelte'
-import { PACKAGE } from '../../constants'
-import { createAction, createMenu } from '../../util/moddingTools'
-import { SveltePanel } from '../../util/sveltePanel'
-import { translate } from '../../util/translation'
-import { Variant } from '../../variants'
-import { openVariantConfigDialog } from '../dialog/variantConfig'
+import { isCurrentFormat } from '../../../blockbench-additions/model-formats/ajblueprint'
+import { PACKAGE } from '../../../constants'
+import { createAction, createMenu } from '../../../util/moddingTools'
+import { SveltePanel } from '../../../util/sveltePanel'
+import { translate } from '../../../util/translation'
+import { Variant } from '../../../variants'
+import { openVariantConfigDialog } from '../../dialogs/variant-config'
+import VariantsPanel from './variantsPanel.svelte'
export const CREATE_VARIANT_ACTION = createAction(`${PACKAGE.name}:createVariant`, {
name: translate('action.variants.create'),
@@ -71,12 +71,7 @@ export const VARIANTS_PANEL = new SveltePanel({
},
icon: 'settings',
condition: () =>
- !!(
- isCurrentFormat() &&
- Modes.selected &&
- (Modes.selected.id === Modes.options.edit.id ||
- Modes.selected.id === Modes.options.paint.id)
- ),
+ !!((isCurrentFormat() && Mode.selected?.id === 'edit') || Mode.selected?.id === 'paint'),
component: VariantsPanel,
props: {},
})
diff --git a/src/components/variantsPanel.svelte b/src/ui/panels/variants/variantsPanel.svelte
similarity index 86%
rename from src/components/variantsPanel.svelte
rename to src/ui/panels/variants/variantsPanel.svelte
index 437a2d21..0910a535 100644
--- a/src/components/variantsPanel.svelte
+++ b/src/ui/panels/variants/variantsPanel.svelte
@@ -1,49 +1,50 @@
-
+ const FLIP_DURATION = 100
-
-
diff --git a/src/interface/popup/animatedJavaLoading.ts b/src/ui/popups/animated-java-loading/index.ts
similarity index 71%
rename from src/interface/popup/animatedJavaLoading.ts
rename to src/ui/popups/animated-java-loading/index.ts
index ec808005..c77413cb 100644
--- a/src/interface/popup/animatedJavaLoading.ts
+++ b/src/ui/popups/animated-java-loading/index.ts
@@ -1,12 +1,12 @@
-import { SvelteComponentDev } from 'svelte/internal'
-import AnimatedJavaLoadingPopup from '../../components/animatedJavaLoadingPopup.svelte'
-import { injectSvelteCompomponent } from '../../util/injectSvelteComponent'
-import { Valuable } from '../../util/stores'
+import type { SvelteComponentDev } from 'svelte/internal'
+import { injectSvelteCompomponent } from '../../../util/injectSvelteComponent'
+import { Syncable } from '../../../util/stores'
+import AnimatedJavaLoadingPopup from './animatedJavaLoadingPopup.svelte'
-const LOADED = new Valuable(false)
-const OFFLINE = new Valuable(false)
-const PROGRESS = new Valuable(0)
-const PROGRESS_LABEL = new Valuable('')
+const LOADED = new Syncable(false)
+const OFFLINE = new Syncable(false)
+const PROGRESS = new Syncable(0)
+const PROGRESS_LABEL = new Syncable('')
let activeComponent: SvelteComponentDev | undefined
export async function showLoadingPopup() {
diff --git a/src/components/blueprintLoadingPopup.svelte b/src/ui/popups/blueprint-loading/blueprintLoadingPopup.svelte
similarity index 58%
rename from src/components/blueprintLoadingPopup.svelte
rename to src/ui/popups/blueprint-loading/blueprintLoadingPopup.svelte
index b52b9092..7049b3c3 100644
--- a/src/components/blueprintLoadingPopup.svelte
+++ b/src/ui/popups/blueprint-loading/blueprintLoadingPopup.svelte
@@ -1,11 +1,9 @@
-
-
diff --git a/src/interface/popup/blueprintLoading.ts b/src/ui/popups/blueprint-loading/index.ts
similarity index 61%
rename from src/interface/popup/blueprintLoading.ts
rename to src/ui/popups/blueprint-loading/index.ts
index 9ae44be5..bd753b9f 100644
--- a/src/interface/popup/blueprintLoading.ts
+++ b/src/ui/popups/blueprint-loading/index.ts
@@ -1,11 +1,11 @@
-import BlueprintLoadingPopup from '../../components/blueprintLoadingPopup.svelte'
-import { PACKAGE } from '../../constants'
-import { Valuable } from '../../util/stores'
-import { SvelteDialog } from '../../util/svelteDialog'
-import { translate } from '../../util/translation'
+import { PACKAGE } from '../../../constants'
+import { Syncable } from '../../../util/stores'
+import { SvelteDialog } from '../../../util/svelteDialog'
+import { translate } from '../../../util/translation'
+import BlueprintLoadingPopup from './blueprintLoadingPopup.svelte'
-export const PROGRESS = new Valuable(0)
-export const MAX_PROGRESS = new Valuable(1)
+export const PROGRESS = new Syncable(0)
+export const MAX_PROGRESS = new Syncable(1)
let instance: SvelteDialog
| null = null
diff --git a/src/components/incompatabilityPopup.svelte b/src/ui/popups/incompatability/incompatabilityPopup.svelte
similarity index 90%
rename from src/components/incompatabilityPopup.svelte
rename to src/ui/popups/incompatability/incompatabilityPopup.svelte
index 54ca9b8d..9c714bc8 100644
--- a/src/components/incompatabilityPopup.svelte
+++ b/src/ui/popups/incompatability/incompatabilityPopup.svelte
@@ -1,6 +1,6 @@
diff --git a/src/util/bufferGeometryUtils.ts b/src/util/bufferGeometryUtils.ts
index 32a9cd4f..379296dc 100644
--- a/src/util/bufferGeometryUtils.ts
+++ b/src/util/bufferGeometryUtils.ts
@@ -1,3 +1,4 @@
+/* eslint-disable */
const {
BufferAttribute,
BufferGeometry,
@@ -12,7 +13,7 @@ const {
} = THREE
function computeMikkTSpaceTangents(geometry: any, MikkTSpace: any, negateSign = true) {
- if (!MikkTSpace || !MikkTSpace.isReady) {
+ if (!MikkTSpace?.isReady) {
throw new Error('BufferGeometryUtils: Initialized MikkTSpace library required.')
}
@@ -1177,13 +1178,13 @@ function toCreasedNormals(geometry: any, creaseAngle = Math.PI / 3 /* 60 degrees
export {
computeMikkTSpaceTangents,
- mergeGeometries,
- mergeAttributes,
- interleaveAttributes,
- estimateBytesUsed,
- mergeVertices,
- toTrianglesDrawMode,
computeMorphedAttributes,
+ estimateBytesUsed,
+ interleaveAttributes,
+ mergeAttributes,
+ mergeGeometries,
mergeGroups,
+ mergeVertices,
toCreasedNormals,
+ toTrianglesDrawMode,
}
diff --git a/src/util/easing.ts b/src/util/easing.ts
index 54c26c4a..e54466ea 100644
--- a/src/util/easing.ts
+++ b/src/util/easing.ts
@@ -230,13 +230,13 @@ class Easing {
}
}
-const quart = Easing.poly(4)
-const quint = Easing.poly(5)
-const back = (direction: DirectionFunction, scalar: number, t: number) =>
+const QUART = Easing.poly(4)
+const QUINT = Easing.poly(5)
+const BACK = (direction: DirectionFunction, scalar: number, t: number) =>
direction(Easing.back(1.70158 * scalar))(t)
-const elastic = (direction: DirectionFunction, bounciness: number, t: number) =>
+const ELASTIC = (direction: DirectionFunction, bounciness: number, t: number) =>
direction(Easing.elastic(bounciness))(t)
-const bounce = (direction: DirectionFunction, bounciness: number, t: number) =>
+const BOUNCE = (direction: DirectionFunction, bounciness: number, t: number) =>
direction(Easing.bounce(bounciness))(t)
export const easingFunctions: Record
= {
@@ -251,12 +251,12 @@ export const easingFunctions: Record = {
easeInCubic: Easing.in(Easing.cubic),
easeOutCubic: Easing.out(Easing.cubic),
easeInOutCubic: Easing.inOut(Easing.cubic),
- easeInQuart: Easing.in(quart),
- easeOutQuart: Easing.out(quart),
- easeInOutQuart: Easing.inOut(quart),
- easeInQuint: Easing.in(quint),
- easeOutQuint: Easing.out(quint),
- easeInOutQuint: Easing.inOut(quint),
+ easeInQuart: Easing.in(QUART),
+ easeOutQuart: Easing.out(QUART),
+ easeInOutQuart: Easing.inOut(QUART),
+ easeInQuint: Easing.in(QUINT),
+ easeOutQuint: Easing.out(QUINT),
+ easeInOutQuint: Easing.inOut(QUINT),
easeInSine: Easing.in(Easing.sin),
easeOutSine: Easing.out(Easing.sin),
easeInOutSine: Easing.inOut(Easing.sin),
@@ -266,15 +266,15 @@ export const easingFunctions: Record = {
easeInCirc: Easing.in(Easing.circle),
easeOutCirc: Easing.out(Easing.circle),
easeInOutCirc: Easing.inOut(Easing.circle),
- easeInBack: back.bind(null, Easing.in),
- easeOutBack: back.bind(null, Easing.out),
- easeInOutBack: back.bind(null, Easing.inOut),
- easeInElastic: elastic.bind(null, Easing.in),
- easeOutElastic: elastic.bind(null, Easing.out),
- easeInOutElastic: elastic.bind(null, Easing.inOut),
- easeInBounce: bounce.bind(null, Easing.in),
- easeOutBounce: bounce.bind(null, Easing.out),
- easeInOutBounce: bounce.bind(null, Easing.inOut),
+ easeInBack: BACK.bind(null, Easing.in),
+ easeOutBack: BACK.bind(null, Easing.out),
+ easeInOutBack: BACK.bind(null, Easing.inOut),
+ easeInElastic: ELASTIC.bind(null, Easing.in),
+ easeOutElastic: ELASTIC.bind(null, Easing.out),
+ easeInOutElastic: ELASTIC.bind(null, Easing.inOut),
+ easeInBounce: BOUNCE.bind(null, Easing.in),
+ easeOutBounce: BOUNCE.bind(null, Easing.out),
+ easeInOutBounce: BOUNCE.bind(null, Easing.inOut),
}
export type EasingKey = keyof typeof easingFunctions
diff --git a/src/util/events.ts b/src/util/events.ts
index ae02915e..d265f8bf 100644
--- a/src/util/events.ts
+++ b/src/util/events.ts
@@ -1,5 +1,5 @@
-import * as PACKAGE from '../../package.json'
-import { Variant } from '../variants'
+import { name as pluginID } from '../../package.json'
+import type { Variant } from '../variants'
import { Subscribable } from './subscribable'
export class PluginEvent extends Subscribable {
@@ -11,7 +11,7 @@ export class PluginEvent extends Subscribable {
}
// Plugin Events
-export const events = {
+const EVENTS = {
LOAD: new PluginEvent('load'),
UNLOAD: new PluginEvent('unload'),
INSTALL: new PluginEvent('install'),
@@ -48,29 +48,31 @@ export const events = {
REDO: new PluginEvent('redo'),
}
+export default EVENTS
+
function injectionHandler() {
- console.groupCollapsed(`Injecting BlockbenchMods added by '${PACKAGE.name}'`)
- events.INJECT_MODS.dispatch()
+ console.groupCollapsed(`Injecting BlockbenchMods added by '${pluginID}'`)
+ EVENTS.INJECT_MODS.dispatch()
console.groupEnd()
}
function extractionHandler() {
- console.groupCollapsed(`Extracting BlockbenchMods added by '${PACKAGE.name}'`)
- events.EXTRACT_MODS.dispatch()
+ console.groupCollapsed(`Extracting BlockbenchMods added by '${pluginID}'`)
+ EVENTS.EXTRACT_MODS.dispatch()
console.groupEnd()
}
-events.LOAD.subscribe(injectionHandler)
-events.UNLOAD.subscribe(extractionHandler)
-events.INSTALL.subscribe(injectionHandler)
-events.UNINSTALL.subscribe(extractionHandler)
+EVENTS.LOAD.subscribe(injectionHandler)
+EVENTS.UNLOAD.subscribe(extractionHandler)
+EVENTS.INSTALL.subscribe(injectionHandler)
+EVENTS.UNINSTALL.subscribe(extractionHandler)
Blockbench.on('select_project', ({ project }: { project: ModelProject }) => {
- events.SELECT_PROJECT.dispatch(project)
+ EVENTS.SELECT_PROJECT.dispatch(project)
})
Blockbench.on('unselect_project', ({ project }: { project: ModelProject }) => {
- events.UNSELECT_PROJECT.dispatch(project)
+ EVENTS.UNSELECT_PROJECT.dispatch(project)
})
-Blockbench.on('update_selection', () => events.UPDATE_SELECTION.dispatch())
-Blockbench.on('undo', (entry: UndoEntry) => events.UNDO.dispatch(entry))
-Blockbench.on('redo', (entry: UndoEntry) => events.REDO.dispatch(entry))
+Blockbench.on('update_selection', () => EVENTS.UPDATE_SELECTION.dispatch())
+Blockbench.on('undo', (entry: UndoEntry) => EVENTS.UNDO.dispatch(entry))
+Blockbench.on('redo', (entry: UndoEntry) => EVENTS.REDO.dispatch(entry))
diff --git a/src/util/excludedNodes.ts b/src/util/excludedNodes.ts
index c1c71761..aec98a8a 100644
--- a/src/util/excludedNodes.ts
+++ b/src/util/excludedNodes.ts
@@ -1,13 +1,13 @@
-import { TextDisplay } from '../outliner/textDisplay'
-import { VanillaBlockDisplay } from '../outliner/vanillaBlockDisplay'
-import { VanillaItemDisplay } from '../outliner/vanillaItemDisplay'
+import { BlockDisplay } from '../blockbench-additions/outliner-elements/blockDisplay'
+import { ItemDisplay } from '../blockbench-additions/outliner-elements/itemDisplay'
+import { TextDisplay } from '../blockbench-additions/outliner-elements/textDisplay'
export function getAvailableNodes(
excludedNodes: CollectionItem[],
options: { groupsOnly?: boolean; excludeEmptyGroups?: boolean } = {}
): CollectionItem[] {
const allNodes: Array<
- Group | Locator | TextDisplay | VanillaItemDisplay | VanillaBlockDisplay | OutlinerElement
+ Group | Locator | TextDisplay | ItemDisplay | BlockDisplay | OutlinerElement
> = []
if (options?.excludeEmptyGroups) {
allNodes.push(
@@ -22,8 +22,8 @@ export function getAvailableNodes(
allNodes.push(
...Locator.all,
...TextDisplay.all,
- ...VanillaItemDisplay.all,
- ...VanillaBlockDisplay.all,
+ ...ItemDisplay.all,
+ ...BlockDisplay.all,
...(OutlinerElement.types.camera?.all || [])
)
}
@@ -42,8 +42,8 @@ export function getAvailableNodes(
icon = 'anchor'
break
case node instanceof TextDisplay:
- case node instanceof VanillaItemDisplay:
- case node instanceof VanillaBlockDisplay:
+ case node instanceof ItemDisplay:
+ case node instanceof BlockDisplay:
icon = node.icon
break
case node instanceof OutlinerElement.types.camera:
diff --git a/src/util/fileUtil.ts b/src/util/fileUtil.ts
index 4ca9be1c..9c048fa5 100644
--- a/src/util/fileUtil.ts
+++ b/src/util/fileUtil.ts
@@ -68,3 +68,11 @@ export function safeReadSync(path: string): Buffer | undefined {
export async function safeRead(path: string) {
return fs.promises.readFile(path).catch(() => undefined)
}
+
+export function directoryExists(path: string): boolean {
+ return fs.existsSync(path) && !!fs.lstatSync(path, { throwIfNoEntry: false })?.isDirectory()
+}
+
+export function fileExists(path: string): boolean {
+ return fs.existsSync(path) && !!fs.lstatSync(path, { throwIfNoEntry: false })?.isFile()
+}
diff --git a/src/util/injectSvelteComponent.ts b/src/util/injectSvelteComponent.ts
index 1e6998b2..b62431ce 100644
--- a/src/util/injectSvelteComponent.ts
+++ b/src/util/injectSvelteComponent.ts
@@ -1,9 +1,9 @@
-import type { SvelteComponentConstructor } from './misc'
import type { ComponentConstructorOptions, SvelteComponentDev } from 'svelte/internal'
-import { pollPromise } from './promises'
+import type { SvelteComponentConstructor } from './misc'
import { createBlockbenchMod } from './moddingTools'
+import { pollPromise } from './promises'
-type InjectSvelteComponentOptions = {
+interface InjectSvelteComponentOptions {
/**
* The svelte component constructor.
*/
diff --git a/src/util/minecraftUtil.ts b/src/util/minecraftUtil.ts
index d3702cc9..7b42868d 100644
--- a/src/util/minecraftUtil.ts
+++ b/src/util/minecraftUtil.ts
@@ -1,10 +1,10 @@
-import * as pathjs from 'path'
import { MinecraftVersion } from '../systems/global'
import {
BlockStateRegistryEntry,
- BlockStateValue,
+ type BlockStateValue,
getBlockState,
-} from '../systems/minecraft/blockstateManager'
+} from '@aj/systems/minecraft-temp/blockstateManager'
+import * as pathjs from 'path'
export interface IMinecraftResourceLocation {
packRoot: string
@@ -55,9 +55,27 @@ export function getPathFromResourceLocation(resourceLocation: string, type: stri
return `assets/${namespace}/${type}/${path.join('/')}`
}
+export function createTagPrefixFromBlueprintID(id: string) {
+ const parsed = parseResourceLocation(id)
+ if (parsed) {
+ const namespace = parsed.namespace === 'animated_java' ? 'aj' : parsed.namespace
+ return namespace + '.' + parsed.subpath.replaceAll('/', '.')
+ }
+}
+
+export function containsInvalidScoreboardTagCharacters(tag: string) {
+ if (tag.match(/[^a-zA-Z0-9_\-.]/g)) return true
+ return false
+}
+
+export function containsInvalidResourceLocationCharacters(resourceLocation: string) {
+ if (resourceLocation.match(/[^a-z0-9_/.:]/g)) return true
+ return false
+}
+
export function isResourcePackPath(path: string) {
const parsed = parseResourcePackPath(path)
- return !!(parsed && parsed.namespace && parsed.resourcePath)
+ return !!(parsed?.namespace && parsed.resourcePath)
}
export function parseResourcePackPath(path: string): IMinecraftResourceLocation | undefined {
@@ -97,14 +115,13 @@ export function parseResourceLocation(resourceLocation: string) {
parts = [namespace]
namespace = 'minecraft'
}
- const path = parts.join('')
- const resourceType = path.split('/')[0]
- const parsed = PathModule.parse(path)
- const fullPath = PathModule.join(namespace, path)
+ const subpath = parts.join('')
+ const resourceType = subpath.split('/')[0]
+ const parsed = PathModule.parse(subpath)
return {
namespace,
- path,
- fullPath,
+ subpath,
+ path: PathModule.join(namespace, parsed.name),
type: resourceType,
dir: parsed.dir,
name: parsed.name,
@@ -113,7 +130,7 @@ export function parseResourceLocation(resourceLocation: string) {
export function isDataPackPath(path: string) {
const parsed = parseDataPackPath(path)
- return !!(parsed && parsed.namespace && parsed.resourcePath)
+ return !!(parsed?.namespace && parsed.resourcePath)
}
export function parseDataPackPath(path: string): IMinecraftResourceLocation | undefined {
@@ -212,7 +229,7 @@ export interface IParsedBlock {
export async function parseBlock(block: string): Promise {
const states: Record> = {}
if (block.includes('[')) {
- const match = block.match(/(.+?)\[((?:[^,=[\]]+=[^,=[\]]+,?)+)?]/)
+ const match = /(.+?)\[((?:[^,=[\]]+=[^,=[\]]+,?)+)?]/.exec(block)
if (!match) return
if (match[2] !== undefined) {
const args = match[2].split(',')
@@ -227,7 +244,7 @@ export async function parseBlock(block: string): Promise = new (
options: U
@@ -67,7 +67,7 @@ export function scrubUndefined>(obj: T) {
}
// Developed by FetchBot 💖
-type LLNode = {
+interface LLNode {
parent?: LLNode
name: string
}
@@ -119,3 +119,55 @@ export function mapObjEntries(
): Record {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => cb(k, v)))
}
+
+export function markdownToHTML(markdown: string) {
+ return markdown
+ .replaceAll('\n', ' ')
+ .replaceAll(/`(.+?)`/g, '$1
')
+ .replaceAll(/\*\*(.+?)\*\*/g, '$1 ')
+ .replaceAll(/\*(.+?)\*/g, '$1 ')
+ .replaceAll(/~~(.+?)~~/g, '$1')
+ .replaceAll(/\[([^\]]+?)\]\(([^)]+?)\)/g, '$1 ')
+}
+
+/**
+ * Returns a new object with the keys sorted alphabetically
+ */
+export function sortObjectKeys>(obj: T): T {
+ const sorted: Record = {}
+ Object.keys(obj)
+ .sort()
+ .forEach(key => {
+ sorted[key] = obj[key]
+ })
+ return sorted as T
+}
+
+export function isCubeValid(cube: Cube) {
+ // Cube is automatically valid if it has no rotation
+ if (cube.rotation[0] === 0 && cube.rotation[1] === 0 && cube.rotation[2] === 0) {
+ return true
+ }
+ const rotation = cube.rotation[0] + cube.rotation[1] + cube.rotation[2]
+ // prettier-ignore
+ if (
+ // Make sure the cube is rotated in only one axis by adding all the rotations together, and checking if the sum is equal to one of the rotations.
+ (
+ rotation === cube.rotation[0] ||
+ rotation === cube.rotation[1] ||
+ rotation === cube.rotation[2]
+ )
+ &&
+ // Make sure the cube is rotated in one of the allowed 22.5 degree increments
+ (
+ rotation === -45 ||
+ rotation === -22.5 ||
+ rotation === 0 ||
+ rotation === 22.5 ||
+ rotation === 45
+ )
+ ) {
+ return true
+ }
+ return false
+}
diff --git a/src/util/moddingTools.ts b/src/util/moddingTools.ts
index 13b80950..da28610c 100644
--- a/src/util/moddingTools.ts
+++ b/src/util/moddingTools.ts
@@ -1,4 +1,4 @@
-import { events } from './events'
+import EVENTS from '@events'
import { Subscribable } from './subscribable'
export type NamespacedString = `${string}${string}:${string}${string}`
@@ -59,7 +59,7 @@ export function createBlockbenchMod