[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://editorconfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\nmax_line_length = off\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "## Contributing\n\nThanks for considering contributing!\n\n* **Step 1.** Forks BetterModel to your repository.\n* **Step 2.** Commits your change.\n* **Step 3.** Opens pull request at 'dev' branch.\n* **Step 4.** Waits for merging or reviewing\n\n## Rule\n* I can't handle a huge change about API module.  \n* You should note that type of your PR. (e.g., `Bug fix` / `API update`)   \n* You should inform your features that what you want to merge by this way.\n```\nDocument\nImage\nVideo\n```\n\n## 📄 Commit Message Format\nUse present tense: `fix:`, `feat:`, `docs:`, etc.\n\n## 📜 License\nContributions are under the MIT License."
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [toxicity188]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: toxicity188\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Report a bug to help us improve BetterModel\ntitle: \"[Bug] \"\nlabels: bug\nassignees: toxicity188\n\n---\n\n### ✔️ Pre-check\n- [ ] Tested with the **latest snapshot** of BetterModel from [Modrinth](https://modrinth.com/plugin/bettermodel)\n- [ ] Confirmed the issue occurs **without** other optional/experimental plugins or clients (see Disclaimer below)\n\n---\n\n### 🐞 Problem Description\nDetailed information about your problem.\n\n---\n\n### 📜 Server Log\nYour error log if exists.\n\n---\n\n### 🖼️ Screenshot / Video\nYour in-game screenshot.\n\n---\n\n### 🧪 Test Model / Code\nUpload the model, resource pack, or test code that can reproduce the issue if possible.\n\n---\n\n### 🌍 Environment\n- OS: (Windows, Linux, etc.)\n- Server software & version: (Paper 1.21.1, etc.)\n\n---\n\n```\nDisclaimer\n\nThe following environments are not supported, and issues occurring under these conditions will not be handled:\n\n- Informal / modified launchers (e.g., Feather client)\n- Closed-source mods/plugins (Optifine, ItemsAdder, Nexo, etc.)\n- Hybrid server platforms (e.g., Arclight)\n```\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea or enhancement for BetterModel\ntitle: \"[Feature] \"\nlabels: help wanted\nassignees: toxicity188\n\n---\n\n### 🐞 Feature Description\nDescribe the feature you want to request.  \nExplain **what problem this feature solves**, **why it's needed**, and **how you expect it to work**.\n\n---\n\n### 🧪 Example / Mock-up (Optional)\nProvide model files, resource packs, code snippets, or conceptual mock-ups that help illustrate the idea.\n\n---\n\n### 🌍 Environment (Optional but Helpful)\n- Server software & version:\n- BetterModel version:\n- Java version:\n\n---\n\n```\nDisclaimer\n\nThe following environments are not supported, and feature requests relying on these conditions will not be considered:\n\n- Informal / modified launchers (e.g., Feather Client)\n- Closed-source mods/plugins (Optifine, ItemsAdder, Nexo, etc.)\n- Hybrid server platforms (e.g., Arclight)\n- Legacy server versions (1.20.1 or lower)\n- Bedrock Edition\n- Extremely outdated CPU / hardware\n- Features fundamentally **impossible on server-side** due to engine or protocol limitations\n```\n"
  },
  {
    "path": ".github/workflows/package.yml",
    "content": "name: Package plugin\n\non:\n  push:\n    branches: [ \"master\", \"v2\", \"v3\" ]\n\npermissions:\n  contents: read\n  packages: write\n  deployments: write\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    env:\n      SIGNING_KEY: ${{ secrets.SIGNING_KEY }}\n      SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}\n      PACKAGES_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      BUILD_NUMBER: ${{ github.run_number }}\n      COMMIT_MESSAGE: ${{ github.event.head_commit.message }}\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up JDK 25\n        uses: actions/setup-java@v5\n        with:\n          java-version: '25'\n          distribution: 'temurin'\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n      - name: Create Deployment\n        id: deployment\n        run: |\n          response=$(curl -s -X POST \\\n            -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" \\\n            -H \"Accept: application/vnd.github+json\" \\\n            https://api.github.com/repos/${{ github.repository }}/deployments \\\n            -d '{\n              \"ref\": \"master\",\n              \"auto_merge\": false,\n              \"required_contexts\": [],\n              \"payload\": \"{ \\\"timestamp\\\": \\\"'$(date +%s)'\\\" }\",\n              \"environment\": \"github-packages\",\n              \"transient_environment\": false,\n              \"description\": \"Publishing to GitHub Packages\"\n            }')\n          echo \"$response\"\n          deployment_id=$(echo \"$response\" | jq -r '.id')\n          echo \"id=$deployment_id\" >> $GITHUB_OUTPUT\n        shell: bash\n      - name: Publish package\n        run: ./gradlew publishAllPublicationToGitHubPackagesRepository --stacktrace\n      - name: Set Deployment Status (success)\n        if: success()\n        run: |\n          curl -X POST \\\n            -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" \\\n            -H \"Accept: application/vnd.github+json\" \\\n            https://api.github.com/repos/${{ github.repository }}/deployments/${{ steps.deployment.outputs.id }}/statuses \\\n            -d '{\"state\":\"success\",\"environment_url\":\"https://github.com/${{ github.repository }}/packages\",\"description\":\"Deployment succeeded\"}'\n        shell: bash\n      - name: Set Deployment Status (failure)\n        if: failure()\n        run: |\n          curl -X POST \\\n            -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" \\\n            -H \"Accept: application/vnd.github+json\" \\\n            https://api.github.com/repos/${{ github.repository }}/deployments/${{ steps.deployment.outputs.id }}/statuses \\\n            -d '{\"state\":\"failure\",\"environment_url\":\"https://github.com/${{ github.repository }}/packages\",\"description\":\"Deployment failed\"}'\n        shell: bash\n"
  },
  {
    "path": ".github/workflows/pr-test.yml",
    "content": "name: PR Test\n\non:\n  pull_request:\n    branches: [ \"dev\", \"v2-dev\", \"v3-dev\" ]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up JDK 25\n        uses: actions/setup-java@v5\n        with:\n          java-version: '25'\n          distribution: 'temurin'\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      - name: Build with Gradle\n        run: ./gradlew build --stacktrace\n\n      - name: Run Tests\n        run: ./gradlew test --stacktrace\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish plugin\n\non:\n  push:\n    branches: [ \"master\", \"v2\", \"v3\" ]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    if: \"!contains(github.event.head_commit.message, '[publish skip]')\"\n    runs-on: ubuntu-latest\n    env:\n      HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }}\n      MODRINTH_API_TOKEN: ${{ secrets.MODRINTH_API_TOKEN }}\n      BUILD_NUMBER: ${{ github.run_number }}\n      COMMIT_MESSAGE: ${{ github.event.head_commit.message }}\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up JDK 25\n        uses: actions/setup-java@v5\n        with:\n          java-version: '25'\n          distribution: 'temurin'\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n      - name: Build all file\n        run: ./gradlew build --stacktrace\n      - name: Publish to Modrinth\n        run: ./gradlew modrinth --stacktrace\n      - name: Publish to Hangar\n        run: ./gradlew publishPluginPublicationToHangar --stacktrace\n"
  },
  {
    "path": ".gitignore",
    "content": ".gradle\nbuild/\n!gradle/wrapper/gradle-wrapper.jar\n!**/src/main/**/build/\n!**/src/test/**/build/\n\n### IntelliJ IDEA ###\n.idea/*\n!.idea/codeStyles\n!.idea/inspectionProfiles\n!.idea/icon.png\n\n### Kotlin ###\n.kotlin\n\n### Eclipse ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\nbin/\n!**/src/main/**/bin/\n!**/src/test/**/bin/\n\n### NetBeans ###\n/nbproject/private/\n/nbbuild/\n/dist/\n/nbdist/\n/.nb-gradle/\n\n### VS Code ###\n.vscode/\n\n### Mac OS ###\n.DS_Store\n\n### Minecraft ###\nrun/\nplugins/\n"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"Default\" />\n  </state>\n</component>"
  },
  {
    "path": "AGENTS.md",
    "content": "# BetterModel AGENT GUIDE (for Codex, Gemini, and other LLM agents)\n\nThis document defines repository-wide operating rules for automated contributors.\nScope: entire repository unless a deeper AGENTS.md overrides this file.\n\n## 0) Mission and Priorities\n\n- Primary mission: act as a maintenance assistant for safe updates and long-term stability.\n- Prioritize documentation quality, API clarity, and compatibility over refactoring.\n- Do not introduce architectural churn or speculative abstractions.\n- Prefer minimal, reversible changes that preserve current behavior.\n\n---\n\n## 1) Module Responsibilities\n\n### MUST\n- Respect module boundaries:\n  - `api`: architecture contracts, data definitions, pure logic, interfaces, domain exceptions.\n  - `core`: implementation of `api`, orchestration, external I/O integration.\n  - `platform/*`: platform packaging and platform-specific business integration using `core`.\n  - `nms/*`: Minecraft-version-specific low-level adapters only.\n- Keep dependency direction one-way where possible: `api -> core -> platform/nms` usage semantics.\n- If a feature needs cross-module changes, start from `api` contract, then implement in `core`, then bind in `platform`.\n\n### FORBIDDEN\n- Do not place platform/runtime-specific details in `api`.\n- Do not move business logic into `nms` unless version coupling is unavoidable.\n- Do not bypass module contracts with ad-hoc cross-module shortcuts.\n\n---\n\n## 2) Language and File Placement Rules\n\n### MUST\n- Use Modern Java (language level 25) and Kotlin.\n- `api` module is Java-only for production code (Kotlin DSL build scripts are allowed).\n- Non-`api` modules should be Kotlin-first unless existing local code is explicitly Java-bound (e.g., mixin/accessor interop).\n- Follow existing package root `kr.toxicity.model...` and module-specific suffixes.\n\n### FORBIDDEN\n- Do not add Kotlin source files to `api/src/main`.\n- Do not introduce new language stacks or code generators without explicit request.\n\n---\n\n## 3) Allowed vs Forbidden Change Rules\n\n### MUST\n- Preserve existing behavior unless the task explicitly requests behavior change.\n- Match local style of touched files (imports, naming, nullability annotations, formatting).\n- Keep diffs small and scoped to the requested issue.\n- Update/extend Javadoc when touching public Java APIs.\n\n### RECOMMENDED\n- Prefer additive changes over invasive rewrites.\n- Prefer extension/composition over inheritance.\n\n### FORBIDDEN\n- No drive-by refactors.\n- No broad renaming/reformatting-only commits mixed with functional changes.\n- No “cleanup” changes unrelated to the requested task.\n\n---\n\n## 4) Code Patterns and Conventions\n\n### Java rules\n\n#### MUST\n- Add English Javadoc for public API types/methods.\n- Use `var` for local variables where legal and clear.\n- When using primitive-key/value collections, prefer fastutil specialized types over boxed `java.util` alternatives.\n- Declare classes `final` when inheritance is not intended.\n- Use `of(...)` for factory method naming.\n- Prefer enum-based singleton pattern when a singleton is required.\n\n#### RECOMMENDED\n- Prefer `record` for immutable data carriers.\n- Interface-based API should expose its own factory method where practical.\n\n#### DISCOURAGED\n- Inheritance-heavy designs.\n\n#### FORBIDDEN\n- Do not depend on Kotlin classes from Java in `api`.\n\n### Kotlin rules\n\n#### MUST\n- For Boolean arguments in calls, use named arguments (`parameter = value`) where available.\n- Keep Kotlin style concise and explicit; avoid hidden side effects.\n\n#### RECOMMENDED\n- Avoid `abstract class` when interface + default methods or delegation works.\n\n#### FORBIDDEN\n- No Kotlin production code in `api` module.\n\n---\n\n## 5) Javadoc Policy\n\n### MUST\n- English only.\n- Use `@since` matching current `gradle.properties` project version policy.\n- Include `@return` for non-void methods.\n- For records, document all components using `@param` at type level.\n- Include `@throws` for throw-capable public methods.\n- Public API docs must include a usage example (`@example` or code block).\n- Keep terminology consistent with project domain (Molang, item_display, packet-based rendering, etc.).\n\n---\n\n## 6) Exception Handling Policy\n\n### MUST\n- Domain exceptions must be declared in `api` and reused by `core/platform`.\n- Use unchecked exceptions for domain errors (`RuntimeException` subclasses).\n- Prefer explicit domain exception types over anonymous `RuntimeException` for new logic.\n\n### FORBIDDEN\n- No new checked exceptions in public API surface unless explicitly requested.\n- No swallowing exceptions without logging/context.\n\n---\n\n## 7) Architectural Boundary Enforcement\n\n### MUST\n- Enforce separation of concerns:\n  - parsing/model contracts in `api`\n  - orchestration and state management in `core`\n  - runtime platform hooks in `platform`\n  - protocol/version internals in `nms`\n- Keep NMS version modules behaviorally equivalent unless version-specific differences are required.\n\n### FORBIDDEN\n- Do not leak platform classes into generic API contracts.\n- Do not duplicate core logic in multiple platform modules.\n\n---\n\n## 8) Change Management Principles\n\n### MUST\n- Before coding, identify smallest viable patch.\n- Keep commits logically atomic.\n- Use conventional commits (`docs:`, `fix:`, `refactor:`, `chore:`).\n- Document why a change is necessary, not only what changed.\n\n### RECOMMENDED\n- Prefer one concern per PR.\n- If migration is unavoidable, provide transitional compatibility notes.\n\n---\n\n## 9) Minimal Change Principle (Anti-Overengineering)\n\n### MUST\n- Solve the concrete problem only.\n- Reuse existing utilities/conventions before introducing new abstractions.\n- Avoid framework-like internal layers unless repeatedly justified by current code.\n\n### FORBIDDEN\n- No speculative extensibility.\n- No premature optimization without evidence.\n\n---\n\n## 10) Backward Compatibility Policy\n\n### MUST\n- Preserve existing public API behavior by default.\n- Treat changes to signatures, semantics, serialization shape, config keys, and command contracts as breaking.\n- For unavoidable breaking changes: document impact, migration path, and versioning intent.\n\n### RECOMMENDED\n- Prefer deprecation + transition period over hard removal.\n\n---\n\n## 11) Dependency Introduction Policy\n\n### MUST\n- Prefer existing dependencies and in-repo utilities.\n- Any new dependency requires clear justification: purpose, scope, size, and maintenance cost.\n- Add dependency versions through central version catalog/patterns already in use.\n\n### FORBIDDEN\n- Do not add dependencies for trivial utilities already present in JDK/Kotlin stdlib/current stack.\n- Do not introduce overlapping libraries providing the same capability.\n\n---\n\n## 12) Diff and PR Format Guidelines\n\n### MUST\n- Keep PRs reviewable: focused scope, clear title, concise rationale.\n- Include:\n  - summary of changes\n  - impacted modules\n  - compatibility notes\n  - tests/checks run\n- Separate mechanical formatting from logic changes whenever possible.\n\n### RECOMMENDED\n- Use checklist format for validation and risk points.\n\n---\n\n## 13) Test Policy\n\n### MUST\n- Run the most relevant checks for touched modules.\n- Validate both compile-time and behavior-level impact where feasible.\n- If tests are absent, run targeted build/lint/verification tasks and report limitations.\n\n### RECOMMENDED\n- Prefer adding focused tests only when directly related to changed behavior.\n- Keep test fixtures lightweight; do not add broad test frameworks without request.\n\n---\n\n## 14) Existing Code Precedence Rule\n\n### MUST\n- Existing local code patterns take precedence over generic best practices when conflicts arise.\n- Follow deeper-scope AGENTS.md if present.\n- If repository reality conflicts with this guide, preserve behavior first and propose policy alignment separately.\n\n### FORBIDDEN\n- Do not force style unification across untouched files.\n\n---\n\n## 15) Final Operational Checklist\n\nBefore finishing, verify:\n- Scope is minimal and task-aligned.\n- Module boundaries are respected.\n- Public API docs are updated (if touched).\n- Exception policy and compatibility policy are respected.\n- Changes are validated with relevant checks.\n- PR description contains rationale, risks, and verification results.\n"
  },
  {
    "path": "BANNER.md",
    "content": "<div align=\"center\">  \n\n![](https://github.com/user-attachments/assets/89e191ba-ed4f-44ab-bb98-634cfe568dca)\n\n# BetterModel\n*- Modern Bedrock model engine for Minecraft Java Edition -*\n\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/modrinth_vector.svg)](https://modrinth.com/plugin/bettermodel)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/hangar_vector.svg)](https://hangar.papermc.io/toxicity188/BetterModel)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/github_vector.svg)](https://github.com/toxicity188/BetterModel)\n\n</div>\n\n* * *\n![](https://github.com/user-attachments/assets/5a6c1a8c-6fe2-4a67-a10e-e63e40825d35)\n![](https://github.com/user-attachments/assets/ff515577-6a72-48ba-9943-81f00dddb375)\n\n* * *\n<sub>(In BlockBench / In Minecraft)</sub>\n\n# ✨ What is BetterModel?\n\n**BetterModel** is a server-based engine that provides runtime BlockBench model rendering & animating for Minecraft Java Edition.  \n\nIt implements **fully server-side 3D models** by using an item display entity packet.\n\n- Importing Generic BlockBench model `.bbmodel`\n- Auto-generating resource pack\n- Playing animation\n- Syncing with base entity\n- Custom hit box\n- 12-limb player animation\n\n## 🚀 Comparison with ModelEngine\nThe main reason I created it is:\n- To reduce network cost—MEG’s network optimization is outdated and insufficient for modern servers.\n- To enable faster updates—We can’t afford to wait for MEG’s slow update cycle anymore.\n- To provide a more flexible API—MEG is closed-source with a very limited API, which makes extending or integrating difficult.\n- To restore vanilla behavior-MEG breaks several vanilla entity features and physics, which this project aims to fix.\n\nAlso, you can refer [my document](https://github.com/toxicity188/BetterModel/wiki/Compare-with-ModelEngine) to compare both ModelEngine and BetterModel.\n\n## 🌎 Generic BlockBench model with animation\n![](https://github.com/user-attachments/assets/b4e69aef-a446-4ac3-b84e-eb42fe4f069d)\n* * *\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/social/youtube-singular_vector.svg)](https://youtu.be/f3U7Lmo3aA8?si=SnglL0YKn20CrR7Y)  \nBetterModel supports Generic BlockBench models with full animation.\n\n#### Custom hitbox\n* * *\n![](https://github.com/user-attachments/assets/94aee9ed-9c2f-4975-92c4-3ea84ae31d24)\n* * *\nBetterModel provides **custom hitbox** both client and server. (tracking animation rotation)\n\n#### MythicMobs support\n* * *\n![](https://github.com/user-attachments/assets/eb2d64ef-7b6e-4306-8c31-d92d0266dbac)\n* * *\nLike MEG, BetterModel supports **MythicMobs**, you can use some MEG's mechanics in BetterModel too.\n\n## 💡 Player model with animation\n![](https://github.com/user-attachments/assets/0c13bec2-898f-4d9a-a709-10e0571337f3)\n![](https://github.com/user-attachments/assets/034dd64c-6889-4a01-961d-e69679b1c71b)\n* * *\nBetterModel supports **player model with using user's custom skin without textures**.\n\n## 📚 Official wiki\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/documentation/ghpages_vector.svg)](https://github.com/toxicity188/BetterModel/wiki)\n\n## 🏗️ Supported environment\n\n[![](https://img.shields.io/badge/minecraft-1.21.4%7E26.1.x-8FCA5C?style=for-the-badge)](https://www.minecraft.net/en-us/download/server)\n[![](https://img.shields.io/badge/java-25%7E-ED8B00?style=for-the-badge)](https://adoptium.net/)\n\n### Bukkit\n[![](https://img.shields.io/badge/folia-supported-blue?style=for-the-badge)](https://papermc.io/downloads/folia)\n\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/supported/paper_vector.svg)](https://papermc.io/downloads/paper)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/supported/purpur_vector.svg)](https://purpurmc.org/download/purpur)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/supported/spigot_vector.svg)](https://www.spigotmc.org/)\n\n### Mod\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/supported/fabric_vector.svg)](https://fabricmc.net/)\n\n\n## 🌈 My community\n[![](https://discord.com/api/guilds/1012718460297551943/widget.png?style=banner2)](https://discord.com/invite/rePyFESDbk)\n\n## 📊 Project Stats (plugin)\n[![](https://bstats.org/signatures/bukkit/BetterModel.svg)](https://bstats.org/plugin/bukkit/BetterModel/24237)\n\n## 💖 Support my project\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/buymeacoffee-singular_vector.svg)](https://buymeacoffee.com/toxicity188)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/ghsponsors-singular_vector.svg)](https://github.com/sponsors/toxicity188)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/paypal-singular_vector.svg)](https://www.paypal.com/paypalme/toxicity188?country.x=KR&locale.x=en_US)\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2024–2026 toxicity188\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "LICENSE_HEADER",
    "content": "This source file is part of BetterModel.\nCopyright (c) ${CREATION_YEAR} toxicity188\nLicensed under the MIT License.\nSee LICENSE.md file for full license text.\n\n#year_selection file\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n![](https://github.com/user-attachments/assets/89e191ba-ed4f-44ab-bb98-634cfe568dca)\n\n# BetterModel\n*- Modern Bedrock model engine for Minecraft Java Edition -*\n\n[![](https://img.shields.io/maven-central/v/io.github.toxicity188/bettermodel-api?style=flat-square&logo=sonatype)](https://central.sonatype.com/artifact/io.github.toxicity188/bettermodel-api)\n[![](https://img.shields.io/github/actions/workflow/status/toxicity188/BetterModel/publish.yml?style=flat-square)](https://modrinth.com/plugin/bettermodel/versions)\n[![](https://img.shields.io/github/issues/toxicity188/BetterModel?style=flat-square&logo=github)](https://github.com/toxicity188/BetterModel/issues)\n[![](https://img.shields.io/bstats/servers/24237?style=flat-square)](https://bstats.org/plugin/bukkit/BetterModel/24237)\n\n</div>\n\n* * *\n![](https://github.com/user-attachments/assets/5a6c1a8c-6fe2-4a67-a10e-e63e40825d35)\n![](https://github.com/user-attachments/assets/ff515577-6a72-48ba-9943-81f00dddb375)\n\n* * *\n<sub>(In BlockBench / In Minecraft)</sub>\n\n# ✨ Introduction\n\n**BetterModel** is a server-based engine that provides runtime BlockBench model rendering & animating for Minecraft Java Edition.\n\nIt implements **fully server-side 3D models** by using an item display entity packet.\n\n- Importing Generic BlockBench model `.bbmodel`\n- Auto-generating resource pack\n- Playing animation\n- Syncing with base entity\n- Custom hit box\n- 12-limb player animation\n\n<details>\n<summary>In-Game Screenshots</summary>\n\n![](https://github.com/user-attachments/assets/b4e69aef-a446-4ac3-b84e-eb42fe4f069d)  \n![](https://github.com/user-attachments/assets/94aee9ed-9c2f-4975-92c4-3ea84ae31d24)  \n![](https://github.com/user-attachments/assets/eb2d64ef-7b6e-4306-8c31-d92d0266dbac)  \n![](https://github.com/user-attachments/assets/034dd64c-6889-4a01-961d-e69679b1c71b)\n</details>\n\n## 🚀 Key Features & Focus\nBetterModel aims to be a reliable engine that provides stable, high-quality animations for Paper-based high-traffic servers.\n\n- **Stability First**: We take a conservative approach to feature expansion. By avoiding the implementation of features that are difficult to maintain or have limited use cases, we focus on providing a stable API and ensuring overall operational safety.\n- **Performance Optimized**: Our goal is to minimize runtime computation, memory footprint, and network overhead. Through asynchronous design and optimized packet handling, we ensure the engine runs efficiently even under heavy server loads.\n- **Tailored for Large-scale Servers**: We provide essential features specifically designed for high-population servers and MMORPG content creation.\n  - **Per-player Animation**: Individual animation control tailored to each player's perspective.\n  - **Player Model Animation**: Support for sophisticated 12-limb animations based on player models.\n\n## 📚 Wiki\n[![](https://img.shields.io/badge/GitHub%20Wiki-181717?logo=github&logoColor=white)](https://github.com/toxicity188/BetterModel/wiki)\n[![](https://deepwiki.com/badge.svg)](https://deepwiki.com/toxicity188/BetterModel)\n\n## 🛠️ Build info\n\n[![](https://img.shields.io/badge/minecraft-1.21.4%7E26.1.x-8FCA5C)](https://www.minecraft.net/en-us/download/server)\n[![](https://img.shields.io/badge/java-25%7E-ED8B00)](https://adoptium.net/)\n\n#### Build\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/built-with/gradle_vector.svg)](https://gradle.org/)\n\n`./gradlew build`: Builds all jars  \n`./gradlew shadowJar`: Builds plugin jar  \n`./gradlew javadocJar`: Builds Javadoc jar  \n`./gradlew runServer`: Runs Paper test server with test plugin\n\n#### Library\n- [Kotlin stdlib](https://github.com/JetBrains/kotlin): modern functional programming\n- [semver4j](https://github.com/semver4j/semver4j): semver parser\n- [cloud](https://github.com/Incendo/cloud-minecraft): command\n- [adventure](https://github.com/KyoriPowered/adventure): component\n- [stable player display](https://github.com/bradleyq/stable_player_display): player animation\n- [caffeine](https://github.com/ben-manes/caffeine): concurrent map cache\n- [DynamicUV](https://github.com/toxicity188/DynamicUV): player model\n- [ArmorModel](https://github.com/toxicity188/ArmorModel): armor in player model\n- [java-mesh](https://github.com/toxicity188/java-mesh): mesh rendering\n- [molang-compiler](https://github.com/Ocelot5836/molang-compiler): compiling and evaluating molang expression\n- [libby](https://github.com/AlessioDP/libby): runtime library downloader\n\n#### Tested Bukkit Server Platform\n- [Paper](https://papermc.io/downloads/paper)\n- [Purpur](https://purpurmc.org/download/purpur)\n- [Spigot](https://www.spigotmc.org/)\n- [Folia](https://papermc.io/downloads/folia)\n- [Leaf](https://www.leafmc.one/download)\n- [Canvas](https://canvasmc.io/downloads/canvas)\n\n#### Tested Mod Server Platform\n- [Fabric Loader](https://fabricmc.net/)\n\n## 💻 API\n\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/maven-central_vector.svg)](https://central.sonatype.com/artifact/io.github.toxicity188/bettermodel)\n\n> [!NOTE]\\\n> For more detailed API specifications, please refer to our [GitHub Wiki](https://github.com/toxicity188/BetterModel/wiki/API-example).\n\n<details open>\n<summary>Gradle (Kotlin)</summary>\n\n#### Release\n```kotlin\nrepositories {\n    mavenCentral()\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION\") // bukkit(spigot, paper, etc) api\n    //api(\"io.github.toxicity188:bettermodel-fabric:VERSION\") // mod(fabric)\n}\n```\n\n#### Snapshot\n```kotlin\nrepositories {\n    maven(\"https://maven.pkg.github.com/toxicity188/BetterModel\") {\n        credentials {\n            username = YOUR_GITHUB_USERNAME\n            password = YOUR_GITHUB_TOKEN\n        }\n    }\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION-SNAPSHOT\") // bukkit(spigot, paper, etc) api\n    //api(\"io.github.toxicity188:bettermodel-fabric:VERSION-SNAPSHOT\") // mod(fabric)\n}\n```\n</details>\n\n<details>\n<summary>Gradle (Groovy)</summary>\n\n#### Release\n```groovy\nrepositories {\n    mavenCentral()\n    maven 'https://maven.blamejared.com/' // For transitive dependency in bettermodel-fabric\n    maven 'https://maven.nucleoid.xyz/' // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly 'io.github.toxicity188:bettermodel-bukkit-api:VERSION' // bukkit(spigot, paper, etc) api\n    //api 'io.github.toxicity188:bettermodel-fabric:VERSION' // mod(fabric)\n}\n```\n\n#### Snapshot\n```groovy\nrepositories {\n    maven {\n        url \"https://maven.pkg.github.com/toxicity188/BetterModel\"\n        credentials {\n            username = YOUR_GITHUB_USERNAME\n            password = YOUR_GITHUB_TOKEN\n        }\n    }\n    maven 'https://maven.blamejared.com/' // For transitive dependency in bettermodel-fabric\n    maven 'https://maven.nucleoid.xyz/' // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly 'io.github.toxicity188:bettermodel-bukkit-api:VERSION-SNAPSHOT' // bukkit(spigot, paper, etc) api\n    //api 'io.github.toxicity188:bettermodel-fabric:VERSION-SNAPSHOT' // mod(fabric)\n}\n```\n</details>\n\n<details>\n<summary>Maven</summary>\n\n#### Release\n```xml\n<repositories>\n    <repository>\n        <id>central</id>\n        <url>https://repo.maven.apache.org/maven2</url>\n    </repository>\n</repositories>\n\n<dependencies>\n    <dependency>\n        <groupId>io.github.toxicity188</groupId>\n        <artifactId>bettermodel-bukkit-api</artifactId>\n        <version>VERSION</version>\n        <scope>provided</scope>\n    </dependency>\n</dependencies>\n```\n\n#### Snapshot\n```xml\n<repositories>\n    <repository>\n        <id>github</id>\n        <url>https://maven.pkg.github.com/toxicity188/BetterModel</url>\n    </repository>\n</repositories>\n\n<dependencies>\n    <dependency>\n        <groupId>io.github.toxicity188</groupId>\n        <artifactId>bettermodel-api</artifactId>\n        <version>VERSION-SNAPSHOT</version>\n        <scope>provided</scope>\n    </dependency>\n    <dependency>\n        <groupId>io.github.toxicity188</groupId>\n        <artifactId>bettermodel-bukkit-api</artifactId>\n        <version>VERSION-SNAPSHOT</version>\n        <scope>provided</scope>\n    </dependency>\n</dependencies>\n```\n</details>\n\n<details>\n<summary>Example code</summary>\n\n#### Gets some model or limb\n```java\nBetterModel.model(\"demon_knight\"); //A model file in BetterModel/models (for general model with saving)\nBetterModel.limb(\"steve\"); //A model file in BetterModel/players (for player model with no saveing)\n\nBetterModel.modelOrNull(\"demon_knight\"); //general model or null\nBetterModel.limbOrNull(\"steve\"); //player model or null\n```\n\n#### Creates model (entity)\n```java\nEntityTracker tracker = BetterModel.model(\"demon_knight\")\n    .map(r -> r.getOrCreate(BukkitAdapter.adapt(entity))) //Gets or creates entity tracker by this renderer to some entity.\n    .orElse(null);\n```\n```java\nEntityTracker tracker = BetterModel.model(\"demon_knight\")\n    .map(r -> r.create(BukkitAdapter.adapt(entity), TrackerModifier.DEFAULT, t -> t.update(TrackerUpdateAction.tint(0x0026FF)))) //Creates entity tracker with pre-spawn task.\n    .orElse(null);\n```\n\n#### Creates model (dummy)\n```java\nDummyTracker tracker = BetterModel.model(\"demon_knight\")\n    .map(r -> r.create(BukkitAdapter.adapt(location))) //Creates some dummy tracker to this location.\n    .orElse(null);\n```\n```java\nDummyTracker tracker = BetterModel.limb(\"steve\")\n    .map(r -> r.create(BukkitAdapter.adapt(location), ModelProfile.of(BukkitAdapter.adapt(player)))) //Creates some dummy tracker to this location and player's skin profile.\n    .orElse(null);\n```\n\n#### Update some tracker's display data\n```java\nBetterModel.model(\"demon_knight\")\n    .map(r -> r.create(BukkitAdapter.adapt(entity), TrackerModifier.DEFAULT, t -> {\n        t.update(TrackerUpdateAction.tint(rgb)); //Tint\n        t.update(TrackerUpdateAction.enchant(true), bone -> true); //Enchant with predicate\n    }))\n    .ifPresent(tracker -> tracker.update(TrackerUpdateAction.composite( //Composite\n        TrackerUpdateAction.brightness(15, 15)  //Brightness\n        TrackerUpdateAction.billboard(Display.Billboard.CENTER) //Billboard\n    )));\n}\n```\n\n</details>\n\n## 💬 Community\n[![](https://discord.com/api/guilds/1012718460297551943/widget.png?style=banner2)](https://discord.com/invite/rePyFESDbk)\n\n## 💖 Support\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/buymeacoffee-singular_vector.svg)](https://buymeacoffee.com/toxicity188)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/ghsponsors-singular_vector.svg)](https://github.com/sponsors/toxicity188)\n[![](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/paypal-singular_vector.svg)](https://www.paypal.com/paypalme/toxicity188?country.x=KR&locale.x=en_US)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "## Security Policy\n\nBetterModel is a server-side 3D model engine that operates in an isolated environment without direct connections to external clients. As such, the risk of traditional security vulnerabilities is minimal.\n\n#### Key Points\n\n- 🔒 **No Client Connection**  \n  BetterModel does not expose any network interface or accept input from external clients.\n\n- 📦 **No Data Leakage**  \n  Animation and bone data are handled on the server and are never sent to the client directly. Only processed vector packets are transmitted, which do not include raw model data.\n\n- 🧱 **Model Privacy**  \n  Your model files are converted into a Minecraft-compatible resource pack. During this conversion process, most of the original model information is stripped or transformed, minimizing the risk of leakage.\n"
  },
  {
    "path": "api/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.publish)\n}\n\ndependencies {\n    compileOnly(libs.bundles.minecraft)\n}\n"
  },
  {
    "path": "api/bukkit-api/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.publish)\n    alias(libs.plugins.convention.bukkit)\n}\n\ndependencies {\n    api(project(\":bettermodel-api\"))\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/BetterModelBukkit.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.BetterModelPlatform;\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter;\nimport kr.toxicity.model.api.bukkit.scheduler.BukkitModelScheduler;\nimport org.jetbrains.annotations.NotNull;\n\nimport static kr.toxicity.model.api.util.ReflectionUtil.classExists;\n\n/**\n * Represents the Bukkit-specific platform interface for BetterModel.\n * <p>\n * This interface extends {@link BetterModelPlatform} to provide Bukkit-specific implementations\n * for scheduling and entity adaptation.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BetterModelBukkit extends BetterModelPlatform {\n\n    /**\n     * Checks if the server is running on the Folia platform.\n     * @since 2.0.0\n     */\n    boolean IS_FOLIA = classExists(\"io.papermc.paper.threadedregions.RegionizedServer\");\n    /**\n     * Checks if the server is running on the Purpur platform.\n     * @since 2.0.0\n     */\n    boolean IS_PURPUR = classExists(\"org.purpurmc.purpur.PurpurConfig\");\n    /**\n     * Checks if the server is running on the Paper platform (or a fork like Purpur/Folia).\n     * @since 2.0.0\n     */\n    boolean IS_PAPER = IS_PURPUR || IS_FOLIA || classExists(\"io.papermc.paper.configuration.PaperConfigurations\");\n\n    /**\n     * Returns the current {@link BetterModelBukkit} instance.\n     *\n     * @return the current platform instance\n     * @since 2.0.0\n     */\n    static @NotNull BetterModelBukkit platform() {\n        return (BetterModelBukkit) BetterModel.platform();\n    }\n\n    /**\n     * Returns the Bukkit-specific scheduler.\n     *\n     * @return the scheduler\n     * @since 2.0.0\n     */\n    @Override\n    @NotNull BukkitModelScheduler scheduler();\n\n    /**\n     * Returns the Bukkit-specific adapter.\n     *\n     * @return the adapter\n     * @since 2.0.0\n     */\n    @Override\n    @NotNull BukkitAdapter adapter();\n\n    /**\n     * Returns the Bukkit-specific event bus.\n     *\n     * @return the event bus\n     * @since 2.0.0\n     */\n    @Override\n    @NotNull BukkitModelEventBus eventBus();\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/BukkitModelEventBus.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit;\n\nimport kr.toxicity.model.api.BetterModelEventBus;\nimport kr.toxicity.model.api.bukkit.event.BukkitEventApplication;\nimport kr.toxicity.model.api.event.ModelEvent;\nimport kr.toxicity.model.api.event.ModelEventListener;\nimport org.bukkit.plugin.Plugin;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Consumer;\n\n/**\n * A Bukkit-specific extension of the {@link BetterModelEventBus}.\n * <p>\n * This interface provides convenience methods for subscribing to events using a Bukkit {@link Plugin} instance.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BukkitModelEventBus extends BetterModelEventBus {\n\n    /**\n     * Subscribes a consumer to a specific event type, associated with a Bukkit plugin.\n     *\n     * @param plugin the plugin that subscribes to the event\n     * @param eventClass the class of the event to subscribe to\n     * @param consumer the consumer to handle the event\n     * @param <T> the type of the event\n     * @return a listener handle that can be used to unregister the subscription\n     * @since 2.0.0\n     */\n    @NotNull\n    default <T extends ModelEvent> ModelEventListener subscribe(@NotNull Plugin plugin, @NotNull Class<T> eventClass, @NotNull Consumer<T> consumer) {\n        return subscribe(BukkitEventApplication.of(plugin), eventClass, consumer);\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/entity/BaseBukkitEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.entity;\n\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter;\nimport kr.toxicity.model.api.bukkit.platform.BukkitEntity;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.persistence.PersistentDataHolder;\nimport org.bukkit.persistence.PersistentDataType;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\n\n/**\n * Represents a Bukkit-specific entity adapter.\n * <p>\n * This interface extends {@link BaseEntity} and {@link PersistentDataHolder} to provide\n * access to the underlying Bukkit entity and its persistent data container.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BaseBukkitEntity extends BaseEntity, PersistentDataHolder {\n\n    /**\n     * The namespaced key used for storing tracker data in the entity's persistent data container.\n     * @since 2.0.0\n     */\n    @NotNull\n    NamespacedKey TRACKING_ID = Objects.requireNonNull(NamespacedKey.fromString(\"bettermodel_tracker\"));\n\n    /**\n     * Returns the underlying Bukkit entity.\n     *\n     * @return the Bukkit entity\n     * @since 2.0.0\n     */\n    default @NotNull Entity entity() {\n        return ((BukkitEntity) platform()).source();\n    }\n\n    /**\n     * Returns the item in the entity's main hand.\n     *\n     * @return the main hand item\n     * @since 2.0.0\n     */\n    @Override\n    default @NotNull TransformedItemStack mainHand() {\n        if (entity() instanceof LivingEntity livingEntity) {\n            var equipment = livingEntity.getEquipment();\n            if (equipment != null) return TransformedItemStack.of(BukkitAdapter.adapt(equipment.getItemInMainHand()));\n        }\n        return TransformedItemStack.empty();\n    }\n\n    /**\n     * Returns the item in the entity's offhand.\n     *\n     * @return the offhand item\n     * @since 2.0.0\n     */\n    @Override\n    default @NotNull TransformedItemStack offHand() {\n        if (entity() instanceof LivingEntity livingEntity) {\n            var equipment = livingEntity.getEquipment();\n            if (equipment != null) return TransformedItemStack.of(BukkitAdapter.adapt(equipment.getItemInOffHand()));\n        }\n        return TransformedItemStack.empty();\n    }\n\n    /**\n     * Retrieves the model data stored in the entity's persistent data container.\n     *\n     * @return the model data string, or null if not present\n     * @since 2.0.0\n     */\n    default @Nullable String modelData() {\n        return getPersistentDataContainer().get(TRACKING_ID, PersistentDataType.STRING);\n    }\n\n    /**\n     * Stores the model data in the entity's persistent data container.\n     *\n     * @param modelData the model data string, or null to remove it\n     * @since 2.0.0\n     */\n    default void modelData(@Nullable String modelData) {\n        var container = getPersistentDataContainer();\n        if (modelData == null) container.remove(TRACKING_ID);\n        else container.set(TRACKING_ID, PersistentDataType.STRING, modelData);\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/entity/BaseBukkitPlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.entity;\n\nimport kr.toxicity.model.api.bukkit.platform.BukkitPlayer;\nimport kr.toxicity.model.api.entity.BasePlayer;\nimport org.bukkit.entity.Player;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a Bukkit-specific player adapter.\n * <p>\n * This interface extends {@link BaseBukkitEntity} and {@link BasePlayer} to provide\n * access to the underlying Bukkit player.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BaseBukkitPlayer extends BaseBukkitEntity, BasePlayer {\n    /**\n     * Returns the underlying Bukkit player.\n     *\n     * @return the Bukkit player\n     * @since 2.0.0\n     */\n    @Override\n    default @NotNull Player entity() {\n        return ((BukkitPlayer) platform()).source();\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/event/BetterModelBukkitEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.event;\n\nimport kr.toxicity.model.api.event.ModelEvent;\nimport org.bukkit.Bukkit;\nimport org.bukkit.event.Event;\nimport org.bukkit.event.HandlerList;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * A wrapper class that adapts {@link ModelEvent} to Bukkit's {@link Event} system.\n * <p>\n * This allows Bukkit plugins to listen for BetterModel events using the standard Bukkit event API.\n * The underlying {@link ModelEvent} is lazily initialized when accessed.\n * </p>\n *\n * @since 2.0.0\n */\npublic final class BetterModelBukkitEvent extends Event {\n\n    private static final HandlerList HANDLER_LIST = new HandlerList();\n\n    private final Class<? extends ModelEvent> eventClass;\n    private final @NotNull Supplier<? extends ModelEvent> supplier;\n    private volatile ModelEvent source;\n\n    /**\n     * Creates a new BetterModelBukkitEvent.\n     *\n     * @param eventClass the class of the model event\n     * @param supplier a supplier that creates the model event\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public BetterModelBukkitEvent(@NotNull Class<? extends ModelEvent> eventClass, @NotNull Supplier<? extends ModelEvent> supplier) {\n        super(!Bukkit.isPrimaryThread());\n        this.eventClass = eventClass;\n        this.supplier = supplier;\n    }\n\n    /**\n     * Checks if the wrapped event is an instance of the specified class.\n     *\n     * @param eventClass the class to check against\n     * @param <T> the type of the event\n     * @return true if the wrapped event is assignable to the class\n     * @since 2.0.0\n     */\n    public <T extends ModelEvent> boolean is(@NotNull Class<T> eventClass) {\n        return eventClass.isAssignableFrom(this.eventClass);\n    }\n\n    /**\n     * Casts the wrapped event to the specified class if possible.\n     * <p>\n     * This method initializes the underlying event if it hasn't been created yet.\n     * </p>\n     *\n     * @param eventClass the class to cast to\n     * @param <T> the type of the event\n     * @return the cast event, or null if the cast is not possible\n     * @since 2.0.0\n     */\n    public <T extends ModelEvent> @Nullable T as(@NotNull Class<T> eventClass) {\n        if (!is(eventClass)) return null;\n        var event = source;\n        if (event == null) {\n            synchronized (this) {\n                event = source;\n                if (event == null) event = source = supplier.get();\n            }\n        }\n        return eventClass.cast(event);\n    }\n\n    /**\n     * Executes a consumer if the wrapped event is of the specified type.\n     *\n     * @param eventClass the class to check against\n     * @param consumer the consumer to execute\n     * @param <T> the type of the event\n     * @since 2.0.0\n     */\n    public <T extends ModelEvent> void as(@NotNull Class<T> eventClass, @NotNull Consumer<? super T> consumer) {\n        var get = as(eventClass);\n        if (get != null) consumer.accept(get);\n    }\n\n    /**\n     * Returns the underlying model event, if initialized.\n     *\n     * @return the model event, or null if not yet initialized\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public @Nullable ModelEvent source() {\n        return source;\n    }\n\n    @Override\n    public @NotNull HandlerList getHandlers() {\n        return HANDLER_LIST;\n    }\n\n    /**\n     * Returns the handler list for this event.\n     *\n     * @return the handler list\n     * @since 2.0.0\n     */\n    public static HandlerList getHandlerList() {\n        return HANDLER_LIST;\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/event/BukkitEventApplication.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.event;\n\nimport kr.toxicity.model.api.event.ModelEventApplication;\nimport org.bukkit.plugin.Plugin;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * An implementation of {@link ModelEventApplication} for Bukkit plugins.\n * <p>\n * This record holds a weak reference to a Bukkit plugin to prevent memory leaks\n * and checks if the plugin is enabled.\n * </p>\n *\n * @param name the name of the plugin\n * @param pluginRef a weak reference to the plugin instance\n * @since 2.0.0\n */\npublic record BukkitEventApplication(@NotNull String name, @NotNull WeakReference<Plugin> pluginRef) implements ModelEventApplication {\n\n    /**\n     * Creates a new BukkitEventApplication for the given plugin.\n     *\n     * @param plugin the Bukkit plugin\n     * @return the event application wrapper\n     * @since 2.0.0\n     */\n    public static @NotNull BukkitEventApplication of(@NotNull Plugin plugin) {\n        return new BukkitEventApplication(plugin.getName(), new WeakReference<>(plugin));\n    }\n\n    @Override\n    public boolean isEnabled() {\n        var get = pluginRef().get();\n        return get != null && get.isEnabled();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof BukkitEventApplication that)) return false;\n        return name.equals(that.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return name.hashCode();\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitAdapter.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit;\nimport kr.toxicity.model.api.platform.*;\nimport org.bukkit.*;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Provides an adapter for converting Bukkit objects to BetterModel platform objects.\n * <p>\n * This class implements {@link PlatformAdapter} and offers static utility methods for adapting\n * entities, players, items, locations, and worlds.\n * </p>\n *\n * @since 2.0.0\n */\npublic final class BukkitAdapter implements PlatformAdapter {\n\n    /**\n     * Adapts a Bukkit entity to a {@link PlatformEntity}.\n     *\n     * @param entity the Bukkit entity\n     * @return the platform entity\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformEntity adapt(@NotNull Entity entity) {\n        return new BukkitEntity(entity);\n    }\n\n    /**\n     * Adapts a Bukkit living entity to a {@link PlatformLivingEntity}.\n     *\n     * @param livingEntity the Bukkit living entity\n     * @return the platform living entity\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformLivingEntity adapt(@NotNull LivingEntity livingEntity) {\n        return new BukkitLivingEntity(livingEntity);\n    }\n\n    /**\n     * Adapts a Bukkit offline player to a {@link PlatformOfflinePlayer}.\n     *\n     * @param player the Bukkit offline player\n     * @return the platform offline player\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformOfflinePlayer adapt(@NotNull OfflinePlayer player) {\n        return new BukkitOfflinePlayer(player);\n    }\n\n    /**\n     * Adapts a Bukkit player to a {@link PlatformPlayer}.\n     *\n     * @param player the Bukkit player\n     * @return the platform player\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformPlayer adapt(@NotNull Player player) {\n        return new BukkitPlayer(player);\n    }\n\n    /**\n     * Adapts a Bukkit item stack to a {@link PlatformItemStack}.\n     *\n     * @param itemStack the Bukkit item stack\n     * @return the platform item stack\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformItemStack adapt(@NotNull ItemStack itemStack) {\n        return new BukkitItemStack(itemStack);\n    }\n\n    /**\n     * Adapts a Bukkit location to a {@link PlatformLocation}.\n     *\n     * @param location the Bukkit location\n     * @return the platform location\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformLocation adapt(@NotNull Location location) {\n        return new BukkitLocation(location);\n    }\n\n    /**\n     * Adapts a Bukkit world to a {@link PlatformWorld}.\n     *\n     * @param world the Bukkit world\n     * @return the platform world\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformWorld adapt(@NotNull World world) {\n        return new BukkitWorld(world);\n    }\n\n    @Override\n    public @Nullable PlatformPlayer player(@NotNull UUID uuid) {\n        var bukkit = Bukkit.getPlayer(uuid);\n        return bukkit != null ? adapt(bukkit) : null;\n    }\n\n    @Override\n    public @NotNull PlatformOfflinePlayer offlinePlayer(@NotNull UUID uuid) {\n        return adapt(Bukkit.getOfflinePlayer(uuid));\n    }\n\n    @Override\n    public int serverViewDistance() {\n        return Bukkit.getViewDistance();\n    }\n\n    @Override\n    public boolean isTickThread() {\n        return Bukkit.isPrimaryThread();\n    }\n\n    @Override\n    public boolean isRegionSafe() {\n        return !BetterModelBukkit.IS_FOLIA || isTickThread();\n    }\n\n    @Override\n    public @NotNull PlatformItemStack air() {\n        return adapt(new ItemStack(Material.AIR));\n    }\n\n    @Override\n    public @NotNull PlatformLocation zero() {\n        return adapt(new Location(null, 0, 0, 0));\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.bukkit.entity.Entity;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.UUID;\n\n/**\n * Represents a Bukkit entity wrapped as a {@link PlatformEntity}.\n *\n * @since 2.0.0\n */\n@ToString\n@EqualsAndHashCode\npublic class BukkitEntity implements PlatformEntity {\n\n    private final Entity source;\n\n    /**\n     * Creates a new BukkitEntity wrapper.\n     *\n     * @param source the source Bukkit entity\n     * @since 2.0.0\n     */\n    public BukkitEntity(@NotNull Entity source) {\n        this.source = source;\n    }\n\n    /**\n     * Returns the underlying Bukkit entity.\n     *\n     * @return the source entity\n     * @since 2.0.0\n     */\n    public Entity source() {\n        return source;\n    }\n\n    @Override\n    public @NotNull UUID uuid() {\n        return source.getUniqueId();\n    }\n\n    @Override\n    public @NotNull PlatformLocation location() {\n        return BukkitAdapter.adapt(source.getLocation());\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitItemStack.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport kr.toxicity.model.api.platform.PlatformNamespace;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.inventory.ItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents a Bukkit item stack wrapped as a {@link PlatformItemStack}.\n *\n * @param source the source Bukkit item stack\n * @since 2.0.0\n */\npublic record BukkitItemStack(@NotNull ItemStack source) implements PlatformItemStack {\n    @Override\n    public boolean isAir() {\n        return source.getType().isAir() || source.getAmount() <= 0;\n    }\n\n    @Override\n    public @NotNull PlatformItemStack enchant(boolean enchant) {\n        var meta = source.getItemMeta();\n        if (meta == null) return this;\n        meta.setEnchantmentGlintOverride(enchant);\n        source.setItemMeta(meta);\n        return this;\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public @NotNull PlatformItemStack modelData(int customModelData, @Nullable PlatformNamespace namespace) {\n        var meta = source.getItemMeta();\n        if (meta == null) return this;\n        meta.setCustomModelData(customModelData);\n        meta.setItemModel(namespace == null ? null : new NamespacedKey(namespace.namespace(), namespace.path()));\n        source.setItemMeta(meta);\n        return this;\n    }\n\n    @Override\n    public @NotNull PlatformItemStack clone() {\n        return BukkitAdapter.adapt(source.clone());\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitLivingEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.platform.PlatformLivingEntity;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport org.bukkit.entity.LivingEntity;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a Bukkit living entity wrapped as a {@link PlatformLivingEntity}.\n *\n * @since 2.0.0\n */\npublic class BukkitLivingEntity extends BukkitEntity implements PlatformLivingEntity {\n\n    /**\n     * Creates a new BukkitLivingEntity wrapper.\n     *\n     * @param source the source Bukkit living entity\n     * @since 2.0.0\n     */\n    public BukkitLivingEntity(@NotNull LivingEntity source) {\n        super(source);\n    }\n\n    /**\n     * Returns the underlying Bukkit living entity.\n     *\n     * @return the source living entity\n     * @since 2.0.0\n     */\n    @Override\n    public LivingEntity source() {\n        return (LivingEntity) super.source();\n    }\n\n    @Override\n    public @NotNull PlatformLocation eyeLocation() {\n        return BukkitAdapter.adapt(source().getEyeLocation());\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitLocation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformWorld;\nimport kr.toxicity.model.api.scheduler.ModelTask;\nimport org.bukkit.Location;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents a Bukkit location wrapped as a {@link PlatformLocation}.\n *\n * @param source the source Bukkit location\n * @since 2.0.0\n */\npublic record BukkitLocation(@NotNull Location source) implements PlatformLocation {\n\n    @Override\n    public @NotNull PlatformWorld world() {\n        return BukkitAdapter.adapt(source.getWorld());\n    }\n\n    @Override\n    public double x() {\n        return source.getX();\n    }\n\n    @Override\n    public double y() {\n        return source.getY();\n    }\n\n    @Override\n    public double z() {\n        return source.getZ();\n    }\n\n    @Override\n    public float pitch() {\n        return source.getPitch();\n    }\n\n    @Override\n    public float yaw() {\n        return source.getYaw();\n    }\n\n    @Override\n    public @NotNull PlatformLocation add(double x, double y, double z) {\n        return BukkitAdapter.adapt(source.clone().add(x, y, z));\n    }\n\n    @Override\n    public @Nullable ModelTask task(@NotNull Runnable runnable) {\n        return BetterModelBukkit.platform().scheduler().task(source, runnable);\n    }\n\n    @Override\n    public @Nullable ModelTask taskLater(long delay, @NotNull Runnable runnable) {\n        return BetterModelBukkit.platform().scheduler().taskLater(source, delay, runnable);\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitOfflinePlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.platform.PlatformOfflinePlayer;\nimport org.bukkit.OfflinePlayer;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Represents a Bukkit offline player wrapped as a {@link PlatformOfflinePlayer}.\n *\n * @param source the source Bukkit offline player\n * @since 2.0.0\n */\npublic record BukkitOfflinePlayer(@NotNull OfflinePlayer source) implements PlatformOfflinePlayer {\n    @Override\n    public @NotNull UUID uuid() {\n        return source.getUniqueId();\n    }\n\n    @Override\n    public @Nullable String name() {\n        return source.getName();\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitPlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.bukkit.entity.Player;\nimport org.jetbrains.annotations.NotNull;\n\n\n/**\n * Represents a Bukkit player wrapped as a {@link PlatformPlayer}.\n *\n * @since 2.0.0\n */\npublic final class BukkitPlayer extends BukkitLivingEntity implements PlatformPlayer {\n\n    /**\n     * Creates a new BukkitPlayer wrapper.\n     *\n     * @param source the source Bukkit player\n     * @since 2.0.0\n     */\n    public BukkitPlayer(@NotNull Player source) {\n        super(source);\n    }\n\n    /**\n     * Returns the underlying Bukkit player.\n     *\n     * @return the source player\n     * @since 2.0.0\n     */\n    public @NotNull Player source() {\n        return (Player) super.source();\n    }\n\n    @Override\n    public @NotNull String name() {\n        return source().getName();\n    }\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/platform/BukkitWorld.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.platform;\n\nimport kr.toxicity.model.api.platform.PlatformWorld;\nimport org.bukkit.World;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a Bukkit world wrapped as a {@link PlatformWorld}.\n *\n * @param source the source Bukkit world\n * @since 2.0.0\n */\npublic record BukkitWorld(@NotNull World source) implements PlatformWorld {\n}\n"
  },
  {
    "path": "api/bukkit-api/src/main/java/kr/toxicity/model/api/bukkit/scheduler/BukkitModelScheduler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bukkit.scheduler;\n\nimport kr.toxicity.model.api.scheduler.ModelScheduler;\nimport kr.toxicity.model.api.scheduler.ModelTask;\nimport org.bukkit.Location;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents a Bukkit-specific scheduler for model tasks.\n * <p>\n * This interface extends {@link ModelScheduler} to provide methods for scheduling tasks\n * that are synchronized with specific locations (e.g., for Folia compatibility).\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BukkitModelScheduler extends ModelScheduler {\n\n    /**\n     * Schedules a task to run on the next tick, synchronized with the given location.\n     *\n     * @param location the location to synchronize with\n     * @param runnable the task to run\n     * @return the scheduled task, or null if scheduling failed\n     * @since 2.0.0\n     */\n    @Nullable ModelTask task(@NotNull Location location, @NotNull Runnable runnable);\n\n    /**\n     * Schedules a task to run after a delay, synchronized with the given location.\n     *\n     * @param location the location to synchronize with\n     * @param delay the delay in ticks\n     * @param runnable the task to run\n     * @return the scheduled task, or null if scheduling failed\n     * @since 2.0.0\n     */\n    @Nullable ModelTask taskLater(@NotNull Location location, long delay, @NotNull Runnable runnable);\n}\n"
  },
  {
    "path": "api/mod-api/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.publish)\n    id(\"net.neoforged.moddev\")\n}\n\ndependencies {\n    api(project(\":bettermodel-api\"))\n}\n\nneoForge {\n    enable {\n        neoFormVersion = libs.versions.neoform.get()\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/BetterModelMod.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.BetterModelPlatform;\nimport kr.toxicity.model.api.mod.scheduler.ModModelScheduler;\nimport net.minecraft.server.MinecraftServer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents the Mod-specific platform interface for BetterModel.\n * <p>\n * This interface extends {@link BetterModelPlatform} to provide access to the underlying\n * Minecraft server instance and region holder for thread-safe operations.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BetterModelMod extends BetterModelPlatform {\n\n    /**\n     * Returns the current {@link BetterModelMod} instance.\n     *\n     * @return the current platform instance\n     * @since 2.0.0\n     */\n    static @NotNull BetterModelMod platform() {\n        return (BetterModelMod) BetterModel.platform();\n    }\n\n    /**\n     * Returns the underlying Minecraft server instance.\n     *\n     * @return the Minecraft server\n     * @since 2.0.0\n     */\n    @NotNull MinecraftServer server();\n\n    /**\n     * Returns the Mod-specific scheduler.\n     *\n     * @return the scheduler\n     * @since 2.0.0\n     */\n    @Override\n    @NotNull ModModelScheduler scheduler();\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/entity/BaseModEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.entity;\n\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport net.minecraft.world.entity.Entity;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a Mod-specific entity adapter.\n * <p>\n * This interface extends {@link BaseEntity} to provide access to the underlying NMS entity.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BaseModEntity extends BaseEntity {\n\n    /**\n     * Returns the underlying NMS entity.\n     *\n     * @return the NMS entity\n     * @since 2.0.0\n     */\n    default @NotNull Entity entity() {\n        return (Entity) handle();\n    }\n\n    /**\n     * Sets the underlying NMS entity.\n     *\n     * @param entity the NMS entity\n     * @since 2.0.0\n     */\n    void entity(@NotNull Entity entity);\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/entity/BaseModPlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.entity;\n\nimport kr.toxicity.model.api.entity.BasePlayer;\nimport net.minecraft.server.level.ServerPlayer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a Mod-specific player adapter.\n * <p>\n * This interface extends {@link BaseModEntity} and {@link BasePlayer} to provide\n * access to the underlying NMS server player.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BaseModPlayer extends BaseModEntity, BasePlayer {\n\n    /**\n     * Returns the underlying NMS server player.\n     *\n     * @return the server player\n     * @since 2.0.0\n     */\n    @Override\n    default @NotNull ServerPlayer entity() {\n        return (ServerPlayer) handle();\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModAdapter.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport com.mojang.authlib.GameProfile;\nimport kr.toxicity.model.api.mod.BetterModelMod;\nimport kr.toxicity.model.api.platform.*;\nimport net.minecraft.server.MinecraftServer;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.server.network.ServerPlayerConnection;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Provides an adapter for converting Mod/NMS objects to BetterModel platform objects.\n * <p>\n * This class implements {@link PlatformAdapter} and offers static utility methods for adapting\n * entities, players, items, and worlds.\n * </p>\n *\n * @since 2.0.0\n */\npublic final class ModAdapter implements PlatformAdapter {\n\n    /**\n     * Adapts an NMS entity to a {@link PlatformEntity}.\n     *\n     * @param entity the NMS entity\n     * @return the platform entity\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformEntity adapt(@NotNull Entity entity) {\n        return ModEntity.of(entity);\n    }\n\n    /**\n     * Adapts an NMS living entity to a {@link PlatformLivingEntity}.\n     *\n     * @param livingEntity the NMS living entity\n     * @return the platform living entity\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformLivingEntity adapt(@NotNull LivingEntity livingEntity) {\n        return ModLivingEntity.of(livingEntity);\n    }\n\n    /**\n     * Adapts an NMS player connection to a {@link PlatformPlayer}.\n     *\n     * @param connection the NMS player connection\n     * @return the platform player\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformPlayer adapt(@NotNull ServerPlayerConnection connection) {\n        return ModPlayer.of(connection);\n    }\n\n    /**\n     * Adapts an NMS server player to a {@link PlatformPlayer}.\n     *\n     * @param player the NMS server player\n     * @return the platform player\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformPlayer adapt(@NotNull ServerPlayer player) {\n        return adapt(player.connection);\n    }\n\n    /**\n     * Adapts a UUID to a {@link PlatformOfflinePlayer}.\n     *\n     * @param uuid the player UUID\n     * @return the platform offline player\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformOfflinePlayer adapt(@NotNull UUID uuid) {\n        return ModOfflinePlayer.of(uuid, null);\n    }\n\n    /**\n     * Adapts a GameProfile to a {@link PlatformOfflinePlayer}.\n     *\n     * @param profile the game profile\n     * @return the platform offline player\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformOfflinePlayer adapt(@NotNull GameProfile profile) {\n        return ModOfflinePlayer.of(profile.id(), profile.name());\n    }\n\n    /**\n     * Adapts an NMS item stack to a {@link PlatformItemStack}.\n     *\n     * @param itemStack the NMS item stack\n     * @return the platform item stack\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformItemStack adapt(@NotNull ItemStack itemStack) {\n        return ModItemStack.of(itemStack);\n    }\n\n    /**\n     * Adapts an NMS level to a {@link PlatformWorld}.\n     *\n     * @param world the NMS level\n     * @return the platform world\n     * @since 2.0.0\n     */\n    public static @NotNull PlatformWorld adapt(@NotNull Level world) {\n        return ModWorld.of(world);\n    }\n\n    @Override\n    public int serverViewDistance() {\n        return server().getPlayerList().getViewDistance();\n    }\n\n    @Override\n    public boolean isTickThread() {\n        return server().isSameThread();\n    }\n\n    @Override\n    public boolean isRegionSafe() {\n        return true;\n    }\n\n    @Override\n    public @Nullable PlatformPlayer player(@NotNull UUID uuid) {\n        var player = server().getPlayerList().getPlayer(uuid);\n        return player == null ? null : adapt(player);\n    }\n\n    @Override\n    public @NotNull PlatformOfflinePlayer offlinePlayer(@NotNull UUID uuid) {\n        var profile = server().services().profileResolver().fetchById(uuid).orElse(null);\n        return profile == null ? adapt(uuid) : adapt(profile);\n    }\n\n    @Override\n    public @NotNull PlatformItemStack air() {\n        return adapt(ItemStack.EMPTY);\n    }\n\n    @Override\n    public @NotNull PlatformLocation zero() {\n        return ModLocation.of(null, 0, 0, 0);\n    }\n\n    private @NotNull MinecraftServer server() {\n        return BetterModelMod.platform().server();\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport net.minecraft.world.entity.Entity;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.UUID;\n\n/**\n * Represents a Mod entity wrapped as a {@link PlatformEntity}.\n *\n * @param source the source NMS entity\n * @since 2.0.0\n */\npublic record ModEntity(@NotNull Entity source) implements PlatformEntity {\n    @ApiStatus.Internal\n    public ModEntity {\n    }\n\n    /**\n     * Creates a ModEntity from the source.\n     *\n     * @param source the source entity\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModEntity of(@NotNull Entity source) {\n        return new ModEntity(source);\n    }\n\n    @Override\n    public @NotNull UUID uuid() {\n        return source.getUUID();\n    }\n\n    @Override\n    public @NotNull PlatformLocation location() {\n        return ModLocation.of(source);\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModItemStack.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport kr.toxicity.model.api.platform.PlatformNamespace;\nimport net.minecraft.core.component.DataComponents;\nimport net.minecraft.resources.Identifier;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.item.component.CustomModelData;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\n\n/**\n * Represents a Mod item stack wrapped as a {@link PlatformItemStack}.\n *\n * @param source the source NMS item stack\n * @since 2.0.0\n */\npublic record ModItemStack(@NotNull ItemStack source) implements PlatformItemStack {\n    @ApiStatus.Internal\n    public ModItemStack {\n    }\n\n    /**\n     * Creates a ModItemStack from the source.\n     *\n     * @param source the source item stack\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModItemStack of(@NotNull ItemStack source) {\n        return new ModItemStack(source);\n    }\n\n    @Override\n    public boolean isAir() {\n        return source.isEmpty();\n    }\n\n    @Override\n    public @NotNull PlatformItemStack enchant(boolean enchant) {\n        source.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, enchant);\n        return this;\n    }\n\n    @Override\n    public @NotNull PlatformItemStack modelData(int customModelData, @Nullable PlatformNamespace namespace) {\n        source.set(\n            DataComponents.CUSTOM_MODEL_DATA,\n            new CustomModelData(List.of((float) customModelData), List.of(), List.of(), List.of())\n        );\n        source.set(\n            DataComponents.ITEM_MODEL,\n            namespace == null ? null : Identifier.fromNamespaceAndPath(namespace.namespace(), namespace.path())\n        );\n        return this;\n    }\n\n    @Override\n    public @NotNull PlatformItemStack clone() {\n        return ModAdapter.adapt(source.copy());\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModLivingEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.platform.PlatformLivingEntity;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport net.minecraft.world.entity.LivingEntity;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.UUID;\n\n/**\n * Represents a Mod living entity wrapped as a {@link PlatformLivingEntity}.\n *\n * @param source the source NMS living entity\n * @since 2.0.0\n */\npublic record ModLivingEntity(@NotNull LivingEntity source) implements PlatformLivingEntity {\n    @ApiStatus.Internal\n    public ModLivingEntity {\n    }\n\n    /**\n     * Creates a ModLivingEntity from the source.\n     *\n     * @param source the source living entity\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModLivingEntity of(@NotNull LivingEntity source) {\n        return new ModLivingEntity(source);\n    }\n\n    @Override\n    public @NotNull UUID uuid() {\n        return source.getUUID();\n    }\n\n    @Override\n    public @NotNull PlatformLocation location() {\n        return ModLocation.of(source);\n    }\n\n    @Override\n    public @NotNull PlatformLocation eyeLocation() {\n        return ModLocation.ofEye(source);\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModLocation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.mod.BetterModelMod;\nimport kr.toxicity.model.api.mod.scheduler.ModModelScheduler;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformWorld;\nimport kr.toxicity.model.api.scheduler.ModelTask;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.phys.Vec3;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents a Mod location wrapped as a {@link PlatformLocation}.\n *\n * @param level the NMS level\n * @param x the x coordinate\n * @param y the y coordinate\n * @param z the z coordinate\n * @param pitch the pitch\n * @param yaw the yaw\n * @since 2.0.0\n */\npublic record ModLocation(@Nullable Level level, double x, double y, double z, float pitch, float yaw) implements PlatformLocation {\n    @ApiStatus.Internal\n    public ModLocation {\n    }\n\n    /**\n     * Creates a ModLocation from the coordinates.\n     *\n     * @param level the NMS level\n     * @param x     the x coordinate\n     * @param y     the y coordinate\n     * @param z     the z coordinate\n     * @param pitch the pitch\n     * @param yaw   the yaw\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModLocation of(@Nullable Level level, double x, double y, double z, float pitch, float yaw) {\n        return new ModLocation(\n            level,\n            x,\n            y,\n            z,\n            pitch,\n            yaw\n        );\n    }\n\n    /**\n     * Creates a ModLocation from the coordinates with zero pitch and yaw.\n     *\n     * @param level the NMS level\n     * @param x     the x coordinate\n     * @param y     the y coordinate\n     * @param z     the z coordinate\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModLocation of(@Nullable Level level, double x, double y, double z) {\n        return new ModLocation(\n            level,\n            x,\n            y,\n            z,\n            0.0f,\n            0.0f\n        );\n    }\n\n    /**\n     * Creates a ModLocation from the position vector.\n     *\n     * @param level    the NMS level\n     * @param position the position vector\n     * @param pitch    the pitch\n     * @param yaw      the yaw\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModLocation of(@Nullable Level level, Vec3 position, float pitch, float yaw) {\n        return new ModLocation(\n            level,\n            position.x,\n            position.y,\n            position.z,\n            pitch,\n            yaw\n        );\n    }\n\n    /**\n     * Creates a ModLocation from the position vector with zero pitch and yaw.\n     *\n     * @param level    the NMS level\n     * @param position the position vector\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModLocation of(@Nullable Level level, Vec3 position) {\n        return new ModLocation(\n            level,\n            position.x,\n            position.y,\n            position.z,\n            0.0f,\n            0.0f\n        );\n    }\n\n    /**\n     * Creates a ModLocation from an entity's position.\n     *\n     * @param entity the entity\n     * @return the location\n     * @since 2.0.0\n     */\n    public static @NotNull ModLocation of(@NotNull Entity entity) {\n        return new ModLocation(\n            entity.level(),\n            entity.getX(),\n            entity.getY(),\n            entity.getZ(),\n            entity.getXRot(),\n            entity.getYRot()\n        );\n    }\n\n    /**\n     * Creates a ModLocation from an entity's eye position.\n     *\n     * @param entity the entity\n     * @return the eye location\n     * @since 2.0.0\n     */\n    public static @NotNull ModLocation ofEye(@NotNull Entity entity) {\n        return new ModLocation(\n            entity.level(),\n            entity.getX(),\n            entity.getEyeY(),\n            entity.getZ(),\n            entity.getXRot(),\n            entity.getYRot()\n        );\n    }\n\n    @Override\n    public @NotNull PlatformWorld world() {\n        if (level == null) {\n            throw new IllegalStateException(\"level is not set\");\n        }\n\n        return ModAdapter.adapt(level);\n    }\n\n    @Override\n    public @NotNull PlatformLocation add(double x, double y, double z) {\n        return new ModLocation(\n            this.level,\n            this.x + x,\n            this.y + y,\n            this.z + z,\n            this.pitch,\n            this.yaw\n        );\n    }\n\n    @Override\n    public @Nullable ModelTask task(@NotNull Runnable runnable) {\n        return scheduler().task(runnable);\n    }\n\n    @Override\n    public @Nullable ModelTask taskLater(long delay, @NotNull Runnable runnable) {\n        return scheduler().taskLater(delay, runnable);\n    }\n\n    private @NotNull ModModelScheduler scheduler() {\n        return BetterModelMod.platform().scheduler();\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModOfflinePlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.platform.PlatformOfflinePlayer;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Represents a Mod offline player wrapped as a {@link PlatformOfflinePlayer}.\n *\n * @param uuid the player UUID\n * @param name the player name, or null if unknown\n * @since 2.0.0\n */\npublic record ModOfflinePlayer(@NotNull UUID uuid, @Nullable String name) implements PlatformOfflinePlayer {\n    @ApiStatus.Internal\n    public ModOfflinePlayer {\n    }\n\n    /**\n     * Creates a ModOfflinePlayer from the UUID and name.\n     *\n     * @param uuid the player uuid\n     * @param name the player name\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModOfflinePlayer of(@NotNull UUID uuid, @Nullable String name) {\n        return new ModOfflinePlayer(uuid, name);\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModPlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport net.minecraft.server.network.ServerPlayerConnection;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.UUID;\n\n/**\n * Represents a Mod player wrapped as a {@link PlatformPlayer}.\n *\n * @param source the source NMS player connection\n * @since 2.0.0\n */\npublic record ModPlayer(@NotNull ServerPlayerConnection source) implements PlatformPlayer {\n    @ApiStatus.Internal\n    public ModPlayer {\n    }\n\n    /**\n     * Creates a ModPlayer from the source.\n     *\n     * @param source the source player connection\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModPlayer of(@NotNull ServerPlayerConnection source) {\n        return new ModPlayer(source);\n    }\n\n    @Override\n    public @NotNull UUID uuid() {\n        return source.getPlayer().getUUID();\n    }\n\n    @Override\n    public @NotNull PlatformLocation location() {\n        return ModLocation.of(source.getPlayer());\n    }\n\n    @Override\n    public @NotNull PlatformLocation eyeLocation() {\n        return ModLocation.ofEye(source.getPlayer());\n    }\n\n    @Override\n    public @NotNull String name() {\n        return source.getPlayer().getPlainTextName();\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModRegionHolder.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.platform.PlatformRegionHolder;\n\n/**\n * Represents a Mod-specific region holder for managing thread-safe operations.\n * <p>\n * This interface extends {@link PlatformRegionHolder} to provide Mod-specific functionality\n * for scheduling tasks within specific regions or contexts.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface ModRegionHolder extends PlatformRegionHolder {\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/platform/ModWorld.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.platform;\n\nimport kr.toxicity.model.api.platform.PlatformWorld;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a Fabric world wrapped as a {@link PlatformWorld}.\n *\n * @param level the source NMS level\n * @since 2.0.0\n */\npublic record ModWorld(@NotNull Level level) implements PlatformWorld {\n    @ApiStatus.Internal\n    public ModWorld {\n    }\n\n    /**\n     * Creates a FabricWorld from the level.\n     *\n     * @param level the source level\n     * @return the instance\n     * @since 2.0.0\n     */\n    public static @NotNull ModWorld of(@NotNull Level level) {\n        return new ModWorld(level);\n    }\n}\n"
  },
  {
    "path": "api/mod-api/src/main/java/kr/toxicity/model/api/mod/scheduler/ModModelScheduler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mod.scheduler;\n\nimport kr.toxicity.model.api.scheduler.ModelScheduler;\nimport kr.toxicity.model.api.scheduler.ModelTask;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents a Mod-specific scheduler for model tasks.\n * <p>\n * This interface extends {@link ModelScheduler} to provide methods for scheduling tasks\n * within the Mod environment.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface ModModelScheduler extends ModelScheduler {\n\n    /**\n     * Schedules a task to run on the next tick.\n     *\n     * @param runnable the task to run\n     * @return the scheduled task, or null if scheduling failed\n     * @since 2.0.0\n     */\n    @Nullable ModelTask task(@NotNull Runnable runnable);\n\n    /**\n     * Schedules a task to run after a delay.\n     *\n     * @param delay the delay in ticks\n     * @param runnable the task to run\n     * @return the scheduled task, or null if scheduling failed\n     * @since 2.0.0\n     */\n    @Nullable ModelTask taskLater(long delay, @NotNull Runnable runnable);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/BetterModel.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api;\n\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.nms.NMS;\nimport kr.toxicity.model.api.nms.PlayerChannelHandler;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.*;\n\n/**\n * The main entry point for the BetterModel API.\n * <p>\n * This class provides static access to the platform instance, configuration, model managers,\n * NMS handlers, and entity registries. It serves as a service provider for interacting with the BetterModel engine.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class BetterModel {\n\n    /**\n     * Private initializer to prevent instantiation.\n     */\n    private BetterModel() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * The singleton platform instance.\n     */\n    private static BetterModelPlatform instance;\n\n    /**\n     * Returns the platform configuration manager.\n     *\n     * @return the configuration manager\n     * @since 1.15.2\n     */\n    public static @NotNull BetterModelConfig config() {\n        return platform().config();\n    }\n\n    /**\n     * Retrieves a model renderer by its name, wrapped in an Optional.\n     *\n     * @param name the name of the model\n     * @return an optional containing the renderer if found\n     * @since 1.15.2\n     */\n    public static @NotNull Optional<ModelRenderer> model(@NotNull String name) {\n        return Optional.ofNullable(modelOrNull(name));\n    }\n\n    /**\n     * Retrieves a model renderer by its name, or null if not found.\n     *\n     * @param name the name of the model\n     * @return the renderer, or null\n     * @since 1.15.2\n     */\n    public static @Nullable ModelRenderer modelOrNull(@NotNull String name) {\n        return platform().modelManager().model(name);\n    }\n\n    /**\n     * Retrieves a player limb renderer by its name, wrapped in an Optional.\n     *\n     * @param name the name of the limb model\n     * @return an optional containing the renderer if found\n     * @since 1.15.2\n     */\n    public static @NotNull Optional<ModelRenderer> limb(@NotNull String name) {\n        return Optional.ofNullable(limbOrNull(name));\n    }\n\n    /**\n     * Retrieves a player limb renderer by its name, or null if not found.\n     *\n     * @param name the name of the limb model\n     * @return the renderer, or null\n     * @since 1.15.2\n     */\n    public static @Nullable ModelRenderer limbOrNull(@NotNull String name) {\n        return platform().modelManager().limb(name);\n    }\n\n    /**\n     * Retrieves a player channel handler by the player's UUID.\n     *\n     * @param uuid the player's UUID\n     * @return an optional containing the channel handler if found\n     * @since 1.15.2\n     */\n    public static @NotNull Optional<PlayerChannelHandler> player(@NotNull UUID uuid) {\n        return Optional.ofNullable(platform().playerManager().player(uuid));\n    }\n\n    /**\n     * Retrieves an entity tracker registry by the entity's UUID.\n     *\n     * @param uuid the entity's UUID\n     * @return an optional containing the registry if found\n     * @since 1.15.2\n     */\n    public static @NotNull Optional<EntityTrackerRegistry> registry(@NotNull UUID uuid) {\n        return Optional.ofNullable(registryOrNull(uuid));\n    }\n\n    /**\n     * Retrieves an entity tracker registry for a Bukkit entity.\n     *\n     * @param entity the Bukkit entity\n     * @return an optional containing the registry if found\n     * @since 1.15.2\n     */\n    public static @NotNull Optional<EntityTrackerRegistry> registry(@NotNull PlatformEntity entity) {\n        return Optional.ofNullable(registryOrNull(entity));\n    }\n\n    /**\n     * Retrieves an entity tracker registry for a base entity.\n     *\n     * @param entity the base entity\n     * @return an optional containing the registry if found\n     * @since 1.15.2\n     */\n    public static @NotNull Optional<EntityTrackerRegistry> registry(@NotNull BaseEntity entity) {\n        return Optional.ofNullable(registryOrNull(entity));\n    }\n\n    /**\n     * Retrieves an entity tracker registry by the entity's UUID, or null if not found.\n     *\n     * @param uuid the entity's UUID\n     * @return the registry, or null\n     * @since 1.15.2\n     */\n    public static @Nullable EntityTrackerRegistry registryOrNull(@NotNull UUID uuid) {\n        return EntityTrackerRegistry.registry(uuid);\n    }\n\n    /**\n     * Retrieves an entity tracker registry for a Bukkit entity, or null if not found.\n     *\n     * @param entity the Bukkit entity\n     * @return the registry, or null\n     * @since 1.15.2\n     */\n    public static @Nullable EntityTrackerRegistry registryOrNull(@NotNull PlatformEntity entity) {\n        return registryOrNull(nms().adapt(entity));\n    }\n\n    /**\n     * Retrieves an entity tracker registry for a base entity, or null if not found.\n     *\n     * @param entity the base entity\n     * @return the registry, or null\n     * @since 1.15.2\n     */\n    public static @Nullable EntityTrackerRegistry registryOrNull(@NotNull BaseEntity entity) {\n        return EntityTrackerRegistry.registry(entity);\n    }\n\n    /**\n     * Returns a collection of all loaded model renderers.\n     *\n     * @return an unmodifiable collection of models\n     * @since 1.15.2\n     */\n    public static @NotNull @Unmodifiable Collection<ModelRenderer> models() {\n        return platform().modelManager().models();\n    }\n\n    /**\n     * Returns a collection of all loaded player limb renderers.\n     *\n     * @return an unmodifiable collection of limb models\n     * @since 1.15.2\n     */\n    public static @NotNull @Unmodifiable Collection<ModelRenderer> limbs() {\n        return platform().modelManager().limbs();\n    }\n\n    /**\n     * Returns a set of all loaded model names.\n     *\n     * @return an unmodifiable set of model keys\n     * @since 1.15.2\n     */\n    public static @NotNull @Unmodifiable Set<String> modelKeys() {\n        return platform().modelManager().modelKeys();\n    }\n\n    /**\n     * Returns a set of all loaded player limb model names.\n     *\n     * @return an unmodifiable set of limb keys\n     * @since 1.15.2\n     */\n    public static @NotNull @Unmodifiable Set<String> limbKeys() {\n        return platform().modelManager().limbKeys();\n    }\n\n    /**\n     * Returns the singleton instance of the BetterModel platform.\n     *\n     * @return the platform instance\n     * @throws NullPointerException if the platform has not been initialized\n     * @since 2.0.0\n     */\n    public static @NotNull BetterModelPlatform platform() {\n        return Objects.requireNonNull(instance, \"BetterModel hasn't been initialized yet!\");\n    }\n\n    /**\n     * Returns the NMS handler instance.\n     *\n     * @return the NMS handler\n     * @since 1.15.2\n     */\n    public static @NotNull NMS nms() {\n        return platform().nms();\n    }\n\n    /**\n     * Returns the event bus.\n     *\n     * @return the event bus\n     * @since 2.0.0\n     */\n    public static @NotNull BetterModelEventBus eventBus() {\n        return platform().eventBus();\n    }\n\n    /**\n     * Registers the platform instance.\n     * <p>\n     * This method is intended for internal use only during platform initialization.\n     * </p>\n     *\n     * @param instance the platform instance\n     * @throws RuntimeException if an instance is already registered\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public static void register(@NotNull BetterModelPlatform instance) {\n        Objects.requireNonNull(instance, \"instance cannot be null.\");\n        if (BetterModel.instance == instance) throw new RuntimeException(\"Duplicated instance.\");\n        BetterModel.instance = instance;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/BetterModelConfig.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api;\n\nimport kr.toxicity.model.api.config.DebugConfig;\nimport kr.toxicity.model.api.config.IndicatorConfig;\nimport kr.toxicity.model.api.config.ModuleConfig;\nimport kr.toxicity.model.api.config.PackConfig;\nimport kr.toxicity.model.api.mount.MountController;\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Supplier;\n\n/**\n * Represents the main configuration interface for BetterModel.\n * <p>\n * This interface provides access to various configuration settings, including debug options,\n * pack generation settings, module toggles, and runtime behaviors.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface BetterModelConfig {\n\n    /**\n     * Returns the debug configuration.\n     *\n     * @return the debug config\n     * @since 1.15.2\n     */\n    @NotNull DebugConfig debug();\n\n    /**\n     * Returns the indicator configuration.\n     *\n     * @return the indicator config\n     * @since 1.15.2\n     */\n    @NotNull IndicatorConfig indicator();\n\n    /**\n     * Returns the module configuration.\n     *\n     * @return the module config\n     * @since 1.15.2\n     */\n    @NotNull ModuleConfig module();\n\n    /**\n     * Returns the resource pack configuration.\n     *\n     * @return the pack config\n     * @since 1.15.2\n     */\n    @NotNull PackConfig pack();\n\n    /**\n     * Checks if metrics collection is enabled.\n     *\n     * @return true if enabled, false otherwise\n     * @since 1.15.2\n     */\n    boolean metrics();\n\n    /**\n     * Checks if sight tracing (visibility checking) is enabled.\n     *\n     * @return true if enabled, false otherwise\n     * @since 1.15.2\n     */\n    boolean sightTrace();\n\n    /**\n     * Checks if BetterModel should attempt to merge its resource pack with external plugins/mods.\n     *\n     * @return true to merge, false otherwise\n     * @since 1.15.2\n     */\n    boolean mergeWithExternalResources();\n\n    /**\n     * Returns a supplier for the platform item stack used as the base for model items.\n     *\n     * @return a supplier providing the target item stack\n     * @since 2.0.0\n     */\n    @NotNull Supplier<PlatformItemStack> item();\n\n    /**\n     * Returns the item model string identifier used for the resource pack target item.\n     *\n     * @return the item model string\n     * @since 2.0.0\n     */\n    @NotNull String itemModel();\n\n    /**\n     * Returns the namespace used for the target item.\n     *\n     * @return the item namespace\n     * @since 1.15.2\n     */\n    @NotNull String itemNamespace();\n\n    /**\n     * Returns the maximum range for sight tracing.\n     *\n     * @return the max range\n     * @since 1.15.2\n     */\n    double maxSight();\n\n    /**\n     * Returns the minimum range for sight tracing.\n     *\n     * @return the min range\n     * @since 1.15.2\n     */\n    double minSight();\n\n    /**\n     * Returns the namespace used for the generated resource pack.\n     *\n     * @return the namespace\n     * @since 1.15.2\n     */\n    @NotNull String namespace();\n\n    /**\n     * Returns the type of resource pack generation (Folder, Zip, or None).\n     *\n     * @return the pack type\n     * @since 1.15.2\n     */\n    @NotNull PackType packType();\n\n    /**\n     * Returns the location of the build folder for resource packs.\n     *\n     * @return the build folder path\n     * @since 1.15.2\n     */\n    @NotNull String buildFolderLocation();\n\n    /**\n     * Checks if model trackers should follow the source entity's invisibility status.\n     *\n     * @return true to follow invisibility, false otherwise\n     * @since 1.15.2\n     */\n    boolean followMobInvisibility();\n\n    /**\n     * Checks if Purpur's AFK API should be used.\n     *\n     * @return true to use Purpur AFK, false otherwise\n     * @since 1.15.2\n     */\n    boolean usePurpurAfk();\n\n    /**\n     * Checks if version update notifications should be sent to OPs on join.\n     *\n     * @return true to send notifications, false otherwise\n     * @since 1.15.2\n     */\n    boolean versionCheck();\n\n    /**\n     * Returns the default mount controller used for entities.\n     *\n     * @return the default mount controller\n     * @see kr.toxicity.model.api.mount.MountControllers\n     * @since 1.15.2\n     */\n    @NotNull MountController defaultMountController();\n\n    /**\n     * Returns the interpolation frame time (lerp) in milliseconds.\n     *\n     * @return the lerp frame time\n     * @since 1.15.2\n     */\n    int lerpFrameTime();\n\n    /**\n     * Checks if inventory swap packets should be cancelled for players with active models.\n     *\n     * @return true to cancel, false otherwise\n     * @since 1.15.2\n     */\n    boolean cancelPlayerModelInventory();\n\n    /**\n     * Returns the delay in ticks before hiding a player's model after they become invisible.\n     *\n     * @return the hide delay\n     * @since 1.15.2\n     */\n    long playerHideDelay();\n\n    /**\n     * Returns the threshold size for packet bundling.\n     *\n     * @return the packet bundling size\n     * @since 1.15.2\n     */\n    int packetBundlingSize();\n\n    /**\n     * Checks if strict loading mode is enabled.\n     * <p>\n     * Strict loading causes the platform to fail fast on model loading errors.\n     * </p>\n     *\n     * @return true if strict loading is enabled, false otherwise\n     * @since 1.15.2\n     */\n    boolean enableStrictLoading();\n\n    /**\n     * Enumerates the types of resource pack generation.\n     *\n     * @since 1.15.2\n     */\n    enum PackType {\n        /**\n         * Generate the resource pack as a folder structure.\n         * @since 1.15.2\n         */\n        FOLDER,\n        /**\n         * Generate the resource pack as a ZIP archive.\n         * @since 1.15.2\n         */\n        ZIP,\n        /**\n         * Do not generate a resource pack.\n         * @since 1.15.2\n         */\n        NONE\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/BetterModelEvaluator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api;\n\nimport kr.toxicity.model.api.util.function.Float2FloatFunction;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Evaluator\n */\npublic interface BetterModelEvaluator {\n    /**\n     * Compiles molang expression\n     * @param expression expression\n     * @return compiled function\n     */\n    @NotNull Float2FloatFunction compile(@NotNull String expression);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/BetterModelEventBus.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api;\n\nimport kr.toxicity.model.api.event.ModelEvent;\nimport kr.toxicity.model.api.event.ModelEventApplication;\nimport kr.toxicity.model.api.event.ModelEventListener;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * A central event bus for handling model-related events.\n * <p>\n * This interface allows subscribing to and publishing {@link ModelEvent}s.\n * It serves as a decoupling mechanism between different parts of the engine.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface BetterModelEventBus {\n\n    /**\n     * Subscribes a consumer to a specific event type.\n     *\n     * @param application the application that subscribes to the event\n     * @param eventClass the class of the event to subscribe to\n     * @param consumer the consumer to handle the event\n     * @param <T> the type of the event\n     * @return a listener handle that can be used to unregister the subscription\n     * @since 2.0.0\n     */\n    @NotNull\n    <T extends ModelEvent> ModelEventListener subscribe(@NotNull ModelEventApplication application, @NotNull Class<T> eventClass, @NotNull Consumer<T> consumer);\n\n    /**\n     * Publishes an event to all registered subscribers.\n     * <p>\n     * The event is created lazily using the provided supplier if there are subscribers.\n     * </p>\n     *\n     * @param eventClass the class of the event\n     * @param eventSupplier a supplier that creates the event\n     * @param <T> the type of the event\n     * @return the result of the event call\n     * @since 2.0.0\n     */\n    <T extends ModelEvent> @NotNull Result call(@NotNull Class<? extends T> eventClass, @NotNull Supplier<T> eventSupplier);\n\n    /**\n     * Publishes an event to all registered subscribers.\n     *\n     * @param event the event to publish\n     * @return the result of the event call\n     * @since 2.0.0\n     */\n    default @NotNull Result call(@NotNull ModelEvent event) {\n        return call(event.getClass(), () -> event);\n    }\n\n    /**\n     * Represents the outcome of an event publication.\n     *\n     * @since 2.0.0\n     */\n    @RequiredArgsConstructor\n    enum Result {\n        /**\n         * The event was successfully processed by at least one subscriber.\n         * @since 2.0.0\n         */\n        SUCCESS(true),\n        /**\n         * The event processing failed or was canceled.\n         * @since 2.0.0\n         */\n        FAIL(false),\n        /**\n         * No handlers were registered for this event type.\n         * @since 2.0.0\n         */\n        NO_EVENT_HANDLER(true)\n        ;\n\n        private final boolean triggered;\n\n        /**\n         * Checks if the event was considered \"triggered\" (i.e., not canceled or failed).\n         * <p>\n         * Note that {@link #NO_EVENT_HANDLER} is considered triggered as the operation wasn't blocked.\n         * </p>\n         *\n         * @return true if triggered, false otherwise\n         * @since 2.0.0\n         */\n        public boolean triggered() {\n            return triggered;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/BetterModelLogger.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api;\n\nimport net.kyori.adventure.text.Component;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * BetterModel's logger\n */\npublic interface BetterModelLogger {\n    /**\n     * Infos messages\n     * @param message message\n     */\n    void info(@NotNull Component... message);\n\n    /**\n     * Warns message\n     * @param message message\n     */\n    void warn(@NotNull Component... message);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/BetterModelPlatform.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api;\n\nimport kr.toxicity.model.api.event.ModelEventApplication;\nimport kr.toxicity.model.api.manager.*;\nimport kr.toxicity.model.api.nms.NMS;\nimport kr.toxicity.model.api.pack.PackResult;\nimport kr.toxicity.model.api.pack.PackZipper;\nimport kr.toxicity.model.api.platform.PlatformAdapter;\nimport kr.toxicity.model.api.scheduler.ModelScheduler;\nimport kr.toxicity.model.api.version.MinecraftVersion;\nimport lombok.RequiredArgsConstructor;\nimport net.kyori.adventure.audience.Audience;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.semver4j.Semver;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.function.Consumer;\n\n/**\n * Represents the main platform interface for BetterModel.\n *\n * @see BetterModel\n * @since 1.15.2\n */\npublic interface BetterModelPlatform extends ModelEventApplication {\n\n    /**\n     * Returns the data folder for the BetterModel plugin.\n     * This is where configuration files, data files, and other plugin-specific resources are stored.\n     *\n     * @return the data folder as a {@link File} object.\n     * @since 2.0.0\n     */\n    @NotNull File dataFolder();\n\n    /**\n     * Returns the type of JAR file this platform is running on (e.g., SPIGOT, PAPER, FABRIC).\n     *\n     * @return the {@link JarType} enum representing the platform's JAR type.\n     * @since 2.0.0\n     */\n    @NotNull JarType jarType();\n\n    /**\n     * Reloads the platform with default settings (console sender).\n     *\n     * @return the result of the reload operation\n     * @since 2.0.0\n     */\n    default @NotNull ReloadResult reload() {\n        return reload(ReloadInfo.DEFAULT);\n    }\n\n    /**\n     * Reloads the platform, specifying the command sender who initiated it.\n     *\n     * @param sender the command sender\n     * @return the result of the reload operation\n     * @since 1.15.2\n     */\n    default @NotNull ReloadResult reload(@NotNull Audience sender) {\n        return reload(ReloadInfo.builder().sender(sender).build());\n    }\n\n    /**\n     * Reloads the platform with specific reload information.\n     *\n     * @param info the reload configuration\n     * @return the result of the reload operation\n     * @since 1.15.2\n     */\n    @NotNull ReloadResult reload(@NotNull ReloadInfo info);\n\n\n    /**\n     * Checks if the running version of BetterModel is a snapshot build.\n     *\n     * @return true if snapshot, false otherwise\n     * @since 1.15.2\n     */\n    boolean isSnapshot();\n\n    /**\n     * Returns the platform's configuration manager.\n     *\n     * @return the configuration\n     * @since 1.15.2\n     */\n    @NotNull BetterModelConfig config();\n\n    /**\n     * Returns the Minecraft version of the running server.\n     *\n     * @return the Minecraft version\n     * @since 1.15.2\n     */\n    @NotNull MinecraftVersion version();\n\n    /**\n     * Returns the semantic version of the platform.\n     *\n     * @return the semantic version\n     * @since 1.15.2\n     */\n    @NotNull Semver semver();\n\n    /**\n     * Returns the NMS (Net.Minecraft.Server) handler for version-specific operations.\n     *\n     * @return the NMS handler\n     * @since 1.15.2\n     */\n    @NotNull NMS nms();\n\n    /**\n     * Returns the model manager.\n     *\n     * @return the model manager\n     * @since 1.15.2\n     */\n    @NotNull ModelManager modelManager();\n\n    /**\n     * Returns the player manager.\n     *\n     * @return the player manager\n     * @since 1.15.2\n     */\n    @NotNull PlayerManager playerManager();\n\n    /**\n     * Returns the script manager.\n     *\n     * @return the script manager\n     * @since 1.15.2\n     */\n    @NotNull ScriptManager scriptManager();\n\n    /**\n     * Returns the skin manager.\n     *\n     * @return the skin manager\n     * @since 1.15.2\n     */\n    @NotNull SkinManager skinManager();\n    /**\n     * Returns the profile manager.\n     *\n     * @return the profile manager\n     * @since 1.15.2\n     */\n    @NotNull ProfileManager profileManager();\n\n    /**\n     * Returns the platform's scheduler.\n     *\n     * @return the scheduler\n     * @since 1.15.2\n     */\n    @NotNull ModelScheduler scheduler();\n\n    /**\n     * Return the platform's adapter\n     * @return the adapter\n     */\n    @NotNull PlatformAdapter adapter();\n\n    /**\n     * Registers a handler to be executed when a reload starts.\n     *\n     * @param consumer the handler, receiving the {@link PackZipper}\n     * @since 1.15.2\n     */\n    void addReloadStartHandler(@NotNull Consumer<PackZipper> consumer);\n\n    /**\n     * Registers a handler to be executed when a reload ends.\n     *\n     * @param consumer the handler, receiving the {@link ReloadResult}\n     * @since 1.15.2\n     */\n    void addReloadEndHandler(@NotNull Consumer<ReloadResult> consumer);\n\n    /**\n     * Returns the platform's logger.\n     *\n     * @return the logger\n     * @since 1.15.2\n     */\n    @NotNull BetterModelLogger logger();\n\n    /**\n     * Returns the expression evaluator.\n     *\n     * @return the evaluator\n     * @since 1.15.2\n     */\n    @NotNull BetterModelEvaluator evaluator();\n\n    /**\n     * Returns the event bus.\n     *\n     * @return the event bus\n     * @since 2.0.0\n     */\n    @NotNull BetterModelEventBus eventBus();\n\n    /**\n     * Retrieves a resource from the platform's JAR file.\n     *\n     * @param path the path to the resource\n     * @return an input stream for the resource, or null if not found\n     * @since 1.15.2\n     */\n    @Nullable InputStream getResource(@NotNull String path);\n\n    /**\n     * Represents the outcome of a platform reload operation.\n     *\n     * @since 1.15.2\n     */\n    sealed interface ReloadResult {\n\n        /**\n         * Indicates a successful reload.\n         *\n         * @param firstLoad true if this is the first load (startup), false otherwise\n         * @param assetsTime the time taken to reload assets in milliseconds\n         * @param packResult the result of the resource pack generation\n         * @since 1.15.2\n         */\n        record Success(boolean firstLoad, long assetsTime, @NotNull PackResult packResult) implements ReloadResult {\n\n            /**\n             * Returns the time taken to generate the resource pack.\n             *\n             * @return the packing time in milliseconds\n             * @since 1.15.2\n             */\n            public long packingTime() {\n                return packResult().time();\n            }\n\n            /**\n             * Returns the total time taken for the reload operation.\n             *\n             * @return the total time in milliseconds\n             * @since 1.15.2\n             */\n            public long totalTime() {\n                return assetsTime + packingTime();\n            }\n\n            /**\n             * Returns the size of the generated resource pack.\n             *\n             * @return the size in bytes\n             * @since 1.15.2\n             */\n            public long length() {\n                var dir = packResult.directory();\n                return dir != null && dir.isFile() ? dir.length() : packResult.stream().mapToLong(b -> b.bytes().length).sum();\n            }\n        }\n\n        /**\n         * Indicates that a reload is currently in progress.\n         * @since 1.15.2\n         */\n        enum OnReload implements ReloadResult {\n            /**\n             * Singleton instance.\n             * @since 1.15.2\n             */\n            INSTANCE\n        }\n\n        /**\n         * Indicates a failed reload.\n         *\n         * @param throwable the exception that caused the failure\n         * @since 1.15.2\n         */\n        record Failure(@NotNull Throwable throwable) implements ReloadResult {\n        }\n    }\n\n    /**\n     * Represents the type of JAR file the platform is running on.\n     * This enum helps identify the specific server implementation (e.g., Spigot, Paper, Fabric).\n     *\n     * @since 2.0.0\n     */\n    @RequiredArgsConstructor\n    enum JarType {\n        /**\n         * Indicates a Spigot-based server.\n         * @since 2.0.0\n         */\n        SPIGOT(\"spigot\"),\n        /**\n         * Indicates a Paper-based server.\n         * @since 2.0.0\n         */\n        PAPER(\"paper\"),\n        /**\n         * Indicates a Fabric-based server.\n         * @since 2.0.0\n         */\n        FABRIC(\"fabric\");\n\n        private final String raw;\n\n        /**\n         * Returns the raw string representation of the JAR type.\n         *\n         * @return the raw string (e.g., \"spigot\", \"paper\", \"fabric\")\n         * @since 2.0.0\n         */\n        public String raw() {\n            return raw;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/AnimationIterator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport com.google.gson.annotations.SerializedName;\nimport lombok.AccessLevel;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Iterator;\n\n/**\n * An iterator for traversing animation keyframes.\n * <p>\n * This interface supports different looping modes (play once, loop, hold on last)\n * and allows resetting the iteration state.\n * </p>\n *\n * @param <T> the type of keyframe (must implement {@link Timed})\n * @since 1.15.2\n */\npublic sealed interface AnimationIterator<T extends Timed> extends Iterator<T> {\n\n    /**\n     * Resets the iterator to its initial state.\n     * @since 1.15.2\n     */\n    void clear();\n\n    /**\n     * Returns the type of this animation iterator.\n     *\n     * @return the animation type\n     * @since 1.15.2\n     */\n    @NotNull Type type();\n\n    /**\n     * Defines the behavior of the animation iterator.\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor\n    enum Type {\n        /**\n         * Plays the animation once and then stops.\n         * @since 1.15.2\n         */\n        @SerializedName(\"once\")\n        PLAY_ONCE {\n            @Override\n            public @NotNull <T extends Timed> AnimationIterator<T> create(@NotNull TimedStorage<T> keyframes) {\n                return new PlayOnce<>(keyframes);\n            }\n        },\n        /**\n         * Loops the animation continuously.\n         * @since 1.15.2\n         */\n        @SerializedName(\"loop\")\n        LOOP {\n            @Override\n            public @NotNull <T extends Timed> AnimationIterator<T> create(@NotNull TimedStorage<T> keyframes) {\n                return new Loop<>(keyframes);\n            }\n        },\n        /**\n         * Plays the animation once and holds the last frame.\n         * @since 1.15.2\n         */\n        @SerializedName(\"hold\")\n        HOLD_ON_LAST {\n            @Override\n            public @NotNull <T extends Timed> AnimationIterator<T> create(@NotNull TimedStorage<T> keyframes) {\n                return new HoldOnLast<>(keyframes);\n            }\n        }\n        ;\n\n        /**\n         * Creates a new iterator for the given keyframes based on this type.\n         *\n         * @param keyframes the keyframes to iterate over\n         * @param <T> the type of keyframe\n         * @return a new animation iterator\n         * @since 1.15.2\n         */\n        public abstract <T extends Timed> @NotNull AnimationIterator<T> create(@NotNull TimedStorage<T> keyframes);\n    }\n\n    /**\n     * Implementation for {@link Type#PLAY_ONCE}.\n     *\n     * @param <T> the type of keyframe\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)\n    final class PlayOnce<T extends Timed> implements AnimationIterator<T> {\n        private final TimedStorage<T> keyframe;\n        private int index = 0;\n\n        @Override\n        public void clear() {\n            index = Integer.MAX_VALUE;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return index < keyframe.size();\n        }\n\n        @Override\n        @NotNull\n        public T next() {\n            return keyframe.get(index++);\n        }\n\n        @NotNull\n        @Override\n        public Type type() {\n            return Type.PLAY_ONCE;\n        }\n    }\n\n    /**\n     * Implementation for {@link Type#HOLD_ON_LAST}.\n     *\n     * @param <T> the type of keyframe\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)\n    final class HoldOnLast<T extends Timed> implements AnimationIterator<T> {\n        private final TimedStorage<T> keyframe;\n        private int index = 0;\n\n        @Override\n        public void clear() {\n            index = 0;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return true;\n        }\n\n        @Override\n        @NotNull\n        public T next() {\n            if (index >= keyframe.size()) return keyframe.getLast();\n            return keyframe.get(index++);\n        }\n\n        @NotNull\n        @Override\n        public Type type() {\n            return Type.HOLD_ON_LAST;\n        }\n    }\n\n    /**\n     * Implementation for {@link Type#LOOP}.\n     *\n     * @param <T> the type of keyframe\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)\n    final class Loop<T extends Timed> implements AnimationIterator<T> {\n        private final TimedStorage<T> keyframe;\n        private int index = 0;\n\n        @Override\n        public void clear() {\n            index = 0;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return true;\n        }\n\n        @Override\n        @NotNull\n        public T next() {\n            if (index >= keyframe.size()) index = 0;\n            return keyframe.get(index++);\n        }\n\n        @NotNull\n        @Override\n        public Type type() {\n            return Type.LOOP;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/AnimationKeyframe.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport kr.toxicity.model.api.bone.BoneMovement;\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\nimport java.util.Arrays;\n\nimport static kr.toxicity.model.api.util.MathUtil.isNotZero;\n\n/**\n * Represents a collection of animation keyframes, optimized for efficient storage and access.\n * <p>\n * This record stores an array of {@link AnimationProgress} objects, which define the state of a bone\n * at specific time intervals. It implements {@link TimedStorage} for indexed access.\n * </p>\n *\n * @param progresses the array of animation progresses\n * @since 2.0.0\n */\npublic record AnimationKeyframe(\n    @NotNull AnimationProgress[] progresses\n) implements TimedStorage<AnimationProgress> {\n\n    /**\n     * Creates a new builder for constructing an AnimationKeyframe.\n     *\n     * @param size the number of keyframes\n     * @param rotateGlobal whether rotation should be applied globally\n     * @return a new builder instance\n     * @since 2.0.0\n     */\n    public static @NotNull Builder builder(int size, boolean rotateGlobal) {\n        return new Builder(size, rotateGlobal);\n    }\n\n    private record AnimationArray(\n        boolean rotateGlobal,\n        boolean[] skipInterpolation,\n        float[] times,\n        float[] position,\n        float[] scale,\n        float[] rotation\n    ) {\n        AnimationArray(int size, boolean rotateGlobal) {\n            this(\n                rotateGlobal,\n                new boolean[size],\n                new float[size],\n                new float[size * 3],\n                new float[size * 3],\n                new float[size * 3]\n            );\n        }\n    }\n\n    /**\n     * Builder for {@link AnimationKeyframe}.\n     * <p>\n     * This builder allows for efficient population of keyframe data using primitive arrays.\n     * </p>\n     *\n     * @since 2.0.0\n     */\n    public static final class Builder {\n        private final AnimationArray set;\n        private final AnimationProgress[] progresses;\n\n        private int index = 0;\n\n        private Builder(int size, boolean rotateGlobal) {\n            set = new AnimationArray(size, rotateGlobal);\n            progresses = new AnimationProgress[size];\n        }\n\n        /**\n         * Writes a keyframe data point.\n         *\n         * @param time the time of the keyframe\n         * @param position the position vector\n         * @param scale the scale vector\n         * @param rotation the rotation vector\n         * @param skipInterpolation whether to skip interpolation for this keyframe\n         * @since 2.0.0\n         */\n        public void write(\n            float time,\n            @NotNull Vector3f position,\n            @NotNull Vector3f scale,\n            @NotNull Vector3f rotation,\n            boolean skipInterpolation\n        ) {\n            var i = index++;\n\n            var x = i * 3;\n            var y = x + 1;\n            var z = x + 2;\n\n            set.times[i] = time;\n            set.position[x] = position.x;\n            set.position[y] = position.y;\n            set.position[z] = position.z;\n            set.scale[x] = scale.x + 1;\n            set.scale[y] = scale.y + 1;\n            set.scale[z] = scale.z + 1;\n            set.rotation[x] = rotation.x;\n            set.rotation[y] = rotation.y;\n            set.rotation[z] = rotation.z;\n            set.skipInterpolation[i] = skipInterpolation;\n\n            this.progresses[i] = isNotZero(position) || isNotZero(scale) || isNotZero(rotation) ? new ArrayProgress(set, i) : AnimationProgress.empty(time);\n        }\n\n        /**\n         * Builds the {@link AnimationKeyframe}.\n         *\n         * @return the created keyframe collection\n         * @since 2.0.0\n         */\n        public @NotNull AnimationKeyframe build() {\n            return new AnimationKeyframe(progresses);\n        }\n    }\n\n    private record ArrayProgress(@NotNull AnimationArray array, int index) implements AnimationProgress {\n\n        @Override\n        public @NotNull BoneMovement animate(@NotNull BoneMovement movement, @NotNull BoneMovement dest) {\n\n            var destPos = movement.position().get(dest.position());\n            var destScl = movement.scale().get(dest.scale());\n            var destRot = movement.rotation().get(dest.rotation());\n            var destRawRot = movement.rawRotation().get(dest.rawRotation());\n\n            var position = array.position;\n            var scale = array.scale;\n            var rotation = array.rotation;\n\n            var x = index * 3;\n            var y = x + 1;\n            var z = x + 2;\n\n            destPos.add(position[x], position[y], position[z]);\n            destScl.mul(scale[x], scale[y], scale[z]);\n            MathUtil.toQuaternion(destRawRot.add(rotation[x], rotation[y], rotation[z]), destRot);\n\n            return dest;\n        }\n\n        @Override\n        public boolean skipInterpolation() {\n            return array.skipInterpolation[index];\n        }\n\n        @Override\n        public boolean globalRotation() {\n            return array.rotateGlobal;\n        }\n\n        @Override\n        public float time() {\n            return array.times[index];\n        }\n    }\n\n    @Override\n    public @NotNull AnimationProgress get(int i) {\n        return progresses[i];\n    }\n\n    @Override\n    public @NotNull AnimationProgress getLast() {\n        return get(progresses.length - 1);\n    }\n\n    @Override\n    public int size() {\n        return progresses.length;\n    }\n\n    /**\n     * Converts this keyframe collection to a storage of empty progresses.\n     *\n     * @return a new timed storage with empty progresses\n     * @since 2.0.0\n     */\n    public @NotNull TimedStorage<AnimationProgress> toEmpty() {\n        return TimedStorage.listOf(Arrays.stream(progresses)\n            .map(AnimationProgress::toEmpty)\n            .toList());\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/AnimationModifier.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.util.FunctionUtil;\nimport kr.toxicity.model.api.util.MathUtil;\nimport kr.toxicity.model.api.util.function.FloatSupplier;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.function.BooleanSupplier;\n\n/**\n * A modifier of animation.\n * @param predicate predicate\n * @param start start lerp\n * @param end end lerp\n * @param priority priority\n * @param type animation type\n * @param speed speed modifier\n * @param override override\n * @param player player\n */\npublic record AnimationModifier(\n    @Nullable BooleanSupplier predicate,\n    int start,\n    int end,\n    int priority,\n    @Nullable AnimationIterator.Type type,\n    @Nullable FloatSupplier speed,\n    @Nullable Boolean override,\n    @Nullable PlatformPlayer player\n) {\n\n    /**\n     * Default modifier\n     */\n    @NotNull\n    public static final AnimationModifier DEFAULT = builder().build();\n\n    /**\n     * Default with play once modifier\n     */\n    public static final AnimationModifier DEFAULT_WITH_PLAY_ONCE = builder().type(AnimationIterator.Type.PLAY_ONCE).build();\n\n    /**\n     * Creates builder\n     * @return builder\n     */\n    public static @NotNull Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Makes this modifier as builder\n     * @return builder\n     */\n    public @NotNull Builder toBuilder() {\n        return builder()\n            .predicate(predicate)\n            .start(start)\n            .end(end)\n            .type(type)\n            .speed(speed)\n            .override(override)\n            .player(player);\n    }\n\n    /**\n     * Builder\n     */\n    public static final class Builder {\n        private BooleanSupplier predicate = null;\n        private int start = 1;\n        private int end = 0;\n        private int priority = 0;\n        private AnimationIterator.Type type = null;\n        private FloatSupplier speed = null;\n        private Boolean override = null;\n        private PlatformPlayer player = null;\n\n        /**\n         * Private initializer\n         */\n        private Builder() {\n        }\n\n        /**\n         * Sets the predicate of this modifier\n         * @param predicate predicate\n         * @return self\n         */\n        public @NotNull Builder predicate(@Nullable BooleanSupplier predicate) {\n            this.predicate = predicate == null ? null : FunctionUtil.throttleTickBoolean(predicate);\n            return this;\n        }\n\n        /**\n         * Sets the lerp-in time of this modifier\n         * @param start lerp-in time\n         * @return self\n         */\n        public @NotNull Builder start(int start) {\n            this.start = start;\n            return this;\n        }\n\n        /**\n         * Sets the lerp-out time of this modifier\n         * @param end lerp-out time\n         * @return self\n         */\n        public @NotNull Builder end(int end) {\n            this.end = end;\n            return this;\n        }\n\n        /**\n         * Sets the priority of this modifier\n         * @param priority priority\n         * @return self\n         */\n        public @NotNull Builder priority(int priority) {\n            this.priority = priority;\n            return this;\n        }\n\n        /**\n         * Sets the animation type of this modifier\n         * @param type animation type\n         * @return self\n         */\n        public @NotNull Builder type(@Nullable AnimationIterator.Type type) {\n            this.type = type;\n            return this;\n        }\n\n        /**\n         * Sets the speed modifier of this modifier\n         * @param speed speed\n         * @return self\n         */\n        public @NotNull Builder speed(float speed) {\n            this.speed = toSupplier(speed);\n            return this;\n        }\n\n        /**\n         * Sets the speed modifier of this modifier\n         * @param speed speed modifier\n         * @return self\n         */\n        public @NotNull Builder speed(@Nullable FloatSupplier speed) {\n            this.speed = speed == null ? null : FunctionUtil.throttleTickFloat(speed);\n            return this;\n        }\n\n        /**\n         * Sets the override flag of this modifier\n         * @param override override flag\n         * @return self\n         */\n        public @NotNull Builder override(@Nullable Boolean override) {\n            this.override = override;\n            return this;\n        }\n\n        /**\n         * Sets the target player of this modifier\n         * @param player target player\n         * @return self\n         */\n        public @NotNull Builder player(@Nullable PlatformPlayer player) {\n            this.player = player;\n            return this;\n        }\n\n        /**\n         * Merges non-default value with other modifier\n         * @param modifier modifier\n         * @return self\n         */\n        public @NotNull Builder mergeNotDefault(@NotNull AnimationModifier modifier) {\n            if (modifier.predicate != null) predicate(modifier.predicate);\n            if (modifier.start >= 0) start(modifier.start);\n            if (modifier.end >= 0) end(modifier.end);\n            if (modifier.type != null) type(modifier.type);\n            if (modifier.speed != null) speed(modifier.speed);\n            if (modifier.override != null) override(modifier.override);\n            if (modifier.player != null) player(modifier.player);\n            return this;\n        }\n\n        /**\n         * Builds animation modifier\n         * @return build\n         */\n        public @NotNull AnimationModifier build() {\n            return new AnimationModifier(\n                predicate,\n                start,\n                end,\n                priority,\n                type,\n                speed,\n                override,\n                player\n            );\n        }\n    }\n\n\n    /**\n     * Creates modifier\n     *\n     * @param start     start time\n     * @param end       end time\n     */\n    public AnimationModifier(int start, int end) {\n        this(start, end, null, null);\n    }\n\n    /**\n     * Creates modifier\n     *\n     * @param start     start time\n     * @param end       end time\n     * @param speedValue  speed value\n     */\n    public AnimationModifier(int start, int end, float speedValue) {\n        this(start, end, null, FloatSupplier.of(speedValue));\n    }\n\n    /**\n     * Creates modifier\n     *\n     * @param start     start time\n     * @param end       end time\n     * @param supplier  speed supplier\n     */\n    public AnimationModifier(int start, int end, @Nullable FloatSupplier supplier) {\n        this(start, end, null, supplier);\n    }\n\n    /**\n     * Creates modifier\n     *\n     * @param start     start time\n     * @param end       end time\n     * @param type      type\n     */\n    public AnimationModifier(int start, int end, @Nullable AnimationIterator.Type type) {\n        this(start, end, type, null);\n    }\n\n    /**\n     * Creates modifier\n     *\n     * @param start     start time\n     * @param end       end time\n     * @param type type\n     * @param speed     speed\n     */\n    public AnimationModifier(int start, int end, @Nullable AnimationIterator.Type type, @Nullable FloatSupplier speed) {\n        this(null, start, end, type, speed);\n    }\n\n    /**\n     * Creates modifier\n     *\n     * @param predicate animation predicate\n     * @param start     start time\n     * @param end       end time\n     * @param type type\n     * @param speed     speed\n     */\n    public AnimationModifier(@Nullable BooleanSupplier predicate, int start, int end, @Nullable AnimationIterator.Type type, @Nullable FloatSupplier speed) {\n        this(predicate, start, end, 0, type, speed, null, null);\n    }\n\n    /**\n     * Gets modifier's type or default value\n     * @param defaultType default value\n     * @return modifier's type or default value\n     */\n    public @NotNull AnimationIterator.Type type(@NotNull AnimationIterator.Type defaultType) {\n        return type != null ? type : defaultType;\n    }\n\n    /**\n     * Gets speed value\n     * @return speed value\n     */\n    public float speedValue() {\n        return speed != null ? speed.getAsFloat() : 1F;\n    }\n\n    /**\n     * Gets predicate value\n     * @return predicate value\n     */\n    public boolean predicateValue() {\n        return predicate == null || predicate.getAsBoolean();\n    }\n\n    /**\n     * Gets override\n     * @param original original value\n     * @return override\n     */\n    public boolean override(boolean original) {\n        return override != null ? override : original;\n    }\n\n    private static @Nullable FloatSupplier toSupplier(float speed) {\n        return MathUtil.isSimilar(speed, 1F) ? null : FloatSupplier.of(speed);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/AnimationOverrideState.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\npublic enum AnimationOverrideState {\n    NOT_MATCHED,\n    MATCHED\n    ;\n\n    public boolean shouldSkip() {\n        return this == NOT_MATCHED;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/AnimationProgress.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport kr.toxicity.model.api.bone.BoneMovement;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\n\n/**\n * Represents the state of an animation at a specific keyframe.\n * <p>\n * This interface defines how to apply the keyframe's transformation to a bone's movement.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface AnimationProgress extends Timed {\n    /**\n     * An empty animation progress that applies no transformation.\n     * @since 2.0.0\n     */\n    AnimationProgress EMPTY = empty(0);\n\n    /**\n     * Checks if interpolation should be skipped after this keyframe.\n     *\n     * @return true to skip interpolation, false otherwise\n     * @since 2.0.0\n     */\n    boolean skipInterpolation();\n\n    /**\n     * Checks if the rotation in this keyframe should be applied globally.\n     *\n     * @return true for global rotation, false for local\n     * @since 2.0.0\n     */\n    boolean globalRotation();\n\n    /**\n     * Creates an empty animation progress at a specific time.\n     *\n     * @param time the time of the keyframe\n     * @return an empty progress\n     * @since 2.0.0\n     */\n    static @NotNull AnimationProgress empty(float time) {\n        return new EmptyProgress(time);\n    }\n\n    /**\n     * Converts this progress to empty progress at the same time.\n     *\n     * @return an empty progress\n     * @since 2.0.0\n     */\n    default @NotNull AnimationProgress toEmpty() {\n        var time = time();\n        return time <= 0 ? EMPTY : empty(time);\n    }\n\n    /**\n     * Creates an empty timed storage with a start and end keyframe.\n     *\n     * @param time the duration of the empty animation\n     * @return the timed storage\n     * @since 2.0.0\n     */\n    static @NotNull TimedStorage<AnimationProgress> emptyStorage(float time) {\n        return TimedStorage.listOf(List.of(\n            EMPTY,\n            empty(time)\n        ));\n    }\n\n    /**\n     * Applies this keyframe's animation to a bone's movement.\n     *\n     * @param movement the current bone movement\n     * @param dest the destination object to store the result\n     * @return the resulting bone movement\n     * @since 2.0.0\n     */\n    @NotNull BoneMovement animate(@NotNull BoneMovement movement, @NotNull BoneMovement dest);\n\n    /**\n     * An implementation of {@link AnimationProgress} that represents an empty keyframe.\n     *\n     * @param time the time of the keyframe\n     * @since 2.0.0\n     */\n    record EmptyProgress(float time) implements AnimationProgress {\n\n        @Override\n        public @NotNull BoneMovement animate(@NotNull BoneMovement movement, @NotNull BoneMovement dest) {\n            return dest.set(movement);\n        }\n\n        @Override\n        public @NotNull AnimationProgress toEmpty() {\n            return this;\n        }\n\n        @Override\n        public boolean skipInterpolation() {\n            return false;\n        }\n\n        @Override\n        public boolean globalRotation() {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/AnimationStateHandler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport kr.toxicity.model.api.tracker.Tracker;\nimport kr.toxicity.model.api.util.MathUtil;\nimport kr.toxicity.model.api.util.collection.PriorityMap;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Iterator;\nimport java.util.function.BiConsumer;\nimport java.util.function.BooleanSupplier;\n\n/**\n * Animation state handler\n * @param <T> timed value\n */\n@RequiredArgsConstructor\n@ApiStatus.Internal\npublic final class AnimationStateHandler<T extends Timed> {\n\n    private final T initialValue;\n    private final BiConsumer<T, T> setConsumer;\n\n    private final PriorityMap<String, TreeIterator> animators = new PriorityMap<>();\n\n    @Getter\n    private int delay;\n    private volatile TreeIterator currentIterator = null;\n    private volatile T beforeKeyframe = null, afterKeyframe = null;\n\n    /**\n     * Checks this keyframe has been finished\n     * @return finished\n     */\n    public boolean keyframeFinished() {\n        return delay <= 0;\n    }\n\n    /**\n     * Gets before keyframe\n     * @return before keyframe\n     */\n    public T beforeKeyframe() {\n        return beforeKeyframe;\n    }\n\n    /**\n     * Gets after keyframe\n     * @return after keyframe\n     */\n    public T afterKeyframe() {\n        return afterKeyframe;\n    }\n\n    /**\n     * Gets before keyframe\n     * @param defaultValue default value\n     * @return before keyframe\n     */\n    @NotNull\n    public T beforeKeyframe(@NotNull T defaultValue) {\n        var value = beforeKeyframe;\n        return value != null ? value : defaultValue;\n    }\n\n    /**\n     * Gets after keyframe\n     * @param defaultValue default value\n     * @return after keyframe\n     */\n    @NotNull\n    public T afterKeyframe(@NotNull T defaultValue) {\n        var value = afterKeyframe;\n        return value != null ? value : defaultValue;\n    }\n\n    /**\n     * Gets running animation\n     * @return animation\n     */\n    public @Nullable RunningAnimation runningAnimation() {\n        var iterator = currentIterator;\n        return iterator != null ? iterator.animation : null;\n    }\n\n    /**\n     * Ticks this state handler\n     * @return keyframe has been shifted or not\n     */\n    public boolean tick() {\n        return tick(() -> {});\n    }\n\n    /**\n     * Ticks this state handler\n     * @param ifEmpty callback if animator is empty\n     * @return keyframe has been shifted or not\n     */\n    public boolean tick(@NotNull Runnable ifEmpty) {\n        delay--;\n        if (animators.isEmpty()) {\n            ifEmpty.run();\n            return false;\n        }\n        return shouldUpdateAnimation() && updateAnimation();\n    }\n\n    /**\n     * Gets the progress of current keyframe\n     * @return progress\n     */\n    public float progress() {\n        var frame = frame();\n        return frame == 0 ? 0 : Math.clamp((float) delay / frame, 0F, 1F);\n    }\n\n    private boolean shouldUpdateAnimation() {\n        return (afterKeyframe != null && keyframeFinished()) || delay % Tracker.MINECRAFT_TICK_MULTIPLIER == 0;\n    }\n\n    private boolean updateAnimation() {\n        synchronized (animators) {\n            var iterator = animators.valueIterator();\n            while (iterator.hasNext()) {\n                var next = iterator.next();\n                if (!next.getAsBoolean()) continue;\n                if (currentIterator == null) {\n                    if (updateKeyframe(iterator, next)) {\n                        currentIterator = next;\n                        return setAfterKeyframe(next.next());\n                    }\n                } else if (currentIterator != next) {\n                    if (updateKeyframe(iterator, next)) {\n                        currentIterator.clear();\n                        currentIterator = next;\n                        return setAfterKeyframe(next.next());\n                    }\n                } else if (keyframeFinished()) {\n                    if (updateKeyframe(iterator, next)) {\n                        return setAfterKeyframe(next.next());\n                    }\n                } else {\n                    return false;\n                }\n            }\n        }\n        return setAfterKeyframe(null);\n    }\n\n    private boolean updateKeyframe(@NotNull Iterator<TreeIterator> iterator, @NotNull TreeIterator next) {\n        if (!next.hasNext()) {\n            next.removeTask.run();\n            iterator.remove();\n            return false;\n        } else {\n            return true;\n        }\n    }\n\n    private boolean setAfterKeyframe(@Nullable T next) {\n        if (afterKeyframe == next) return false;\n        setConsumer.accept(\n            beforeKeyframe = afterKeyframe,\n            afterKeyframe = next\n        );\n        delay = Math.round(frame());\n        return true;\n    }\n\n    /**\n     * Adds animation\n     * @param name name\n     * @param iterator iterator\n     * @param modifier modifier\n     * @param removeTask remove task\n     */\n    public void addAnimation(@NotNull String name, @NotNull AnimationIterator<T> iterator, @NotNull AnimationModifier modifier, @NotNull Runnable removeTask) {\n        synchronized (animators) {\n            animators.put(name, new TreeIterator(name, iterator, modifier, removeTask), modifier.priority());\n        }\n    }\n\n    /**\n     * Replaces animation\n     * @param name name\n     * @param iterator iterator\n     * @param modifier modifier\n     */\n    public void replaceAnimation(@NotNull String name, @NotNull AnimationIterator<T> iterator, @NotNull AnimationModifier modifier) {\n        synchronized (animators) {\n            animators.replace(name, v -> new TreeIterator(name, iterator, v.modifier.toBuilder()\n                .mergeNotDefault(modifier)\n                .build(), v.removeTask));\n        }\n    }\n\n    /**\n     * Remove animation\n     * @param name name\n     * @return success\n     */\n    public boolean stopAnimation(@NotNull String name) {\n        synchronized (animators) {\n            if (animators.remove(name) != null) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Gets ticking frame of current keyframe\n     * @return ticking frame\n     */\n    public float frame() {\n        return afterKeyframe != null ? 20 * Tracker.MINECRAFT_TICK_MULTIPLIER * (currentIterator.time + MathUtil.FRAME_EPSILON) : 0F;\n    }\n\n    private class TreeIterator implements BooleanSupplier {\n        private final RunningAnimation animation;\n        private final AnimationIterator<T> iterator;\n        private final AnimationModifier modifier;\n        private final Runnable removeTask;\n\n        private final T previous;\n\n        private boolean started = false;\n        private boolean ended = false;\n\n        private float time = 0;\n\n        public TreeIterator(String name, AnimationIterator<T> iterator, AnimationModifier modifier, Runnable removeTask) {\n            animation = new RunningAnimation(name, iterator.type());\n            this.iterator = iterator;\n            this.modifier = modifier;\n            this.removeTask = removeTask;\n\n            previous = afterKeyframe != null ? afterKeyframe : initialValue;\n        }\n\n        @Override\n        public boolean getAsBoolean() {\n            return modifier.predicateValue();\n        }\n\n        public boolean hasNext() {\n            return iterator.hasNext() || (modifier.end() > 0 && !ended);\n        }\n\n        public @NotNull T next() {\n            if (!started) {\n                started = true;\n                time = (float) modifier.start() / 20;\n                return iterator.next();\n            }\n            if (!iterator.hasNext()) {\n                ended = true;\n                time = (float) modifier.end() / 20;\n                return previous;\n            }\n            var nxt = iterator.next();\n            time = nxt.time() / modifier.speedValue();\n            return nxt;\n        }\n\n        public void clear() {\n            iterator.clear();\n            started = ended = !iterator.hasNext();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/RunningAnimation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Running animation\n * @param name name\n * @param type type\n */\npublic record RunningAnimation(@NotNull String name, @NotNull AnimationIterator.Type type) {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/Timed.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Object with keyframe time\n */\npublic interface Timed extends Comparable<Timed> {\n\n    default int compareTo(@NotNull Timed o) {\n        return MathUtil.FRAME_COMPARATOR.compare(time(), o.time());\n    }\n\n    /**\n     * Gets time\n     * @return time\n     */\n    float time();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/TimedStorage.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jspecify.annotations.NonNull;\n\nimport java.util.List;\n\n/**\n * A read-only storage for timed elements (keyframes), allowing indexed access.\n * <p>\n * This interface abstracts the underlying data structure (e.g., List, Array) used to store animation frames.\n * </p>\n *\n * @param <T> the type of timed element\n * @since 2.0.0\n */\npublic interface TimedStorage<T extends Timed> {\n\n    /**\n     * Creates a TimedStorage backed by a List.\n     *\n     * @param list the list of elements\n     * @param <T> the type of element\n     * @return a new TimedStorage\n     * @since 2.0.0\n     */\n    @NotNull\n    static <T extends Timed> TimedStorage<T> listOf(@NotNull List<T> list) {\n        return new ListDelegate<>(list);\n    }\n\n    /**\n     * Retrieves the element at the specified index.\n     *\n     * @param index the index of the element\n     * @return the element\n     * @throws IndexOutOfBoundsException if the index is out of range\n     * @since 2.0.0\n     */\n    @NotNull T get(int index);\n\n    /**\n     * Returns the number of elements in the storage.\n     *\n     * @return the size\n     * @since 2.0.0\n     */\n    int size();\n\n    /**\n     * Retrieves the last element in the storage.\n     *\n     * @return the last element\n     * @throws java.util.NoSuchElementException if the storage is empty\n     * @since 2.0.0\n     */\n    @NotNull T getLast();\n\n    /**\n     * A {@link TimedStorage} implementation that delegates to a {@link List}.\n     *\n     * @param list the backing list\n     * @param <T> the type of element\n     * @since 2.0.0\n     */\n    record ListDelegate<T extends Timed>(@NotNull List<T> list) implements TimedStorage<T> {\n        @Override\n        public @NonNull T get(int index) {\n            return list.get(index);\n        }\n\n        @Override\n        public int size() {\n            return list.size();\n        }\n\n        @Override\n        public @NonNull T getLast() {\n            return list.getLast();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/animation/VectorPoint.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.animation;\n\nimport kr.toxicity.model.api.util.function.FloatFunction;\nimport kr.toxicity.model.api.util.interpolator.VectorInterpolator;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Vector3f;\n\n/**\n * Represents a keyframe point in an animation timeline.\n * <p>\n * This record holds the value of a vector (position, rotation, or scale) at a specific time,\n * along with interpolation information to create smooth transitions between keyframes.\n * </p>\n *\n * @param function a function to get the vector value, which may be dynamic (e.g., based on Molang expressions)\n * @param time the time of this keyframe in seconds\n * @param bezier the bezier curve configuration for interpolation, if applicable\n * @param interpolator the interpolation method to use (e.g., linear, bezier, catmull-rom)\n * @since 1.15.2\n */\npublic record VectorPoint(@NotNull FloatFunction<Vector3f> function, float time, @NotNull BezierConfig bezier, @NotNull VectorInterpolator interpolator) implements Timed {\n\n    private static final Vector3f ZERO = new Vector3f();\n\n    /**\n     * An empty, default vector point at time 0 with linear interpolation.\n     * @since 1.15.2\n     */\n    public static final VectorPoint EMPTY = new VectorPoint(\n        FloatFunction.of(ZERO),\n        0F,\n        new BezierConfig(null, null, null, null),\n        VectorInterpolator.LINEAR\n    );\n\n    /**\n     * Gets the vector value at this keyframe's specific time.\n     *\n     * @return the vector value\n     * @since 1.15.2\n     */\n    public @NotNull Vector3f vector() {\n        return vector(time);\n    }\n\n    /**\n     * Gets the vector value at a specific time, evaluating the function if necessary.\n     *\n     * @param time the time to evaluate at\n     * @return the calculated vector\n     * @since 1.15.2\n     */\n    public @NotNull Vector3f vector(float time) {\n        return function.apply(time);\n    }\n\n    /**\n     * Checks if the interpolation method for this point is continuous.\n     *\n     * @return true if continuous (e.g., linear), false if stepped\n     * @since 1.15.2\n     */\n    public boolean isContinuous() {\n        return interpolator.isContinuous();\n    }\n\n    /**\n     * Configuration for bezier curve interpolation.\n     *\n     * @param leftTime the time offset for the incoming (left) handle\n     * @param leftValue the value offset for the incoming (left) handle\n     * @param rightTime the time offset for the outgoing (right) handle\n     * @param rightValue the value offset for the outgoing (right) handle\n     * @since 1.15.2\n     */\n    public record BezierConfig(@Nullable Vector3f leftTime, @Nullable Vector3f leftValue, @Nullable Vector3f rightTime, @Nullable Vector3f rightValue) {\n\n        /**\n         * Gets the time offset for the incoming (left) handle.\n         * If null, returns a zero vector.\n         * @return the left time offset vector\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Vector3f leftTime() {\n            return leftTime != null ? leftTime : ZERO;\n        }\n\n        /**\n         * Gets the value offset for the incoming (left) handle.\n         * If null, returns a zero vector.\n         * @return the left value offset vector\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Vector3f leftValue() {\n            return leftValue != null ? leftValue : ZERO;\n        }\n\n        /**\n         * Gets the time offset for the outgoing (right) handle.\n         * If null, returns a zero vector.\n         * @return the right time offset vector\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Vector3f rightTime() {\n            return rightTime != null ? rightTime : ZERO;\n        }\n\n        /**\n         * Gets the value offset for the outgoing (right) handle.\n         * If null, returns a zero vector.\n         * @return the right value offset vector\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Vector3f rightValue() {\n            return rightValue != null ? rightValue : ZERO;\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof VectorPoint that)) return false;\n        return Float.compare(time, that.time) == 0;\n    }\n\n    @Override\n    public int hashCode() {\n        return Float.hashCode(time);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/armor/ArmorItem.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.armor;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Armor item\n * @param tint tint value\n * @param type armor type\n * @param trim trim\n * @param palette palette\n */\npublic record ArmorItem(int tint, @NotNull String type, @Nullable String trim, @Nullable String palette) {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/armor/PlayerArmor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.armor;\n\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Player armor\n */\npublic interface PlayerArmor {\n\n    /**\n     * Empty armor\n     */\n    PlayerArmor EMPTY = new PlayerArmor() {\n        @Override\n        public @Nullable ArmorItem helmet() {\n            return null;\n        }\n\n        @Override\n        public @Nullable ArmorItem chestplate() {\n            return null;\n        }\n\n        @Override\n        public @Nullable ArmorItem leggings() {\n            return null;\n        }\n\n        @Override\n        public @Nullable ArmorItem boots() {\n            return null;\n        }\n    };\n\n    /**\n     * Gets helmet\n     * @return helmet\n     */\n    @Nullable ArmorItem helmet();\n\n    /**\n     * Gets chestplate\n     * @return chestplate\n     */\n    @Nullable ArmorItem chestplate();\n\n    /**\n     * Gets leggings\n     * @return leggings\n     */\n    @Nullable ArmorItem leggings();\n\n    /**\n     * Gets boots\n     * @return boots\n     */\n    @Nullable ArmorItem boots();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneEventDispatcher.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport kr.toxicity.model.api.nms.HitBoxListener;\nimport lombok.AllArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.UUID;\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\n\n/**\n * Dispatches events related to bone lifecycle and interaction.\n * <p>\n * This class manages handlers for hitbox creation, state creation, and state removal.\n * It allows for extending behavior by chaining dispatchers.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class BoneEventDispatcher {\n    private final EventFunction builder = new EventFunction();\n    private EventFunction applier = builder;\n\n    /**\n     * Extends this dispatcher with another dispatcher's handlers.\n     * <p>\n     * The handlers from the provided dispatcher will be executed before the handlers in this dispatcher.\n     * </p>\n     *\n     * @param dispatcher the dispatcher to extend\n     * @throws UnsupportedOperationException if attempting to extend self\n     * @since 1.15.2\n     */\n    public synchronized void extend(@NotNull BoneEventDispatcher dispatcher) {\n        if (dispatcher == this) throw new UnsupportedOperationException(\"cannot extend self\");\n        applier = EventFunction.concat(dispatcher.applier, builder);\n    }\n\n    /**\n     * Registers a handler for hitbox creation.\n     *\n     * @param function the function to modify the hitbox listener builder\n     * @since 1.15.2\n     */\n    public synchronized void handleCreateHitBox(@NotNull BiFunction<RenderedBone, HitBoxListener.Builder, HitBoxListener.Builder> function) {\n        var before = builder.createHitBox;\n        builder.createHitBox = (b, l) -> function.apply(b, before.apply(b, l));\n    }\n\n    /**\n     * Registers a handler for state creation (e.g., when a bone is initialized for a player).\n     *\n     * @param function the consumer to handle state creation\n     * @since 1.15.2\n     */\n    public synchronized void handleStateCreate(@NotNull BiConsumer<RenderedBone, UUID> function) {\n        builder.stateCreate = builder.stateCreate.andThen(function);\n    }\n\n    /**\n     * Registers a handler for state removal (e.g., when a bone is removed for a player).\n     *\n     * @param function the consumer to handle state removal\n     * @since 1.15.2\n     */\n    public synchronized void handleStateRemove(@NotNull BiConsumer<RenderedBone, UUID> function) {\n        builder.stateRemove = builder.stateRemove.andThen(function);\n    }\n\n    @NotNull HitBoxListener.Builder onCreateHitBox(@NotNull RenderedBone bone, @NotNull HitBoxListener.Builder builder) {\n        return applier.createHitBox.apply(bone, builder);\n    }\n\n    void onStateCreated(@NotNull RenderedBone bone, @NotNull UUID uuid) {\n        applier.stateCreate.accept(bone, uuid);\n    }\n\n    void onStateRemoved(@NotNull RenderedBone bone, @NotNull UUID uuid) {\n        applier.stateRemove.accept(bone, uuid);\n    }\n\n    @AllArgsConstructor\n    private static class EventFunction {\n        private BiFunction<RenderedBone, HitBoxListener.Builder, HitBoxListener.Builder> createHitBox;\n        private BiConsumer<RenderedBone, UUID> stateCreate;\n        private BiConsumer<RenderedBone, UUID> stateRemove;\n\n        EventFunction() {\n            this(\n                (_, l) -> l,\n                (_, _) -> {},\n                (_, _) -> {}\n            );\n        }\n\n        static @NotNull EventFunction concat(@NotNull EventFunction first, @NotNull EventFunction second) {\n            return new EventFunction(\n                (b, l) -> second.createHitBox.apply(b, first.createHitBox.apply(b, l)),\n                (b, u) -> {\n                    first.stateCreate.accept(b, u);\n                    second.stateCreate.accept(b, u);\n                },\n                (b, u) -> {\n                    first.stateRemove.accept(b, u);\n                    second.stateRemove.accept(b, u);\n                }\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneEventHandler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport org.jetbrains.annotations.NotNull;\n\npublic interface BoneEventHandler {\n    @NotNull BoneEventDispatcher eventDispatcher();\n\n    default void extend(@NotNull BoneEventHandler eventHandler) {\n        eventDispatcher().extend(eventHandler.eventDispatcher());\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneIKSolver.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;\nimport kr.toxicity.model.api.util.InterpolationUtil;\nimport kr.toxicity.model.api.util.MathUtil;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\nimport java.util.Map;\nimport java.util.UUID;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.newSequencedAddressingMap;\n\n/**\n * Bone IK solver\n */\n@ApiStatus.Internal\n@RequiredArgsConstructor\npublic final class BoneIKSolver {\n\n    private static final int MAX_IK_ITERATION = 20;\n    private static final Vector3f FROM_VECTOR = new Vector3f(0, -1, 0).normalize();\n\n    private final Map<UUID, RenderedBone> boneMap;\n    private final Object2ObjectLinkedOpenHashMap<RenderedBone, IKChain> locators = newSequencedAddressingMap();\n\n    /**\n     * Adds some external locator to this solver\n     * @param ikSource nullable source\n     * @param ikTarget target bone\n     * @param locator locator bone\n     */\n    public void addLocator(@Nullable UUID ikSource, @NotNull UUID ikTarget, @NotNull RenderedBone locator) {\n        var target = boneMap.get(ikTarget);\n        if (target == null) return;\n        var source = ikSource == null ? target.root : boneMap.getOrDefault(ikSource, target.root);\n        var chainArray = source.flatten()\n            .filter(bone -> !bone.flattenBones().contains(locator) && bone.flattenBones().contains(target))\n            .toArray(RenderedBone[]::new);\n        if (chainArray.length < 2) return;\n        locators.put(locator, new IKChain(chainArray));\n    }\n\n    /**\n     * Solves ik\n     */\n    public void solve() {\n        solve(null);\n    }\n\n    /**\n     * Solves ik\n     * @param uuid player uuid\n     */\n    public void solve(@Nullable UUID uuid) {\n        if (locators.isEmpty()) return;\n        locators.object2ObjectEntrySet().fastForEach(entry -> {\n            var locator = entry.getKey();\n            var value = entry.getValue();\n            fabrik(\n                value.movements(uuid),\n                value.invertedFirstRotation(uuid),\n                value.cache.lengths,\n                locator.state(uuid).after().position().get(value.cache.destination)\n                    .add(locator.root.group.getPosition())\n                    .sub(value.first().root.group.getPosition())\n            );\n        });\n    }\n\n    private record IKChain(@NotNull RenderedBone[] bones, @NotNull IKCache cache) {\n\n        private IKChain(@NotNull RenderedBone[] bones) {\n            this(bones, new IKCache(bones.length));\n        }\n\n        private @NotNull RenderedBone first() {\n            return bones[0];\n        }\n\n        private @NotNull Quaternionf invertedFirstRotation(@Nullable UUID uuid) {\n            return first().state(uuid).after().rotation().invert(cache.rotation);\n        }\n\n        private @NotNull BoneMovement[] movements(@Nullable UUID uuid) {\n            var movements = cache.movements;\n            for (int i = 0; i < bones.length; i++) {\n                movements[i] = bones[i].state(uuid).after();\n            }\n            return movements;\n        }\n    }\n\n    private record IKCache(@NotNull BoneMovement[] movements, float[] lengths, @NotNull Vector3f destination, @NotNull Quaternionf rotation) {\n        private IKCache(int length) {\n            this(new BoneMovement[length], new float[length - 1], new Vector3f(), new Quaternionf());\n        }\n    }\n\n    private static void fabrik(@NotNull BoneMovement[] bones, @NotNull Quaternionf firstRot, float[] lengths, @NotNull Vector3f target) {\n        var first = bones[0].position();\n        var last = bones[bones.length - 1].position();\n\n        var vecCache = new Vector3f();\n        var rootPos = first.get(vecCache);\n\n        for (int i = 0; i < bones.length - 1; i++) {\n            var before = bones[i];\n            var after = bones[i + 1];\n            lengths[i] = before.position().distance(after.position());\n        }\n        for (int iter = 0; iter < MAX_IK_ITERATION; iter++) {\n            // Forward\n            last.set(target);\n            for (int i = bones.length - 2; i >= 0; i--) {\n                var current = bones[i].position();\n                var next = bones[i + 1].position();\n                var dist = current.distanceSquared(next);\n                if (dist < MathUtil.VECTOR_COMPARISON_EPSILON_SQ) continue;\n                InterpolationUtil.lerp(next, current, lengths[i] / (float) Math.sqrt(dist), current);\n            }\n            // Backward\n            first.set(rootPos);\n            for (int i = 0; i < bones.length - 1; i++) {\n                var current = bones[i].position();\n                var next = bones[i + 1].position();\n                var dist = current.distanceSquared(next);\n                if (dist < MathUtil.VECTOR_COMPARISON_EPSILON_SQ) continue;\n                InterpolationUtil.lerp(current, next, lengths[i] / (float) Math.sqrt(dist), next);\n            }\n            // Check\n            if (last.distanceSquared(target) < MathUtil.VECTOR_COMPARISON_EPSILON_SQ) break;\n        }\n        var rotCache = new Quaternionf();\n        for (int i = 0; i < bones.length - 1; i++) {\n            var current = bones[i];\n            var next = bones[i + 1];\n\n            var dir = next.position().sub(current.position(), vecCache);\n            current.rotation().set(rotCache.identity().rotateTo(FROM_VECTOR, dir.normalize()).mul(firstRot).mul(current.rotation()));\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneItemMapper.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport kr.toxicity.model.api.data.renderer.RenderSource;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.platform.PlatformItemTransform;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\n/**\n * Item-mapper of bone\n */\npublic interface BoneItemMapper extends BiFunction<BoneRenderContext, TransformedItemStack, TransformedItemStack> {\n\n    @Override\n    @NotNull TransformedItemStack apply(@NotNull BoneRenderContext context, @NotNull TransformedItemStack transformedItemStack);\n\n    /**\n     * Empty\n     */\n    BoneItemMapper EMPTY = new BoneItemMapper() {\n        @NotNull\n        @Override\n        public PlatformItemTransform transform() {\n            return PlatformItemTransform.FIXED;\n        }\n\n        @Override\n        @NotNull\n        public TransformedItemStack apply(@NotNull BoneRenderContext context, @NotNull TransformedItemStack transformedItemStack) {\n            return transformedItemStack;\n        }\n    };\n\n    /**\n     * Mapped if a render source is player\n     * @param transform transformation\n     * @param mapper mapper\n     * @return bone item mapper\n     */\n    static @NotNull BoneItemMapper player(@NotNull PlatformItemTransform transform, @NotNull Function<PlatformPlayer, TransformedItemStack> mapper) {\n        return new BoneItemMapper() {\n\n            private static final TransformedItemStack AIR = TransformedItemStack.empty();\n\n            @NotNull\n            @Override\n            public PlatformItemTransform transform() {\n                return transform;\n            }\n\n            @Override\n            public @NotNull TransformedItemStack apply(@NotNull BoneRenderContext context, @NotNull TransformedItemStack transformedItemStack) {\n                if (context.source() instanceof RenderSource.BasePlayer(PlatformPlayer player)) {\n                    var get = mapper.apply(player);\n                    return get == null ? AIR : get;\n                }\n                return transformedItemStack;\n            }\n        };\n    }\n\n    /**\n     * Mapped if a render source is entity\n     * @param transform transformation\n     * @param mapper mapper\n     * @return bone item mapper\n     */\n    static @NotNull BoneItemMapper entity(@NotNull PlatformItemTransform transform, @NotNull Function<BaseEntity, TransformedItemStack> mapper) {\n        return new BoneItemMapper() {\n\n            private static final TransformedItemStack AIR = TransformedItemStack.empty();\n\n            @NotNull\n            @Override\n            public PlatformItemTransform transform() {\n                return transform;\n            }\n\n            @Override\n            public @NotNull TransformedItemStack apply(@NotNull BoneRenderContext context, @NotNull TransformedItemStack transformedItemStack) {\n                if (context.source() instanceof RenderSource.Entity entity) {\n                    var get = mapper.apply(entity.entity());\n                    return get == null ? AIR : get;\n                }\n                return transformedItemStack;\n            }\n        };\n    }\n\n    /**\n     * Gets this mapper's display is fixed\n     * @return fixed\n     */\n    default boolean fixed() {\n        return transform() == PlatformItemTransform.FIXED;\n    }\n\n    /**\n     * Gets item display transformation\n     * @return transformation\n     */\n    @NotNull PlatformItemTransform transform();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneMovement.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport kr.toxicity.model.api.util.InterpolationUtil;\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\n/**\n * Represents the transformation state of a single bone, including its position, scale, and rotation.\n * <p>\n * This record is used to calculate the final transformation of a bone after applying animations.\n * </p>\n *\n * @param position the local position of the bone\n * @param scale the local scale of the bone\n * @param rotation the final local rotation of the bone as a quaternion\n * @param rawRotation the local rotation of the bone in Euler angles (degrees) before being converted to a quaternion\n * @since 1.15.2\n */\npublic record BoneMovement(\n    @NotNull Vector3f position,\n    @NotNull Vector3f scale,\n    @NotNull Quaternionf rotation,\n    @NotNull Vector3f rawRotation\n) {\n\n    /**\n     * Creates a new BoneMovement with default (identity) transformations.\n     * @since 1.15.2\n     */\n    public BoneMovement() {\n        this(\n            new Vector3f(),\n            new Vector3f(1),\n            new Quaternionf(),\n            new Vector3f()\n        );\n    }\n\n    /**\n     * Copies the values from another BoneMovement into this one.\n     *\n     * @param movement the source movement\n     * @return this movement instance\n     * @since 1.15.2\n     */\n    public @NotNull BoneMovement set(@NotNull BoneMovement movement) {\n        position.set(movement.position);\n        scale.set(movement.scale);\n        rotation.set(movement.rotation);\n        rawRotation.set(movement.rawRotation);\n        return this;\n    }\n\n    /**\n     * Linearly interpolates between this movement and another movement.\n     *\n     * @param to the target movement\n     * @param alpha the interpolation factor (0.0 to 1.0)\n     * @param dest the destination movement to store the result\n     * @return the destination movement\n     * @since 2.1.0\n     */\n    public @NotNull BoneMovement lerp(@NotNull BoneMovement to, float alpha, @NotNull BoneMovement dest) {\n        InterpolationUtil.lerp(position, to.position, alpha, dest.position);\n        InterpolationUtil.lerp(scale, to.scale, alpha, dest.scale);\n        MathUtil.toQuaternion(InterpolationUtil.lerp(rawRotation, to.rawRotation, alpha, dest.rawRotation), dest.rotation);\n        return dest;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneName.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport com.google.gson.JsonDeserializer;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * A tagged name of some bone\n * @param tags tags\n * @param name name\n * @param rawName original name\n */\npublic record BoneName(@NotNull @Unmodifiable Set<BoneTag> tags, @NotNull String name, @NotNull String rawName) {\n\n    /**\n     * A JSON deserializer for parsing BoneName from a string.\n     * @since 2.0.1\n     */\n    public static final JsonDeserializer<BoneName> PARSER = (json, _, _) -> BoneName.of(json.getAsString());\n\n    /**\n     * Internal constructor for BoneName.\n     */\n    @ApiStatus.Internal\n    public BoneName {\n    }\n\n    /**\n     * Creates a new BoneName by parsing the raw name string.\n     * @param rawName the raw string to parse\n     * @since 2.0.1\n     * @return a parsed BoneName instance\n     */\n    public static @NotNull BoneName of(@NotNull String rawName) {\n        return BoneTag.REGISTRY.parse(rawName);\n    }\n\n    /**\n     * Checks this name has some tags\n     * @param tags tags\n     * @return any match\n     */\n    public boolean tagged(@NotNull BoneTag... tags) {\n        for (BoneTag boneTag : tags) {\n            if (this.tags.contains(boneTag)) return true;\n        }\n        return false;\n    }\n\n    /**\n     * Gets an item mapper of this bone name.\n     * @return item mapper\n     */\n    public @NotNull BoneItemMapper toItemMapper() {\n        return tags.isEmpty() ? BoneItemMapper.EMPTY : tags.stream().map(BoneTag::itemMapper).filter(Objects::nonNull).findFirst().orElse(BoneItemMapper.EMPTY);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (!(o instanceof BoneName boneName)) return false;\n        return rawName.equals(boneName.rawName);\n    }\n\n    @Override\n    public int hashCode() {\n        return rawName.hashCode();\n    }\n\n    @Override\n    public @NotNull String toString() {\n        return rawName;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BonePosition.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Vector3f;\n\nimport java.util.UUID;\n\n/**\n * Represents the position and state of a bone in a model.\n *\n * @param globalOffset the global offset vector\n * @param localOffset  the local offset vector\n * @param state        the unique identifier of the current state, or null if none\n * @since 2.1.0\n */\npublic record BonePosition(\n    @NotNull Vector3f globalOffset,\n    @NotNull Vector3f localOffset,\n    @Nullable UUID state\n) {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneRenderContext.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.data.renderer.RenderSource;\nimport kr.toxicity.model.api.skin.SkinData;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Render item context\n * @param source source\n * @param skin skin\n */\npublic record BoneRenderContext(@NotNull RenderSource<?> source, @NotNull SkinData skin) {\n\n    /**\n     * Creates default context\n     * @param source source\n     */\n    public BoneRenderContext(@NotNull RenderSource<?> source) {\n        this(source, BetterModel.platform().skinManager().fallback());\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneTag.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.List;\n\n/**\n * A tag of bone\n */\npublic interface BoneTag {\n\n    /**\n     * The default registry for bone tags.\n     * @since 2.0.1\n     */\n    BoneTagRegistry REGISTRY = new BoneTagRegistry();\n\n    /**\n     * Gets tag name\n     * @return tag name\n     */\n    @NotNull String name();\n\n    /**\n     * Gets an item mapper\n     * @return item mapper\n     */\n    @Nullable BoneItemMapper itemMapper();\n\n    /**\n     * Gets a tag list like 'h', 'hi', 'b'\n     * @since 2.0.1\n     * @return tags\n     */\n    @NotNull @Unmodifiable\n    List<String> tags();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneTagRegistry.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport it.unimi.dsi.fastutil.objects.*;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Optional;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.newAddressingMap;\n\n/**\n * Bone tag registry\n */\npublic final class BoneTagRegistry {\n\n    private static final String TAG_SPLITTER = \"_\";\n    private final Object2ObjectMap<String, BoneTag> byName = newAddressingMap();\n\n    BoneTagRegistry() {\n        for (BoneTags value : BoneTags.values()) {\n            addTag(value);\n        }\n    }\n\n    /**\n     * Adds some tag to this registry\n     * @param tag tag\n     */\n    public void addTag(@NotNull BoneTag tag) {\n        BoneTag checkDuplicate;\n        for (String s : tag.tags()) {\n            if ((checkDuplicate = byName.put(s, tag)) != null) throw new RuntimeException(\"Duplicated tags: \" + tag.name() + \" between \" + checkDuplicate.name());\n        }\n    }\n\n    /**\n     * Gets a bone tag by its name wrapped in an Optional.\n     * @param tag tag name\n     * @return bone tag\n     * @since 1.15.2\n     */\n    public @NotNull Optional<BoneTag> byTagName(@NotNull String tag) {\n        return Optional.ofNullable(byTagNameOrNull(tag));\n    }\n\n    /**\n     * Gets a bone tag by its name.\n     * @param tag tag name\n     * @return bone tag or null\n     * @since 2.1.0\n     */\n    public @Nullable BoneTag byTagNameOrNull(@NotNull String tag) {\n        return byName.get(tag);\n    }\n\n    /**\n     * Parses bone name by raw group name\n     * @param rawName raw name\n     * @return bone name\n     */\n    public @NotNull BoneName parse(@NotNull String rawName) {\n        rawName = rawName.toLowerCase(Locale.ROOT);\n        var tagArray = rawName.split(TAG_SPLITTER);\n        if (tagArray.length < 2) return new BoneName(ObjectSets.emptySet(), rawName, rawName);\n        var tagList = List.of(tagArray);\n        var maxSize = tagList.size() - 1;\n        ObjectSet<BoneTag> set = maxSize <= 4 ? new ObjectArraySet<>(maxSize) : new ObjectOpenHashSet<>(maxSize);\n        for (String s : tagList) {\n            var tag = byTagNameOrNull(s);\n            if (tag != null && set.size() < maxSize) set.add(tag);\n            else return new BoneName(\n                set.isEmpty() ? ObjectSets.emptySet() : ObjectSets.unmodifiable(set),\n                set.isEmpty() ? rawName : String.join(TAG_SPLITTER, tagList.subList(set.size(), tagList.size())),\n                rawName\n            );\n        }\n        return new BoneName(\n            ObjectSets.unmodifiable(set),\n            String.join(TAG_SPLITTER, tagList.subList(set.size(), tagList.size())),\n            rawName\n        );\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/BoneTags.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.nms.Profiled;\nimport kr.toxicity.model.api.platform.PlatformItemTransform;\nimport kr.toxicity.model.api.player.PlayerLimb;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.List;\n\n/**\n * Builtin tags\n */\npublic enum BoneTags implements BoneTag {\n    /**\n     * Follows entity's head rotation\n     */\n    HEAD(\"h\"),\n    /**\n     * Follows entity's head rotation\n     */\n    HEAD_WITH_CHILDREN(\"hi\"),\n    /**\n     * Creates a hitbox following this bone\n     */\n    HITBOX(\"b\", \"ob\"),\n    /**\n     * It can be used as a seat\n     */\n    SEAT(\"p\"),\n    /**\n     * It can be used as a seat but not controllable\n     */\n    SUB_SEAT(\"sp\"),\n    /**\n     * Nametag\n     */\n    TAG(\"tag\"),\n    /**\n     * Mob's nametag\n     */\n    MOB_TAG(\"mtag\"),\n    /**\n     * Player's nametag\n     */\n    PLAYER_TAG(\"ptag\"),\n    /**\n     * Glow\n     */\n    GLOW(\"glow\"),\n    /**\n     * Entity's item in left hand\n     */\n    LEFT_ITEM(BoneItemMapper.entity(\n        PlatformItemTransform.THIRDPERSON_LEFTHAND,\n        BaseEntity::offHand\n    ), \"pli\", \"li\"),\n    /**\n     * Entity's item in right hand\n     */\n    RIGHT_ITEM(BoneItemMapper.entity(\n        PlatformItemTransform.THIRDPERSON_RIGHTHAND,\n        BaseEntity::mainHand\n    ), \"pri\", \"ri\"),\n    /**\n     * Player head\n     */\n    PLAYER_HEAD(PlayerLimb.HEAD.getItemMapper(), \"ph\"),\n    /**\n     * Player right arm\n     */\n    PLAYER_RIGHT_ARM(PlayerLimb.RIGHT_ARM.getItemMapper(), \"pra\"),\n    /**\n     * Player right forearm\n     */\n    PLAYER_RIGHT_FOREARM(PlayerLimb.RIGHT_FOREARM.getItemMapper(), \"prfa\"),\n    /**\n     * Player left arm\n     */\n    PLAYER_LEFT_ARM(PlayerLimb.LEFT_ARM.getItemMapper(), \"pla\"),\n    /**\n     * Player left forearm\n     */\n    PLAYER_LEFT_FOREARM(PlayerLimb.LEFT_FOREARM.getItemMapper(), \"plfa\"),\n    /**\n     * Player left hip\n     */\n    PLAYER_HIP(PlayerLimb.HIP.getItemMapper(), \"phip\"),\n    /**\n     * Player left waist\n     */\n    PLAYER_WAIST(PlayerLimb.WAIST.getItemMapper(), \"pw\"),\n    /**\n     * Player left chest\n     */\n    PLAYER_CHEST(PlayerLimb.CHEST.getItemMapper(), \"pc\"),\n    /**\n     * Player right leg\n     */\n    PLAYER_RIGHT_LEG(PlayerLimb.RIGHT_LEG.getItemMapper(), \"prl\"),\n    /**\n     * Player right foreleg\n     */\n    PLAYER_RIGHT_FORELEG(PlayerLimb.RIGHT_FORELEG.getItemMapper(), \"prfl\"),\n    /**\n     * Player left leg\n     */\n    PLAYER_LEFT_LEG(PlayerLimb.LEFT_LEG.getItemMapper(), \"pll\"),\n    /**\n     * Player left foreleg\n     */\n    PLAYER_LEFT_FORELEG(PlayerLimb.LEFT_FORELEG.getItemMapper(), \"plfl\"),\n    /**\n     * Cape\n     */\n    CAPE(new BoneItemMapper() {\n        @Override\n        public @NotNull TransformedItemStack apply(@NotNull BoneRenderContext context, @NotNull TransformedItemStack transformedItemStack) {\n            TransformedItemStack cape = null;\n            if (context.source() instanceof Profiled profiled && profiled.skinParts().isCapeEnabled()) {\n                cape = context.skin().cape(profiled.armors());\n            }\n            return cape != null ? cape : TransformedItemStack.empty();\n        }\n\n        @Override\n        public @NotNull PlatformItemTransform transform() {\n            return PlatformItemTransform.FIXED;\n        }\n    }, \"cape\")\n    ;\n\n    BoneTags(@NotNull String... tags) {\n        this(null, tags);\n    }\n\n    BoneTags(@Nullable BoneItemMapper itemMapper, @NotNull String... tags) {\n        this.itemMapper = itemMapper;\n        this.tags = List.of(tags);\n    }\n\n    @Nullable\n    private final BoneItemMapper itemMapper;\n    @NotNull\n    private final List<String> tags;\n\n    @Nullable\n    @Override\n    public BoneItemMapper itemMapper() {\n        return itemMapper;\n    }\n\n    @NotNull\n    @Unmodifiable\n    @Override\n    public List<String> tags() {\n        return tags;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/bone/RenderedBone.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.bone;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;\nimport it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;\nimport it.unimi.dsi.fastutil.objects.ObjectSortedSets;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.animation.*;\nimport kr.toxicity.model.api.data.blueprint.BlueprintAnimation;\nimport kr.toxicity.model.api.data.blueprint.BlueprintElement;\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox;\nimport kr.toxicity.model.api.data.renderer.RenderSource;\nimport kr.toxicity.model.api.data.renderer.RendererGroup;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.nms.*;\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.ModelRotation;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport kr.toxicity.model.api.util.*;\nimport kr.toxicity.model.api.util.collection.SingletonSequencedSet;\nimport kr.toxicity.model.api.util.function.BonePredicate;\nimport kr.toxicity.model.api.util.function.FloatConstantSupplier;\nimport kr.toxicity.model.api.util.function.FloatSupplier;\nimport kr.toxicity.model.api.util.lock.DuplexLock;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * A rendered item-display.\n */\npublic final class RenderedBone implements BoneEventHandler {\n\n    private static final int INITIAL_TINT_VALUE = 0xFFFFFF;\n    private static final Vector3f EMPTY_VECTOR = new Vector3f();\n    private static final BonePosition EMPTY_POSITION = new BonePosition(EMPTY_VECTOR, EMPTY_VECTOR, null);\n\n    @Getter\n    @NotNull\n    final RendererGroup group;\n    private final BoneMovement defaultFrame;\n    private volatile BoneRenderContext renderContext;\n    private final BoneEventDispatcher eventDispatcher = new BoneEventDispatcher();\n\n    @NotNull\n    @Getter\n    final RenderedBone root;\n    @Nullable\n    @Getter\n    final RenderedBone parent;\n    final RenderedBone[] children;\n\n    private volatile SequencedSet<RenderedBone> flattenBones;\n\n    private final Int2ObjectMap<PlatformItemStack> tintCacheMap = new Int2ObjectOpenHashMap<>();\n    @Getter\n    private final boolean dummyBone;\n    private final Object itemLock = new Object();\n\n    //Resource\n    @Getter\n    @Nullable\n    private final ModelDisplay display;\n    @Getter\n    @Nullable\n    private HitBox hitBox;\n    @Getter\n    @Nullable\n    private ModelNametag nametag;\n\n    //Item\n    @Getter\n    @Setter\n    private BoneItemMapper itemMapper;\n    private volatile int previousTint = INITIAL_TINT_VALUE, tint = INITIAL_TINT_VALUE;\n    private volatile TransformedItemStack itemStack;\n\n    //Animation\n    private final BoneStateHandler globalState;\n    private final Map<UUID, BoneStateHandler> perPlayerState = new ConcurrentHashMap<>();\n    private volatile ModelRotation rotation = ModelRotation.EMPTY;\n\n    private Supplier<Vector3f> defaultPosition = FunctionUtil.asSupplier(EMPTY_VECTOR);\n    private FloatSupplier scale = FloatConstantSupplier.ONE;\n\n    private Function<Vector3f, Vector3f> positionModifier = p -> p;\n    private Vector3f lastModifiedPosition = new Vector3f();\n    private Function<Quaternionf, Quaternionf> localRotModifier = r -> r, globalRotModifier = r -> r;\n    private Quaternionf lastModifiedLocalRot = new Quaternionf(), lastModifiedGlobalRot = new Quaternionf();\n\n    /**\n     * Creates entity.\n     * @param group group\n     * @param parent parent entity\n     * @param context render context\n     * @param movement spawn movement\n     * @param childrenMapper mapper\n     */\n    @ApiStatus.Internal\n    public RenderedBone(\n        @NotNull RendererGroup group,\n        @Nullable RenderedBone parent,\n        @NotNull BoneRenderContext context,\n        @NotNull BoneMovement movement,\n        @NotNull Function<RenderedBone, RenderedBone[]> childrenMapper\n    ) {\n        this.group = group;\n        this.parent = parent;\n        this.renderContext = context;\n        itemMapper = group.getItemMapper();\n        root = parent != null ? parent.root : this;\n        this.itemStack = itemMapper.apply(renderContext, group.getItemStack());\n        this.dummyBone = group.getItemStack().isAir() && itemMapper == BoneItemMapper.EMPTY;\n        defaultFrame = movement;\n        children = childrenMapper.apply(this);\n        if (!dummyBone) {\n            display = BetterModel.nms().create(context.source().location(), context.source() instanceof RenderSource.Entity ? -4096 : 0, d -> {\n                d.display(itemMapper.transform());\n                d.invisible(!group.getParent().visibility());\n                d.viewRange(EntityUtil.entityModelViewRadius());\n                applyItem(d);\n            });\n        } else display = null;\n        globalState = new BoneStateHandler(null, _ -> {});\n    }\n\n    public void locator(@NotNull BoneIKSolver solver) {\n        if (getGroup().getParent() instanceof BlueprintElement.NullObject nullObject) {\n            var ikTarget = nullObject.ikTarget();\n            if (ikTarget == null) return;\n            solver.addLocator(nullObject.ikSource(), ikTarget, this);\n        }\n    }\n\n    private @NotNull BoneStateHandler state(@Nullable PlatformPlayer player) {\n        return state(player != null ? player.uuid() : null);\n    }\n\n    @NotNull BoneStateHandler state(@Nullable UUID uuid) {\n        return uuid == null ? globalState : perPlayerState.getOrDefault(uuid, globalState);\n    }\n\n    private @NotNull BoneStateHandler getOrCreateState(@Nullable PlatformPlayer player) {\n        return getOrCreateState(player != null ? player.uuid() : null);\n    }\n\n    private @NotNull BoneStateHandler getOrCreateState(@Nullable UUID uuid) {\n        return uuid == null ? globalState : perPlayerState.computeIfAbsent(uuid, u -> {\n            eventDispatcher.onStateCreated(this, u);\n            return new BoneStateHandler(u, targetUUID -> eventDispatcher.onStateRemoved(this, targetUUID));\n        });\n    }\n\n    public @Nullable RunningAnimation runningAnimation() {\n        return globalState.state.runningAnimation();\n    }\n\n    @Override\n    public @NotNull BoneEventDispatcher eventDispatcher() {\n        return eventDispatcher;\n    }\n\n    public boolean updateItem(@NotNull Predicate<RenderedBone> predicate) {\n        return itemStack(predicate, itemMapper.apply(renderContext, itemStack));\n    }\n\n    public boolean updateItem(@NotNull BoneRenderContext context) {\n        synchronized (this) {\n            renderContext = context;\n        }\n        return updateItem(_ -> true);\n    }\n\n    /**\n     * Creates hit box.\n     * @param entity target entity\n     * @param predicate predicate\n     * @param listener hit box listener\n     * @return success\n     */\n    public boolean createHitBox(@NotNull BaseEntity entity, @NotNull Predicate<RenderedBone> predicate, @Nullable HitBoxListener listener) {\n        if (predicate.test(this)) {\n            var previous = hitBox;\n            synchronized (this) {\n                if (previous != hitBox) return false;\n                var h = group.getHitBox();\n                if (h == null) h = ModelBoundingBox.MIN;\n                var l = eventDispatcher.onCreateHitBox(this, (listener != null ? listener : HitBoxListener.EMPTY).toBuilder()).build();\n                if (hitBox != null) hitBox.removeHitBox();\n                hitBox = BetterModel.nms().createHitBox(entity, this, h, group.getMountController(), l);\n                return hitBox != null;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Creates nametag\n     * @param predicate predicate\n     * @param consumer nametag consumer\n     * @return success\n     */\n    public boolean createNametag(@NotNull Predicate<RenderedBone> predicate, @NotNull Consumer<ModelNametag> consumer) {\n        if (nametag == null && predicate.test(this)) {\n            synchronized (this) {\n                if (nametag != null) return false;\n                nametag = BetterModel.nms().createNametag(this, consumer);\n            }\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Make item has enchantment or not\n     * @param predicate predicate\n     * @param enchant should enchant\n     * @return success or not\n     */\n    public boolean enchant(@NotNull Predicate<RenderedBone> predicate, boolean enchant) {\n        return itemStack(predicate, itemStack.modify(i -> i.enchant(enchant)));\n    }\n\n    /**\n     * Sets the scale of this bone\n     * @param scale scale\n     */\n    public void scale(@NotNull FloatSupplier scale) {\n        this.scale = scale;\n    }\n\n    /**\n     * Applies some function at display\n     * @param predicate predicate\n     * @param consumer consumer\n     * @return success or not\n     */\n    public boolean applyAtDisplay(@NotNull Predicate<RenderedBone> predicate, @NotNull Consumer<ModelDisplay> consumer) {\n        if (display != null && predicate.test(this)) {\n            consumer.accept(display);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Changes displayed item\n     * @param predicate predicate\n     * @param itemStack target item\n     * @return success\n     */\n    public boolean itemStack(@NotNull Predicate<RenderedBone> predicate, @NotNull TransformedItemStack itemStack) {\n        if (this.itemStack != itemStack && predicate.test(this)) {\n            synchronized (itemLock) {\n                if (this.itemStack == itemStack) return false;\n                this.itemStack = itemStack;\n                if (display != null) display.invisible(itemStack.isAir());\n                tintCacheMap.clear();\n                return applyItem();\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Adds local rot modifier.\n     * @param predicate predicate\n     * @param function animation consumer\n     * @return whether to success\n     */\n    public synchronized boolean addLocalRotModifier(@NotNull Predicate<RenderedBone> predicate, @NotNull Function<Quaternionf, Quaternionf> function) {\n        if (predicate.test(this)) {\n            localRotModifier = localRotModifier.andThen(function);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Adds global rot modifier.\n     * @param predicate predicate\n     * @param function animation consumer\n     * @return whether to success\n     */\n    public synchronized boolean addGlobalRotModifier(@NotNull Predicate<RenderedBone> predicate, @NotNull Function<Quaternionf, Quaternionf> function) {\n        if (predicate.test(this)) {\n            globalRotModifier = globalRotModifier.andThen(function);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Adds position modifier.\n     * @param predicate predicate\n     * @param function animation consumer\n     * @return whether to success\n     */\n    public synchronized boolean addPositionModifier(@NotNull Predicate<RenderedBone> predicate, @NotNull Function<Vector3f, Vector3f> function) {\n        if (predicate.test(this)) {\n            positionModifier = positionModifier.andThen(function);\n            return true;\n        }\n        return false;\n    }\n\n    public boolean rotate(@NotNull ModelRotation rotation, @NotNull PacketBundler bundler) {\n        this.rotation = rotation;\n        if (display != null) {\n            display.rotate(rotation, bundler);\n            return true;\n        }\n        return false;\n    }\n\n    public boolean tick() {\n        return globalState.tick();\n    }\n\n    public boolean tick(@NotNull UUID uuid) {\n        var get = perPlayerState.get(uuid);\n        return get != null && get.tick();\n    }\n\n    public void dirtyUpdate(@NotNull PacketBundler bundler) {\n        var d = display;\n        if (d != null) d.sendDirtyEntityData(bundler);\n    }\n\n    public void forceUpdate(boolean showItem, @NotNull PacketBundler bundler) {\n        var d = display;\n        if (d != null) d.sendEntityData(showItem, bundler);\n    }\n\n    public void forceUpdate(@NotNull PacketBundler bundler) {\n        var d = display;\n        if (d != null) d.sendEntityData(!d.invisible(), bundler);\n    }\n\n    public void sendTransformation(@Nullable UUID uuid, @NotNull AnimationBundler bundler) {\n        state(uuid).sendTransformation(bundler);\n    }\n\n    public void forceTransformation(@NotNull PacketBundler bundler) {\n        var d = globalState.transformer;\n        if (d != null) d.sendTransformation(bundler);\n    }\n\n    public int interpolationDuration() {\n        return globalState.interpolationDuration();\n    }\n\n    public @NotNull Vector3f worldPosition() {\n        return worldPosition(EMPTY_POSITION);\n    }\n\n    public @NotNull Vector3f worldPosition(@NotNull BonePosition position) {\n        return worldPosition(position, new BoneMovement());\n    }\n\n    public @NotNull Vector3f worldPosition(@NotNull BoneMovement cache) {\n        return worldPosition(EMPTY_POSITION, cache);\n    }\n\n    public @NotNull Vector3f worldPosition(@NotNull BonePosition position, @NotNull BoneMovement cache) {\n        return state(position.state()).worldPosition(position, cache);\n    }\n\n    public @NotNull Vector3f worldRotation() {\n        return worldRotation(null);\n    }\n\n    public @NotNull Vector3f worldRotation(@Nullable UUID uuid) {\n        return state(uuid).worldRotation();\n    }\n\n    public void defaultPosition(@NotNull Supplier<Vector3f> movement) {\n        defaultPosition = movement;\n    }\n\n    private @NotNull Vector3f modifiedPosition(boolean preventModifierUpdate) {\n        return preventModifierUpdate ? lastModifiedPosition : (lastModifiedPosition = positionModifier.apply(lastModifiedPosition.set(EMPTY_VECTOR)));\n    }\n\n    private @NotNull Quaternionf modifiedLocalRot(boolean preventModifierUpdate) {\n        return preventModifierUpdate ? lastModifiedLocalRot : (lastModifiedLocalRot = localRotModifier.apply(lastModifiedLocalRot.identity()));\n    }\n\n    private @NotNull Quaternionf modifiedGlobalRot(boolean preventModifierUpdate) {\n        return preventModifierUpdate ? lastModifiedGlobalRot : (lastModifiedGlobalRot = globalRotModifier.apply(lastModifiedGlobalRot.identity()));\n    }\n\n    public boolean tint(@NotNull Predicate<RenderedBone> predicate) {\n        return tint(predicate, previousTint);\n    }\n\n    public boolean tint(@NotNull Predicate<RenderedBone> predicate, int tint) {\n        if (this.tint != tint && predicate.test(this)) {\n            synchronized (itemLock) {\n                if (this.tint == tint) return false;\n                this.previousTint = this.tint;\n                this.tint = tint;\n                return applyItem();\n            }\n        }\n        return false;\n    }\n\n    private boolean applyItem() {\n        if (display != null) {\n            applyItem(display);\n            return true;\n        }\n        return false;\n    }\n\n    private void applyItem(@NotNull ModelDisplay targetDisplay) {\n        targetDisplay.item(itemStack.isAir() ? itemStack.itemStack() : tintCacheMap.computeIfAbsent(tint, i -> BetterModel.nms().tint(itemStack.itemStack(), i)));\n    }\n\n    public void teleport(@NotNull PlatformLocation location, @NotNull PacketBundler bundler) {\n        if (display != null) display.teleport(location, bundler);\n    }\n\n    public void spawn(boolean hide, @NotNull PacketBundler bundler) {\n        if (display != null) display.spawn(!hide && !display.invisible(), bundler);\n        var transformer = globalState.transformer;\n        if (transformer != null) transformer.sendTransformation(bundler);\n    }\n\n    public boolean addAnimation(@NotNull AnimationOverrideState overrideState, @NotNull BlueprintAnimation animator, @NotNull AnimationModifier modifier, @NotNull Runnable removeTask) {\n        var get = animator.animator().get(name());\n        if (get == null && modifier.override(animator.override()) && overrideState.shouldSkip()) return false;\n        var type = modifier.type(animator.loop());\n        var iterator = get != null ? get.iterator(type) : animator.emptyIterator(type);\n        getOrCreateState(modifier.player()).state.addAnimation(animator.name(), iterator, modifier, removeTask);\n        return true;\n    }\n\n    public boolean replaceAnimation(@NotNull AnimationOverrideState overrideState, @NotNull String target, @NotNull BlueprintAnimation animator, @NotNull AnimationModifier modifier) {\n        var get = animator.animator().get(name());\n        if (get == null && modifier.override(animator.override()) && overrideState.shouldSkip()) return false;\n        var type = modifier.type(animator.loop());\n        var iterator = get != null ? get.iterator(type) : animator.emptyIterator(type);\n        state(modifier.player()).state.replaceAnimation(target, iterator, modifier);\n        return true;\n    }\n\n    /**\n     * Stops bone's animation\n     * @param filter filter\n     * @param name animation's name\n     * @param player player\n     */\n    public boolean stopAnimation(@NotNull Predicate<RenderedBone> filter, @NotNull String name, @Nullable PlatformPlayer player) {\n        return filter.test(this) && state(player).state.stopAnimation(name);\n    }\n\n    /**\n     * Removes model's display\n     * @param bundler packet bundler\n     */\n    public void remove(@NotNull PacketBundler bundler) {\n        if (display != null) display.remove(bundler);\n        if (nametag != null) nametag.remove(bundler);\n    }\n\n    public @NotNull Stream<RenderedBone> flatten() {\n        return flattenBones().stream();\n    }\n\n    @Unmodifiable\n    @NotNull\n    public SequencedSet<RenderedBone> flattenBones() {\n        SequencedSet<RenderedBone> set;\n        if ((set = flattenBones) != null) return set;\n        synchronized (this) {\n            if ((set = flattenBones) != null) return set;\n            return flattenBones = children.length == 0 ? SingletonSequencedSet.of(this) : Stream.concat(\n                Stream.of(this),\n                Arrays.stream(children).flatMap(RenderedBone::flatten)\n            ).collect(Collectors.collectingAndThen(\n                Collectors.toCollection(ObjectLinkedOpenHashSet::new),\n                ObjectSortedSets::unmodifiable\n            ));\n        }\n    }\n\n    public boolean matchTree(@NotNull BonePredicate predicate, @NotNull BiPredicate<RenderedBone, BonePredicate> mapper) {\n        var parentResult = mapper.test(this, predicate);\n        var childPredicate = predicate.children(parentResult);\n        for (RenderedBone value : children) {\n            if (value.matchTree(childPredicate, mapper)) parentResult = true;\n        }\n        return parentResult;\n    }\n\n    public boolean matchAnimation(@NotNull AnimationOverrideState overrideState, @NotNull BiPredicate<RenderedBone, AnimationOverrideState> mapper) {\n        var parentResult = mapper.test(this, overrideState);\n        if (parentResult) overrideState = AnimationOverrideState.MATCHED;\n        for (RenderedBone value : children) {\n            if (value.matchAnimation(overrideState, mapper)) parentResult = true;\n        }\n        return parentResult;\n    }\n\n    @NotNull\n    public Vector3f hitBoxPosition() {\n        return hitBoxPosition(new BoneMovement());\n    }\n\n    @NotNull\n    public Vector3f hitBoxPosition(@NotNull BoneMovement cache) {\n        var box = getGroup().getHitBox();\n        if (box != null) return worldPosition(new BonePosition(EMPTY_VECTOR, group.getHitBoxPoint(), null), cache);\n        return worldPosition(cache);\n    }\n\n    public float hitBoxScale() {\n        return scale.getAsFloat();\n    }\n\n    @NotNull\n    public ModelRotation rotation() {\n        return rotation;\n    }\n\n    final class BoneStateHandler {\n\n        private final @Nullable UUID uuid;\n        private final Consumer<UUID> consumer;\n\n        //States\n        private final AnimationStateHandler<AnimationProgress> state;\n        private final BoneMovement before = new BoneMovement(), after = new BoneMovement(), current = new BoneMovement();\n        private final DisplayTransformer transformer = display != null ? display.createTransformer() : null;\n\n        //Flags\n        private boolean firstTick = true;\n        private boolean skipInterpolation = false;\n        private final AtomicBoolean updateAfter = new AtomicBoolean();\n        private final AtomicBoolean updateCurrent = new AtomicBoolean();\n\n        //Caches\n        private final Vector3f positionCache = new Vector3f(), scaleCache = new Vector3f();\n        private final Quaternionf localRotCache = new Quaternionf(), globalRotCache = new Quaternionf();\n\n        //Lock\n        private final DuplexLock lock = new DuplexLock();\n\n        private BoneStateHandler(@Nullable UUID uuid, @NotNull Consumer<UUID> consumer) {\n            this.uuid = uuid;\n            this.consumer = consumer;\n            state = new AnimationStateHandler<>(\n                AnimationProgress.EMPTY,\n                (_, a) -> skipInterpolation = (a != null && a.skipInterpolation()) || (parent != null && parent.state(uuid).skipInterpolation)\n            );\n        }\n\n        @NotNull BoneMovement after() {\n            if (!updateAfter.compareAndSet(true, false)) return after;\n            var keyframe = state.afterKeyframe(AnimationProgress.EMPTY);\n            var preventModifierUpdate = interpolationDuration() < 1;\n            var def = keyframe.animate(defaultFrame, after);\n            if (parent != null) {\n                var p = parent.state(uuid).after();\n                MathUtil.fma(\n                        def.position().rotate(p.rotation()),\n                        p.scale(),\n                        p.position()\n                    ).sub(parent.lastModifiedPosition)\n                    .add(modifiedPosition(preventModifierUpdate));\n                def.scale().mul(p.scale());\n                def.rotation().set(parent.lastModifiedGlobalRot.invert(globalRotCache)\n                    .mul(modifiedGlobalRot(preventModifierUpdate))\n                    .mul((keyframe.globalRotation() ? localRotCache.identity() : p.rotation().div(parent.lastModifiedLocalRot, localRotCache)).mul(def.rotation()))\n                    .mul(modifiedLocalRot(preventModifierUpdate))\n                );\n            } else {\n                def.position().add(modifiedPosition(preventModifierUpdate));\n                def.rotation().set(modifiedGlobalRot(preventModifierUpdate).get(globalRotCache)\n                    .mul(def.rotation())\n                    .mul(modifiedLocalRot(preventModifierUpdate)));\n            }\n            return def;\n        }\n\n        private boolean tick() {\n            var result = state.tick(() -> {\n                if (uuid != null) {\n                    perPlayerState.remove(uuid);\n                    consumer.accept(uuid);\n                }\n            }) || firstTick;\n            if (result && updateAfter.compareAndSet(false, true)) {\n                lock.accessToWriteLock(() -> before.set(current));\n                updateCurrent.set(true);\n            }\n            firstTick = false;\n            return result;\n        }\n\n        private float progress() {\n            return 1F - state.progress();\n        }\n\n        private int interpolationDuration() {\n            if (skipInterpolation) return 0;\n            var frame = state.frame() / (float) Tracker.MINECRAFT_TICK_MULTIPLIER;\n            return Math.round(frame + MathUtil.FLOAT_COMPARISON_EPSILON);\n        }\n\n        private void sendTransformation(@NotNull AnimationBundler bundler) {\n            if (!updateCurrent.compareAndSet(true, false)) return;\n            var after = after();\n            var movement = lock.accessToWriteLock(() -> current.set(after));\n            if (transformer == null) return;\n            var mul = scale.getAsFloat();\n            transformer.transform(\n                interpolationDuration(),\n                MathUtil.fma(\n                    itemStack.offset().rotate(movement.rotation(), positionCache)\n                        .add(movement.position())\n                        .add(root.group.getPosition()),\n                    mul,\n                    itemStack.position()\n                ).add(defaultPosition.get()),\n                movement.scale()\n                    .mul(itemStack.scale(), scaleCache)\n                    .mul(mul)\n                    .max(EMPTY_VECTOR),\n                movement.rotation(),\n                bundler\n            );\n        }\n\n        private @NotNull Vector3f worldPosition(@NotNull BonePosition position, @NotNull BoneMovement cache) {\n            var progress = progress();\n            var interpolated = lock.accessToReadLock(() -> before.lerp(current, progress, cache));\n            return MathUtil.fma(\n                    interpolated.position()\n                        .add(itemStack.offset())\n                        .add(position.localOffset())\n                        .rotate(interpolated.rotation()),\n                    interpolated.scale(),\n                    position.globalOffset()\n                )\n                .add(root.getGroup().getPosition())\n                .mul(scale.getAsFloat())\n                .rotateX(-rotation.radianX())\n                .rotateY(-rotation.radianY());\n        }\n\n        private @NotNull Vector3f worldRotation() {\n            var progress = progress();\n            return lock.accessToReadLock(() -> InterpolationUtil.lerp(before.rawRotation(), current.rawRotation(), progress));\n        }\n    }\n\n    public @NotNull BoneName name() {\n        return getGroup().name();\n    }\n\n    public @NotNull UUID uuid() {\n        return getGroup().uuid();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) return true;\n        if (!(obj instanceof RenderedBone bone)) return false;\n        return uuid().equals(bone.uuid());\n    }\n\n    @Override\n    public int hashCode() {\n        return uuid().hashCode();\n    }\n\n    @Override\n    public String toString() {\n        return name().toString();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/config/DebugConfig.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.config;\n\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.EnumSet;\nimport java.util.Set;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\n/**\n * Debug config\n * @param options options\n */\npublic record DebugConfig(@NotNull @Unmodifiable Set<DebugOption> options) {\n    /**\n     * Debug option\n     */\n    @RequiredArgsConstructor\n    public enum DebugOption {\n        /**\n         * Debug stack trace of exception\n         */\n        EXCEPTION(\"exception\"),\n        /**\n         * Debug hit-box entity\n         */\n        HITBOX(\"hitbox\"),\n        /**\n         * Debug packing resource pack\n         */\n        PACK(\"pack\"),\n        /**\n         * Debug tracker thread\n         */\n        TRACKER(\"tracker\")\n        ;\n        private final String config;\n    }\n\n    /**\n     * Checks this config has this option\n     * @param option option\n     * @return has or not\n     */\n    public boolean has(@NotNull DebugOption option) {\n        return options.contains(option);\n    }\n\n    /**\n     * Default config\n     */\n    public static final DebugConfig DEFAULT = new DebugConfig(Collections.emptySet());\n\n    /**\n     * Creates config from YAML\n     * @param predicate predicate\n     * @return config\n     */\n    public static @NotNull DebugConfig from(@NotNull Predicate<String> predicate) {\n        return new DebugConfig(Collections.unmodifiableSet(Arrays.stream(DebugOption.values())\n            .filter(o -> predicate.test(o.config))\n            .collect(Collectors.toCollection(() -> EnumSet.noneOf(DebugOption.class)))));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/config/IndicatorConfig.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.config;\n\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.EnumSet;\nimport java.util.Set;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\n/**\n * Indicator config\n * @param options options\n */\npublic record IndicatorConfig(@NotNull @Unmodifiable Set<IndicatorOption> options) {\n    /**\n     * Indicator option\n     */\n    @RequiredArgsConstructor\n    public enum IndicatorOption {\n        /**\n         * Progress bar\n         */\n        PROGRESS_BAR(\"progress_bar\"),\n        ;\n        private final String config;\n    }\n\n    /**\n     * Default config\n     */\n    public static final IndicatorConfig DEFAULT = new IndicatorConfig(Collections.emptySet());\n\n    /**\n     * Creates config from YAML\n     * @param predicate predicate\n     * @return config\n     */\n    public static @NotNull IndicatorConfig from(@NotNull Predicate<String> predicate) {\n        return new IndicatorConfig(Collections.unmodifiableSet(Arrays.stream(IndicatorOption.values())\n            .filter(o -> predicate.test(o.config))\n            .collect(Collectors.toCollection(() -> EnumSet.noneOf(IndicatorOption.class)))));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/config/ModuleConfig.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.config;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Predicate;\n\n/**\n * Module config\n * @param model creates model\n * @param playerAnimation create player animation\n */\npublic record ModuleConfig(\n    boolean model,\n    boolean playerAnimation\n) {\n    /**\n     * Default config\n     */\n    public static final ModuleConfig DEFAULT = new ModuleConfig(\n        true,\n        true\n    );\n\n    /**\n     * Creates config from YAML\n     * @param predicate predicate\n     * @return config\n     */\n    public static @NotNull ModuleConfig from(@NotNull Predicate<String> predicate) {\n        return new ModuleConfig(\n            predicate.test(\"model\"),\n            predicate.test(\"player-animation\")\n        );\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/config/PackConfig.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.config;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Predicate;\n\n/**\n * Pack config\n * @param generateModernModel generate modern model\n * @param generateLegacyModel generate legacy model\n * @param useObfuscation use obfuscation\n */\npublic record PackConfig(\n    boolean generateModernModel,\n    boolean generateLegacyModel,\n    boolean useObfuscation\n) {\n    /**\n     * Default config\n     */\n    public static final PackConfig DEFAULT = new PackConfig(true, true, false);\n\n    /**\n     * Creates config from YAML\n     * @param predicate predicate\n     * @return config\n     */\n    public static @NotNull PackConfig from(@NotNull Predicate<String> predicate) {\n        return new PackConfig(\n            predicate.test(\"generate-modern-model\"),\n            predicate.test(\"generate-legacy-model\"),\n            predicate.test(\"use-obfuscation\")\n        );\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/Float2.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data;\n\nimport com.google.gson.JsonDeserializer;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector2f;\n\n/**\n * A simple record representing two float values.\n *\n * @param x the x value\n * @param y the y value\n * @since 3.0.0\n */\npublic record Float2(\n    float x,\n    float y\n) {\n    /**\n     * A GSON deserializer for {@link Float2}.\n     * @since 3.0.0\n     */\n    public static final JsonDeserializer<Float2> PARSER = (json, _, _) -> {\n        var array = json.getAsJsonArray();\n        return new Float2(\n            array.get(0).getAsFloat(),\n            array.get(1).getAsFloat()\n        );\n    };\n\n    /**\n     * Converts this record to a {@link Vector2f}.\n     *\n     * @return a new vector instance\n     * @since 3.0.0\n     */\n    public @NotNull Vector2f toVector() {\n        return new Vector2f(x, y);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/Float3.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonDeserializer;\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\n/**\n * A three float value (origin, rotation)\n * @param x x\n * @param y y\n * @param z z\n */\n@ApiStatus.Internal\npublic record Float3(\n    float x,\n    float y,\n    float z\n) {\n\n    /**\n     * Creates floats\n     * @param value scala\n     */\n    public Float3(float value) {\n        this(value, value, value);\n    }\n    /**\n     * Center\n     */\n    public static final Float3 CENTER = new Float3(8, 8, 8);\n    /**\n     * Zero\n     */\n    public static final Float3 ZERO = new Float3(0, 0, 0);\n\n    public static final Float3 MESH_TRIANGLE_FROM = new Float3(-8, 0, 0);\n\n    public static final Float3 MESH_TRIANGLE_TO = new Float3(0, 8, 0);\n\n    /**\n     * Parser\n     */\n    public static final JsonDeserializer<Float3> PARSER = (json, _, _) -> {\n        var array = json.getAsJsonArray();\n        return new Float3(\n            array.get(0).getAsFloat(),\n            array.get(1).getAsFloat(),\n            array.get(2).getAsFloat()\n        );\n    };\n\n    /**\n     * Adds other floats.\n     * @param other other floats\n     * @return new floats\n     */\n    public @NotNull Float3 plus(@NotNull Float3 other) {\n        return new Float3(\n            x + other.x,\n            y + other.y,\n            z + other.z\n        );\n    }\n\n    /**\n     * Converts zxy euler to xyz euler (Minecraft)\n     * @return new float\n     */\n    public @NotNull Float3 convertToMinecraftDegree() {\n        var vec = MathUtil.toXYZEuler(toVector());\n        return new Float3(vec.x, vec.y, vec.z);\n    }\n\n    /**\n     * Rotates this float\n     * @param quaternionf rotation\n     * @return new float\n     */\n    public @NotNull Float3 rotate(@NotNull Quaternionf quaternionf) {\n        var vec = toVector().rotate(quaternionf);\n        return new Float3(vec.x, vec.y, vec.z);\n    }\n\n\n    /**\n     * Subtracts other floats.\n     * @param other other floats\n     * @return new floats\n     */\n    public @NotNull Float3 minus(@NotNull Float3 other) {\n        return new Float3(\n            x - other.x,\n            y - other.y,\n            z - other.z\n        );\n    }\n\n    /**\n     * Converts item model scale to block scale\n     * @return block\n     */\n    public @NotNull Float3 toBlockScale() {\n        return div(MathUtil.MODEL_TO_BLOCK_MULTIPLIER);\n    }\n\n    /**\n     * Multiplies floats.\n     * @param value multiplier\n     * @return new floats\n     */\n    public @NotNull Float3 times(float value) {\n        return new Float3(\n            x * value,\n            y * value,\n            z * value\n        );\n    }\n    /**\n     * Divides floats.\n     * @param value multiplier\n     * @return new floats\n     */\n    public @NotNull Float3 div(float value) {\n        return new Float3(\n            x / value,\n            y / value,\n            z / value\n        );\n    }\n\n    /**\n     * Inverts XZ\n     * @return new floats\n     */\n    public @NotNull Float3 invertXZ() {\n        return new Float3(\n            -x,\n            y,\n            -z\n        );\n    }\n\n    /**\n     * Converts floats to JSON array.\n     * @return json array\n     */\n    public @NotNull JsonArray toJson() {\n        var array = new JsonArray(3);\n        array.add(x);\n        array.add(y);\n        array.add(z);\n        return array;\n    }\n\n    public @NotNull Quaternionf toQuaternionZYX() {\n        return new Quaternionf()\n            .rotateZYX(\n                z * MathUtil.DEGREES_TO_RADIANS,\n                y * MathUtil.DEGREES_TO_RADIANS,\n                x * MathUtil.DEGREES_TO_RADIANS\n            );\n    }\n\n    public @NotNull Quaternionf toQuaternionXYZ() {\n        return new Quaternionf()\n            .rotateXYZ(\n                x * MathUtil.DEGREES_TO_RADIANS,\n                y * MathUtil.DEGREES_TO_RADIANS,\n                z * MathUtil.DEGREES_TO_RADIANS\n            );\n    }\n\n    /**\n     * Converts floats to vector.\n     * @return vector\n     */\n    public @NotNull Vector3f toVector() {\n        return new Vector3f(x, y, z);\n    }\n\n    @Override\n    public int hashCode() {\n        var hash = 31;\n        var value = 1;\n        value = value * hash + MathUtil.similarHashCode(x);\n        value = value * hash + MathUtil.similarHashCode(y);\n        value = value * hash + MathUtil.similarHashCode(z);\n        return value;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) return true;\n        if (!(obj instanceof Float3(float x1, float y1, float z1))) return false;\n        return MathUtil.isSimilar(x, x1)\n            && MathUtil.isSimilar(y, y1)\n            && MathUtil.isSimilar(z, z1);\n    }\n\n    @Override\n    public @NotNull String toString() {\n        return toJson().toString();\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/Float4.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonDeserializer;\nimport kr.toxicity.model.api.data.raw.ModelResolution;\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * A four float values (uv)\n * @param dx from-x\n * @param dz from-z\n * @param tx to-x\n * @param tz to-z\n */\n@ApiStatus.Internal\npublic record Float4(\n    float dx,\n    float dz,\n    float tx,\n    float tz\n) {\n\n    /**\n     * Parser\n     */\n    public static final JsonDeserializer<Float4> PARSER = (json, _, _) -> {\n        var array = json.getAsJsonArray();\n        return new Float4(\n            array.get(0).getAsFloat(),\n            array.get(1).getAsFloat(),\n            array.get(2).getAsFloat(),\n            array.get(3).getAsFloat()\n        );\n    };\n\n    public static final Float4 MAX_UV = new Float4(0, 0, 16, 16);\n\n    /**\n     * Divides floats by resolution.\n     * @param resolution model resolution\n     * @return new floats\n     */\n    public @NotNull Float4 div(@NotNull ModelResolution resolution) {\n        return div((float) resolution.width() / MathUtil.MODEL_TO_BLOCK_MULTIPLIER, (float) resolution.height() / MathUtil.MODEL_TO_BLOCK_MULTIPLIER);\n    }\n\n    /**\n     * Divides floats by width, height\n     * @param width width\n     * @param height height\n     * @return new floats\n     */\n    public @NotNull Float4 div(float width, float height) {\n        return new Float4(\n            dx / width,\n            dz / height,\n            tx / width,\n            tz / height\n        );\n    }\n\n    /**\n     * Checks validity of this uv\n     * @return is valid\n     */\n    public boolean isValid() {\n        return dx >= 0 && dx <= 16\n            && dz >= 0 && dz <= 16\n            && tx >= 0 && tx <= 16\n            && tz >= 0 && tz <= 16;\n    }\n\n    /**\n     * Converts floats to JSON array.\n     * @return json array\n     */\n    public @NotNull JsonArray toJson() {\n        var array = new JsonArray(4);\n        array.add(dx);\n        array.add(dz);\n        array.add(tx);\n        array.add(tz);\n        return array;\n    }\n\n    @Override\n    public int hashCode() {\n        var hash = 31;\n        var value = 1;\n        value = value * hash + MathUtil.similarHashCode(dx);\n        value = value * hash + MathUtil.similarHashCode(dz);\n        value = value * hash + MathUtil.similarHashCode(tx);\n        value = value * hash + MathUtil.similarHashCode(tz);\n        return value;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) return true;\n        if (!(obj instanceof Float4(float dx1, float dz1, float tx1, float tz1))) return false;\n        return MathUtil.isSimilar(dx, dx1)\n            && MathUtil.isSimilar(dz, dz1)\n            && MathUtil.isSimilar(tx, tx1)\n            && MathUtil.isSimilar(tz, tz1);\n    }\n\n    @Override\n    public @NotNull String toString() {\n        return toJson().toString();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/ModelAsset.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data;\n\nimport com.google.gson.JsonParseException;\nimport kr.toxicity.model.api.data.raw.ModelData;\nimport kr.toxicity.model.api.data.raw.ModelLoadResult;\nimport kr.toxicity.model.api.util.PackUtil;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/**\n * Represents a raw model asset that can be loaded into the engine.\n * <p>\n * This record encapsulates the source of the model data (e.g., a file or stream), its name, and metadata.\n * It provides methods to load and parse the model data into a usable format.\n * </p>\n *\n * @param rawName the original raw name or path of the asset\n * @param name the sanitized, pack-compliant name of the asset\n * @param sizeAssume the estimated size of the asset in bytes (0 if unknown)\n * @param supplier a supplier for the input stream containing the model data\n * @since 2.0.0\n */\npublic record ModelAsset(\n    @NotNull String rawName,\n    @NotNull String name,\n    long sizeAssume,\n    @NotNull StreamSupplier supplier\n) implements Comparable<ModelAsset> {\n\n    /**\n     * Internal constructor for ModelAsset.\n     */\n    @ApiStatus.Internal\n    public ModelAsset {\n    }\n\n    /**\n     * Creates a new ModelAsset from a name and byte array.\n     *\n     * @param name the name of the asset\n     * @param bytes the byte array containing the model data\n     * @return the created asset\n     * @since 2.0.0\n     */\n    public static @NotNull ModelAsset of(@NotNull String name, byte[] bytes) {\n        return of(name, bytes.length, () -> new ByteArrayInputStream(bytes));\n    }\n\n    /**\n     * Creates a new ModelAsset from a name and stream supplier.\n     *\n     * @param name the name of the asset\n     * @param supplier the stream supplier\n     * @return the created asset\n     * @since 2.0.0\n     */\n    public static @NotNull ModelAsset of(@NotNull String name, @NotNull StreamSupplier supplier) {\n        return of(name, 0, supplier); // Unknown size\n    }\n\n    /**\n     * Creates a new ModelAsset from a name, stream supplier, and estimated size.\n     *\n     * @param name the name of the asset\n     * @param sizeAssume the estimated size in bytes\n     * @param supplier the stream supplier\n     * @return the created asset\n     * @since 2.0.0\n     */\n    public static @NotNull ModelAsset of(@NotNull String name, long sizeAssume, @NotNull StreamSupplier supplier) {\n        PackUtil.assertPackName(name);\n        return new ModelAsset(name, name, sizeAssume, supplier);\n    }\n\n    /**\n     * Creates a new ModelAsset from a file.\n     *\n     * @param file the source file\n     * @return the created asset\n     * @since 2.0.0\n     */\n    public static @NotNull ModelAsset of(@NotNull File file) {\n        return new ModelAsset(file.getPath(), nameWithoutExtension(file.getName()), file.length(), () -> new FileInputStream(file));\n    }\n\n    /**\n     * Creates a new ModelAsset from a path.\n     *\n     * @param path the source path\n     * @return the created asset\n     * @throws RuntimeException if an I/O error occurs\n     * @since 2.0.0\n     */\n    public static @NotNull ModelAsset of(@NotNull Path path) {\n        try {\n            return new ModelAsset(path.toString(), nameWithoutExtension(path.getFileName().toString()), Files.size(path), () -> Files.newInputStream(path));\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static @NotNull String nameWithoutExtension(@NotNull String name) {\n        var index = name.lastIndexOf('.');\n        return PackUtil.toPackName(index > 0 ? name.substring(0, index) : name);\n    }\n\n    /**\n     * Loads and parses the model data from this asset.\n     *\n     * @return the result of the load operation\n     * @throws RuntimeException if an I/O or parsing error occurs\n     * @since 2.0.0\n     */\n    public @NotNull ModelLoadResult toResult() {\n        try (\n            var stream = supplier.get();\n            var reader = new InputStreamReader(stream, StandardCharsets.UTF_8)\n        ) {\n            var result = ModelData.GSON.fromJson(reader, ModelData.class);\n            result.assertSupported();\n            return result.loadBlueprint(name);\n        } catch (IOException e) {\n            throw new RuntimeException(\"Unable to load this asset: \" + this, e);\n        } catch (JsonParseException e) {\n            throw new RuntimeException(\"Unable to parse this json asset: \" + this, e);\n        }\n    }\n\n    @Override\n    public int compareTo(@NotNull ModelAsset o) {\n        return name.compareTo(o.name);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof ModelAsset that)) return false;\n        return name.equals(that.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return name.hashCode();\n    }\n\n    @Override\n    public @NotNull String toString() {\n        return rawName;\n    }\n\n    /**\n     * A functional interface for supplying an input stream.\n     *\n     * @since 2.0.0\n     */\n    public interface StreamSupplier {\n        /**\n         * Gets the input stream.\n         *\n         * @return the input stream\n         * @throws IOException if an I/O error occurs\n         * @since 2.0.0\n         */\n        @NotNull InputStream get() throws IOException;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/AnimationGenerator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport it.unimi.dsi.fastutil.floats.*;\nimport kr.toxicity.model.api.animation.VectorPoint;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.util.InterpolationUtil;\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Vector3f;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.*;\n\n/**\n * Generates animation data by interpolating keyframes and calculating bone movements.\n * <p>\n * This class processes raw animation points and generates smooth transitions for position, rotation, and scale.\n * It handles the creation of intermediate frames to ensure fluid motion, especially for rotations.\n * </p>\n *\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic final class AnimationGenerator {\n\n    private static final Vector3f EMPTY = new Vector3f();\n    private final Map<BoneName, BlueprintAnimator.AnimatorData> pointMap;\n    private final List<AnimationTree> trees;\n\n    /**\n     * Creates a map of blueprint animators from the provided animation data.\n     * <p>\n     * This method calculates all necessary interpolation frames and builds the final animation structures for each bone.\n     * </p>\n     *\n     * @param length the total length of the animation in seconds\n     * @param children the list of root blueprint elements (bones)\n     * @param pointMap a map containing raw animation data for each bone\n     * @return a map of generated blueprint animators keyed by bone name\n     * @since 1.15.2\n     */\n    public static @NotNull Map<BoneName, BlueprintAnimator> createMovements(\n        float length,\n        @NotNull List<BlueprintElement> children,\n        @NotNull Map<BoneName, BlueprintAnimator.AnimatorData> pointMap\n    ) {\n        var floatSet = mapFloat(pointMap.values()\n            .stream()\n            .flatMap(BlueprintAnimator.AnimatorData::allPoints), VectorPoint::time, () -> new FloatAVLTreeSet(MathUtil.FRAME_COMPARATOR));\n        floatSet.add(0F);\n        floatSet.add(length);\n        InterpolationUtil.insertLerpFrame(floatSet);\n        var generator = new AnimationGenerator(pointMap, children);\n        generator.interpolateRotation(floatSet);\n        generator.interpolateStep(floatSet);\n        return mapValue(pointMap, v -> new BlueprintAnimator(\n            v.name(),\n            InterpolationUtil.buildAnimation(\n                v.position(),\n                v.rotation(),\n                v.scale(),\n                v.rotationGlobal(),\n                floatSet\n            )\n        ));\n    }\n\n    private AnimationGenerator(\n        @NotNull Map<BoneName, BlueprintAnimator.AnimatorData> pointMap,\n        @NotNull List<BlueprintElement> children\n    ) {\n        this.pointMap = pointMap;\n        trees = filterIsInstance(children, BlueprintElement.Group.class)\n            .map(g -> new AnimationTree(g, pointMap.get(g.name())))\n            .flatMap(AnimationTree::flatten)\n            .toList();\n    }\n\n    private float firstTime = 0F;\n    private float secondTime = 0F;\n\n    /**\n     * Inserts additional keyframes to smooth out large rotations.\n     * <p>\n     * This ensures that rotations larger than 90 degrees between frames are broken down into smaller steps.\n     * </p>\n     *\n     * @param floats the set of keyframe times to update\n     * @since 1.15.2\n     */\n    public void interpolateRotation(@NotNull FloatSortedSet floats) {\n        var iterator = new FloatArrayList(floats).iterator();\n        var time = 0.05F;\n        while (iterator.hasNext()) {\n            firstTime = secondTime;\n            secondTime = iterator.nextFloat();\n            if (secondTime - firstTime <= 0) continue;\n            var minus = trees.stream()\n                .mapToDouble(t -> t.tree(firstTime, secondTime, BlueprintAnimator.AnimatorData::rotation))\n                .max()\n                .orElse(0);\n            var length = (float) Math.ceil(minus / 90);\n            if (length < 2) continue;\n            var addTime = Math.max(\n                InterpolationUtil.lerp(0, secondTime - firstTime, 1F / length),\n                time\n            );\n            for (float f = 1; f < length; f++) {\n                if (secondTime - addTime < time + MathUtil.FRAME_EPSILON) continue;\n                floats.add(firstTime + f * addTime);\n            }\n        }\n    }\n\n    /**\n     * Inserts keyframes for step interpolation (non-continuous transitions).\n     *\n     * @param floats the set of keyframe times to update\n     * @since 1.15.2\n     */\n    public void interpolateStep(@NotNull FloatSortedSet floats) {\n        trees.stream()\n            .map(tree -> tree.data)\n            .filter(Objects::nonNull)\n            .forEach(data -> {\n                interpolateStep(floats, data.position());\n                interpolateStep(floats, data.rotation());\n                interpolateStep(floats, data.scale());\n            });\n    }\n\n    private void interpolateStep(@NotNull FloatSortedSet floats, @NotNull List<VectorPoint> points) {\n        if (points.size() < 2) return;\n        for (int i = 1; i < points.size(); i++) {\n            var before = points.get(i - 1);\n            if (before.isContinuous()) continue;\n            var time = points.get(i).time() - 0.05F;\n            if (time < 0 || time - before.time() < 0) continue;\n            floats.add(time);\n        }\n    }\n\n    private class AnimationTree {\n        private final AnimationTree parent;\n        private final List<AnimationTree> children;\n        private final BlueprintAnimator.AnimatorData data;\n        private int searchCache = 0;\n        private final Float2ObjectMap<Vector3f> valueCache = new Float2ObjectOpenHashMap<>();\n\n        AnimationTree(@NotNull BlueprintElement.Group group, @Nullable BlueprintAnimator.AnimatorData data) {\n            this(null, group, data);\n        }\n        AnimationTree(\n            @Nullable AnimationTree parent,\n            @NotNull BlueprintElement.Group group,\n            @Nullable BlueprintAnimator.AnimatorData data\n        ) {\n            this.parent = parent;\n            this.data = data;\n            children = filterIsInstance(group.children(), BlueprintElement.Group.class)\n                .map(g -> new AnimationTree(this, g, pointMap.get(g.name())))\n                .toList();\n        }\n\n        @NotNull\n        Stream<AnimationTree> flatten() {\n            return children.isEmpty() ? Stream.of(this) : Stream.concat(\n                Stream.of(this),\n                children.stream().flatMap(AnimationTree::flatten)\n            );\n        }\n\n        private float tree(float first, float second, @NotNull Function<BlueprintAnimator.AnimatorData, List<VectorPoint>> mapper) {\n            var value = data != null ? mapper.apply(data) : Collections.<VectorPoint>emptyList();\n            return findTree(first, second, value).length();\n        }\n\n        private @NotNull Vector3f findTree(float first, float second, @NotNull List<VectorPoint> target) {\n            var get = find(first, second, target);\n            return parent != null ? parent.findTree(first, second, target).add(get) : get;\n        }\n        private @NotNull Vector3f find(float first, float second, @NotNull List<VectorPoint> target) {\n            return find(second, target).sub(find(first, target), new Vector3f());\n        }\n        private @NotNull Vector3f find(float time, @NotNull List<VectorPoint> target) {\n            return valueCache.computeIfAbsent(time, _ -> {\n                if (target.size() <= 1) return EMPTY;\n                var i = searchCache;\n                for (; i < target.size(); i++) {\n                    if (target.get(i).time() >= time) break;\n                }\n                searchCache = i;\n                if (i == 0) return EMPTY;\n                if (i == target.size()) return EMPTY;\n                var first = target.get(i - 1);\n                var second = target.get(i);\n                var t1 = first.time();\n                var t2 = second.time();\n                var a = InterpolationUtil.alpha(t1, t2, time);\n                return second.time() == time ? second.vector() : InterpolationUtil.lerp(\n                    first.vector(InterpolationUtil.lerp(t1, t2, a)),\n                    second.vector(),\n                    a\n                );\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/BlueprintAnimation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport kr.toxicity.model.api.animation.AnimationIterator;\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport kr.toxicity.model.api.animation.AnimationProgress;\nimport kr.toxicity.model.api.animation.TimedStorage;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.script.BlueprintScript;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Map;\n\n/**\n * Represents a complete, processed animation for a model.\n * <p>\n * This record contains all the necessary data to play an animation, including keyframes for each bone,\n * loop settings, and associated scripts.\n * </p>\n *\n * @param name the name of the animation\n * @param loop the default loop mode\n * @param length the length of the animation in seconds\n * @param override whether this animation overrides others\n * @param animator a map of animators for each bone\n * @param script the script associated with this animation, if any\n * @param emptyAnimator a list of empty movements, used as a fallback or for initialization\n * @since 1.15.2\n */\npublic record BlueprintAnimation(\n    @NotNull String name,\n    @NotNull AnimationIterator.Type loop,\n    float length,\n    boolean override,\n    @NotNull @Unmodifiable Map<BoneName, BlueprintAnimator> animator,\n    @Nullable BlueprintScript script,\n    @NotNull TimedStorage<AnimationProgress> emptyAnimator\n) {\n\n    /**\n     * Retrieves the script for this animation, considering the provided modifier.\n     * <p>\n     * If the modifier overrides the animation or specifies a player, the script may be suppressed.\n     * </p>\n     *\n     * @param modifier the animation modifier\n     * @return the script, or null if suppressed\n     * @since 1.15.2\n     */\n    public @Nullable BlueprintScript script(@NotNull AnimationModifier modifier) {\n        return modifier.override(override) || modifier.player() != null ? null : script;\n    }\n\n    /**\n     * Creates an iterator for the empty animation sequence.\n     *\n     * @param type the loop type\n     * @return an animation iterator\n     * @since 1.15.2\n     */\n    public @NotNull AnimationIterator<AnimationProgress> emptyIterator(@NotNull AnimationIterator.Type type) {\n        return type.create(emptyAnimator);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/BlueprintAnimator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport kr.toxicity.model.api.animation.AnimationIterator;\nimport kr.toxicity.model.api.animation.AnimationKeyframe;\nimport kr.toxicity.model.api.animation.AnimationProgress;\nimport kr.toxicity.model.api.animation.VectorPoint;\nimport kr.toxicity.model.api.bone.BoneName;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\nimport java.util.stream.Stream;\n\n/**\n * Represents the processed animation data for a single bone within a model blueprint.\n * <p>\n * This record holds the sequence of keyframes that define the bone's movement over time.\n * </p>\n *\n * @param name the name of the bone this animator applies to\n * @param keyframe a list of animation movements representing the keyframes\n * @since 1.15.2\n */\npublic record BlueprintAnimator(\n    @NotNull BoneName name,\n    @NotNull AnimationKeyframe keyframe\n) {\n\n    /**\n     * Holds the raw, separated animation data points for a bone before final processing.\n     *\n     * @param name the name of the bone\n     * @param position a list of position keyframes\n     * @param scale a list of scale keyframes\n     * @param rotation a list of rotation keyframes\n     * @param rotationGlobal whether the rotation is applied globally\n     * @since 1.15.2\n     */\n    public record AnimatorData(\n        @NotNull BoneName name,\n        @NotNull List<VectorPoint> position,\n        @NotNull List<VectorPoint> scale,\n        @NotNull List<VectorPoint> rotation,\n        boolean rotationGlobal\n    ) {\n        /**\n         * Returns a stream containing all keyframe points (position, scale, and rotation).\n         *\n         * @return a stream of all vector points\n         * @since 1.15.2\n         */\n        public @NotNull Stream<VectorPoint> allPoints() {\n            return Stream.concat(\n                Stream.concat(\n                    position.stream(),\n                    scale.stream()\n                ),\n                rotation.stream()\n            );\n        }\n    }\n\n    /**\n     * Creates an iterator for the keyframes based on a specified loop type.\n     *\n     * @param type the loop type (e.g., play_once, loop)\n     * @return an animation iterator\n     * @since 1.15.2\n     */\n    public @NotNull AnimationIterator<AnimationProgress> iterator(@NotNull AnimationIterator.Type type) {\n        return type.create(keyframe);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/BlueprintElement.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport com.google.gson.JsonObject;\nimport io.github.toxicity188.javamesh.MeshBuilder;\nimport io.github.toxicity188.javamesh.MeshPoint;\nimport io.github.toxicity188.javamesh.MeshShape;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.bone.BoneTags;\nimport kr.toxicity.model.api.data.Float2;\nimport kr.toxicity.model.api.data.Float3;\nimport kr.toxicity.model.api.data.raw.ModelFace;\nimport kr.toxicity.model.api.pack.PackObfuscator;\nimport kr.toxicity.model.api.util.MathUtil;\nimport kr.toxicity.model.api.util.PackUtil;\nimport kr.toxicity.model.api.util.json.JsonObjectBuilder;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\nimport org.joml.Quaternionf;\n\nimport java.util.*;\nimport java.util.stream.Stream;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.*;\n\n/**\n * Represents a processed element within a model blueprint.\n * <p>\n * This is a sealed interface with implementations for different types of elements like bones, groups, cubes, and locators.\n * </p>\n *\n * @since 1.15.2\n */\npublic sealed interface BlueprintElement {\n\n    String MESH_TRIANGLE_SINGLE = \"mesh_triangle_single\";\n\n    String MESH_TRIANGLE_DUPLEX = \"mesh_triangle_duplex\";\n\n    String MESH_PIXEL = \"mesh_pixel\";\n\n    /**\n     * Represents an element that acts as a bone in the model's armature.\n     *\n     * @since 1.15.2\n     */\n    sealed interface Bone extends BlueprintElement {\n\n        /**\n         * Returns the UUID of the bone.\n         *\n         * @return the UUID\n         * @since 1.15.2\n         */\n        @NotNull UUID uuid();\n\n        /**\n         * Returns the name of the bone.\n         *\n         * @return the bone name\n         * @since 1.15.2\n         */\n        @NotNull BoneName name();\n\n        /**\n         * Returns the origin (pivot point) of the bone.\n         *\n         * @return the origin\n         * @since 1.15.2\n         */\n        @NotNull Float3 origin();\n    }\n\n    /**\n     * Returns the rotation of the element.\n     *\n     * @return the rotation, defaulting to zero\n     * @since 1.15.2\n     */\n    default @NotNull Float3 rotation() {\n        return Float3.ZERO;\n    }\n\n    /**\n     * Checks if the element is visible.\n     *\n     * @return true if visible, false otherwise\n     * @since 1.15.2\n     */\n    default boolean visibility() {\n        return false;\n    }\n\n    /**\n     * Represents a group of elements, forming a bone in the hierarchy.\n     *\n     * @param uuid the UUID of the group\n     * @param name the name of the group/bone\n     * @param origin the pivot point of the group\n     * @param rotation the rotation of the group\n     * @param children the list of child elements\n     * @param visibility whether the group is visible\n     * @since 1.15.2\n     */\n    record Group(\n        @NotNull UUID uuid,\n        @NotNull BoneName name,\n        @NotNull Float3 origin,\n        @NotNull Float3 rotation,\n        @NotNull List<BlueprintElement> children,\n        boolean visibility\n    ) implements Bone {\n\n        /**\n         * Returns the origin with inverted X and Z axes.\n         *\n         * @return the inverted origin\n         * @since 1.15.2\n         */\n        @Override\n        @NotNull\n        public Float3 origin() {\n            return origin.invertXZ();\n        }\n\n        private @NotNull String jsonName(@NotNull BlueprintLoadContext context) {\n            return PackUtil.toPackName(context.name() + \"_\" + name.rawName());\n        }\n\n        /**\n         * Builds the JSON representation for legacy clients (1.21.3 or under).\n         *\n         * @param obfuscator the obfuscator for model and texture names\n         * @param context the load context\n         * @return the generated blueprint JSON, or null if not applicable\n         * @since 1.15.2\n         */\n        public @Nullable BlueprintJson buildLegacyJson(\n            @NotNull PackObfuscator.Pair obfuscator,\n            @NotNull BlueprintLoadContext context\n        ) {\n            return buildJson(-2, 1, scale(), obfuscator, context, Float3.ZERO, filterIsInstance(children, Cube.class).filter(element -> MathUtil.checkValidDegree(element.identifierDegree())));\n        }\n\n        /**\n         * Builds the JSON representation for modern clients.\n         *\n         * @param obfuscator the obfuscator for model and texture names\n         * @param context the load context\n         * @return a list of generated blueprint JSONs, or null if not applicable\n         * @since 1.15.2\n         */\n        @Nullable\n        @Unmodifiable\n        public List<BlueprintJson> buildModernJson(\n            @NotNull PackObfuscator.Pair obfuscator,\n            @NotNull BlueprintLoadContext context\n        ) {\n            var scale = scale();\n            var list = mapIndexed(\n                group(\n                    filterIsInstance(children, Cube.class),\n                    Cube::identifierDegree\n                ),\n                (i, entry) -> buildJson(0, i + 1, scale, obfuscator, context, entry.getKey(), entry.getValue().stream())\n            ).filter(Objects::nonNull)\n                .toList();\n            return list.isEmpty() ? null : list;\n        }\n\n        /**\n         * Builds the JSON representation for a mesh-based item model.\n         *\n         * @param context the load context\n         * @return the generated mesh JSON, or null if no meshes are present\n         * @since 3.0.0\n         */\n        public @Nullable JsonObject buildMeshItemModel(\n            @NotNull BlueprintLoadContext context\n        ) {\n            var scale = 1F / scale();\n            var meshes = filterIsInstance(children, Mesh.class).toList();\n            if (meshes.isEmpty()) return null;\n            var builder = MeshBuilder.of(context.triangleName())\n                .matrixModifier(mat -> mat.scale(scale))\n                .image(context.imageByIndex());\n            meshes.forEach(mesh -> builder.load(mesh.toShape(origin)));\n            return builder.toJson();\n        }\n\n        private @Nullable BlueprintJson buildJson(\n            int tint,\n            int number,\n            float scale,\n            @NotNull PackObfuscator.Pair obfuscator,\n            @NotNull BlueprintLoadContext context,\n            @NotNull Float3 identifier,\n            @NotNull Stream<Cube> cubes\n        ) {\n            var cubeElement = cubes\n                .filter(Cube::hasTexture)\n                .toList();\n            var selectedTextures = cubeElement.stream()\n                .flatMapToInt(tex -> tex.faces().textureIndex())\n                .distinct()\n                .sorted()\n                .mapToObj(i -> Map.entry(Integer.toString(i), context.texture(i).packNamespace(obfuscator.textures())))\n                .toList();\n            if (selectedTextures.isEmpty()) return null;\n            return new BlueprintJson(obfuscator.models().obfuscate(jsonName(context) + \"_\" + number), () -> JsonObjectBuilder.builder()\n                .jsonObject(\"textures\", textures -> textures\n                    .stringProperties(selectedTextures)\n                    .property(\"particle\", selectedTextures.getFirst().getValue()))\n                .jsonArray(\"elements\", mapToJson(cubeElement, cube -> cube.buildJson(tint, scale, context, this, identifier)))\n                .jsonObject(\"display\", display -> display.jsonObject(\"fixed\", fixed -> {\n                    if (!identifier.equals(Float3.ZERO)) {\n                        fixed.jsonArray(\"rotation\", identifier.convertToMinecraftDegree().toJson());\n                    }\n                }))\n                .build());\n        }\n\n        /**\n         * Calculates the required scale for the cubes in this group.\n         *\n         * @return the scale factor\n         * @since 1.15.2\n         */\n        public float scale() {\n            return (float) Math.max(filterIsInstance(children, Cube.class)\n                .mapToDouble(e -> e.max(origin) / 16F)\n                .max()\n                .orElse(1F), 1F);\n        }\n\n        /**\n         * Calculates the bounding box for this group to be used as a hitbox.\n         *\n         * @return the named bounding box, or null if no cubes are present\n         * @since 1.15.2\n         */\n        public @Nullable ModelBoundingBox hitBox() {\n            return filterIsInstance(children, Cube.class)\n                .map(element -> {\n                    var from = element.from()\n                        .minus(origin)\n                        .toBlockScale();\n                    var to = element.to()\n                        .minus(origin)\n                        .toBlockScale();\n                    return ModelBoundingBox.of(\n                        from.x(),\n                        from.y(),\n                        from.z(),\n                        to.x(),\n                        to.y(),\n                        to.z()\n                    ).invert();\n                })\n                .max(Comparator.comparingDouble(ModelBoundingBox::length))\n                .orElse(null);\n        }\n    }\n\n    /**\n     * Represents a locator element, used as a named attachment point.\n     *\n     * @param uuid the UUID of the locator\n     * @param name the name of the locator\n     * @param origin the position of the locator\n     * @since 1.15.2\n     */\n    record Locator(\n        @NotNull UUID uuid,\n        @NotNull BoneName name,\n        @NotNull Float3 origin\n    ) implements Bone {\n        /**\n         * Returns the origin with inverted X and Z axes.\n         *\n         * @return the inverted origin\n         * @since 1.15.2\n         */\n        @Override\n        @NotNull\n        public Float3 origin() {\n            return origin.invertXZ();\n        }\n    }\n\n    /**\n     * Represents a camera element (currently a placeholder).\n     *\n     * @param uuid the UUID of the camera\n     * @since 1.15.2\n     */\n    record Camera(\n        @NotNull UUID uuid\n    ) implements BlueprintElement {\n    }\n\n    /**\n     * Represents a null object, often used for IK or as a simple bone.\n     *\n     * @param uuid the UUID of the null object\n     * @param name the name of the null object\n     * @param ikTarget the UUID of the IK target bone\n     * @param ikSource the UUID of the IK source bone\n     * @param origin the position of the null object\n     * @since 1.15.2\n     */\n    record NullObject(\n        @NotNull UUID uuid,\n        @NotNull BoneName name,\n        @Nullable UUID ikTarget,\n        @Nullable UUID ikSource,\n        @NotNull Float3 origin\n    ) implements Bone {\n        /**\n         * Returns the origin with inverted X and Z axes.\n         *\n         * @return the inverted origin\n         * @since 1.15.2\n         */\n        @Override\n        @NotNull\n        public Float3 origin() {\n            return origin.invertXZ();\n        }\n    }\n\n    /**\n     * Represents a cube element, the basic building block of a model.\n     *\n     * @param name the name of the cube\n     * @param from the starting coordinate (min corner)\n     * @param to the ending coordinate (max corner)\n     * @param inflate the inflation value\n     * @param rotation the rotation of the cube\n     * @param origin the pivot point of the cube\n     * @param faces the UV mapping for the faces\n     * @param lightEmission the light emission level (1-15 or null if 0)\n     * @param visibility whether the cube is visible\n     * @since 1.15.2\n     */\n    record Cube(\n        @NotNull String name,\n        @NotNull Float3 from,\n        @NotNull Float3 to,\n        float inflate,\n        @NotNull Float3 rotation,\n        @NotNull Float3 origin,\n        @Nullable ModelFace faces,\n        @Nullable Integer lightEmission,\n        boolean visibility\n    ) implements BlueprintElement {\n\n        private @NotNull Float3 identifierDegree() {\n            return MathUtil.identifier(rotation());\n        }\n\n        private static @NotNull Float3 centralize(@NotNull Float3 target, @NotNull Float3 groupOrigin, float scale) {\n            return target.minus(groupOrigin).div(scale);\n        }\n\n        private static @NotNull Float3 deltaPosition(@NotNull Float3 target, @NotNull Quaternionf quaternionf) {\n            return target.rotate(quaternionf).minus(target);\n        }\n\n        private @NotNull JsonObject buildJson(\n            int tint,\n            float scale,\n            @NotNull BlueprintLoadContext parent,\n            @NotNull BlueprintElement.Group group,\n            @NotNull Float3 identifier\n        ) {\n            var qua = identifier.toQuaternionZYX().invert();\n            var centerOrigin = centralize(origin(), group.origin, scale);\n            var groupDelta = deltaPosition(centerOrigin, qua);\n            var inflate = new Float3(inflate() / scale);\n            return JsonObjectBuilder.builder()\n                .property(\"light_emission\", group.name.tagged(BoneTags.GLOW) ? Integer.valueOf(15) : lightEmission)\n                .jsonArray(\"from\", centralize(from(), group.origin, scale)\n                    .plus(groupDelta)\n                    .plus(Float3.CENTER)\n                    .minus(inflate)\n                    .toJson())\n                .jsonArray(\"to\", centralize(to(), group.origin, scale)\n                    .plus(groupDelta)\n                    .plus(Float3.CENTER)\n                    .plus(inflate)\n                    .toJson())\n                .jsonObject(\"faces\", faces().toJson(parent, tint))\n                .jsonObject(\"rotation\", Optional.of(rotation().minus(identifier))\n                    .filter(r -> !Float3.ZERO.equals(r))\n                    .map(rot -> {\n                        var rotation = getRotation(rot);\n                        rotation.add(\"origin\", centerOrigin\n                            .plus(groupDelta)\n                            .plus(Float3.CENTER)\n                            .toJson());\n                        return rotation;\n                    })\n                    .orElse(null))\n                .build();\n        }\n\n        /**\n         * Calculates the maximum distance from the origin to any corner of the cube.\n         *\n         * @param origin the reference origin\n         * @return the maximum length\n         * @since 1.15.2\n         */\n        public float max(@NotNull Float3 origin) {\n            var f = from().minus(origin);\n            var t = to().minus(origin);\n            var max = 0F;\n            max = Math.max(max, Math.abs(f.x()));\n            max = Math.max(max, Math.abs(f.y()));\n            max = Math.max(max, Math.abs(f.z()));\n            max = Math.max(max, Math.abs(t.x()));\n            max = Math.max(max, Math.abs(t.y()));\n            max = Math.max(max, Math.abs(t.z()));\n            return max;\n        }\n\n        @Override\n        public @NotNull ModelFace faces() {\n            return Objects.requireNonNull(faces);\n        }\n\n        /**\n         * Checks if this cube has any textures defined.\n         *\n         * @return true if it has textures, false otherwise\n         * @since 1.15.2\n         */\n        public boolean hasTexture() {\n            return faces != null && faces.hasTexture();\n        }\n\n        private @NotNull JsonObject getRotation(@NotNull Float3 rot) {\n            var rotation = new JsonObject();\n            if (Math.abs(rot.x()) > 0) {\n                rotation.addProperty(\"angle\", rot.x());\n                rotation.addProperty(\"axis\", \"x\");\n            } else if (Math.abs(rot.y()) > 0) {\n                rotation.addProperty(\"angle\", rot.y());\n                rotation.addProperty(\"axis\", \"y\");\n            } else if (Math.abs(rot.z()) > 0) {\n                rotation.addProperty(\"angle\", rot.z());\n                rotation.addProperty(\"axis\", \"z\");\n            }\n            return rotation;\n        }\n    }\n\n    /**\n     * Represents a mesh element, allowing for complex geometry beyond simple cubes.\n     *\n     * @param origin the pivot point of the mesh\n     * @param rotation the rotation of the mesh\n     * @param faces the list of faces forming the mesh\n     * @param visibility whether the mesh is visible\n     * @since 3.0.0\n     */\n    record Mesh(\n        @NotNull Float3 origin,\n        @NotNull Float3 rotation,\n        @NotNull List<Face> faces,\n        boolean visibility\n    ) implements BlueprintElement {\n\n        /**\n         * Converts this mesh into a list of {@link MeshShape} for rendering.\n         *\n         * @param parentOrigin the origin of the parent bone\n         * @return an unmodifiable list of mesh shapes\n         * @since 3.0.0\n         */\n        @NotNull\n        @Unmodifiable\n        public List<MeshShape> toShape(@NotNull Float3 parentOrigin) {\n            var deltaOrigin = origin().minus(parentOrigin).toVector();\n            var pointRotation = rotation().toQuaternionXYZ();\n            return faces.stream()\n                .map(face -> new MeshShape(\n                    face.points.stream()\n                        .map(p -> new MeshPoint(\n                            p.vertices.toVector()\n                                .rotate(pointRotation)\n                                .add(deltaOrigin)\n                                .mul(-1F, 1F, -1F)\n                                .div(MathUtil.MODEL_TO_BLOCK_MULTIPLIER),\n                            p.uv.toVector()\n                                .div(MathUtil.MODEL_TO_BLOCK_MULTIPLIER)\n                        ))\n                        .toList(),\n                    Integer.toString(face.texture)\n                ))\n                .toList();\n        }\n\n        /**\n         * Represents a single face of a mesh.\n         *\n         * @param points the vertices and UV coordinates of the face\n         * @param texture the index of the texture used by this face\n         * @since 3.0.0\n         */\n        public record Face(@NotNull @Unmodifiable List<Point> points, int texture) {}\n\n        /**\n         * Represents a single point (vertex) in a mesh face.\n         *\n         * @param vertices the 3D coordinates of the vertex\n         * @param uv the 2D UV coordinates for texture mapping\n         * @since 3.0.0\n         */\n        public record Point(@NotNull Float3 vertices, @NotNull Float2 uv) {}\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/BlueprintImage.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport com.google.gson.JsonObject;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents an image file to be generated as part of the resource pack.\n * <p>\n * This record holds the image's name, its binary content, and an optional .mcmeta file for animations.\n * </p>\n *\n * @param name the name of the image file (including extension)\n * @param image the binary content of the image\n * @param mcmeta the JSON object for the .mcmeta file, if any\n * @since 1.15.2\n */\npublic record BlueprintImage(@NotNull String name, byte[] image, @Nullable JsonObject mcmeta) {\n    /**\n     * Returns the estimated size of the image in bytes.\n     *\n     * @return the image size\n     * @since 1.15.2\n     */\n    public long estimatedSize() {\n        return image.length;\n    }\n\n    /**\n     * Returns the name of the image file with a .png extension.\n     *\n     * @return the png file name\n     * @since 2.0.1\n     */\n    public @NotNull String pngName() {\n        return name + \".png\";\n    }\n\n    /**\n     * Returns the name of the metadata file associated with the png.\n     *\n     * @return the mcmeta file name\n     * @since 2.0.1\n     */\n    public @NotNull String mcmetaName() {\n        return pngName() + \".mcmeta\";\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/BlueprintJson.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport com.google.gson.JsonElement;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Supplier;\n\n/**\n * Represents a JSON file to be generated as part of the resource pack.\n * <p>\n * This record holds the file name and a supplier for the JSON content.\n * </p>\n *\n * @param name the name of the JSON file (without extension)\n * @param element a supplier that provides the JSON content\n * @since 1.15.2\n */\npublic record BlueprintJson(\n    @NotNull String name,\n    @NotNull Supplier<JsonElement> element\n) {\n\n    /**\n     * Returns the name of the JSON file with a .json extension.\n     *\n     * @return the JSON file name\n     * @since 2.0.1\n     */\n    public @NotNull String jsonName() {\n        return name + \".json\";\n    }\n\n    /**\n     * Builds and returns the JSON content by invoking the supplier.\n     *\n     * @since 2.0.1\n     * @return the generated JSON element\n     */\n    public @NotNull JsonElement buildJson() {\n        return element.get();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/BlueprintLoadContext.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport io.github.toxicity188.javamesh.MeshImage;\nimport io.github.toxicity188.javamesh.MeshTriangleName;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.data.raw.ModelResolution;\nimport kr.toxicity.model.api.pack.PackObfuscator;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport javax.imageio.ImageIO;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\n/**\n * A context class for loading blueprints.\n *\n * @since 3.0.0\n */\n@ApiStatus.Internal\npublic final class BlueprintLoadContext {\n\n    private final String name;\n    private final ModelResolution resolution;\n    private final TextureRef[] textureRefs;\n    private final boolean canBeRendered;\n\n    private volatile Map<String, MeshImage> imageRefMap;\n\n    private final MeshTriangleName triangleName = new MeshTriangleName(\n        BetterModel.config().namespace() + \":\" + BlueprintElement.MESH_TRIANGLE_SINGLE,\n        BetterModel.config().namespace() + \":\" + BlueprintElement.MESH_TRIANGLE_DUPLEX\n    );\n\n    BlueprintLoadContext(\n        @NotNull String name,\n        @NotNull ModelResolution resolution,\n        @NotNull List<BlueprintTexture> textures\n    ) {\n        this.name = name;\n        this.resolution = resolution;\n        this.textureRefs = new TextureRef[textures.size()];\n        var i = 0;\n        var canBeRendered = false;\n        for (BlueprintTexture texture : textures) {\n            canBeRendered |= texture.canBeRendered();\n            this.textureRefs[i++] = new TextureRef(texture);\n        }\n        this.canBeRendered = canBeRendered;\n    }\n\n    /**\n     * Gets the name of the blueprint.\n     *\n     * @return the name\n     * @since 3.0.0\n     */\n    public @NotNull String name() {\n        return name;\n    }\n\n    /**\n     * Gets the mesh triangle name.\n     *\n     * @return the triangle name\n     * @since 3.0.0\n     */\n    public @NotNull MeshTriangleName triangleName() {\n        return triangleName;\n    }\n\n    /**\n     * Gets the model resolution.\n     *\n     * @return the resolution\n     * @since 3.0.0\n     */\n    public @NotNull ModelResolution resolution() {\n        return resolution;\n    }\n\n    /**\n     * Gets a texture by its index.\n     *\n     * @param index the index\n     * @return the texture\n     * @since 3.0.0\n     */\n    public @NotNull BlueprintTexture texture(int index) {\n        return Objects.requireNonNull(textureRefs[index]).texture();\n    }\n\n    @NotNull\n    @Unmodifiable\n    Map<String, MeshImage> imageByIndex() {\n        Map<String, MeshImage> map;\n        if ((map = imageRefMap) != null) return map;\n        synchronized (this) {\n            if ((map = imageRefMap) != null) return map;\n            return imageRefMap = Collections.unmodifiableMap(new AbstractMap<>() {\n                @Override\n                public MeshImage get(Object key) {\n                    var get = textureRefs[Integer.parseInt(key.toString())];\n                    return get != null ? get.image() : null;\n                }\n\n                @Override\n                @Unmodifiable\n                public @NotNull Set<Entry<String, MeshImage>> entrySet() {\n                    return IntStream.range(0, textureRefs.length)\n                        .mapToObj(i -> Map.entry(Integer.toString(i), textureRefs[i].image()))\n                        .collect(Collectors.toUnmodifiableSet());\n                }\n            });\n        }\n    }\n\n    /**\n     * Returns whether this context can be rendered.\n     *\n     * @return true if renderable\n     * @since 3.0.0\n     */\n    public boolean canBeRendered() {\n        return canBeRendered;\n    }\n\n    /**\n     * Builds a stream of blueprint images using the provided obfuscator.\n     *\n     * @param obfuscator the obfuscator\n     * @return a stream of images\n     * @since 3.0.0\n     */\n    @NotNull\n    public Stream<BlueprintImage> buildImage(@NotNull PackObfuscator obfuscator) {\n        if (!canBeRendered()) return Stream.empty();\n        return Arrays.stream(textureRefs)\n            .filter(TextureRef::canBeRendered)\n            .map(ref -> new BlueprintImage(\n                ref.texture.packName(obfuscator),\n                ref.texture.image(),\n                ref.texture.isAnimatedTexture() ? ref.texture.toMcmeta() : null)\n            );\n    }\n\n    private static final class TextureRef {\n\n        private final BlueprintTexture texture;\n        private final AtomicBoolean referenced = new AtomicBoolean();\n        private volatile MeshImage image;\n\n        private TextureRef(BlueprintTexture texture) {\n            this.texture = texture;\n        }\n\n        public boolean canBeRendered() {\n            return referenced.get() && texture.canBeRendered();\n        }\n\n        public @NotNull BlueprintTexture texture() {\n            referenced.set(true);\n            return texture;\n        }\n\n        public @NotNull MeshImage image() {\n            MeshImage img;\n            if ((img = image) != null) return img;\n            synchronized (this) {\n                if ((img = image) != null) return img;\n                try (var input = new ByteArrayInputStream(texture.image())) {\n                    return image = MeshImage.from(ImageIO.read(input));\n                } catch (IOException e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/BlueprintTexture.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport com.google.gson.JsonObject;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.data.raw.ModelResolution;\nimport kr.toxicity.model.api.pack.PackObfuscator;\nimport kr.toxicity.model.api.util.json.JsonObjectBuilder;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a processed texture in a model blueprint.\n * <p>\n * This record holds the texture's name, binary image data, dimensions, and rendering properties.\n * </p>\n *\n * @param name the internal name of the texture\n * @param image the binary content of the texture image\n * @param width the original width of the texture in pixels\n * @param height the original height of the texture in pixels\n * @param uvWidth the UV width of the texture, if specified\n * @param uvHeight the UV height of the texture, if specified\n * @param canBeRendered whether this texture should be included in the resource pack\n * @param frameTime the frame time of the texture\n * @param frameInterpolate the interpolation flag of the texture\n * @since 1.15.2\n */\npublic record BlueprintTexture(\n    @NotNull String name,\n    byte[] image,\n    int width,\n    int height,\n    int uvWidth,\n    int uvHeight,\n    boolean canBeRendered,\n    int frameTime,\n    boolean frameInterpolate\n) {\n    /**\n     * Checks if this texture is an animated texture (a texture atlas for animation).\n     *\n     * @return true if it is an animated texture, false otherwise\n     * @since 1.15.2\n     */\n    public boolean isAnimatedTexture() {\n        if (hasUVSize()) {\n            var h = (float) height / uvHeight;\n            var w = (float) width / uvWidth;\n            return h > w;\n        } else {\n            return height > 0 && width > 0 && height / width > 1;\n        }\n    }\n\n    /**\n     * Generates the .mcmeta file content for this texture if it is animated.\n     *\n     * @return the JSON object for the .mcmeta file\n     * @since 1.15.2\n     */\n    public @NotNull JsonObject toMcmeta() {\n        return JsonObjectBuilder.builder()\n            .jsonObject(\"animation\", animation -> {\n                animation.property(\"interpolate\", frameInterpolate());\n                animation.property(\"frametime\", frameTime());\n            })\n            .build();\n    }\n\n    /**\n     * Generates the pack-compliant file name for this texture.\n     *\n     * @param obfuscator the obfuscator to use for the name\n     * @return the obfuscated file name\n     * @since 1.15.2\n     */\n    public @NotNull String packName(@NotNull PackObfuscator obfuscator) {\n        return obfuscator.obfuscate(name());\n    }\n\n    /**\n     * Generates the full resource pack namespace path for this texture.\n     *\n     * @param obfuscator the obfuscator to use for the name\n     * @return the texture's namespace path\n     * @since 1.15.2\n     */\n    public @NotNull String packNamespace(@NotNull PackObfuscator obfuscator) {\n        return BetterModel.config().namespace() + \":item/\" + packName(obfuscator);\n    }\n\n    /**\n     * Checks if this texture has a specific UV size defined.\n     *\n     * @return true if UV width and height are specified, false otherwise\n     * @since 1.15.2\n     */\n    public boolean hasUVSize() {\n        return uvWidth > 0 && uvHeight > 0;\n    }\n\n    /**\n     * Returns the effective resolution for this texture's UV mapping.\n     *\n     * @param resolution the parent model's resolution\n     * @return the UV resolution, or the parent resolution if not specified\n     * @since 1.15.2\n     */\n    public @NotNull ModelResolution resolution(@NotNull ModelResolution resolution) {\n        if (!hasUVSize()) return resolution;\n        return resolution.width() == width && resolution.height() == height ? resolution : new ModelResolution(uvWidth, uvHeight);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/ModelBlueprint.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport kr.toxicity.model.api.data.raw.ModelResolution;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Represents a fully processed model blueprint, ready for generation and rendering.\n * <p>\n * This record contains all the necessary data derived from a raw model file, including\n * textures, structural elements (bones/cubes), and animations.\n * </p>\n *\n * @param name the name of the model\n * @param resolution the texture resolution of the model\n * @param textures the list of textures used by the model\n * @param elements the hierarchical list of model elements (bones)\n * @param animations a map of animations available for this model\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelBlueprint(\n    @NotNull String name,\n    @NotNull ModelResolution resolution,\n    @NotNull List<BlueprintTexture> textures,\n    @NotNull List<BlueprintElement> elements,\n    @NotNull Map<String, BlueprintAnimation> animations\n) {\n\n    /**\n     * Creates a new load context for this blueprint.\n     *\n     * @since 3.0.0\n     * @return a new blueprint load context\n     */\n    public @NotNull BlueprintLoadContext context() {\n        return new BlueprintLoadContext(\n            name(),\n            resolution(),\n            textures()\n        );\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/blueprint/ModelBoundingBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.blueprint;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaterniond;\nimport org.joml.Vector3d;\nimport org.joml.Vector3f;\n\n/**\n * Represents an axis-aligned bounding box (AABB) for a model part.\n * <p>\n * This record defines the spatial extent of a model element or group, used for hitboxes and collision detection.\n * </p>\n *\n * @param minX the minimum X coordinate\n * @param minY the minimum Y coordinate\n * @param minZ the minimum Z coordinate\n * @param maxX the maximum X coordinate\n * @param maxY the maximum Y coordinate\n * @param maxZ the maximum Z coordinate\n * @since 1.15.2\n */\npublic record ModelBoundingBox(\n    double minX,\n    double minY,\n    double minZ,\n    double maxX,\n    double maxY,\n    double maxZ\n) {\n    /**\n     * A minimal bounding box size (0.1 x 0.1 x 0.1).\n     * @since 1.15.2\n     */\n    public static final ModelBoundingBox MIN = of(0.1, 0.1, 0.1);\n\n    /**\n     * Creates a bounding box from two corner vectors.\n     *\n     * @param min the minimum corner vector\n     * @param max the maximum corner vector\n     * @return the bounding box\n     * @since 1.15.2\n     */\n    public static @NotNull ModelBoundingBox of(@NotNull Vector3d min, @NotNull Vector3d max) {\n        return of(\n            min.x,\n            min.y,\n            min.z,\n            max.x,\n            max.y,\n            max.z\n        );\n    }\n\n    /**\n     * Creates a bounding box centered at the origin with the given dimensions.\n     *\n     * @param x the width (X-axis)\n     * @param y the height (Y-axis)\n     * @param z the depth (Z-axis)\n     * @return the centered bounding box\n     * @since 1.15.2\n     */\n    public static @NotNull ModelBoundingBox of(double x, double y, double z) {\n        return of(\n            -x / 2,\n            -y / 2,\n            -z / 2,\n            x / 2,\n            y / 2,\n            z / 2\n        );\n    }\n\n    /**\n     * Creates a bounding box from explicit min/max coordinates.\n     * <p>\n     * This method automatically ensures that min values are less than or equal to max values.\n     * </p>\n     *\n     * @param minX the first X coordinate\n     * @param minY the first Y coordinate\n     * @param minZ the first Z coordinate\n     * @param maxX the second X coordinate\n     * @param maxY the second Y coordinate\n     * @param maxZ the second Z coordinate\n     * @return the normalized bounding box\n     * @since 1.15.2\n     */\n    public static @NotNull ModelBoundingBox of(\n        double minX,\n        double minY,\n        double minZ,\n        double maxX,\n        double maxY,\n        double maxZ\n    ) {\n        return new ModelBoundingBox(\n            Math.min(minX, maxX),\n            Math.min(minY, maxY),\n            Math.min(minZ, maxZ),\n            Math.max(minX, maxX),\n            Math.max(minY, maxY),\n            Math.max(minZ, maxZ)\n        );\n    }\n\n    /**\n     * Returns the width of the bounding box (X-axis extent).\n     *\n     * @return the width\n     * @since 1.15.2\n     */\n    public double x() {\n        return maxX - minX;\n    }\n\n    /**\n     * Returns the height of the bounding box (Y-axis extent).\n     *\n     * @return the height\n     * @since 1.15.2\n     */\n    public double y() {\n        return maxY - minY;\n    }\n\n    /**\n     * Returns the Y coordinate of the center of the bounding box.\n     *\n     * @return the center Y\n     * @since 1.15.2\n     */\n    public double centerY() {\n        return (maxY + minY) / 2;\n    }\n\n    /**\n     * Returns the depth of the bounding box (Z-axis extent).\n     *\n     * @return the depth\n     * @since 1.15.2\n     */\n    public double z() {\n        return maxZ - minZ;\n    }\n\n    /**\n     * Returns the center point of the bounding box as a vector.\n     *\n     * @return the center vector\n     * @since 1.15.2\n     */\n    public @NotNull Vector3f centerPoint() {\n        return new Vector3f(\n            (float) (minX + maxX),\n            (float) (minY + maxY),\n            (float) (minZ + maxZ)\n        ).div(2F);\n    }\n\n    /**\n     * Scales the bounding box by a uniform factor.\n     *\n     * @param scale the scale factor\n     * @return the scaled bounding box\n     * @since 1.15.2\n     */\n    public @NotNull ModelBoundingBox times(double scale) {\n        return of(\n            minX * scale,\n            minY * scale,\n            minZ * scale,\n            maxX * scale,\n            maxY * scale,\n            maxZ * scale\n        );\n    }\n\n    /**\n     * Returns a new bounding box with the same dimensions but centered at the origin (0,0,0).\n     *\n     * @return the centered bounding box\n     * @since 1.15.2\n     */\n    public @NotNull ModelBoundingBox center() {\n        var center = centerPoint();\n        return of(\n            minX - center.x,\n            minY - center.y,\n            minZ - center.z,\n            maxX - center.x,\n            maxY - center.y,\n            maxZ - center.z\n        );\n    }\n\n    /**\n     * Inverts the X and Z coordinates of the bounding box.\n     *\n     * @return the inverted bounding box\n     * @since 1.15.2\n     */\n    public @NotNull ModelBoundingBox invert() {\n        return of(\n            -minX,\n            minY,\n            -minZ,\n            -maxX,\n            maxY,\n            -maxZ\n        );\n    }\n\n    /**\n     * Rotates the bounding box around its center.\n     *\n     * @param quaterniond the rotation quaternion\n     * @return the rotated bounding box\n     * @since 1.15.2\n     */\n    public @NotNull ModelBoundingBox rotate(@NotNull Quaterniond quaterniond) {\n        var centerVec = centerPoint();\n        return of(\n            min().sub(centerVec).rotate(quaterniond).add(centerVec),\n            max().sub(centerVec).rotate(quaterniond).add(centerVec)\n        );\n    }\n\n    /**\n     * Returns the minimum corner as a vector.\n     *\n     * @return the min vector\n     * @since 1.15.2\n     */\n    public @NotNull Vector3d min() {\n        return new Vector3d(minX, minY, minZ);\n    }\n\n    /**\n     * Returns the maximum corner as a vector.\n     *\n     * @return the max vector\n     * @since 1.15.2\n     */\n    public @NotNull Vector3d max() {\n        return new Vector3d(maxX, maxY, maxZ);\n    }\n\n    /**\n     * Calculates the diagonal length in the XZ plane.\n     *\n     * @return the XZ diagonal length\n     * @since 1.15.2\n     */\n    public double lengthZX() {\n        return Math.sqrt(Math.pow(x(), 2) + Math.pow(z(), 2));\n    }\n\n    /**\n     * Calculates the full diagonal length of the bounding box.\n     *\n     * @return the diagonal length\n     * @since 1.15.2\n     */\n    public double length() {\n        return Math.sqrt(Math.pow(x(), 2) + Math.pow(y(), 2) + Math.pow(z(), 2));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/KeyframeChannel.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jetbrains.annotations.ApiStatus;\n\n/**\n * Represents the type of property a keyframe affects.\n *\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic enum KeyframeChannel {\n    /**\n     * Affects the position of a bone.\n     * @since 1.15.2\n     */\n    @SerializedName(\"position\")\n    POSITION,\n    /**\n     * Affects the rotation of a bone.\n     * @since 1.15.2\n     */\n    @SerializedName(\"rotation\")\n    ROTATION,\n    /**\n     * Affects the scale of a bone.\n     * @since 1.15.2\n     */\n    @SerializedName(\"scale\")\n    SCALE,\n    /**\n     * Represents a timeline for sound or particle effects.\n     * @since 1.15.2\n     */\n    @SerializedName(\"timeline\")\n    TIMELINE,\n    /**\n     * Represents a sound effect.\n     * @since 1.15.2\n     */\n    @SerializedName(\"sound\")\n    SOUND,\n    /**\n     * Represents a particle effect.\n     * @since 1.15.2\n     */\n    @SerializedName(\"particle\")\n    PARTICLE,\n    /**\n     * Represents an unknown or unsupported channel.\n     * @since 1.15.2\n     */\n    NOT_FOUND\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelAnimation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.animation.AnimationIterator;\nimport kr.toxicity.model.api.animation.AnimationProgress;\nimport kr.toxicity.model.api.animation.VectorPoint;\nimport kr.toxicity.model.api.data.blueprint.AnimationGenerator;\nimport kr.toxicity.model.api.data.blueprint.BlueprintAnimation;\nimport kr.toxicity.model.api.data.blueprint.BlueprintAnimator;\nimport kr.toxicity.model.api.data.blueprint.BlueprintElement;\nimport kr.toxicity.model.api.script.AnimationScript;\nimport kr.toxicity.model.api.script.BlueprintScript;\nimport kr.toxicity.model.api.script.TimeScript;\nimport kr.toxicity.model.api.util.InterpolationUtil;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.associate;\n\n/**\n * Represents a raw animation definition from a model file.\n * <p>\n * This record holds the properties of an animation, such as its name, length, loop mode,\n * and the animators that define the keyframes for each bone group.\n * </p>\n *\n * @param name the name of the animation\n * @param loop the loop mode (e.g., play_once, loop, hold)\n * @param override whether this animation should override others\n * @param uuid the unique identifier of the animation\n * @param length the total length of the animation in seconds\n * @param animators a map of animators, keyed by the UUID of the bone group they affect\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelAnimation(\n    @NotNull String name,\n    @Nullable AnimationIterator.Type loop,\n    boolean override,\n    @NotNull String uuid,\n    float length,\n    @Nullable Map<String, ModelAnimator> animators\n) {\n    /**\n     * Converts this raw animation data into a processed {@link BlueprintAnimation}.\n     *\n     * @param context the model loading context\n     * @param children the list of root blueprint elements\n     * @return the blueprint animation\n     * @since 1.15.2\n     */\n    public @NotNull BlueprintAnimation toBlueprint(\n        @NotNull ModelLoadContext context,\n        @NotNull List<BlueprintElement> children\n    ) {\n        var animators = AnimationGenerator.createMovements(length(), children, associate(\n            animators().entrySet().stream()\n                .filter(e -> context.availableUUIDs.contains(e.getKey()))\n                .map(Map.Entry::getValue)\n                .filter(ModelAnimator::isAvailable)\n                .map(a -> buildAnimationData(context, a)),\n            BlueprintAnimator.AnimatorData::name\n        ));\n        return new BlueprintAnimation(\n            name(),\n            loop(),\n            length(),\n            override(),\n            animators,\n            Optional.ofNullable(animators().get(\"effects\"))\n                .filter(ModelAnimator::isNotEmpty)\n                .map(a -> toScript(a, context.placeholder))\n                .orElseGet(() -> BlueprintScript.fromEmpty(this)),\n            animators.isEmpty() ? AnimationProgress.emptyStorage(length()) : animators.values()\n                .iterator()\n                .next()\n                .keyframe()\n                .toEmpty()\n        );\n    }\n\n    private @NotNull BlueprintScript toScript(@NotNull ModelAnimator animator, @NotNull ModelPlaceholder placeholder) {\n        var set = new ObjectAVLTreeSet<TimeScript>();\n        set.add(TimeScript.EMPTY);\n        set.add(TimeScript.EMPTY.time(length()));\n        animator.stream()\n            .filter(f -> f.point().hasScript())\n            .map(d -> AnimationScript.of(Arrays.stream(placeholder.parseVariable(d.point().script()).split(\"\\n\"))\n                .map(BetterModel.platform().scriptManager()::build)\n                .filter(Objects::nonNull)\n                .toList())\n                .time(d.time()))\n            .forEach(set::add);\n        var array = new TimeScript[set.size()];\n        var before = 0F;\n        var i = 0;\n        for (TimeScript timeScript : set) {\n            var t = timeScript.time();\n            array[i++] = timeScript.time(InterpolationUtil.roundTime(t - before));\n            before = t;\n        }\n        return new BlueprintScript(\n            name(),\n            loop(),\n            length(),\n            List.of(array)\n        );\n    }\n\n    /**\n     * Returns the loop mode of the animation.\n     *\n     * @return the loop mode, defaulting to {@link AnimationIterator.Type#PLAY_ONCE} if null\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull AnimationIterator.Type loop() {\n        return loop != null ? loop : AnimationIterator.Type.PLAY_ONCE;\n    }\n\n    /**\n     * Returns the map of animators for this animation.\n     *\n     * @return the animators, or an empty map if null\n     * @since 1.15.2\n     */\n    @Override\n    @NotNull\n    public Map<String, ModelAnimator> animators() {\n        return animators != null ? animators : Collections.emptyMap();\n    }\n\n    @NotNull\n    private BlueprintAnimator.AnimatorData buildAnimationData(@NotNull ModelLoadContext context, @NotNull ModelAnimator animator) {\n        var position = new ArrayList<VectorPoint>();\n        var rotation = new ArrayList<VectorPoint>();\n        var scale = new ArrayList<VectorPoint>();\n        var version = context.meta.formatVersion();\n        animator.stream().filter(keyframe -> keyframe.time() <= length()).forEach(keyframe -> {\n            switch (keyframe.channel()) {\n                case POSITION -> position.add(keyframe.point(context, version::convertAnimationPosition));\n                case ROTATION -> rotation.add(keyframe.point(context, version::convertAnimationRotation));\n                case SCALE -> scale.add(keyframe.point(context, version::convertAnimationScale));\n            }\n        });\n        return new BlueprintAnimator.AnimatorData(\n            animator.name(),\n            position,\n            scale,\n            rotation,\n            animator.rotationGlobal()\n        );\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelAnimator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.bone.BoneName;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n/**\n * Represents a raw animator from a model file, containing a set of keyframes for a specific bone group.\n *\n * @param name the name of the bone group this animator affects\n * @param keyframes the list of keyframes\n * @param _rotationGlobal whether the rotation is applied globally (true) or locally (false/null)\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelAnimator(\n    @Nullable BoneName name,\n    @Nullable List<ModelKeyframe> keyframes,\n    @Nullable @SerializedName(\"rotation_global\") Boolean _rotationGlobal\n) {\n\n    /**\n     * Checks if the rotation should be applied globally.\n     *\n     * @return true if rotation is global, false otherwise\n     * @since 1.15.2\n     */\n    public boolean rotationGlobal() {\n        return Boolean.TRUE.equals(_rotationGlobal);\n    }\n\n    /**\n     * Checks if this animator is valid and has keyframes.\n     *\n     * @return true if available, false otherwise\n     * @since 1.15.2\n     */\n    public boolean isAvailable() {\n        return name != null && isNotEmpty();\n    }\n\n    /**\n     * Checks if this animator contains any keyframes.\n     *\n     * @return true if not empty, false otherwise\n     * @since 1.15.2\n     */\n    public boolean isNotEmpty() {\n        return !keyframes().isEmpty();\n    }\n\n    /**\n     * Returns the name of the bone group this animator affects.\n     *\n     * @return the name of the bone group\n     */\n    @Override\n    public @NotNull BoneName name() {\n        return Objects.requireNonNull(name);\n    }\n\n    /**\n     * Returns the list of keyframes for this animator. If no keyframes are present, an empty list is returned.\n     *\n     * @return the list of keyframes\n     */\n    @Override\n    public @NotNull List<ModelKeyframe> keyframes() {\n        return keyframes != null ? keyframes : Collections.emptyList();\n    }\n\n    /**\n     * Returns a sorted stream of valid keyframes.\n     *\n     * @return the stream of keyframes\n     * @since 1.15.2\n     */\n    public @NotNull Stream<ModelKeyframe> stream() {\n        return keyframes().stream()\n            .filter(ModelKeyframe::hasPoint)\n            .sorted();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelData.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.data.Float2;\nimport kr.toxicity.model.api.data.Float3;\nimport kr.toxicity.model.api.data.Float4;\nimport kr.toxicity.model.api.data.blueprint.BlueprintAnimation;\nimport kr.toxicity.model.api.data.blueprint.ModelBlueprint;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.*;\n\n/**\n * Represents the raw data structure of a model file, typically parsed from a .bbmodel JSON file.\n * <p>\n * This record holds all the top-level components of a BlockBench model, including metadata, elements, textures, and animations.\n * It serves as the initial data container before being processed into a {@link ModelBlueprint}.\n * </p>\n *\n * @param meta the metadata of the model\n * @param resolution the texture resolution\n * @param elements the list of cube/mesh elements\n * @param outliner the hierarchical structure of the model\n * @param textures the list of textures used in the model\n * @param animations the list of animations\n * @param groups the list of groups (used in BlockBench 5.0.0+)\n * @param placeholder the animation variable placeholders\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelData(\n    @NotNull ModelMeta meta,\n    @NotNull ModelResolution resolution,\n    @NotNull List<ModelElement> elements,\n    @NotNull List<ModelOutliner> outliner,\n    @NotNull List<ModelTexture> textures,\n    @Nullable List<ModelAnimation> animations,\n    @Nullable List<ModelGroup> groups,\n    @Nullable @SerializedName(\"animation_variable_placeholders\") ModelPlaceholder placeholder\n) {\n    /**\n     * The GSON parser configured for deserializing model data.\n     * @since 1.15.2\n     */\n    public static final Gson GSON = new GsonBuilder()\n        .registerTypeAdapter(Float2.class, Float2.PARSER)\n        .registerTypeAdapter(Float3.class, Float3.PARSER)\n        .registerTypeAdapter(Float4.class, Float4.PARSER)\n        .registerTypeAdapter(BoneName.class, BoneName.PARSER)\n        .registerTypeAdapter(ModelMeta.class, ModelMeta.PARSER)\n        .registerTypeAdapter(ModelOutliner.class, ModelOutliner.PARSER)\n        .registerTypeAdapter(ModelPlaceholder.class, ModelPlaceholder.PARSER)\n        .registerTypeAdapter(ModelElement.class, ModelElement.PARSER)\n        .create();\n\n    /**\n     * Converts this raw model data into a processed {@link ModelBlueprint}.\n     *\n     * @param name the name to assign to the blueprint\n     * @return the result of the loading process, containing the blueprint and any errors\n     * @since 1.15.2\n     */\n    public @NotNull ModelLoadResult loadBlueprint(@NotNull String name) {\n        return loadBlueprint(name, BetterModel.config().enableStrictLoading());\n    }\n\n    /**\n     * Converts this raw model data into a processed {@link ModelBlueprint} with a specific loading mode.\n     *\n     * @param name the name to assign to the blueprint\n     * @param strict whether to use strict loading mode (fail on unsupported features)\n     * @return the result of the loading process, containing the blueprint and any errors\n     * @since 1.15.2\n     */\n    public @NotNull ModelLoadResult loadBlueprint(@NotNull String name, boolean strict) {\n        var context = new ModelLoadContext(\n            name,\n            placeholder(),\n            meta(),\n            associate(elements(), ModelElement::uuid),\n            associate(groups(), ModelGroup::uuid),\n            mapToSet(outliner().stream().flatMap(ModelOutliner::flatten), ModelOutliner::uuid),\n            strict\n        );\n        var group = mapToList(outliner(), outliner -> outliner.toBlueprint(context));\n        return new ModelLoadResult(\n            new ModelBlueprint(\n                context.name,\n                resolution(),\n                mapToList(textures(), texture -> texture.toBlueprint(context)),\n                group,\n                associate(animations().stream().map(raw -> raw.toBlueprint(context, group)), BlueprintAnimation::name)\n            ),\n            context.errors\n        );\n    }\n\n    /**\n     * Asserts that the model does not contain any unsupported element types.\n     *\n     * @throws RuntimeException if an unsupported element is found\n     * @since 1.15.2\n     */\n    public void assertSupported() {\n        elements().stream()\n            .filter(e -> !e.isSupported())\n            .findFirst()\n            .ifPresent(e -> {\n                throw new RuntimeException(\"This model file has unsupported element type: \" + e.type());\n            });\n    }\n\n    /**\n     * Returns the animation variable placeholders, or an empty placeholder if none are defined.\n     *\n     * @return the animation variable placeholders\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull ModelPlaceholder placeholder() {\n        return placeholder != null ? placeholder : ModelPlaceholder.EMPTY;\n    }\n\n    /**\n     * Returns the list of animations, or an empty list if none are defined.\n     *\n     * @return the list of animations\n     * @since 1.15.2\n     */\n    @Override\n    @NotNull\n    public List<ModelAnimation> animations() {\n        return animations != null ? animations : Collections.emptyList();\n    }\n\n    /**\n     * Returns the list of groups, or an empty list if none are defined.\n     *\n     * @return the list of groups\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull List<ModelGroup> groups() {\n        return groups != null ? groups : Collections.emptyList();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelDatapoint.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.JsonPrimitive;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.util.function.Float2FloatConstantFunction;\nimport kr.toxicity.model.api.util.function.Float2FloatFunction;\nimport kr.toxicity.model.api.util.function.FloatFunction;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Vector3f;\n\nimport java.util.Objects;\n\n/**\n * Represents a single data point within a keyframe, which can be a static value or a Molang script.\n * <p>\n * This record holds the raw JSON values for x, y, and z coordinates, or a script string.\n * </p>\n *\n * @param x the x-coordinate value or script\n * @param y the y-coordinate value or script\n * @param z the z-coordinate value or script\n * @param script the script string (used for sound/particle effects)\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelDatapoint(\n    @Nullable JsonPrimitive x,\n    @Nullable JsonPrimitive y,\n    @Nullable JsonPrimitive z,\n    @Nullable String script\n) {\n\n    /**\n     * Checks if this data point contains a script.\n     *\n     * @return true if a script is present, false otherwise\n     * @since 1.15.2\n     */\n    public boolean hasScript() {\n        return script != null;\n    }\n\n    /**\n     * Returns the script string.\n     *\n     * @return the script\n     * @throws NullPointerException if no script is present\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull String script() {\n        return Objects.requireNonNull(script);\n    }\n\n    /**\n     * Converts this data point into a function that returns a {@link Vector3f}.\n     * <p>\n     * If the data point contains static values, it returns a constant function.\n     * If it contains Molang expressions, it compiles them into a function that evaluates the expressions.\n     * </p>\n     *\n     * @param context the model loading context\n     * @return a function to get the vector value\n     * @since 1.15.2\n     */\n    public @NotNull FloatFunction<Vector3f> toFunction(@NotNull ModelLoadContext context) {\n        var xb = build(x, context);\n        var yb = build(y, context);\n        var zb = build(z, context);\n        if (xb instanceof Float2FloatConstantFunction(float xc)\n            && yb instanceof Float2FloatConstantFunction(float yc)\n            && zb instanceof Float2FloatConstantFunction(float zc)\n        ) {\n            return FloatFunction.of(new Vector3f(xc, yc, zc));\n        } else {\n            return f -> new Vector3f(\n                xb.applyAsFloat(f),\n                yb.applyAsFloat(f),\n                zb.applyAsFloat(f)\n            );\n        }\n    }\n\n    private static @NotNull Float2FloatFunction build(@Nullable JsonPrimitive primitive, @NotNull ModelLoadContext context) {\n        if (primitive == null) return Float2FloatFunction.ZERO;\n        if (primitive.isNumber()) return Float2FloatFunction.of(primitive.getAsFloat());\n        var string = primitive.getAsString().trim();\n        if (string.isEmpty()) return Float2FloatFunction.ZERO;\n        try {\n            return Float2FloatFunction.of(Float.parseFloat(string));\n        } catch (NumberFormatException ignored) {\n            return context.trySupply(\n                () -> BetterModel.platform().evaluator().compile(context.placeholder.parseVariable(string)),\n                error -> new ModelLoadContext.Fallback<>(\n                    Float2FloatFunction.ZERO,\n                    \"Cannot parse this datapoint: \" + primitive + \", reason: \" + error.getMessage()\n                )\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelElement.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.data.Float2;\nimport kr.toxicity.model.api.data.Float3;\nimport kr.toxicity.model.api.data.blueprint.BlueprintElement;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\n\n/**\n * Represents a raw element within a model file.\n * <p>\n * This interface is a sealed type that permits specific implementations for different element types\n * found in BlockBench models, such as cubes, locators, null objects, and cameras.\n * </p>\n *\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic sealed interface ModelElement {\n\n    /**\n     * The type identifier for a null object element.\n     * @since 2.2.1\n     */\n    String NULL_OBJECT = \"null_object\";\n    /**\n     * The type identifier for a locator element.\n     * @since 2.2.1\n     */\n    String LOCATOR = \"locator\";\n    /**\n     * The type identifier for a camera element.\n     * @since 2.2.1\n     */\n    String CAMERA = \"camera\";\n    /**\n     * The type identifier for a cube element.\n     * @since 2.2.1\n     */\n    String CUBE = \"cube\";\n    /**\n     * The type identifier for a mesh element.\n     * @since 3.0.0\n     */\n    String MESH = \"mesh\";\n\n    /**\n     * A JSON deserializer that automatically dispatches to the correct {@link ModelElement} implementation based on the \"type\" field.\n     * @since 1.15.2\n     */\n    JsonDeserializer<ModelElement> PARSER = (json, _, context) -> {\n        var t = json.getAsJsonObject().getAsJsonPrimitive(\"type\");\n        var select = t != null ? t.getAsString() : CUBE;\n        return switch (select) {\n            case NULL_OBJECT -> context.deserialize(json, NullObject.class);\n            case LOCATOR -> context.deserialize(json, Locator.class);\n            case CAMERA -> context.deserialize(json, Camera.class);\n            case CUBE -> context.deserialize(json, Cube.class);\n            case MESH -> context.deserialize(json, Mesh.class);\n            default -> new Unsupported(select);\n        };\n    };\n\n    /**\n     * Returns the unique identifier (UUID) of this element.\n     *\n     * @return the UUID string\n     * @since 1.15.2\n     */\n    @NotNull String uuid();\n\n    /**\n     * Returns the type identifier of this element (e.g., \"cube\", \"locator\").\n     *\n     * @return the type string\n     * @since 1.15.2\n     */\n    @NotNull String type();\n\n    /**\n     * Converts this raw element into a processed {@link BlueprintElement}.\n     *\n     * @return the blueprint element\n     * @since 1.15.2\n     */\n    @NotNull BlueprintElement toBlueprint();\n\n    /**\n     * Checks if this element type is supported by the engine.\n     *\n     * @return true if supported, false otherwise\n     * @since 1.15.2\n     */\n    default boolean isSupported() {\n        return true;\n    }\n\n    /**\n     * Represents a locator element, used for positioning attachments or particles.\n     *\n     * @param name the name of the locator\n     * @param uuid the UUID of the locator\n     * @param position the position of the locator\n     * @since 1.15.2\n     */\n    record Locator(\n        @NotNull String name,\n        @NotNull String uuid,\n        @Nullable Float3 position\n    ) implements ModelElement {\n        @Override\n        public @NotNull String type() {\n            return LOCATOR;\n        }\n        /**\n         * Returns the position of the locator.\n         *\n         * @return the position, or {@link Float3#ZERO} if not specified\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Float3 position() {\n            return position != null ? position : Float3.ZERO;\n        }\n\n        @Override\n        public @NotNull BlueprintElement toBlueprint() {\n            return new BlueprintElement.Locator(\n                UUID.fromString(uuid),\n                BoneName.of(name()),\n                position()\n            );\n        }\n    }\n\n    /**\n     * Represents a camera element (currently used as a placeholder).\n     *\n     * @param uuid the UUID of the camera\n     * @since 1.15.2\n     */\n    record Camera(\n        @NotNull String uuid\n    ) implements ModelElement {\n        @Override\n        public @NotNull String type() {\n            return CAMERA;\n        }\n\n        @Override\n        public @NotNull BlueprintElement toBlueprint() {\n            return new BlueprintElement.Camera(\n                UUID.fromString(uuid)\n            );\n        }\n    }\n\n    /**\n     * Represents a null object, often used for grouping or IK targets.\n     *\n     * @param name the name of the null object\n     * @param uuid the UUID of the null object\n     * @param ikTarget the UUID of the IK target, if any\n     * @param ikSource the UUID of the IK source, if any\n     * @param position the position of the null object\n     * @since 1.15.2\n     */\n    record NullObject(\n        @NotNull String name,\n        @NotNull String uuid,\n        @Nullable @SerializedName(\"ik_target\") String ikTarget,\n        @Nullable @SerializedName(\"ik_source\") String ikSource,\n        @Nullable Float3 position\n    ) implements ModelElement {\n        @Override\n        public @NotNull String type() {\n            return NULL_OBJECT;\n        }\n\n        /**\n         * Returns the position of the null object.\n         *\n         * @return the position, or {@link Float3#ZERO} if not specified\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Float3 position() {\n            return position != null ? position : Float3.ZERO;\n        }\n\n        @Override\n        public @NotNull BlueprintElement toBlueprint() {\n            return new BlueprintElement.NullObject(\n                UUID.fromString(uuid),\n                BoneName.of(name()),\n                Optional.ofNullable(ikTarget())\n                    .filter(str -> !str.isEmpty())\n                    .map(UUID::fromString)\n                    .orElse(null),\n                Optional.ofNullable(ikSource())\n                    .filter(str -> !str.isEmpty())\n                    .map(UUID::fromString)\n                    .orElse(null),\n                position()\n            );\n        }\n    }\n\n    /**\n     * Represents an unsupported element type.\n     *\n     * @param type the unsupported type string\n     * @since 1.15.2\n     */\n    record Unsupported(@NotNull String type) implements ModelElement {\n\n        @Override\n        public @NotNull String uuid() {\n            throw new UnsupportedOperationException(type());\n        }\n\n        @Override\n        public boolean isSupported() {\n            return false;\n        }\n\n        @Override\n        public @NotNull BlueprintElement toBlueprint() {\n            throw new UnsupportedOperationException(type());\n        }\n    }\n\n    /**\n     * Represents a standard cube element.\n     *\n     * @param name the name of the cube\n     * @param uuid the UUID of the cube\n     * @param from the starting coordinate (min corner)\n     * @param to the ending coordinate (max corner)\n     * @param inflate the inflation value (size increase)\n     * @param rotation the rotation of the cube\n     * @param origin the pivot point (origin) of the cube\n     * @param faces the UV mapping for the faces\n     * @param lightEmission the light emission level (0-15)\n     * @param _visibility the visibility state (null means visible)\n     * @since 1.15.2\n     */\n    record Cube(\n        @NotNull String name,\n        @NotNull String uuid,\n        @Nullable Float3 from,\n        @Nullable Float3 to,\n        float inflate,\n        @Nullable Float3 rotation,\n        @NotNull Float3 origin,\n        @Nullable ModelFace faces,\n        @SerializedName(\"light_emission\") int lightEmission,\n        @SerializedName(\"visibility\") @Nullable Boolean _visibility\n    ) implements ModelElement {\n\n        @Override\n        public @NotNull String type() {\n            return CUBE;\n        }\n\n        /**\n         * Returns the starting coordinate (min corner).\n         *\n         * @return the from vector, or {@link Float3#ZERO} if not specified\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Float3 from() {\n            return from != null ? from : Float3.ZERO;\n        }\n\n        /**\n         * Returns the ending coordinate (max corner).\n         *\n         * @return the to vector, or {@link Float3#ZERO} if not specified\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Float3 to() {\n            return to != null ? to : Float3.ZERO;\n        }\n\n        /**\n         * Checks if the cube is visible.\n         *\n         * @return true if visible, false otherwise\n         * @since 1.15.2\n         */\n        public boolean visibility() {\n            return !Boolean.FALSE.equals(_visibility);\n        }\n\n        /**\n         * Returns the rotation of the cube.\n         *\n         * @return the rotation, or {@link Float3#ZERO} if not specified\n         * @since 1.15.2\n         */\n        @Override\n        public @NotNull Float3 rotation() {\n            return rotation != null ? rotation : Float3.ZERO;\n        }\n\n        /**\n         * Returns the light emission level of the cube.\n         *\n         * @return the light emission level (0-15)\n         * @since 2.1.0\n         */\n        @Override\n        public int lightEmission() {\n            return name().toLowerCase().contains(\"glow\") ? 15 : lightEmission;\n        }\n\n        @Override\n        public @NotNull BlueprintElement toBlueprint() {\n            return new BlueprintElement.Cube(\n                name(),\n                from(),\n                to(),\n                inflate(),\n                rotation(),\n                origin(),\n                faces(),\n                Optional.of(lightEmission()).filter(i -> i > 0).orElse(null),\n                visibility()\n            );\n        }\n    }\n\n    /**\n     * Represents a mesh element, allowing for complex geometry beyond simple cubes.\n     *\n     * @param uuid the UUID of the mesh\n     * @param origin the pivot point (origin) of the mesh\n     * @param rotation the rotation of the mesh\n     * @param vertices a map of vertex identifiers to their 3D positions\n     * @param faces a map of face identifiers to their face data\n     * @param _visibility the visibility state (null means visible)\n     * @since 3.0.0\n     */\n    record Mesh(\n        @NotNull String uuid,\n        @Nullable Float3 origin,\n        @Nullable Float3 rotation,\n        @NotNull Map<String, Float3> vertices,\n        @NotNull Map<String, Face> faces,\n        @SerializedName(\"visibility\") @Nullable Boolean _visibility\n    ) implements ModelElement {\n        /**\n         * Returns the pivot point (origin) of the mesh.\n         *\n         * @return the origin vector, or {@link Float3#ZERO} if not specified\n         * @since 3.0.0\n         */\n        @Override\n        public @NotNull Float3 origin() {\n            return origin != null ? origin : Float3.ZERO;\n        }\n\n        /**\n         * Returns the rotation of the mesh.\n         *\n         * @return the rotation vector, or {@link Float3#ZERO} if not specified\n         * @since 3.0.0\n         */\n        @Override\n        public @NotNull Float3 rotation() {\n            return rotation != null ? rotation : Float3.ZERO;\n        }\n\n        /**\n         * Returns the type identifier of this element.\n         *\n         * @return {@link #MESH}\n         * @since 3.0.0\n         */\n        @Override\n        public @NotNull String type() {\n            return MESH;\n        }\n\n        public boolean visibility() {\n            return !Boolean.FALSE.equals(_visibility);\n        }\n\n        @Override\n        public @NotNull BlueprintElement toBlueprint() {\n            return new BlueprintElement.Mesh(\n                origin(),\n                rotation(),\n                faces.values()\n                    .stream()\n                    .map(face -> new BlueprintElement.Mesh.Face(\n                        face.vertices.stream()\n                            .map(n -> new BlueprintElement.Mesh.Point(\n                                Objects.requireNonNull(vertices.get(n)),\n                                Objects.requireNonNull(face.uv.get(n))\n                            ))\n                            .toList(),\n                        face.texture\n                    ))\n                    .toList(),\n                visibility()\n            );\n        }\n\n        /**\n         * Represents a single face of a mesh.\n         *\n         * @param uv a map of vertex identifiers to their UV coordinates\n         * @param vertices a set of vertex identifiers that form this face\n         * @param texture the index of the texture used by this face\n         * @since 3.0.0\n         */\n        public record Face(@NotNull Map<String, Float2> uv, @NotNull Set<String> vertices, int texture) {}\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelFace.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.JsonObject;\nimport kr.toxicity.model.api.data.blueprint.BlueprintLoadContext;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.stream.IntStream;\n\n/**\n * Represents the UV mappings for all six faces of a cube element.\n *\n * @param north the UV mapping for the north face\n * @param east the UV mapping for the east face\n * @param south the UV mapping for the south face\n * @param west the UV mapping for the west face\n * @param up the UV mapping for the up face\n * @param down the UV mapping for the down face\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelFace(\n    @NotNull ModelUV north,\n    @NotNull ModelUV east,\n    @NotNull ModelUV south,\n    @NotNull ModelUV west,\n    @NotNull ModelUV up,\n    @NotNull ModelUV down\n) {\n    /**\n     * Converts the face UV data to a JSON object for the Minecraft model file.\n     * <p>\n     * Only faces with a defined texture will be included in the output.\n     * </p>\n     *\n     * @param parent the parent model blueprint, used for texture resolution\n     * @param tint the tint index to apply\n     * @return the generated JSON object\n     * @since 1.15.2\n     */\n    public @NotNull JsonObject toJson(@NotNull BlueprintLoadContext parent, int tint) {\n        var object = new JsonObject();\n        JsonObject add;\n        if ((add = north.toJson(parent, tint)) != null) object.add(\"north\", add);\n        if ((add = east.toJson(parent, tint)) != null) object.add(\"east\", add);\n        if ((add = south.toJson(parent, tint)) != null) object.add(\"south\", add);\n        if ((add = west.toJson(parent, tint)) != null) object.add(\"west\", add);\n        if ((add = up.toJson(parent, tint)) != null) object.add(\"up\", add);\n        if ((add = down.toJson(parent, tint)) != null) object.add(\"down\", add);\n        return object;\n    }\n\n    /**\n     * Checks if any face has a texture defined.\n     *\n     * @return true if at least one face has a texture, false otherwise\n     * @since 1.15.2\n     */\n    public boolean hasTexture() {\n        return north.hasTexture()\n            || east.hasTexture()\n            || south.hasTexture()\n            || west.hasTexture()\n            || up.hasTexture()\n            || down.hasTexture();\n    }\n\n    public @NotNull IntStream textureIndex() {\n        var builder = IntStream.builder();\n        if (north.hasTexture()) builder.add(north.textureIndex());\n        if (east.hasTexture()) builder.add(east.textureIndex());\n        if (south.hasTexture()) builder.add(south.textureIndex());\n        if (west.hasTexture()) builder.add(west.textureIndex());\n        if (up.hasTexture()) builder.add(up.textureIndex());\n        if (down.hasTexture()) builder.add(down.textureIndex());\n        return builder.build();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelGroup.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.data.Float3;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents a group definition in the raw model data.\n * <p>\n * Groups are used to organize elements and other groups hierarchically.\n * This record corresponds to the group structure found in newer BlockBench versions (>= 5.0.0).\n * </p>\n *\n * @param name the name of the group\n * @param uuid the unique identifier of the group\n * @param origin the pivot point (origin) of the group\n * @param rotation the rotation of the group\n * @param lightEmission the light emission level (0-15)\n * @param _visibility the visibility state of the group (null means visible)\n * @since 1.15.2\n */\npublic record ModelGroup(\n    @NotNull String name,\n    @NotNull String uuid,\n    @Nullable Float3 origin,\n    @Nullable Float3 rotation,\n    @SerializedName(\"light_emission\") int lightEmission,\n    @Nullable @SerializedName(\"visibility\") Boolean _visibility\n) {\n    /**\n     * Returns the origin (pivot point) of the group.\n     *\n     * @return the origin, or {@link Float3#ZERO} if not specified\n     * @since 1.15.2\n     */\n    @Override\n    @NotNull\n    public Float3 origin() {\n        return origin != null ? origin : Float3.ZERO;\n    }\n\n    /**\n     * Returns the rotation of the group.\n     *\n     * @return the rotation, or {@link Float3#ZERO} if not specified\n     * @since 1.15.2\n     */\n    @Override\n    @NotNull\n    public Float3 rotation() {\n        return rotation != null ? rotation : Float3.ZERO;\n    }\n\n    /**\n     * Checks if the group is visible.\n     *\n     * @return true if visible, false otherwise\n     * @since 1.15.2\n     */\n    public boolean visibility() {\n        return !Boolean.FALSE.equals(_visibility);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelKeyframe.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.animation.Timed;\nimport kr.toxicity.model.api.animation.VectorPoint;\nimport kr.toxicity.model.api.data.Float3;\nimport kr.toxicity.model.api.util.interpolator.VectorInterpolator;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Vector3f;\n\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Function;\n\n/**\n * Represents a single keyframe in an animation timeline.\n * <p>\n * A keyframe defines the state of a bone (position, rotation, or scale) at a specific time,\n * along with interpolation data (linear, catmull-rom, bezier) to smooth transitions.\n * </p>\n *\n * @param channel the channel this keyframe affects (position, rotation, scale)\n * @param dataPoints the list of data points (values) for this keyframe\n * @param bezierLeftTime the time offset for the left bezier handle\n * @param bezierLeftValue the value offset for the left bezier handle\n * @param bezierRightTime the time offset for the right bezier handle\n * @param bezierRightValue the value offset for the right bezier handle\n * @param interpolation the interpolation type (e.g., linear, catmullrom, bezier)\n * @param time the time of the keyframe in seconds\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelKeyframe(\n    @Nullable KeyframeChannel channel,\n    @SerializedName(\"data_points\") @NotNull List<ModelDatapoint> dataPoints,\n    @SerializedName(\"bezier_left_time\") @Nullable Float3 bezierLeftTime,\n    @SerializedName(\"bezier_left_value\") @Nullable Float3 bezierLeftValue,\n    @SerializedName(\"bezier_right_time\") @Nullable Float3 bezierRightTime,\n    @SerializedName(\"bezier_right_value\") @Nullable Float3 bezierRightValue,\n    @Nullable VectorInterpolator interpolation,\n    float time\n) implements Timed {\n\n    /**\n     * Checks if this keyframe contains any data points.\n     *\n     * @return true if data points exist, false otherwise\n     * @since 1.15.2\n     */\n    public boolean hasPoint() {\n        return !dataPoints.isEmpty();\n    }\n\n    /**\n     * Returns the first data point in the list.\n     *\n     * @return the first data point\n     * @throws java.util.NoSuchElementException if the list is empty\n     * @since 1.15.2\n     */\n    public @NotNull ModelDatapoint point() {\n        return dataPoints.getFirst();\n    }\n\n    /**\n     * Converts this keyframe into a processed {@link VectorPoint}.\n     *\n     * @param context the model loading context\n     * @param function a transformation function to apply to the vector values (e.g., coordinate conversion)\n     * @return the vector point\n     * @since 1.15.2\n     */\n    public @NotNull VectorPoint point(@NotNull ModelLoadContext context, @NotNull Function<Vector3f, Vector3f> function) {\n        return new VectorPoint(\n            point().toFunction(context).map(function).memoize(),\n            time(),\n            new VectorPoint.BezierConfig(\n                Optional.ofNullable(bezierLeftTime).map(Float3::toVector).orElse(null),\n                Optional.ofNullable(bezierLeftValue).map(Float3::toVector).map(function).orElse(null),\n                Optional.ofNullable(bezierRightTime).map(Float3::toVector).orElse(null),\n                Optional.ofNullable(bezierRightValue).map(Float3::toVector).map(function).orElse(null)\n            ),\n            interpolation()\n        );\n    }\n\n    /**\n     * Returns the interpolation type for this keyframe.\n     *\n     * @return the interpolation type, defaulting to {@link VectorInterpolator#LINEAR} if null\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull VectorInterpolator interpolation() {\n        return interpolation != null ? interpolation : VectorInterpolator.LINEAR;\n    }\n\n    /**\n     * Returns the channel this keyframe affects.\n     *\n     * @return the channel, defaulting to {@link KeyframeChannel#NOT_FOUND} if null\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull KeyframeChannel channel() {\n        return channel != null ? channel : KeyframeChannel.NOT_FOUND;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelLoadContext.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\n/**\n * Holds the context and state during the model loading process.\n * <p>\n * This class provides access to all parts of the raw model data and accumulates errors\n * that occur during processing. It also controls the loading mode (strict or lenient).\n * </p>\n *\n * @since 1.15.2\n */\n@RequiredArgsConstructor\n@ApiStatus.Internal\npublic final class ModelLoadContext {\n    final @NotNull String name;\n    final @NotNull ModelPlaceholder placeholder;\n    final @NotNull ModelMeta meta;\n    final @NotNull Map<String, ModelElement> elements;\n    final @NotNull Map<String, ModelGroup> groups;\n    final @NotNull Set<String> availableUUIDs;\n    private final boolean strict;\n    private final List<String> _errors = new ArrayList<>();\n    final List<String> errors = Collections.unmodifiableList(_errors);\n\n    /**\n     * Tries to execute a supplier, catching exceptions in lenient mode.\n     *\n     * @param supplier the supplier to execute\n     * @param fallbackFunction a function to provide a fallback value and error message on exception\n     * @param <T> the return type\n     * @return the result of the supplier or the fallback value\n     * @since 1.15.2\n     */\n    @NotNull <T> T trySupply(@NotNull Supplier<T> supplier, @NotNull Function<Exception, Fallback<T>> fallbackFunction) {\n        if (strict) return supplier.get();\n        try {\n            return supplier.get();\n        } catch (Exception e) {\n            var fallback = fallbackFunction.apply(e);\n            _errors.add(fallback.message);\n            return fallback.value;\n        }\n    }\n\n    /**\n     * Represents a fallback value and an associated error message.\n     *\n     * @param value the fallback value\n     * @param message the error message\n     * @param <T> the type of the value\n     * @since 1.15.2\n     */\n    record Fallback<T>(@NotNull T value, @NotNull String message) {}\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelLoadResult.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport kr.toxicity.model.api.data.blueprint.ModelBlueprint;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.List;\n\n/**\n * Represents the result of loading and processing a raw model file.\n * <p>\n * This record contains the successfully created {@link ModelBlueprint} and a list of any errors\n * or warnings that occurred during the loading process.\n * </p>\n *\n * @param blueprint the processed model blueprint\n * @param errors a list of error or warning messages generated during loading\n * @since 1.15.2\n */\npublic record ModelLoadResult(@NotNull ModelBlueprint blueprint, @NotNull @Unmodifiable List<String> errors) {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelMeta.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.JsonDeserializer;\nimport kr.toxicity.model.api.util.MathUtil;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\nimport org.semver4j.Semver;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/**\n * Represents metadata about the model file, specifically the format version.\n * <p>\n * This record is used to handle differences in coordinate systems and animation data between different BlockBench versions.\n * </p>\n *\n * @param formatVersion the detected format version of the model file\n * @since 1.15.2\n */\npublic record ModelMeta(\n    @NotNull FormatVersion formatVersion\n) {\n    /**\n     * A JSON deserializer for parsing {@link ModelMeta} from the \"meta\" object in a .bbmodel file.\n     * @since 1.15.2\n     */\n    public static final JsonDeserializer<ModelMeta> PARSER = (json, _, _) -> new ModelMeta(\n        FormatVersion.find(Objects.requireNonNull(Semver.coerce(json.getAsJsonObject().getAsJsonPrimitive(\"format_version\").getAsString())).getMajor())\n    );\n\n    /**\n     * Enumerates supported BlockBench format versions and their specific coordinate conversions.\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor\n    public enum FormatVersion {\n        /**\n         * BlockBench version 5.0.0 and later.\n         * @since 1.15.2\n         */\n        BLOCKBENCH_5(5) {\n            @Override\n            public @NotNull Vector3f convertAnimationRotation(@NotNull Vector3f vector) {\n                vector.x = -vector.x;\n                vector.z = -vector.z;\n                return vector;\n            }\n\n            @Override\n            public @NotNull Vector3f convertAnimationPosition(@NotNull Vector3f vector) {\n                vector.x = -vector.x;\n                vector.z = -vector.z;\n                return vector.div(MathUtil.MODEL_TO_BLOCK_MULTIPLIER);\n            }\n        },\n        /**\n         * Legacy BlockBench versions (pre-5.0.0).\n         * @since 1.15.2\n         */\n        BLOCKBENCH_LEGACY(0) {\n            @Override\n            public @NotNull Vector3f convertAnimationRotation(@NotNull Vector3f vector) {\n                vector.y = -vector.y;\n                vector.z = -vector.z;\n                return vector;\n            }\n\n            @Override\n            public @NotNull Vector3f convertAnimationPosition(@NotNull Vector3f vector) {\n                vector.z = -vector.z;\n                return vector.div(MathUtil.MODEL_TO_BLOCK_MULTIPLIER);\n            }\n        },\n        ;\n\n        private final int major;\n\n        /**\n         * Finds the appropriate format version based on the major version number.\n         *\n         * @param major the major version number\n         * @return the matching format version\n         * @throws java.util.NoSuchElementException if no matching version is found\n         * @since 1.15.2\n         */\n        public static @NotNull FormatVersion find(int major) {\n            return Arrays.stream(values())\n                .filter(v -> v.major <= major)\n                .findFirst()\n                .orElseThrow();\n        }\n\n        /**\n         * Converts animation rotation values to the engine's coordinate system.\n         *\n         * @param vector the raw rotation vector\n         * @return the converted rotation vector\n         * @since 1.15.2\n         */\n        public abstract @NotNull Vector3f convertAnimationRotation(@NotNull Vector3f vector);\n\n        /**\n         * Converts animation position values to the engine's coordinate system.\n         *\n         * @param vector the raw position vector\n         * @return the converted position vector\n         * @since 1.15.2\n         */\n        public abstract @NotNull Vector3f convertAnimationPosition(@NotNull Vector3f vector);\n\n        /**\n         * Converts animation scale values to the engine's format (relative to 1.0).\n         *\n         * @param vector the raw scale vector\n         * @return the converted scale vector\n         * @since 1.15.2\n         */\n        public @NotNull Vector3f convertAnimationScale(@NotNull Vector3f vector) {\n            vector.sub(1F, 1F, 1F);\n            return vector;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelOutliner.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.JsonDeserializer;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.data.blueprint.BlueprintElement;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.UUID;\nimport java.util.stream.Stream;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.filterIsInstance;\nimport static kr.toxicity.model.api.util.CollectionUtil.mapToList;\n\n/**\n * Represents the hierarchical structure (outliner) of a model.\n * <p>\n * The outliner defines the parent-child relationships between groups and elements (cubes, locators, etc.).\n * It can be either a direct reference to an element (UUID string) or a tree node (Group) containing children.\n * </p>\n *\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic sealed interface ModelOutliner {\n\n    /**\n     * A JSON deserializer that parses the outliner structure.\n     * <p>\n     * It distinguishes between leaf nodes (UUID strings) and branch nodes (Groups with children).\n     * </p>\n     * @since 1.15.2\n     */\n    JsonDeserializer<ModelOutliner> PARSER = (json, _, context) -> {\n        if (json.isJsonPrimitive()) return new Reference(json.getAsString());\n        else if (json.isJsonObject()) {\n            var children = json.getAsJsonObject().getAsJsonArray(\"children\");\n            return new Tree(\n                context.deserialize(json, ModelGroup.class),\n                children.asList()\n                    .stream()\n                    .map(child -> (ModelOutliner) context.deserialize(child, ModelOutliner.class))\n                    .toList()\n            );\n        }\n        else throw new RuntimeException();\n    };\n\n    /**\n     * Converts this outliner node into a processed {@link BlueprintElement}.\n     *\n     * @param context the model loading context\n     * @return the blueprint element\n     * @since 1.15.2\n     */\n    @NotNull BlueprintElement toBlueprint(@NotNull ModelLoadContext context);\n\n    /**\n     * Flattens the outliner tree into a stream of all nodes.\n     *\n     * @return a stream of all outliner nodes\n     * @since 1.15.2\n     */\n    @NotNull Stream<ModelOutliner> flatten();\n\n    /**\n     * Returns the UUID of this outliner node.\n     *\n     * @return the UUID string\n     * @since 1.15.2\n     */\n    @NotNull String uuid();\n\n    /**\n     * Represents a leaf node in the outliner, referencing a specific element by UUID.\n     *\n     * @param uuid the UUID of the referenced element\n     * @since 1.15.2\n     */\n    record Reference(@NotNull String uuid) implements ModelOutliner {\n        @Override\n        public @NotNull BlueprintElement toBlueprint(@NotNull ModelLoadContext context) {\n            return Objects.requireNonNull(context.elements.get(uuid())).toBlueprint();\n        }\n\n        @Override\n        public @NotNull Stream<ModelOutliner> flatten() {\n            return Stream.of(this);\n        }\n    }\n\n    /**\n     * Represents a branch node (Group) in the outliner, containing child nodes.\n     *\n     * @param group the group definition\n     * @param children the list of child outliner nodes\n     * @since 1.15.2\n     */\n    record Tree(\n        @NotNull ModelGroup group,\n        @NotNull @Unmodifiable List<ModelOutliner> children\n    ) implements ModelOutliner {\n\n        @Override\n        public @NotNull BlueprintElement toBlueprint(@NotNull ModelLoadContext context) {\n            var child = mapToList(children, c -> c.toBlueprint(context));\n            var filtered = filterIsInstance(child, BlueprintElement.Cube.class).toList();\n            var selectedGroup = context.groups.getOrDefault(uuid(), group);\n            return new BlueprintElement.Group(\n                UUID.fromString(selectedGroup.uuid()),\n                BoneName.of(selectedGroup.name()),\n                selectedGroup.origin(),\n                selectedGroup.rotation().invertXZ(),\n                child,\n                filtered.isEmpty() ? selectedGroup.visibility() : filtered.stream().anyMatch(BlueprintElement.Cube::visibility)\n            );\n        }\n\n        @Override\n        public @NotNull Stream<ModelOutliner> flatten() {\n            return children.isEmpty() ? Stream.of(this) : Stream.concat(\n                Stream.of(this),\n                children.stream().flatMap(ModelOutliner::flatten)\n            );\n        }\n\n        @Override\n        public @NotNull String uuid() {\n            return group.uuid();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelPlaceholder.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.JsonDeserializer;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.associate;\n\n/**\n * Represents animation variable placeholders defined in a model file.\n * <p>\n * These placeholders allow for defining reusable values or expressions that can be referenced within Molang scripts in animations.\n * </p>\n *\n * @param variables a map of placeholder variable names to their string values\n * @since 1.15.2\n */\npublic record ModelPlaceholder(\n    @NotNull @Unmodifiable Map<String, String> variables\n) {\n    /**\n     * An empty placeholder instance with no variables.\n     * @since 1.15.2\n     */\n    public static final ModelPlaceholder EMPTY = new ModelPlaceholder(Collections.emptyMap());\n\n    /**\n     * A JSON deserializer for parsing placeholders from a multi-line string.\n     * @since 1.15.2\n     */\n    public static final JsonDeserializer<ModelPlaceholder> PARSER = (json, _, _) -> new ModelPlaceholder(associate(\n        Arrays.stream(json.getAsString().split(\"\\n\"))\n            .map(line -> line.split(\"=\", 2))\n            .filter(array -> array.length == 2),\n        array -> array[0].trim(),\n        array -> array[1].trim()\n    ));\n\n    /**\n     * Replaces all placeholder variables in a given expression with their corresponding values.\n     *\n     * @param expression the expression containing placeholders\n     * @return the expression with placeholders substituted\n     * @since 1.15.2\n     */\n    public @NotNull String parseVariable(@NotNull String expression) {\n        for (var entry : variables.entrySet()) {\n            expression = expression.replace(entry.getKey(), entry.getValue());\n        }\n        return expression;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelResolution.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport org.jetbrains.annotations.ApiStatus;\n\n/**\n * Represents the texture resolution of a model.\n *\n * @param width the width of the texture in pixels\n * @param height the height of the texture in pixels\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelResolution(\n    int width,\n    int height\n) {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelTexture.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.data.blueprint.BlueprintTexture;\nimport kr.toxicity.model.api.util.PackUtil;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Base64;\n\n/**\n * Represents a raw texture definition from a model file.\n * <p>\n * This record contains the texture's metadata and its content encoded as a Base64 string.\n * </p>\n *\n * @param name the name of the texture file (e.g., \"texture.png\")\n * @param source the Base64-encoded content of the texture image\n * @param width the width of the texture in pixels\n * @param height the height of the texture in pixels\n * @param uvWidth the UV width of the texture\n * @param uvHeight the UV height of the texture\n * @param frameTime the frame time of the texture\n * @param frameInterpolate the interpolation flag of the texture\n * @since 1.15.2\n */\n@ApiStatus.Internal\npublic record ModelTexture(\n    @NotNull String name,\n    @NotNull String source,\n    int width,\n    int height,\n    @SerializedName(\"uv_width\") int uvWidth,\n    @SerializedName(\"uv_height\") int uvHeight,\n    @SerializedName(\"frame_time\") int frameTime,\n    @SerializedName(\"frame_interpolate\") boolean frameInterpolate\n) {\n\n    /**\n     * Converts this raw texture into a processed {@link BlueprintTexture}.\n     * <p>\n     * This method decodes the Base64 source, generates a pack-compliant name, and determines if the texture should be included in the pack.\n     * </p>\n     *\n     * @param context the model loading context\n     * @return the blueprint texture\n     * @since 1.15.2\n     */\n    public @NotNull BlueprintTexture toBlueprint(@NotNull ModelLoadContext context) {\n        var name = nameWithoutExtension();\n        return new BlueprintTexture(\n            PackUtil.toPackName(name.startsWith(\"global_\") ? name : context.name + \"_\" + name),\n            Base64.getDecoder().decode(source().substring(source().indexOf(',') + 1)),\n            width(),\n            height(),\n            uvWidth(),\n            uvHeight(),\n            !name.startsWith(\"-\"),\n            frameTime(),\n            frameInterpolate()\n        );\n    }\n\n    /**\n     * Returns the texture name without its file extension.\n     *\n     * @return the name without extension\n     * @since 1.15.2\n     */\n    public @NotNull String nameWithoutExtension() {\n        var name = name();\n        var nameIndex = name.lastIndexOf('.');\n        return nameIndex >= 0 ? name.substring(0, nameIndex) : name;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/raw/ModelUV.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.raw;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport kr.toxicity.model.api.data.Float4;\nimport kr.toxicity.model.api.data.blueprint.BlueprintLoadContext;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\n\n/**\n * Represents the UV mapping data for a model face.\n * <p>\n * This record holds the UV coordinates, rotation, and texture index for a specific face of a model element.\n * </p>\n *\n * @param uv the UV coordinates as a {@link Float4} (u1, v1, u2, v2)\n * @param rotation the rotation of the UV map in degrees (0, 90, 180, 270)\n * @param texture the JSON element representing the texture index, can be null\n * @since 1.15.2\n */\npublic record ModelUV(\n    @NotNull Float4 uv,\n    int rotation,\n    @Nullable JsonElement texture\n) {\n\n    /**\n     * Checks if this UV mapping has a valid texture index.\n     *\n     * @return true if a texture is defined, false otherwise\n     * @since 1.15.2\n     */\n    public boolean hasTexture() {\n        return texture != null && texture.isJsonPrimitive() && texture.getAsJsonPrimitive().isNumber();\n    }\n\n    /**\n     * Returns the texture index associated with this UV mapping.\n     *\n     * @return the texture index\n     * @throws NullPointerException if no texture is defined\n     * @since 1.15.2\n     */\n    public int textureIndex() {\n        return Objects.requireNonNull(texture).getAsInt();\n    }\n\n    /**\n     * Converts this UV data to a JSON object for the Minecraft model file.\n     *\n     * @param context the blueprint context, used for texture resolution\n     * @param tint the tint index to apply\n     * @return the generated JSON object\n     * @since 1.15.2\n     */\n    public @Nullable JsonObject toJson(@NotNull BlueprintLoadContext context, int tint) {\n        if (!hasTexture()) return null;\n        var div = uv.div(context.texture(textureIndex()).resolution(context.resolution()));\n        if (!div.isValid()) return null;\n        var object = new JsonObject();\n        object.add(\"uv\", div.toJson());\n        if (rotation != 0) object.addProperty(\"rotation\", rotation);\n        object.addProperty(\"tintindex\", tint);\n        object.addProperty(\"texture\", \"#\" + texture);\n        return object;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/renderer/ModelRenderer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.renderer;\n\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.data.blueprint.BlueprintAnimation;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport kr.toxicity.model.api.tracker.DummyTracker;\nimport kr.toxicity.model.api.tracker.EntityTracker;\nimport kr.toxicity.model.api.tracker.TrackerModifier;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.SequencedMap;\nimport java.util.function.Consumer;\nimport java.util.stream.Stream;\n\n/**\n * A blueprint renderer.\n *\n * @param name name\n * @param type type\n * @param rendererGroups group map\n * @param animations animations\n */\npublic record ModelRenderer(\n    @NotNull String name,\n    @NotNull Type type,\n    @NotNull @Unmodifiable SequencedMap<BoneName, RendererGroup> rendererGroups,\n    @NotNull @Unmodifiable Map<String, BlueprintAnimation> animations\n) {\n    /**\n     * Gets a renderer group by tree\n     *\n     * @param name part name\n     * @return group or null\n     */\n    public @Nullable RendererGroup groupByTree(@NotNull BoneName name) {\n        return groupByTree0(rendererGroups, name);\n    }\n\n    private static @Nullable RendererGroup groupByTree0(@NotNull Map<BoneName, RendererGroup> map, @NotNull BoneName name) {\n        if (map.isEmpty()) return null;\n        var get = map.get(name);\n        if (get != null) return get;\n        else return map.values()\n            .stream()\n            .map(g -> groupByTree0(g.getChildren(), name))\n            .filter(Objects::nonNull)\n            .findFirst()\n            .orElse(null);\n    }\n\n    /**\n     * Gets flatten groups.\n     * @return flatten groups\n     */\n    public @NotNull Stream<RendererGroup> flatten() {\n        return rendererGroups.values().stream().flatMap(RendererGroup::flatten);\n    }\n\n    /**\n     * Gets blueprint animation by name\n     *\n     * @param name name\n     * @return optional animation\n     */\n    public @NotNull Optional<BlueprintAnimation> animation(@NotNull String name) {\n        return Optional.ofNullable(animations().get(name));\n    }\n\n    //----- Dummy -----\n\n    /**\n     * Creates tracker by location\n     *\n     * @param location location\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location) {\n        return create(location, TrackerModifier.DEFAULT);\n    }\n\n    /**\n     * Creates tracker by location\n     *\n     * @param location location\n     * @param modifier modifier\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, @NotNull TrackerModifier modifier) {\n        return create(location, modifier, _ -> {\n        });\n    }\n\n    /**\n     * Creates tracker by location\n     *\n     * @param location          location\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, @NotNull TrackerModifier modifier, @NotNull Consumer<DummyTracker> preUpdateConsumer) {\n        var source = RenderSource.of(location);\n        return source.create(\n            pipeline(source),\n            modifier,\n            preUpdateConsumer\n        );\n    }\n\n    /**\n     * Creates tracker by location and completed profile\n     *\n     * @param location location\n     * @param profile  profile\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, @NotNull ModelProfile profile) {\n        return create(location, profile.asUncompleted());\n    }\n\n    /**\n     * Creates tracker by location and completed profile\n     *\n     * @param location location\n     * @param profile  profile\n     * @param modifier modifier\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, ModelProfile profile, @NotNull TrackerModifier modifier) {\n        return create(location, profile.asUncompleted(), modifier);\n    }\n\n    /**\n     * Creates tracker by location and completed profile\n     *\n     * @param location          location\n     * @param profile           profile\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, @NotNull ModelProfile profile, @NotNull TrackerModifier modifier, @NotNull Consumer<DummyTracker> preUpdateConsumer) {\n        return create(location, profile.asUncompleted(), modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Creates tracker by location and uncompleted profile\n     *\n     * @param location location\n     * @param profile  profile\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, @NotNull ModelProfile.Uncompleted profile) {\n        return create(location, profile, TrackerModifier.DEFAULT);\n    }\n\n    /**\n     * Creates tracker by location and uncompleted profile\n     *\n     * @param location location\n     * @param profile  profile\n     * @param modifier modifier\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier) {\n        return create(location, profile, modifier, _ -> {\n        });\n    }\n\n    /**\n     * Creates tracker by location and uncompleted profile\n     *\n     * @param location          location\n     * @param profile           profile\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return empty tracker\n     */\n    public @NotNull DummyTracker create(@NotNull PlatformLocation location, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier, @NotNull Consumer<DummyTracker> preUpdateConsumer) {\n        var source = RenderSource.of(location, profile);\n        return source.create(\n            pipeline(source),\n            modifier,\n            preUpdateConsumer\n        );\n    }\n\n    //----- Entity -----\n\n    /**\n     * Creates tracker by entity\n     *\n     * @param entity entity\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull PlatformEntity entity) {\n        return create(BaseEntity.of(entity));\n    }\n\n    /**\n     * Creates tracker by entity\n     *\n     * @param entity   entity\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull PlatformEntity entity, @NotNull TrackerModifier modifier) {\n        return create(BaseEntity.of(entity), modifier);\n    }\n\n    /**\n     * Creates tracker by entity\n     *\n     * @param entity            entity\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull PlatformEntity entity, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        return create(BaseEntity.of(entity), modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull PlatformEntity entity, @NotNull ModelProfile.Uncompleted profile) {\n        return create(BaseEntity.of(entity), profile);\n    }\n\n    /**\n     * Creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull PlatformEntity entity, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier) {\n        return create(BaseEntity.of(entity), profile, modifier);\n    }\n\n    /**\n     * Creates tracker by entity and uncompleted profile\n     *\n     * @param entity            entity\n     * @param profile           skin\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull PlatformEntity entity, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        return create(BaseEntity.of(entity), profile, modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Gets or creates tracker by entity\n     *\n     * @param entity entity\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity) {\n        return getOrCreate(BaseEntity.of(entity));\n    }\n\n    /**\n     * Gets or creates tracker by entity\n     *\n     * @param entity   entity\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull TrackerModifier modifier) {\n        return getOrCreate(BaseEntity.of(entity), modifier);\n    }\n\n    /**\n     * Gets or creates tracker by entity\n     *\n     * @param entity            entity\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        return getOrCreate(BaseEntity.of(entity), modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Gets or creates tracker by entity and completed profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull ModelProfile profile) {\n        return getOrCreate(entity, profile.asUncompleted());\n    }\n\n    /**\n     * Gets or creates tracker by entity and completed profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull ModelProfile profile, @NotNull TrackerModifier modifier) {\n        return getOrCreate(entity, profile.asUncompleted(), modifier);\n    }\n\n    /**\n     * Gets or creates tracker by entity and completed profile\n     *\n     * @param entity            entity\n     * @param profile           skin\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull ModelProfile profile, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        return getOrCreate(entity, profile.asUncompleted(), modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Gets or creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull ModelProfile.Uncompleted profile) {\n        return getOrCreate(BaseEntity.of(entity), profile);\n    }\n\n    /**\n     * Gets or creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier) {\n        return getOrCreate(BaseEntity.of(entity), profile, modifier);\n    }\n\n    /**\n     * Gets or creates tracker by entity and uncompleted profile\n     *\n     * @param entity            entity\n     * @param profile           skin\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull PlatformEntity entity, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        return getOrCreate(BaseEntity.of(entity), profile, modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Creates tracker by entity\n     *\n     * @param entity entity\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity) {\n        return create(entity, TrackerModifier.DEFAULT);\n    }\n    /**\n     * Creates tracker by entity\n     *\n     * @param entity   entity\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull TrackerModifier modifier) {\n        return create(entity, modifier, _ -> {\n        });\n    }\n\n    /**\n     * Creates tracker by entity\n     *\n     * @param entity            entity\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        var source = RenderSource.of(entity);\n        return source.create(\n            pipeline(source),\n            modifier,\n            preUpdateConsumer\n        );\n    }\n\n    /**\n     * Creates tracker by entity and completed profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull ModelProfile profile) {\n        return create(entity, profile.asUncompleted());\n    }\n\n    /**\n     * Creates tracker by entity and completed profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull ModelProfile profile, @NotNull TrackerModifier modifier) {\n        return create(entity, profile.asUncompleted(), modifier);\n    }\n\n    /**\n     * Creates tracker by entity and completed profile\n     *\n     * @param entity            entity\n     * @param profile           profile\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull ModelProfile profile, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        return create(entity, profile.asUncompleted(), modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull ModelProfile.Uncompleted profile) {\n        return create(entity, profile, TrackerModifier.DEFAULT);\n    }\n\n    /**\n     * Creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier) {\n        return create(entity, profile, modifier, _ -> {\n        });\n    }\n\n    /**\n     * Creates tracker by entity and uncompleted profile\n     *\n     * @param entity            entity\n     * @param profile           profile\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker create(@NotNull BaseEntity entity, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        var source = RenderSource.of(entity, profile);\n        return source.create(\n            pipeline(source),\n            modifier,\n            preUpdateConsumer\n        );\n    }\n\n    /**\n     * Gets or creates tracker by entity\n     *\n     * @param entity entity\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity) {\n        return getOrCreate(entity, TrackerModifier.DEFAULT);\n    }\n\n    /**\n     * Gets or creates tracker by entity\n     *\n     * @param entity   entity\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, @NotNull TrackerModifier modifier) {\n        return getOrCreate(entity, modifier, _ -> {\n        });\n    }\n\n    /**\n     * Gets or creates tracker by entity\n     *\n     * @param entity            entity\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        var source = RenderSource.of(entity);\n        return source.getOrCreate(\n            name(),\n            () -> pipeline(source),\n            modifier,\n            preUpdateConsumer\n        );\n    }\n\n    /**\n     * Gets or creates tracker by entity and completed profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, @NotNull ModelProfile profile) {\n        return getOrCreate(entity, profile.asUncompleted());\n    }\n\n\n    /**\n     * Gets or creates tracker by entity and completed profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, ModelProfile profile, @NotNull TrackerModifier modifier) {\n        return getOrCreate(entity, profile.asUncompleted(), modifier);\n    }\n\n    /**\n     * Gets or creates tracker by entity and completed profile\n     *\n     * @param entity            entity\n     * @param profile           profile\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, @NotNull ModelProfile profile, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        return getOrCreate(entity, profile.asUncompleted(), modifier, preUpdateConsumer);\n    }\n\n    /**\n     * Gets or creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, @NotNull ModelProfile.Uncompleted profile) {\n        return getOrCreate(entity, profile, TrackerModifier.DEFAULT);\n    }\n\n\n    /**\n     * Gets or creates tracker by entity and uncompleted profile\n     *\n     * @param entity   entity\n     * @param profile  profile\n     * @param modifier modifier\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier) {\n        return getOrCreate(entity, profile, modifier, _ -> {\n        });\n    }\n\n    /**\n     * Gets or creates tracker by entity and uncompleted profile\n     *\n     * @param entity            entity\n     * @param profile           profile\n     * @param modifier          modifier\n     * @param preUpdateConsumer task on pre-update\n     * @return entity tracker\n     */\n    public @NotNull EntityTracker getOrCreate(@NotNull BaseEntity entity, @NotNull ModelProfile.Uncompleted profile, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        var source = RenderSource.of(entity, profile);\n        return source.getOrCreate(\n            name(),\n            () -> pipeline(source),\n            modifier,\n            preUpdateConsumer\n        );\n    }\n\n    private @NotNull RenderPipeline pipeline(@NotNull RenderSource<?> source) {\n        return new RenderPipeline(\n            this,\n            source,\n            rendererGroups.values().stream().map(value -> value.create(source)).toArray(RenderedBone[]::new)\n        );\n    }\n\n    /**\n     * Renderer type\n     */\n    @RequiredArgsConstructor\n    @Getter\n    public enum Type {\n        /**\n         * General\n         */\n        GENERAL(true),\n        /**\n         * Player\n         */\n        PLAYER(false)\n        ;\n        private final boolean canBeSaved;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/renderer/RenderPipeline.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.renderer;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.animation.AnimationOverrideState;\nimport kr.toxicity.model.api.animation.RunningAnimation;\nimport kr.toxicity.model.api.bone.*;\nimport kr.toxicity.model.api.nms.AnimationBundler;\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.nms.PacketBundler;\nimport kr.toxicity.model.api.nms.PlayerChannelHandler;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.ModelRotation;\nimport kr.toxicity.model.api.util.FunctionUtil;\nimport kr.toxicity.model.api.util.function.BonePredicate;\nimport kr.toxicity.model.api.util.function.FloatSupplier;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.BiPredicate;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.associate;\nimport static kr.toxicity.model.api.util.CollectionUtil.associateSequenced;\n\n/**\n * Represents the rendering pipeline for a specific model instance.\n * <p>\n * This class manages the hierarchy of {@link RenderedBone}s, handles player visibility and packet bundling,\n * and coordinates animation updates and inverse kinematics (IK) solving.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class RenderPipeline implements BoneEventHandler, Iterable<RenderedBone> {\n\n    @Getter\n    private final ModelRenderer parent;\n    @Getter\n    private final RenderSource<?> source;\n\n    private final RenderedBone[] bones;\n    private final RenderedBone[] flattenBones;\n    private final SequencedMap<BoneName, RenderedBone> byIdMap;\n\n    private final int displayAmount;\n    private final Map<UUID, SpawnedPlayer> playerMap = new ConcurrentHashMap<>();\n    private final Set<UUID> hidePlayerSet = ConcurrentHashMap.newKeySet();\n\n    private final BoneEventDispatcher eventDispatcher = new BoneEventDispatcher();\n    private final BoneIKSolver ikSolver;\n\n    private Predicate<PlatformPlayer> viewFilter = _ -> true;\n    private Predicate<PlatformPlayer> hideFilter = p -> hidePlayerSet.contains(p.uuid());\n\n    private Consumer<PacketBundler> spawnPacketHandler = _ -> {};\n    private Consumer<PacketBundler> despawnPacketHandler = _ -> {};\n    private Consumer<PacketBundler> hidePacketHandler = _ -> {};\n    private Consumer<PacketBundler> showPacketHandler = _ -> {};\n\n    @Getter\n    private ModelRotation rotation = ModelRotation.INVALID;\n\n    /**\n     * Creates a new render pipeline.\n     *\n     * @param parent the parent model renderer\n     * @param source the source of the rendering (entity or location)\n     * @param bones the array of root bones\n     * @since 1.15.2\n     */\n    public RenderPipeline(\n        @NotNull ModelRenderer parent,\n        @NotNull RenderSource<?> source,\n        @NotNull RenderedBone[] bones\n    ) {\n        this.parent = parent;\n        this.source = source;\n        this.bones = bones;\n        // Bone\n        flattenBones = Arrays.stream(bones).flatMap(RenderedBone::flatten).toArray(RenderedBone[]::new);\n        byIdMap = associateSequenced(flattenBones, RenderedBone::name);\n        ikSolver = new BoneIKSolver(associate(flattenBones, RenderedBone::uuid));\n        // Setup\n        displayAmount = (int) Arrays.stream(flattenBones)\n            .peek(bone -> bone.extend(this))\n            .peek(bone -> bone.locator(ikSolver))\n            .filter(rb -> rb.getDisplay() != null)\n            .count();\n    }\n\n    /**\n     * Creates a packet bundler for this pipeline.\n     *\n     * @return a new packet bundler\n     * @since 1.15.2\n     */\n    public @NotNull PacketBundler createBundler() {\n        return BetterModel.nms().createBundler(displayAmount + 1);\n    }\n\n    /**\n     * Retrieves the channel handler for a specific player.\n     *\n     * @param uuid the UUID of the player\n     * @return the channel handler, or null if not found\n     * @since 1.15.2\n     */\n    public @Nullable PlayerChannelHandler channel(@NotNull UUID uuid) {\n        var get = playerMap.get(uuid);\n        return get != null ? get.handler : null;\n    }\n\n    /**\n     * Creates an animation packet bundler based on configuration.\n     *\n     * @return a new animation packet bundler\n     * @since 2.2.1\n     */\n    public @NotNull AnimationBundler createAnimationBundler() {\n        var size = BetterModel.config().packetBundlingSize();\n        var nms = BetterModel.nms();\n        return new AnimationBundler(\n            size <= 0 ? createBundler() : nms.createParallelBundler(size),\n            nms.createModAnimationBuilder(displayAmount)\n        );\n    }\n\n    @Override\n    public @NotNull BoneEventDispatcher eventDispatcher() {\n        return eventDispatcher;\n    }\n\n    /**\n     * Adds a filter to restrict which players can view the model.\n     *\n     * @param filter the predicate to filter players\n     * @since 1.15.2\n     */\n    public void viewFilter(@NotNull Predicate<PlatformPlayer> filter) {\n        this.viewFilter = this.viewFilter.and(Objects.requireNonNull(filter));\n    }\n\n    /**\n     * Adds a filter to determine if a player should be hidden from the model.\n     *\n     * @param filter the predicate to hide players\n     * @since 1.15.2\n     */\n    public void hideFilter(@NotNull Predicate<PlatformPlayer> filter) {\n        this.hideFilter = this.hideFilter.or(Objects.requireNonNull(filter));\n    }\n\n    /**\n     * Adds a handler for spawn packets.\n     *\n     * @param spawnPacketHandler the consumer to handle spawn packets\n     * @since 1.15.2\n     */\n    public void spawnPacketHandler(@NotNull Consumer<PacketBundler> spawnPacketHandler) {\n        this.spawnPacketHandler = this.spawnPacketHandler.andThen(Objects.requireNonNull(spawnPacketHandler));\n    }\n\n    /**\n     * Adds a handler for despawn packets.\n     *\n     * @param despawnPacketHandler the consumer to handle despawn packets\n     * @since 1.15.2\n     */\n    public void despawnPacketHandler(@NotNull Consumer<PacketBundler> despawnPacketHandler) {\n        this.despawnPacketHandler = this.despawnPacketHandler.andThen(Objects.requireNonNull(despawnPacketHandler));\n    }\n\n    /**\n     * Adds a handler for hide packets.\n     *\n     * @param despawnPacketHandler the consumer to handle hide packets\n     * @since 1.15.2\n     */\n    public void hidePacketHandler(@NotNull Consumer<PacketBundler> despawnPacketHandler) {\n        this.hidePacketHandler = this.hidePacketHandler.andThen(Objects.requireNonNull(despawnPacketHandler));\n    }\n\n    /**\n     * Adds a handler for show packets.\n     *\n     * @param despawnPacketHandler the consumer to handle show packets\n     * @since 1.15.2\n     */\n    public void showPacketHandler(@NotNull Consumer<PacketBundler> despawnPacketHandler) {\n        this.showPacketHandler = this.showPacketHandler.andThen(Objects.requireNonNull(despawnPacketHandler));\n    }\n\n    /**\n     * Checks if the model is spawned for a specific player.\n     *\n     * @param uuid the UUID of the player\n     * @return true if spawned, false otherwise\n     * @since 1.15.2\n     */\n    public boolean isSpawned(@NotNull UUID uuid) {\n        return playerMap.containsKey(uuid);\n    }\n\n    /**\n     * Retrieves the currently running animation, if any.\n     *\n     * @return the running animation, or null if none\n     * @since 1.15.2\n     */\n    public @Nullable RunningAnimation runningAnimation() {\n        return firstNotNull(RenderedBone::runningAnimation);\n    }\n\n    /**\n     * Returns the name of the model.\n     *\n     * @return the model name\n     * @since 1.15.2\n     */\n    public @NotNull String name() {\n        return parent.name();\n    }\n\n    /**\n     * Despawns the model for all players and clears internal state.\n     *\n     * @since 1.15.2\n     */\n    public void despawn() {\n        hitboxes().forEach(HitBox::removeHitBox);\n        var bundler = createBundler();\n        remove0(bundler);\n        if (bundler.isNotEmpty()) allPlayer().map(PlayerChannelHandler::player).forEach(bundler::send);\n        playerMap.clear();\n    }\n\n    /**\n     * Rotates the model to a new orientation.\n     *\n     * @param rotation the new rotation\n     * @param bundler the packet bundler to use\n     * @return true if the rotation changed, false otherwise\n     * @since 1.15.2\n     */\n    public boolean rotate(@NotNull ModelRotation rotation, @NotNull PacketBundler bundler) {\n        if (rotation.equals(this.rotation)) return false;\n        this.rotation = rotation;\n        return matchTree(b -> b.rotate(rotation, bundler));\n    }\n\n    /**\n     * Ticks the model, updating animations and IK.\n     *\n     * @param bundler the packet bundler to use\n     * @return true if any updates occurred\n     * @since 1.15.2\n     */\n    public boolean tick(@NotNull AnimationBundler bundler) {\n        var match = matchTree(RenderedBone::tick);\n        if (match) {\n            ikSolver.solve();\n            forEach(b -> b.sendTransformation(null, bundler));\n        }\n        return match;\n    }\n\n    /**\n     * Ticks the model for a specific player (e.g., for per-player animations).\n     *\n     * @param uuid the UUID of the player\n     * @param bundler the packet bundler to use\n     * @return true if any updates occurred\n     * @since 1.15.2\n     */\n    public boolean tick(@NotNull UUID uuid, @NotNull AnimationBundler bundler) {\n        var match = matchTree(b -> b.tick(uuid));\n        if (match) {\n            ikSolver.solve(uuid);\n            forEach(b -> b.sendTransformation(uuid, bundler));\n        }\n        return match;\n    }\n\n    /**\n     * Sets the default position modifier for all bones.\n     *\n     * @param movement the movement function\n     * @since 1.15.2\n     */\n    public void defaultPosition(@NotNull Function<Vector3f, Vector3f> movement) {\n        var vec = new Vector3f();\n        var supplier = FunctionUtil.throttleTick(() -> movement.apply(vec));\n        forEach(b -> b.defaultPosition(supplier));\n    }\n\n    /**\n     * Scales the model.\n     *\n     * @param scale the scale supplier\n     * @since 1.15.2\n     */\n    public void scale(@NotNull FloatSupplier scale) {\n        forEach(b -> b.scale(scale));\n    }\n\n    /**\n     * Adds a local rotation modifier to matching bones.\n     *\n     * @param predicate the predicate to select bones\n     * @param mapper the rotation mapping function\n     * @return true if any bones were modified\n     * @since 3.0.0\n     */\n    public boolean addLocalRotModifier(@NotNull BonePredicate predicate, @NotNull Function<Quaternionf, Quaternionf> mapper) {\n        return matchTree(predicate, (b, p) -> b.addLocalRotModifier(p, mapper));\n    }\n\n    /**\n     * Adds a global rotation modifier to matching bones.\n     *\n     * @param predicate the predicate to select bones\n     * @param mapper the rotation mapping function\n     * @return true if any bones were modified\n     * @since 3.0.0\n     */\n    public boolean addGlobalRotModifier(@NotNull BonePredicate predicate, @NotNull Function<Quaternionf, Quaternionf> mapper) {\n        return matchTree(predicate, (b, p) -> b.addGlobalRotModifier(p, mapper));\n    }\n\n    /**\n     * Adds a position modifier to matching bones.\n     *\n     * @param predicate the predicate to select bones\n     * @param mapper the position mapping function\n     * @return true if any bones were modified\n     * @since 1.15.2\n     */\n    public boolean addPositionModifier(@NotNull BonePredicate predicate, @NotNull Function<Vector3f, Vector3f> mapper) {\n        return matchTree(predicate, (b, p) -> b.addPositionModifier(p, mapper));\n    }\n\n    /**\n     * Returns a collection of all bones in this pipeline.\n     *\n     * @return the collection of bones\n     * @since 1.15.2\n     */\n    public @NotNull @Unmodifiable Collection<RenderedBone> bones() {\n        return byIdMap.values();\n    }\n\n    /**\n     * Returns a stream of all hitboxes associated with this model.\n     *\n     * @return the stream of hitboxes\n     * @since 1.15.2\n     */\n    public @NotNull Stream<HitBox> hitboxes() {\n        return stream()\n            .map(RenderedBone::getHitBox)\n            .filter(Objects::nonNull);\n    }\n\n    /**\n     * Retrieves a bone by its name.\n     *\n     * @param name the name of the bone\n     * @return the rendered bone, or null if not found\n     * @since 1.15.2\n     */\n    public @Nullable RenderedBone boneOf(@NotNull BoneName name) {\n        return byIdMap.get(name);\n    }\n\n    /**\n     * Spawns the model for a player.\n     *\n     * @param player the player to spawn for\n     * @param bundler the packet bundler to use\n     * @param consumer a consumer for the spawned player object\n     * @return true if spawned successfully\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public boolean spawn(@NotNull PlatformPlayer player, @NotNull PacketBundler bundler, @NotNull Consumer<SpawnedPlayer> consumer) {\n        var get = BetterModel.platform().playerManager().player(player.uuid());\n        if (get == null) return false;\n        var spawnedPlayer = new SpawnedPlayer(get);\n        playerMap.put(player.uuid(), spawnedPlayer);\n        spawnPacketHandler.accept(bundler);\n        var hided = isHide(player);\n        forEach(b -> b.spawn(hided, bundler));\n        consumer.accept(spawnedPlayer);\n        return true;\n    }\n\n    /**\n     * Removes the model for a player.\n     *\n     * @param player the player to remove for\n     * @return true if removed successfully\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public boolean remove(@NotNull PlatformPlayer player) {\n        if (playerMap.remove(player.uuid()) == null) return false;\n        var bundler = createBundler();\n        remove0(bundler);\n        bundler.send(player);\n        return true;\n    }\n\n    @ApiStatus.Internal\n    private void remove0(@NotNull PacketBundler bundler) {\n        despawnPacketHandler.accept(bundler);\n        forEach(b -> b.remove(bundler));\n    }\n\n    /**\n     * Applies a mapper to bones matching a predicate.\n     *\n     * @param predicate the bone predicate\n     * @param mapper the mapper function\n     * @return true if any bones matched\n     * @since 1.15.2\n     */\n    public boolean matchTree(@NotNull BonePredicate predicate, BiPredicate<RenderedBone, BonePredicate> mapper) {\n        Objects.requireNonNull(predicate);\n        Objects.requireNonNull(mapper);\n        if (predicate == BonePredicate.FALSE) return false;\n        if (predicate == BonePredicate.TRUE || predicate.applyAtChildren() == BonePredicate.State.NOT_SET) return matchTree(b -> mapper.test(b, predicate));\n        var result = false;\n        for (RenderedBone value : bones) {\n            if (value.matchTree(predicate, mapper)) result = true;\n        }\n        return result;\n    }\n\n    /**\n     * Applies a mapper to bones matching an animation predicate.\n     *\n     * @param mapper the mapper function\n     * @return true if any bones matched\n     * @since 1.15.2\n     */\n    public boolean matchAnimation(@NotNull BiPredicate<RenderedBone, AnimationOverrideState> mapper) {\n        Objects.requireNonNull(mapper);\n        var result = false;\n        for (RenderedBone value : bones) {\n            if (value.matchAnimation(AnimationOverrideState.NOT_MATCHED, mapper)) result = true;\n        }\n        return result;\n    }\n\n    /**\n     * Checks if any bones match a predicate.\n     *\n     * @param predicate the predicate\n     * @return true if any bones matched\n     * @since 1.15.2\n     */\n    public boolean matchTree(@NotNull Predicate<RenderedBone> predicate) {\n        Objects.requireNonNull(predicate);\n        var result = false;\n        for (RenderedBone value : flattenBones) {\n            if (predicate.test(value)) result = true;\n        }\n        return result;\n    }\n\n    /**\n     * Finds the first non-null result of applying a mapper to all bones.\n     *\n     * @param mapper the mapper function\n     * @param <T> the result type\n     * @return the first non-null result, or null\n     * @since 1.15.2\n     */\n    public <T> @Nullable T firstNotNull(@NotNull Function<RenderedBone, T> mapper) {\n        Objects.requireNonNull(mapper);\n        for (RenderedBone value : flattenBones) {\n            var t = mapper.apply(value);\n            if (t != null) return t;\n        }\n        return null;\n    }\n\n    /**\n     * Returns the number of players currently viewing this model.\n     *\n     * @return the player count\n     * @since 1.15.2\n     */\n    public int playerCount() {\n        return playerMap.size();\n    }\n\n    /**\n     * Returns a stream of all players viewing this model.\n     *\n     * @return the stream of players\n     * @since 1.15.2\n     */\n    public @NotNull Stream<PlayerChannelHandler> allPlayer() {\n        return playerMap.values()\n            .stream()\n            .map(spawned -> spawned.handler);\n    }\n\n    /**\n     * Returns a stream of players who are not hidden and pass the view filter.\n     *\n     * @return the stream of visible players\n     * @since 1.15.2\n     */\n    public @NotNull Stream<PlayerChannelHandler> nonHidePlayer() {\n        return playerMap.values()\n            .stream()\n            .filter(spawned -> spawned.initialLoad)\n            .map(spawned -> spawned.handler)\n            .filter(p -> !hideFilter.test(p.player()));\n    }\n\n    /**\n     * Returns a stream of players who pass the view filter (regardless of hidden status).\n     *\n     * @return the stream of viewed players\n     * @since 1.15.2\n     */\n    public @NotNull Stream<PlayerChannelHandler> viewedPlayer() {\n        return allPlayer().filter(channel -> viewFilter.test(channel.player()));\n    }\n\n    /**\n     * Hides the model from a specific player.\n     *\n     * @param player the player to hide from\n     * @return true if the player was successfully hidden\n     * @since 1.15.2\n     */\n    public boolean hide(@NotNull PlatformPlayer player) {\n        if (isHide(player) || !hidePlayerSet.add(player.uuid())) return false;\n        if (isSpawned(player.uuid())) {\n            var bundler = createBundler();\n            forEach(b -> b.forceUpdate(false, bundler));\n            hidePacketHandler.accept(bundler);\n            if (bundler.isNotEmpty()) bundler.send(player);\n        }\n        player.task(() -> hitboxes().forEach(hb -> hb.hide(player)));\n        return true;\n    }\n\n    /**\n     * Checks if the model is hidden from a specific player.\n     *\n     * @param player the player to check\n     * @return true if hidden\n     * @since 1.15.2\n     */\n    public boolean isHide(@NotNull PlatformPlayer player) {\n        return hideFilter.test(player);\n    }\n\n    /**\n     * Shows the model to a specific player (if previously hidden).\n     *\n     * @param player the player to show to\n     * @return true if the player was successfully shown\n     * @since 1.15.2\n     */\n    public boolean show(@NotNull PlatformPlayer player) {\n        if (!isHide(player) || !hidePlayerSet.remove(player.uuid())) return false;\n        if (isSpawned(player.uuid())) {\n            var bundler = createBundler();\n            forEach(b -> b.forceUpdate(true, bundler));\n            showPacketHandler.accept(bundler);\n            if (bundler.isNotEmpty()) bundler.send(player);\n        }\n        player.task(() -> hitboxes().forEach(hb -> hb.show(player)));\n        return true;\n    }\n\n    @Override\n    public void forEach(@NotNull Consumer<? super RenderedBone> action) {\n        for (RenderedBone bone : flattenBones) {\n            action.accept(bone);\n        }\n    }\n\n    @Override\n    @NotNull\n    public Iterator<RenderedBone> iterator() {\n        return bones().iterator();\n    }\n\n    @Override\n    @NotNull\n    public Spliterator<RenderedBone> spliterator() {\n        return Arrays.spliterator(flattenBones);\n    }\n\n    /**\n     * Returns a sequential {@code Stream} with the flattened bones as its source.\n     *\n     * @return a stream of all bones in this pipeline\n     * @since 2.2.0\n     */\n    public @NotNull Stream<RenderedBone> stream() {\n        return Arrays.stream(flattenBones);\n    }\n\n    /**\n     * Represents a player for whom the model has been spawned.\n     *\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor\n    public class SpawnedPlayer {\n        private final PlayerChannelHandler handler;\n        private boolean initialLoad;\n\n        /**\n         * Loads the model for this player, sending initial packets.\n         *\n         * @since 1.15.2\n         */\n        public void load() {\n            initialLoad = true;\n            if (isHide(handler.player())) return;\n            var b = createBundler();\n            forEach(bone -> bone.forceUpdate(b));\n            if (b.isNotEmpty()) b.send(handler.player());\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/renderer/RenderSource.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.renderer;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.armor.PlayerArmor;\nimport kr.toxicity.model.api.bone.BoneRenderContext;\nimport kr.toxicity.model.api.nms.Profiled;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.player.PlayerSkinParts;\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport kr.toxicity.model.api.tracker.*;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * Represents a source for rendering models, providing necessary context such as location and entity data.\n * <p>\n * This interface serves as the entry point for creating {@link Tracker} instances, which manage the lifecycle of a rendered model.\n * It supports both entity-based and location-based (dummy) rendering sources.\n * </p>\n *\n * @param <T> the type of tracker created by this source\n * @since 1.15.2\n */\npublic sealed interface RenderSource<T extends Tracker> {\n\n    /**\n     * Creates a dummy render source at the specified location.\n     *\n     * @param location the location where the model will be rendered\n     * @return a new dummy render source\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    static @NotNull RenderSource.Dummy of(@NotNull PlatformLocation location) {\n        return new BaseDummy(location);\n    }\n\n    /**\n     * Creates a dummy render source at the specified location with a specific model profile.\n     *\n     * @param location the location where the model will be rendered\n     * @param profile the uncompleted model profile to use\n     * @return a new profiled dummy render source\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    static @NotNull RenderSource.Dummy of(@NotNull PlatformLocation location, @NotNull ModelProfile.Uncompleted profile) {\n        return new ProfiledDummy(location, profile);\n    }\n\n    /**\n     * Creates an entity render source for the given entity with a specific model profile.\n     *\n     * @param entity the entity to attach the model to\n     * @param profile the uncompleted model profile to use\n     * @return a new entity render source\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    static @NotNull RenderSource.Entity of(@NotNull kr.toxicity.model.api.entity.BaseEntity entity, @NotNull ModelProfile.Uncompleted profile) {\n        return entity instanceof kr.toxicity.model.api.entity.BasePlayer player ? new ProfiledPlayer(player, profile) : new ProfiledEntity(entity, profile);\n    }\n\n    /**\n     * Creates an entity render source for the given entity.\n     *\n     * @param entity the entity to attach the model to\n     * @return a new entity render source\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    static @NotNull RenderSource.Entity of(@NotNull kr.toxicity.model.api.entity.BaseEntity entity) {\n        return entity instanceof kr.toxicity.model.api.entity.BasePlayer player ? new BasePlayer(player) : new BaseEntity(entity);\n    }\n\n    /**\n     * Returns the location of this render source.\n     *\n     * @return the location\n     * @since 1.15.2\n     */\n    @NotNull PlatformLocation location();\n\n    /**\n     * Creates a new tracker for this render source.\n     *\n     * @param pipeline the render pipeline to use\n     * @param modifier the tracker modifier\n     * @param preUpdateConsumer a consumer to run before updates\n     * @return the created tracker\n     * @since 1.15.2\n     */\n    T create(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<T> preUpdateConsumer);\n\n    /**\n     * Asynchronously completes the bone render context for this source.\n     * <p>\n     * This method may involve fetching skin data or other resources.\n     * </p>\n     *\n     * @return a future completing with the bone render context\n     * @since 1.15.2\n     */\n    @NotNull CompletableFuture<BoneRenderContext> completeContext();\n\n    /**\n     * Returns a fallback bone render context.\n     * <p>\n     * This context is used when the complete context cannot be resolved or is not yet available.\n     * </p>\n     *\n     * @return the fallback bone render context\n     * @since 1.15.2\n     */\n    default BoneRenderContext fallbackContext() {\n        return new BoneRenderContext(this);\n    }\n\n    /**\n     * Represents a render source attached to an entity.\n     *\n     * @since 1.15.2\n     */\n    sealed interface Entity extends RenderSource<EntityTracker> {\n        /**\n         * Returns the entity associated with this source.\n         *\n         * @return the entity\n         * @since 1.15.2\n         */\n        @NotNull kr.toxicity.model.api.entity.BaseEntity entity();\n\n        /**\n         * Gets or creates an entity tracker for this source.\n         *\n         * @param name the name of the tracker\n         * @param supplier a supplier for the render pipeline\n         * @param modifier the tracker modifier\n         * @param preUpdateConsumer a consumer to run before updates\n         * @return the entity tracker\n         * @since 1.15.2\n         */\n        @NotNull\n        EntityTracker getOrCreate(@NotNull String name, @NotNull Supplier<RenderPipeline> supplier, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer);\n    }\n\n    /**\n     * Represents a render source at a fixed location, not attached to an entity.\n     *\n     * @since 1.15.2\n     */\n    sealed interface Dummy extends RenderSource<DummyTracker> {\n    }\n\n\n    /**\n     * A basic implementation of {@link Dummy} with a location.\n     *\n     * @param location the location\n     * @since 1.15.2\n     */\n    record BaseDummy(@NotNull PlatformLocation location) implements Dummy {\n        @NotNull\n        @Override\n        public DummyTracker create(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<DummyTracker> preUpdateConsumer) {\n            return new DummyTracker(location, pipeline, modifier, preUpdateConsumer);\n        }\n\n        @Override\n        public @NotNull CompletableFuture<BoneRenderContext> completeContext() {\n            return CompletableFuture.completedFuture(fallbackContext());\n        }\n    }\n\n    /**\n     * A profiled implementation of {@link Dummy} with a location and a model profile.\n     *\n     * @param location the location\n     * @param profile the model profile\n     * @since 1.15.2\n     */\n    record ProfiledDummy(@NotNull PlatformLocation location, @NotNull ModelProfile.Uncompleted profile) implements Dummy {\n        @NotNull\n        @Override\n        public DummyTracker create(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<DummyTracker> preUpdateConsumer) {\n            return new DummyTracker(location, pipeline, modifier, preUpdateConsumer);\n        }\n\n        @Override\n        public @NotNull CompletableFuture<BoneRenderContext> completeContext() {\n            return BetterModel.platform().skinManager().complete(profile).thenApply(skin -> new BoneRenderContext(this, skin));\n        }\n    }\n\n    /**\n     * A basic implementation of {@link Entity} wrapping a {@link kr.toxicity.model.api.entity.BaseEntity}.\n     *\n     * @param entity the entity\n     * @since 1.15.2\n     */\n    record BaseEntity(@NotNull kr.toxicity.model.api.entity.BaseEntity entity) implements Entity {\n\n        @NotNull\n        @Override\n        public EntityTracker create(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).create(pipeline.name(), r -> new EntityTracker(r, pipeline, modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull EntityTracker getOrCreate(@NotNull String name, @NotNull Supplier<RenderPipeline> supplier, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).getOrCreate(name, r -> new EntityTracker(r, supplier.get(), modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull PlatformLocation location() {\n            return entity.location();\n        }\n\n\n        @Override\n        public @NotNull CompletableFuture<BoneRenderContext> completeContext() {\n            return CompletableFuture.completedFuture(fallbackContext());\n        }\n    }\n\n    /**\n     * A profiled implementation of {@link Entity} wrapping a {@link kr.toxicity.model.api.entity.BaseEntity} and a model profile.\n     *\n     * @param entity the entity\n     * @param profile the model profile\n     * @since 1.15.2\n     */\n    record ProfiledEntity(@NotNull kr.toxicity.model.api.entity.BaseEntity entity, @NotNull ModelProfile.Uncompleted profile) implements Entity {\n\n        @NotNull\n        @Override\n        public EntityTracker create(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).create(pipeline.name(), r -> new EntityTracker(r, pipeline, modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull EntityTracker getOrCreate(@NotNull String name, @NotNull Supplier<RenderPipeline> supplier, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).getOrCreate(name, r -> new EntityTracker(r, supplier.get(), modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull PlatformLocation location() {\n            return entity.location();\n        }\n\n        @Override\n        public @NotNull CompletableFuture<BoneRenderContext> completeContext() {\n            return BetterModel.platform().skinManager().complete(profile).thenApply(skin -> new BoneRenderContext(this, skin));\n        }\n    }\n\n    /**\n     * A basic implementation of {@link Entity} wrapping a {@link kr.toxicity.model.api.entity.BasePlayer}.\n     *\n     * @param entity the player entity\n     * @since 1.15.2\n     */\n    record BasePlayer(@NotNull kr.toxicity.model.api.entity.BasePlayer entity) implements Entity, Profiled {\n\n        @NotNull\n        @Override\n        public EntityTracker create(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).create(pipeline.name(), r -> new PlayerTracker(r, pipeline, modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull EntityTracker getOrCreate(@NotNull String name, @NotNull Supplier<RenderPipeline> supplier, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).getOrCreate(name, r -> new PlayerTracker(r, supplier.get(), modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull PlatformLocation location() {\n            return entity.location();\n        }\n\n        @Override\n        public @NotNull CompletableFuture<BoneRenderContext> completeContext() {\n            return BetterModel.platform().skinManager().complete(profile().asUncompleted()).thenApply(skin -> new BoneRenderContext(this, skin));\n        }\n\n        @Override\n        public @NotNull ModelProfile profile() {\n            return entity.profile();\n        }\n\n        @Override\n        public @NotNull PlayerArmor armors() {\n            return entity.armors();\n        }\n\n        @Override\n        public @NotNull PlayerSkinParts skinParts() {\n            return entity.skinParts();\n        }\n    }\n\n    /**\n     * A profiled implementation of {@link Entity} wrapping a {@link kr.toxicity.model.api.entity.BasePlayer} and a model profile.\n     *\n     * @param entity the player entity\n     * @param externalProfile the external model profile\n     * @since 1.15.2\n     */\n    record ProfiledPlayer(@NotNull kr.toxicity.model.api.entity.BasePlayer entity, @NotNull ModelProfile.Uncompleted externalProfile) implements Entity, Profiled {\n        @NotNull\n        @Override\n        public EntityTracker create(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).create(pipeline.name(), r -> new PlayerTracker(r, pipeline, modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull EntityTracker getOrCreate(@NotNull String name, @NotNull Supplier<RenderPipeline> supplier, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n            return EntityTrackerRegistry.getOrCreate(entity).getOrCreate(name, r -> new PlayerTracker(r, supplier.get(), modifier, preUpdateConsumer));\n        }\n\n        @Override\n        public @NotNull PlatformLocation location() {\n            return entity.location();\n        }\n\n        @Override\n        public @NotNull CompletableFuture<BoneRenderContext> completeContext() {\n            return BetterModel.platform().skinManager().complete(externalProfile).thenApply(skin -> new BoneRenderContext(this, skin));\n        }\n\n        @Override\n        public @NotNull ModelProfile profile() {\n            return entity.profile();\n        }\n\n        @Override\n        public @NotNull PlayerArmor armors() {\n            return entity.armors();\n        }\n\n        @Override\n        public @NotNull PlayerSkinParts skinParts() {\n            return entity.skinParts();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/data/renderer/RendererGroup.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.data.renderer;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.bone.*;\nimport kr.toxicity.model.api.data.blueprint.BlueprintElement;\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox;\nimport kr.toxicity.model.api.mount.MountController;\nimport kr.toxicity.model.api.mount.MountControllers;\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport kr.toxicity.model.api.util.MathUtil;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\nimport org.joml.Vector3f;\n\nimport java.util.SequencedMap;\nimport java.util.UUID;\nimport java.util.stream.Stream;\n\n/**\n * A group of models.\n */\n@RequiredArgsConstructor\npublic final class RendererGroup {\n\n    private static final Vector3f DEFAULT_SCALE = new Vector3f(1);\n    @Getter\n    private final BlueprintElement.Bone parent;\n    @Getter\n    private final Vector3f position;\n    private final Vector3f rotation;\n    private final TransformedItemStack itemStack;\n    @Getter\n    @Unmodifiable\n    private final SequencedMap<BoneName, RendererGroup> children;\n    @Getter\n    private final @Nullable ModelBoundingBox hitBox;\n    @Getter\n    private final @NotNull Vector3f hitBoxPoint;\n\n    @Getter\n    private final @NotNull BoneItemMapper itemMapper;\n\n    @Getter\n    private final @NotNull MountController mountController;\n\n    /**\n     * Creates group instance.\n     * @param scale scale\n     * @param itemStack item\n     * @param group parent\n     * @param children children\n     * @param box hit-box\n     */\n    public RendererGroup(\n        float scale,\n        @Nullable PlatformItemStack itemStack,\n        @NotNull BlueprintElement.Bone group,\n        @NotNull SequencedMap<BoneName, RendererGroup> children,\n        @Nullable ModelBoundingBox box\n    ) {\n        this.parent = group;\n        this.children = children;\n        this.itemStack = TransformedItemStack.of(\n            new Vector3f(),\n            new Vector3f(),\n            new Vector3f(scale),\n            itemStack != null ? itemStack : BetterModel.platform().adapter().air()\n        );\n        this.itemMapper = name().toItemMapper();\n        this.position = group.origin().toBlockScale().toVector();\n        this.hitBox = box;\n        this.hitBoxPoint = box == null ? new Vector3f() : box.centerPoint();\n        this.rotation = group.rotation().toVector();\n        if (name().tagged(BoneTags.SEAT)) {\n            mountController = BetterModel.config().defaultMountController();\n        } else if (name().tagged(BoneTags.SUB_SEAT)) {\n            mountController = MountControllers.NONE;\n        } else mountController = MountControllers.INVALID;\n    }\n\n    public @NotNull Stream<RendererGroup> flatten() {\n        return children.isEmpty() ? Stream.of(this) : Stream.concat(\n            Stream.of(this),\n            children.values().stream().flatMap(RendererGroup::flatten)\n        );\n    }\n\n    /**\n     * Gets name\n     * @return name\n     */\n    public @NotNull BoneName name() {\n        return parent.name();\n    }\n\n    /**\n     * Gets uuid\n     * @return uuid\n     */\n    public @NotNull UUID uuid() {\n        return parent.uuid();\n    }\n\n    /**\n     * Creates entity.\n     * @param source source\n     * @return entity\n     */\n    public @NotNull RenderedBone create(@NotNull RenderSource<?> source) {\n        return create(source.fallbackContext(), null);\n    }\n\n    private @NotNull RenderedBone create(@NotNull BoneRenderContext context, @Nullable RenderedBone parentBone) {\n        return new RenderedBone(\n            this,\n            parentBone,\n            context,\n            new BoneMovement(\n                parentBone != null ? position.sub(parentBone.getGroup().position, new Vector3f()) : new Vector3f(),\n                DEFAULT_SCALE,\n                MathUtil.toQuaternion(rotation),\n                rotation\n            ),\n            parent -> children.values().stream().map(value -> value.create(context, parent)).toArray(RenderedBone[]::new)\n        );\n    }\n\n    /**\n     * Gets display item.\n     * @return item\n     */\n    public @NotNull TransformedItemStack getItemStack() {\n        return itemStack.copy();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (!(o instanceof RendererGroup that)) return false;\n        return uuid().equals(that.uuid());\n    }\n\n    @Override\n    public int hashCode() {\n        return uuid().hashCode();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/entity/BaseEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.entity;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.nms.Identifiable;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport net.kyori.adventure.text.Component;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Vector3f;\n\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\n/**\n * An adapter of entity\n */\npublic interface BaseEntity extends Identifiable {\n\n    /**\n     * Gets base entity\n     * @param entity platform entity\n     * @return base entity\n     */\n    static @NotNull BaseEntity of(@NotNull PlatformEntity entity) {\n        if (entity instanceof PlatformPlayer player) {\n            var channel = BetterModel.platform().playerManager().player(player.uuid());\n            return channel != null ? channel.base() : BetterModel.nms().adapt(player);\n        }\n        return BetterModel.nms().adapt(entity);\n    }\n\n    /**\n     * Gets the platform-specific entity object.\n     * @since 2.0.0\n     * @return The platform entity.\n     */\n    @NotNull PlatformEntity platform();\n\n    /**\n     * Gets the current location of the entity.\n     * @since 2.0.0\n     * @return The entity's location.\n     */\n    default @NotNull PlatformLocation location() {\n        return platform().location();\n    }\n\n    /**\n     * Gets custom name of this entity\n     * @return custom name\n     */\n    @Nullable Component customName();\n\n    /**\n     * Gets vanilla entity\n     * @return vanilla entity\n     */\n    @NotNull Object handle();\n\n    /**\n     * Gets entity id\n     * @return entity id\n     */\n    int id();\n\n    /**\n     * Checks source entity is dead\n     * @return dead\n     */\n    boolean dead();\n\n    /**\n     * Checks source entity is on the ground\n     * @return on the ground\n     */\n    boolean ground();\n\n    /**\n     * Checks source entity is invisible\n     * @return invisible\n     */\n    boolean invisible();\n\n    /**\n     * Check source entity is on a glow\n     * @return glow\n     */\n    boolean glow();\n\n    /**\n     * Check source entity is on a walk\n     * @return walk\n     */\n    boolean onWalk();\n\n    /**\n     * Check source entity is on the fly\n     * @return fly\n     */\n    boolean fly();\n\n    /**\n     * Gets entity's scale\n     * @return scale\n     */\n    double scale();\n\n    /**\n     * Gets entity's pitch (x-rot)\n     * @return pitch\n     */\n    float pitch();\n\n    /**\n     * Gets entity's body yaw (y-rot)\n     * @return body yaw\n     */\n    float bodyYaw();\n\n    /**\n     * Gets entity's yaw (y-rot)\n     * @return yaw\n     */\n    float yaw();\n\n    /**\n     * Gets entity's head yaw (y-rot)\n     * @return head yaw\n     */\n    float headYaw();\n\n    /**\n     * Gets entity's damage tick\n     * @return damage tick\n     */\n    float damageTick();\n\n    /**\n     * Gets entity's walk speed\n     * @return walk speed\n     */\n    float walkSpeed();\n\n    /**\n     * Gets entity's passenger point\n     * @param dest destination vector\n     * @return passenger point\n     */\n    @NotNull Vector3f passengerPosition(@NotNull Vector3f dest);\n\n    /**\n     * Gets tracked player set\n     * @return tracked player set\n     */\n    @NotNull Stream<PlatformPlayer> trackedBy();\n\n    /**\n     * Gets main hand item\n     * @return main hand\n     */\n    @NotNull TransformedItemStack mainHand();\n\n    /**\n     * Gets offhand item\n     * @return offhand\n     */\n    @NotNull TransformedItemStack offHand();\n\n    /**\n     * Gets tracker registry of this adapter\n     * @return optional tracker registry\n     */\n    default @NotNull Optional<EntityTrackerRegistry> registry() {\n        return BetterModel.registry(uuid());\n    }\n\n    /**\n     * Checks this entity has controlling passenger\n     * @return has controlling passenger\n     */\n    default boolean hasControllingPassenger() {\n        var registry = registry().orElse(null);\n        return registry != null && registry.hasControllingPassenger();\n    }\n\n    /**\n     * Checks this entity has model data\n     * @return has model data\n     */\n    default boolean hasModelData() {\n        return modelData() != null;\n    }\n\n    /**\n     * Gets this entity's model data\n     * @return model data\n     */\n    @Nullable String modelData();\n\n    /**\n     * Sets this entity's model data\n     * @param modelData model data\n     */\n    void modelData(@Nullable String modelData);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/entity/BasePlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.entity;\n\nimport kr.toxicity.model.api.nms.Profiled;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * An adapter of player\n */\npublic interface BasePlayer extends BaseEntity, Profiled {\n\n    /**\n     * Updates current inventory\n     */\n    void updateInventory();\n\n    @Override\n    @NotNull PlatformPlayer platform();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/AnimationSignalEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when an animation script emits a signal.\n * <p>\n * This event allows plugins/mods to react to custom signals defined within BlockBench animations.\n * </p>\n *\n * @param player the player associated with the animation\n * @param signal the signal\n * @since 2.0.0\n */\npublic record AnimationSignalEvent(\n    @NotNull PlatformPlayer player,\n    @NotNull String signal\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/CancellableEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\n/**\n * Represents an event that can be canceled.\n * <p>\n * Cancelling an event typically prevents the associated action from completing.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface CancellableEvent extends ModelEvent {\n\n    /**\n     * Checks if the event has been canceled.\n     *\n     * @return true if canceled, false otherwise\n     * @since 2.0.0\n     */\n    boolean isCancelled();\n\n    /**\n     * Sets the cancellation state of the event.\n     *\n     * @param cancel true to cancel the event, false to allow it\n     * @since 2.0.0\n     */\n    void setCancelled(boolean cancel);\n\n    @Override\n    default boolean call() {\n        return ModelEvent.super.call() && !isCancelled();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/CloseTrackerEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.tracker.Tracker;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a tracker is closed.\n * <p>\n * This event provides information about the tracker being closed and the reason for closure.\n * </p>\n *\n * @param tracker the tracker being closed\n * @param reason the reason for closing the tracker\n * @since 2.0.0\n */\npublic record CloseTrackerEvent(\n    @NotNull Tracker tracker,\n    @NotNull Tracker.CloseReason reason\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/CreateDummyTrackerEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.tracker.DummyTracker;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a new {@link DummyTracker} is created.\n * <p>\n * This event allows plugins/mods to perform initialization or tracking logic for dummy trackers.\n * </p>\n *\n * @param tracker the newly created dummy tracker\n * @since 2.0.0\n */\npublic record CreateDummyTrackerEvent(\n    @NotNull DummyTracker tracker\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/CreateEntityTrackerEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.tracker.EntityTracker;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a new {@link EntityTracker} is created.\n * <p>\n * This event allows plugins/mods to perform initialization or tracking logic for entity trackers.\n * </p>\n *\n * @param tracker the newly created entity tracker\n * @since 2.0.0\n */\npublic record CreateEntityTrackerEvent(\n    @NotNull EntityTracker tracker\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/CreatePlayerSkinEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a player's skin data is created or loaded.\n * <p>\n * This event allows modifying the player's model profile before it is used.\n * </p>\n *\n * @since 2.0.0\n */\n@Getter\n@Setter\npublic final class CreatePlayerSkinEvent implements ModelEvent {\n\n    private ModelProfile modelProfile;\n\n    /**\n     * Creates a new CreatePlayerSkinEvent.\n     *\n     * @param modelProfile the model profile being created\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public CreatePlayerSkinEvent(@NotNull ModelProfile modelProfile) {\n        this.modelProfile = modelProfile;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/DismountModelEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.tracker.EntityTracker;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when an entity dismounts from a model's hitbox.\n * <p>\n * This event allows plugins/mods to intercept and potentially cancel the dismounting process.\n * </p>\n *\n * @since 2.0.0\n */\npublic final class DismountModelEvent implements CancellableEvent {\n\n    private final EntityTracker tracker;\n    private final RenderedBone bone;\n    private final HitBox hitbox;\n    private final PlatformEntity entity;\n    @Getter\n    @Setter\n    private boolean cancelled;\n\n    /**\n     * Creates a new DismountModelEvent.\n     *\n     * @param tracker the entity tracker associated with the model\n     * @param bone the bone associated with the hitbox\n     * @param hitbox the hitbox being dismounted\n     * @param entity the entity dismounting\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public DismountModelEvent(@NotNull EntityTracker tracker, @NotNull RenderedBone bone, @NotNull HitBox hitbox, @NotNull PlatformEntity entity) {\n        this.tracker = tracker;\n        this.bone = bone;\n        this.hitbox = hitbox;\n        this.entity = entity;\n    }\n\n    /**\n     * Returns the entity tracker associated with the model.\n     *\n     * @return the entity tracker\n     * @since 2.0.0\n     */\n    public @NotNull EntityTracker tracker() {\n        return tracker;\n    }\n\n    /**\n     * Returns the bone associated with the hitbox.\n     *\n     * @return the rendered bone\n     * @since 2.0.0\n     */\n    public @NotNull RenderedBone bone() {\n        return bone;\n    }\n\n    /**\n     * Returns the hitbox being dismounted.\n     *\n     * @return the hitbox\n     * @since 2.0.0\n     */\n    public @NotNull HitBox hitbox() {\n        return hitbox;\n    }\n\n    /**\n     * Returns the entity dismounting the hitbox.\n     *\n     * @return the passenger entity\n     * @since 2.0.0\n     */\n    public PlatformEntity entity() {\n        return entity;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelAssetsEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.data.ModelAsset;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Set;\n\n/**\n * Triggered when model assets are being gathered.\n * <p>\n * This event allows plugins to register custom {@link ModelAsset}s to be loaded by the engine.\n * </p>\n *\n * @param type the renderer type\n * @param assets the set of assets to be loaded\n * @since 2.0.0\n */\npublic record ModelAssetsEvent(@NotNull ModelRenderer.Type type, @NotNull Set<ModelAsset> assets) implements ModelEvent {\n    /**\n     * Adds a new model asset to the loading queue.\n     *\n     * @param asset the asset to add\n     * @throws IllegalArgumentException if an asset with the same name already exists\n     * @since 2.0.0\n     */\n    public void addAsset(@NotNull ModelAsset asset) {\n        if (!assets.add(asset)) throw new IllegalArgumentException(\"Asset \" + asset.name() + \" already exists.\");\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelDamageSource.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents the source of damage inflicted on a model's hitbox.\n * <p>\n * This interface abstracts the platform-specific damage source details.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface ModelDamageSource {\n\n    /**\n     * Returns the entity that caused the damage (e.g., the shooter of an arrow).\n     *\n     * @return the causing entity, or null if none\n     * @see kr.toxicity.model.api.platform.PlatformLivingEntity\n     * @since 2.0.0\n     */\n    @Nullable PlatformEntity getCausingEntity();\n\n    /**\n     * Returns the entity that directly inflicted the damage (e.g., the arrow itself).\n     *\n     * @return the direct entity, or null if none\n     * @since 2.0.0\n     */\n    @Nullable PlatformEntity getDirectEntity();\n\n    /**\n     * Returns the location where the damage occurred.\n     *\n     * @return the damage location, or null if unknown\n     * @since 2.0.0\n     */\n    @Nullable PlatformLocation getDamageLocation();\n\n    /**\n     * Returns the location of the damage source.\n     *\n     * @return the source location, or null if unknown\n     * @since 2.0.0\n     */\n    @Nullable PlatformLocation getSourceLocation();\n\n    /**\n     * Checks if the damage was indirect (e.g., projectile).\n     *\n     * @return true if indirect, false otherwise\n     * @since 2.0.0\n     */\n    boolean isIndirect();\n\n    /**\n     * Returns the amount of food exhaustion caused by this damage.\n     *\n     * @return the food exhaustion\n     * @since 2.0.0\n     */\n    float getFoodExhaustion();\n\n    /**\n     * Checks if this damage should be scaled based on difficulty.\n     *\n     * @return true if scalable, false otherwise\n     * @since 2.0.0\n     */\n    boolean scalesWithDifficulty();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelDespawnAtPlayerEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a model tracker is despawned for a specific player.\n * <p>\n * This event notifies plugins/mods that a model is no longer visible to a player.\n * </p>\n *\n * @param player the player for whom the model is despawning\n * @param tracker the tracker being despawned\n * @since 2.0.0\n */\npublic record ModelDespawnAtPlayerEvent(\n    @NotNull PlatformPlayer player,\n    @NotNull Tracker tracker\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.BetterModel;\n\n/**\n * Represents a base event in the BetterModel system.\n * <p>\n * All events related to model lifecycle, interaction, and animation implement this interface.\n * Events can be dispatched using the {@link #call()} method.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface ModelEvent {\n\n    /**\n     * Dispatches this event to the global event bus.\n     *\n     * @return the event is successfully triggered\n     * @since 2.0.0\n     */\n    default boolean call() {\n        return BetterModel.eventBus().call(this).triggered();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelEventApplication.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\n/**\n * Represents an application or plugin that subscribes to model events.\n * <p>\n * This interface is used to check if the subscribing application is still enabled,\n * allowing the event bus to automatically unregister listeners from disabled plugins.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface ModelEventApplication {\n    /**\n     * Checks if the application is currently enabled.\n     *\n     * @return true if enabled, false otherwise\n     * @since 2.0.0\n     */\n    boolean isEnabled();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelEventListener.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\n/**\n * Represents a listener for model events.\n * <p>\n * This interface provides a mechanism to unregister the listener when it is no longer needed.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface ModelEventListener {\n\n    /**\n     * A no-op listener implementation.\n     * @since 2.0.0\n     */\n    ModelEventListener NONE = () -> {};\n\n    /**\n     * Unregisters this listener, stopping it from receiving further events.\n     *\n     * @since 2.0.0\n     */\n    void unregister();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelImportedEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\n\nimport kr.toxicity.model.api.data.blueprint.ModelBlueprint;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a model is successfully imported and registered.\n * <p>\n * This event provides access to the raw blueprint and the created renderer.\n * </p>\n *\n * @param blueprint the model blueprint\n * @param renderer the model renderer\n * @since 2.0.0\n */\npublic record ModelImportedEvent(\n    @NotNull ModelBlueprint blueprint,\n    @NotNull ModelRenderer renderer\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/ModelSpawnAtPlayerEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a model tracker is about to be spawned for a specific player.\n * <p>\n * This event allows preventing the model from spawning for that player.\n * </p>\n *\n * @since 2.0.0\n */\n@Getter\n@Setter\npublic final class ModelSpawnAtPlayerEvent implements CancellableEvent {\n\n    private final Tracker tracker;\n    private final PlatformPlayer player;\n    private boolean cancelled;\n\n    /**\n     * Creates a new ModelSpawnAtPlayerEvent.\n     *\n     * @param player the player for whom the model is spawning\n     * @param tracker the tracker being spawned\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public ModelSpawnAtPlayerEvent(@NotNull PlatformPlayer player, @NotNull Tracker tracker) {\n        this.tracker = tracker;\n        this.player = player;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/MountModelEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.tracker.EntityTracker;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when an entity mounts a model's hitbox.\n * <p>\n * This event allows plugins/mods to intercept and potentially cancel the mounting process.\n * </p>\n *\n * @since 2.0.0\n */\npublic final class MountModelEvent implements CancellableEvent {\n\n    private final EntityTracker tracker;\n    private final RenderedBone bone;\n    private final HitBox hitBox;\n    private final PlatformEntity entity;\n    @Getter\n    @Setter\n    private boolean cancelled;\n\n    /**\n     * Creates a new MountModelEvent.\n     *\n     * @param tracker the entity tracker associated with the model\n     * @param bone the bone associated with the hitbox\n     * @param hitBox the hitbox being mounted\n     * @param entity the entity attempting to mount\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public MountModelEvent(@NotNull EntityTracker tracker, @NotNull RenderedBone bone, @NotNull HitBox hitBox, @NotNull PlatformEntity entity) {\n        this.tracker = tracker;\n        this.bone = bone;\n        this.hitBox = hitBox;\n        this.entity = entity;\n    }\n\n    /**\n     * Returns the entity tracker associated with the model.\n     *\n     * @return the entity tracker\n     * @since 2.0.0\n     */\n    public @NotNull EntityTracker tracker() {\n        return tracker;\n    }\n\n    /**\n     * Returns the bone associated with the hitbox.\n     *\n     * @return the rendered bone\n     * @since 2.0.0\n     */\n    public @NotNull RenderedBone bone() {\n        return bone;\n    }\n\n    /**\n     * Returns the hitbox being mounted.\n     *\n     * @return the hitbox\n     * @since 2.0.0\n     */\n    public @NotNull HitBox hitbox() {\n        return hitBox;\n    }\n\n    /**\n     * Returns the entity attempting to mount the hitbox.\n     *\n     * @return the passenger entity\n     * @since 2.0.0\n     */\n    public PlatformEntity entity() {\n        return entity;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/PlayerHideTrackerEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a tracker is about to be hidden from a specific player.\n * <p>\n * This event allows preventing the tracker from being hidden.\n * </p>\n *\n * @since 2.0.0\n */\n@Getter\n@Setter\npublic final class PlayerHideTrackerEvent implements CancellableEvent {\n\n    private final Tracker tracker;\n    private final PlatformPlayer player;\n    private boolean cancelled;\n\n    /**\n     * Creates a new PlayerHideTrackerEvent.\n     *\n     * @param tracker the tracker being hidden\n     * @param player the player from whom the tracker is being hidden\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public PlayerHideTrackerEvent(@NotNull Tracker tracker, @NotNull PlatformPlayer player) {\n        this.tracker = tracker;\n        this.player = player;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/PlayerPerAnimationEndEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a per-player animation sequence ends.\n * <p>\n * This event signifies that a specific animation playing only for one player has finished.\n * </p>\n *\n * @param tracker the tracker playing the animation\n * @param player the player who viewed the animation\n * @since 2.0.0\n */\npublic record PlayerPerAnimationEndEvent(\n    @NotNull Tracker tracker,\n    @NotNull PlatformPlayer player\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/PlayerPerAnimationStartEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a per-player animation sequence starts.\n * <p>\n * This event signifies that a specific animation is beginning to play for only one player.\n * </p>\n *\n * @param tracker the tracker playing the animation\n * @param player the player viewing the animation\n * @since 2.0.0\n */\npublic record PlayerPerAnimationStartEvent(\n    @NotNull Tracker tracker,\n    @NotNull PlatformPlayer player\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/PlayerShowTrackerEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a tracker is about to be shown to a specific player.\n * <p>\n * This event allows preventing the tracker from being shown.\n * </p>\n *\n * @since 2.0.0\n */\n@Getter\n@Setter\npublic final class PlayerShowTrackerEvent implements CancellableEvent {\n\n    private final Tracker tracker;\n    private final PlatformPlayer player;\n\n    private boolean cancelled;\n\n    /**\n     * Creates a new PlayerShowTrackerEvent.\n     *\n     * @param tracker the tracker being shown\n     * @param player the player to whom the tracker is being shown\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public PlayerShowTrackerEvent(@NotNull Tracker tracker, @NotNull PlatformPlayer player) {\n        this.tracker = tracker;\n        this.player = player;\n    }\n\n    /**\n     * Returns the tracker being shown.\n     *\n     * @return the tracker\n     * @since 2.0.0\n     */\n    public @NotNull Tracker tracker() {\n        return tracker;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/PluginEndReloadEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.BetterModelPlatform;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when the BetterModel platform finishes reloading.\n * <p>\n * This event provides the result of the reload operation.\n * </p>\n *\n * @param result the result of the reload\n * @since 2.0.0\n */\npublic record PluginEndReloadEvent(\n    @NotNull BetterModelPlatform.ReloadResult result\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/PluginStartReloadEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.pack.PackZipper;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when the BetterModel platform starts reloading.\n * <p>\n * This event provides access to the {@link PackZipper}, allowing other plugins/mods to inject custom assets\n * into the resource pack before it is generated.\n * </p>\n *\n * @param zipper the pack zipper for adding assets\n * @since 2.0.0\n */\npublic record PluginStartReloadEvent(\n    @NotNull PackZipper zipper\n) implements ModelEvent {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/RemovePlayerSkinEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event;\n\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a player's skin data is about to be removed from the cache.\n * <p>\n * This event allows cancelling the removal to keep the skin data cached.\n * </p>\n *\n * @since 2.0.0\n */\n@Getter\n@Setter\npublic final class RemovePlayerSkinEvent implements CancellableEvent {\n    private final ModelProfile modelProfile;\n    private boolean cancelled;\n\n    /**\n     * Creates a new RemovePlayerSkinEvent.\n     *\n     * @param modelProfile the model profile being removed\n     * @since 2.0.0\n     */\n    public RemovePlayerSkinEvent(@NotNull ModelProfile modelProfile) {\n        this.modelProfile = modelProfile;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/hitbox/HitBoxCreateEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event.hitbox;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a hitbox is created.\n *\n * @param hitBox created hitbox\n * @since 2.1.0\n */\npublic record HitBoxCreateEvent(@NotNull HitBox hitBox) implements HitBoxEvent {\n\n    /**\n     * Returns the created hitbox.\n     *\n     * @return created hitbox\n     * @since 2.2.0\n     */\n    @Override\n    public @NotNull HitBox getHitBox() {\n        return hitBox;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/hitbox/HitBoxDamagedEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event.hitbox;\n\nimport kr.toxicity.model.api.event.CancellableEvent;\nimport kr.toxicity.model.api.event.ModelDamageSource;\nimport kr.toxicity.model.api.nms.HitBox;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a model's hitbox is damaged.\n * <p>\n * This event allows modifying the damage amount or cancelling the damage entirely.\n * </p>\n *\n * @since 2.0.0\n */\n@Getter\n@Setter\npublic final class HitBoxDamagedEvent implements CancellableEvent, HitBoxEvent {\n\n    private final @NotNull HitBox hitBox;\n    private final ModelDamageSource source;\n\n    private float damage;\n    private boolean cancelled;\n\n    /**\n     * Creates a new ModelDamagedEvent.\n     *\n     * @param hitBox the hitbox being damaged\n     * @param source the source of the damage\n     * @param damage the amount of damage\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public HitBoxDamagedEvent(@NotNull HitBox hitBox, @NotNull ModelDamageSource source, float damage) {\n        this.hitBox = hitBox;\n        this.source = source;\n        this.damage = damage;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/hitbox/HitBoxDismountEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event.hitbox;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * An event called when an entity dismounts from a hit box.\n *\n * @param hitBox the hit box\n * @param entity the entity\n * @since 2.1.0\n */\npublic record HitBoxDismountEvent(@NotNull HitBox hitBox, @NotNull PlatformEntity entity) implements HitBoxEvent {\n    @Override\n    public @NotNull HitBox getHitBox() {\n        return hitBox;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/hitbox/HitBoxEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event.hitbox;\n\nimport kr.toxicity.model.api.event.ModelEvent;\nimport kr.toxicity.model.api.nms.HitBox;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Base contract for events associated with a {@link HitBox}.\n *\n * @since 2.1.0\n */\npublic interface HitBoxEvent extends ModelEvent {\n\n    /**\n     * Returns the target hitbox of this event.\n     *\n     * @return target hitbox\n     * @since 2.1.0\n     */\n    @NotNull HitBox getHitBox();\n}\n\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/hitbox/HitBoxInteractAtEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event.hitbox;\n\nimport kr.toxicity.model.api.event.CancellableEvent;\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.nms.ModelInteractionHand;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\n/**\n * Triggered when a player interacts with a model's hitbox at some position.\n * <p>\n * This event corresponds to a right-click interaction.\n * </p>\n *\n * @since 2.0.0\n */\n@Getter\npublic final class HitBoxInteractAtEvent implements CancellableEvent, HitBoxEvent {\n\n    @Setter\n    private boolean cancelled;\n    private final PlatformPlayer who;\n    private final @NotNull HitBox hitBox;\n    private final @NotNull ModelInteractionHand hand;\n    private final @NotNull Vector3f position;\n\n    /**\n     * Creates a new HitBoxInteractAtEvent.\n     *\n     * @param who the player interacting\n     * @param hitBox the hitbox being interacted with\n     * @param hand the hand used for interaction\n     * @param position position\n     * @since 2.0.0\n     */\n    @ApiStatus.Internal\n    public HitBoxInteractAtEvent(@NotNull PlatformPlayer who, @NotNull HitBox hitBox, @NotNull ModelInteractionHand hand, @NotNull Vector3f position) {\n        this.who = who;\n        this.hitBox = hitBox;\n        this.hand = hand;\n        this.position = position;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/hitbox/HitBoxMountEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event.hitbox;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * An event called when an entity mounts to a hit box.\n *\n * @param hitBox the hit box\n * @param entity the entity\n * @since 2.1.0\n */\npublic record HitBoxMountEvent(@NotNull HitBox hitBox, @NotNull PlatformEntity entity) implements HitBoxEvent {\n    @Override\n    public @NotNull HitBox getHitBox() {\n        return hitBox;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/event/hitbox/HitBoxRemoveEvent.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.event.hitbox;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a hitbox is removed.\n *\n * @param hitBox removed hitbox\n * @since 2.1.0\n */\npublic record HitBoxRemoveEvent(@NotNull HitBox hitBox) implements HitBoxEvent {\n\n    /**\n     * Returns the removed hitbox.\n     *\n     * @return removed hitbox\n     * @since 2.1.0\n     */\n    @Override\n    public @NotNull HitBox getHitBox() {\n        return hitBox;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/manager/ModelManager.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.manager;\n\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.EntityTracker;\nimport kr.toxicity.model.api.tracker.TrackerModifier;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Collection;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * Manages all loaded models and provides access to them.\n * <p>\n * This manager is the primary entry point for retrieving {@link ModelRenderer} instances,\n * which are used to create and control model trackers.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface ModelManager {\n\n    /**\n     * Retrieves a model renderer by its name.\n     *\n     * @param name the name of the model\n     * @return the model renderer, or null if not found\n     * @since 1.15.2\n     */\n    @Nullable ModelRenderer model(@NotNull String name);\n\n\n    /**\n     * @deprecated Use {@link #model(String)} instead.\n     * @param name the name of the model\n     * @return the model renderer, or null if not found\n     * @since 1.15.2\n     */\n    @Deprecated\n    @Nullable\n    default ModelRenderer renderer(@NotNull String name) {\n        return model(name);\n    }\n\n    /**\n     * Returns a collection of all loaded model renderers.\n     *\n     * @return an unmodifiable collection of models\n     * @since 1.15.2\n     */\n    @NotNull @Unmodifiable\n    Collection<ModelRenderer> models();\n\n    /**\n     * Returns a set of all loaded model names.\n     *\n     * @return an unmodifiable set of model keys\n     * @since 1.15.2\n     */\n    @NotNull @Unmodifiable\n    Set<String> modelKeys();\n\n    /**\n     * Returns a collection of all renderers designated for player limb animations.\n     *\n     * @return an unmodifiable collection of limb models\n     * @since 1.15.2\n     */\n    @NotNull @Unmodifiable\n    Collection<ModelRenderer> limbs();\n\n    /**\n     * Retrieves a player limb renderer by its name.\n     *\n     * @param name the name of the limb model\n     * @return the limb renderer, or null if not found\n     * @since 1.15.2\n     */\n    @Nullable ModelRenderer limb(@NotNull String name);\n\n    /**\n     * Returns a set of all loaded player limb model names.\n     *\n     * @return an unmodifiable set of limb keys\n     * @since 1.15.2\n     */\n    @NotNull @Unmodifiable\n    Set<String> limbKeys();\n\n\n    /**\n     * Plays an animation on a player.\n     *\n     * @param player the target player\n     * @param model the name of the limb model\n     * @param animation the name of the animation\n     * @return true if the animation started successfully\n     * @since 1.15.2\n     */\n    default boolean animate(@NotNull PlatformPlayer player, @NotNull String model, @NotNull String animation) {\n        return animate(player, model, animation, AnimationModifier.DEFAULT_WITH_PLAY_ONCE);\n    }\n\n    /**\n     * Plays an animation on a player with a specific modifier.\n     *\n     * @param player the target player\n     * @param model the name of the limb model\n     * @param animation the name of the animation\n     * @param modifier the animation modifier\n     * @return true if the animation started successfully\n     * @since 1.15.2\n     */\n    default boolean animate(@NotNull PlatformPlayer player, @NotNull String model, @NotNull String animation, @NotNull AnimationModifier modifier) {\n        return animate(player, model, animation, modifier, _ -> {});\n    }\n\n    /**\n     * Plays an animation on a player with a modifier and a configuration consumer.\n     *\n     * @param player the target player\n     * @param model the name of the limb model\n     * @param animation the name of the animation\n     * @param modifier the animation modifier\n     * @param consumer a consumer to configure the created tracker\n     * @return true if the animation started successfully\n     * @since 1.15.2\n     */\n    default boolean animate(@NotNull PlatformPlayer player, @NotNull String model, @NotNull String animation, @NotNull AnimationModifier modifier, @NotNull Consumer<EntityTracker> consumer) {\n        var get = limb(model);\n        if (get == null) return false;\n        var create = get.getOrCreate(player, TrackerModifier.DEFAULT, consumer);\n        if (!create.animate(animation, modifier, create::close)) {\n            create.close();\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/manager/PlayerManager.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.manager;\n\nimport kr.toxicity.model.api.nms.PlayerChannelHandler;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Manages player-specific data and network channels.\n * <p>\n * This manager is responsible for injecting and retrieving {@link PlayerChannelHandler} instances,\n * which are essential for sending custom packets to players.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface PlayerManager {\n    /**\n     * Retrieves the channel handler for a player by their UUID.\n     *\n     * @param uuid the player's UUID\n     * @return the channel handler, or null if not found\n     * @since 1.15.2\n     */\n    @Nullable PlayerChannelHandler player(@NotNull UUID uuid);\n\n    /**\n     * Gets or creates the channel handler for a player.\n     * <p>\n     * Note: This should not be used with fake players. Use {@link #player(UUID)} instead for those cases.\n     * </p>\n     *\n     * @param player the player\n     * @return the channel handler\n     * @since 1.15.2\n     */\n    @NotNull PlayerChannelHandler player(@NotNull PlatformPlayer player);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/manager/ProfileManager.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.manager;\n\nimport kr.toxicity.model.api.profile.ModelProfileSkin;\nimport kr.toxicity.model.api.profile.ModelProfileSupplier;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Manages the resolution and creation of model profiles (skins).\n * <p>\n * This manager allows configuring the strategy for fetching profiles (e.g., from Mojang API, cache, or custom sources)\n * and creating skin instances from raw texture data.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface ProfileManager {\n\n    /**\n     * Returns the current profile supplier strategy.\n     *\n     * @return the profile supplier\n     * @since 1.15.2\n     */\n    @NotNull ModelProfileSupplier supplier();\n\n    /**\n     * Creates a {@link ModelProfileSkin} from a raw texture string (Base64 encoded).\n     *\n     * @param rawTextures the raw texture data\n     * @return the created skin profile\n     * @since 1.15.2\n     */\n    @NotNull ModelProfileSkin skin(@NotNull String rawTextures);\n\n    /**\n     * Sets the profile supplier strategy.\n     *\n     * @param supplier the new profile supplier\n     * @since 1.15.2\n     */\n    void supplier(@NotNull ModelProfileSupplier supplier);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/manager/ReloadInfo.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.manager;\n\nimport lombok.Builder;\nimport net.kyori.adventure.audience.Audience;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents the context for a platform reload operation.\n * <p>\n * This record holds information about who initiated the reload and whether certain parts of the reload should be skipped.\n * </p>\n *\n * @param skipConfig whether to skip reloading the main configuration file\n * @param sender the command sender who initiated the reload\n * @since 1.15.2\n */\n@Builder\npublic record ReloadInfo(boolean skipConfig, @NotNull Audience sender) {\n    /**\n     * The default reload info, representing a standard reload initiated from the console.\n     * @since 1.15.2\n     */\n    public static final ReloadInfo DEFAULT = ReloadInfo.builder()\n        .skipConfig(false)\n        .sender(Audience.empty())\n        .build();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/manager/ScriptManager.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.manager;\n\nimport kr.toxicity.model.api.script.AnimationScript;\nimport kr.toxicity.model.api.script.ScriptBuilder;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Manages the parsing and registration of animation scripts.\n * <p>\n * This manager allows for the creation of custom script logic that can be embedded within animations.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface ScriptManager {\n    /**\n     * Parses a raw script string into an {@link AnimationScript}.\n     *\n     * @param script the raw script string\n     * @return the parsed script, or null if parsing failed\n     * @since 1.15.2\n     */\n    @Nullable AnimationScript build(@NotNull String script);\n\n    /**\n     * Registers a new script builder.\n     *\n     * @param name the name of the parser/builder\n     * @param script the script builder instance\n     * @since 1.15.2\n     */\n    void addBuilder(@NotNull String name, @NotNull ScriptBuilder script);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/manager/SkinManager.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.manager;\n\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport kr.toxicity.model.api.skin.SkinData;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Manages the retrieval and caching of player skins.\n * <p>\n * This manager handles fetching skin data from various sources (e.g., Mojang, SkinsRestorer)\n * and provides a fallback skin when a profile cannot be resolved.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface SkinManager {\n\n    /**\n     * Returns the fallback skin data used when a skin cannot be resolved.\n     *\n     * @return the fallback skin data\n     * @since 1.15.2\n     */\n    @NotNull SkinData fallback();\n\n    /**\n     * Asynchronously completes an uncompleted model profile to retrieve its skin data.\n     *\n     * @param profile the uncompleted profile\n     * @return a future that completes with the skin data\n     * @since 1.15.2\n     */\n    @NotNull CompletableFuture<? extends SkinData> complete(@NotNull ModelProfile.Uncompleted profile);\n\n    /**\n     * Removes a specific profile from the skin cache.\n     *\n     * @param profile the profile to remove\n     * @since 1.15.2\n     */\n    void removeCache(@NotNull ModelProfile profile);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/mount/MountController.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mount;\n\nimport kr.toxicity.model.api.platform.PlatformLivingEntity;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\n/**\n * A mount controller of hit-box\n */\npublic interface MountController {\n\n    /**\n     * Moves entity by player's input\n     * @param player passenger\n     * @param entity target\n     * @param input input vector\n     * @param travelVector travel vector\n     * @return movement\n     */\n    @NotNull Vector3f move(@NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector);\n\n    /**\n     * Moves entity by player's input on the fly\n     * @param player passenger\n     * @param entity target\n     * @param input input vector\n     * @param travelVector travel vector\n     * @return movement\n     */\n    default @NotNull Vector3f moveOnFly(@NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector) {\n        return move(player, entity, input, travelVector).mul(1.5F);\n    }\n\n    /**\n     * Moves entity by player's input by type\n     * @param type type\n     * @param player passenger\n     * @param entity target\n     * @param input input vector\n     * @param travelVector travel vector\n     * @return movement\n     */\n    default Vector3f move(@NotNull MoveType type, @NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector) {\n        return switch (type) {\n            case DEFAULT -> move(player, entity, input, travelVector);\n            case FLY -> moveOnFly(player, entity, input, travelVector);\n        };\n    }\n\n    /**\n     * Checks some player can mount\n     * @return can mount\n     */\n    default boolean canMount() {\n        return true;\n    }\n\n    /**\n     * Checks some player can dismount by self (right-click or sneak)\n     * @return can dismount by self\n     */\n    default boolean canDismountBySelf() {\n        return true;\n\n    }\n\n    /**\n     * Checks some player can control\n     * @return can control\n     */\n    default boolean canControl() {\n        return true;\n    }\n\n    /**\n     * Checks some player can jump\n     * @return can jump\n     */\n    default boolean canJump() {\n        return true;\n    }\n\n    /**\n     * Checks some player can fly\n     * @return can fly\n     */\n    default boolean canFly() {\n        return false;\n    }\n\n    /**\n     * Checks can rider damage this entity\n     * @return can damage\n     */\n    default boolean canBeDamagedByRider() {\n        return false;\n    }\n\n    /**\n     * Movement type\n     */\n    enum MoveType {\n        /**\n         * On the ground\n         */\n        DEFAULT,\n        /**\n         * On fly\n         */\n        FLY\n    }\n\n    /**\n     * Creates modifier of this controller\n     * @return modifier\n     */\n    default @NotNull Modifier modifier() {\n        return new Modifier(this);\n    }\n\n    /**\n     * Modifier\n     */\n    class Modifier {\n\n        private final MountController source;\n\n        private boolean canMount;\n        private boolean canDismountBySelf;\n        private boolean canControl;\n        private boolean canJump;\n        private boolean canFly;\n        private boolean canBeDamagedByRider;\n\n        /**\n         * Modifier of a source\n         * @param controller source\n         */\n        private Modifier(@NotNull MountController controller) {\n            this.source = controller;\n            canMount = controller.canMount();\n            canDismountBySelf = controller.canDismountBySelf();\n            canControl = controller.canControl();\n            canJump = controller.canJump();\n            canFly = controller.canFly();\n        }\n\n        /**\n         * Sets some player can dismount by self (right-click or sneak)\n         * @param canDismountBySelf can dismount\n         * @return self\n         */\n        public @NotNull Modifier canDismountBySelf(boolean canDismountBySelf) {\n            this.canDismountBySelf = canDismountBySelf;\n            return this;\n        }\n\n        /**\n         * Sets some player can mount\n         * @param canMount can mount\n         * @return self\n         */\n        public @NotNull Modifier canMount(boolean canMount) {\n            this.canMount = canMount;\n            return this;\n        }\n\n        /**\n         * Sets some player can control\n         * @param canControl can control\n         * @return self\n         */\n        public @NotNull Modifier canControl(boolean canControl) {\n            this.canControl = canControl;\n            return this;\n        }\n\n        /**\n         * Sets some player can fly\n         * @param canFly can fly\n         * @return self\n         */\n        public @NotNull Modifier canFly(boolean canFly) {\n            this.canFly = canFly;\n            return this;\n        }\n\n        /**\n         * Sets some player can jump\n         * @param canJump can jump\n         * @return self\n         */\n        public @NotNull Modifier canJump(boolean canJump) {\n            this.canJump = canJump;\n            return this;\n        }\n\n        /**\n         * Sets can rider damage this entity\n         * @param canBeDamagedByRider can be damaged by rider\n         * @return self\n         */\n        public @NotNull Modifier canBeDamagedByRider(boolean canBeDamagedByRider) {\n            this.canBeDamagedByRider = canBeDamagedByRider;\n            return this;\n        }\n\n        /**\n         * Builds controller with modified value\n         * @return modified controller\n         */\n        public @NotNull MountController build() {\n            return new MountController() {\n                @NotNull\n                @Override\n                public Vector3f move(@NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector) {\n                    return source.move(player, entity, input, travelVector);\n                }\n\n                @Override\n                public boolean canDismountBySelf() {\n                    return canDismountBySelf;\n                }\n\n                @Override\n                public boolean canControl() {\n                    return canControl;\n                }\n\n                @Override\n                public boolean canFly() {\n                    return canFly;\n                }\n\n                @Override\n                public boolean canJump() {\n                    return canJump;\n                }\n\n                @Override\n                public boolean canMount() {\n                    return canMount;\n                }\n\n                @Override\n                public boolean canBeDamagedByRider() {\n                    return canBeDamagedByRider;\n                }\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/mount/MountControllers.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.mount;\n\nimport kr.toxicity.model.api.platform.PlatformLivingEntity;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\n/**\n * Builtin mount controllers\n */\npublic enum MountControllers implements MountController {\n    /**\n     * Invalid\n     */\n    INVALID {\n        @NotNull\n        @Override\n        public Vector3f move(@NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector) {\n            return new Vector3f();\n        }\n\n        @Override\n        public boolean canMount() {\n            return false;\n        }\n    },\n    /**\n     * None\n     */\n    NONE {\n        @NotNull\n        @Override\n        public Vector3f move(@NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector) {\n            return new Vector3f();\n        }\n\n        @Override\n        public boolean canControl() {\n            return false;\n        }\n    },\n    /**\n     * Walk\n     */\n    WALK {\n        @NotNull\n        @Override\n        public Vector3f move(@NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector) {\n            input.normalize();\n            input.y = 0;\n            input.x = input.x * 0.5F;\n            if (input.z <= 0.0F) {\n                input.z *= 0.25F;\n            }\n            return input;\n        }\n    },\n    /**\n     * Fly\n     */\n    FLY {\n        @NotNull\n        @Override\n        public Vector3f move(@NotNull PlatformPlayer player, @NotNull PlatformLivingEntity entity, @NotNull Vector3f input, @NotNull Vector3f travelVector) {\n            input.normalize();\n            input.x = input.x * 0.5F;\n            if (input.z <= 0.0F) {\n                input.z *= 0.25F;\n            }\n            return input;\n        }\n\n        @Override\n        public boolean canFly() {\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/AnimationBundler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * A record that bundles animation packets for both standard clients and modded clients.\n *\n * @since 2.2.1\n * @param standard the packet bundler for standard Minecraft clients\n * @param mod      the packet bundler for clients with the specific mod enabled\n */\npublic record AnimationBundler(\n    @NotNull PacketBundler standard,\n    @NotNull ModAnimationBundler mod\n) {\n\n    /**\n     * Checks if there are any animation packets to be sent.\n     *\n     * @since 2.2.1\n     * @return true if the standard packet bundler is not empty\n     */\n    public boolean isNotEmpty() {\n        return standard.isNotEmpty();\n    }\n\n    /**\n     * Sends the appropriate animation packets to the player based on their client type.\n     *\n     * @since 2.2.1\n     * @param handler the player's channel handler used to determine mod status and send packets\n     */\n    public void send(@NotNull PlayerChannelHandler handler) {\n        if (handler.isModEnabled()) mod.send(handler.player());\n        else standard.send(handler.player());\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/DisplayTransformer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\n/**\n * Handles the transformation (position, scale, rotation) of a display entity.\n * <p>\n * This interface abstracts the interpolation logic for smooth animations.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface DisplayTransformer {\n\n    /**\n     * Applies a transformation to the display.\n     *\n     * @param duration the interpolation duration in ticks\n     * @param position the target position\n     * @param scale the target scale\n     * @param rotation the target rotation\n     * @param bundler the packet bundler to use\n     * @since 1.15.2\n     */\n    void transform(int duration, @NotNull Vector3f position, @NotNull Vector3f scale, @NotNull Quaternionf rotation, @NotNull AnimationBundler bundler);\n\n    /**\n     * Sends the current transformation state to clients.\n     *\n     * @param bundler the packet bundler to use\n     * @since 1.15.2\n     */\n    void sendTransformation(@NotNull PacketBundler bundler);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/HitBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.mount.MountController;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\nimport java.util.Optional;\nimport java.util.function.Function;\n\n/**\n * Represents a hitbox for a model part, allowing for interaction and collision detection.\n * <p>\n * Hitboxes are often implemented using invisible entities (like Slimes or Interaction entities)\n * and are linked to specific bones in the model.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface HitBox extends Identifiable {\n\n    /**\n     * Hides this hitbox from a specific player.\n     *\n     * @param player the target player\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    void hide(@NotNull PlatformPlayer player);\n\n    /**\n     * Shows this hitbox to a specific player.\n     *\n     * @param player the target player\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    void show(@NotNull PlatformPlayer player);\n\n    /**\n     * Returns the name of the bone group associated with this hitbox.\n     *\n     * @return the group name\n     * @since 1.15.2\n     */\n    default @NotNull BoneName groupName() {\n        return positionSource().name();\n    }\n\n    /**\n     * Returns the mount controller for this hitbox.\n     *\n     * @return the mount controller\n     * @since 1.15.2\n     */\n    @NotNull MountController mountController();\n\n    /**\n     * Sets the mount controller for this hitbox.\n     *\n     * @param controller the new mount controller\n     * @since 1.15.2\n     */\n    void mountController(@NotNull MountController controller);\n\n    /**\n     * Checks if the passenger of this hitbox is walking.\n     *\n     * @return true if walking, false otherwise\n     * @since 1.15.2\n     */\n    boolean onWalk();\n\n    /**\n     * Returns the source entity of this hitbox.\n     *\n     * @return the source entity\n     * @since 1.15.2\n     */\n    @NotNull PlatformEntity source();\n\n    /**\n     * Mounts an entity onto this hitbox.\n     *\n     * @param entity the entity to mount\n     * @since 1.15.2\n     */\n    void mount(@NotNull PlatformEntity entity);\n\n    /**\n     * Checks if this hitbox has a mount driver.\n     *\n     * @return true if it has a driver, false otherwise\n     * @since 1.15.2\n     */\n    boolean hasMountDriver();\n\n    /**\n     * Checks if this hitbox is being controlled by another entity.\n     *\n     * @return true if controlled, false otherwise\n     * @since 1.15.2\n     */\n    default boolean hasBeenControlled() {\n        return mountController().canControl() && hasMountDriver();\n    }\n\n    /**\n     * Dismounts an entity from this hitbox.\n     *\n     * @param entity the entity to dismount\n     * @since 1.15.2\n     */\n    void dismount(@NotNull PlatformEntity entity);\n\n    /**\n     * Dismounts all passengers from this hitbox.\n     *\n     * @since 1.15.2\n     */\n    void dismountAll();\n\n    /**\n     * Checks if a dismount operation is forced.\n     *\n     * @return true if forced, false otherwise\n     * @since 1.15.2\n     */\n    boolean forceDismount();\n\n    /**\n     * Returns the relative position of this hitbox to its source entity.\n     *\n     * @return the relative position\n     * @since 1.15.2\n     */\n    @NotNull Vector3f relativePosition();\n\n    /**\n     * Removes this hitbox safely.\n     *\n     * @since 1.15.2\n     */\n    void removeHitBox();\n\n    /**\n     * Returns the listener associated with this hitbox.\n     *\n     * @return the listener\n     * @since 1.15.2\n     */\n    @NotNull HitBoxListener listener();\n\n    /**\n     * Sets the listener for this hitbox.\n     *\n     * @param listener the new listener\n     * @since 3.0.2\n     */\n    @ApiStatus.Internal\n    void listener(@NotNull HitBoxListener listener);\n\n    /**\n     * Updates the listener for this hitbox using a builder function.\n     * <p>\n     * This method retrieves the current listener, converts it to a builder,\n     * applies the provided function, and sets the resulting listener.\n     * </p>\n     *\n     * @param function the function to apply to the builder\n     * @since 3.0.2\n     */\n    default void listener(@NotNull Function<HitBoxListener.Builder, HitBoxListener.Builder> function) {\n        listener(function.apply(listener().toBuilder()).build());\n    }\n\n    /**\n     * Returns the rendered bone that acts as the position source for this hitbox.\n     *\n     * @return the position source bone\n     * @since 1.15.2\n     */\n    @NotNull RenderedBone positionSource();\n\n    /**\n     * Returns the entity tracker registry for this hitbox's source entity.\n     *\n     * @return an optional containing the registry, or empty if not found\n     * @since 1.15.2\n     */\n    default @NotNull Optional<EntityTrackerRegistry> registry() {\n        return BetterModel.registry(source().uuid());\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/HitBoxListener.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport com.google.common.collect.ImmutableMap;\nimport it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;\nimport kr.toxicity.model.api.event.hitbox.*;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.newAddressingMap;\n\n/**\n * Listens for events related to a {@link HitBox}, such as damage, interaction, and mounting.\n * <p>\n * This interface allows for custom behavior when a hitbox is interacted with.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface HitBoxListener {\n\n    /**\n     * An empty listener that does nothing.\n     * @since 1.15.2\n     */\n    HitBoxListener EMPTY = builder().build();\n\n    /**\n     * Creates a new builder for {@link HitBoxListener}.\n     *\n     * @return a new builder\n     * @since 1.15.2\n     */\n    static @NotNull Builder builder() {\n        return new Builder(newAddressingMap(), null);\n    }\n\n    /**\n     * Builder for {@link HitBoxListener}.\n     *\n     * @since 1.15.2\n     */\n    final class Builder {\n\n        private final Map<Class<? extends HitBoxEvent>, Consumer<?>> listeners;\n        private Consumer<HitBox> syncConsumer;\n\n        /**\n         * Private initializer.\n         */\n        private Builder(\n            @NotNull Map<Class<? extends HitBoxEvent>, Consumer<?>> listeners,\n            @Nullable Consumer<HitBox> syncConsumer\n        ) {\n            this.listeners = listeners;\n            this.syncConsumer = syncConsumer;\n        }\n\n        /**\n         * Adds a handler for the specified hitbox event class.\n         *\n         * @param eventClass event class\n         * @param consumer event consumer\n         * @param <T> event type\n         * @return this builder\n         * @since 2.1.0\n         */\n        @SuppressWarnings(\"unchecked\")\n        public <T extends HitBoxEvent> @NotNull Builder listen(@NotNull Class<T> eventClass, @NotNull Consumer<T> consumer) {\n            listeners.compute(eventClass, (_, old) -> old == null ? consumer : ((Consumer<T>) old).andThen(consumer));\n            return this;\n        }\n\n        /**\n         * Adds a sync handler.\n         *\n         * @param sync the sync consumer\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder sync(@NotNull Consumer<HitBox> sync) {\n            var previous = syncConsumer;\n            syncConsumer = previous != null ? previous.andThen(sync) : sync;\n            return this;\n        }\n\n        /**\n         * Adds a damage handler.\n         *\n         * @param damage the damage handler\n         * @return this builder\n         * @since 2.1.0\n         */\n        public @NotNull Builder damage(@NotNull Consumer<HitBoxDamagedEvent> damage) {\n            return listen(HitBoxDamagedEvent.class, damage);\n        }\n\n        /**\n         * Adds an interact-at handler.\n         *\n         * @param interactAt the interact-at handler\n         * @return this builder\n         * @since 2.1.0\n         */\n        public @NotNull Builder interactAt(@NotNull Consumer<HitBoxInteractAtEvent> interactAt) {\n            return listen(HitBoxInteractAtEvent.class, interactAt);\n        }\n\n        /**\n         * Adds a remove handler.\n         *\n         * @param remove the remove consumer\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder remove(@NotNull Consumer<HitBox> remove) {\n            return listen(HitBoxRemoveEvent.class, event -> remove.accept(event.getHitBox()));\n        }\n\n        /**\n         * Adds a creation handler.\n         *\n         * @param create the creation consumer\n         * @return this builder\n         * @since 2.2.0\n         */\n        public @NotNull Builder create(@NotNull Consumer<HitBox> create) {\n            return listen(HitBoxCreateEvent.class, event -> create.accept(event.getHitBox()));\n        }\n\n        /**\n         * Adds a mount handler.\n         *\n         * @param mount the mount consumer\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder mount(@NotNull BiConsumer<HitBox, PlatformEntity> mount) {\n            return listen(HitBoxMountEvent.class, event -> mount.accept(event.getHitBox(), event.entity()));\n        }\n\n        /**\n         * Adds a dismount handler.\n         *\n         * @param dismount the dismount consumer\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder dismount(@NotNull BiConsumer<HitBox, PlatformEntity> dismount) {\n            return listen(HitBoxDismountEvent.class, event -> dismount.accept(event.getHitBox(), event.entity()));\n        }\n\n        /**\n         * Builds the listener.\n         *\n         * @return the created listener\n         * @since 1.15.2\n         */\n        @SuppressWarnings(\"unchecked\")\n        public @NotNull HitBoxListener build() {\n            var copied = ImmutableMap.copyOf(listeners);\n            var sync = syncConsumer;\n            return new HitBoxListener() {\n                @Override\n                @SuppressWarnings(\"unchecked\")\n                public boolean handle(@NotNull HitBoxEvent event) {\n                    var consumer = (Consumer<HitBoxEvent>) copied.get(event.getClass());\n                    if (consumer != null) {\n                        consumer.accept(event);\n                    }\n                    return event.call();\n                }\n\n                @Override\n                public void sync(@NotNull HitBox hitBox) {\n                    if (sync != null) sync.accept(hitBox);\n                }\n\n                @Override\n                public @NotNull Builder toBuilder() {\n                    return new Builder(new Object2ObjectOpenHashMap<>(copied), sync);\n                }\n            };\n        }\n    }\n\n    /**\n     * Handles a hitbox event.\n     *\n     * @param event target event\n     * @return whether target event is triggered\n     * @since 2.1.0\n     */\n    @ApiStatus.Internal\n    boolean handle(@NotNull HitBoxEvent event);\n\n    /**\n     * Handles tick method.\n     *\n     * @param hitBox target hitbox\n     * @since 2.1.0\n     */\n    @ApiStatus.Internal\n    void sync(@NotNull HitBox hitBox);\n\n    /**\n     * Creates a builder initialized with this listener's current handlers.\n     *\n     * @return a new builder\n     * @since 1.15.2\n     */\n    @NotNull Builder toBuilder();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/Identifiable.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.UUID;\n\n/**\n * Represents an object that can be identified by a unique ID and a UUID.\n * <p>\n * This is commonly used for entities and other tracked objects in the game.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface Identifiable {\n\n    /**\n     * Returns the integer ID of the object (e.g., entity ID).\n     *\n     * @return the ID\n     * @since 1.15.2\n     */\n    int id();\n\n    /**\n     * Returns the UUID of the object.\n     *\n     * @return the UUID\n     * @since 1.15.2\n     */\n    @NotNull UUID uuid();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/ModAnimationBundler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * A bundler that sends animation data to a player.\n * @since 2.2.1\n */\npublic interface ModAnimationBundler {\n    /**\n     * Sends the bundled animation data to the specified player.\n     *\n     * @since 2.2.1\n     * @param player the player to receive the animation\n     */\n    void send(@NotNull PlatformPlayer player);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/ModelDisplay.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.platform.PlatformBillboard;\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport kr.toxicity.model.api.platform.PlatformItemTransform;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.tracker.ModelRotation;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents an item display entity used for rendering model parts.\n * <p>\n * This interface abstracts the NMS implementation of item displays, allowing for manipulation of\n * position, rotation, scale, and other display properties.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface ModelDisplay extends Identifiable {\n\n    /**\n     * Checks if this display is currently invisible.\n     *\n     * @return true if invisible, false otherwise\n     * @since 1.15.2\n     */\n    boolean invisible();\n\n    /**\n     * Sets the visibility of this display.\n     *\n     * @param invisible true to make invisible, false to make visible\n     * @since 1.15.2\n     */\n    void invisible(boolean invisible);\n\n    /**\n     * Rotates this display to a specific orientation.\n     *\n     * @param rotation the target rotation\n     * @param bundler the packet bundler to use\n     * @since 1.15.2\n     */\n    void rotate(@NotNull ModelRotation rotation, @NotNull PacketBundler bundler);\n\n    /**\n     * Synchronizes the potion effect (glowing, etc.) from the base entity to this display.\n     *\n     * @param entity the source entity\n     * @since 2.2.0\n     */\n    void syncPotionEffect(@NotNull BaseEntity entity);\n\n    /**\n     * Synchronizes this display's position with a location.\n     *\n     * @param location the target location\n     * @since 1.15.2\n     */\n    void syncPosition(@NotNull PlatformLocation location);\n\n    /**\n     * Sets the duration for position/rotation interpolation.\n     *\n     * @param duration the duration in ticks\n     * @since 1.15.2\n     */\n    void moveDuration(int duration);\n\n    /**\n     * Sets the item display transform type.\n     *\n     * @param transform the transform type\n     * @since 1.15.2\n     */\n    void display(@NotNull PlatformItemTransform transform);\n\n    /**\n     * Spawns this display using the provided packet bundler.\n     *\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    default void spawn(@NotNull PacketBundler bundler) {\n        spawn(!invisible(), bundler);\n    }\n\n    /**\n     * Spawns this display and sends initial entity data.\n     *\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    default void spawnWithEntityData(@NotNull PacketBundler bundler) {\n        var visible = !invisible();\n        spawn(visible, bundler);\n        sendEntityData(visible, bundler);\n    }\n\n    /**\n     * Spawns this display, optionally showing the item.\n     *\n     * @param showItem true to show the item, false otherwise\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    void spawn(boolean showItem, @NotNull PacketBundler bundler);\n\n    /**\n     * Removes this display.\n     *\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    void remove(@NotNull PacketBundler bundler);\n\n    /**\n     * Teleports this display to a new location.\n     *\n     * @param location the target location\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    void teleport(@NotNull PlatformLocation location, @NotNull PacketBundler bundler);\n\n    /**\n     * Sets the item stack to be displayed.\n     *\n     * @param itemStack the item stack\n     * @since 1.15.2\n     */\n    void item(@NotNull PlatformItemStack itemStack);\n\n    /**\n     * Creates a transformer for animating this display.\n     *\n     * @return the display transformer\n     * @since 1.15.2\n     */\n    @NotNull DisplayTransformer createTransformer();\n\n    /**\n     * Sends updated entity data if it has changed.\n     *\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    void sendDirtyEntityData(@NotNull PacketBundler bundler);\n\n    /**\n     * Sends entity data, optionally showing the item.\n     *\n     * @param showItem true to show the item, false otherwise\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    void sendEntityData(boolean showItem, @NotNull PacketBundler bundler);\n\n    /**\n     * Sets the brightness override for the display.\n     *\n     * @param block the block light level\n     * @param sky the skylight level\n     * @since 1.15.2\n     */\n    void brightness(int block, int sky);\n\n    /**\n     * Sets the view range of the display.\n     *\n     * @param range the view range\n     * @since 1.15.2\n     */\n    void viewRange(float range);\n\n    /**\n     * Sets the shadow radius of the display.\n     *\n     * @param radius the shadow radius\n     * @since 1.15.2\n     */\n    void shadowRadius(float radius);\n\n    /**\n     * Sends the position of the display relative to an entity adapter.\n     *\n     * @param adapter the entity adapter\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    void sendPosition(@NotNull BaseEntity adapter, @NotNull PacketBundler bundler);\n\n    /**\n     * Toggles the glowing effect.\n     *\n     * @param glow true to enable glow, false to disable\n     * @since 1.15.2\n     */\n    void glow(boolean glow);\n\n    /**\n     * Sets the glow color.\n     *\n     * @param glowColor the RGB glow color\n     * @since 1.15.2\n     */\n    void glowColor(int glowColor);\n\n    /**\n     * Sets the billboard constraint for the display.\n     *\n     * @param billboard the billboard type\n     * @since 1.15.2\n     */\n    void billboard(@NotNull PlatformBillboard billboard);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/ModelInteractionHand.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\n/**\n * Represents the hand used during an interaction with a model hitbox.\n *\n * @since 1.15.2\n */\npublic enum ModelInteractionHand {\n    /**\n     * The main hand (usually right).\n     * @since 1.15.2\n     */\n    LEFT,\n    /**\n     * The off-hand (usually left).\n     * @since 1.15.2\n     */\n    RIGHT\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/ModelNametag.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport net.kyori.adventure.text.Component;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents a nametag associated with a model part.\n * <p>\n * Nametags are typically implemented as invisible armor stands or text displays\n * that float above a specific bone.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface ModelNametag {\n\n    /**\n     * Sets whether the nametag should always be visible (even through blocks).\n     *\n     * @param alwaysVisible true for always visible, false otherwise\n     * @since 1.15.2\n     */\n    void alwaysVisible(boolean alwaysVisible);\n\n    /**\n     * Sets the text component of the nametag.\n     *\n     * @param component the text component, or null to clear\n     * @since 1.15.2\n     */\n    void component(@Nullable Component component);\n\n    /**\n     * Teleports the nametag to a new location.\n     *\n     * @param location the target location\n     * @since 1.15.2\n     */\n    void teleport(@NotNull PlatformLocation location);\n\n    /**\n     * Sends the nametag packet to a specific player.\n     *\n     * @param player the target player\n     * @since 1.15.2\n     */\n    void send(@NotNull PlatformPlayer player);\n\n    /**\n     * Removes the nametag.\n     *\n     * @param bundler the packet bundler to use\n     * @since 1.15.2\n     */\n    void remove(@NotNull PacketBundler bundler);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/NMS.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.entity.BasePlayer;\nimport kr.toxicity.model.api.mount.MountController;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.function.BooleanSupplier;\nimport java.util.function.Consumer;\n\n/**\n * Handles direct interactions with Minecraft's internal server code (NMS).\n * <p>\n * This interface provides methods for creating displays, managing packets, handling hitboxes,\n * and adapting entities for different server environments (e.g., Folia).\n * </p>\n *\n * @since 1.15.2\n */\npublic interface NMS {\n\n    /**\n     * Creates a model display at the specified location.\n     *\n     * @param location the starting location\n     * @return the created model display\n     * @since 1.15.2\n     */\n    default @NotNull ModelDisplay create(@NotNull PlatformLocation location) {\n        return create(location, 0, _ -> {});\n    }\n\n    /**\n     * Creates a model display at the specified location with an initial configuration.\n     *\n     * @param location the starting location\n     * @param initialConsumer a consumer to configure the display upon creation\n     * @return the created model display\n     * @since 1.15.2\n     */\n    default @NotNull ModelDisplay create(@NotNull PlatformLocation location, @NotNull Consumer<ModelDisplay> initialConsumer) {\n        return create(location, 0, initialConsumer);\n    }\n\n    /**\n     * Creates a model display at the specified location with a Y-offset and initial configuration.\n     *\n     * @param location the starting location\n     * @param yOffset the vertical offset\n     * @param initialConsumer a consumer to configure the display upon creation\n     * @return the created model display\n     * @since 1.15.2\n     */\n    @NotNull ModelDisplay create(@NotNull PlatformLocation location, double yOffset, @NotNull Consumer<ModelDisplay> initialConsumer);\n\n    /**\n     * Creates a nametag for a rendered bone.\n     *\n     * @param bone the bone to attach the nametag to\n     * @return the created nametag\n     * @since 1.15.2\n     */\n    @NotNull ModelNametag createNametag(@NotNull RenderedBone bone);\n\n    /**\n     * Creates a nametag for a rendered bone with configuration.\n     *\n     * @param bone the bone to attach the nametag to\n     * @param consumer a consumer to configure the nametag\n     * @return the created nametag\n     * @since 1.15.2\n     */\n    default @NotNull ModelNametag createNametag(@NotNull RenderedBone bone, @NotNull Consumer<ModelNametag> consumer) {\n        var created = createNametag(bone);\n        consumer.accept(created);\n        return created;\n    }\n\n    /**\n     * Injects a Netty channel handler into a player's connection.\n     *\n     * @param player the player to inject\n     * @return the created channel handler\n     * @since 1.15.2\n     */\n    @NotNull PlayerChannelHandler inject(@NotNull PlatformPlayer player);\n\n    /**\n     * Creates a packet bundler with an initial capacity.\n     *\n     * @param initialCapacity the initial capacity\n     * @return the packet bundler\n     * @since 1.15.2\n     */\n    @NotNull PacketBundler createBundler(int initialCapacity);\n\n    /**\n     * Creates a parallel packet bundler with a size threshold.\n     *\n     * @param threshold the size threshold for parallel processing\n     * @return the packet bundler\n     * @since 1.15.2\n     */\n    @NotNull PacketBundler createParallelBundler(int threshold);\n\n    /**\n     * Creates a mod animation bundler.\n     *\n     * @param initialCapacity the initial capacity\n     * @return mod animation bundler.\n     * @since 2.2.1\n     */\n    @NotNull ModAnimationBundler createModAnimationBuilder(int initialCapacity);\n\n    /**\n     * Applies a tint color to an item stack.\n     *\n     * @param itemStack the item to tint\n     * @param rgb the RGB color value\n     * @return the tinted item stack\n     * @since 1.15.2\n     */\n    @NotNull PlatformItemStack tint(@NotNull PlatformItemStack itemStack, int rgb);\n\n    /**\n     * Adds a mount packet for an entity tracker to a bundler.\n     *\n     * @param registry the entity tracker registry\n     * @param bundler the packet bundler\n     * @since 1.15.2\n     */\n    void mount(@NotNull EntityTrackerRegistry registry, @NotNull PacketBundler bundler);\n\n    /**\n     * Sends a hide packet for an entity to a player via their channel handler.\n     *\n     * @param channel the player's channel handler\n     * @param registry the entity tracker registry\n     * @since 1.15.2\n     */\n    void hide(@NotNull PlayerChannelHandler channel, @NotNull EntityTrackerRegistry registry);\n\n    /**\n     * Sends a hide packet for an entity to a player if a condition is met.\n     * <p>\n     * For players, the hide operation is delayed based on configuration.\n     * </p>\n     *\n     * @param channel the player's channel handler\n     * @param registry the entity tracker registry\n     * @param condition the condition to check\n     * @since 1.15.2\n     */\n    default void hide(@NotNull PlayerChannelHandler channel, @NotNull EntityTrackerRegistry registry, @NotNull BooleanSupplier condition) {\n        if (registry.entity() instanceof BasePlayer) {\n            var plugin = BetterModel.platform();\n            plugin.scheduler().asyncTaskLater(plugin.config().playerHideDelay(), () -> {\n                if (condition.getAsBoolean()) hide(channel, registry);\n            });\n        } else hide(channel, registry);\n    }\n\n    /**\n     * Creates a delegate hitbox for a target entity.\n     *\n     * @param entity the target entity\n     * @param bone the bone associated with the hitbox\n     * @param boundingBox the bounding box definition\n     * @param controller the mount controller\n     * @param listener the hitbox listener\n     * @return the created hitbox, or null if creation failed\n     * @since 1.15.2\n     */\n    @Nullable HitBox createHitBox(@NotNull BaseEntity entity, @NotNull RenderedBone bone, @NotNull ModelBoundingBox boundingBox, @NotNull MountController controller, @NotNull HitBoxListener listener);\n\n    /**\n     * Returns the NMS version of the server.\n     *\n     * @return the version\n     * @since 1.15.2\n     */\n    @NotNull NMSVersion version();\n\n    /**\n     * Adapts a Bukkit entity to a {@link BaseEntity}, handling Folia compatibility.\n     *\n     * @param entity the Bukkit entity\n     * @return the adapted entity\n     * @since 1.15.2\n     */\n    @NotNull BaseEntity adapt(@NotNull PlatformEntity entity);\n\n    /**\n     * Adapts a Bukkit player to a {@link BasePlayer}, handling Folia compatibility.\n     *\n     * @param player the Bukkit player\n     * @return the adapted player\n     * @since 1.15.2\n     */\n    @NotNull BasePlayer adapt(@NotNull PlatformPlayer player);\n\n    /**\n     * Retrieves the model profile (skin) for a player.\n     *\n     * @param player the player\n     * @return the model profile\n     * @since 1.15.2\n     */\n    @NotNull ModelProfile profile(@NotNull PlatformPlayer player);\n\n    /**\n     * Creates a custom skin item stack.\n     *\n     * @param model the model name\n     * @param floats a list of floats\n     * @param flags a list of flags\n     * @param strings a list of strings\n     * @param colors a list of colors\n     * @return the transformed item stack\n     * @since 1.15.2\n     */\n    default @NotNull TransformedItemStack createSkinItem(@NotNull String model, @NotNull List<Float> floats, @NotNull List<Boolean> flags, @NotNull List<String> strings, @NotNull List<Integer> colors) {\n        return TransformedItemStack.empty();\n    }\n\n    /**\n     * Checks if the server is in online mode (either natively or via proxy).\n     *\n     * @return true if online mode, false otherwise\n     * @since 1.15.2\n     */\n    boolean isProxyOnlineMode();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/NMSVersion.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Enumerates supported Minecraft server versions and their associated metadata.\n * <p>\n * This enum maps internal version identifiers to Minecraft versions and resource pack formats.\n * </p>\n *\n * @since 1.15.2\n */\n@RequiredArgsConstructor\n@Getter\npublic enum NMSVersion {\n    /**\n     * Minecraft 1.21.4\n     * @since 1.15.2\n     */\n    V1_21_R3(46),\n    /**\n     * Minecraft 1.21.5\n     * @since 1.15.2\n     */\n    V1_21_R4(55),\n    /**\n     * Minecraft 1.21.6 - 1.21.8\n     * @since 1.15.2\n     */\n    V1_21_R5(64),\n    /**\n     * Minecraft 1.21.9 - 1.21.10\n     * @since 1.15.2\n     */\n    V1_21_R6(69),\n    /**\n     * Minecraft 1.21.11\n     * @since 1.15.2\n     */\n    V1_21_R7(75),\n    /**\n     * Minecraft 26.1.x\n     * @since 3.0.0\n     */\n    V26_R1(84)\n    ;\n    /**\n     * The resource pack format version (pack.mcmeta).\n     */\n    private final int metaVersion;\n\n    /**\n     * Returns the oldest supported version.\n     *\n     * @return the first version enum\n     * @since 1.15.2\n     */\n    public static @NotNull NMSVersion first() {\n        return values()[0];\n    }\n\n    /**\n     * Returns the latest supported version.\n     *\n     * @return the last version enum\n     * @since 1.15.2\n     */\n    public static @NotNull NMSVersion latest() {\n        var entries = values();\n        return entries[entries.length - 1];\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/PacketBundler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Collects multiple packets to be sent together to a player.\n * <p>\n * This helps optimize network traffic by grouping related updates (e.g., bone movements)\n * into a single bundle or batch.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface PacketBundler {\n\n    /**\n     * Checks if the bundler contains no packets.\n     *\n     * @return true if empty, false otherwise\n     * @since 1.15.2\n     */\n    boolean isEmpty();\n\n    /**\n     * Checks if the bundler contains at least one packet.\n     *\n     * @return true if not empty, false otherwise\n     * @since 1.15.2\n     */\n    default boolean isNotEmpty() {\n        return !isEmpty();\n    }\n\n    /**\n     * Returns the number of packets in the bundler.\n     *\n     * @return the packet count\n     * @since 1.15.2\n     */\n    int size();\n\n    /**\n     * Sends all collected packets to the specified player.\n     *\n     * @param player the target player\n     * @since 1.15.2\n     */\n    default void send(@NotNull PlatformPlayer player) {\n        send(player, () -> {});\n    }\n\n    /**\n     * Sends all collected packets to the specified player and executes a callback on success.\n     *\n     * @param player the target player\n     * @param onSuccess the callback to run after sending\n     * @since 1.15.2\n     */\n    void send(@NotNull PlatformPlayer player, @NotNull Runnable onSuccess);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/PlayerChannelHandler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.entity.BasePlayer;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.UUID;\n\n/**\n * Manages the network channel for a player, allowing for packet interception and injection.\n * <p>\n * This is crucial for handling custom packets and entity tracking.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface PlayerChannelHandler extends Identifiable, AutoCloseable {\n\n    /**\n     * Returns the Bukkit player associated with this handler.\n     *\n     * @return the player\n     * @since 1.15.2\n     */\n    default @NotNull PlatformPlayer player() {\n        return base().platform();\n    }\n\n    @Override\n    default @NotNull UUID uuid() {\n        return base().uuid();\n    }\n\n    @Override\n    default int id() {\n        return base().id();\n    }\n\n    /**\n     * Returns the base player adapter.\n     *\n     * @return the base player\n     * @since 1.15.2\n     */\n    @NotNull BasePlayer base();\n\n    /**\n     * Sends the correct entity data for a specific tracker to the player.\n     *\n     * @param registry the entity tracker registry\n     * @since 1.15.2\n     */\n    void sendEntityData(@NotNull EntityTrackerRegistry registry);\n\n    /**\n     * Closes the channel handler, cleaning up resources.\n     *\n     * @since 1.15.2\n     */\n    @Override\n    void close();\n\n    /**\n     * Checks if the meg-mod is enabled.\n     * @return meg-mod is enabled.\n     */\n    boolean isModEnabled();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/nms/Profiled.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.nms;\n\nimport kr.toxicity.model.api.armor.PlayerArmor;\nimport kr.toxicity.model.api.player.PlayerSkinParts;\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents an entity that has a player profile, armor, and skin customization settings.\n * <p>\n * This interface is typically implemented by player-like entities or trackers that need to render player skins and equipment.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface Profiled {\n\n    /**\n     * Returns the model profile (skin) of the entity.\n     *\n     * @return the model profile\n     * @since 1.15.2\n     */\n    @NotNull ModelProfile profile();\n\n    /**\n     * Returns the armor equipment of the entity.\n     *\n     * @return the player armor\n     * @since 1.15.2\n     */\n    @NotNull PlayerArmor armors();\n\n    /**\n     * Returns the skin customization parts (e.g., jacket, hat) of the entity.\n     *\n     * @return the skin parts\n     * @since 1.15.2\n     */\n    @NotNull PlayerSkinParts skinParts();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackAssets.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport kr.toxicity.model.api.BetterModel;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Supplier;\n\n/**\n * Manages assets within a specific pack overlay.\n * <p>\n * This class provides access to namespaces (like 'bettermodel' and 'minecraft') and allows adding resources to the pack.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class PackAssets {\n    final PackPath path;\n    final PackOverlay overlay;\n    final Map<PackPath, PackResource> resourceMap = new ConcurrentHashMap<>();\n\n    private final PackNamespace bettermodel, minecraft;\n\n    PackAssets(@NotNull PackOverlay overlay) {\n        this.overlay = overlay;\n        this.path = overlay.path(BetterModel.config().namespace());\n        bettermodel = new PackNamespace(this, BetterModel.config().namespace());\n        minecraft = new PackNamespace(this, \"minecraft\");\n    }\n\n    /**\n     * Returns the 'bettermodel' namespace (or the configured namespace).\n     *\n     * @return the namespace\n     * @since 1.15.2\n     */\n    public @NotNull PackNamespace bettermodel() {\n        return bettermodel;\n    }\n\n    /**\n     * Returns the 'minecraft' namespace.\n     *\n     * @return the namespace\n     * @since 1.15.2\n     */\n    public @NotNull PackNamespace minecraft() {\n        return minecraft;\n    }\n\n    int size() {\n        return resourceMap.size();\n    }\n\n    boolean dirty() {\n        return size() > 0;\n    }\n\n    /**\n     * Adds a resource to the pack.\n     *\n     * @param path the path of the resource\n     * @param size the estimated size of the resource\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String path, long size, @NotNull Supplier<byte[]> supplier) {\n        add(new String[] { path }, size, supplier);\n    }\n\n    /**\n     * Adds a resource to the pack using multiple path components.\n     *\n     * @param paths the path components\n     * @param size the estimated size of the resource\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String[] paths, long size, @NotNull Supplier<byte[]> supplier) {\n        var resolve = path.resolve(paths);\n        resourceMap.putIfAbsent(resolve, PackResource.of(overlay, resolve, size, supplier));\n    }\n\n    /**\n     * Adds a resource to the pack with unknown size.\n     *\n     * @param path the path of the resource\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String path, @NotNull Supplier<byte[]> supplier) {\n        add(path, -1, supplier);\n    }\n\n    /**\n     * Adds a resource to the pack using multiple path components with unknown size.\n     *\n     * @param paths the path components\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String[] paths, @NotNull Supplier<byte[]> supplier) {\n        add(paths, -1, supplier);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackBuilder.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport lombok.AccessLevel;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Supplier;\n\n/**\n * A builder for constructing resource pack contents within a specific path context.\n * <p>\n * This class simplifies adding resources to a pack by managing the current path and providing methods to resolve sub-paths.\n * It also integrates with {@link PackObfuscator} for resource name obfuscation.\n * </p>\n *\n * @since 1.15.2\n */\n@RequiredArgsConstructor(access = AccessLevel.PACKAGE)\npublic final class PackBuilder {\n    private final PackAssets assets;\n    private final PackPath path;\n    private final PackObfuscator obfuscator = PackObfuscator.order();\n\n    /**\n     * Resolves a sub-path and returns a new builder for that path.\n     *\n     * @param paths the sub-path components\n     * @return a new PackBuilder for the resolved path\n     * @since 1.15.2\n     */\n    public @NotNull PackBuilder resolve(@NotNull String... paths) {\n        return new PackBuilder(assets, path.resolve(paths));\n    }\n\n    /**\n     * Adds a resource to the pack at the current path.\n     *\n     * @param path the relative path of the resource\n     * @param estimatedSize the estimated size of the resource\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String path, long estimatedSize, @NotNull Supplier<byte[]> supplier) {\n        add(new String[] { path }, estimatedSize, supplier);\n    }\n\n    /**\n     * Adds a resource to the pack at the current path using multiple path components.\n     *\n     * @param paths the relative path components\n     * @param size the estimated size of the resource\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String[] paths, long size, @NotNull Supplier<byte[]> supplier) {\n        var resolve = path.resolve(paths);\n        assets.resourceMap.putIfAbsent(resolve, PackResource.of(assets.overlay, resolve, size, supplier));\n    }\n\n    /**\n     * Returns the obfuscator associated with this builder.\n     *\n     * @return the obfuscator\n     * @since 1.15.2\n     */\n    public @NotNull PackObfuscator obfuscator() {\n        return obfuscator;\n    }\n\n    /**\n     * Adds a resource to the pack at the current path with unknown size.\n     *\n     * @param path the relative path of the resource\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String path, @NotNull Supplier<byte[]> supplier) {\n        add(path, -1, supplier);\n    }\n\n    /**\n     * Adds a resource to the pack at the current path using multiple path components with unknown size.\n     *\n     * @param paths the relative path components\n     * @param supplier the supplier for the resource content\n     * @since 1.15.2\n     */\n    public void add(@NotNull String[] paths, @NotNull Supplier<byte[]> supplier) {\n        add(paths, -1, supplier);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackBuiltInAssets.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport com.google.gson.JsonObject;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.data.Float3;\nimport kr.toxicity.model.api.data.Float4;\nimport kr.toxicity.model.api.data.blueprint.BlueprintElement;\nimport kr.toxicity.model.api.util.json.JsonObjectBuilder;\nimport org.jetbrains.annotations.NotNull;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.BufferedImage;\nimport java.awt.image.DataBufferInt;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\nrecord PackBuiltInAssets(\n    @NotNull String path,\n    @NotNull Function<PackZipper, PackBuilder> builderFunction,\n    @NotNull Supplier<byte[]> supplier\n) {\n\n    private static final byte[] MESH_PIXEL_IMAGE;\n\n    static {\n        var image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);\n        Arrays.fill(((DataBufferInt) image.getRaster().getDataBuffer()).getData(), 0xFFFFFF);\n        try (var output = new ByteArrayOutputStream()) {\n            ImageIO.write(image, \"png\", output);\n            MESH_PIXEL_IMAGE = output.toByteArray();\n        } catch (IOException ex) {\n            throw new RuntimeException(ex);\n        }\n    }\n\n    private static final Set<PackBuiltInAssets> ASSETS = Set.of(\n        new PackBuiltInAssets(\n            BlueprintElement.MESH_TRIANGLE_SINGLE + \".json\",\n            zipper -> zipper.modern().bettermodel().models(),\n            () -> PackMeta.GSON.toJson(meshTriangle(faces -> faces\n                .jsonObject(\"north\", north -> north\n                    .jsonArray(\"uv\", Float4.MAX_UV.toJson())\n                    .property(\"texture\", \"#0\")\n                    .property(\"tintindex\", 0))\n            )).getBytes(StandardCharsets.UTF_8)\n        ),\n        new PackBuiltInAssets(\n            BlueprintElement.MESH_TRIANGLE_DUPLEX + \".json\",\n            zipper -> zipper.modern().bettermodel().models(),\n            () -> PackMeta.GSON.toJson(meshTriangle(faces -> faces\n                .jsonObject(\"north\", north -> north\n                    .jsonArray(\"uv\", Float4.MAX_UV.toJson())\n                    .property(\"texture\", \"#0\")\n                    .property(\"tintindex\", 0))\n                .jsonObject(\"south\", north -> north\n                    .jsonArray(\"uv\", Float4.MAX_UV.toJson())\n                    .property(\"texture\", \"#0\")\n                    .property(\"tintindex\", 0))\n            )).getBytes(StandardCharsets.UTF_8)\n        ),\n        new PackBuiltInAssets(\n            BlueprintElement.MESH_PIXEL + \".png\",\n            zipper -> zipper.modern().bettermodel().textures(),\n            () -> MESH_PIXEL_IMAGE\n        )\n    );\n\n    static void applyAs(@NotNull PackZipper zipper) {\n        for (PackBuiltInAssets asset : ASSETS) {\n            asset.builderFunction.apply(zipper).add(asset.path, asset.supplier);\n        }\n    }\n\n    private static @NotNull JsonObject meshTriangle(@NotNull Function<JsonObjectBuilder, JsonObjectBuilder> faceBuilder) {\n        var pixel = BetterModel.config().namespace() + \":item/\" + BlueprintElement.MESH_PIXEL;\n        return JsonObjectBuilder.builder()\n            .jsonObject(\"textures\", textures -> textures\n                .property(\"0\", pixel)\n                .property(\"particle\", pixel))\n            .jsonArray(\"elements\", elements -> elements\n                .jsonObject(element -> element\n                    .jsonArray(\"from\", Float3.MESH_TRIANGLE_FROM.toJson())\n                    .jsonArray(\"to\", Float3.MESH_TRIANGLE_TO.toJson())\n                    .jsonObject(\"faces\", faceBuilder::apply)))\n            .build();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackByte.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a raw byte array associated with a specific pack path.\n * <p>\n * This record is used to store the binary content of a resource within a resource pack.\n * It implements {@link Comparable} to allow sorting based on the path.\n * </p>\n *\n * @param path the path of the resource\n * @param bytes the binary content of the resource\n * @since 1.15.2\n */\npublic record PackByte(@NotNull PackPath path, byte[] bytes) implements Comparable<PackByte> {\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof PackByte packByte)) return false;\n        return path.equals(packByte.path);\n    }\n\n    @Override\n    public int hashCode() {\n        return path.hashCode();\n    }\n\n    @Override\n    public int compareTo(@NotNull PackByte o) {\n        return path.compareTo(o.path);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackMeta.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.nms.NMSVersion;\nimport lombok.AccessLevel;\nimport lombok.NoArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Represents the metadata (pack.mcmeta) of a resource pack.\n * <p>\n * This record handles the serialization and deserialization of the pack metadata, including\n * pack format, description, and supported formats. It also supports overlays for multi-version support.\n * </p>\n *\n * @param pack the main pack configuration\n * @param overlays the optional overlays configuration\n * @since 1.15.2\n */\npublic record PackMeta(\n    @NotNull Pack pack,\n    @Nullable Overlay overlays\n) {\n\n    /**\n     * The standard path for the pack metadata file.\n     * @since 1.15.2\n     */\n    public static final PackPath PATH = new PackPath(\"pack.mcmeta\");\n\n    static final Gson GSON = new GsonBuilder()\n        .registerTypeAdapter(PackVersion.class, (JsonDeserializer<PackVersion>) (json, _, _) -> {\n            if (json.isJsonPrimitive()) return new PackVersion(json.getAsInt());\n            else if (json.isJsonArray()) {\n                var array = json.getAsJsonArray();\n                return new PackVersion(array.get(0).getAsInt(), array.size() < 2 ? 0 : array.get(1).getAsInt());\n            } else return null;\n        })\n        .registerTypeAdapter(PackVersion.class, (JsonSerializer<PackVersion>) (src, _, _) -> src.toJson())\n        .registerTypeAdapter(VersionRange.class, (JsonDeserializer<VersionRange>) (json, _, context) -> {\n            if (json.isJsonPrimitive()) return new VersionRange(json.getAsInt());\n            else if (json.isJsonArray()) {\n                var array = json.getAsJsonArray();\n                return new VersionRange(\n                    array.get(0).getAsInt(),\n                    array.get(1).getAsInt()\n                );\n            } else if (json.isJsonObject()) {\n                return context.deserialize(json, VersionRange.class);\n            } else return null;\n        })\n        .registerTypeAdapter(VersionRange.class, (JsonSerializer<VersionRange>) (src, _, _) -> src.toJson())\n        .create();\n\n    /**\n     * Creates a new builder for {@link PackMeta}.\n     *\n     * @return a new builder instance\n     * @since 1.15.2\n     */\n    public static @NotNull Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Converts this metadata to a JSON element.\n     *\n     * @return the JSON representation\n     * @since 1.15.2\n     */\n    public @NotNull JsonElement toJson() {\n        return GSON.toJsonTree(this);\n    }\n\n    /**\n     * Converts this metadata to a pack resource.\n     *\n     * @return the pack resource containing the JSON data\n     * @since 1.15.2\n     */\n    public @NotNull PackResource toResource() {\n        var json = GSON.toJson(this);\n        return PackResource.of(PATH, 2L * json.length(), () -> json.getBytes(StandardCharsets.UTF_8));\n    }\n\n    /**\n     * Represents the 'pack' section of the metadata.\n     *\n     * @param packFormat the pack format version\n     * @param description the pack description\n     * @param supportedFormats the range of supported formats\n     * @param minFormat the minimum supported format (>= 1.21.9)\n     * @param maxFormat the maximum supported format\n     * @since 1.15.2\n     */\n    public record Pack(\n        @SerializedName(\"pack_format\") int packFormat,\n        @SerializedName(\"description\") @NotNull String description,\n        @SerializedName(\"supported_formats\") @NotNull VersionRange supportedFormats,\n        @SerializedName(\"min_format\") @NotNull PackVersion minFormat, //>=1.21.9\n        @SerializedName(\"max_format\") @NotNull PackVersion maxFormat\n    ) {\n    }\n\n    /**\n     * Represents a pack version, consisting of major and minor numbers.\n     *\n     * @param major the major version\n     * @param minor the minor version\n     * @since 1.15.2\n     */\n    public record PackVersion(\n        int major,\n        int minor\n    ) {\n        /**\n         * Creates a pack version with only a major number.\n         *\n         * @param major the major version\n         * @since 1.15.2\n         */\n        public PackVersion(int major) {\n            this(major, 0);\n        }\n\n        /**\n         * Converts this version to a JSON element.\n         *\n         * @return the JSON representation (primitive int or array)\n         * @since 1.15.2\n         */\n        public @NotNull JsonElement toJson() {\n            if (minor <= 0) {\n                return new JsonPrimitive(major);\n            } else {\n                var array = new JsonArray(2);\n                array.add(major);\n                array.add(minor);\n                return array;\n            }\n        }\n    }\n\n    /**\n     * Represents the 'overlays' section of the metadata.\n     *\n     * @param entries the list of overlay entries\n     * @since 1.15.2\n     */\n    public record Overlay(@Nullable List<OverlayEntry> entries) {\n    }\n\n    /**\n     * Represents a single entry in the overlays list.\n     *\n     * @param formats the range of formats this overlay applies to (removed in 1.21.9)\n     * @param directory the directory name for the overlay\n     * @param minFormat the minimum format (>= 1.21.9)\n     * @param maxFormat the maximum format\n     * @since 1.15.2\n     */\n    public record OverlayEntry(\n        @NotNull VersionRange formats, //Removed in 1.21.9\n        @NotNull String directory,\n        @SerializedName(\"min_format\") @NotNull PackVersion minFormat, //>=1.21.9\n        @SerializedName(\"max_format\") @NotNull PackVersion maxFormat\n    ) {\n        /**\n         * Creates an overlay entry with a format range and directory.\n         *\n         * @param formats the format range\n         * @param directory the directory\n         * @since 1.15.2\n         */\n        public OverlayEntry(\n            @NotNull VersionRange formats, //Removed in 1.21.9\n            @NotNull String directory\n        ) {\n            this(\n                formats,\n                directory,\n                new PackVersion(formats.minInclusive),\n                new PackVersion(formats.maxInclusive)\n            );\n        }\n    }\n\n    /**\n     * Represents a range of versions.\n     *\n     * @param minInclusive the minimum version (inclusive)\n     * @param maxInclusive the maximum version (inclusive)\n     * @since 1.15.2\n     */\n    public record VersionRange(\n        @SerializedName(\"min_inclusive\") int minInclusive,\n        @SerializedName(\"max_inclusive\") int maxInclusive\n    ) {\n\n        /**\n         * Creates a version range for a single value.\n         *\n         * @param value the version value\n         * @since 1.15.2\n         */\n        public VersionRange(int value) {\n            this(value, value);\n        }\n\n        /**\n         * Converts this range to a JSON element.\n         *\n         * @return the JSON representation (primitive int or array)\n         * @since 1.15.2\n         */\n        public @NotNull JsonElement toJson() {\n            if (minInclusive == maxInclusive) return new JsonPrimitive(minInclusive);\n            var arr = new JsonArray(2);\n            arr.add(minInclusive);\n            arr.add(maxInclusive);\n            return arr;\n        }\n    }\n\n    /**\n     * Builder for {@link PackMeta}.\n     *\n     * @since 1.15.2\n     */\n    @NoArgsConstructor(access = AccessLevel.PRIVATE)\n    public static final class Builder {\n        private int format = BetterModel.nms().version().getMetaVersion();\n        private String description = \"BetterModel's default pack.\";\n        private final List<OverlayEntry> entries = new ArrayList<>();\n        private VersionRange supportedFormats = new VersionRange(\n            NMSVersion.first().getMetaVersion(),\n            NMSVersion.latest().getMetaVersion() //<=1.21.8\n        );\n        private PackVersion minFormat = new PackVersion(NMSVersion.first().getMetaVersion());\n        private PackVersion maxFormat = new PackVersion(NMSVersion.latest().getMetaVersion());\n\n        /**\n         * Sets the pack description.\n         *\n         * @param description the description\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder description(@NotNull String description) {\n            this.description = Objects.requireNonNull(description, \"description\");\n            return this;\n        }\n\n        /**\n         * Sets the pack format.\n         *\n         * @param format the format version\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder format(int format) {\n            this.format = format;\n            return this;\n        }\n\n        /**\n         * Adds an overlay entry.\n         *\n         * @param overlayEntry the overlay entry to add\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder overlayEntry(@NotNull OverlayEntry overlayEntry) {\n            entries.add(overlayEntry);\n            return this;\n        }\n\n        /**\n         * Sets the supported formats range.\n         *\n         * @param range the version range\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder supportedFormats(@NotNull VersionRange range) {\n            supportedFormats = Objects.requireNonNull(range);\n            return this;\n        }\n\n        /**\n         * Sets the minimum supported format.\n         *\n         * @param version the minimum version\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder minFormat(@NotNull PackVersion version) {\n            minFormat = Objects.requireNonNull(version);\n            return this;\n        }\n\n        /**\n         * Sets the maximum supported format.\n         *\n         * @param version the maximum version\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder maxFormat(@NotNull PackVersion version) {\n            maxFormat = Objects.requireNonNull(version);\n            return this;\n        }\n\n        /**\n         * Builds the {@link PackMeta} instance.\n         *\n         * @return the created pack meta\n         * @since 1.15.2\n         */\n        public @NotNull PackMeta build() {\n            return new PackMeta(\n                new Pack(\n                    format,\n                    description,\n                    supportedFormats,\n                    minFormat,\n                    maxFormat\n                ),\n                entries.isEmpty() ? null : new Overlay(entries)\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackNamespace.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a namespace within a resource pack, providing access to standard subdirectories.\n * <p>\n * This class organizes resources into 'items', 'models', and 'textures' categories.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class PackNamespace {\n\n    private final PackBuilder items, models, textures;\n\n    PackNamespace(@NotNull PackAssets assets, @NotNull String namespace) {\n        var subPath = assets.path.resolve(\"assets\", namespace);\n        items = new PackBuilder(assets, subPath.resolve(\"items\"));\n        models = new PackBuilder(assets, subPath.resolve(\"models\"));\n        textures = new PackBuilder(assets, subPath.resolve(\"textures\", \"item\"));\n    }\n\n    /**\n     * Returns the builder for the 'items' directory.\n     *\n     * @return the items builder\n     * @since 1.15.2\n     */\n    public @NotNull PackBuilder items() {\n        return items;\n    }\n\n    /**\n     * Returns the builder for the 'models' directory.\n     *\n     * @return the models builder\n     * @since 1.15.2\n     */\n    public @NotNull PackBuilder models() {\n        return models;\n    }\n\n    /**\n     * Returns the builder for the 'textures/item' directory.\n     *\n     * @return the textures builder\n     * @since 1.15.2\n     */\n    public @NotNull PackBuilder textures() {\n        return textures;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackObfuscator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport kr.toxicity.model.api.BetterModel;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Defines a strategy for obfuscating resource names in the pack.\n * <p>\n * Obfuscation can help reduce file path lengths and protect asset names.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface PackObfuscator {\n\n    /**\n     * A no-op obfuscator that returns the name as-is.\n     * @since 1.15.2\n     */\n    PackObfuscator NONE = name -> name;\n\n    /**\n     * Obfuscates the given raw name.\n     *\n     * @param rawName the original name\n     * @return the obfuscated name\n     * @since 1.15.2\n     */\n    @NotNull String obfuscate(@NotNull String rawName);\n\n    /**\n     * Creates an order-based obfuscator if obfuscation is enabled in the configuration.\n     *\n     * @return the obfuscator\n     * @since 1.15.2\n     */\n    static @NotNull PackObfuscator order() {\n        return BetterModel.config().pack().useObfuscation() ? new Order() : NONE;\n    }\n\n    /**\n     * Creates a pair obfuscator, combining this obfuscator (as textures) with another (as models).\n     *\n     * @param models the models obfuscator\n     * @return the pair obfuscator\n     * @since 1.15.2\n     */\n    default @NotNull Pair withModels(@NotNull PackObfuscator models) {\n        return pair(models, this);\n    }\n\n    /**\n     * Creates a pair obfuscator from two separate obfuscators.\n     *\n     * @param models the models obfuscator\n     * @param textures the textures obfuscator\n     * @return the pair obfuscator\n     * @since 1.15.2\n     */\n    static @NotNull Pair pair(@NotNull PackObfuscator models, @NotNull PackObfuscator textures) {\n        return new Pair(models, textures);\n    }\n\n    /**\n     * An obfuscator that generates short names based on the order of appearance.\n     *\n     * @since 1.15.2\n     */\n    final class Order implements PackObfuscator {\n\n        private static final char[] AVAILABLE_NAME = {\n            'a', 'b', 'c', 'd', 'e', 'f', 'g',\n            'h', 'i', 'j', 'k', 'm', 'n', 'l', 'o', 'p',\n            'q', 'r', 's', 't', 'u', 'v',\n            'w', 'x', 'y', 'z',\n            '0', '1', '2', '3', '4', '5', '6' ,'7', '8', '9'\n        };\n        private static final int NAME_LENGTH = AVAILABLE_NAME.length;\n\n        private final Map<String, String> nameMap = new ConcurrentHashMap<>();\n        private final StringBuilder builder = new StringBuilder();\n\n        /**\n         * Private initializer\n         */\n        private Order() {\n        }\n\n        public @NotNull String obfuscate(@NotNull String rawName) {\n            return nameMap.computeIfAbsent(rawName, _ -> {\n                var size = nameMap.size();\n                builder.setLength(0);\n                while (size >= NAME_LENGTH) {\n                    builder.append(AVAILABLE_NAME[size % NAME_LENGTH]);\n                    size /= NAME_LENGTH;\n                }\n                builder.append(AVAILABLE_NAME[size % NAME_LENGTH]);\n                return builder.toString();\n            });\n        }\n    }\n\n    /**\n     * A pair of obfuscators for models and textures.\n     *\n     * @param models the models obfuscator\n     * @param textures the textures obfuscator\n     * @since 1.15.2\n     */\n    record Pair(@NotNull PackObfuscator models, @NotNull PackObfuscator textures) {}\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackOverlay.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.util.function.BooleanConstantSupplier;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Optional;\nimport java.util.function.BooleanSupplier;\n\n/**\n * Represents a resource pack overlay, allowing for version-specific resources.\n * <p>\n * Overlays are used to support multiple Minecraft versions within a single resource pack.\n * </p>\n *\n * @param packName the name of the overlay (e.g., \"legacy\", \"modern\")\n * @param range the version range this overlay applies to\n * @param tester a supplier to determine if this overlay should be active\n * @since 1.15.2\n */\npublic record PackOverlay(\n    @NotNull String packName,\n    @NotNull Optional<PackMeta.VersionRange> range,\n    @NotNull BooleanSupplier tester\n) implements Comparable<PackOverlay> {\n    /**\n     * The default overlay (base pack).\n     * @since 1.15.2\n     */\n    public static final PackOverlay DEFAULT = new PackOverlay(\n        \"\",\n        Optional.empty(),\n        BooleanConstantSupplier.TRUE\n    );\n\n    /**\n     * The legacy overlay (for older versions).\n     * @since 1.15.2\n     */\n    public static final PackOverlay LEGACY = new PackOverlay(\n        \"legacy\",\n        Optional.of(new PackMeta.VersionRange(22, 45)),\n        () -> BetterModel.config().pack().generateLegacyModel()\n    );\n\n    /**\n     * The modern overlay (for newer versions).\n     * @since 1.15.2\n     */\n    public static final PackOverlay MODERN = new PackOverlay(\n        \"modern\",\n        Optional.of(new PackMeta.VersionRange(46, 99)),\n        () -> BetterModel.config().pack().generateModernModel()\n    );\n\n\n    /**\n     * Generates the root path for this overlay.\n     *\n     * @param namespace the namespace prefix\n     * @return the pack path\n     * @since 1.15.2\n     */\n    public @NotNull PackPath path(@NotNull String namespace) {\n        return packName.isEmpty() ? PackPath.EMPTY : new PackPath(namespace + \"_\" + packName);\n    }\n\n    /**\n     * Checks if this overlay is active.\n     *\n     * @return true if active, false otherwise\n     * @since 1.15.2\n     */\n    public boolean test() {\n        return tester.getAsBoolean();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (!(o instanceof PackOverlay that)) return false;\n        return packName.equals(that.packName);\n    }\n\n    @Override\n    public int hashCode() {\n        return packName.hashCode();\n    }\n\n    @Override\n    public int compareTo(@NotNull PackOverlay o) {\n        return packName.compareTo(o.packName);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackPath.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport static java.lang.String.join;\n\n/**\n * Represents a path within a resource pack.\n * <p>\n * This record encapsulates a string path and provides utility methods for resolving sub-paths.\n * It implements {@link Comparable} for sorting purposes.\n * </p>\n *\n * @param path the string representation of the path\n * @since 1.15.2\n */\npublic record PackPath(@NotNull String path) implements Comparable<PackPath> {\n\n    /**\n     * The delimiter used for path separation.\n     * @since 1.15.2\n     */\n    public static final String DELIMITER = \"/\";\n\n    /**\n     * An empty pack path.\n     * @since 1.15.2\n     */\n    public static final PackPath EMPTY = new PackPath(\"\");\n\n    /**\n     * Resolves a sub-path relative to this path.\n     *\n     * @param subPaths the sub-path components to resolve\n     * @return the resolved pack path\n     * @since 1.15.2\n     */\n    public @NotNull PackPath resolve(@NotNull String... subPaths) {\n        if (subPaths.length == 0) return this;\n        return new PackPath(path.isEmpty() ? join(DELIMITER, subPaths) : path + DELIMITER + join(DELIMITER, subPaths));\n    }\n\n    @Override\n    public @NotNull String toString() {\n        return path;\n    }\n\n    @Override\n    public int compareTo(@NotNull PackPath o) {\n        return path.compareTo(o.path);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackResource.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * Represents a resource within a resource pack.\n * <p>\n * A resource consists of its content (as a byte array), its path, and optionally the overlay it belongs to.\n * </p>\n *\n * @since 1.15.2\n */\npublic interface PackResource extends Supplier<byte[]> {\n\n    /**\n     * Returns the overlay this resource belongs to.\n     *\n     * @return the overlay, or null if it belongs to the base pack\n     * @since 1.15.2\n     */\n    @Nullable\n    PackOverlay overlay();\n\n    /**\n     * Returns the path of this resource.\n     *\n     * @return the pack path\n     * @since 1.15.2\n     */\n    @NotNull\n    PackPath path();\n\n    /**\n     * Returns the estimated size of this resource in bytes.\n     *\n     * @return the estimated size\n     * @since 1.15.2\n     */\n    long estimatedSize();\n\n    /**\n     * Creates a new pack resource for the base pack.\n     *\n     * @param path the path of the resource\n     * @param size the estimated size\n     * @param supplier the content supplier\n     * @return the created resource\n     * @since 1.15.2\n     */\n    static @NotNull PackResource of(@NotNull PackPath path, long size, @NotNull Supplier<byte[]> supplier) {\n        return of(null, path, size, supplier);\n    }\n\n    /**\n     * Creates a new pack resource for a specific overlay.\n     *\n     * @param overlay the overlay (or null for base pack)\n     * @param path the path of the resource\n     * @param size the estimated size\n     * @param supplier the content supplier\n     * @return the created resource\n     * @since 1.15.2\n     */\n    static @NotNull PackResource of(@Nullable PackOverlay overlay, @NotNull PackPath path, long size, @NotNull Supplier<byte[]> supplier) {\n        Objects.requireNonNull(path, \"path\");\n        Objects.requireNonNull(supplier, \"supplier\");\n        return new Packed(overlay, path, size, supplier);\n    }\n\n    /**\n     * A simple implementation of {@link PackResource}.\n     *\n     * @param overlay the overlay\n     * @param path the path\n     * @param estimatedSize the estimated size\n     * @param supplier the content supplier\n     * @since 1.15.2\n     */\n    record Packed(\n        @Nullable PackOverlay overlay,\n        @NotNull PackPath path,\n        long estimatedSize,\n        @NotNull Supplier<byte[]> supplier\n    ) implements PackResource {\n        @Override\n        public byte[] get() {\n            return supplier.get();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackResult.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.io.File;\nimport java.security.MessageDigest;\nimport java.util.*;\nimport java.util.stream.Stream;\n\n/**\n * Represents the result of a pack building process.\n * <p>\n * This class holds the generated pack metadata, the output directory, and the collection of generated resources (assets and overlays).\n * It also provides methods to calculate the pack hash and check for changes.\n * </p>\n *\n * @since 1.15.2\n */\n@RequiredArgsConstructor\npublic final class PackResult {\n    private final PackMeta meta;\n    private final File directory;\n    private final SortedMap<PackOverlay, SortedSet<PackByte>> overlays = new TreeMap<>();\n    private final SortedSet<PackByte> assets = new TreeSet<>();\n    private final SortedSet<PackByte> assetsView = Collections.unmodifiableSortedSet(assets);\n\n    private final long creationTime = System.currentTimeMillis();\n    private boolean frozen = false;\n    private boolean changed = false;\n    private UUID uuid;\n\n    /**\n     * Adds a resource to the result.\n     *\n     * @param overlay the overlay the resource belongs to (or null for base assets)\n     * @param packByte the resource data\n     * @throws IllegalStateException if the result is frozen\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public void set(@Nullable PackOverlay overlay, @NotNull PackByte packByte) {\n        if (frozen) throw new IllegalStateException(\"result is frozen.\");\n        if (overlay == null) {\n            synchronized (assets) {\n                assets.add(packByte);\n            }\n            return;\n        }\n        synchronized (overlays) {\n            overlays.computeIfAbsent(overlay, _ -> new TreeSet<>()).add(packByte);\n        }\n    }\n\n    /**\n     * Freezes the result, preventing further modifications.\n     *\n     * @since 1.15.2\n     */\n    public void freeze() {\n        freeze(false);\n    }\n\n    /**\n     * Checks if the pack content has changed.\n     *\n     * @return true if changed, false otherwise\n     * @since 1.15.2\n     */\n    public boolean changed() {\n        return changed;\n    }\n\n    /**\n     * Freezes the result and sets the changed status.\n     *\n     * @param changed whether the pack content has changed\n     * @throws IllegalStateException if the result is already frozen\n     * @since 1.15.2\n     */\n    public void freeze(boolean changed) {\n        if (frozen) throw new IllegalStateException(\"result is frozen.\");\n        frozen = true;\n        this.changed = changed;\n    }\n\n    /**\n     * Returns the pack metadata.\n     *\n     * @return the pack metadata\n     * @since 1.15.2\n     */\n    @NotNull\n    public PackMeta meta() {\n        return meta;\n    }\n\n    /**\n     * Returns the output directory of the pack.\n     *\n     * @return the directory, or null if not applicable\n     * @since 1.15.2\n     */\n    public @Nullable File directory() {\n        return directory;\n    }\n\n    /**\n     * Calculates and returns the SHA-256 hash of the pack content as a UUID.\n     *\n     * @return the hash UUID\n     * @since 1.15.2\n     */\n    public @NotNull UUID hash() {\n        if (uuid != null) return uuid;\n        synchronized (this) {\n            if (uuid != null) return uuid;\n            try {\n                var sha = MessageDigest.getInstance(\"SHA-256\");\n                stream().map(PackByte::bytes).forEach(sha::update);\n                return uuid = UUID.nameUUIDFromBytes(sha.digest());\n            } catch (Exception e) {\n                return uuid = UUID.randomUUID();\n            }\n        }\n    }\n\n    /**\n     * Returns the total number of resources in the pack.\n     *\n     * @return the size\n     * @since 1.15.2\n     */\n    public int size() {\n        return assets.size() + overlays.values().stream().mapToInt(Set::size).sum();\n    }\n\n    /**\n     * Returns the time elapsed since the result was created.\n     *\n     * @return the elapsed time in milliseconds\n     * @since 1.15.2\n     */\n    public long time() {\n        return System.currentTimeMillis() - creationTime;\n    }\n\n    /**\n     * Returns the resources for a specific overlay.\n     *\n     * @param overlay the overlay\n     * @return the set of resources\n     * @since 1.15.2\n     */\n    @NotNull\n    @Unmodifiable\n    public SortedSet<PackByte> overlays(@NotNull PackOverlay overlay) {\n        var get = overlays.get(overlay);\n        return get != null ? Collections.unmodifiableSortedSet(get) : Collections.emptySortedSet();\n    }\n\n    /**\n     * Returns a stream of all resources in the pack.\n     *\n     * @return the stream of resources\n     * @since 1.15.2\n     */\n    public @NotNull Stream<PackByte> stream() {\n        return Stream.concat(\n            overlays.values().stream().flatMap(Collection::stream),\n            assets.stream()\n        );\n    }\n\n    /**\n     * Returns the base assets of the pack.\n     *\n     * @return the set of assets\n     * @since 1.15.2\n     */\n    @NotNull\n    @Unmodifiable\n    public SortedSet<PackByte> assets() {\n        return assetsView;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/pack/PackZipper.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.pack;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.util.LogUtil;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Manages the assembly and zipping of resource pack contents.\n * <p>\n * This class coordinates the collection of assets across different overlays (default, legacy, modern)\n * and prepares the data for final pack generation.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class PackZipper {\n\n    private PackZipper() {\n        PackBuiltInAssets.applyAs(this);\n    }\n\n    private static final PackPath PACK_ICON = new PackPath(\"pack.png\");\n\n    /**\n     * Creates a new PackZipper instance.\n     *\n     * @return a new PackZipper\n     * @since 1.15.2\n     */\n    public static @NotNull PackZipper zipper() {\n        return new PackZipper();\n    }\n\n    private final PackMeta.Builder metaBuilder = PackMeta.builder();\n    private final Map<PackOverlay, PackAssets> overlayMap = new ConcurrentHashMap<>();\n\n    /**\n     * Retrieves the default assets collection.\n     *\n     * @return the default assets\n     * @since 1.15.2\n     */\n    public @NotNull PackAssets assets() {\n        return overlay(PackOverlay.DEFAULT);\n    }\n\n    /**\n     * Retrieves the legacy assets collection.\n     *\n     * @return the legacy assets\n     * @since 1.15.2\n     */\n    public @NotNull PackAssets legacy() {\n        return overlay(PackOverlay.LEGACY);\n    }\n\n    /**\n     * Retrieves the modern assets' collection.\n     *\n     * @return the modern assets\n     * @since 1.15.2\n     */\n    public @NotNull PackAssets modern() {\n        return overlay(PackOverlay.MODERN);\n    }\n\n    /**\n     * Retrieves the assets collection for a specific overlay.\n     *\n     * @param overlay the overlay\n     * @return the assets collection\n     * @since 1.15.2\n     */\n    public @NotNull PackAssets overlay(@NotNull PackOverlay overlay) {\n        return overlayMap.computeIfAbsent(overlay, PackAssets::new);\n    }\n\n    /**\n     * Returns the builder for the pack metadata.\n     *\n     * @return the metadata builder\n     * @since 1.15.2\n     */\n    public @NotNull PackMeta.Builder metaBuilder() {\n        return metaBuilder;\n    }\n\n    /**\n     * Builds the final pack data, including metadata and all resources.\n     *\n     * @return the build data\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public @NotNull BuildData build() {\n        var resources = new ArrayList<PackResource>(size());\n        for (Map.Entry<PackOverlay, PackAssets> entry : overlayMap.entrySet()) {\n            var overlay = entry.getKey();\n            var value = entry.getValue();\n            if (overlay.test() && value.dirty()) {\n                resources.addAll(value.resourceMap.values());\n                overlay.range().ifPresent(range -> metaBuilder.overlayEntry(new PackMeta.OverlayEntry(range, value.path.path())));\n            }\n            value.resourceMap.clear();\n        }\n        var meta = metaBuilder.build();\n        resources.add(meta.toResource());\n        var icon = loadIcon();\n        if (icon != null) resources.add(icon);\n        return new BuildData(meta, resources);\n    }\n\n    /**\n     * Returns the total estimated number of resources.\n     *\n     * @return the size\n     * @since 1.15.2\n     */\n    public int size() {\n        return overlayMap.values().stream().mapToInt(PackAssets::size).sum() + 2;\n    }\n\n    private static @Nullable PackResource loadIcon() {\n        try (\n            var icon = BetterModel.platform().getResource(\"icon.png\")\n        ) {\n            if (icon == null) return null;\n            var read = icon.readAllBytes();\n            return PackResource.of(PACK_ICON, read.length, () -> read);\n        } catch (IOException e) {\n            LogUtil.handleException(\"Unable to get icon.png\", e);\n            return null;\n        }\n    }\n\n    /**\n     * Holds the result of a build operation.\n     *\n     * @param meta the generated pack metadata\n     * @param resources the list of generated resources\n     * @since 1.15.2\n     */\n    public record BuildData(@NotNull PackMeta meta, @NotNull List<PackResource> resources) {\n\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformAdapter.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Adapts platform-specific objects and operations to the BetterModel API.\n * <p>\n * This interface provides methods for retrieving players, creating items, and checking server state,\n * abstracting away the differences between platforms like Bukkit and Fabric.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformAdapter {\n\n    /**\n     * Returns the server's default view distance.\n     *\n     * @return the view distance in chunks\n     * @since 2.0.0\n     */\n    int serverViewDistance();\n\n    /**\n     * Checks if the current thread is the main server tick thread.\n     *\n     * @return true if on the tick thread, false otherwise\n     * @since 2.0.0\n     */\n    boolean isTickThread();\n\n    /**\n     * Checks if it is safe to access region data from the current thread.\n     *\n     * @return true if safe, false otherwise\n     * @since 2.0.0\n     */\n    boolean isRegionSafe();\n\n    /**\n     * Retrieves an online player by their UUID.\n     *\n     * @param uuid the player's UUID\n     * @return the player, or null if not found/offline\n     * @since 2.0.0\n     */\n    @Nullable PlatformPlayer player(@NotNull UUID uuid);\n\n    /**\n     * Retrieves an offline player by their UUID.\n     *\n     * @param uuid the player's UUID\n     * @return the offline player\n     * @since 2.0.0\n     */\n    @NotNull PlatformOfflinePlayer offlinePlayer(@NotNull UUID uuid);\n\n    /**\n     * Returns a platform-specific representation of an empty item stack (air).\n     *\n     * @return the air item stack\n     * @since 2.0.0\n     */\n    @NotNull PlatformItemStack air();\n\n    /**\n     * Returns a location at coordinates (0, 0, 0) in a default or null world.\n     *\n     * @return the zero location\n     * @since 2.0.0\n     */\n    @NotNull PlatformLocation zero();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformBillboard.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\n/**\n * Defines the billboard constraints for a display entity.\n * <p>\n * Billboard settings control how the display rotates to face the player.\n * </p>\n *\n * @since 2.0.0\n */\npublic enum PlatformBillboard {\n    /**\n     * No rotation (default).\n     */\n    FIXED,\n    /**\n     * Can pivot around vertical axis.\n     */\n    VERTICAL,\n    /**\n     * Can pivot around horizontal axis.\n     */\n    HORIZONTAL,\n    /**\n     * Can pivot around center point.\n     */\n    CENTER\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.scheduler.ModelTask;\nimport kr.toxicity.model.api.tracker.EntityTracker;\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Optional;\nimport java.util.UUID;\n\n/**\n * Represents an entity in the underlying platform.\n * <p>\n * This interface provides access to basic entity properties like UUID and location,\n * as well as integration with the {@link EntityTrackerRegistry}.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformEntity extends PlatformRegionHolder {\n\n    /**\n     * Returns the unique identifier of the entity.\n     *\n     * @return the UUID\n     * @since 2.0.0\n     */\n    @NotNull UUID uuid();\n\n    /**\n     * Returns the current location of the entity.\n     *\n     * @return the location\n     * @since 2.0.0\n     */\n    @NotNull PlatformLocation location();\n\n    @Override\n    default @Nullable ModelTask task(@NotNull Runnable runnable) {\n        return location().task(runnable);\n    }\n\n    @Override\n    default @Nullable ModelTask taskLater(long delay, @NotNull Runnable runnable) {\n        return location().taskLater(delay, runnable);\n    }\n\n    /**\n     * Retrieves the tracker registry associated with this entity.\n     *\n     * @return an optional containing the registry if it exists\n     * @since 2.0.0\n     */\n    default @NotNull Optional<EntityTrackerRegistry> registry() {\n        return BetterModel.registry(uuid());\n    }\n\n    /**\n     * Retrieves a specific tracker by name from this entity's registry.\n     *\n     * @param name the name of the tracker\n     * @return an optional containing the tracker if found\n     * @since 2.0.0\n     */\n    default @NotNull Optional<EntityTracker> tracker(@NotNull String name) {\n        return registry().map(registry -> registry.tracker(name));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformItemStack.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents an item stack in the underlying platform.\n * <p>\n * This interface provides methods for manipulating item properties like custom model data,\n * enchantment glint, and cloning.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformItemStack {\n\n    /**\n     * Checks if the item stack is empty or air.\n     *\n     * @return true if air, false otherwise\n     * @since 2.0.0\n     */\n    boolean isAir();\n\n    /**\n     * Sets the enchantment glint override for the item.\n     *\n     * @param enchant true to enable glint, false to disable\n     * @return this item stack\n     * @since 2.0.0\n     */\n    @NotNull PlatformItemStack enchant(boolean enchant);\n\n    /**\n     * Sets the custom model data and item model namespace for the item.\n     *\n     * @param customModelData the custom model data integer\n     * @param namespace the item model namespace (optional)\n     * @return this item stack\n     * @since 2.0.0\n     */\n    @NotNull PlatformItemStack modelData(int customModelData, @Nullable PlatformNamespace namespace);\n\n    /**\n     * Creates a copy of this item stack.\n     *\n     * @return the cloned item stack\n     * @since 2.0.0\n     */\n    @NotNull PlatformItemStack clone();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformItemTransform.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\n/**\n * Defines the display context for an item model.\n * <p>\n * These values correspond to the display settings in a Minecraft item model file.\n * </p>\n *\n * @since 2.0.0\n */\npublic enum PlatformItemTransform {\n    /**\n     * No specific transform.\n     * @since 2.0.0\n     */\n    NONE,\n    /**\n     * Displayed in the left hand in third-person view.\n     * @since 2.0.0\n     */\n    THIRDPERSON_LEFTHAND,\n    /**\n     * Displayed in the right hand in third-person view.\n     * @since 2.0.0\n     */\n    THIRDPERSON_RIGHTHAND,\n    /**\n     * Displayed in the left hand in first-person view.\n     * @since 2.0.0\n     */\n    FIRSTPERSON_LEFTHAND,\n    /**\n     * Displayed in the right hand in first-person view.\n     * @since 2.0.0\n     */\n    FIRSTPERSON_RIGHTHAND,\n    /**\n     * Displayed on the head (e.g., helmet).\n     * @since 2.0.0\n     */\n    HEAD,\n    /**\n     * Displayed in a GUI slot.\n     * @since 2.0.0\n     */\n    GUI,\n    /**\n     * Displayed on the ground as an item entity.\n     * @since 2.0.0\n     */\n    GROUND,\n    /**\n     * Displayed in an item frame.\n     * @since 2.0.0\n     */\n    FIXED\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformLivingEntity.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a living entity in the underlying platform.\n * <p>\n * This interface extends {@link PlatformEntity} to provide access to living entity-specific properties,\n * such as eye location.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformLivingEntity extends PlatformEntity {\n\n    /**\n     * Returns the eye location of the living entity.\n     *\n     * @return the eye location\n     * @since 2.0.0\n     */\n    @NotNull PlatformLocation eyeLocation();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformLocation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport static java.lang.Math.fma;\nimport static java.lang.Math.sqrt;\n\n/**\n * Represents a location in the underlying platform.\n * <p>\n * This interface provides access to coordinates, rotation, and the world,\n * as well as methods for manipulating the location.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformLocation extends PlatformRegionHolder {\n\n    /**\n     * Returns the world associated with this location.\n     *\n     * @return the world\n     * @since 2.0.0\n     */\n    PlatformWorld world();\n\n    /**\n     * Returns the X coordinate.\n     *\n     * @return the X coordinate\n     * @since 2.0.0\n     */\n    double x();\n\n    /**\n     * Returns the Y coordinate.\n     *\n     * @return the Y coordinate\n     * @since 2.0.0\n     */\n    double y();\n\n    /**\n     * Returns the Z coordinate.\n     *\n     * @return the Z coordinate\n     * @since 2.0.0\n     */\n    double z();\n\n    /**\n     * Returns the pitch (vertical rotation).\n     *\n     * @return the pitch\n     * @since 2.0.0\n     */\n    float pitch();\n\n    /**\n     * Returns the yaw (horizontal rotation).\n     *\n     * @return the yaw\n     * @since 2.0.0\n     */\n    float yaw();\n\n    /**\n     * Creates a new location by adding the specified coordinates to this location.\n     *\n     * @param x the X offset\n     * @param y the Y offset\n     * @param z the Z offset\n     * @return the new location\n     * @since 2.0.0\n     */\n    @NotNull PlatformLocation add(double x, double y, double z);\n\n    /**\n     * Calculates the distance between this location and another location.\n     *\n     * @param other the other location\n     * @return the distance\n     * @since 2.1.0\n     */\n    default double distance(@NotNull PlatformLocation other) {\n        return sqrt(distanceSquared(other));\n    }\n\n    /**\n     * Calculates the squared distance between this location and another location.\n     *\n     * @param other the other location\n     * @return the squared distance\n     * @since 2.1.0\n     */\n    default double distanceSquared(@NotNull PlatformLocation other) {\n        var x = x() - other.x();\n        var y = y() - other.y();\n        var z = z() - other.z();\n        return fma(x, x, fma(y, y, z * z));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformNamespace.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a namespaced key (e.g., \"minecraft:apple\").\n *\n * @param namespace the namespace (e.g., \"minecraft\")\n * @param path the path (e.g., \"apple\")\n * @since 2.0.0\n */\npublic record PlatformNamespace(@NotNull String namespace, @NotNull String path) {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformOfflinePlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Represents an offline player in the underlying platform.\n * <p>\n * This interface provides access to basic player identification data, such as UUID and name,\n * without requiring the player to be online.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformOfflinePlayer {\n\n    /**\n     * Returns the unique identifier of the player.\n     *\n     * @return the UUID\n     * @since 2.0.0\n     */\n    @NotNull UUID uuid();\n\n    /**\n     * Returns the name of the player, if known.\n     *\n     * @return the player name, or null if unknown\n     * @since 2.0.0\n     */\n    @Nullable String name();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformPlayer.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents a player in the underlying platform.\n * <p>\n * This interface combines the properties of a living entity and an offline player,\n * providing access to player-specific data like name and online status.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformPlayer extends PlatformLivingEntity, PlatformOfflinePlayer {\n\n    /**\n     * Returns the name of the player.\n     *\n     * @return the player name\n     * @since 2.0.0\n     */\n    @Override\n    @NotNull String name();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformRegionHolder.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\nimport kr.toxicity.model.api.scheduler.ModelTask;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Represents an object that holds a region context (e.g., an entity or location) for task scheduling.\n * <p>\n * This interface is crucial for platforms like Folia where tasks must be scheduled relative to a specific region.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformRegionHolder {\n    /**\n     * Schedules a task to run on the next tick, synchronized with this region holder.\n     *\n     * @param runnable the task to run\n     * @return the scheduled task, or null if scheduling failed\n     * @since 2.0.0\n     */\n    @Nullable ModelTask task(@NotNull Runnable runnable);\n\n    /**\n     * Schedules a task to run after a delay, synchronized with this region holder.\n     *\n     * @param delay the delay in ticks\n     * @param runnable the task to run\n     * @return the scheduled task, or null if scheduling failed\n     * @since 2.0.0\n     */\n    @Nullable ModelTask taskLater(long delay, @NotNull Runnable runnable);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/platform/PlatformWorld.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.platform;\n\n/**\n * Represents a world in the underlying platform (Bukkit, Fabric, etc.).\n * <p>\n * This interface serves as an abstraction layer for world-related operations,\n * allowing the core engine to interact with worlds without direct dependencies on platform-specific APIs.\n * </p>\n *\n * @since 2.0.0\n */\npublic interface PlatformWorld {\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/player/PlayerLimb.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.player;\n\nimport kr.toxicity.model.api.armor.PlayerArmor;\nimport kr.toxicity.model.api.bone.BoneItemMapper;\nimport kr.toxicity.model.api.bone.BoneRenderContext;\nimport kr.toxicity.model.api.nms.Profiled;\nimport kr.toxicity.model.api.platform.PlatformItemTransform;\nimport kr.toxicity.model.api.skin.SkinData;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\n/**\n * Player limb data\n */\n@RequiredArgsConstructor\n@Getter\npublic enum PlayerLimb {\n    /**\n     * Head\n     */\n    HEAD(\n        SkinData::head,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Right arm\n     */\n    RIGHT_ARM(\n        SkinData::rightArm,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Right forearm\n     */\n    RIGHT_FOREARM(\n        (d, _) -> d.rightForeArm(),\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Left arm\n     */\n    LEFT_ARM(\n        SkinData::leftArm,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Left forearm\n     */\n    LEFT_FOREARM(\n        (d, _) -> d.leftForeArm(),\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Hip\n     */\n    HIP(\n        SkinData::hip,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Waist\n     */\n    WAIST(\n        SkinData::waist,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Chest\n     */\n    CHEST(\n        SkinData::chest,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Right leg\n     */\n    RIGHT_LEG(\n        SkinData::rightLeg,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Right foreleg\n     */\n    RIGHT_FORELEG(\n        SkinData::rightForeLeg,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Left leg\n     */\n    LEFT_LEG(\n        SkinData::leftLeg,\n        PlatformItemTransform.FIXED\n    ),\n    /**\n     * Left foreleg\n     */\n    LEFT_FORELEG(\n        SkinData::leftForeLeg,\n        PlatformItemTransform.FIXED\n    ),\n    ;\n\n    private final @NotNull BiFunction<SkinData, PlayerArmor, TransformedItemStack> skinMapper;\n    private final @NotNull PlatformItemTransform transform;\n\n    @Getter\n    private final @NotNull LimbItemMapper itemMapper = new LimbItemMapper(this::createItem);\n\n    /**\n     * Generates transformed item from player\n     * @param context context\n     * @return item\n     */\n    public @NotNull TransformedItemStack createItem(@NotNull BoneRenderContext context) {\n        return skinMapper.apply(context.skin(), context.source() instanceof Profiled profiled ? profiled.armors() : PlayerArmor.EMPTY);\n    }\n\n    /**\n     * Limb item mapper\n     */\n    @RequiredArgsConstructor\n    public class LimbItemMapper implements BoneItemMapper {\n\n        private final Function<BoneRenderContext, TransformedItemStack> playerMapper;\n\n        @NotNull\n        @Override\n        public PlatformItemTransform transform() {\n            return transform;\n        }\n\n        @Override\n        public @NotNull TransformedItemStack apply(@NotNull BoneRenderContext context, @NotNull TransformedItemStack transformedItemStack) {\n            return playerMapper.apply(context);\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/player/PlayerSkinParts.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.player;\n\n/**\n * Player skin parts\n * @param bitmask bit mask\n */\npublic record PlayerSkinParts(int bitmask) {\n    /**\n     * The default skin parts configuration where all parts (Cape, Jacket, Sleeves, Pants, Hat) are visible.\n     */\n    public static final PlayerSkinParts DEFAULT = new PlayerSkinParts(0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40);\n\n    /**\n     * Checks if the 'Cape' part of the player's skin is enabled (visible).\n     *\n     * @return {@code true} if the cape is enabled, {@code false} otherwise.\n     */\n    public boolean isCapeEnabled() {\n        return (bitmask & 0x01) != 0;\n    }\n\n    /**\n     * Checks if the 'Jacket' part of the player's skin is enabled (visible).\n     *\n     * @return {@code true} if the jacket is enabled, {@code false} otherwise.\n     */\n    public boolean isJacketEnabled() {\n        return (bitmask & 0x02) != 0;\n    }\n\n    /**\n     * Checks if the 'Left Sleeve' part of the player's skin is enabled (visible).\n     *\n     * @return {@code true} if the left sleeve is enabled, {@code false} otherwise.\n     */\n    public boolean isLeftSleeveEnabled() {\n        return (bitmask & 0x04) != 0;\n    }\n\n    /**\n     * Checks if the 'Right Sleeve' part of the player's skin is enabled (visible).\n     *\n     * @return {@code true} if the right sleeve is enabled, {@code false} otherwise.\n     */\n    public boolean isRightSleeveEnabled() {\n        return (bitmask & 0x08) != 0;\n    }\n\n    /**\n     * Checks if the 'Left Pants Leg' part of the player's skin is enabled (visible).\n     *\n     * @return {@code true} if the left pants leg is enabled, {@code false} otherwise.\n     */\n    public boolean isLeftPantsEnabled() {\n        return (bitmask & 0x10) != 0;\n    }\n\n    /**\n     * Checks if the 'Right Pants Leg' part of the player's skin is enabled (visible).\n     *\n     * @return {@code true} if the right pants leg is enabled, {@code false} otherwise.\n     */\n    public boolean isRightPantsEnabled() {\n        return (bitmask & 0x20) != 0;\n    }\n\n    /**\n     * Checks if the 'Hat' (or head overlay) part of the player's skin is enabled (visible).\n     *\n     * @return {@code true} if the hat is enabled, {@code false} otherwise.\n     */\n    public boolean isHatEnabled() {\n        return (bitmask & 0x40) != 0;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/profile/ModelProfile.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.profile;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.platform.PlatformOfflinePlayer;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Model skin\n */\npublic interface ModelProfile {\n\n    /**\n     * Unknown skin\n     */\n    ModelProfile UNKNOWN = of(ModelProfileInfo.UNKNOWN);\n\n    /**\n     * Creates profile\n     * @param info info\n     * @return profile\n     */\n    static @NotNull ModelProfile of(@NotNull ModelProfileInfo info) {\n        return new Simple(info, ModelProfileSkin.EMPTY);\n    }\n\n    /**\n     * Creates profile\n     * @param info info\n     * @param skin skin\n     * @return profile\n     */\n    static @NotNull ModelProfile of(@NotNull ModelProfileInfo info, @NotNull ModelProfileSkin skin) {\n        return new Simple(info, skin);\n    }\n\n    /**\n     * Gets skin by player\n     * @param player player\n     * @return model profile\n     */\n    static @NotNull ModelProfile of(@NotNull PlatformPlayer player) {\n        var channel = BetterModel.platform().playerManager().player(player.uuid());\n        return channel != null ? channel.base().profile() : BetterModel.nms().profile(player);\n    }\n\n    /**\n     * Gets uncompleted profile by offline player\n     * @param offlinePlayer offline player\n     * @return uncompleted profile\n     */\n    static @NotNull Uncompleted of(@NotNull PlatformOfflinePlayer offlinePlayer) {\n        return BetterModel.platform().profileManager().supplier().supply(offlinePlayer);\n    }\n\n    /**\n     * Gets uncompleted profile by offline player's uuid\n     * @param uuid offline player's uuid\n     * @return uncompleted profile\n     */\n    static @NotNull Uncompleted of(@NotNull UUID uuid) {\n        return of(BetterModel.platform().adapter().offlinePlayer(uuid));\n    }\n\n    /**\n     * Gets info\n     * @return info\n     */\n    @NotNull ModelProfileInfo info();\n\n    /**\n     * Gets skin\n     * @return skin\n     */\n    @NotNull ModelProfileSkin skin();\n\n\n    /**\n     * Makes this profile as uncompleted\n     * @return uncompleted profile\n     */\n    default @NotNull Uncompleted asUncompleted() {\n        return new Uncompleted() {\n            @Override\n            public @NotNull ModelProfileInfo info() {\n                return ModelProfile.this.info();\n            }\n\n            @Override\n            public @NotNull CompletableFuture<ModelProfile> complete() {\n                return CompletableFuture.completedFuture(ModelProfile.this);\n            }\n        };\n    }\n\n    /**\n     * Gets player\n     * @return player\n     */\n    default @Nullable PlatformPlayer player() {\n        return BetterModel.platform().adapter().player(info().id());\n    }\n\n    /**\n     * Simple profile\n     * @param info info\n     * @param skin skin\n     */\n    record Simple(@NotNull ModelProfileInfo info, @NotNull ModelProfileSkin skin) implements ModelProfile {\n    }\n\n    /**\n     * Uncompleted profile\n     */\n    interface Uncompleted {\n\n        /**\n         * Gets info\n         * @return info\n         */\n        @NotNull ModelProfileInfo info();\n\n        /**\n         * Completes profile\n         * @return completed profile\n         */\n        @NotNull CompletableFuture<ModelProfile> complete();\n\n        /**\n         * Gets fallback profile\n         * @return profile\n         */\n        default @NotNull ModelProfile fallback() {\n            return of(info());\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/profile/ModelProfileInfo.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.profile;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Profile info\n * @param id id\n * @param name name\n */\npublic record ModelProfileInfo(@NotNull UUID id, @Nullable String name) {\n\n    /**\n     * Unknown info\n     */\n    public static final ModelProfileInfo UNKNOWN = new ModelProfileInfo(\n        UUID.fromString(\"00000000-0000-0000-0000-000000000000\"),\n        null\n    );\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/profile/ModelProfileSkin.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.profile;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.URI;\n\n/**\n * Profile skin\n * @param skin skin\n * @param cape cape\n * @param slim is slim model\n * @param raw raw textures\n */\npublic record ModelProfileSkin(\n    @Nullable URI skin,\n    @Nullable URI cape,\n    boolean slim,\n    @NotNull String raw\n) {\n\n    /**\n     * Empty skin\n     */\n    public static final ModelProfileSkin EMPTY = new ModelProfileSkin(null, null, false, \"\");\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/profile/ModelProfileSupplier.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.profile;\n\nimport kr.toxicity.model.api.platform.PlatformOfflinePlayer;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Profile supplier\n */\npublic interface ModelProfileSupplier {\n\n    /**\n     * Supplies profile\n     * @param info info\n     * @return uncompleted profile\n     */\n    @NotNull ModelProfile.Uncompleted supply(@NotNull ModelProfileInfo info);\n\n    /**\n     * Supplies profile by player\n     * @param player player\n     * @return uncompleted profile\n     */\n    default @NotNull ModelProfile.Uncompleted supply(@NotNull PlatformOfflinePlayer player) {\n        return supply(new ModelProfileInfo(player.uuid(), player.name()));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/scheduler/ModelScheduler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.scheduler;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * A scheduler of BetterModel\n */\npublic interface ModelScheduler {\n\n    /**\n     * Runs async task\n     * @param runnable task\n     * @return scheduled task\n     */\n    @NotNull ModelTask asyncTask(@NotNull Runnable runnable);\n\n    /**\n     * Runs async task\n     * @param delay delay\n     * @param runnable task\n     * @return scheduled task\n     */\n    @NotNull ModelTask asyncTaskLater(long delay, @NotNull Runnable runnable);\n\n    /**\n     * Runs async task\n     * @param delay delay\n     * @param period period\n     * @param runnable task\n     * @return scheduled task\n     */\n    @NotNull ModelTask asyncTaskTimer(long delay, long period, @NotNull Runnable runnable);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/scheduler/ModelTask.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.scheduler;\n\n/**\n * A scheduled task of BetterModel\n */\npublic interface ModelTask {\n\n    /**\n     * Checks this task is canceled\n     * @return whether to cancel\n     */\n    boolean isCancelled();\n\n    /**\n     * Cancels this task\n     */\n    void cancel();\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/script/AnimationScript.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.script;\n\nimport kr.toxicity.model.api.tracker.Tracker;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * Animation script\n */\npublic interface AnimationScript extends Consumer<Tracker> {\n\n    /**\n     * Empty script\n     */\n    AnimationScript EMPTY = of(_ -> {});\n\n    @Override\n    void accept(@NotNull Tracker tracker);\n\n    /**\n     * Checks this script should be called in tick thread\n     * @return requires tick thread\n     */\n    boolean isSync();\n\n    /**\n     * Creates a timed script\n     * @param time time\n     * @return timed script\n     */\n    default @NotNull TimeScript time(float time) {\n        return new TimeScript(time, this);\n    }\n\n    /**\n     * Creates script\n     * @param source consumer\n     * @return script\n     */\n    static @NotNull AnimationScript of(@NotNull Consumer<Tracker> source) {\n        return of(false, source);\n    }\n\n    /**\n     * Creates script\n     * @param isSync should be called in tick thread\n     * @param source consumer\n     * @return script\n     */\n    static @NotNull AnimationScript of(boolean isSync, @NotNull Consumer<Tracker> source) {\n        Objects.requireNonNull(source);\n        return new AnimationScript() {\n            @Override\n            public boolean isSync() {\n                return isSync;\n            }\n\n            @Override\n            public void accept(@NotNull Tracker tracker) {\n                source.accept(tracker);\n            }\n        };\n    }\n\n    /**\n     * Sums a script list to one\n     * @param scriptList list of a script\n     * @return merged script\n     */\n    static @NotNull AnimationScript of(@NotNull List<AnimationScript> scriptList) {\n        return switch (scriptList.size()) {\n            case 0 -> EMPTY;\n            case 1 -> scriptList.getFirst();\n            default -> {\n                var sync = false;\n                Consumer<Tracker> consumer = _ -> {};\n                for (AnimationScript entityScript : scriptList) {\n                    sync |= entityScript.isSync();\n                    consumer = consumer.andThen(entityScript);\n                }\n                yield of(sync, consumer);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/script/BlueprintScript.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.script;\n\nimport kr.toxicity.model.api.animation.AnimationIterator;\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport kr.toxicity.model.api.animation.TimedStorage;\nimport kr.toxicity.model.api.data.raw.ModelAnimation;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\n\n/**\n * A script data of blueprint.\n * @param name script name\n * @param type type\n * @param length playtime\n * @param scripts scripts\n */\n@ApiStatus.Internal\npublic record BlueprintScript(@NotNull String name, @NotNull AnimationIterator.Type type, float length, @NotNull List<TimeScript> scripts) {\n\n    /**\n     * Creates empty script\n     * @param animation animation\n     * @return empty script\n     */\n    public static @NotNull BlueprintScript fromEmpty(@NotNull ModelAnimation animation) {\n        return new BlueprintScript(\n            animation.name(),\n            animation.loop(),\n            animation.length(),\n            List.of(TimeScript.EMPTY, AnimationScript.EMPTY.time(animation.length()))\n        );\n    }\n\n    /**\n     * Creates animation iterator of this script\n     * @param modifier modifier\n     * @return animation iterator\n     */\n    public @NotNull AnimationIterator<TimeScript> iterator(@NotNull AnimationModifier modifier) {\n        return modifier.type(type).create(TimedStorage.listOf(scripts));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/script/ScriptBuilder.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.script;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.math.BigDecimal;\nimport java.util.Map;\n\n/**\n * Script parser\n */\n@FunctionalInterface\npublic interface ScriptBuilder {\n    /**\n     * Build an entity script by data\n     * @param data script data\n     * @return script\n     */\n    @NotNull AnimationScript build(@NotNull ScriptData data);\n\n    record ScriptData(\n        @Nullable String args,\n        @NotNull ScriptMetaData metadata\n    ) {}\n\n    interface ScriptMetaData {\n\n        @NotNull @Unmodifiable\n        Map<String, String> toMap();\n\n        default @Nullable Boolean asBoolean(@NotNull String key) {\n            var get = toMap().get(key);\n            if (get == null) return null;\n            return switch (get) {\n                case \"true\" -> true;\n                case \"false\" -> false;\n                default -> null;\n            };\n        }\n\n        default @Nullable Number asNumber(@NotNull String key) {\n            var get = toMap().get(key);\n            if (get == null) return null;\n            try {\n                return new BigDecimal(get);\n            } catch (NumberFormatException e) {\n                return null;\n            }\n        }\n\n        default @Nullable String asString(@NotNull String key) {\n            return toMap().get(key);\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/script/TimeScript.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.script;\n\nimport kr.toxicity.model.api.animation.Timed;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Script with time\n * @param time time\n * @param script source script\n */\npublic record TimeScript(float time, @NotNull AnimationScript script) implements AnimationScript, Timed {\n\n    public static final TimeScript EMPTY = AnimationScript.EMPTY.time(0);\n\n    @Override\n    public boolean isSync() {\n        return script.isSync();\n    }\n\n    @Override\n    public void accept(@NotNull Tracker tracker) {\n        script.accept(tracker);\n    }\n\n    public @NotNull TimeScript time(float newTime) {\n        if (time == newTime) return this;\n        return new TimeScript(newTime, script);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/skin/SkinData.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.skin;\n\nimport kr.toxicity.model.api.armor.PlayerArmor;\nimport kr.toxicity.model.api.profile.ModelProfile;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * Skin data of player.\n */\npublic interface SkinData {\n\n    /**\n     * Gets model skin\n     * @return skin\n     */\n    @NotNull ModelProfile profile();\n\n    /**\n     * Gets head part\n     * @param armor armor\n     * @return head\n     */\n    @NotNull TransformedItemStack head(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets hip part\n     * @param armor armor\n     * @return hip\n     */\n    @NotNull TransformedItemStack hip(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets waist part\n     * @param armor armor\n     * @return waist\n     */\n    @NotNull TransformedItemStack waist(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets chest part\n     * @param armor armor\n     * @return chest\n     */\n    @NotNull TransformedItemStack chest(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets left arm part\n     * @param armor armor\n     * @return left arm\n     */\n    @NotNull TransformedItemStack leftArm(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets left forearm part\n     * @return left forearm\n     */\n    @NotNull TransformedItemStack leftForeArm();\n\n    /**\n     * Gets right arm part\n     * @param armor armor\n     * @return right arm\n     */\n    @NotNull TransformedItemStack rightArm(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets right forearm part\n     * @return right forearm\n     */\n    @NotNull TransformedItemStack rightForeArm();\n\n    /**\n     * Gets left leg part\n     * @param armor armor\n     * @return left leg\n     */\n    @NotNull TransformedItemStack leftLeg(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets left foreleg part\n     * @param armor armor\n     * @return left foreleg\n     */\n    @NotNull TransformedItemStack leftForeLeg(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets right leg part\n     * @param armor armor\n     * @return right leg\n     */\n    @NotNull TransformedItemStack rightLeg(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets right foreleg part\n     * @param armor armor\n     * @return right foreleg\n     */\n    @NotNull TransformedItemStack rightForeLeg(@NotNull PlayerArmor armor);\n\n    /**\n     * Gets cape\n     * @param armor armor\n     * @return cape\n     */\n    @Nullable TransformedItemStack cape(@NotNull PlayerArmor armor);\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/DummyTracker.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport kr.toxicity.model.api.data.renderer.RenderPipeline;\nimport kr.toxicity.model.api.event.CreateDummyTrackerEvent;\nimport kr.toxicity.model.api.nms.PlayerChannelHandler;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.util.EventUtil;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * A tracker implementation that is not attached to any entity.\n * <p>\n * Dummy trackers are positioned at a fixed location in the world and can be moved manually.\n * They are useful for static models or models controlled entirely by scripts/plugins/mods.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class DummyTracker extends Tracker {\n\n    private volatile PlatformLocation location;\n\n    /**\n     * Creates a new dummy tracker.\n     *\n     * @param location the initial location\n     * @param pipeline the render pipeline\n     * @param modifier the tracker modifier\n     * @param preUpdateConsumer a consumer to run before the first update\n     * @since 1.15.2\n     */\n    public DummyTracker(@NotNull PlatformLocation location, @NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<DummyTracker> preUpdateConsumer) {\n        super(pipeline, modifier);\n        this.location = location;\n        animate(\"spawn\", AnimationModifier.DEFAULT_WITH_PLAY_ONCE);\n        pipeline.scale(() -> scaler().scale(this));\n        rotation(() -> new ModelRotation(this.location.pitch(), this.location.yaw()));\n        preUpdateConsumer.accept(this);\n        EventUtil.call(CreateDummyTrackerEvent.class, () -> new CreateDummyTrackerEvent(this));\n    }\n\n    /**\n     * Moves the model to a new location.\n     *\n     * @param location the new location\n     * @since 1.15.2\n     */\n    public void location(@NotNull PlatformLocation location) {\n        Objects.requireNonNull(location, \"location\");\n        if (this.location.equals(location)) return;\n        synchronized (this) {\n            this.location = location;\n            var bundler = pipeline.createBundler();\n            pipeline.forEach(b -> b.teleport(location, bundler));\n            if (bundler.isNotEmpty()) pipeline.allPlayer().map(PlayerChannelHandler::player).forEach(bundler::send);\n        }\n    }\n\n    /**\n     * Returns the current location of the tracker.\n     *\n     * @return the location\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull PlatformLocation location() {\n        return location;\n    }\n\n    /**\n     * Spawns the model for a specific player.\n     *\n     * @param player the target player\n     * @since 1.15.2\n     */\n    public void spawn(@NotNull PlatformPlayer player) {\n        var bundler = pipeline.createBundler();\n        spawn(player, bundler);\n        bundler.send(player);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/EntityBodyRotator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.util.FunctionUtil;\nimport kr.toxicity.model.api.util.MathUtil;\nimport kr.toxicity.model.api.util.lazy.LazyFloatProvider;\nimport lombok.AllArgsConstructor;\nimport lombok.Setter;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * Manages the body and head rotation logic for an entity tracker.\n * <p>\n * This class handles the complex interactions between body yaw, head yaw, and pitch,\n * including smoothing, clamping, and player-specific behaviors.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class EntityBodyRotator {\n\n    private static final float DEGREE_EPSILON = 1 / MathUtil.DEGREES_TO_PACKED_BYTE;\n\n    private final EntityTrackerRegistry registry;\n    private final BaseEntity entity;\n    private final LazyFloatProvider provider;\n    private final Supplier<Quaternionf> headSupplier;\n    private final Supplier<ModelRotation> bodySupplier;\n    private final AtomicBoolean rotationLock = new AtomicBoolean();\n    private int tick;\n    private final Quaternionf headRotation = new Quaternionf();\n    private ModelRotation rotation;\n    private volatile boolean headUneven;\n    private volatile boolean bodyUneven;\n    private volatile boolean playerMode;\n    private volatile float minBody;\n    private volatile float maxBody;\n    private volatile float minHead;\n    private volatile float maxHead;\n    private volatile float stable;\n    private volatile int rotationDuration;\n    private volatile int rotationDelay;\n\n    static @NotNull RotatorData defaultData() {\n        return new RotatorData(\n            false,\n            false,\n            false,\n            -75,\n            75,\n            -75,\n            75,\n            15,\n            10,\n            10\n        );\n    }\n\n    EntityBodyRotator(@NotNull EntityTrackerRegistry registry) {\n        this.registry = registry;\n        this.entity = registry.entity();\n        this.rotation = new ModelRotation(\n            entity.pitch(),\n            entity.yaw()\n        );\n        this.provider = new LazyFloatProvider(entity.yaw(), () -> rotationDuration * MathUtil.MINECRAFT_TICK_MILLS);\n        var vector = new Vector3f();\n        var vectorSupplier = LazyFloatProvider.ofVector(() -> 3 * MathUtil.MINECRAFT_TICK_MILLS, () -> vector.set(\n            clampHead(entity.pitch()),\n            clampHead(wrapDegrees(bodyRotation().y() - entity.headYaw())),\n            0\n        ));\n        headSupplier = FunctionUtil.throttleTick(Tracker.TRACKER_TICK_INTERVAL, () -> MathUtil.toQuaternion(vectorSupplier.get(), headRotation));\n        bodySupplier = FunctionUtil.throttleTick(() -> new ModelRotation(\n            entity.pitch(),\n            bodyRotation0()\n        ));\n        reset();\n    }\n\n    private float clampHead(float value) {\n        return Math.clamp(value, headUneven ? minHead : -maxHead, maxHead);\n    }\n\n    private float clampBody(float value, float compare) {\n        return Math.clamp(value, compare + (bodyUneven ? minBody : -maxBody), compare + maxBody);\n    }\n\n    /**\n     * Locks or unlocks the rotation updates.\n     *\n     * @param lock true to lock, false to unlock\n     * @return true if the state changed\n     * @since 1.15.2\n     */\n    public boolean lockRotation(boolean lock) {\n        return rotationLock.compareAndSet(!lock, lock);\n    }\n\n    @NotNull ModelRotation bodyRotation() {\n        return rotationLock.get() ? rotation : (rotation = bodySupplier.get());\n    }\n\n    private float bodyRotation0() {\n        if (playerMode) return entity.bodyYaw();\n        if (registry.hasControllingPassenger()) return entity.yaw();\n        if (entity.onWalk()) {\n            tick = rotationDelay;\n            return stableBodyYaw();\n        } else if (MathUtil.isSimilar(entity.headYaw(), rotation.y(), DEGREE_EPSILON)) {\n            tick = 0;\n            return entity.headYaw();\n        } else if (++tick > rotationDelay) {\n            var headYaw = entity.headYaw();\n            var providedYaw = provider.updateAndGet(headYaw);\n            return wrapDegrees(clampBody(providedYaw, headYaw));\n        }\n        provider.storedValue(rotation.y());\n        return rotation.y();\n    }\n\n    private float stableBodyYaw() {\n        var bodyYaw = rotation.y();\n        var yaw = entity.yaw();\n        var minStable = yaw - stable;\n        var maxStable = yaw + stable;\n        return wrapDegrees(Math.clamp(bodyYaw, Math.min(minStable, maxStable), Math.max(minStable, maxStable)));\n    }\n\n    private static float wrapDegrees(float value) {\n        var f = value % 360.0F;\n        if (f >= 180.0F) f -= 360.0F;\n        if (f < -180.0F) f += 360.0F;\n        return f;\n    }\n\n    @NotNull Quaternionf headRotation() {\n        return rotationLock.get() ? headRotation : headSupplier.get();\n    }\n\n    /**\n     * Configures the rotator using a consumer.\n     *\n     * @param consumer the configuration consumer\n     * @since 1.15.2\n     */\n    public void setValue(@NotNull Consumer<RotatorData> consumer) {\n        Objects.requireNonNull(consumer);\n        var data = createData();\n        consumer.accept(data);\n        setValue(data);\n    }\n\n    synchronized void setValue(@NotNull RotatorData data) {\n        data.set(this);\n    }\n\n    /**\n     * Resets the rotator to default settings.\n     *\n     * @since 1.15.2\n     */\n    public void reset() {\n        setValue(defaultData());\n    }\n\n    synchronized @NotNull RotatorData createData() {\n        return new RotatorData(\n            headUneven,\n            bodyUneven,\n            playerMode,\n            minBody,\n            maxBody,\n            minHead,\n            maxHead,\n            stable,\n            rotationDuration,\n            rotationDelay\n        );\n    }\n\n    /**\n     * Configuration data for the entity body rotator.\n     *\n     * @since 1.15.2\n     */\n    @Setter\n    @AllArgsConstructor\n    public static final class RotatorData {\n\n        @SerializedName(\"head_uneven\")\n        private boolean headUneven;\n        @SerializedName(\"body_uneven\")\n        private boolean bodyUneven;\n        @SerializedName(\"player_mode\")\n        private boolean playerMode;\n        @SerializedName(\"min_body\")\n        private float minBody;\n        @SerializedName(\"max_body\")\n        private float maxBody;\n        @SerializedName(\"min_head\")\n        private float minHead;\n        @SerializedName(\"max_head\")\n        private float maxHead;\n        @SerializedName(\"stable\")\n        private float stable;\n        @SerializedName(\"rotation_duration\")\n        private int rotationDuration;\n        @SerializedName(\"rotation_delay\")\n        private int rotationDelay;\n\n        private void set(@NotNull EntityBodyRotator rotator) {\n            rotator.headUneven = headUneven;\n            rotator.bodyUneven = bodyUneven;\n            rotator.playerMode = playerMode;\n            rotator.minBody = Math.min(minBody, maxBody);\n            rotator.maxBody = Math.max(minBody, maxBody);\n            rotator.minHead = Math.min(minHead, maxHead);\n            rotator.maxHead = Math.max(minHead, maxHead);\n            rotator.stable = Math.max(stable, 0);\n            rotator.rotationDuration = Math.max(rotationDuration, 0);\n            rotator.rotationDelay = Math.max(rotationDelay, 0);\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/EntityHideOption.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport com.google.gson.JsonArray;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.stream.Stream;\n\n/**\n * Configuration for hiding various visual aspects of an entity.\n * <p>\n * This record allows selective hiding of equipment, fire effects, the entity body itself, and glowing effects.\n * </p>\n *\n * @param equipment whether to hide equipment\n * @param fire whether to hide burning state\n * @param visibility whether to hide entity's body\n * @param glowing whether to hide entity's glowing state\n * @since 1.15.2\n */\npublic record EntityHideOption(\n    boolean equipment,\n    boolean fire,\n    boolean visibility,\n    boolean glowing\n) {\n    /**\n     * Default option (hides everything).\n     * @since 1.15.2\n     */\n    public static final EntityHideOption DEFAULT = new EntityHideOption(\n        true,\n        true,\n        true,\n        true\n    );\n    /**\n     * Disabled option (hides nothing).\n     * @since 1.15.2\n     */\n    public static final EntityHideOption FALSE = builder().build();\n\n    /**\n     * Composites multiple options into a single one using OR logic.\n     *\n     * @param options the stream of options\n     * @return the composited option\n     * @since 1.15.2\n     */\n    public static @NotNull EntityHideOption composite(@NotNull Stream<EntityHideOption> options) {\n        return builder()\n            .composite(options)\n            .build();\n    }\n\n    /**\n     * Deserializes hide option from a JSON array.\n     *\n     * @param array the JSON array\n     * @return the option\n     * @since 1.15.2\n     */\n    public static @NotNull EntityHideOption deserialize(@NotNull JsonArray array) {\n        return new EntityHideOption(\n            array.get(0).getAsBoolean(),\n            array.get(1).getAsBoolean(),\n            array.get(2).getAsBoolean(),\n            array.get(3).getAsBoolean()\n        );\n    }\n\n    /**\n     * Serializes hide option to a JSON array.\n     *\n     * @return the JSON array\n     * @since 1.15.2\n     */\n    public @NotNull JsonArray serialize() {\n        var array = new JsonArray(4);\n        array.add(equipment);\n        array.add(fire);\n        array.add(visibility);\n        array.add(glowing);\n        return array;\n    }\n\n    /**\n     * Creates a new builder for {@link EntityHideOption}.\n     *\n     * @return the builder\n     * @since 1.15.2\n     */\n    public static @NotNull Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Builder for {@link EntityHideOption}.\n     *\n     * @since 1.15.2\n     */\n    public static final class Builder {\n        private boolean equipment;\n        private boolean fire;\n        private boolean visibility;\n        private boolean glowing;\n\n        /**\n         * Private initializer\n         */\n        private Builder() {\n\n        }\n\n        /**\n         * Composites multiple options into this builder.\n         *\n         * @param options the stream of options\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder composite(@NotNull Stream<EntityHideOption> options) {\n            options.forEach(this::or);\n            return this;\n        }\n\n        /**\n         * Merges another hide option using OR logic.\n         *\n         * @param option the option to merge\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder or(@NotNull EntityHideOption option) {\n            equipment |= option.equipment;\n            fire |= option.fire;\n            visibility |= option.visibility;\n            glowing |= option.glowing;\n            return this;\n        }\n\n        /**\n         * Builds the {@link EntityHideOption}.\n         *\n         * @return the created option\n         * @since 1.15.2\n         */\n        public @NotNull EntityHideOption build() {\n            return new EntityHideOption(\n                equipment,\n                fire,\n                visibility,\n                glowing\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/EntityTracker.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.bone.BoneMovement;\nimport kr.toxicity.model.api.bone.BoneTags;\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.data.renderer.RenderPipeline;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.entity.BasePlayer;\nimport kr.toxicity.model.api.event.CreateEntityTrackerEvent;\nimport kr.toxicity.model.api.event.DismountModelEvent;\nimport kr.toxicity.model.api.event.MountModelEvent;\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.nms.HitBoxListener;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.util.EventUtil;\nimport kr.toxicity.model.api.util.FunctionUtil;\nimport kr.toxicity.model.api.util.MathUtil;\nimport kr.toxicity.model.api.util.function.BonePredicate;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.joml.Quaternionf;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * A tracker implementation that is attached to a living entity.\n * <p>\n * This tracker synchronizes the model's position, rotation, and animations with the target entity.\n * It handles hitboxes, nametags, damage tinting, and mounting mechanics.\n * </p>\n *\n * @since 1.15.2\n */\npublic class EntityTracker extends Tracker {\n\n    private static final BonePredicate CREATE_HITBOX_PREDICATE = BonePredicate.name(\"hitbox\")\n        .or(BonePredicate.tag(BoneTags.HITBOX))\n        .or(b -> b.getGroup().getMountController().canMount())\n        .notSet();\n\n    private static final BonePredicate CREATE_NAMETAG_PREDICATE = BonePredicate.tag(BoneTags.TAG, BoneTags.MOB_TAG, BoneTags.PLAYER_TAG).notSet();\n    private static final BonePredicate HITBOX_REFRESH_PREDICATE = BonePredicate.from(r -> r.getHitBox() != null);\n    private static final BonePredicate HEAD_PREDICATE = BonePredicate.tag(BoneTags.HEAD).notSet();\n    private static final BonePredicate HEAD_WITH_CHILDREN_PREDICATE = BonePredicate.tag(BoneTags.HEAD_WITH_CHILDREN).withChildren();\n\n    private final EntityTrackerRegistry registry;\n\n    private final AtomicInteger damageTintValue = new AtomicInteger(0xFF8080);\n    private final AtomicLong damageTint = new AtomicLong(-1);\n    private final Set<UUID> markForSpawn = ConcurrentHashMap.newKeySet();\n\n    private final EntityBodyRotator bodyRotator;\n    private EntityHideOption hideOption = EntityHideOption.DEFAULT;\n\n    private volatile PlatformLocation location;\n\n    /**\n     * Creates a new entity tracker.\n     *\n     * @param registry the entity tracker registry\n     * @param pipeline the render pipeline\n     * @param modifier the tracker modifier\n     * @param preUpdateConsumer a consumer to run before the first update\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public EntityTracker(@NotNull EntityTrackerRegistry registry, @NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        super(pipeline, modifier);\n        this.registry = registry;\n        this.location = registry.entity().location();\n        bodyRotator = new EntityBodyRotator(registry);\n\n        var entity = registry.entity();\n        var scale = FunctionUtil.throttleTickFloat(() -> scaler().scale(this));\n        //Shadow\n        Optional.ofNullable(bone(\"shadow\"))\n            .ifPresent(bone -> {\n                var box = bone.getGroup().getHitBox();\n                if (box == null) return;\n                var shadow = BetterModel.nms().create(entity.location(), d -> {\n                    if (entity instanceof BasePlayer) d.moveDuration(1);\n                });\n                var baseScale = (float) (box.x() + box.z()) / 4F;\n                var posCache = new BoneMovement();\n                tick(((_, s) -> {\n                    var wPos = bone.hitBoxPosition(posCache);\n                    shadow.shadowRadius(scale.getAsFloat() * baseScale);\n                    shadow.syncPotionEffect(entity);\n                    shadow.syncPosition(location().add(wPos.x, wPos.y, wPos.z));\n                    shadow.sendDirtyEntityData(s.getDataBundler());\n                    shadow.sendPosition(entity, s.getTickBundler());\n                }));\n                pipeline.spawnPacketHandler(shadow::spawnWithEntityData);\n                pipeline.showPacketHandler(shadow::spawnWithEntityData);\n                pipeline.despawnPacketHandler(shadow::remove);\n                pipeline.hidePacketHandler(shadow::remove);\n            });\n\n        //Animation\n        pipeline.defaultPosition(vec -> entity.passengerPosition(vec).mul(-1));\n        pipeline.scale(scale);\n        Function<Quaternionf, Quaternionf> headRotator = r -> r.mul(bodyRotator.headRotation());\n\n        pipeline.addGlobalRotModifier(HEAD_PREDICATE, headRotator);\n        pipeline.addGlobalRotModifier(HEAD_WITH_CHILDREN_PREDICATE, headRotator);\n\n        createNametag(CREATE_NAMETAG_PREDICATE, (bone, tag) -> {\n            if (bone.name().tagged(BoneTags.PLAYER_TAG)) {\n                tag.alwaysVisible(true);\n            } else if (bone.name().tagged(BoneTags.MOB_TAG)) {\n                tag.alwaysVisible(false);\n            } else tag.alwaysVisible(entity instanceof BasePlayer);\n            tag.component(entity.customName());\n        });\n        listenHitBox((b, l) -> l\n            .create(h -> registry.hitBoxCache.put(h.uuid(), h))\n            .remove(h -> registry.hitBoxCache.remove(h.uuid()))\n            .mount((h, e) -> {\n                registry.mountedHitBoxCache.put(e.uuid(), new EntityTrackerRegistry.MountedHitBox(e, h));\n                EventUtil.call(MountModelEvent.class, () -> new MountModelEvent(this, b, h, e));\n            })\n            .dismount((h, e) -> {\n                registry.mountedHitBoxCache.remove(e.uuid());\n                EventUtil.call(DismountModelEvent.class, () -> new DismountModelEvent(this, b, h, e));\n            }));\n        entity.platform().task(() -> {\n            if (isClosed()) return;\n            createHitBox(null, CREATE_HITBOX_PREDICATE);\n        });\n        tick((_, _) -> updateLocation());\n        tick((_, _) -> {\n            if (damageTint.getAndDecrement() == 0) update(TrackerUpdateAction.previousTint());\n        });\n        rotation(bodyRotator::bodyRotation);\n        preUpdateConsumer.accept(this);\n        EventUtil.call(CreateEntityTrackerEvent.class, () -> new CreateEntityTrackerEvent(this));\n    }\n\n    @Override\n    public @NotNull ModelRotation rotation() {\n        return sourceEntity().dead() ? pipeline.getRotation() : super.rotation();\n    }\n\n    /**\n     * Synchronizes the tracker with the base entity's data asynchronously.\n     *\n     * @since 1.15.2\n     */\n    public void updateBaseEntity() {\n        if (sourceEntity().dead() || isClosed()) return;\n        BetterModel.platform().scheduler().asyncTaskLater(1, () -> {\n            var entity = sourceEntity();\n            pipeline.forEach(bone -> bone.applyAtDisplay(BonePredicate.TRUE, display -> display.syncPotionEffect(entity)));\n            updateLocation();\n            forceUpdate(true);\n        });\n    }\n\n    private void updateLocation() {\n        var loc = sourceEntity().location();\n        if (this.location.distanceSquared(loc) < MathUtil.VECTOR_COMPARISON_EPSILON_SQ) return;\n        synchronized (this) {\n            this.location = loc;\n        }\n        pipeline.forEach(bone -> bone.applyAtDisplay(BonePredicate.TRUE, display -> display.syncPosition(loc)));\n    }\n\n    /**\n     * Returns the entity tracker registry associated with this tracker.\n     *\n     * @return the registry\n     * @since 1.15.2\n     */\n    public @NotNull EntityTrackerRegistry registry() {\n        return registry;\n    }\n\n    /**\n     * Creates hitboxes for the entity based on a predicate.\n     *\n     * @param listener the hitbox listener\n     * @param predicate the bone predicate\n     * @return true if any hitboxes were created\n     * @since 1.15.2\n     */\n    public boolean createHitBox(@Nullable HitBoxListener listener, @NotNull BonePredicate predicate) {\n        return createHitBox(registry.entity(), listener, predicate);\n    }\n\n    /**\n     * Retrieves or creates a hitbox for the entity.\n     *\n     * @param listener the hitbox listener\n     * @param predicate the bone predicate\n     * @return the hitbox, or null if not found/created\n     * @since 1.15.2\n     */\n    public @Nullable HitBox hitbox(@Nullable HitBoxListener listener, @NotNull Predicate<RenderedBone> predicate) {\n        return hitbox(registry.entity(), listener, predicate);\n    }\n\n    /**\n     * Returns the current damage tint color value.\n     *\n     * @return the hex color value\n     * @since 1.15.2\n     */\n    public int damageTintValue() {\n        return damageTintValue.get();\n    }\n\n    /**\n     * Sets the damage tint color value.\n     *\n     * @param tint the hex color value\n     * @since 1.15.2\n     */\n    public void damageTintValue(int tint) {\n        damageTintValue.set(tint);\n    }\n\n    /**\n     * Triggers the damage tint effect if enabled.\n     *\n     * @since 1.15.2\n     */\n    public void damageTint() {\n        if (!modifier().damageTint()) return;\n        var get = damageTint.get();\n        if (get < 0 && damageTint.compareAndSet(get, 10)) task(() -> update(TrackerUpdateAction.tint(damageTintValue())));\n    }\n\n    @Override\n    public void despawn() {\n        if (sourceEntity().dead()) {\n            close(CloseReason.DESPAWN);\n            return;\n        }\n        super.despawn();\n    }\n\n    @Override\n    public @NotNull PlatformLocation location() {\n        return location;\n    }\n\n    /**\n     * Returns the source entity being tracked.\n     *\n     * @return the source entity\n     * @since 1.15.2\n     */\n    public @NotNull BaseEntity sourceEntity() {\n        return registry.entity();\n    }\n\n    /**\n     * Cancels the active damage tint effect.\n     *\n     * @since 1.15.2\n     */\n    public void cancelDamageTint() {\n        damageTint.set(-1);\n    }\n\n    /**\n     * Refreshes the tracker, updating entity data and hitboxes.\n     *\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public void refresh() {\n        updateLocation();\n        registry.entity().platform().task(() -> createHitBox(null, HITBOX_REFRESH_PREDICATE));\n    }\n\n    /**\n     * Marks a player for spawning the model.\n     *\n     * @param player the player\n     * @return true if the player was added\n     * @since 1.15.2\n     */\n    public boolean markPlayerForSpawn(@NotNull PlatformPlayer player) {\n        return markForSpawn.add(player.uuid());\n    }\n\n    /**\n     * Marks a set of players for spawning the model.\n     *\n     * @param uuids the set of player UUIDs\n     * @return true if any players were added\n     * @since 1.15.2\n     */\n    public boolean markPlayerForSpawn(@NotNull Set<UUID> uuids) {\n        return markForSpawn.addAll(uuids);\n    }\n\n    /**\n     * Unmarks a player for spawning the model.\n     *\n     * @param player the player\n     * @return true if the player was removed\n     * @since 1.15.2\n     */\n    public boolean unmarkPlayerForSpawn(@NotNull PlatformPlayer player) {\n        return markForSpawn.remove(player.uuid());\n    }\n\n    /**\n     * Converts the current tracker state to a {@link TrackerData} object.\n     *\n     * @return the tracker data\n     * @since 1.15.2\n     */\n    public @NotNull TrackerData asTrackerData() {\n        return new TrackerData(\n            name(),\n            scaler,\n            rotator,\n            modifier,\n            bodyRotator.createData(),\n            hideOption,\n            markForSpawn\n        );\n    }\n\n    /**\n     * Returns the entity body rotator.\n     *\n     * @return the body rotator\n     * @since 1.15.2\n     */\n    public @NotNull EntityBodyRotator bodyRotator() {\n        return bodyRotator;\n    }\n\n    /**\n     * Checks if the model can be spawned for a specific player.\n     *\n     * @param player the player\n     * @return true if allowed\n     * @since 1.15.2\n     */\n    public boolean canBeSpawnedAt(@NotNull PlatformPlayer player) {\n        return markForSpawn.isEmpty() || markForSpawn.contains(player.uuid());\n    }\n\n    /**\n     * Returns the hide option for this tracker.\n     *\n     * @return the hide option\n     * @since 1.15.2\n     */\n    public @NotNull EntityHideOption hideOption() {\n        return hideOption;\n    }\n\n    /**\n     * Sets the hide option for this tracker.\n     *\n     * @param hideOption the new hide option\n     * @since 1.15.2\n     */\n    public void hideOption(@NotNull EntityHideOption hideOption) {\n        this.hideOption = Objects.requireNonNull(hideOption);\n    }\n\n    /**\n     * Checks if this tracker's data can be saved.\n     *\n     * @return true if saveable\n     * @since 1.15.2\n     */\n    public boolean canBeSaved() {\n        return pipeline.getParent().type().isCanBeSaved();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/EntityTrackerRegistry.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonParser;\nimport it.unimi.dsi.fastutil.ints.Int2ReferenceMap;\nimport it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;\nimport it.unimi.dsi.fastutil.objects.Object2ReferenceMap;\nimport it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.config.DebugConfig;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.entity.BasePlayer;\nimport kr.toxicity.model.api.nms.HitBox;\nimport kr.toxicity.model.api.nms.ModelDisplay;\nimport kr.toxicity.model.api.nms.PacketBundler;\nimport kr.toxicity.model.api.nms.PlayerChannelHandler;\nimport kr.toxicity.model.api.platform.PlatformEntity;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.util.CollectionUtil;\nimport kr.toxicity.model.api.util.FunctionUtil;\nimport kr.toxicity.model.api.util.LogUtil;\nimport kr.toxicity.model.api.util.function.FloatSupplier;\nimport kr.toxicity.model.api.util.lock.DuplexLock;\nimport lombok.RequiredArgsConstructor;\nimport lombok.ToString;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentNavigableMap;\nimport java.util.concurrent.ConcurrentSkipListMap;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.BooleanSupplier;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\n/**\n * Manages all entity trackers for a specific entity.\n * <p>\n * This registry handles the lifecycle of trackers attached to an entity, including loading, saving,\n * spawning, and despawning. It acts as a central hub for accessing and manipulating models on an entity.\n * </p>\n *\n * @since 1.15.2\n */\n@ToString(onlyExplicitlyIncluded = true)\npublic final class EntityTrackerRegistry {\n\n    private static final Object2ReferenceMap<UUID, EntityTrackerRegistry> UUID_REGISTRY_MAP = new Object2ReferenceOpenHashMap<>();\n    private static final Int2ReferenceMap<EntityTrackerRegistry> ID_REGISTRY_MAP = new Int2ReferenceOpenHashMap<>();\n    private static final DuplexLock REGISTRY_LOCK = new DuplexLock();\n\n    @ToString.Include\n    private final AtomicBoolean closed = new AtomicBoolean();\n    private final AtomicBoolean loaded = new AtomicBoolean();\n    @ToString.Include\n    private final BaseEntity entity;\n    private final int id;\n    private final UUID uuid;\n    private final ConcurrentNavigableMap<String, EntityTracker> trackerMap = new ConcurrentSkipListMap<>();\n    @ToString.Include\n    private final Collection<EntityTracker> trackers = Collections.unmodifiableCollection(trackerMap.values());\n    private final Map<UUID, PlayerChannelCache> viewedPlayerMap = new ConcurrentHashMap<>();\n\n    final AnimationProperty animationProperty;\n\n    final Map<UUID, HitBox> hitBoxCache = new ConcurrentHashMap<>();\n    private final Collection<HitBox> hitBox = Collections.unmodifiableCollection(hitBoxCache.values());\n    final Map<UUID, MountedHitBox> mountedHitBoxCache = new ConcurrentHashMap<>();\n    private final Map<UUID, MountedHitBox> mountedHitBox = Collections.unmodifiableMap(mountedHitBoxCache);\n\n    /**\n     * Retrieves a registry by entity UUID.\n     *\n     * @param uuid the entity UUID\n     * @return the registry, or null if not found\n     * @since 1.15.2\n     */\n    public static @Nullable EntityTrackerRegistry registry(@NotNull UUID uuid) {\n        return REGISTRY_LOCK.accessToReadLock(() -> UUID_REGISTRY_MAP.get(uuid));\n    }\n\n    /**\n     * Retrieves a registry by entity ID.\n     *\n     * @param id the entity ID\n     * @return the registry, or null if not found\n     * @since 1.15.2\n     */\n    public static @Nullable EntityTrackerRegistry registry(int id) {\n        return REGISTRY_LOCK.accessToReadLock(() -> ID_REGISTRY_MAP.get(id));\n    }\n\n    /**\n     * Retrieves a registry for a base entity.\n     *\n     * @param entity the base entity\n     * @return the registry, or null if the entity has no model data\n     * @since 1.15.2\n     */\n    public static @Nullable EntityTrackerRegistry registry(@NotNull BaseEntity entity) {\n        var get = registry(entity.uuid());\n        if (get != null) return get;\n        return entity.hasModelData() ? create(entity) : null;\n    }\n\n    /**\n     * Iterates over all active registries.\n     *\n     * @param consumer the consumer to apply\n     * @since 1.15.2\n     */\n    public static void registries(@NotNull Consumer<EntityTrackerRegistry> consumer) {\n        for (EntityTrackerRegistry registry : registries()) {\n            consumer.accept(registry);\n        }\n    }\n\n    /**\n     * Returns a list of all active registries.\n     *\n     * @return the list of registries\n     * @since 1.15.2\n     */\n    public static @NotNull @Unmodifiable List<EntityTrackerRegistry> registries() {\n        return REGISTRY_LOCK.accessToReadLock(() -> ImmutableList.copyOf(UUID_REGISTRY_MAP.values()));\n    }\n\n    /**\n     * Gets or creates a registry for a base entity.\n     *\n     * @param entity the base entity\n     * @return the registry\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public static @NotNull EntityTrackerRegistry getOrCreate(@NotNull BaseEntity entity) {\n        var get = registry(entity.uuid());\n        return get != null ? get : create(entity);\n    }\n\n    private static @NotNull EntityTrackerRegistry create(@NotNull BaseEntity entity) {\n        var uuid = entity.uuid();\n        EntityTrackerRegistry registry;\n        synchronized (uuid) {\n            var get2 = registry(uuid);\n            if (get2 != null) return get2;\n            registry = new EntityTrackerRegistry(entity);\n            REGISTRY_LOCK.accessToWriteLock(() -> {\n                UUID_REGISTRY_MAP.put(registry.uuid, registry);\n                ID_REGISTRY_MAP.put(registry.id, registry);\n                return null;\n            });\n        }\n        registry.initialLoad();\n        return registry;\n    }\n\n    private static @NotNull Collection<JsonElement> deserialize(@Nullable String raw) {\n        if (raw == null) return Collections.emptyList();\n        var json = JsonParser.parseString(raw);\n        return json.isJsonArray() ? json.getAsJsonArray().asList() : Collections.singletonList(json);\n    }\n\n    private EntityTrackerRegistry(@NotNull BaseEntity entity) {\n        this.entity = entity;\n        this.uuid = entity.uuid();\n        this.id = entity.id();\n        animationProperty = new AnimationProperty();\n    }\n\n    /**\n     * Returns the source entity.\n     *\n     * @return the entity\n     * @since 1.15.2\n     */\n    public @NotNull BaseEntity entity() {\n        return entity;\n    }\n\n    /**\n     * Returns the entity UUID.\n     *\n     * @return the UUID\n     * @since 1.15.2\n     */\n    public @NotNull UUID uuid() {\n        return uuid;\n    }\n\n    /**\n     * Returns the entity ID.\n     *\n     * @return the ID\n     * @since 1.15.2\n     */\n    public int id() {\n        return id;\n    }\n\n    /**\n     * Returns all trackers in this registry.\n     *\n     * @return the trackers\n     * @since 1.15.2\n     */\n    public @NotNull @Unmodifiable Collection<EntityTracker> trackers() {\n        return trackers;\n    }\n\n    /**\n     * Retrieves a tracker by key.\n     *\n     * @param key the key (model ID), or null for the first tracker\n     * @return the tracker, or null if not found\n     * @since 1.15.2\n     */\n    public @Nullable EntityTracker tracker(@Nullable String key) {\n        return key == null ? first() : trackerMap.get(key);\n    }\n\n    /**\n     * Returns the first tracker in the registry.\n     *\n     * @return the first tracker, or null if empty\n     * @since 1.15.2\n     */\n    public @Nullable EntityTracker first() {\n        var entry = trackerMap.firstEntry();\n        return entry != null ? entry.getValue() : null;\n    }\n\n    /**\n     * Creates a new tracker in this registry.\n     *\n     * @param key the key (model ID)\n     * @param supplier the supplier to create the tracker\n     * @return the created tracker\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public @NotNull EntityTracker create(@NotNull String key, @NotNull Function<EntityTrackerRegistry, EntityTracker> supplier) {\n        var created = supplier.apply(this);\n        if (putTracker(key, created)) {\n            refreshSpawn();\n            save();\n        }\n        return created;\n    }\n\n    /**\n     * Gets or creates a tracker in this registry.\n     *\n     * @param key the key (model ID)\n     * @param supplier the supplier to create the tracker\n     * @return the tracker\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public @NotNull EntityTracker getOrCreate(@NotNull String key, @NotNull Function<EntityTrackerRegistry, EntityTracker> supplier) {\n        var get = trackerMap.get(key);\n        return get != null ? get : create(key, supplier);\n    }\n\n    private boolean putTracker(@NotNull String key, @NotNull EntityTracker created) {\n        if (isClosed() || created.isClosed()) return false;\n        created.handleCloseEvent((_, r) -> {\n            if (isClosed()) return;\n            if (trackerMap.compute(key, (_, v) -> v == created ? null : v) == null) {\n                LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> uuid + \"'s tracker \" + key + \" has been removed. (\" + trackerMap.size() + \")\");\n            }\n            if (trackerMap.isEmpty()) close(r);\n            else refreshRemove();\n        });\n        var previous = trackerMap.put(key, created);\n        if (previous != null) previous.close();\n        return true;\n    }\n\n    private void refreshSpawn() {\n        viewedPlayer().forEach(value -> spawnIfNotSpawned(value.player()));\n    }\n\n    private void refreshRemove() {\n        for (PlayerChannelCache value : viewedPlayerMap.values()) {\n            value.hide();\n        }\n    }\n\n    private void initialLoad() {\n        if (BetterModel.platform().adapter().isRegionSafe() && loaded.compareAndSet(false, true)) {\n            load();\n            refreshPlayer();\n        }\n    }\n\n    private void refreshPlayer() {\n        entity.trackedBy()\n            .map(p -> BetterModel.player(p.uuid()).orElse(null))\n            .filter(Objects::nonNull)\n            .forEach(this::registerPlayer);\n    }\n\n    /**\n     * Removes a tracker from the registry.\n     *\n     * @param key the key (model ID)\n     * @return true if removed successfully\n     * @since 1.15.2\n     */\n    public boolean remove(@NotNull String key) {\n        try (var removed = trackerMap.remove(key)) {\n            save();\n            return removed != null;\n        }\n    }\n\n    /**\n     * Checks if the registry is closed.\n     *\n     * @return true if closed\n     * @since 1.15.2\n     */\n    public boolean isClosed() {\n        return closed.get();\n    }\n\n    /**\n     * Closes the registry.\n     *\n     * @return true if closed successfully\n     * @since 1.15.2\n     */\n    public boolean close() {\n        return close(Tracker.CloseReason.REMOVE);\n    }\n\n    /**\n     * Closes the registry with a specific reason.\n     *\n     * @param reason the close reason\n     * @return true if closed successfully\n     * @since 1.15.2\n     */\n    public boolean close(@NotNull Tracker.CloseReason reason) {\n        if (!closed.compareAndSet(false, true)) return false;\n        viewedPlayer().forEach(value -> value.sendEntityData(this));\n        viewedPlayerMap.clear();\n        for (EntityTracker value : trackers()) {\n            value.close(reason);\n        }\n        if (!reason.shouldBeSave()) runSync(() -> entity.modelData(null));\n        REGISTRY_LOCK.accessToWriteLock(() -> {\n            UUID_REGISTRY_MAP.remove(uuid);\n            ID_REGISTRY_MAP.remove(id);\n            if (entity instanceof BasePlayer player) player.updateInventory();\n            return null;\n        });\n        LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> uuid + \"'s tracker registry has been removed. (\" + UUID_REGISTRY_MAP.size() + \")\");\n        return true;\n    }\n\n    /**\n     * Reloads the registry, refreshing all trackers.\n     *\n     * @since 1.15.2\n     */\n    public void reload() {\n        closed.set(true);\n        var data = new ArrayList<TrackerData>(trackerMap.size());\n        for (EntityTracker value : trackers()) {\n            value.close();\n            if (value.canBeSaved()) data.add(value.asTrackerData());\n        }\n        trackerMap.clear();\n        closed.set(false);\n        load(data.stream());\n    }\n\n    /**\n     * Refreshes the registry state.\n     *\n     * @since 1.15.2\n     */\n    public void refresh() {\n        if (entity.dead()) return;\n        for (EntityTracker value : trackers()) {\n            value.refresh();\n        }\n        refreshPlayer();\n        refreshSpawn();\n    }\n\n    /**\n     * Despawns all trackers in the registry.\n     *\n     * @since 1.15.2\n     */\n    public void despawn() {\n        for (EntityTracker value : trackers()) {\n            if (!value.forRemoval()) value.despawn();\n        }\n        viewedPlayerMap.clear();\n    }\n\n    /**\n     * Loads trackers from a stream of data.\n     *\n     * @param stream the data stream\n     * @since 1.15.2\n     */\n    public void load(@NotNull Stream<TrackerData> stream) {\n        stream.forEach(parsed -> BetterModel.model(parsed.id()).ifPresent(model -> model.create(entity, parsed.modifier(), parsed::applyAs)));\n        save();\n    }\n\n    /**\n     * Loads trackers from the entity's persistent data.\n     *\n     * @since 1.15.2\n     */\n    public void load() {\n        load(deserialize(entity.modelData())\n            .stream()\n            .map(TrackerData::deserialize));\n    }\n\n    /**\n     * Saves the current tracker state to the entity's persistent data.\n     *\n     * @since 1.15.2\n     */\n    public void save() {\n        var data = serialize();\n        if (!data.isEmpty()) runSync(() -> entity.modelData(data.toString()));\n    }\n\n    private void runSync(@NotNull Runnable runnable) {\n        if (BetterModel.platform().adapter().isTickThread()) {\n            runnable.run();\n        } else entity.platform().task(runnable);\n    }\n\n    /**\n     * Returns a stream of all displays from all trackers.\n     *\n     * @return the displays\n     * @since 1.15.2\n     */\n    public @NotNull Stream<ModelDisplay> displays() {\n        return trackers()\n            .stream()\n            .flatMap(Tracker::displays);\n    }\n\n    /**\n     * Serializes the registry state to a JSON array.\n     *\n     * @return the JSON array\n     * @since 1.15.2\n     */\n    public @NotNull JsonArray serialize() {\n        return CollectionUtil.mapToJson(trackers().stream().filter(EntityTracker::canBeSaved), value -> value.asTrackerData().serialize());\n    }\n\n    /**\n     * Checks if any tracker is spawned for a player.\n     *\n     * @param player the player\n     * @return true if spawned\n     * @since 1.15.2\n     */\n    public boolean isSpawned(@NotNull PlatformPlayer player) {\n        return isSpawned(player.uuid());\n    }\n    /**\n     * Checks if any tracker is spawned for a player UUID.\n     *\n     * @param uuid the player UUID\n     * @return true if spawned\n     * @since 1.15.2\n     */\n    public boolean isSpawned(@NotNull UUID uuid) {\n        return viewedPlayerMap.containsKey(uuid) && trackers()\n            .stream()\n            .anyMatch(t -> t.isSpawned(uuid));\n    }\n\n    /**\n     * Spawns trackers for a player.\n     *\n     * @param player the player\n     * @return true if spawned successfully\n     * @since 1.15.2\n     */\n    public boolean spawn(@NotNull PlatformPlayer player) {\n        initialLoad();\n        return spawn(player, false);\n    }\n    /**\n     * Spawns trackers for a player only if not already spawned.\n     *\n     * @param player the player\n     * @return true if spawned successfully\n     * @since 1.15.2\n     */\n    public boolean spawnIfNotSpawned(@NotNull PlatformPlayer player) {\n        initialLoad();\n        return spawn(player, true);\n    }\n    private boolean spawn(@NotNull PlatformPlayer player, boolean shouldNotSpawned) {\n        var handler = BetterModel.platform()\n            .playerManager()\n            .player(player.uuid());\n        if (handler == null) return false;\n        var cache = registerPlayer(handler);\n        if (trackerMap.isEmpty()) return false;\n        var bundler = BetterModel.nms().createBundler(10);\n        for (EntityTracker value : trackers()) {\n            if (shouldNotSpawned && value.isSpawned(player)) continue;\n            if (value.canBeSpawnedAt(player)) value.spawn(player, bundler);\n        }\n        if (bundler.isEmpty()) return false;\n        BetterModel.nms().mount(this, bundler);\n        cache.spawn(bundler);\n        return true;\n    }\n\n    private @NotNull PlayerChannelCache registerPlayer(@NotNull PlayerChannelHandler handler) {\n        return viewedPlayerMap.computeIfAbsent(handler.uuid(), _ -> new PlayerChannelCache(handler));\n    }\n\n    /**\n     * Returns a stream of all players viewing this registry.\n     *\n     * @return the players\n     * @since 1.15.2\n     */\n    public @NotNull Stream<PlayerChannelHandler> viewedPlayer() {\n        return viewedPlayerMap.values().stream().map(c -> c.channelHandler);\n    }\n\n    /**\n     * Removes a player from viewing this registry.\n     *\n     * @param player the player\n     * @return true if removed successfully\n     * @since 1.15.2\n     */\n    public boolean remove(@NotNull PlatformPlayer player) {\n        var cache = viewedPlayerMap.remove(player.uuid());\n        if (cache == null) return false;\n        var handler = cache.channelHandler;\n        handler.sendEntityData(this);\n        for (EntityTracker value : trackers()) {\n            if (!value.forRemoval() && value.isSpawned(player)) value.remove(handler.player());\n        }\n        return true;\n    }\n\n    /**\n     * Returns the hide option for a specific player.\n     *\n     * @param uuid the player UUID\n     * @return the hide option\n     * @since 1.15.2\n     */\n    public @NotNull EntityHideOption hideOption(@NotNull UUID uuid) {\n        var cache = viewedPlayerMap.get(uuid);\n        return cache != null ? cache.hideOption : EntityHideOption.FALSE;\n    }\n\n    /**\n     * Returns the map of currently mounted hitboxes.\n     *\n     * @return the mounted hitboxes\n     * @since 1.15.2\n     */\n    @NotNull\n    @Unmodifiable\n    public Map<UUID, MountedHitBox> mountedHitBox() {\n        return mountedHitBox;\n    }\n\n    /**\n     * Returns a collection of all active hitboxes for this registry.\n     *\n     * @return the hitboxes\n     * @since 2.2.0\n     */\n    @NotNull\n    @Unmodifiable\n    public Collection<HitBox> hitBoxes() {\n        return hitBox;\n    }\n\n    /**\n     * Checks if any hitbox has a passenger.\n     *\n     * @return true if there is a passenger\n     * @since 1.15.2\n     */\n    public boolean hasPassenger() {\n        return !mountedHitBox().isEmpty();\n    }\n\n    /**\n     * Checks if any hitbox has a controlling passenger.\n     *\n     * @return true if there is a controlling passenger\n     * @since 1.15.2\n     */\n    public boolean hasControllingPassenger() {\n        return mountedHitBox()\n            .values()\n            .stream()\n            .map(MountedHitBox::hitBox)\n            .anyMatch(HitBox::hasBeenControlled);\n    }\n\n    /**\n     * Represents a hitbox that has an entity mounted on it.\n     *\n     * @param entity the mounted entity\n     * @param hitBox the hitbox itself\n     * @since 1.15.2\n     */\n    public record MountedHitBox(@NotNull PlatformEntity entity, @NotNull HitBox hitBox) {\n        /**\n         * Dismounts the entity from the hitbox.\n         * @since 1.15.2\n         */\n        public void dismount() {\n            hitBox.dismount(entity);\n        }\n        /**\n         * Dismounts all entities from the hitbox.\n         * @since 1.15.2\n         */\n        public void dismountAll() {\n            hitBox.dismountAll();\n        }\n    }\n\n    final class AnimationProperty {\n        final FloatSupplier damageTick = FunctionUtil.throttleTickFloat(entity::damageTick);\n        final FloatSupplier walkSpeed = FunctionUtil.throttleTickFloat(() -> entity.walkSpeed() + (float) Math.sqrt(damageTick.getAsFloat()));\n        final BooleanSupplier onWalk = FunctionUtil.throttleTickBoolean(() -> entity.onWalk() || damageTick.getAsFloat() > 0.25 || hitBoxes().stream().anyMatch(HitBox::onWalk));\n        final BooleanSupplier onFly = FunctionUtil.throttleTickBoolean(entity::fly);\n    }\n\n    @RequiredArgsConstructor\n    private class PlayerChannelCache {\n        private final PlayerChannelHandler channelHandler;\n        private volatile EntityHideOption hideOption = EntityHideOption.DEFAULT;\n\n        private void hide() {\n            reapplyHideOption();\n            BetterModel.nms().hide(channelHandler, EntityTrackerRegistry.this);\n        }\n\n        private void spawn(@NotNull PacketBundler bundler) {\n            reapplyHideOption();\n            bundler.send(channelHandler.player(), () -> BetterModel.nms().hide(channelHandler, EntityTrackerRegistry.this, () -> viewedPlayerMap.containsKey(channelHandler.uuid())));\n        }\n\n        private synchronized void reapplyHideOption() {\n            hideOption = EntityHideOption.composite(trackers()\n                .stream()\n                .filter(t -> t.isSpawned(channelHandler.uuid()))\n                .map(EntityTracker::hideOption));\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/ModelRotation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Represents the rotation of a model in degrees.\n * <p>\n * This record stores pitch (x) and yaw (y) values and provides utility methods for conversion.\n * </p>\n *\n * @param x the pitch (x-rotation) in degrees\n * @param y the yaw (y-rotation) in degrees\n * @since 1.15.2\n */\npublic record ModelRotation(float x, float y) {\n    /**\n     * A rotation of (0, 0).\n     * @since 1.15.2\n     */\n    public static final ModelRotation EMPTY = new ModelRotation(0, 0);\n    /**\n     * An invalid rotation value used for initialization or error states.\n     * @since 1.15.2\n     */\n    public static final ModelRotation INVALID = new ModelRotation(Float.MAX_VALUE, Float.MAX_VALUE);\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == this) return true;\n        return o instanceof ModelRotation other && packedX() == other.packedX() && packedY() == other.packedY();\n    }\n\n    @Override\n    public int hashCode() {\n        return ((Byte.hashCode(packedX()) & 0xFF) << 8) | (Byte.hashCode(packedY()) & 0xFF);\n    }\n\n    /**\n     * Returns a new rotation with only the pitch component.\n     *\n     * @return the pitch-only rotation\n     * @since 1.15.2\n     */\n    public @NotNull ModelRotation pitch() {\n        return new ModelRotation(x, 0);\n    }\n\n    /**\n     * Returns a new rotation with only the yaw component.\n     *\n     * @return the yaw-only rotation\n     * @since 1.15.2\n     */\n    public @NotNull ModelRotation yaw() {\n        return new ModelRotation(0, y);\n    }\n\n    /**\n     * Returns the pitch in radians.\n     *\n     * @return the pitch in radians\n     * @since 1.15.2\n     */\n    public float radianX() {\n        return x * MathUtil.DEGREES_TO_RADIANS;\n    }\n\n    /**\n     * Returns the yaw in radians.\n     *\n     * @return the yaw in radians\n     * @since 1.15.2\n     */\n    public float radianY() {\n        return y * MathUtil.DEGREES_TO_RADIANS;\n    }\n\n    /**\n     * Returns the pitch packed as a byte (Minecraft protocol format).\n     *\n     * @return the packed pitch\n     * @since 1.15.2\n     */\n    public byte packedX() {\n        return (byte) (x * MathUtil.DEGREES_TO_PACKED_BYTE);\n    }\n\n    /**\n     * Returns the yaw packed as a byte (Minecraft protocol format).\n     *\n     * @return the packed yaw\n     * @since 1.15.2\n     */\n    public byte packedY() {\n        return (byte) (y * MathUtil.DEGREES_TO_PACKED_BYTE);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/ModelRotator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonNull;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonPrimitive;\nimport kr.toxicity.model.api.util.CollectionUtil;\nimport kr.toxicity.model.api.util.lazy.LazyFloatProvider;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\n/**\n * Defines how a model's rotation is calculated and applied.\n * <p>\n * Rotators can modify the base rotation (e.g., only applying yaw, smoothing rotation)\n * and can be chained together.\n * </p>\n *\n * @since 1.15.2\n */\npublic sealed interface ModelRotator extends BiFunction<Tracker, ModelRotation, ModelRotation> {\n    /**\n     * The global deserializer instance for rotators.\n     * @since 1.15.2\n     */\n    Deserializer DESERIALIZER = new Deserializer();\n    /**\n     * Default rotator (applies rotation as-is).\n     * @since 1.15.2\n     */\n    @NotNull\n    ModelRotator DEFAULT = Objects.requireNonNull(DESERIALIZER._default.apply());\n    /**\n     * Empty rotator (returns zero rotation).\n     * @since 1.15.2\n     */\n    @NotNull\n    ModelRotator EMPTY = Objects.requireNonNull(DESERIALIZER.empty.apply());\n    /**\n     * Pitch-only rotator.\n     * @since 1.15.2\n     */\n    @NotNull\n    ModelRotator PITCH = Objects.requireNonNull(DESERIALIZER.pitch.apply());\n    /**\n     * Yaw-only rotator.\n     * @since 1.15.2\n     */\n    @NotNull\n    ModelRotator YAW = Objects.requireNonNull(DESERIALIZER.yaw.apply());\n\n    /**\n     * Deserializes a rotator from a JSON object.\n     *\n     * @param object the JSON object\n     * @return the deserialized rotator, or EMPTY if invalid\n     * @since 1.15.2\n     */\n    static @NotNull ModelRotator deserialize(@NotNull JsonObject object) {\n        var result = DESERIALIZER.deserialize(object);\n        return result != null ? result : EMPTY;\n    }\n\n    /**\n     * Creates a lazy rotator that smooths rotation over time.\n     *\n     * @param mills the smoothing duration in milliseconds\n     * @return the lazy rotator\n     * @since 1.15.2\n     */\n    static @NotNull ModelRotator lazy(long mills) {\n        return Objects.requireNonNull(DESERIALIZER.lazy.apply(mills));\n    }\n\n    /**\n     * Returns the name of this rotator type.\n     *\n     * @return the name\n     * @since 1.15.2\n     */\n    @NotNull String name();\n\n    /**\n     * Returns the source rotator if this is a chained rotator.\n     *\n     * @return the source rotator, or null\n     * @since 1.15.2\n     */\n    @Nullable ModelRotator source();\n\n    /**\n     * Returns the configuration data for this rotator.\n     *\n     * @return the data, or null\n     * @since 1.15.2\n     */\n    @Nullable JsonElement data();\n\n    /**\n     * Returns the root rotator in the chain.\n     *\n     * @return the root rotator\n     * @since 1.15.2\n     */\n    default @NotNull ModelRotator root() {\n        var source = source();\n        return source != null ? source.root() : this;\n    }\n\n    /**\n     * Serializes this rotator to a JSON object.\n     *\n     * @return the JSON object\n     * @since 1.15.2\n     */\n    default @NotNull JsonObject serialize() {\n        var json = new JsonObject();\n        json.addProperty(\"name\", name());\n        var d = data();\n        if (d != null) json.add(\"data\", d);\n        var s = source();\n        if (s != null) json.add(\"source\", s.serialize());\n        return json;\n    }\n\n    /**\n     * Applies the rotator to a tracker with default rotation.\n     *\n     * @param tracker the tracker\n     * @return the calculated rotation\n     * @since 1.15.2\n     */\n    default @NotNull ModelRotation apply(@NotNull Tracker tracker) {\n        return apply(tracker, ModelRotation.EMPTY);\n    }\n\n    /**\n     * Applies the rotator to a tracker with a base rotation.\n     *\n     * @param tracker the tracker\n     * @param rotation the base rotation\n     * @return the calculated rotation\n     * @since 1.15.2\n     */\n    @Override\n    @NotNull\n    ModelRotation apply(@NotNull Tracker tracker, @NotNull ModelRotation rotation);\n\n    /**\n     * Chains this rotator with another one.\n     *\n     * @param rotator the next rotator in the chain\n     * @return the chained rotator\n     * @since 1.15.2\n     */\n    default @NotNull ModelRotator then(@NotNull ModelRotator rotator) {\n        return new SourcedRotator(this, rotator);\n    }\n\n    /**\n     * Implementation of a chained rotator.\n     *\n     * @param source source rotator\n     * @param delegate delegated rotator\n     * @since 1.15.2\n     */\n    record SourcedRotator(@NotNull ModelRotator source, @NotNull ModelRotator delegate) implements ModelRotator {\n        @Override\n        public @NotNull String name() {\n            return delegate.name();\n        }\n\n        @Override\n        public @Nullable JsonElement data() {\n            return delegate.data();\n        }\n\n        @Override\n        public @NotNull ModelRotation apply(@NotNull Tracker tracker, @NotNull ModelRotation rotation) {\n            return delegate.apply(tracker, source.apply(tracker, rotation));\n        }\n    }\n\n    /**\n     * Functional interface for calculating rotation.\n     *\n     * @since 1.15.2\n     */\n    interface Getter {\n        /**\n         * Default getter returning the input rotation.\n         * @since 1.15.2\n         */\n        Getter DEFAULT = of(r -> r);\n        /**\n         * Calculates the rotation.\n         *\n         * @param tracker the tracker\n         * @param modelRotation the base rotation\n         * @return the calculated rotation\n         * @since 1.15.2\n         */\n        @NotNull\n        ModelRotation apply(@NotNull Tracker tracker, @NotNull ModelRotation modelRotation);\n\n        /**\n         * Creates a constant rotation getter.\n         *\n         * @param rotator the rotation\n         * @return the getter\n         * @since 1.15.2\n         */\n        static @NotNull Getter of(@NotNull ModelRotation rotator) {\n            return (_, _) -> rotator;\n        }\n        /**\n         * Creates a supplier-based rotation getter.\n         *\n         * @param rotator the supplier\n         * @return the getter\n         * @since 1.15.2\n         */\n        static @NotNull Getter of(@NotNull Supplier<ModelRotation> rotator) {\n            return (_, _) -> rotator.get();\n        }\n        /**\n         * Creates a function-based rotation getter.\n         *\n         * @param rotator the function\n         * @return the getter\n         * @since 1.15.2\n         */\n        static @NotNull Getter of(@NotNull Function<ModelRotation, ModelRotation> rotator) {\n            return (_, r) -> rotator.apply(r);\n        }\n    }\n\n    /**\n     * Builder interface for creating Getters from JSON.\n     *\n     * @since 1.15.2\n     */\n    interface Builder {\n        /**\n         * Builds a getter from JSON data.\n         *\n         * @param element the JSON data\n         * @return the getter, or null if invalid\n         * @since 1.15.2\n         */\n        @Nullable Getter build(@NotNull JsonElement element);\n    }\n\n    /**\n     * Helper interface for built-in deserializers.\n     *\n     * @since 1.15.2\n     */\n    interface BuiltInDeserializer extends Function<JsonElement, ModelRotator> {\n        @Override\n        @Nullable\n        ModelRotator apply(@NotNull JsonElement element);\n\n        /**\n         * Deserializes a default instance.\n         *\n         * @return the rotator\n         * @since 1.15.2\n         */\n        default @Nullable ModelRotator apply() {\n            return apply(JsonNull.INSTANCE);\n        }\n\n        /**\n         * Deserializes from a long value.\n         *\n         * @param value the value\n         * @return the rotator\n         * @since 1.15.2\n         */\n        default @Nullable ModelRotator apply(long value) {\n            return apply(new JsonPrimitive(value));\n        }\n    }\n\n    /**\n     * Registry and factory for rotators.\n     *\n     * @since 1.15.2\n     */\n    final class Deserializer {\n        private final Map<String, Builder> builderMap = CollectionUtil.newAddressingMap();\n\n        private final BuiltInDeserializer _default = register(\"default\", _ -> Getter.of(r -> r));\n        private final BuiltInDeserializer empty = register(\"empty\", _ -> Getter.of(ModelRotation.EMPTY));\n        private final BuiltInDeserializer yaw = register(\"yaw\", _ -> Getter.of(ModelRotation::yaw));\n        private final BuiltInDeserializer pitch = register(\"pitch\", _ -> Getter.of(ModelRotation::pitch));\n        private final BuiltInDeserializer lazy = register(\"lazy\", j -> {\n            if (j.isJsonPrimitive()) {\n                var f = j.getAsLong();\n                var xLazy = new LazyFloatProvider(f);\n                var yLazy = new LazyFloatProvider(f);\n                return Getter.of(r -> new ModelRotation(xLazy.updateAndGet(r.x()), yLazy.updateAndGet(r.y())));\n            } else return null;\n        });\n\n        private Deserializer() {\n        }\n\n        /**\n         * Registers a new rotator type.\n         *\n         * @param name the rotator name\n         * @param builder the builder\n         * @return a built-in deserializer helper\n         * @since 1.15.2\n         */\n        public @NotNull BuiltInDeserializer register(@NotNull String name, @NotNull Builder builder) {\n            var get = builderMap.putIfAbsent(name, builder);\n            var selected = get != null ? get : builder;\n            return e -> {\n                var build = selected.build(e);\n                var source = e.isJsonObject() ? e.getAsJsonObject().get(\"source\") : null;\n                return build != null ? pack(name, source != null && source.isJsonObject() ? deserialize(source.getAsJsonObject()) : null, e, build) : null;\n            };\n        }\n\n        /**\n         * Deserializes a rotator from a JSON object.\n         *\n         * @param object the JSON object\n         * @return the rotator, or null if invalid\n         * @since 1.15.2\n         */\n        public @Nullable ModelRotator deserialize(@NotNull JsonObject object) {\n            var rawName = object.getAsJsonPrimitive(\"name\");\n            if (rawName == null) return null;\n            var name = rawName.getAsString();\n            var get = builderMap.get(name);\n            if (get == null) return null;\n            var data = object.get(\"data\");\n            var source = object.getAsJsonObject().get(\"source\");\n            var build = get.build(data == null ? JsonNull.INSTANCE : data);\n            return build != null ? pack(\n                    name,\n                    source != null && source.isJsonObject() ? deserialize(source.getAsJsonObject()) : null,\n                    data,\n                    build\n            ) : null;\n        }\n\n        private @NotNull Pack pack(@NotNull String name, @Nullable ModelRotator source, @Nullable JsonElement data, @NotNull Getter getter) {\n            return new Pack(name, source, data, getter);\n        }\n\n        private record Pack(@NotNull String name, @Nullable ModelRotator source, @Nullable JsonElement data, @NotNull Getter delegate) implements ModelRotator {\n\n            @Override\n            public @NotNull ModelRotation apply(@NotNull Tracker tracker, @NotNull ModelRotation modelRotation) {\n                return delegate.apply(tracker, modelRotation);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/ModelScaler.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport com.google.gson.*;\nimport kr.toxicity.model.api.util.CollectionUtil;\nimport lombok.AccessLevel;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.function.Function;\n\n/**\n * Defines how a model's scale is calculated.\n * <p>\n * Scalers can be constant values, derived from entity attributes, or composites of multiple scalers.\n * They are serializable to JSON for configuration purposes.\n * </p>\n *\n * @since 1.15.2\n */\npublic sealed interface ModelScaler {\n\n    /**\n     * The global deserializer instance for scalers.\n     * @since 1.15.2\n     */\n    Deserializer DESERIALIZER = new Deserializer();\n\n    /**\n     * Returns the name of this scaler type.\n     *\n     * @return the name\n     * @since 1.15.2\n     */\n    @NotNull String name();\n\n    /**\n     * Calculates the scale for a given tracker.\n     *\n     * @param tracker the tracker\n     * @return the calculated scale factor\n     * @since 1.15.2\n     */\n    float scale(@NotNull Tracker tracker);\n\n    /**\n     * Returns the configuration data for this scaler as a JSON element.\n     *\n     * @return the data, or null if none\n     * @since 1.15.2\n     */\n    @Nullable JsonElement data();\n\n    /**\n     * Deserializes a scaler from a JSON object.\n     *\n     * @param element the JSON object\n     * @return the deserialized scaler, or the default scaler if invalid\n     * @since 1.15.2\n     */\n    static @NotNull ModelScaler deserialize(@NotNull JsonObject element) {\n        var scaler = DESERIALIZER.buildScaler(element);\n        return scaler != null ? scaler : defaultScaler();\n    }\n\n    /**\n     * Returns the default scaler (constant 1.0).\n     *\n     * @return the default scaler\n     * @since 1.15.2\n     */\n    static @NotNull ModelScaler defaultScaler() {\n        return DESERIALIZER.defaultScaler();\n    }\n\n    /**\n     * Returns a scaler that uses the entity's scale attribute.\n     *\n     * @return the entity scaler\n     * @since 1.15.2\n     */\n    static @NotNull ModelScaler entity() {\n        return DESERIALIZER.entity.deserialize();\n    }\n\n    /**\n     * Returns a constant value scaler.\n     *\n     * @param value the scale value\n     * @return the value scaler\n     * @since 1.15.2\n     */\n    static @NotNull ModelScaler value(float value) {\n        return DESERIALIZER.value.deserialize(value);\n    }\n\n    /**\n     * Creates a composite scaler that multiplies the results of multiple scalers.\n     *\n     * @param scalers the scalers to combine\n     * @return the composite scaler\n     * @since 1.15.2\n     */\n    static @NotNull ModelScaler composite(@NotNull ModelScaler... scalers) {\n        return new Composite(new Composite.CompositeGetter(Arrays.asList(scalers)));\n    }\n\n    /**\n     * Multiplies this scaler by a constant value.\n     *\n     * @param value the multiplier\n     * @return the new composite scaler\n     * @since 1.15.2\n     */\n    default @NotNull ModelScaler multiply(float value) {\n        return composite(value(value));\n    }\n\n    /**\n     * Multiplies this scaler by another scaler.\n     *\n     * @param scaler the other scaler\n     * @return the new composite scaler\n     * @since 1.15.2\n     */\n    default @NotNull ModelScaler composite(@NotNull ModelScaler scaler) {\n        var list = new ArrayList<ModelScaler>();\n        if (this instanceof Composite composite) {\n            list.addAll(composite.getter.list);\n        } else list.add(this);\n        if (scaler instanceof Composite composite) {\n            list.addAll(composite.getter.list);\n        } else list.add(scaler);\n        return new Composite(new Composite.CompositeGetter(list));\n    }\n\n    /**\n     * Serializes this scaler to a JSON object.\n     *\n     * @return the JSON object\n     * @since 1.15.2\n     */\n    default @NotNull JsonObject serialize() {\n        var json = new JsonObject();\n        json.addProperty(\"name\", name());\n        var d = data();\n        if (d != null) json.add(\"data\", d);\n        return json;\n    }\n\n    /**\n     * Functional interface for calculating scale.\n     *\n     * @since 1.15.2\n     */\n    interface Getter {\n        /**\n         * Default getter returning 1.0.\n         * @since 1.15.2\n         */\n        Getter DEFAULT = _ -> 1F;\n        /**\n         * Getter using entity scale.\n         * @since 1.15.2\n         */\n        Getter ENTITY = t -> t instanceof EntityTracker entityTracker ? (float) entityTracker.registry().entity().scale() : 1F;\n\n        /**\n         * Calculates the scale.\n         *\n         * @param tracker the tracker\n         * @return the scale\n         * @since 1.15.2\n         */\n        float get(@NotNull Tracker tracker);\n\n        /**\n         * Creates a constant value getter.\n         *\n         * @param value the value\n         * @return the getter\n         * @since 1.15.2\n         */\n        static @NotNull Getter value(float value) {\n            return _ -> value;\n        }\n    }\n\n    /**\n     * Builder interface for creating Getters from JSON.\n     *\n     * @since 1.15.2\n     */\n    interface Builder {\n        /**\n         * Builds a getter from JSON data.\n         *\n         * @param data the JSON data\n         * @return the getter, or null if invalid\n         * @since 1.15.2\n         */\n        @Nullable Getter build(@NotNull JsonElement data);\n    }\n\n    /**\n     * Implementation of a composite scaler.\n     *\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)\n    final class Composite implements ModelScaler {\n\n        private final CompositeGetter getter;\n\n        private record CompositeGetter(@NotNull List<ModelScaler> list) implements Getter {\n            @Override\n            public float get(@NotNull Tracker tracker) {\n                var f = 1F;\n                for (ModelScaler modelScaler : list) {\n                    f *= modelScaler.scale(tracker);\n                }\n                return f;\n            }\n        }\n\n        @NotNull\n        @Override\n        public String name() {\n            return \"composite\";\n        }\n\n        @Override\n        public float scale(@NotNull Tracker tracker) {\n            return getter.get(tracker);\n        }\n\n        private void add(@NotNull JsonArray array, @NotNull ModelScaler scaler) {\n            if (scaler instanceof Composite composite) {\n                for (ModelScaler childScaler : composite.getter.list) {\n                    add(array, childScaler);\n                }\n            } else array.add(scaler.serialize());\n        }\n\n        @Override\n        public JsonElement data() {\n            var arr = new JsonArray();\n            for (ModelScaler modelScaler : getter.list) {\n                add(arr, modelScaler);\n            }\n            return arr.isEmpty() ? null : arr;\n        }\n    }\n\n    /**\n     * Helper interface for built-in deserializers.\n     *\n     * @since 1.15.2\n     */\n    interface BuiltInDeserializer extends Function<JsonElement, ModelScaler> {\n        /**\n         * Deserializes from a float value.\n         *\n         * @param value the value\n         * @return the scaler\n         * @since 1.15.2\n         */\n        default @NotNull ModelScaler deserialize(float value) {\n            return apply(new JsonPrimitive(value));\n        }\n        /**\n         * Deserializes a default instance.\n         *\n         * @return the scaler\n         * @since 1.15.2\n         */\n        default @NotNull ModelScaler deserialize() {\n            return apply(JsonNull.INSTANCE);\n        }\n    }\n\n    /**\n     * Registry and factory for scalers.\n     *\n     * @since 1.15.2\n     */\n    final class Deserializer {\n\n        private final Map<String, Builder> getterMap = CollectionUtil.newAddressingMap();\n\n        private final BuiltInDeserializer def = addScaler(\"default\", _ -> Getter.DEFAULT);\n        private final BuiltInDeserializer entity = addScaler(\"entity\", _ -> Getter.ENTITY);\n        private final BuiltInDeserializer value = addScaler(\"value\", d -> d.isJsonPrimitive() ? Getter.value(d.getAsFloat()) : Getter.DEFAULT);\n\n        private Deserializer() {\n            getterMap.put(\"composite\", d -> {\n                if (d.isJsonArray()) {\n                    return new Composite.CompositeGetter(d.getAsJsonArray()\n                            .asList()\n                            .stream()\n                            .filter(JsonElement::isJsonObject)\n                            .map(element -> buildScaler(element.getAsJsonObject()))\n                            .filter(Objects::nonNull)\n                            .toList());\n                } else return Getter.DEFAULT;\n            });\n        }\n\n        private @NotNull ModelScaler defaultScaler() {\n            return def.deserialize();\n        }\n\n        /**\n         * Registers a new scaler type.\n         *\n         * @param name the scaler name\n         * @param builder the builder\n         * @return a built-in deserializer helper\n         * @since 1.15.2\n         */\n        public @NotNull BuiltInDeserializer addScaler(@NotNull String name, @NotNull Builder builder) {\n            var put = getterMap.putIfAbsent(name, builder);\n            var target = put != null ? put : builder;\n            return element -> pack(name, target, element);\n        }\n\n        /**\n         * Builds a scaler from a JSON object.\n         *\n         * @param rawData the JSON object\n         * @return the scaler, or null if invalid\n         * @since 1.15.2\n         */\n        public @Nullable ModelScaler buildScaler(@NotNull JsonObject rawData) {\n            var n = rawData.getAsJsonPrimitive(\"name\");\n            if (n == null) return null;\n            var name = n.getAsString();\n            var get = getterMap.get(name);\n            if (get == null) return null;\n            var d = rawData.get(\"data\");\n            return pack(name, get, d);\n        }\n\n        private @NotNull ModelScaler pack(@NotNull String name, @NotNull Builder builder, @Nullable JsonElement data) {\n            var build = Optional.ofNullable(builder.build(data != null ? data : JsonNull.INSTANCE))\n                    .orElse(Getter.DEFAULT);\n            return build instanceof Composite.CompositeGetter compositeGetter ? new Composite(compositeGetter) : new Pack(name, build, data);\n        }\n\n        private record Pack(@NotNull String name, @NotNull Getter getter, @Nullable JsonElement data) implements ModelScaler {\n            @Override\n            public float scale(@NotNull Tracker tracker) {\n                return getter.get(tracker);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/PlayerTracker.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.data.renderer.RenderPipeline;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Consumer;\n\n/**\n * A specialized {@link EntityTracker} for tracking players.\n * <p>\n * This tracker automatically configures the body rotator to player mode, ensuring correct\n * head and body rotation synchronization for player entities.\n * </p>\n *\n * @since 1.15.2\n */\npublic final class PlayerTracker extends EntityTracker {\n\n    /**\n     * Creates a new player tracker.\n     *\n     * @param registry the entity tracker registry\n     * @param pipeline the render pipeline\n     * @param modifier the tracker modifier\n     * @param preUpdateConsumer a consumer to run before the first update\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public PlayerTracker(@NotNull EntityTrackerRegistry registry, @NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier, @NotNull Consumer<EntityTracker> preUpdateConsumer) {\n        super(registry, pipeline, modifier, preUpdateConsumer);\n        bodyRotator().setValue(setter -> setter.setPlayerMode(true));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/Tracker.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport kr.toxicity.model.api.animation.AnimationStateHandler;\nimport kr.toxicity.model.api.bone.BoneMovement;\nimport kr.toxicity.model.api.bone.BoneName;\nimport kr.toxicity.model.api.bone.BoneTags;\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.config.DebugConfig;\nimport kr.toxicity.model.api.data.blueprint.BlueprintAnimation;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport kr.toxicity.model.api.data.renderer.RenderPipeline;\nimport kr.toxicity.model.api.data.renderer.RenderSource;\nimport kr.toxicity.model.api.entity.BaseEntity;\nimport kr.toxicity.model.api.event.*;\nimport kr.toxicity.model.api.event.hitbox.HitBoxEvent;\nimport kr.toxicity.model.api.nms.*;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport kr.toxicity.model.api.script.TimeScript;\nimport kr.toxicity.model.api.util.*;\nimport kr.toxicity.model.api.util.function.BonePredicate;\nimport kr.toxicity.model.api.util.function.FloatSupplier;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.*;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.*;\nimport java.util.stream.Stream;\n\n/**\n * Represents the core controller for a specific model instance.\n * <p>\n * A Tracker manages the lifecycle, rendering, animation, and player interaction of a model.\n * It coordinates with the {@link RenderPipeline} to update bone positions and send packets to players.\n * </p>\n *\n * @since 1.15.2\n */\npublic abstract class Tracker implements AutoCloseable {\n\n    private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors() * 2, new ThreadFactory() {\n\n        private final AtomicInteger integer = new AtomicInteger();\n\n        @Override\n        public Thread newThread(@NotNull Runnable r) {\n            var thread = new Thread(r);\n            thread.setDaemon(true);\n            thread.setName(\"BetterModel-Worker-\" + integer.getAndIncrement());\n            thread.setUncaughtExceptionHandler((t, e) -> LogUtil.handleException(\"Exception has occurred in \" + t.getName(), e));\n            return thread;\n        }\n    });\n    /**\n     * The interval in milliseconds between tracker ticks.\n     * @since 1.15.2\n     */\n    public static final int TRACKER_TICK_INTERVAL = 25;\n    /**\n     * The multiplier to convert tracker ticks to Minecraft ticks (50ms).\n     * @since 1.15.2\n     */\n    public static final int MINECRAFT_TICK_MULTIPLIER = MathUtil.MINECRAFT_TICK_MILLS / TRACKER_TICK_INTERVAL;\n\n    @Getter\n    protected final RenderPipeline pipeline;\n    private long frame = 0;\n    private final Queue<Runnable> queuedTask = new ConcurrentLinkedQueue<>();\n    private final AtomicBoolean tickPause = new AtomicBoolean();\n    private final AtomicBoolean isClosed = new AtomicBoolean();\n    private final AtomicBoolean readyForForceUpdate = new AtomicBoolean();\n    private final AtomicBoolean forRemoval = new AtomicBoolean();\n    private final AtomicBoolean firstStart = new AtomicBoolean();\n    protected final TrackerModifier modifier;\n    private final Runnable updater;\n    private final BundlerSet bundlerSet;\n    private final FloatSupplier heightSupplier = FunctionUtil.throttleTickFloat(TRACKER_TICK_INTERVAL, new FloatSupplier() {\n\n        private final BoneMovement heightCache = new BoneMovement();\n\n        @Override\n        public float getAsFloat() {\n            return (float) pipeline\n                .stream()\n                .filter(bone -> bone.name().tagged(BoneTags.HEAD, BoneTags.HEAD_WITH_CHILDREN))\n                .mapToDouble(bone -> bone.hitBoxPosition(heightCache).y)\n                .max()\n                .orElse(0F);\n        }\n    });\n    private final AnimationStateHandler<TimeScript> scriptProcessor = new AnimationStateHandler<>(\n        TimeScript.EMPTY,\n        (b, _) -> {\n            if (b == null) return;\n            if (b.isSync()) {\n                location().task(() -> b.accept(this));\n            } else b.accept(this);\n        }\n    );\n    private volatile ScheduledFuture<?> task;\n    protected ModelRotator rotator = ModelRotator.YAW;\n    protected ModelScaler scaler = ModelScaler.entity();\n    private Supplier<ModelRotation> rotationSupplier = () -> ModelRotation.EMPTY;\n    private BiConsumer<Tracker, CloseReason> closeEventHandler = (t, r) -> EventUtil.call(CloseTrackerEvent.class, () -> new CloseTrackerEvent(t, r));\n\n    private ScheduledPacketHandler handler = (t, s) -> {\n        if (!tickPause.get()) {\n            scriptProcessor.tick();\n            t.pipeline.tick(s.getViewBundler());\n        }\n    };\n    private BiConsumer<Tracker, PlatformPlayer> perPlayerHandler = null;\n\n    /**\n     * Creates a new tracker.\n     *\n     * @param pipeline the render pipeline\n     * @param modifier the tracker modifier\n     * @since 1.15.2\n     */\n    public Tracker(@NotNull RenderPipeline pipeline, @NotNull TrackerModifier modifier) {\n        this.pipeline = pipeline;\n        this.modifier = modifier;\n        bundlerSet = new BundlerSet();\n        updater = () -> {\n            try {\n                if (frame % MINECRAFT_TICK_MULTIPLIER == 0) {\n                    Runnable task;\n                    while ((task = queuedTask.poll()) != null) task.run();\n                }\n                handler.handle(this, bundlerSet);\n                bundlerSet.send();\n            } catch (Throwable throwable) {\n                LogUtil.handleException(\"Ticking this tracker has been failed: \" + name(), throwable);\n            }\n        };\n        if (modifier.sightTrace()) pipeline.viewFilter(p -> EntityUtil.canSee(p.eyeLocation(), location()));\n        frame((t, s) -> {\n            if (readyForForceUpdate.compareAndSet(true, false)) t.pipeline.forEach(b -> b.dirtyUpdate(s.dataBundler));\n        });\n        tick((t, s) -> pipeline.rotate(\n            t.rotation(),\n            s.tickBundler\n        ));\n        tick((t, _) -> {\n            var perPlayer = perPlayerHandler;\n            if (perPlayer != null) pipeline.nonHidePlayer().forEach(p -> perPlayer.accept(t, p.player()));\n        });\n        pipeline.spawnPacketHandler(_ -> start());\n        pipeline.eventDispatcher().handleStateCreate((_, uuid) -> bundlerSet.perPlayerViewBundler\n            .computeIfAbsent(uuid, PerPlayerCache::new)\n            .add());\n        pipeline.eventDispatcher().handleStateRemove((_, uuid) -> {\n            var get = bundlerSet.perPlayerViewBundler.get(uuid);\n            if (get != null) get.remove();\n        });\n        LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> getClass().getSimpleName() + \" tracker created: \" + name());\n        pipeline.getSource().completeContext().thenAccept(context -> {\n            if (pipeline.matchTree(bone -> bone.updateItem(context))) forceUpdate(true);\n        });\n    }\n\n    /**\n     * Checks if the tracker's update task is currently scheduled.\n     *\n     * @return true if scheduled, false otherwise\n     * @since 1.15.2\n     */\n    public boolean isScheduled() {\n        var currentTask = task;\n        return currentTask != null && !currentTask.isCancelled();\n    }\n\n    private void start() {\n        if (isScheduled()) return;\n        synchronized (this) {\n            if (isScheduled()) return;\n            if (firstStart.compareAndSet(false, true)) {\n                TrackerBuiltInAnimation.play(this);\n            }\n            updater.run();\n            task = EXECUTOR.scheduleAtFixedRate(() -> {\n                if (playerCount() == 0 && !forRemoval.get()) {\n                    shutdown();\n                    return;\n                }\n                frame++;\n                updater.run();\n            }, TRACKER_TICK_INTERVAL, TRACKER_TICK_INTERVAL, TimeUnit.MILLISECONDS);\n            LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> getClass().getSimpleName() + \" scheduler started: \" + name());\n        }\n    }\n\n    private void shutdown() {\n        if (!isScheduled()) return;\n        synchronized (this) {\n            if (!isScheduled()) return;\n            task.cancel(true);\n            task = null;\n            frame = 0;\n            LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> getClass().getSimpleName() + \" scheduler shutdown: \" + name());\n        }\n    }\n\n    /**\n     * Returns the current rotation of the model.\n     *\n     * @return the model rotation\n     * @since 1.15.2\n     */\n    public @NotNull ModelRotation rotation() {\n        return rotator.apply(this, rotationSupplier.get());\n    }\n\n    /**\n     * Sets the supplier for the base model rotation.\n     *\n     * @param supplier the rotation supplier\n     * @since 1.15.2\n     */\n    public final void rotation(@NotNull Supplier<ModelRotation> supplier) {\n        this.rotationSupplier = Objects.requireNonNull(supplier);\n    }\n\n    /**\n     * Sets the model rotator strategy.\n     *\n     * @param rotator the rotator strategy\n     * @since 1.15.2\n     */\n    public final void rotator(@NotNull ModelRotator rotator) {\n        this.rotator = Objects.requireNonNull(rotator);\n    }\n\n    /**\n     * Returns the model scaler.\n     *\n     * @return the scaler\n     * @since 1.15.2\n     */\n    public @NotNull ModelScaler scaler() {\n        return scaler;\n    }\n\n    /**\n     * Sets the model scaler.\n     *\n     * @param scaler the new scaler\n     * @since 1.15.2\n     */\n    public void scaler(@NotNull ModelScaler scaler) {\n        this.scaler = Objects.requireNonNull(scaler);\n    }\n\n    /**\n     * Schedules a task to run on the next tracker tick.\n     *\n     * @param runnable the task to run\n     * @since 1.15.2\n     */\n    public void task(@NotNull Runnable runnable) {\n        queuedTask.add(Objects.requireNonNull(runnable));\n    }\n\n    /**\n     * Registers a handler to run every frame (tracker tick).\n     *\n     * @param handler the packet handler\n     * @since 1.15.2\n     */\n    public synchronized void frame(@NotNull ScheduledPacketHandler handler) {\n        this.handler = this.handler.then(Objects.requireNonNull(handler));\n    }\n    /**\n     * Registers a handler to run every Minecraft tick (50ms).\n     *\n     * @param handler the packet handler\n     * @since 1.15.2\n     */\n    public void tick(@NotNull ScheduledPacketHandler handler) {\n        tick(1, handler);\n    }\n    /**\n     * Registers a handler to run every N Minecraft ticks.\n     *\n     * @param tick the interval in Minecraft ticks\n     * @param handler the packet handler\n     * @since 1.15.2\n     */\n    public void tick(long tick, @NotNull ScheduledPacketHandler handler) {\n        schedule(MINECRAFT_TICK_MULTIPLIER * tick, handler);\n    }\n\n    /**\n     * Registers a handler to run every tick for each visible player.\n     *\n     * @param perPlayerHandler the per-player handler\n     * @since 1.15.2\n     */\n    public synchronized void perPlayerTick(@NotNull BiConsumer<Tracker, PlatformPlayer> perPlayerHandler) {\n        var previous = this.perPlayerHandler;\n        this.perPlayerHandler = previous == null ? perPlayerHandler : previous.andThen(perPlayerHandler);\n    }\n\n    /**\n     * Schedules a handler to run periodically.\n     *\n     * @param period the period in tracker ticks\n     * @param handler the packet handler\n     * @since 1.15.2\n     */\n    public void schedule(long period, @NotNull ScheduledPacketHandler handler) {\n        Objects.requireNonNull(handler);\n        if (period <= 0) throw new RuntimeException(\"period cannot be <= 0\");\n        frame(period == 1 ? handler : (t, s) -> {\n            if (frame % period == 0) handler.handle(t, s);\n        });\n    }\n\n    /**\n     * Returns the name of the model being tracked.\n     *\n     * @return the model name\n     * @since 1.15.2\n     */\n    public @NotNull String name() {\n        return pipeline.name();\n    }\n\n    /**\n     * Calculates the height of the model based on its head bone position.\n     *\n     * @return the height\n     * @since 1.15.2\n     */\n    public double height() {\n        return heightSupplier.getAsFloat();\n    }\n\n    /**\n     * Checks if the tracker has been closed.\n     *\n     * @return true if closed, false otherwise\n     * @since 1.15.2\n     */\n    public boolean isClosed() {\n        return isClosed.get();\n    }\n\n    @Override\n    public void close() {\n        close(CloseReason.REMOVE);\n    }\n\n    protected void close(@NotNull CloseReason reason) {\n        if (isClosed.compareAndSet(false, true)) {\n            closeEventHandler.accept(this, reason);\n            shutdown();\n            pipeline.despawn();\n            LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> getClass().getSimpleName() + \" closed: \" + name());\n        }\n    }\n\n    /**\n     * Despawns the model for all players without closing the tracker completely.\n     *\n     * @since 1.15.2\n     */\n    public void despawn() {\n        if (!isClosed()) {\n            pipeline.despawn();\n            LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> getClass().getSimpleName() + \" despawned: \" + name());\n        }\n    }\n\n    /**\n     * Returns the tracker modifier.\n     *\n     * @return the modifier\n     * @since 1.15.2\n     */\n    public @NotNull TrackerModifier modifier() {\n        return modifier;\n    }\n\n    /**\n     * Pauses or resumes the tracker's ticking.\n     *\n     * @param pause true to pause, false to resume\n     * @return true if the state changed, false otherwise\n     * @since 1.15.2\n     */\n    public boolean pause(boolean pause) {\n        return tickPause.compareAndSet(!pause, pause);\n    }\n\n    /**\n     * Flags the tracker for a forced update on the next tick.\n     *\n     * @param force true to force update\n     * @return true if the state changed\n     * @since 1.15.2\n     */\n    public boolean forceUpdate(boolean force) {\n        return readyForForceUpdate.compareAndSet(!force, force);\n    }\n\n    /**\n     * Spawns the model for a specific player.\n     *\n     * @param player the target player\n     * @param bundler the packet bundler\n     * @return true if spawned successfully\n     * @since 1.15.2\n     */\n    protected boolean spawn(@NotNull PlatformPlayer player, @NotNull PacketBundler bundler) {\n        if (isClosed()) return false;\n        if (!EventUtil.call(ModelSpawnAtPlayerEvent.class, () -> new ModelSpawnAtPlayerEvent(player, this)).triggered()) return false;\n        return pipeline.spawn(player, bundler, spawned -> {\n            LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> getClass().getSimpleName() + \" is spawned at player \" + player.name() + \": \" + name());\n            task(spawned::load);\n        });\n    }\n\n    /**\n     * Removes the model for a specific player.\n     *\n     * @param player the target player\n     * @return true if removed successfully\n     * @since 1.15.2\n     */\n    public boolean remove(@NotNull PlatformPlayer player) {\n        if (isClosed()) return false;\n        EventUtil.call(ModelDespawnAtPlayerEvent.class, () -> new ModelDespawnAtPlayerEvent(player, this));\n        var result = pipeline.remove(player);\n        if (result) LogUtil.debug(DebugConfig.DebugOption.TRACKER, () -> getClass().getSimpleName() + \" is despawned at player \" + player.name() + \": \" + name());\n        return result;\n    }\n\n    /**\n     * Returns the number of players currently viewing the model.\n     *\n     * @return the player count\n     * @since 1.15.2\n     */\n    public int playerCount() {\n        return pipeline.playerCount();\n    }\n\n    /**\n     * Returns the current location of the model.\n     *\n     * @return the location\n     * @since 1.15.2\n     */\n    public abstract @NotNull PlatformLocation location();\n\n    /**\n     * Plays an animation by name with default settings.\n     *\n     * @param animation the animation name\n     * @return true if the animation started\n     * @since 1.15.2\n     */\n    public boolean animate(@NotNull String animation) {\n        return animate(animation, AnimationModifier.DEFAULT);\n    }\n\n    /**\n     * Plays an animation by name with a modifier.\n     *\n     * @param animation the animation name\n     * @param modifier the animation modifier\n     * @return true if the animation started\n     * @since 1.15.2\n     */\n    public boolean animate(@NotNull String animation, @NotNull AnimationModifier modifier) {\n        return animate(animation, modifier, () -> {});\n    }\n\n    /**\n     * Plays an animation by name with a modifier and a completion task.\n     *\n     * @param animation the animation name\n     * @param modifier the animation modifier\n     * @param removeTask the task to run when the animation ends\n     * @return true if the animation started\n     * @since 1.15.2\n     */\n    public boolean animate(@NotNull String animation, @NotNull AnimationModifier modifier, @NotNull Runnable removeTask) {\n        return renderer().animation(animation)\n            .map(get -> animate(get, modifier, removeTask))\n            .orElse(false);\n    }\n\n    /**\n     * Plays a blueprint animation with a modifier.\n     *\n     * @param animation the blueprint animation\n     * @param modifier the animation modifier\n     * @return true if the animation started\n     * @since 1.15.2\n     */\n    public boolean animate(@NotNull BlueprintAnimation animation, @NotNull AnimationModifier modifier) {\n        return animate(animation, modifier, () -> {});\n    }\n\n    public boolean animate(@NotNull TrackerAnimation<?> animation) {\n        return animation.play(this);\n    }\n\n    public boolean animate(@NotNull TrackerAnimation<?> animation, @NotNull Runnable removeTask) {\n        return animation.play(this, removeTask);\n    }\n\n\n    /**\n     * Plays a blueprint animation on filtered bones.\n     *\n     * @param animation the blueprint animation\n     * @param modifier the animation modifier\n     * @param removeTask the task to run when the animation ends\n     * @return true if the animation started\n     * @since 1.15.2\n     */\n    public boolean animate(@NotNull BlueprintAnimation animation, @NotNull AnimationModifier modifier, @NotNull Runnable removeTask) {\n        var script = animation.script(modifier);\n        if (script != null) scriptProcessor.addAnimation(animation.name(), script.iterator(modifier), modifier, () -> {});\n        return pipeline.matchAnimation((b, a) -> b.addAnimation(a, animation, modifier, removeTask));\n    }\n\n    /**\n     * Stops an animation by name.\n     *\n     * @param animation the animation name\n     * @return true if the animation was stopped\n     * @since 1.15.2\n     */\n    public boolean stopAnimation(@NotNull String animation) {\n        return stopAnimation(_ -> true, animation);\n    }\n\n    /**\n     * Stops an animation on filtered bones.\n     *\n     * @param filter the bone filter\n     * @param animation the animation name\n     * @return true if the animation was stopped\n     * @since 1.15.2\n     */\n    public boolean stopAnimation(@NotNull Predicate<RenderedBone> filter, @NotNull String animation) {\n        return stopAnimation(filter, animation, null);\n    }\n\n    /**\n     * Stops an animation on filtered bones for a specific player (optional).\n     *\n     * @param filter the bone filter\n     * @param animation the animation name\n     * @param player the player (can be null)\n     * @return true if the animation was stopped\n     * @since 1.15.2\n     */\n    public boolean stopAnimation(@NotNull Predicate<RenderedBone> filter, @NotNull String animation, @Nullable PlatformPlayer player) {\n        var script = scriptProcessor.stopAnimation(animation);\n        return pipeline.matchTree(b -> b.stopAnimation(filter, animation, player)) || script;\n    }\n\n    /**\n     * Replaces a running animation on filtered bones.\n     *\n     * @param target the name of the animation to replace\n     * @param animation the name of the new animation\n     * @param modifier the modifier for the new animation\n     * @return true if the replacement occurred\n     * @since 1.15.2\n     */\n    public boolean replace(@NotNull String target, @NotNull String animation, @NotNull AnimationModifier modifier) {\n        return renderer().animation(animation)\n            .map(get -> replace(target, get, modifier))\n            .orElse(false);\n    }\n\n    /**\n     * Replaces a running animation on filtered bones with a blueprint animation.\n     *\n     * @param target the name of the animation to replace\n     * @param animation the new blueprint animation\n     * @param modifier the modifier for the new animation\n     * @return true if the replacement occurred\n     * @since 1.15.2\n     */\n    public boolean replace(@NotNull String target, @NotNull BlueprintAnimation animation, @NotNull AnimationModifier modifier) {\n        var script = animation.script(modifier);\n        if (script != null) scriptProcessor.replaceAnimation(target, script.iterator(modifier), modifier);\n        return pipeline.matchAnimation((b, a) -> b.replaceAnimation(a, target, animation, modifier));\n    }\n\n    //--- Listener ---\n\n    /**\n     * Registers a hitbox-listener builder hook that is applied when hitboxes are created.\n     * <p>\n     * This delegates to {@link kr.toxicity.model.api.bone.BoneEventDispatcher#handleCreateHitBox(BiFunction)}\n     * in this tracker's render pipeline event dispatcher.\n     * </p>\n     *\n     * <pre>{@code\n     * tracker.listenHitBox((bone, builder) -> builder.interact(event -> {\n     *     // custom interaction handling\n     * }));\n     * }</pre>\n     *\n     * @param function the hitbox listener builder transformer\n     * @since 2.1.0\n     */\n    public void listenHitBox(@NotNull BiFunction<RenderedBone, HitBoxListener.Builder, HitBoxListener.Builder> function) {\n        pipeline.hitboxes().forEach(hb -> {\n            var bone = hb.positionSource();\n            hb.listener(b -> function.apply(bone, b));\n        });\n        pipeline.eventDispatcher().handleCreateHitBox(function);\n    }\n\n    /**\n     * Registers a hitbox event listener for newly created hitboxes.\n     * <p>\n     * This is a convenience wrapper over {@link #listenHitBox(BiFunction)}.\n     * </p>\n     *\n     * <pre>{@code\n     * tracker.listenHitBox(HitBoxInteractEvent.class, event -> {\n     *     // custom interaction handling\n     * });\n     * }</pre>\n     *\n     * @param eventClass target hitbox event class\n     * @param consumer event consumer\n     * @param <T> event type\n     * @since 2.1.0\n     */\n    public <T extends HitBoxEvent> void listenHitBox(@NotNull Class<T> eventClass, @NotNull Consumer<T> consumer) {\n        listenHitBox((_, builder) -> builder.listen(eventClass, consumer));\n    }\n\n    /**\n     * Creates a hitbox for bones matching a predicate.\n     *\n     * @param entity the source entity for the hitbox\n     * @param listener the hitbox listener\n     * @param predicate the bone predicate\n     * @return true if any hitboxes were created\n     * @since 1.15.2\n     */\n    public boolean createHitBox(@NotNull BaseEntity entity, @Nullable HitBoxListener listener, @NotNull BonePredicate predicate) {\n        return tryUpdate((b, p) -> b.createHitBox(entity, p, listener), predicate);\n    }\n\n    /**\n     * Retrieves or creates a hitbox for a specific bone.\n     *\n     * @param entity the source entity\n     * @param listener the hitbox listener\n     * @param predicate the bone predicate\n     * @return the hitbox, or null if not found/created\n     * @since 1.15.2\n     */\n    public @Nullable HitBox hitbox(@NotNull BaseEntity entity, @Nullable HitBoxListener listener, @NotNull Predicate<RenderedBone> predicate) {\n        return pipeline.firstNotNull(bone -> {\n            if (predicate.test(bone)) {\n                if (bone.getHitBox() == null) bone.createHitBox(entity, BonePredicate.TRUE, listener);\n                return bone.getHitBox();\n            } else return null;\n        });\n    }\n\n    /**\n     * Creates a nametag for bones matching a predicate.\n     *\n     * @param predicate the bone predicate\n     * @param consumer a consumer to configure the nametag\n     * @return true if any nametags were created\n     * @since 1.15.2\n     */\n    public boolean createNametag(@NotNull BonePredicate predicate, @NotNull BiConsumer<RenderedBone, ModelNametag> consumer) {\n        return tryUpdate((b, p) -> b.createNametag(p, tag -> {\n            consumer.accept(b, tag);\n            perPlayerTick((tracker, player) -> {\n                if (pipeline.getSource() instanceof RenderSource.Entity entity && entity.entity().uuid().equals(player.uuid())) return;\n                tag.teleport(tracker.location());\n                tag.send(player);\n            });\n        }), predicate);\n    }\n\n    //--- Update action ---\n\n    /**\n     * Forces an update action on all bones.\n     *\n     * @param action the update action\n     * @param <T> the action type\n     * @since 1.15.2\n     */\n    public <T extends TrackerUpdateAction> void update(@NotNull T action) {\n        update(action, BonePredicate.TRUE);\n    }\n\n    /**\n     * Forces an update action on filtered bones.\n     *\n     * @param action the update action\n     * @param predicate the bone predicate\n     * @param <T> the action type\n     * @since 1.15.2\n     */\n    public <T extends TrackerUpdateAction> void update(@NotNull T action, @NotNull Predicate<RenderedBone> predicate) {\n        update(action, BonePredicate.from(predicate));\n    }\n\n    /**\n     * Forces an update action on filtered bones.\n     *\n     * @param action the update action\n     * @param predicate the bone predicate\n     * @param <T> the action type\n     * @since 1.15.2\n     */\n    public <T extends TrackerUpdateAction> void update(@NotNull T action, @NotNull BonePredicate predicate) {\n        if (tryUpdate(action, predicate)) forceUpdate(true);\n    }\n\n    /**\n     * Tries to apply an update action to bones matching a predicate.\n     *\n     * @param action the update action\n     * @param predicate the bone predicate\n     * @return true if any bones were updated\n     * @since 1.15.2\n     */\n    public boolean tryUpdate(@NotNull BiPredicate<RenderedBone, BonePredicate> action, @NotNull BonePredicate predicate) {\n        return pipeline.matchTree(predicate, action);\n    }\n\n    /**\n     * Retrieves a bone by name.\n     *\n     * @param name the bone name\n     * @return the bone, or null if not found\n     * @since 1.15.2\n     */\n    public @Nullable RenderedBone bone(@NotNull BoneName name) {\n        return pipeline.boneOf(name);\n    }\n\n    /**\n     * Retrieves a bone by name string.\n     *\n     * @param name the bone name\n     * @return the bone, or null if not found\n     * @since 1.15.2\n     */\n    public @Nullable RenderedBone bone(@NotNull String name) {\n        return bone(BonePredicate.name(name));\n    }\n\n    /**\n     * Retrieves the first bone matching a predicate.\n     *\n     * @param predicate the bone predicate\n     * @return the bone, or null if not found\n     * @since 1.15.2\n     */\n    public @Nullable RenderedBone bone(@NotNull Predicate<RenderedBone> predicate) {\n        return pipeline.stream()\n            .filter(predicate)\n            .findFirst()\n            .orElse(null);\n    }\n\n    /**\n     * Returns a collection of all bones in the model.\n     *\n     * @return the bones\n     * @since 1.15.2\n     */\n    public @NotNull @Unmodifiable Collection<RenderedBone> bones() {\n        return pipeline.bones();\n    }\n\n    /**\n     * Returns a stream of all model displays.\n     *\n     * @return the displays\n     * @since 1.15.2\n     */\n    public @NotNull Stream<ModelDisplay> displays() {\n        return pipeline.stream()\n            .map(RenderedBone::getDisplay)\n            .filter(Objects::nonNull);\n    }\n\n    /**\n     * Hides the tracker from a specific player.\n     *\n     * @param player the target player\n     * @return true if hidden successfully\n     * @since 1.15.2\n     */\n    public boolean hide(@NotNull PlatformPlayer player) {\n        return EventUtil.call(PlayerHideTrackerEvent.class, () -> new PlayerHideTrackerEvent(this, player)).triggered() && pipeline.hide(player);\n    }\n\n    /**\n     * Checks if the tracker is hidden from a specific player.\n     *\n     * @param player the target player\n     * @return true if hidden\n     * @since 1.15.2\n     */\n    public boolean isHide(@NotNull PlatformPlayer player) {\n        return pipeline.isHide(player);\n    }\n\n    /**\n     * Shows the tracker to a specific player.\n     *\n     * @param player the target player\n     * @return true if shown successfully\n     * @since 1.15.2\n     */\n    public boolean show(@NotNull PlatformPlayer player) {\n        return EventUtil.call(PlayerShowTrackerEvent.class, () -> new PlayerShowTrackerEvent(this, player)).triggered() && pipeline.show(player);\n    }\n\n    /**\n     * Registers a handler for the tracker close event.\n     *\n     * @param consumer the handler\n     * @since 1.15.2\n     */\n    public void handleCloseEvent(@NotNull BiConsumer<Tracker, CloseReason> consumer) {\n        closeEventHandler = closeEventHandler.andThen(Objects.requireNonNull(consumer));\n    }\n\n    /**\n     * Checks if the model is spawned for a player (by UUID).\n     *\n     * @param uuid the player UUID\n     * @return true if spawned\n     * @since 1.15.2\n     */\n    public boolean isSpawned(@NotNull UUID uuid) {\n        return pipeline.isSpawned(uuid);\n    }\n\n    /**\n     * Checks if the model is spawned for a player.\n     *\n     * @param player the player\n     * @return true if spawned\n     * @since 1.15.2\n     */\n    public boolean isSpawned(@NotNull PlatformPlayer player) {\n        return isSpawned(player.uuid());\n    }\n\n    /**\n     * Returns the renderer associated with this tracker.\n     *\n     * @return the renderer\n     * @since 1.15.2\n     */\n    public @NotNull ModelRenderer renderer() {\n        return pipeline.getParent();\n    }\n\n    /**\n     * Marks the tracker for removal.\n     *\n     * @param removal true to mark for removal\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public void forRemoval(boolean removal) {\n        forRemoval.set(removal);\n    }\n\n    /**\n     * Checks if the tracker is marked for removal.\n     *\n     * @return true if marked for removal\n     * @since 1.15.2\n     */\n    @ApiStatus.Internal\n    public boolean forRemoval() {\n        return forRemoval.get();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == this) return true;\n        if (!(o instanceof Tracker tracker)) return false;\n        return name().equals(tracker.name());\n    }\n\n    @Override\n    public int hashCode() {\n        return name().hashCode();\n    }\n\n    /**\n     * Functional interface for handling scheduled packets.\n     *\n     * @since 1.15.2\n     */\n    @FunctionalInterface\n    public interface ScheduledPacketHandler {\n        /**\n         * Handles packets for a tracker.\n         *\n         * @param tracker the tracker\n         * @param bundlerSet the set of packet bundlers\n         * @since 1.15.2\n         */\n        void handle(@NotNull Tracker tracker, @NotNull BundlerSet bundlerSet);\n\n        /**\n         * Chains this handler with another.\n         *\n         * @param other the other handler\n         * @return the combined handler\n         * @since 1.15.2\n         */\n        default @NotNull ScheduledPacketHandler then(@NotNull ScheduledPacketHandler other) {\n            return (t, s) -> {\n                handle(t, s);\n                other.handle(t, s);\n            };\n        }\n    }\n\n    /**\n     * Holds different types of packet bundlers for a tracker tick.\n     *\n     * @since 1.15.2\n     */\n    public final class BundlerSet {\n        @Getter\n        private PacketBundler tickBundler = pipeline.createBundler();\n        @Getter\n        private PacketBundler dataBundler = pipeline.createBundler();\n        @Getter\n        private AnimationBundler viewBundler = pipeline.createAnimationBundler();\n\n        private final Map<UUID, PerPlayerCache> perPlayerViewBundler = new ConcurrentHashMap<>();\n\n        /**\n         * Private initializer\n         */\n        private BundlerSet() {\n        }\n\n        private void send() {\n            globalSend();\n            perPlayerSend();\n        }\n\n        private void perPlayerSend() {\n            if (perPlayerViewBundler.isEmpty()) return;\n            perPlayerViewBundler.values().forEach(PerPlayerCache::send);\n        }\n\n        private void globalSend() {\n            if (tickBundler.isNotEmpty()) {\n                pipeline.allPlayer().map(PlayerChannelHandler::player).forEach(tickBundler::send);\n                tickBundler = pipeline.createBundler();\n            }\n            if (dataBundler.isNotEmpty()) {\n                pipeline.nonHidePlayer().map(PlayerChannelHandler::player).forEach(dataBundler::send);\n                dataBundler = pipeline.createBundler();\n            }\n            if (viewBundler.isNotEmpty()) {\n                pipeline.viewedPlayer().filter(p -> !perPlayerViewBundler.containsKey(p.uuid())).forEach(viewBundler::send);\n                viewBundler = pipeline.createAnimationBundler();\n            }\n        }\n    }\n\n    @RequiredArgsConstructor\n    private final class PerPlayerCache {\n        private final UUID uuid;\n        private final AtomicInteger counter = new AtomicInteger();\n        private AnimationBundler bundler = pipeline.createAnimationBundler();\n\n        private @NotNull Optional<PlayerChannelHandler> channel() {\n            return Optional.ofNullable(pipeline.channel(uuid));\n        }\n\n        public void add() {\n            if (counter.getAndIncrement() == 0) {\n                channel().ifPresent(handler -> EventUtil.call(PlayerPerAnimationStartEvent.class, () -> new PlayerPerAnimationStartEvent(Tracker.this, handler.player())));\n            }\n        }\n\n        public void remove() {\n            if (counter.decrementAndGet() == 0) {\n                bundlerSet.perPlayerViewBundler.remove(uuid);\n                channel().ifPresent(handler -> {\n                    var bundler = pipeline.createBundler();\n                    pipeline.forEach(bone -> bone.forceTransformation(bundler));\n                    bundler.send(handler.player());\n                    EventUtil.call(PlayerPerAnimationEndEvent.class, () -> new PlayerPerAnimationEndEvent(Tracker.this, handler.player()));\n                });\n            }\n        }\n\n        private void send() {\n            if (pipeline.tick(uuid, bundler) && bundler.isNotEmpty()) {\n                channel().ifPresent(handler -> bundler.send(handler));\n                bundler = pipeline.createAnimationBundler();\n            }\n        }\n    }\n\n    @Override\n    public String toString() {\n        return name();\n    }\n\n    /**\n     * Reason for closing a tracker.\n     *\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor\n    public enum CloseReason {\n        /**\n         * The tracker was manually removed.\n         * @since 1.15.2\n         */\n        REMOVE(false),\n        /**\n         * The plugin is being disabled.\n         * @since 1.15.2\n         */\n        PLUGIN_DISABLE(true),\n        /**\n         * The entity or tracker was despawned.\n         * @since 1.15.2\n         */\n        DESPAWN(true)\n        ;\n        private final boolean save;\n\n        /**\n         * Checks if the tracker state should be saved.\n         *\n         * @return true if it should be saved\n         * @since 1.15.2\n         */\n        public boolean shouldBeSave() {\n            return save;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/TrackerAnimation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport lombok.AccessLevel;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jspecify.annotations.NonNull;\n\nimport java.util.Comparator;\nimport java.util.Objects;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * A record representing an animation configuration for a {@link Tracker}.\n * <p>\n * This class defines how an animation should be applied, its priority, and the tasks to run\n * during different lifecycle stages of the animation.\n *\n * @param <T> the type of tracker this animation applies to\n * @param name the unique name of the animation\n * @param priority the priority of the animation (higher values usually take precedence)\n * @param targetClass the class type of the tracker\n * @param applyCondition a condition to check if the animation can be played\n * @param modifierBuilder a function that provides an {@link AnimationModifier}\n * @param removeTask the task to run when the animation is removed\n * @param successTask the task to run when the animation starts successfully\n * @param fallbackTask the task to run when the animation fails to start\n * @since 2.2.0\n */\npublic record TrackerAnimation<T extends Tracker>(\n    @NotNull String name,\n    int priority,\n    @NotNull Class<T> targetClass,\n    @NotNull Predicate<? super T> applyCondition,\n    @NotNull Function<? super T, AnimationModifier> modifierBuilder,\n    @NotNull Consumer<? super T> removeTask,\n    @NotNull Consumer<? super T> successTask,\n    @NotNull Consumer<? super T> fallbackTask\n) implements Comparable<TrackerAnimation<T>> {\n\n    private static final Comparator<TrackerAnimation<?>> COMPARATOR = Comparator.comparing((TrackerAnimation<?> animation) -> animation.priority)\n        .thenComparing(animation -> animation.name);\n\n    /**\n     * Creates a new builder for a {@link TrackerAnimation}.\n     *\n     * @param name the name of the animation\n     * @return a new builder instance\n     * @since 2.2.0\n     */\n    public static @NotNull Builder<Tracker> builder(@NotNull String name) {\n        return new Builder<>(name, Tracker.class);\n    }\n\n    @ApiStatus.Internal\n    public TrackerAnimation {\n    }\n\n    @Override\n    public int compareTo(@NonNull TrackerAnimation<T> o) {\n        return COMPARATOR.compare(this, o);\n    }\n\n    boolean play(@NotNull Tracker tracker) {\n        return play(tracker, () -> {});\n    }\n\n    boolean play(@NotNull Tracker tracker, @NotNull Runnable removeTask) {\n        if (!targetClass.isInstance(tracker)) return false;\n        var cast = targetClass.cast(tracker);\n        if (!applyCondition.test(cast)) return false;\n        var result = cast.animate(\n            name,\n            modifierBuilder.apply(cast),\n            () -> {\n                this.removeTask.accept(cast);\n                removeTask.run();\n            }\n        );\n        if (result) successTask.accept(cast);\n        else fallbackTask.accept(cast);\n        return result;\n    }\n\n    /**\n     * A builder class for creating instances of {@link TrackerAnimation}.\n     *\n     * @param <T> the type of tracker\n     * @since 2.2.0\n     */\n    @RequiredArgsConstructor(access = AccessLevel.PACKAGE)\n    public static final class Builder<T extends Tracker> {\n        private final String name;\n        private final Class<T> targetClass;\n\n        private int priority = 0;\n        private @NotNull Predicate<? super T> applyCondition = _ -> true;\n        private @NotNull Function<? super T, AnimationModifier> modifierBuilder = _ -> AnimationModifier.DEFAULT;\n        private @NotNull Consumer<? super T> removeTask = _ -> {};\n        private @NotNull Consumer<? super T> successTask = _ -> {};\n        private @NotNull Consumer<? super T> fallbackTask = _ -> {};\n\n        /**\n         * Changes the target tracker type for this builder.\n         *\n         * @param newTargetClass the new target class\n         * @param <R> the new tracker type\n         * @return a new builder for the specified type\n         * @since 2.2.0\n         */\n        public <R extends T> Builder<R> type(@NotNull Class<R> newTargetClass) {\n            return new Builder<>(name, newTargetClass)\n                .priority(priority)\n                .check(applyCondition)\n                .modifier(modifierBuilder)\n                .onRemove(removeTask)\n                .onSuccess(successTask)\n                .onFallback(fallbackTask);\n        }\n\n        /**\n         * Sets the priority of the animation.\n         *\n         * @param priority the priority\n         * @return this builder\n         * @since 2.2.0\n         */\n        public @NotNull Builder<T> priority(int priority) {\n            this.priority = priority;\n            return this;\n        }\n\n        /**\n         * Sets the condition that must be met for the animation to play.\n         *\n         * @param applyCondition the condition predicate\n         * @return this builder\n         * @since 2.2.0\n         */\n        public @NotNull Builder<T> check(@NotNull Predicate<? super T> applyCondition) {\n            this.applyCondition = Objects.requireNonNull(applyCondition, \"applyCondition cannot be null\");\n            return this;\n        }\n\n        /**\n         * Sets the modifier builder for the animation.\n         *\n         * @param modifierBuilder a function that provides an {@link AnimationModifier}\n         * @return this builder\n         * @since 2.2.0\n         */\n        public @NotNull Builder<T> modifier(@NotNull Function<? super T, AnimationModifier> modifierBuilder) {\n            this.modifierBuilder = Objects.requireNonNull(modifierBuilder, \"modifierBuilder cannot be null\");\n            return this;\n        }\n\n        /**\n         * Sets the task to run when the animation is removed.\n         *\n         * @param removeTask the removal task\n         * @return this builder\n         * @since 2.2.0\n         */\n        public @NotNull Builder<T> onRemove(@NotNull Consumer<? super T> removeTask) {\n            this.removeTask = Objects.requireNonNull(removeTask, \"removeTask cannot be null\");\n            return this;\n        }\n\n        /**\n         * Sets the task to run when the animation starts successfully.\n         *\n         * @param successTask the success task\n         * @return this builder\n         * @since 2.2.0\n         */\n        public @NotNull Builder<T> onSuccess(@NotNull Consumer<? super T> successTask) {\n            this.successTask = Objects.requireNonNull(successTask, \"successTask cannot be null\");\n            return this;\n        }\n\n        /**\n         * Sets the task to run when the animation fails to start.\n         *\n         * @param fallbackTask the fallback task\n         * @return this builder\n         * @since 2.2.0\n         */\n        public @NotNull Builder<T> onFallback(@NotNull Consumer<? super T> fallbackTask) {\n            this.fallbackTask = Objects.requireNonNull(fallbackTask, \"fallbackTask cannot be null\");\n            return this;\n        }\n\n        /**\n         * Builds the {@link TrackerAnimation} instance.\n         *\n         * @return the constructed animation\n         * @since 2.2.0\n         */\n        public @NotNull TrackerAnimation<T> build() {\n            return new TrackerAnimation<>(\n                name,\n                priority,\n                targetClass,\n                applyCondition,\n                modifierBuilder,\n                removeTask,\n                successTask,\n                fallbackTask\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/TrackerBuiltInAnimation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.animation.AnimationIterator;\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport java.util.function.Function;\n\nimport static kr.toxicity.model.api.util.CollectionUtil.newAddressingMap;\n\n/**\n * A utility class for managing built-in animations for trackers.\n *\n * @since 2.2.0\n */\npublic final class TrackerBuiltInAnimation {\n\n    private static final Map<String, TrackerAnimation<?>> BY_NAME = newAddressingMap();\n    private static final SortedSet<TrackerAnimation<?>> BY_PRIORITY = new TreeSet<>();\n\n    /**\n     * Registers a new tracker animation.\n     *\n     * @param name             the name of the animation\n     * @param builderFunction the function to build the animation\n     * @param <T>              the tracker type\n     * @return the registered animation\n     */\n    public static <T extends Tracker> @NotNull TrackerAnimation<T> register(@NotNull String name, @NotNull Function<TrackerAnimation.Builder<Tracker>, TrackerAnimation.Builder<T>> builderFunction) {\n        var animation = builderFunction.apply(TrackerAnimation.builder(name)).build();\n        register(animation);\n        return animation;\n    }\n\n    private static void register(@NotNull TrackerAnimation<?> animation) {\n        synchronized (BY_NAME) {\n            if (BY_NAME.put(animation.name(), animation) != null) throw new IllegalStateException(\"Duplicate animation name: \" + animation.name());\n            BY_PRIORITY.add(animation);\n        }\n    }\n\n    static void play(@NotNull Tracker tracker) {\n        BY_PRIORITY.forEach(animation -> animation.play(tracker));\n    }\n\n    /**\n     * The default idle animation.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<Tracker> IDLE = register(\"idle\", b -> b.modifier(_ -> AnimationModifier.builder()\n        .start(6)\n        .type(AnimationIterator.Type.LOOP)\n        .build()\n    ));\n\n    /**\n     * The default walk animation for entity trackers.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<EntityTracker> WALK = register(\"walk\", b -> b.type(EntityTracker.class)\n        .modifier(tracker -> {\n            var property = tracker.registry().animationProperty;\n            return AnimationModifier.builder()\n                .start(6)\n                .predicate(property.onWalk)\n                .speed(tracker.modifier.damageAnimation() ? property.walkSpeed : null)\n                .type(AnimationIterator.Type.LOOP)\n                .build();\n        }));\n\n    /**\n     * The default flying idle animation for entity trackers.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<EntityTracker> IDLE_FLY = register(\"idle_fly\", b -> b.type(EntityTracker.class)\n        .modifier(tracker -> {\n            var property = tracker.registry().animationProperty;\n            return AnimationModifier.builder()\n                .start(6)\n                .predicate(property.onFly)\n                .type(AnimationIterator.Type.LOOP)\n                .build();\n        }));\n\n    /**\n     * The default flying walk animation for entity trackers.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<EntityTracker> WALK_FLY = register(\"walk_fly\", b -> b.type(EntityTracker.class)\n        .modifier(tracker -> {\n            var property = tracker.registry().animationProperty;\n            return AnimationModifier.builder()\n                .start(6)\n                .predicate(() -> property.onFly.getAsBoolean() && property.onWalk.getAsBoolean())\n                .type(AnimationIterator.Type.LOOP)\n                .build();\n        }));\n\n    /**\n     * The default spawn animation for entity trackers.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<EntityTracker> SPAWN = register(\"spawn\", b -> b.type(EntityTracker.class)\n        .modifier(_ -> AnimationModifier.DEFAULT_WITH_PLAY_ONCE));\n\n    private TrackerBuiltInAnimation() {\n        throw new IllegalStateException(\"Utility class\");\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/TrackerData.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.SerializedName;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.UUID;\n\n/**\n * Represents the persistent data state of a tracker.\n * <p>\n * This record holds configuration and state information such as model ID, scaling, rotation,\n * and visibility options, which can be serialized to JSON.\n * </p>\n *\n * @param id the model ID\n * @param scaler the model scaler\n * @param rotator the model rotator\n * @param modifier the tracker modifier\n * @param bodyRotator the body rotation data\n * @param hideOption the entity hide options\n * @param markForSpawn the set of player UUIDs marked for spawning\n * @since 1.15.2\n */\npublic record TrackerData(\n    @NotNull String id,\n    @Nullable ModelScaler scaler,\n    @Nullable ModelRotator rotator,\n    @NotNull TrackerModifier modifier,\n    @Nullable @SerializedName(\"body-rotator\") EntityBodyRotator.RotatorData bodyRotator,\n    @Nullable @SerializedName(\"hide-option\") EntityHideOption hideOption,\n    @Nullable @SerializedName(\"mark-for-spawn\") Set<UUID> markForSpawn\n) {\n    /**\n     * The GSON parser for serializing and deserializing tracker data.\n     * @since 1.15.2\n     */\n    public static final Gson PARSER = new GsonBuilder()\n        .registerTypeAdapter(ModelScaler.class, (JsonDeserializer<ModelScaler>) (json, _, _) -> json.isJsonObject() ? ModelScaler.deserialize(json.getAsJsonObject()) : ModelScaler.defaultScaler())\n        .registerTypeAdapter(ModelScaler.class, (JsonSerializer<ModelScaler>) (src, _, _) -> src.serialize())\n        .registerTypeAdapter(ModelRotator.class, (JsonDeserializer<ModelRotator>) (json, _, _) -> json.isJsonObject() ? ModelRotator.deserialize(json.getAsJsonObject()) : ModelRotator.YAW)\n        .registerTypeAdapter(ModelRotator.class, (JsonSerializer<ModelRotator>) (src, _, _) -> src.serialize())\n        .registerTypeAdapter(EntityHideOption.class, (JsonDeserializer<EntityHideOption>) (json, _, _) -> json.isJsonArray() ? EntityHideOption.deserialize(json.getAsJsonArray()) : EntityHideOption.DEFAULT)\n        .registerTypeAdapter(EntityHideOption.class, (JsonSerializer<EntityHideOption>) (src, _, _) -> src.serialize())\n        .registerTypeAdapter(UUID.class, (JsonDeserializer<UUID>) (json, _, _) -> UUID.fromString(json.getAsString()))\n        .registerTypeAdapter(UUID.class, (JsonSerializer<UUID>) (src, _, _) -> new JsonPrimitive(src.toString()))\n        .create();\n\n    /**\n     * Applies this data to an existing entity tracker.\n     *\n     * @param tracker the target tracker\n     * @since 1.15.2\n     */\n    public void applyAs(@NotNull EntityTracker tracker) {\n        tracker.markPlayerForSpawn(markForSpawn());\n        tracker.hideOption(hideOption());\n        tracker.scaler(scaler());\n        tracker.rotator(rotator());\n        tracker.bodyRotator().setValue(bodyRotator());\n    }\n\n    /**\n     * Serializes this data to a JSON element.\n     *\n     * @return the JSON element\n     * @since 1.15.2\n     */\n    public @NotNull JsonElement serialize() {\n        return PARSER.toJsonTree(this);\n    }\n\n    /**\n     * Deserializes tracker data from a JSON element.\n     *\n     * @param element the JSON element\n     * @return the tracker data\n     * @since 1.15.2\n     */\n    public static @NotNull TrackerData deserialize(@NotNull JsonElement element) {\n        return element.isJsonPrimitive() ? new TrackerData(\n            element.getAsString(),\n            ModelScaler.entity(),\n            null,\n            TrackerModifier.DEFAULT,\n            EntityBodyRotator.defaultData(),\n            null,\n            null\n        ) : PARSER.fromJson(element, TrackerData.class);\n    }\n\n    /**\n     * Returns the model scaler, or a default entity scaler if not specified.\n     *\n     * @return the model scaler\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull ModelScaler scaler() {\n        return scaler != null ? scaler : ModelScaler.entity();\n    }\n\n    /**\n     * Returns the model rotator, or a default YAW rotator if not specified.\n     *\n     * @return the model rotator\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull ModelRotator rotator() {\n        return rotator != null ? rotator : ModelRotator.YAW;\n    }\n\n    /**\n     * Returns the entity hide option, or the default hide option if not specified.\n     *\n     * @return the entity hide option\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull EntityHideOption hideOption() {\n        return hideOption != null ? hideOption : EntityHideOption.DEFAULT;\n    }\n\n    /**\n     * Returns the set of player UUIDs marked for spawning, or an empty set if not specified.\n     *\n     * @return the set of player UUIDs marked for spawning\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull Set<UUID> markForSpawn() {\n        return markForSpawn != null ? markForSpawn : Collections.emptySet();\n    }\n\n    /**\n     * Returns the body rotation data, or default body rotation data if not specified.\n     *\n     * @return the body rotation data\n     * @since 1.15.2\n     */\n    @Override\n    public @NotNull EntityBodyRotator.RotatorData bodyRotator() {\n        return bodyRotator != null ? bodyRotator : EntityBodyRotator.defaultData();\n    }\n\n    /**\n     * Serializes this TrackerData object to a JSON string.\n     *\n     * @return a JSON string representation of this object\n     * @since 1.15.2\n     */\n    @NotNull\n    @Override\n    public String toString() {\n        return serialize().toString();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/TrackerExtraAnimation.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.animation.AnimationModifier;\n\n/**\n * A utility class that contains predefined {@link TrackerAnimation} instances for common entity actions.\n *\n * @since 2.2.0\n */\npublic final class TrackerExtraAnimation {\n\n    /**\n     * Animation played when an entity dies.\n     * Closes the tracker and marks it for removal upon completion.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<EntityTracker> DEATH = TrackerAnimation.builder(\"death\")\n        .type(EntityTracker.class)\n        .modifier(_ -> AnimationModifier.DEFAULT_WITH_PLAY_ONCE)\n        .onRemove(Tracker::close)\n        .onSuccess(tracker -> tracker.forRemoval(true))\n        .build();\n\n    /**\n     * Animation played when an entity takes damage.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<EntityTracker> DAMAGE = TrackerAnimation.builder(\"damage\")\n        .type(EntityTracker.class)\n        .modifier(_ -> AnimationModifier.DEFAULT_WITH_PLAY_ONCE)\n        .build();\n\n    /**\n     * Animation played when an entity jumps.\n     *\n     * @since 2.2.0\n     */\n    public static final TrackerAnimation<EntityTracker> JUMP = TrackerAnimation.builder(\"jump\")\n        .type(EntityTracker.class)\n        .modifier(_ -> AnimationModifier.DEFAULT_WITH_PLAY_ONCE)\n        .build();\n\n    private TrackerExtraAnimation() {\n        throw new IllegalStateException(\"Utility class\");\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/TrackerModifier.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Configuration options for a {@link Tracker}.\n * <p>\n * This record controls various behaviors such as visibility checks (sight trace),\n * automatic damage animations, and damage tinting effects.\n * </p>\n *\n * @param sightTrace whether to perform sight tracing for visibility\n * @param damageAnimation whether to play automatic damage animations\n * @param damageTint whether to apply a red tint when damaged\n * @since 1.15.2\n */\npublic record TrackerModifier(\n    @SerializedName(\"sight-trace\") boolean sightTrace,\n    @SerializedName(\"damage-animation\") boolean damageAnimation,\n    @SerializedName(\"damage-tint\") boolean damageTint\n) {\n    /**\n     * The default modifier configuration (all enabled).\n     * @since 1.15.2\n     */\n    public static final TrackerModifier DEFAULT = new TrackerModifier(\n        true,\n        true,\n        true\n    );\n\n    /**\n     * Creates a new builder initialized with default values.\n     *\n     * @return a new builder\n     * @since 1.15.2\n     */\n    public static @NotNull Builder builder() {\n        return DEFAULT.toBuilder();\n    }\n\n    /**\n     * Creates a new builder initialized with this modifier's values.\n     *\n     * @return a new builder\n     * @since 1.15.2\n     */\n    public @NotNull Builder toBuilder() {\n        return new Builder(this);\n    }\n\n    /**\n     * Builder for {@link TrackerModifier}.\n     *\n     * @since 1.15.2\n     */\n    public static final class Builder {\n        private boolean sightTrace;\n        private boolean damageAnimation;\n        private boolean damageTint;\n\n        /**\n         * Private initializer\n         * @param modifier modifier\n         */\n        private Builder(@NotNull TrackerModifier modifier) {\n            this.sightTrace = modifier.sightTrace;\n            this.damageAnimation = modifier.damageAnimation;\n            this.damageTint = modifier.damageTint;\n        }\n\n        /**\n         * Sets whether to use sight tracing.\n         *\n         * @param sightTrace true to enable sight tracing\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder sightTrace(boolean sightTrace) {\n            this.sightTrace = sightTrace;\n            return this;\n        }\n\n        /**\n         * Sets whether to enable damage animations.\n         *\n         * @param damageAnimation true to enable damage animations\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder damageAnimation(boolean damageAnimation) {\n            this.damageAnimation = damageAnimation;\n            return this;\n        }\n\n        /**\n         * Sets whether to enable damage tinting.\n         *\n         * @param damageTint true to enable damage tinting\n         * @return this builder\n         * @since 1.15.2\n         */\n        public @NotNull Builder damageTint(boolean damageTint) {\n            this.damageTint = damageTint;\n            return this;\n        }\n\n        /**\n         * Builds the {@link TrackerModifier}.\n         *\n         * @return the created modifier\n         * @since 1.15.2\n         */\n        public @NotNull TrackerModifier build() {\n            return new TrackerModifier(\n                sightTrace,\n                damageAnimation,\n                damageTint\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/tracker/TrackerUpdateAction.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.tracker;\n\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.platform.PlatformBillboard;\nimport kr.toxicity.model.api.util.TransformedItemStack;\nimport kr.toxicity.model.api.util.function.BonePredicate;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.BiPredicate;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\n/**\n * Represents an action that updates the state of a {@link RenderedBone}.\n * <p>\n * Actions can modify display properties like brightness, glow, item stack, and more.\n * They are applied to bones matching a specific predicate.\n * </p>\n *\n * @since 1.15.2\n */\npublic sealed interface TrackerUpdateAction extends BiPredicate<RenderedBone, BonePredicate> {\n\n    /**\n     * Creates an action to update display brightness.\n     *\n     * @param block the block light level\n     * @param sky the skylight level\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull Brightness brightness(int block, int sky) {\n        return new Brightness(block, sky);\n    }\n\n    /**\n     * Creates an action to toggle the glowing effect.\n     *\n     * @param glow true to enable glow\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull Glow glow(boolean glow) {\n        return glow ? Glow.TRUE : Glow.FALSE;\n    }\n\n    /**\n     * Creates an action to set the glow color.\n     *\n     * @param glowColor the RGB glow color\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull GlowColor glowColor(int glowColor) {\n        return new GlowColor(glowColor);\n    }\n\n    /**\n     * Creates an action to set the view range.\n     *\n     * @param viewRange the view range\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull ViewRange viewRange(float viewRange) {\n        return new ViewRange(viewRange);\n    }\n\n    /**\n     * Creates an action to apply a tint color.\n     *\n     * @param rgb the RGB tint color\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull Tint tint(int rgb) {\n        return new Tint(rgb);\n    }\n\n    /**\n     * Creates an action to revert to the previous tint.\n     *\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull PreviousTint previousTint() {\n        return PreviousTint.INSTANCE;\n    }\n\n    /**\n     * Creates an action to toggle the enchanted glint effect.\n     *\n     * @param enchant true to enable glint\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull Enchant enchant(boolean enchant) {\n        return enchant ? Enchant.TRUE : Enchant.FALSE;\n    }\n\n    /**\n     * Creates an action to toggle the visibility of a part.\n     *\n     * @param toggle true to show, false to hide\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull TogglePart togglePart(boolean toggle) {\n        return toggle ? TogglePart.TRUE : TogglePart.FALSE;\n    }\n\n    /**\n     * Creates an action to update the displayed item stack.\n     *\n     * @param itemStack the new item stack\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull ItemStack itemStack(@NotNull TransformedItemStack itemStack) {\n        Objects.requireNonNull(itemStack);\n        return new ItemStack(itemStack);\n    }\n\n    /**\n     * Creates an action to set the billboard constraint.\n     *\n     * @param billboard the billboard type\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull Billboard billboard(@NotNull PlatformBillboard billboard) {\n        Objects.requireNonNull(billboard);\n        return new Billboard(billboard);\n    }\n\n    /**\n     * Creates an action to update the item mapping.\n     *\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull ItemMapping itemMapping() {\n        return ItemMapping.INSTANCE;\n    }\n\n    /**\n     * Creates an action to set the movement interpolation duration.\n     *\n     * @param moveDuration the duration in ticks\n     * @return the action\n     * @since 1.15.2\n     */\n    static @NotNull MoveDuration moveDuration(int moveDuration) {\n        return new MoveDuration(moveDuration);\n    }\n\n    /**\n     * Combines multiple actions into a single composite action.\n     *\n     * @param actions the actions to combine\n     * @return the composite action\n     * @since 1.15.2\n     */\n    static @NotNull TrackerUpdateAction composite(@NotNull TrackerUpdateAction... actions) {\n        return switch (actions.length) {\n            case 0 -> none();\n            case 1 -> actions[0];\n            default -> new Composite(Arrays.stream(actions).flatMap(TrackerUpdateAction::stream).toList());\n        };\n    }\n\n    /**\n     * Creates an action that generates a specific action for each bone.\n     *\n     * @param builder the function to generate actions\n     * @return the per-bone action\n     * @since 1.15.2\n     */\n    static @NotNull PerBone perBone(@NotNull Function<RenderedBone, TrackerUpdateAction> builder) {\n        return new PerBone(builder);\n    }\n\n    /**\n     * Returns a no-op action.\n     *\n     * @return the none action\n     * @since 1.15.2\n     */\n    static @NotNull None none() {\n        return None.INSTANCE;\n    }\n\n    /**\n     * Applies the action to a bone if it matches the predicate.\n     *\n     * @param bone the target bone\n     * @param predicate the predicate to check against\n     * @return true if the bone was updated\n     * @since 1.15.2\n     */\n    @Override\n    boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate);\n\n    /**\n     * Chains this action with another action.\n     *\n     * @param action the next action\n     * @return the combined action\n     * @since 1.15.2\n     */\n    default @NotNull TrackerUpdateAction then(@NotNull TrackerUpdateAction action) {\n        return composite(this, action);\n    }\n\n    /**\n     * Returns a stream of actions (useful for flattening composites).\n     *\n     * @return the stream\n     * @since 1.15.2\n     */\n    default @NotNull Stream<TrackerUpdateAction> stream() {\n        return Stream.of(this);\n    }\n\n    /**\n     * Action to update brightness.\n     * @param block the block light level\n     * @param sky the skylight level\n     * @since 1.15.2\n     */\n    record Brightness(int block, int sky) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.applyAtDisplay(predicate, display -> display.brightness(block, sky));\n        }\n    }\n\n    /**\n     * Action to update glow status.\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor\n    enum Glow implements TrackerUpdateAction {\n        /**\n         * Enable glow.\n         * @since 1.15.2\n         */\n        TRUE(true),\n        /**\n         * Disable glow.\n         * @since 1.15.2\n         */\n        FALSE(false)\n        ;\n        private final boolean value;\n\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.applyAtDisplay(predicate, display -> display.glow(value));\n        }\n    }\n\n    /**\n     * Action to update glow color.\n     * @param glowColor the RGB glow color\n     * @since 1.15.2\n     */\n    record GlowColor(int glowColor) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.applyAtDisplay(predicate, display -> display.glowColor(glowColor));\n        }\n    }\n\n    /**\n     * Action to update view range.\n     * @param viewRange the view range\n     * @since 1.15.2\n     */\n    record ViewRange(float viewRange) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.applyAtDisplay(predicate, display -> display.viewRange(viewRange));\n        }\n    }\n\n    /**\n     * Action to update enchantment glint.\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor\n    enum Enchant implements TrackerUpdateAction {\n        /**\n         * Enable glint.\n         * @since 1.15.2\n         */\n        TRUE(true),\n        /**\n         * Disable glint.\n         * @since 1.15.2\n         */\n        FALSE(false)\n        ;\n        private final boolean value;\n\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.enchant(predicate, value);\n        }\n    }\n\n    /**\n     * Action to apply a tint color.\n     * @param rgb the RGB tint color\n     * @since 1.15.2\n     */\n    record Tint(int rgb) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.tint(predicate, rgb);\n        }\n    }\n\n    /**\n     * Action to revert to previous tint.\n     * @since 1.15.2\n     */\n    enum PreviousTint implements TrackerUpdateAction {\n        /**\n         * Instance.\n         * @since 1.15.2\n         */\n        INSTANCE\n        ;\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.tint(predicate);\n        }\n    }\n\n    /**\n     * Action to toggle part visibility.\n     * @since 1.15.2\n     */\n    @RequiredArgsConstructor\n    enum TogglePart implements TrackerUpdateAction {\n        /**\n         * Show part.\n         * @since 1.15.2\n         */\n        TRUE(true),\n        /**\n         * Hide part.\n         * @since 1.15.2\n         */\n        FALSE(false)\n        ;\n        private final boolean value;\n\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.applyAtDisplay(predicate, display -> display.invisible(!value));\n        }\n    }\n\n    /**\n     * Action to update the item stack.\n     * @param itemStack the new item stack\n     * @since 1.15.2\n     */\n    record ItemStack(@NotNull TransformedItemStack itemStack) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.itemStack(predicate, itemStack);\n        }\n    }\n\n    /**\n     * Action to update the billboard constraint.\n     * @param billboard the billboard type\n     * @since 1.15.2\n     */\n    record Billboard(@NotNull PlatformBillboard billboard) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.applyAtDisplay(predicate, display -> display.billboard(billboard));\n        }\n    }\n\n    /**\n     * Action to update item mapping.\n     * @since 1.15.2\n     */\n    enum ItemMapping implements TrackerUpdateAction {\n        /**\n         * Instance.\n         * @since 1.15.2\n         */\n        INSTANCE\n        ;\n\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.updateItem(predicate);\n        }\n    }\n\n    /**\n     * Action to update movement duration.\n     * @param moveDuration the duration in ticks\n     * @since 1.15.2\n     */\n    record MoveDuration(int moveDuration) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return bone.applyAtDisplay(predicate, display -> display.moveDuration(moveDuration));\n        }\n    }\n\n    /**\n     * Composite action.\n     * @param actions the list of actions\n     * @since 1.15.2\n     */\n    record Composite(@NotNull @Unmodifiable List<TrackerUpdateAction> actions) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            var result = false;\n            for (TrackerUpdateAction action : actions) {\n                if (action.test(bone, predicate)) result = true;\n            }\n            return result;\n        }\n\n        @Override\n        public @NotNull Stream<TrackerUpdateAction> stream() {\n            return actions.stream().flatMap(TrackerUpdateAction::stream);\n        }\n    }\n\n    /**\n     * Per-bone dynamic action.\n     * @param builder the function to generate actions\n     * @since 1.15.2\n     */\n    record PerBone(@NotNull Function<RenderedBone, TrackerUpdateAction> builder) implements TrackerUpdateAction {\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return builder.apply(bone).test(bone, predicate);\n        }\n    }\n\n    /**\n     * No-op action.\n     * @since 1.15.2\n     */\n    enum None implements TrackerUpdateAction {\n        /**\n         * Instance.\n         * @since 1.15.2\n         */\n        INSTANCE\n        ;\n\n        @Override\n        public boolean test(@NotNull RenderedBone bone, @NotNull BonePredicate predicate) {\n            return false;\n        }\n\n        @Override\n        public @NotNull Stream<TrackerUpdateAction> stream() {\n            return Stream.empty();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/CollectionUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport it.unimi.dsi.fastutil.floats.FloatCollection;\nimport it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;\nimport it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;\nimport kr.toxicity.model.api.BetterModel;\nimport net.kyori.adventure.text.format.NamedTextColor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Collection util\n */\n@ApiStatus.Internal\npublic final class CollectionUtil {\n    /**\n     * No initializer\n     */\n    private CollectionUtil() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * Creates a new addressing hash map.\n     * @return new addressing map\n     * @param <K> key type\n     * @param <V> value type\n     * @since 2.2.1\n     */\n    @NotNull\n    public static <K, V> Object2ObjectOpenHashMap<K, V> newAddressingMap() {\n        return new Object2ObjectOpenHashMap<>();\n    }\n\n    /**\n     * Creates a new sequenced chaining hash map.\n     * @return new linked hash map\n     * @param <K> key type\n     * @param <V> value type\n     * @since 2.2.1\n     */\n    @NotNull\n    public static <K, V> SequencedMap<K, V> newSequencedChainingMap() {\n        return new LinkedHashMap<>();\n    }\n\n    /**\n     * Creates a new sequenced addressing hash map.\n     * @return new sequenced addressing map\n     * @param <K> key type\n     * @param <V> value type\n     * @since 2.2.1\n     */\n    @NotNull\n    public static <K, V> Object2ObjectLinkedOpenHashMap<K, V> newSequencedAddressingMap() {\n        return new Object2ObjectLinkedOpenHashMap<>();\n    }\n\n    /**\n     * Creates a new chaining hash map.\n     * @return new hash map\n     * @param <K> key type\n     * @param <V> value type\n     * @param capacity the initial capacity\n     * @since 2.2.1\n     */\n    @NotNull\n    public static <K, V> Map<K, V> newChainingMap(int capacity) {\n        return new HashMap<>(capacity);\n    }\n\n    /**\n     * Creates a new addressing hash map.\n     * @return new addressing map\n     * @param <K> key type\n     * @param <V> value type\n     * @param capacity the initial capacity\n     * @since 2.2.1\n     */\n    @NotNull\n    public static <K, V> Object2ObjectOpenHashMap<K, V> newAddressingMap(int capacity) {\n        return new Object2ObjectOpenHashMap<>(capacity);\n    }\n\n    /**\n     * Creates a new sequenced chaining hash map.\n     * @return new linked hash map\n     * @param <K> key type\n     * @param <V> value type\n     * @param capacity the initial capacity\n     * @since 2.2.1\n     */\n    @NotNull\n    public static <K, V> SequencedMap<K, V> newSequencedChainingMap(int capacity) {\n        return new LinkedHashMap<>(capacity);\n    }\n\n    /**\n     * Creates a new sequenced addressing hash map.\n     * @return new sequenced addressing map\n     * @param <K> key type\n     * @param <V> value type\n     * @param capacity the initial capacity\n     * @since 2.2.1\n     */\n    @NotNull\n    public static <K, V> Object2ObjectLinkedOpenHashMap<K, V> newSequencedAddressingMap(int capacity) {\n        return new Object2ObjectLinkedOpenHashMap<>(capacity);\n    }\n\n    /**\n     * Filters stream by some instance\n     * @param collection collection\n     * @param rClass class of instance\n     * @return filtered stream\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    public static <E, R> Stream<R> filterIsInstance(@NotNull Collection<E> collection, @NotNull Class<R> rClass) {\n        return collection.isEmpty() ? Stream.empty() : filterIsInstance(collection.stream(), rClass);\n    }\n\n    /**\n     * Maps stream to list\n     * @param collection collection\n     * @param mapper mapper\n     * @return unmodifiable list\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, R> List<R> mapToList(@NotNull Collection<E> collection, @NotNull Function<E, R> mapper) {\n        return collection.isEmpty() ? Collections.emptyList() : mapToList(collection.stream(), mapper);\n    }\n\n    /**\n     * Maps stream to list\n     * @param stream stream\n     * @param mapper mapper\n     * @return unmodifiable list\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, R> List<R> mapToList(@NotNull Stream<E> stream, @NotNull Function<E, R> mapper) {\n        return stream.map(mapper).toList();\n    }\n\n    /**\n     * Maps stream to set\n     * @param collection collection\n     * @param mapper mapper\n     * @return unmodifiable set\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, R> Set<R> mapToSet(@NotNull Collection<E> collection, @NotNull Function<E, R> mapper) {\n        return collection.isEmpty() ? Collections.emptySet() : mapToSet(collection.stream(), mapper);\n    }\n\n    /**\n     * Maps stream to set\n     * @param stream stream\n     * @param mapper mapper\n     * @return unmodifiable set\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, R> Set<R> mapToSet(@NotNull Stream<E> stream, @NotNull Function<E, R> mapper) {\n        return stream.map(mapper).collect(Collectors.toUnmodifiableSet());\n    }\n\n    /**\n     * Maps stream to JSON\n     * @param collection collection\n     * @param mapper mapper\n     * @return JSON array\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    public static <E, R extends JsonElement> JsonArray mapToJson(@NotNull Collection<E> collection, @NotNull Function<E, R> mapper) {\n        return collection.isEmpty() ? new JsonArray() : mapToJson(collection.size(), collection.stream(), mapper);\n    }\n\n    /**\n     * Maps stream to JSON\n     * @param stream stream\n     * @param mapper mapper\n     * @return JSON array\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    public static <E, R extends JsonElement> JsonArray mapToJson(@NotNull Stream<E> stream, @NotNull Function<E, R> mapper) {\n        return mapToJson(10, stream, mapper);\n    }\n\n    /**\n     * Maps stream to JSON\n     * @param capacity initial capacity\n     * @param stream stream\n     * @param mapper mapper\n     * @return JSON array\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    public static <E, R extends JsonElement> JsonArray mapToJson(int capacity, @NotNull Stream<E> stream, @NotNull Function<E, R> mapper) {\n        var array = new JsonArray(capacity);\n        stream.map(mapper).forEach(array::add);\n        return array;\n    }\n\n    /**\n     * Filters stream by some instance\n     * @param stream stream\n     * @param rClass class of instance\n     * @return filtered stream\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    public static <E, R> Stream<R> filterIsInstance(@NotNull Stream<E> stream, @NotNull Class<R> rClass) {\n        return stream.filter(rClass::isInstance)\n            .map(rClass::cast);\n    }\n\n    /**\n     * Groups stream by some key\n     * @param stream stream\n     * @param keyMapper key mapper\n     * @return unmodifiable grouped map\n     * @param <K> key\n     * @param <E> element\n     */\n    @NotNull\n    @Unmodifiable\n    public static <K, E> Map<K, List<E>> group(@NotNull Stream<E> stream, @NotNull Function<E, K> keyMapper) {\n        return Collections.unmodifiableMap(stream.collect(Collectors.groupingBy(keyMapper)));\n    }\n\n    /**\n     * Maps some collection with map index\n     * @param map map\n     * @param function mapper\n     * @return mapped stream\n     * @param <K> key\n     * @param <V> value\n     * @param <R> return value\n     */\n    @NotNull\n    public static <K, V, R> Stream<R> mapIndexed(@NotNull Map<K, V> map, @NotNull IndexedFunction<Map.Entry<K, V>, R> function) {\n        return mapIndexed(map.entrySet(), function);\n    }\n\n    /**\n     * Maps some collection with map index\n     * @param collection collection\n     * @param function mapper\n     * @return mapped stream\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    public static <E, R> Stream<R> mapIndexed(@NotNull Collection<E> collection, @NotNull IndexedFunction<E, R> function) {\n        return mapIndexed(collection.stream(), function);\n    }\n\n    /**\n     * Maps some collection with map index\n     * @param stream stream\n     * @param function mapper\n     * @return mapped stream\n     * @param <E> element\n     * @param <R> return value\n     */\n    @NotNull\n    public static <E, R> Stream<R> mapIndexed(@NotNull Stream<E> stream, @NotNull IndexedFunction<E, R> function) {\n        var integer = new AtomicInteger();\n        return stream.map(e -> function.apply(integer.getAndIncrement(), e));\n    }\n\n    /**\n     * Maps some stream to a float collection\n     * @param stream stream\n     * @param mapper mapper\n     * @param creator float collection creator\n     * @return float collection\n     * @param <E> element\n     * @param <T> type\n     */\n    @NotNull\n    public static <E, T extends FloatCollection> T mapFloat(@NotNull Stream<E> stream, @NotNull FloatFunction<E> mapper, @NotNull Supplier<T> creator) {\n        var collect = creator.get();\n        stream.forEach(e -> collect.add(mapper.apply(e)));\n        return collect;\n    }\n\n    /**\n     * Map some map's value.\n     * @param original original map\n     * @param mapper value mapper\n     * @return unmodifiable map\n     * @param <K> key\n     * @param <V> value\n     * @param <R> new value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <K, V, R> Map<K, R> mapValue(@NotNull Map<K, V> original, @NotNull Function<V, R> mapper) {\n        return associate(original.entrySet(), Map.Entry::getKey, e -> mapper.apply(e.getValue()));\n    }\n\n    /**\n     * Gets filter with warning if not matched\n     * @param predicate delegated predicate\n     * @param lazyLogFunction log function\n     * @return predicate\n     * @param <T> type\n     */\n    @NotNull\n    public static <T> Predicate<T> filterWithWarning(@NotNull Predicate<T> predicate, @NotNull Function<T, String> lazyLogFunction) {\n        var logger = BetterModel.platform().logger();\n        return t -> {\n            var testedValue = predicate.test(t);\n            if (!testedValue) logger.warn(LogUtil.toLog(lazyLogFunction.apply(t), NamedTextColor.YELLOW));\n            return testedValue;\n        };\n    }\n\n    /**\n     * Associates collection to map\n     * @param collection collection\n     * @param keyMapper key mapper\n     * @param valueMapper value mapper\n     * @return unmodifiable map\n     * @param <E> element\n     * @param <K> key\n     * @param <V> value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K, V> Map<K, V> associate(@NotNull Collection<E> collection, @NotNull Function<E, K> keyMapper, @NotNull Function<E, V> valueMapper) {\n        return collection.isEmpty() ? Collections.emptyMap() : associate(collection.stream(), keyMapper, valueMapper);\n    }\n\n    /**\n     * Associates stream to map\n     * @param collection collection\n     * @param keyMapper key mapper\n     * @return unmodifiable map\n     * @param <E> element\n     * @param <K> key\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K> Map<K, E> associate(@NotNull Collection<E> collection, @NotNull Function<E, K> keyMapper) {\n        return collection.isEmpty() ? Collections.emptyMap() : associate(collection.stream(), keyMapper);\n    }\n\n    /**\n     * Associates stream to map\n     * @param array array\n     * @param keyMapper key mapper\n     * @return unmodifiable map\n     * @param <E> element\n     * @param <K> key\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K> Map<K, E> associate(@NotNull E[] array, @NotNull Function<E, K> keyMapper) {\n        return array.length == 0 ? Collections.emptyMap() : associate(Arrays.stream(array), keyMapper);\n    }\n\n    /**\n     * Associates stream to map\n     * @param stream stream\n     * @param keyMapper key mapper\n     * @return unmodifiable map\n     * @param <E> element\n     * @param <K> key\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K> Map<K, E> associate(@NotNull Stream<E> stream, @NotNull Function<E, K> keyMapper) {\n        return associate(stream, keyMapper, e -> e);\n    }\n\n    /**\n     * Associates stream to map\n     * @param stream stream\n     * @param keyMapper key mapper\n     * @param valueMapper value mapper\n     * @return unmodifiable map\n     * @param <E> element\n     * @param <K> key\n     * @param <V> value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K, V> Map<K, V> associate(@NotNull Stream<E> stream, @NotNull Function<E, K> keyMapper, @NotNull Function<E, V> valueMapper) {\n        return stream.collect(Collectors.toUnmodifiableMap(keyMapper, valueMapper));\n    }\n\n    /**\n     * Associates stream to sequenced map\n     * @param collection collection\n     * @param keyMapper key mapper\n     * @return unmodifiable sequenced map\n     * @param <E> element\n     * @param <K> key\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K> SequencedMap<K, E> associateSequenced(@NotNull Collection<E> collection, @NotNull Function<E, K> keyMapper) {\n        return associateSequenced(collection, keyMapper, v -> v);\n    }\n\n    /**\n     * Associates collection to sequenced map\n     * @param collection collection\n     * @param keyMapper key mapper\n     * @param valueMapper value mapper\n     * @return unmodifiable sequenced map\n     * @param <E> element\n     * @param <K> key\n     * @param <V> value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K, V> SequencedMap<K, V> associateSequenced(@NotNull Collection<E> collection, @NotNull Function<E, K> keyMapper, @NotNull Function<E, V> valueMapper) {\n        return collection.isEmpty() ? Collections.emptyNavigableMap() : associateSequenced(collection.size(), collection.stream(), keyMapper, valueMapper);\n    }\n\n    /**\n     * Associates stream to sequenced map\n     * @param array array\n     * @param keyMapper key mapper\n     * @return unmodifiable sequenced map\n     * @param <E> element\n     * @param <K> key\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K> SequencedMap<K, E> associateSequenced(@NotNull E[] array, @NotNull Function<E, K> keyMapper) {\n        return associateSequenced(array, keyMapper, v -> v);\n    }\n\n    /**\n     * Associates collection to sequenced map\n     * @param array array\n     * @param keyMapper key mapper\n     * @param valueMapper value mapper\n     * @return unmodifiable sequenced map\n     * @param <E> element\n     * @param <K> key\n     * @param <V> value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K, V> SequencedMap<K, V> associateSequenced(@NotNull E[] array, @NotNull Function<E, K> keyMapper, @NotNull Function<E, V> valueMapper) {\n        var len = array.length;\n        return len == 0 ? Collections.emptyNavigableMap() : associateSequenced(len, Arrays.stream(array), keyMapper, valueMapper);\n    }\n\n    /**\n     * Associates stream to sequenced map\n     * @param capacity the initial capacity\n     * @param stream stream\n     * @param keyMapper key mapper\n     * @return unmodifiable sequenced map\n     * @param <E> element\n     * @param <K> key\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K> SequencedMap<K, E> associateSequenced(int capacity, @NotNull Stream<E> stream, @NotNull Function<E, K> keyMapper) {\n        return associateSequenced(capacity, stream, keyMapper, e -> e);\n    }\n\n    /**\n     * Associates stream to sequenced map\n     * @param capacity the initial capacity\n     * @param stream stream\n     * @param keyMapper key mapper\n     * @param valueMapper value mapper\n     * @return unmodifiable sequenced map\n     * @param <E> element\n     * @param <K> key\n     * @param <V> value\n     */\n    @NotNull\n    @Unmodifiable\n    public static <E, K, V> SequencedMap<K, V> associateSequenced(int capacity, @NotNull Stream<E> stream, @NotNull Function<E, K> keyMapper, @NotNull Function<E, V> valueMapper) {\n        return stream.collect(Collectors.collectingAndThen(\n            Collectors.toMap(\n                keyMapper,\n                valueMapper,\n                (oldV, newV) -> { throw new IllegalStateException(\"Duplicate key: \" + oldV + \" and \" + newV); },\n                () -> newSequencedAddressingMap(capacity)\n            ),\n            Collections::unmodifiableSequencedMap\n        ));\n    }\n\n    /**\n     * Function with index\n     * @param <T> type\n     * @param <R> return value\n     */\n    @FunctionalInterface\n    public interface IndexedFunction<T, R> {\n        /**\n         * Maps to new value\n         * @param index index\n         * @param t input\n         * @return mapped value\n         */\n        R apply(int index, T t);\n    }\n\n    /**\n     * Function of float\n     * @param <T> type\n     */\n    @FunctionalInterface\n    public interface FloatFunction<T> {\n        /**\n         * Maps to new value\n         * @param t input\n         * @return mapped float\n         */\n        float apply(T t);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/EntityUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.platform.PlatformLocation;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport static java.lang.Math.*;\n\n/**\n * Utility class for entity-related calculations, primarily visibility checks.\n * <p>\n * This class provides methods to determine if an entity is within a player's field of view\n * or render distance, optimizing client-side rendering performance.\n * </p>\n *\n * @since 2.0.0\n */\n@ApiStatus.Internal\npublic final class EntityUtil {\n\n    /**\n     * Private initializer to prevent instantiation.\n     */\n    private EntityUtil() {\n        throw new RuntimeException();\n    }\n\n\n    /**\n     * Y-axis threshold of user screen.\n     */\n    private static final double Y_RENDER_THRESHOLD = toRadians(45);\n    /**\n     * In point threshold of user screen.\n     */\n    private static final double IN_POINT_THRESHOLD = toRadians(10);\n    /**\n     * X-axis threshold of user screen.\n     */\n    private static final double X_RENDER_THRESHOLD = Y_RENDER_THRESHOLD * 1.78;\n\n    /**\n     * Calculates the render distance in blocks based on the server's view distance.\n     *\n     * @return the render distance\n     * @since 2.0.0\n     */\n    public static double renderDistance() {\n        return BetterModel.platform().adapter().serverViewDistance() << 3;\n    }\n\n    /**\n     * Calculates the entity model view radius based on the server's view distance.\n     *\n     * @return the view radius\n     * @since 2.0.0\n     */\n    public static float entityModelViewRadius() {\n        return (float) BetterModel.platform().adapter().serverViewDistance() / 4;\n    }\n\n    /**\n     * Checks if a player can see a target entity based on sight tracing configuration.\n     *\n     * @param player the player's location\n     * @param target the target entity's location\n     * @return true if the target is visible, false otherwise\n     * @since 2.0.0\n     */\n    public static boolean canSee(@NotNull PlatformLocation player, @NotNull PlatformLocation target) {\n        var manager = BetterModel.config();\n        if (!manager.sightTrace()) return true;\n        else if (!player.world().equals(target.world())) return false;\n\n        var d = player.distance(target);\n        if (d > manager.maxSight()) return false;\n        else if (d <= manager.minSight()) return true;\n\n        var t = fma(-abs(atan(d)), 2, PI);\n        var ty = t + Y_RENDER_THRESHOLD;\n        var tz = t + X_RENDER_THRESHOLD;\n        return isInDegree(player, target, ty, tz);\n    }\n\n    /**\n     * Checks if a target entity's custom name is visible to a player.\n     *\n     * @param player the player's location\n     * @param target the target entity's location\n     * @return true if the custom name is visible, false otherwise\n     * @since 2.0.0\n     */\n    public static boolean isCustomNameVisible(@NotNull PlatformLocation player, @NotNull PlatformLocation target) {\n        if (!player.world().equals(target.world())) return false;\n        if (player.distanceSquared(target) > 25) return false; // 5 ^ 2\n        return isInPoint(player, target);\n    }\n\n    /**\n     * Checks if a target entity is directly in the player's crosshair (point of view).\n     *\n     * @param player the player's location\n     * @param target the target entity's location\n     * @return true if the target is in the player's point of view\n     * @since 2.0.0\n     */\n    public static boolean isInPoint(@NotNull PlatformLocation player, @NotNull PlatformLocation target) {\n        return isInDegree(player, target, IN_POINT_THRESHOLD, IN_POINT_THRESHOLD);\n    }\n\n    private static boolean isInDegree(@NotNull PlatformLocation player, @NotNull PlatformLocation target, double ty, double tz) {\n        var playerYaw = toRadians(player.yaw());\n        var playerPitch = -toRadians(player.pitch());\n\n        var dz = target.z() - player.z();\n        var dy = target.y() - player.y();\n        var dx = target.x() - player.x();\n\n        var ry = abs(atan2(dy, sqrt(MathUtil.fma(dz, dz, dx * dx))) - playerPitch);\n        var rz = abs(atan2(-dx, dz) - playerYaw);\n        return (ry <= ty || ry >= TAU - ty) && (rz <= tz || rz >= TAU - tz);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/EventUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.BetterModelEventBus;\nimport kr.toxicity.model.api.event.ModelEvent;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Supplier;\n\n/**\n * Utility class for dispatching model events.\n * <p>\n * This class provides helper methods to call events on the {@link BetterModelEventBus}\n * and handle cancellable events conveniently.\n * </p>\n *\n * @since 2.0.0\n */\n@ApiStatus.Internal\npublic final class EventUtil {\n    /**\n     * Private initializer to prevent instantiation.\n     */\n    private EventUtil() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * Calls an event on the global event bus.\n     *\n     * @param eventClass the class of the event\n     * @param eventSupplier a supplier that creates the event\n     * @param <T> the type of the event\n     * @return the result of the event call\n     * @since 2.0.0\n     */\n    @NotNull\n    public static <T extends ModelEvent> BetterModelEventBus.Result call(@NotNull Class<T> eventClass, @NotNull Supplier<T> eventSupplier) {\n        return BetterModel.eventBus().call(eventClass, eventSupplier);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/FunctionUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport kr.toxicity.model.api.util.function.BooleanConstantSupplier;\nimport kr.toxicity.model.api.util.function.FloatConstantSupplier;\nimport kr.toxicity.model.api.util.function.FloatSupplier;\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.function.BooleanSupplier;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\n\n/**\n * Function util\n */\n@ApiStatus.Internal\npublic final class FunctionUtil {\n    /**\n     * No initializer\n     */\n    private FunctionUtil() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * Makes constant value as supplier.\n     * @param t value\n     * @return supplier that returns always the same object.\n     * @param <T> supplier\n     */\n    public static <T> @NotNull Supplier<T> asSupplier(@NotNull T t) {\n        return () -> t;\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param <T> type\n     * @param supplier target\n     * @return throttled function\n     */\n    public static <T> @NotNull Supplier<T> throttleTick(@NotNull Supplier<T> supplier) {\n        return throttleTick(MathUtil.MINECRAFT_TICK_MILLS, supplier);\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param <T> type\n     * @param tick tick\n     * @param supplier target\n     * @return throttled function\n     */\n    public static <T> @NotNull Supplier<T> throttleTick(long tick, @NotNull Supplier<T> supplier) {\n        return supplier instanceof TickThrottledSupplier<T> throttledSupplier ? new TickThrottledSupplier<>(tick, throttledSupplier.delegate) : new TickThrottledSupplier<>(tick, supplier);\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param supplier target\n     * @return throttled function\n     */\n    public static @NotNull FloatSupplier throttleTickFloat(@NotNull FloatSupplier supplier) {\n        return throttleTickFloat(MathUtil.MINECRAFT_TICK_MILLS, supplier);\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param tick tick\n     * @param supplier target\n     * @return throttled function\n     */\n    public static @NotNull FloatSupplier throttleTickFloat(long tick, @NotNull FloatSupplier supplier) {\n        return switch (supplier) {\n            case TickThrottledFloatSupplier throttledSupplier -> new TickThrottledFloatSupplier(tick, throttledSupplier.delegate);\n            case FloatConstantSupplier constantSupplier -> constantSupplier;\n            default -> new TickThrottledFloatSupplier(tick, supplier);\n        };\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param supplier target\n     * @return throttled function\n     */\n    public static @NotNull BooleanSupplier throttleTickBoolean(@NotNull BooleanSupplier supplier) {\n        return switch (supplier) {\n            case TickThrottledBooleanSupplier throttledSupplier -> throttledSupplier;\n            case BooleanConstantSupplier booleanConstantSupplier -> booleanConstantSupplier;\n            default -> new TickThrottledBooleanSupplier(supplier);\n        };\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param <T> type\n     * @param predicate target\n     * @return throttled function\n     */\n    public static <T> @NotNull Predicate<T> throttleTick(@NotNull Predicate<T> predicate) {\n        return predicate instanceof TickThrottledPredicate<T> throttledSupplier ? throttledSupplier : new TickThrottledPredicate<>(predicate);\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param <T> from\n     * @param <R> return\n     * @param function target\n     * @return throttled function\n     */\n    public static <T, R> @NotNull Function<T, R> throttleTick(@NotNull Function<T, R> function) {\n        return throttleTick(MathUtil.MINECRAFT_TICK_MILLS, function);\n    }\n\n    /**\n     * Throttles this function by tick\n     * @param <T> from\n     * @param <R> return\n     * @param tick tick\n     * @param function target\n     * @return throttled function\n     */\n    public static <T, R> @NotNull Function<T, R> throttleTick(long tick, @NotNull Function<T, R> function) {\n        return function instanceof TickThrottledFunction<T, R> throttledFunction ? new TickThrottledFunction<>(tick, throttledFunction.delegate) : new TickThrottledFunction<>(tick, function);\n    }\n\n    private static class TickThrottledSupplier<T> implements Supplier<T> {\n        private final long tick;\n        private final Supplier<T> delegate;\n        private final AtomicLong time;\n        private volatile T cache;\n\n        public TickThrottledSupplier(long tick, @NotNull Supplier<T> delegate) {\n            this.tick = tick;\n            this.delegate = delegate;\n            time = new AtomicLong(-tick - 1);\n        }\n\n        @Override\n        public T get() {\n            var old = time.get();\n            var current = System.currentTimeMillis();\n            if (current - old >= tick && time.compareAndSet(old, current)) cache = delegate.get();\n            return cache;\n        }\n    }\n\n    @RequiredArgsConstructor\n    private static class TickThrottledFloatSupplier implements FloatSupplier {\n        private final long tick;\n        private final FloatSupplier delegate;\n        private final AtomicLong time;\n        private volatile float cache;\n\n        public TickThrottledFloatSupplier(long tick, @NotNull FloatSupplier delegate) {\n            this.tick = tick;\n            this.delegate = delegate;\n            time = new AtomicLong(-tick - 1);\n        }\n\n        @Override\n        public float getAsFloat() {\n            var old = time.get();\n            var current = System.currentTimeMillis();\n            if (current - old >= tick && time.compareAndSet(old, current)) cache = delegate.getAsFloat();\n            return cache;\n        }\n    }\n\n    @RequiredArgsConstructor\n    private static class TickThrottledBooleanSupplier implements BooleanSupplier {\n        private final @NotNull BooleanSupplier delegate;\n        private final AtomicLong time = new AtomicLong(-51);\n        private volatile boolean cache;\n\n        @Override\n        public boolean getAsBoolean() {\n            var old = time.get();\n            var current = System.currentTimeMillis();\n            if (current - old >= 50 && time.compareAndSet(old, current)) cache = delegate.getAsBoolean();\n            return cache;\n        }\n    }\n\n    @RequiredArgsConstructor\n    private static class TickThrottledPredicate<T> implements Predicate<T> {\n        private final @NotNull Predicate<T> delegate;\n        private final AtomicLong time = new AtomicLong(-51);\n        private volatile boolean cache;\n\n        @Override\n        public boolean test(T t) {\n            var old = time.get();\n            var current = System.currentTimeMillis();\n            if (current - old >= 50 && time.compareAndSet(old, current)) cache = delegate.test(t);\n            return cache;\n        }\n    }\n\n    @RequiredArgsConstructor\n    private static class TickThrottledFunction<T, R> implements Function<T, R> {\n        private final long tick;\n        private final @NotNull Function<T, R> delegate;\n        private final AtomicLong time;\n        private volatile R cache;\n\n        public TickThrottledFunction(long tick, @NotNull Function<T, R> delegate) {\n            this.tick = tick;\n            this.delegate = delegate;\n            time = new AtomicLong(-tick - 1);\n        }\n\n        @Override\n        public R apply(T t) {\n            var old = time.get();\n            var current = System.currentTimeMillis();\n            if (current - old >= tick && time.compareAndSet(old, current)) cache = delegate.apply(t);\n            return cache;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/HttpUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonParser;\nimport com.google.gson.annotations.SerializedName;\nimport com.google.gson.stream.JsonReader;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.version.MinecraftVersion;\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.text.event.ClickEvent;\nimport net.kyori.adventure.text.event.HoverEvent;\nimport net.kyori.adventure.text.format.NamedTextColor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.semver4j.Semver;\n\nimport java.io.InputStreamReader;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.Executors;\nimport java.util.function.Function;\n\n/**\n * Http util\n */\n@ApiStatus.Internal\npublic final class HttpUtil {\n\n    private static final HttpClient CLIENT = HttpClient.newBuilder()\n        .connectTimeout(Duration.of(5, ChronoUnit.SECONDS))\n        .executor(Executors.newVirtualThreadPerTaskExecutor())\n        .build();\n    private static final Gson GSON = new GsonBuilder()\n        .registerTypeAdapter(MinecraftVersion.class, (JsonDeserializer<MinecraftVersion>) (json, _, _) -> MinecraftVersion.parse(json.getAsString()))\n        .registerTypeAdapter(Semver.class, (JsonDeserializer<Semver>) (json, _, _) -> Semver.coerce(json.getAsString()))\n        .create();\n\n    /**\n     * No initializer\n     */\n    private HttpUtil() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * Searches BetterModel's latest version\n     * @return latest version\n     */\n    public static @NotNull LatestVersion versionList() {\n        return versionList(BetterModel.platform().version());\n    }\n\n    /**\n     * Searches BetterModel's latest version compatible with current server\n     * @param version server version\n     * @return latest version\n     */\n    public static @NotNull LatestVersion versionList(@NotNull MinecraftVersion version) {\n        return client(client -> {\n            try (var stream = client.send(HttpRequest.newBuilder()\n                .GET()\n                .uri(URI.create(\"https://api.modrinth.com/v2/project/bettermodel/version\"))\n                .build(), HttpResponse.BodyHandlers.ofInputStream()).body();\n                 var reader = new InputStreamReader(stream);\n                 var jsonReader = new JsonReader(reader)\n            ) {\n                return latestOf(JsonParser.parseReader(jsonReader)\n                    .getAsJsonArray()\n                    .asList()\n                    .stream()\n                    .map(e -> GSON.fromJson(e, PluginVersion.class))\n                    .filter(PluginVersion::isSamePlatform)\n                    .filter(v -> v.versions.contains(version))\n                    .sorted(Comparator.comparing((PluginVersion v) -> v.versionNumber).reversed())\n                    .toList());\n            }\n        }).orElse(e -> {\n            LogUtil.handleException(\"Unable to get BetterModel's version info.\", e);\n            return new LatestVersion(null, null);\n        });\n    }\n\n    /**\n     * Gets the latest version from a version list\n     * @param versions versions\n     * @return latest version\n     */\n    public static @NotNull LatestVersion latestOf(@NotNull List<PluginVersion> versions) {\n        PluginVersion release = null, snapshot = null;\n        for (PluginVersion version : versions) {\n            if (version.versionType.equals(\"release\")) {\n                if (release == null) release = version;\n            } else if (snapshot == null) snapshot = version;\n        }\n        return new LatestVersion(release, snapshot);\n    }\n\n    /**\n     * Latest version\n     * @param release release\n     * @param snapshot snapshot\n     */\n    public record LatestVersion(@Nullable PluginVersion release, @Nullable PluginVersion snapshot) {\n    }\n\n    /**\n     * Plugin version\n     * @param id id\n     * @param versionNumber number\n     * @param versionType type\n     * @param versions game versions\n     * @param loaders loaders\n     */\n    public record PluginVersion(\n        @NotNull String id,\n        @NotNull @SerializedName(\"version_number\") Semver versionNumber,\n        @NotNull @SerializedName(\"version_type\") String versionType,\n        @NotNull @SerializedName(\"game_versions\") Set<MinecraftVersion> versions,\n        @NotNull Set<String> loaders\n    ) {\n        /**\n         * Creates a text component with URL\n         * @return text component\n         */\n        public @NotNull Component toURLComponent() {\n            var url = \"https://modrinth.com/plugin/bettermodel/version/\" + id;\n            return Component.text()\n                .content(versionNumber.getVersion())\n                .color(NamedTextColor.AQUA)\n                .hoverEvent(\n                    HoverEvent.showText(Component.text()\n                        .append(Component.text(url).color(NamedTextColor.DARK_AQUA))\n                        .appendNewline()\n                        .append(Component.text(\"Click to open link.\")))\n                )\n                .clickEvent(ClickEvent.openUrl(url))\n                .build();\n        }\n\n        /**\n         * Checks this version is same platform with running platform\n         * @return is same platform\n         */\n        public boolean isSamePlatform() {\n            return loaders.contains(BetterModel.platform().jarType().raw());\n        }\n    }\n\n    /**\n     * Uses http client\n     * @param consumer consumer\n     * @return result\n     * @param <T> type\n     */\n    public static <T> @NotNull Result<T> client(@NotNull HttpClientConsumer<T> consumer) {\n        try {\n            return new Result.Success<>(consumer.accept(CLIENT));\n        } catch (Exception e) {\n            return new Result.Failure<>(e);\n        }\n    }\n\n    /**\n     * http result\n     * @param <T> type\n     */\n    public sealed interface Result<T> {\n\n        /**\n         * Gets the value or handle exception\n         * @param function exception handler\n         * @return value\n         */\n        default @NotNull T orElse(@NotNull Function<Exception, T> function) {\n            return switch (this) {\n                case Failure<T> failure -> function.apply(failure.exception);\n                case Success<T> success -> success.result;\n            };\n        }\n\n        /**\n         * Success\n         * @param result result value\n         * @param <T> type\n         */\n        record Success<T>(@NotNull T result) implements Result<T> {}\n\n        /**\n         * Failure\n         * @param exception exception\n         * @param <T> type\n         */\n        record Failure<T>(@NotNull Exception exception) implements Result<T> {}\n    }\n\n    /**\n     * Http client consumer\n     * @param <T> type\n     */\n    @FunctionalInterface\n    public interface HttpClientConsumer<T> {\n        /**\n         * Accepts a task with an http client\n         * @param client client\n         * @return value\n         * @throws Exception exception\n         */\n        @NotNull T accept(@NotNull HttpClient client) throws Exception;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/InterpolationUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport it.unimi.dsi.fastutil.floats.FloatArrayList;\nimport it.unimi.dsi.fastutil.floats.FloatSortedSet;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.animation.AnimationKeyframe;\nimport kr.toxicity.model.api.animation.VectorPoint;\nimport kr.toxicity.model.api.tracker.Tracker;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\nimport java.util.List;\n\nimport static kr.toxicity.model.api.util.MathUtil.*;\n\n/**\n * Interpolation util\n */\n@ApiStatus.Internal\npublic final class InterpolationUtil {\n\n    /**\n     * No initializer\n     */\n    private InterpolationUtil() {\n        throw new RuntimeException();\n    }\n\n    private static final float FRAME_HASH = (float) Tracker.TRACKER_TICK_INTERVAL / 1000F;\n    private static final float FRAME_HASH_REVERT = 1 / FRAME_HASH;\n\n    /**\n     * Builds animation keyframe\n     * @param position position point\n     * @param rotation rotation point\n     * @param scale scale point\n     * @param rotationGlobal rotation global\n     * @param points keyframe time set\n     * @return animation keyframe\n     */\n    @NotNull\n    public static AnimationKeyframe buildAnimation(\n        @NotNull List<VectorPoint> position,\n        @NotNull List<VectorPoint> rotation,\n        @NotNull List<VectorPoint> scale,\n        boolean rotationGlobal,\n        @NotNull FloatSortedSet points\n    ) {\n        var pp = interpolatorFor(position);\n        var sp = interpolatorFor(scale);\n        var rp = interpolatorFor(rotation);\n        var keyframe = AnimationKeyframe.builder(points.size(), rotationGlobal);\n        var before = 0F;\n        var iterator = points.iterator();\n        while (iterator.hasNext()) {\n            var f = iterator.nextFloat();\n            var pr = pp.build(f);\n            var sr = sp.build(f);\n            var rr = rp.build(f);\n            keyframe.write(\n                roundTime(f - before),\n                pr.vector,\n                sr.vector,\n                rr.vector,\n                pr.skipInterpolation || sr.skipInterpolation || rr.skipInterpolation\n            );\n            before = f;\n        }\n        return keyframe.build();\n    }\n\n    /**\n     * Creates interpolator\n     * @param vectors target vector list\n     * @return point builder\n     */\n    public static @NotNull VectorPointBuilder interpolatorFor(@NotNull List<VectorPoint> vectors) {\n        var last = vectors.isEmpty() ? VectorPoint.EMPTY : vectors.getLast();\n        return vectors.size() < 2 ? f -> new VectorResult(last.vector(f)) : new VectorPointBuilder() {\n            private VectorPoint p1 = VectorPoint.EMPTY;\n            private VectorPoint p2 = vectors.getFirst();\n            private int i = 0;\n            private float t = p2.time();\n\n            @Override\n            public @NotNull VectorResult build(float nextFloat) {\n                while (i < vectors.size() - 1 && t < nextFloat) {\n                    p1 = p2;\n                    t = (p2 = vectors.get(++i)).time();\n                }\n                if (nextFloat > last.time()) return new VectorResult(last.vector(nextFloat));\n                else return nextFloat == t ? new VectorResult(p2.vector(), !p1.isContinuous()) : new VectorResult(p1.interpolator().interpolate(vectors, i, nextFloat));\n            }\n        };\n    }\n\n    /**\n     * Rounds float to frame time\n     * @param time time\n     * @return rounded time\n     */\n    public static float roundTime(float time) {\n        return (int) fma(time, FRAME_HASH_REVERT, FRAME_EPSILON) * FRAME_HASH;\n    }\n\n    /**\n     * Inserts lerp frame to given set\n     * @param frames target set\n     */\n    public static void insertLerpFrame(@NotNull FloatSortedSet frames) {\n        insertLerpFrame(frames, (float) BetterModel.config().lerpFrameTime() / 20F);\n    }\n\n    /**\n     * Inserts lerp frame to given set\n     * @param frames target set\n     * @param frame frame\n     */\n    public static void insertLerpFrame(@NotNull FloatSortedSet frames, float frame) {\n        if (frame <= 0F) return;\n        var first = 0F;\n        var second = 0F;\n        var iterator = new FloatArrayList(frames).iterator();\n        while (iterator.hasNext()) {\n            first = second;\n            second = iterator.nextFloat();\n            var max = (int) ((second - first) / frame);\n            for (int i = 0; i < max; i++) {\n                var add = fma(frame, i + 1, first);\n                if (second - add < frame + FRAME_EPSILON) continue;\n                frames.add(add);\n            }\n        }\n    }\n\n    /**\n     * Finds alpha value\n     * @param p0 p0\n     * @param p1 p1\n     * @param alpha target value between p0 and p1\n     * @return alpha (0..1)\n     */\n    public static float alpha(float p0, float p1, float alpha) {\n        var div = p1 - p0;\n        return div == 0 ? 0 : (alpha - p0) / div;\n    }\n\n    /**\n     * Lerps two point\n     * @param p0 p0\n     * @param p1 p1\n     * @param alpha alpha\n     * @return lerped vector\n     */\n    public static @NotNull Vector3f lerp(@NotNull Vector3f p0, @NotNull Vector3f p1, float alpha) {\n        return lerp(p0, p1, alpha, new Vector3f());\n    }\n\n    /**\n     * Lerps two point\n     * @param p0 p0\n     * @param p1 p1\n     * @param alpha alpha\n     * @param dest destination vector\n     * @return lerped vector\n     */\n    public static @NotNull Vector3f lerp(@NotNull Vector3f p0, @NotNull Vector3f p1, float alpha, @NotNull Vector3f dest) {\n        dest.x = lerp(p0.x, p1.x, alpha);\n        dest.y = lerp(p0.y, p1.y, alpha);\n        dest.z = lerp(p0.z, p1.z, alpha);\n        return dest;\n    }\n\n    /**\n     * Lerps two point\n     * @param p0 p0\n     * @param p1 p1\n     * @param alpha alpha\n     * @return lerped point\n     */\n    public static float lerp(float p0, float p1, float alpha) {\n        return fma(p1 - p0, alpha, p0);\n    }\n\n    private static float cubicBezier(float p0, float p1, float p2, float p3, float t) {\n        var u = 1.0F - t;\n        var uu = u * u;\n        var tt = t * t;\n        var uuu = uu * u;\n        var utt = u * tt;\n        var uut = uu * t;\n        var ttt = tt * t;\n        return fma(uuu, p0, fma(3.0F * uut, p1, fma(3.0F * utt, p2, ttt * p3)));\n    }\n\n    private static float derivativeBezier(float p1, float p2, float t) {\n        var u = 1.0F - t;\n        var uu = u * u;\n        var ut = u * t;\n        var tt = t * t;\n        return fma(3.0F * uu, p1, fma(6.0F * ut, p2 - p1, 3.0F * tt * (1 - p2)));\n    }\n\n    private static float solveBezierTForTime(float time, float h1, float h2) {\n        var t = 0.5F;\n        var maxIterations = 20;\n        for (int i = 0; i < maxIterations; i++) {\n            var bezTime = cubicBezier(0, h1, h2, 1, t);\n            var derivative = derivativeBezier(h1, h2, t);\n            var error = bezTime - time;\n            if (Math.abs(error) < FLOAT_COMPARISON_EPSILON) {\n                return t;\n            }\n            if (derivative != 0) {\n                t -= error / derivative;\n            }\n            t = Math.clamp(t, 0F, 1F);\n        }\n\n        return t;\n    }\n\n    /**\n     * Interpolates two vectors as bezier\n     * @param alpha alpha time\n     * @param start start keyframe\n     * @param end end keyframe\n     * @param bezierRightTime bezier right time\n     * @param bezierRightValue bezier right value\n     * @param bezierLeftTime bezier left time\n     * @param bezierLeftValue bezier left value\n     * @return interpolated vector\n     */\n    public static @NotNull Vector3f bezier(\n        float alpha,\n        @NotNull Vector3f start,\n        @NotNull Vector3f end,\n        @NotNull Vector3f bezierRightTime,\n        @NotNull Vector3f bezierRightValue,\n        @NotNull Vector3f bezierLeftTime,\n        @NotNull Vector3f bezierLeftValue\n    ) {\n        return new Vector3f(\n            cubicBezier(start.x, start.x + bezierRightValue.x, end.x + bezierLeftValue.x, end.x, solveBezierTForTime(\n                alpha,\n                bezierRightTime.x,\n                1 + bezierLeftTime.x\n            )),\n            cubicBezier(start.y, start.y + bezierRightValue.y, end.y + bezierLeftValue.y, end.y, solveBezierTForTime(\n                alpha,\n                bezierRightTime.y,\n                1 + bezierLeftTime.y\n            )),\n            cubicBezier(start.z, start.z + bezierRightValue.z, end.z + bezierLeftValue.z, end.z, solveBezierTForTime(\n                alpha,\n                bezierRightTime.z,\n                1 + bezierLeftTime.z\n            ))\n        );\n    }\n\n    /**\n     * Interpolates four vectors as catmull-rom.\n     * @param p0 p0\n     * @param p1 p1\n     * @param p2 p2\n     * @param p3 p3\n     * @param t alpha\n     * @return interpolated vector\n     */\n    public static @NotNull Vector3f catmull_rom(@NotNull Vector3f p0, @NotNull Vector3f p1, @NotNull Vector3f p2, @NotNull Vector3f p3, float t) {\n        var t2 = t * t;\n        var t3 = t2 * t;\n        return new Vector3f(\n            fma(t3, fma(-1F, p0.x, fma(3F, p1.x, fma(-3F, p2.x, p3.x))), fma(t2, fma(2F, p0.x, fma(-5F, p1.x, fma(4F, p2.x, -p3.x))), fma(t, -p0.x + p2.x, 2F * p1.x))),\n            fma(t3, fma(-1F, p0.y, fma(3F, p1.y, fma(-3F, p2.y, p3.y))), fma(t2, fma(2F, p0.y, fma(-5F, p1.y, fma(4F, p2.y, -p3.y))), fma(t, -p0.y + p2.y, 2F * p1.y))),\n            fma(t3, fma(-1F, p0.z, fma(3F, p1.z, fma(-3F, p2.z, p3.z))), fma(t2, fma(2F, p0.z, fma(-5F, p1.z, fma(4F, p2.z, -p3.z))), fma(t, -p0.z + p2.z, 2F * p1.z)))\n        ).mul(0.5F);\n    }\n\n    /**\n     * Vector point builder\n     */\n    @FunctionalInterface\n    public interface VectorPointBuilder {\n        /**\n         * Interpolates vector from given float\n         * @param nextFloat next float value\n         * @return interpolated vector\n         */\n        @NotNull VectorResult build(float nextFloat);\n    }\n\n    /**\n     * Vector result\n     * @param vector vector\n     * @param skipInterpolation skip interpolation\n     */\n    public record VectorResult(@NotNull Vector3f vector, boolean skipInterpolation) {\n        /**\n         * Vector result\n         * @param vector vector\n         */\n        public VectorResult(@NotNull Vector3f vector) {\n            this(vector, false);\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/LogUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.config.DebugConfig;\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.text.format.NamedTextColor;\nimport net.kyori.adventure.text.format.TextColor;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.function.Supplier;\n\n/**\n * Log util\n */\n@ApiStatus.Internal\npublic final class LogUtil {\n    /**\n     * No initializer\n     */\n    private LogUtil() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * Handles exception message\n     * @param message message\n     * @param throwable exception\n     */\n    public static void handleException(@NotNull String message, @NotNull Throwable throwable) {\n        var list = new ArrayList<Component>(4);\n        list.add(Component.text(message));\n        list.add(toLog(\"Reason: \" + throwable.getMessage(), NamedTextColor.YELLOW));\n        if (BetterModel.config().debug().has(DebugConfig.DebugOption.EXCEPTION)) {\n            list.add(toLog(\"Stack trace:\", NamedTextColor.RED));\n            try (\n                var byteArray = new ByteArrayOutputStream();\n                var print = new PrintStream(byteArray)\n            ) {\n                throwable.printStackTrace(print);\n                list.add(toLog(byteArray.toString(StandardCharsets.UTF_8), NamedTextColor.RED));\n            } catch (IOException e) {\n                list.add(toLog(\"Unknown\", NamedTextColor.RED));\n            }\n        } else list.add(toLog(\"If you want to see the stack trace, set debug.exception to true in config.yml\", NamedTextColor.LIGHT_PURPLE));\n        BetterModel.platform().logger().warn(list.toArray(Component[]::new));\n    }\n\n    /**\n     * Gets log component\n     * @param message message\n     * @param color color\n     * @return component\n     */\n    public static @NotNull Component toLog(@NotNull String message, @NotNull TextColor color) {\n        return Component.text().content(message).color(color).build();\n    }\n\n    /**\n     * Logs debug if some option is matched.\n     * @param option option\n     * @param log log\n     */\n    public static void debug(@NotNull DebugConfig.DebugOption option, @NotNull Supplier<String> log) {\n        debug(option, () -> BetterModel.platform().logger().info(Component.text()\n            .append(toLog(\"[DEBUG-\" + option + \"] \", NamedTextColor.YELLOW))\n            .append(Component.text(log.get()))\n            .build())\n        );\n    }\n\n    /**\n     * Runs debug if some option is matched.\n     * @param option option\n     * @param runnable debug task\n     */\n    public static void debug(@NotNull DebugConfig.DebugOption option, @NotNull Runnable runnable) {\n        if (BetterModel.config().debug().has(option)) runnable.run();\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/MathUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport it.unimi.dsi.fastutil.floats.FloatComparator;\nimport it.unimi.dsi.fastutil.floats.FloatSet;\nimport kr.toxicity.model.api.data.Float3;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\nimport org.joml.Vector3fc;\n\nimport static java.lang.Math.PI;\nimport static java.lang.Math.abs;\n\n/**\n * Math\n */\n@ApiStatus.Internal\npublic final class MathUtil {\n\n    /**\n     * No initializer\n     */\n    private MathUtil() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * Minecraft tick mills\n     */\n    public static final int MINECRAFT_TICK_MILLS = 50;\n\n    /**\n     * Valid rotation degree\n     */\n    public static final float ROTATION_DEGREE = 22.5F;\n\n    /**\n     * Degrees to radians\n     */\n    public static final float DEGREES_TO_RADIANS = (float) PI / 180F;\n\n    /**\n     * Radians to degrees\n     */\n    public static final float RADIANS_TO_DEGREES = 1F / DEGREES_TO_RADIANS;\n\n    /**\n     * Degrees to packed byte\n     */\n    public static final float DEGREES_TO_PACKED_BYTE = 256F / 360F;\n\n    /**\n     * Multiplier value for convert model size to block size\n     */\n    public static final float MODEL_TO_BLOCK_MULTIPLIER = 16;\n\n    /**\n     * Frame epsilon value\n     */\n    public static final float FRAME_EPSILON = 0.001F;\n\n    /**\n     * Float comparison epsilon value\n     */\n    public static final float FLOAT_COMPARISON_EPSILON = 1E-5F;\n\n    /**\n     * Squared vector comparison epsilon value\n     */\n    public static final float VECTOR_COMPARISON_EPSILON_SQ = 1E-8F;\n\n    /**\n     * Quaternion comparison epsilon value\n     */\n    public static final float QUATERNION_COMPARISON_EPSILON = 1E-5F;\n\n    private static final Vector3f ZERO_VECTOR = new Vector3f();\n\n    /**\n     * Float comparator\n     */\n    public static final FloatComparator FRAME_COMPARATOR = (a, b) -> isSimilar(a, b, FRAME_EPSILON) ? 0 : Float.compare(a, b);\n\n    private static final FloatSet VALID_ROTATION_DEGREES = FloatSet.of(\n        0F,\n        ROTATION_DEGREE,\n        ROTATION_DEGREE * 2,\n        -ROTATION_DEGREE,\n        -ROTATION_DEGREE * 2\n    );\n\n    /**\n     * Checks two floats are similar.\n     * @param a a\n     * @param b b\n     * @return similar or not\n     */\n    public static boolean isSimilar(float a, float b) {\n        return isSimilar(a, b, FLOAT_COMPARISON_EPSILON);\n    }\n\n    /**\n     * Checks two floats are similar.\n     * @param a a\n     * @param b b\n     * @param epsilon epsilon\n     * @return similar or not\n     */\n    public static boolean isSimilar(float a, float b, float epsilon) {\n        return abs(a - b) < epsilon;\n    }\n\n    /**\n     * Checks two vectors are similar.\n     * @param a a\n     * @param b b\n     * @return similar or not\n     */\n    public static boolean isSimilar(@NotNull Vector3fc a, @NotNull Vector3fc b) {\n        return isSimilar(a, b, VECTOR_COMPARISON_EPSILON_SQ);\n    }\n\n    /**\n     * Checks two vectors are similar.\n     * @param a a\n     * @param b b\n     * @param epsilon epsilon\n     * @return similar or not\n     */\n    public static boolean isSimilar(@NotNull Vector3fc a, @NotNull Vector3fc b, float epsilon) {\n        return a.distanceSquared(b) < epsilon;\n    }\n\n    /**\n     * Checks two quaternion are similar.\n     * @param a a\n     * @param b b\n     * @return similar or not\n     */\n    public static boolean isSimilar(@NotNull Quaternionf a, @NotNull Quaternionf b) {\n        return isSimilar(a, b, QUATERNION_COMPARISON_EPSILON);\n    }\n\n    /**\n     * Checks two quaternion are similar.\n     * @param a a\n     * @param b b\n     * @param epsilon epsilon\n     * @return similar or not\n     */\n    public static boolean isSimilar(@NotNull Quaternionf a, @NotNull Quaternionf b, float epsilon) {\n        return abs(fma(a.x, b.x, fma(a.y, b.y, fma(a.z, b.z, a.w * b.w)))) > 1.0F - epsilon;\n    }\n\n    /**\n     * Creates epsilon-based hashcode of given float\n     * @param value value\n     * @return hashcode\n     */\n    public static int similarHashCode(float value) {\n        return (int) (value / FLOAT_COMPARISON_EPSILON);\n    }\n\n    /**\n     * Checks these floats are valid Minecraft degree\n     * @param rotation rotation\n     * @return is valid\n     */\n    public static boolean checkValidDegree(@NotNull Float3 rotation) {\n        var i = 0;\n        if (rotation.x() != 0F) i++;\n        if (rotation.y() != 0F) i++;\n        if (rotation.z() != 0F) i++;\n        return i < 2 && checkValidDegree(rotation.x()) && checkValidDegree(rotation.y()) && checkValidDegree(rotation.z());\n    }\n\n    /**\n     * Checks this float is valid Minecraft degree\n     * @param rotation rotation\n     * @return is valid\n     */\n    public static boolean checkValidDegree(float rotation) {\n        return VALID_ROTATION_DEGREES.contains(rotation);\n    }\n\n    /**\n     * Creates rotation identifier\n     * @param rotation rotation\n     * @return identifier\n     */\n    public static @NotNull Float3 identifier(@NotNull Float3 rotation) {\n        if (checkValidDegree(rotation)) return Float3.ZERO;\n        return rotation;\n    }\n\n    /**\n     * Converts vector rotation to quaternion\n     * @param vector vector\n     * @return rotation\n     */\n    public static @NotNull Quaternionf toQuaternion(@NotNull Vector3f vector) {\n        return toQuaternion(vector, new Quaternionf());\n    }\n\n    /**\n     * Converts vector rotation to quaternion\n     * @param vector vector\n     * @param dest destination quaternion\n     * @return rotation\n     */\n    public static @NotNull Quaternionf toQuaternion(@NotNull Vector3f vector, @NotNull Quaternionf dest) {\n        return dest\n            .identity()\n            .rotateZYX(\n                vector.z * DEGREES_TO_RADIANS,\n                vector.y * DEGREES_TO_RADIANS,\n                vector.x * DEGREES_TO_RADIANS\n            );\n    }\n\n    /**\n     * Converts zyx euler to xyz euler\n     * @param vec zyx euler\n     * @return xyz euler\n     */\n    public static @NotNull Vector3f toXYZEuler(@NotNull Vector3f vec) {\n        return toQuaternion(vec)\n            .getEulerAnglesXYZ(vec)\n            .mul(RADIANS_TO_DEGREES);\n    }\n\n    /**\n     * Executes fused multiply add (a * b + c)\n     * @param a a vector\n     * @param b b vector\n     * @param c c vector\n     * @return added a\n     */\n    public static @NotNull Vector3f fma(@NotNull Vector3f a, @NotNull Vector3f b, @NotNull Vector3f c) {\n        a.x = fma(a.x, b.x, c.x);\n        a.y = fma(a.y, b.y, c.y);\n        a.z = fma(a.z, b.z, c.z);\n        return a;\n    }\n\n    /**\n     * Executes fused multiply add (a * b + c)\n     * @param a a vector\n     * @param b b scala\n     * @param c c vector\n     * @return added a\n     */\n    public static @NotNull Vector3f fma(@NotNull Vector3f a, float b, @NotNull Vector3f c) {\n        a.x = fma(a.x, b, c.x);\n        a.y = fma(a.y, b, c.y);\n        a.z = fma(a.z, b, c.z);\n        return a;\n    }\n\n    /**\n     * Executes fused multiply add (a * b + c)\n     * @param a a\n     * @param b b\n     * @param c c\n     * @return a * b + c\n     */\n    public static float fma(float a, float b, float c) {\n        return org.joml.Math.fma(a, b, c);\n    }\n\n    /**\n     * Executes fused multiply add (a * b + c)\n     * @param a a\n     * @param b b\n     * @param c c\n     * @return a * b + c\n     */\n    public static double fma(double a, double b, double c) {\n        return org.joml.Math.fma(a, b, c);\n    }\n\n    /**\n     * Checks this vector is not zero\n     * @param vector3f vector\n     * @return is not zero\n     */\n    public static boolean isNotZero(@NotNull Vector3f vector3f) {\n        return !isZero(vector3f);\n    }\n\n    /**\n     * Checks this vector is zero\n     * @param vector vector\n     * @return is zero\n     */\n    public static boolean isZero(@NotNull Vector3f vector) {\n        return isSimilar(vector, ZERO_VECTOR);\n    }\n\n    /**\n     * Converts a 32-bit float to IEEE 754 half-precision bits.\n     *\n     * @param value source float\n     * @return half-float bit pattern stored in a short\n     */\n    public static short floatToHalf(float value) {\n        int bits = Float.floatToIntBits(value);\n\n        int sign = (bits >>> 16) & 0x8000;\n        int exp = ((bits >>> 23) & 0xFF) - 127 + 15;\n        int mant = bits & 0x7FFFFF;\n\n        if (((bits >>> 23) & 0xFF) == 0xFF) {\n            if (mant == 0) return (short) (sign | 0x7C00);\n            return (short) (sign | 0x7E00);\n        }\n        if (exp >= 0x1F) return (short) (sign | 0x7C00);\n        if (exp <= 0) {\n            if (exp < -10) return (short) sign;\n\n            mant |= 0x800000;\n            int shift = 14 - exp;\n            int halfMant = mant >> shift;\n\n            int roundBit = 1 << (shift - 1);\n            if ((mant & roundBit) != 0 && ((mant & (roundBit - 1)) != 0 || (halfMant & 1) != 0)) halfMant++;\n\n            return (short) (sign | halfMant);\n        }\n        int halfExp = exp << 10;\n        int halfMant = mant >> 13;\n\n        if ((mant & 0x00001000) != 0) {\n            halfMant++;\n            if ((halfMant & 0x0400) != 0) {\n                halfMant = 0;\n                halfExp += 0x0400;\n                if (halfExp >= 0x7C00) return (short) (sign | 0x7C00);\n            }\n        }\n        return (short) (sign | halfExp | halfMant);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/PackUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.regex.Pattern;\n\n/**\n * Pack util\n */\n@ApiStatus.Internal\npublic final class PackUtil {\n    /**\n     * No initializer\n     */\n    private PackUtil() {\n        throw new RuntimeException();\n    }\n\n    private static final Pattern REPLACE_SOURCE = Pattern.compile(\"[^a-z0-9_.]\");\n\n    /**\n     * Asserts that the given raw string is a valid pack name.\n     * Throws a {@link IllegalArgumentException} if the name contains illegal characters.\n     * @param raw The raw string to validate.\n     */\n    public static void assertPackName(@NotNull String raw) {\n        if (REPLACE_SOURCE.matcher(raw).find()) throw new IllegalArgumentException(\"Illegal pack name: \" + raw);\n    }\n\n    /**\n     * Converts some path to compatible with Minecraft resource location\n     * @param raw raw path\n     * @return converted path\n     */\n    public static @NotNull String toPackName(@NotNull String raw) {\n        return REPLACE_SOURCE.matcher(raw.toLowerCase())\n            .replaceAll(result -> Integer.toString(result.group().hashCode(), 16).toLowerCase());\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/ReflectionUtil.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Reflection util\n */\n@ApiStatus.Internal\npublic final class ReflectionUtil {\n    /**\n     * No initializer\n     */\n    private ReflectionUtil() {\n        throw new RuntimeException();\n    }\n\n    /**\n     * Checks some class is existing.\n     * @param clazz class path\n     * @return exists\n     */\n    public static boolean classExists(@NotNull String clazz) {\n        try {\n            Class.forName(clazz);\n            return true;\n        } catch (ClassNotFoundException e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/TransformedItemStack.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util;\n\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.platform.PlatformItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\nimport java.util.function.Function;\n\n/**\n * ItemStack with offset and scale\n * @see PlatformItemStack\n * @param position global position (x, y, z)\n * @param offset offset (x, y, z)\n * @param scale scale (x, y, z)\n * @param itemStack item\n */\npublic record TransformedItemStack(@NotNull Vector3f position, @NotNull Vector3f offset, @NotNull Vector3f scale, @NotNull PlatformItemStack itemStack) {\n\n    /**\n     * Creates empty transformed item\n     * @return empty transformed item\n     */\n    public static @NotNull TransformedItemStack empty() {\n        return of(BetterModel.platform().adapter().air());\n    }\n\n    /**\n     * Creates transformed item\n     * @param itemStack item\n     * @return transformed item\n     */\n    public static @NotNull TransformedItemStack of(@NotNull PlatformItemStack itemStack) {\n        return of(new Vector3f(), new Vector3f(), new Vector3f(1), itemStack);\n    }\n\n    /**\n     * Creates transformed item\n     * @param position position\n     * @param offset offset\n     * @param scale scale\n     * @param itemStack item\n     * @return transformed item\n     */\n    public static @NotNull TransformedItemStack of(@NotNull Vector3f position, @NotNull Vector3f offset, @NotNull Vector3f scale, @NotNull PlatformItemStack itemStack) {\n        return new TransformedItemStack(position, offset, scale, itemStack);\n    }\n\n    /**\n     * Gets transformed item as air\n     * @return air item\n     */\n    public @NotNull TransformedItemStack asAir() {\n        return of(position, offset, scale, BetterModel.platform().adapter().air());\n    }\n\n    /**\n     * Sets offset\n     * @param offset offset\n     * @return new item\n     */\n    public @NotNull TransformedItemStack offset(@NotNull Vector3f offset) {\n        return of(position, offset, scale, itemStack);\n    }\n\n    /**\n     * Modify item\n     * @param mapper mapper\n     * @return modified item\n     */\n    public @NotNull TransformedItemStack modify(@NotNull Function<PlatformItemStack, PlatformItemStack> mapper) {\n        return of(position, offset, scale, mapper.apply(itemStack.clone()));\n    }\n\n    /**\n     * Checks this item is air\n     * @return is air\n     */\n    public boolean isAir() {\n        return itemStack.isAir();\n    }\n\n    /**\n     * Copy this item\n     * @return copied item\n     */\n    public @NotNull TransformedItemStack copy() {\n        return new TransformedItemStack(\n            new Vector3f(position),\n            new Vector3f(offset),\n            new Vector3f(scale),\n            itemStack.clone()\n        );\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/collection/PriorityMap.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.collection;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.function.Function;\n\n/**\n * A map that maintains the order of values based on priority, insertion order, and key comparison.\n *\n * @param <K> the type of keys maintained by this map\n * @param <V> the type of mapped values\n * @since 3.0.0\n */\npublic final class PriorityMap<K extends Comparable<K>, V> {\n\n    private final Map<K, Identifier<K>> keyMap = new HashMap<>();\n    private final TreeMap<Identifier<K>, V> valueMap = new TreeMap<>();\n    private long counter;\n\n    /**\n     * Returns true if this map contains no key-value mappings.\n     *\n     * @return true if this map contains no key-value mappings\n     */\n    public boolean isEmpty() {\n        return valueMap.isEmpty();\n    }\n\n    /**\n     * Internal identifier used to sort entries in the value map.\n     */\n    private record Identifier<K extends Comparable<K>>(\n        int priority,\n        long count,\n        @NotNull K key\n    ) implements Comparable<Identifier<K>> {\n\n        /**\n         * Compares this identifier with another based on priority (descending),\n         * then insertion count (descending), and finally the key itself.\n         *\n         * @param o the object to be compared.\n         * @return a negative integer, zero, or a positive integer as this object\n         * is less than, equal to, or greater than the specified object.\n         */\n        @Override\n        public int compareTo(@NotNull Identifier<K> o) {\n            int c;\n            if ((c = Integer.compare(o.priority, priority)) != 0) return c;\n            if ((c = Long.compare(o.count, count)) != 0) return c;\n            return key.compareTo(o.key);\n        }\n    }\n\n    /**\n     * Associates the specified value with the specified key and priority in this map.\n     *\n     * @param key      key with which the specified value is to be associated\n     * @param value    value to be associated with the specified key\n     * @param priority priority of the entry\n     * @return the previous value associated with key, or null if there was no mapping for key.\n     */\n    public @Nullable V put(@NotNull K key, @NotNull V value, int priority) {\n        Objects.requireNonNull(key);\n        Objects.requireNonNull(value);\n        Identifier<K> newIdentifier, oldIdentifier;\n        if ((oldIdentifier = keyMap.put(key, newIdentifier = new Identifier<>(priority, counter++, key))) != null) valueMap.remove(oldIdentifier);\n        return valueMap.put(newIdentifier, value);\n    }\n\n    /**\n     * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.\n     *\n     * @param key the key whose associated value is to be returned\n     * @return the value to which the specified key is mapped, or null if this map contains no mapping for the key\n     */\n    public @Nullable V get(@NotNull K key) {\n        Identifier<K> identifier;\n        return (identifier = keyMap.get(Objects.requireNonNull(key))) == null ? null : valueMap.get(identifier);\n    }\n\n    /**\n     * Removes the mapping for a key from this map if it is present.\n     *\n     * @param key key whose mapping is to be removed from the map\n     * @return the previous value associated with key, or null if there was no mapping for key.\n     */\n    public @Nullable V remove(@NotNull K key) {\n        Identifier<K> identifier;\n        return (identifier = keyMap.remove(Objects.requireNonNull(key))) == null ? null : valueMap.remove(identifier);\n    }\n\n    /**\n     * Replaces the entry for the specified key only if it is currently mapped to some value.\n     *\n     * @param Key      key with which the specified value is associated\n     * @param function the function to compute a value\n     * @return the previous value associated with the specified key, or null if there was no mapping for the key.\n     */\n    public @Nullable V replace(@NotNull K Key, @NotNull Function<V, V> function) {\n        Identifier<K> identifier;\n        if ((identifier = keyMap.get(Objects.requireNonNull(Key))) == null) return null;\n        return valueMap.computeIfPresent(identifier, (_, v) -> function.apply(v));\n    }\n\n    /**\n     * Returns an iterator over the values in this map in priority order.\n     *\n     * @return a value iterator\n     * @since 3.0.1\n     */\n    public @NotNull Iterator<V> valueIterator() {\n        return new ValueIterator();\n    }\n\n    private class ValueIterator implements Iterator<V> {\n\n        private final Iterator<Map.Entry<Identifier<K>, V>> delegate = valueMap.entrySet().iterator();\n        private Identifier<K> identifier;\n\n        @Override\n        public boolean hasNext() {\n            return delegate.hasNext();\n        }\n\n        @Override\n        public V next() {\n            var next = delegate.next();\n            identifier = next.getKey();\n            return next.getValue();\n        }\n\n        @Override\n        public void remove() {\n            delegate.remove();\n            keyMap.remove(Objects.requireNonNull(identifier).key);\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/collection/SingletonSequencedSet.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.collection;\n\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\n@Unmodifiable\n@ApiStatus.Internal\npublic final class SingletonSequencedSet<E> extends AbstractSet<E> implements SequencedSet<E> {\n\n    private final E element;\n    private final Set<E> delegate;\n\n    public static <E> @NotNull SingletonSequencedSet<E> of(@NotNull E element) {\n        return new SingletonSequencedSet<>(element);\n    }\n\n    private SingletonSequencedSet(@NotNull E element) {\n        this.element = element;\n        this.delegate = Collections.singleton(element);\n    }\n\n    public int size() {\n        return 1;\n    }\n\n    public boolean contains(Object o) {\n        return element.equals(o);\n    }\n\n    @Override\n    public int hashCode() {\n        return element.hashCode();\n    }\n\n    @NotNull\n    public Iterator<E> iterator() {\n        return delegate.iterator();\n    }\n\n\n    // Override default methods for Collection\n    @Override\n    @NotNull\n    public Spliterator<E> spliterator() {\n        return delegate.spliterator();\n    }\n\n    @Override\n    public void forEach(Consumer<? super E> action) {\n        action.accept(element);\n    }\n\n    @Override\n    public boolean removeIf(@NotNull Predicate<? super E> filter) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public @NotNull Stream<E> stream() {\n        return Stream.of(element);\n    }\n\n    @Override\n    public @NotNull Stream<E> parallelStream() {\n        return stream();\n    }\n\n    // Override default methods for SequencedCollection\n    @Override\n    public E getFirst() {\n        return element;\n    }\n\n    @Override\n    public E getLast() {\n        return element;\n    }\n\n    @Override\n    public E removeFirst() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public E removeLast() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    @NotNull\n    @Unmodifiable\n    public SequencedSet<E> reversed() {\n        return this;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/BonePredicate.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\nimport kr.toxicity.model.api.bone.BoneTag;\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\nimport java.util.function.Predicate;\n\n/**\n * Bone predicate\n */\npublic interface BonePredicate extends Predicate<RenderedBone> {\n\n    /**\n     * True\n     */\n    BonePredicate TRUE = of(State.TRUE, _ -> true);\n\n    /**\n     * False\n     */\n    BonePredicate FALSE = of(State.FALSE, _ -> false);\n\n    /**\n     * Gets builder by name\n     * @param name name\n     * @return builder\n     */\n    static @NotNull Builder name(@NotNull String name) {\n        return b -> b.name().name().equalsIgnoreCase(name);\n    }\n\n    /**\n     * Gets builder by tags\n     * @param tags tags\n     * @return builder\n     */\n    static @NotNull Builder tag(@NotNull BoneTag... tags) {\n        if (tags.length == 0) throw new RuntimeException(\"tags cannot be empty.\");\n        return b -> b.name().tagged(tags);\n    }\n\n    @Override\n    boolean test(@NotNull RenderedBone bone);\n\n    @Override\n    @NotNull\n    BonePredicate and(@NotNull Predicate<? super RenderedBone> other);\n\n    @Override\n    @NotNull\n    BonePredicate or(@NotNull Predicate<? super RenderedBone> other);\n\n    @Override\n    @NotNull\n    BonePredicate negate();\n\n    /**\n     * Should apply at children bone too\n     * @return apply at children\n     */\n    @NotNull State applyAtChildren();\n\n    /**\n     * Gets bone predicate\n     * @param predicate predicate\n     * @return bone predicate\n     */\n    static @NotNull BonePredicate from(@NotNull Predicate<RenderedBone> predicate) {\n        return of(State.NOT_SET, predicate);\n    }\n\n    /**\n     * Gets bone predicate\n     * @param applyAtChildren apply at children\n     * @param predicate predicate\n     * @return bone predicate\n     */\n    static @NotNull BonePredicate of(@NotNull State applyAtChildren, @NotNull Predicate<RenderedBone> predicate) {\n        Objects.requireNonNull(predicate, \"predicate cannot be null.\");\n        return new Packed(applyAtChildren, predicate);\n    }\n\n    /**\n     * Packed value\n     * @param applyAtChildren apply at children\n     * @param predicate predicate\n     */\n    record Packed(@NotNull State applyAtChildren, @NotNull Predicate<RenderedBone> predicate) implements BonePredicate {\n        @Override\n        public boolean test(@NotNull RenderedBone bone) {\n            return predicate.test(bone);\n        }\n\n        @Override\n        @NotNull\n        public BonePredicate and(@NotNull Predicate<? super RenderedBone> other) {\n            Objects.requireNonNull(other);\n            return of(applyAtChildren, t -> predicate.test(t) && other.test(t));\n        }\n\n        @Override\n        @NotNull\n        public BonePredicate or(@NotNull Predicate<? super RenderedBone> other) {\n            Objects.requireNonNull(other);\n            return of(applyAtChildren, t -> predicate.test(t) || other.test(t));\n        }\n\n        @Override\n        @NotNull\n        public BonePredicate negate() {\n            return of(applyAtChildren, t -> !predicate.test(t));\n        }\n    }\n\n    /**\n     * children bone state\n     */\n    enum State {\n        /**\n         * Apply with children too\n         */\n        TRUE,\n        /**\n         * Doesn't apply children\n         */\n        FALSE,\n        /**\n         * Ignore parent's result\n         */\n        NOT_SET\n    }\n\n    /**\n     * Gets children predicate\n     * @param parentSuccess result at parent bone\n     * @return bone predicate\n     */\n    @ApiStatus.Internal\n    default @NotNull BonePredicate children(boolean parentSuccess) {\n        return parentSuccess ? switch (applyAtChildren()) {\n            case TRUE -> BonePredicate.TRUE;\n            case FALSE -> BonePredicate.FALSE;\n            case NOT_SET -> this;\n        } : this;\n    }\n\n    /**\n     * Builder\n     */\n    @FunctionalInterface\n    interface Builder extends Predicate<RenderedBone> {\n\n        /**\n         * Builds with child state\n         * @return bone predicate\n         */\n        default @NotNull BonePredicate notSet() {\n            return build(State.NOT_SET);\n        }\n\n        /**\n         * Builds with applying children bone\n         * @return bone predicate\n         */\n        default @NotNull BonePredicate withChildren() {\n            return build(State.TRUE);\n        }\n\n        /**\n         * Builds without applying children bone\n         * @return bone predicate\n         */\n        default @NotNull BonePredicate withoutChildren() {\n            return build(State.FALSE);\n        }\n\n        /**\n         * Builds with child state\n         * @param state state\n         * @return bone predicate\n         */\n        default @NotNull BonePredicate build(@NotNull State state) {\n            return of(state, this);\n        }\n\n        @Override\n        @NotNull\n        default Builder and(@NotNull Predicate<? super RenderedBone> other) {\n            return bone -> test(bone) && other.test(bone);\n        }\n\n        @Override\n        @NotNull\n        default Builder or(@NotNull Predicate<? super RenderedBone> other) {\n            return bone -> test(bone) || other.test(bone);\n        }\n\n        @Override\n        @NotNull\n        default Builder negate() {\n            return bone -> !test(bone);\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/BooleanConstantSupplier.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\nimport lombok.RequiredArgsConstructor;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.BooleanSupplier;\n\n/**\n * Boolean constant supplier\n */\n@RequiredArgsConstructor\npublic enum BooleanConstantSupplier implements BooleanSupplier {\n    /**\n     * True\n     */\n    TRUE(true),\n    /**\n     * False\n     */\n    FALSE(false)\n    ;\n\n    private final boolean value;\n\n    public static @NotNull BooleanConstantSupplier of(boolean value) {\n        return value ? TRUE : FALSE;\n    }\n\n    @Override\n    public boolean getAsBoolean() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/Float2FloatConstantFunction.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\n/**\n * Float to float constant function\n * @param value value\n */\npublic record Float2FloatConstantFunction(float value) implements Float2FloatFunction {\n    @Override\n    public float applyAsFloat(float value) {\n        return this.value;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/Float2FloatFunction.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Float to float function\n */\n@FunctionalInterface\npublic interface Float2FloatFunction {\n    /**\n     * Zero\n     */\n    Float2FloatConstantFunction ZERO = of(0);\n\n    /**\n     * Applies float value\n     * @param value value\n     * @return applied value\n     */\n    float applyAsFloat(float value);\n\n    /**\n     * Creates constant function by given value\n     * @param value value\n     * @return constant function\n     */\n    static @NotNull Float2FloatConstantFunction of(float value) {\n        return new Float2FloatConstantFunction(value);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/FloatConstantFunction.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Function;\n\n/**\n * Float constant function\n * @param value value\n * @param <T> type\n */\npublic record FloatConstantFunction<T>(@NotNull T value) implements FloatFunction<T> {\n    @Override\n    public @NotNull T apply(float value) {\n        return this.value;\n    }\n\n    @Override\n    public @NotNull <R> FloatFunction<R> map(@NotNull Function<T, R> mapper) {\n        return FloatFunction.of(mapper.apply(value));\n    }\n\n    @Override\n    public @NotNull FloatFunction<T> memoize() {\n        return this;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/FloatConstantSupplier.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\n/**\n * Float constant supplier\n * @param value value\n */\npublic record FloatConstantSupplier(float value) implements FloatSupplier {\n\n    /**\n     * One\n     */\n    public static final FloatConstantSupplier ONE = FloatSupplier.of(1F);\n\n    @Override\n    public float getAsFloat() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/FloatFunction.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\nimport it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;\nimport kr.toxicity.model.api.util.MathUtil;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\nimport java.util.function.Function;\n\n/**\n * Float function\n * @param <T> type\n */\n@FunctionalInterface\npublic interface FloatFunction<T> {\n\n    /**\n     * Applies float\n     * @param value float value\n     * @return applied value\n     */\n    @NotNull T apply(float value);\n\n    /**\n     * Creates constant function by given value\n     * @param t value\n     * @return constant function\n     * @param <T> type\n     */\n    static <T> @NotNull FloatConstantFunction<T> of(@NotNull T t) {\n        return new FloatConstantFunction<>(Objects.requireNonNull(t));\n    }\n\n    /**\n     * Maps this function to new type\n     * @param mapper mapper\n     * @return mapped function\n     * @param <R> return type\n     */\n    default <R> @NotNull FloatFunction<R> map(@NotNull Function<T, R> mapper) {\n        return f -> mapper.apply(apply(f));\n    }\n\n    /**\n     * Memoize this function\n     * @return memoized function\n     */\n    default @NotNull FloatFunction<T> memoize() {\n        var map = new Int2ReferenceOpenHashMap<T>();\n        return f -> map.computeIfAbsent(MathUtil.similarHashCode(f), _ -> apply(f));\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/function/FloatSupplier.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.function;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Float supplier\n */\n@FunctionalInterface\npublic interface FloatSupplier {\n    /**\n     * Gets float value\n     * @return float value\n     */\n    float getAsFloat();\n\n    /**\n     * Creates supplier by given value\n     * @param value val ue\n     * @return supplier\n     */\n    static @NotNull FloatConstantSupplier of(float value) {\n        return new FloatConstantSupplier(value);\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/interpolator/VectorInterpolator.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.interpolator;\n\nimport com.google.gson.annotations.SerializedName;\nimport kr.toxicity.model.api.animation.VectorPoint;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\nimport java.util.List;\n\nimport static kr.toxicity.model.api.util.InterpolationUtil.*;\n\n/**\n * Interpolator\n */\n@ApiStatus.Internal\npublic enum VectorInterpolator {\n    /**\n     * Linear\n     */\n    @SerializedName(\"linear\")\n    LINEAR {\n        @NotNull\n        @Override\n        public Vector3f interpolate(@NotNull List<VectorPoint> points, int p2Index, float time) {\n            var p1 = p2Index > 0 ? points.get(p2Index - 1) : points.getFirst();\n            var p2 = points.get(p2Index);\n            var t1 = p1.time();\n            var t2 = p2.time();\n            var a = alpha(t1, t2, time);\n            return lerp(\n                p1.vector(lerp(t1, t2, a)),\n                p2.vector(),\n                a\n            );\n        }\n    },\n    /**\n     * Catmullrom\n     */\n    @SerializedName(\"catmullrom\")\n    CATMULLROM {\n        private static @NotNull VectorPoint indexOf(@NotNull List<VectorPoint> list, int index, int relative) {\n            var i = index + relative;\n            while (i < 0) i += list.size();\n            return list.get(i % list.size());\n        }\n\n        @NotNull\n        @Override\n        public Vector3f interpolate(@NotNull List<VectorPoint> points, int p2Index, float time) {\n            var p0 = indexOf(points, p2Index, -2);\n            var p1 = indexOf(points, p2Index, -1);\n            var p2 = points.get(p2Index);\n            var p3 = indexOf(points, p2Index, 1);\n\n            var t1 = p1.time();\n            var t2 = p2.time();\n            var a = alpha(t1, t2, time);\n\n            return catmull_rom(\n                p0.vector(),\n                p1.vector(lerp(t1, t2, a)),\n                p2.vector(),\n                p3.vector(),\n                a\n            );\n        }\n    },\n    /**\n     * Bezier\n     */\n    @SerializedName(\"bezier\")\n    BEZIER {\n        @NotNull\n        @Override\n        public Vector3f interpolate(@NotNull List<VectorPoint> points, int p2Index, float time) {\n            var p1 = p2Index > 0 ? points.get(p2Index - 1) : points.getFirst();\n            var p2 = points.get(p2Index);\n\n            var t1 = p1.time();\n            var t2 = p2.time();\n            var a = alpha(t1, t2, time);\n\n            return bezier(\n                a,\n                p1.vector(lerp(t1, t2, a)),\n                p2.vector(),\n                p1.bezier().rightTime(),\n                p1.bezier().rightValue(),\n                p2.bezier().leftTime(),\n                p2.bezier().leftValue()\n            );\n        }\n    },\n    /**\n     * Step\n     */\n    @SerializedName(\"step\")\n    STEP {\n        @NotNull\n        @Override\n        public Vector3f interpolate(@NotNull List<VectorPoint> points, int p2Index, float time) {\n            return (p2Index > 0 ? points.get(p2Index - 1) : points.getFirst()).vector(time);\n        }\n\n        @Override\n        public boolean isContinuous() {\n            return false;\n        }\n    }\n    ;\n\n    /**\n     * Interpolates vector\n     * @param points points\n     * @param p2Index p2 index\n     * @param time destination time\n     * @return interpolated vector\n     */\n    @NotNull\n    public abstract Vector3f interpolate(@NotNull List<VectorPoint> points, int p2Index, float time);\n\n    /**\n     * Checks this interpolator is continuous\n     * @return is continuous\n     */\n    public boolean isContinuous() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/json/JsonArrayBuilder.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.json;\n\nimport com.google.gson.JsonArray;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\npublic final class JsonArrayBuilder {\n\n    private final JsonArray array = new JsonArray();\n\n    /**\n     * Creates builder\n     * @return builder\n     */\n    public static @NotNull JsonArrayBuilder builder() {\n        return new JsonArrayBuilder();\n    }\n\n    public @NotNull JsonArrayBuilder jsonObject(@NotNull Consumer<JsonObjectBuilder> consumer) {\n        var builder = JsonObjectBuilder.builder();\n        Objects.requireNonNull(consumer).accept(builder);\n        var json = builder.build();\n        if (!json.isEmpty()) array.add(json);\n        return this;\n    }\n\n    public @NotNull JsonArray build() {\n        return array;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/json/JsonObjectBuilder.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.json;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * JSON object builder\n */\n@ApiStatus.Internal\npublic final class JsonObjectBuilder {\n    private final JsonObject object = new JsonObject();\n\n    /**\n     * Private initializer\n     */\n    private JsonObjectBuilder() {\n    }\n\n    /**\n     * Creates builder\n     * @return builder\n     */\n    public static @NotNull JsonObjectBuilder builder() {\n        return new JsonObjectBuilder();\n    }\n\n    /**\n     * Builds JSON object\n     * @return build\n     */\n    public @NotNull JsonObject build() {\n        return object;\n    }\n\n    /**\n     * Adds JSON object\n     * @param name name\n     * @param consumer builder\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder jsonObject(@NotNull String name, @NotNull Consumer<JsonObjectBuilder> consumer) {\n        var builder = builder();\n        Objects.requireNonNull(consumer).accept(builder);\n        if (builder.object.isEmpty()) return this;\n        object.add(name, builder.build());\n        return this;\n    }\n\n    /**\n     * Adds JSON object\n     * @param name name\n     * @param jsonObject object\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder jsonObject(@NotNull String name, @Nullable JsonObject jsonObject) {\n        if (jsonObject != null) object.add(name, jsonObject);\n        return this;\n    }\n\n    /**\n     * Adds JSON array\n     * @param name name\n     * @param array array\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder jsonArray(@NotNull String name, @Nullable JsonArray array) {\n        if (array != null) object.add(name, array);\n        return this;\n    }\n\n    /**\n     * Adds JSON array\n     * @param name name\n     * @param consumer consumer\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder jsonArray(@NotNull String name, @NotNull Consumer<JsonArrayBuilder> consumer) {\n        var builder = JsonArrayBuilder.builder();\n        Objects.requireNonNull(consumer).accept(builder);\n        var json = builder.build();\n        if (!json.isEmpty()) object.add(name, json);\n        return this;\n    }\n\n    /**\n     * Adds JSON property\n     * @param name name\n     * @param property property\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder property(@NotNull String name, @NotNull String property) {\n        object.addProperty(name, property);\n        return this;\n    }\n\n    /**\n     * Adds JSON property\n     * @param name name\n     * @param property property\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder property(@NotNull String name, @NotNull Boolean property) {\n        object.addProperty(name, property);\n        return this;\n    }\n\n    /**\n     * Adds JSON property\n     * @param name name\n     * @param property property\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder property(@NotNull String name, @Nullable Number property) {\n        if (property != null) object.addProperty(name, property);\n        return this;\n    }\n\n    /**\n     * Adds JSON property\n     * @param entries entries\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder stringProperties(@NotNull Iterable<Map.Entry<String, String>> entries) {\n        for (var entry : entries) {\n            property(entry.getKey(), entry.getValue());\n        }\n        return this;\n    }\n\n    /**\n     * Adds JSON property\n     * @param entries entries\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder booleanProperties(@NotNull Iterable<Map.Entry<String, Boolean>> entries) {\n        for (var entry : entries) {\n            property(entry.getKey(), entry.getValue());\n        }\n        return this;\n    }\n\n    /**\n     * Adds JSON property\n     * @param entries entries\n     * @return self\n     */\n    public @NotNull JsonObjectBuilder numberProperties(@NotNull Iterable<Map.Entry<String, Number>> entries) {\n        for (var entry : entries) {\n            property(entry.getKey(), entry.getValue());\n        }\n        return this;\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/lazy/LazyFloatProvider.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.lazy;\n\nimport kr.toxicity.model.api.util.InterpolationUtil;\nimport kr.toxicity.model.api.util.function.FloatSupplier;\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Vector3f;\n\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * Lazy float provider\n */\n@ApiStatus.Internal\npublic final class LazyFloatProvider {\n    private final FloatSupplier requiredTime;\n    private long time = System.currentTimeMillis();\n    private float storedValue;\n    private boolean first = true;\n\n    /**\n     * Creates from time\n     * @param requiredTime required time\n     */\n    public LazyFloatProvider(long requiredTime) {\n        this((float) requiredTime);\n    }\n    /**\n     * Creates from time\n     * @param requiredTime required time\n     */\n    public LazyFloatProvider(float requiredTime) {\n        this(() -> requiredTime);\n    }\n    /**\n     * Creates from time supplier\n     * @param requiredTime required time supplier\n     */\n    public LazyFloatProvider(@NotNull FloatSupplier requiredTime) {\n        this.requiredTime = requiredTime;\n    }\n\n    /**\n     * Creates from time supplier\n     * @param requiredTime required time supplier\n     * @param initialValue initial value\n     */\n    public LazyFloatProvider(float initialValue, @NotNull FloatSupplier requiredTime) {\n        this(requiredTime);\n        this.storedValue = initialValue;\n    }\n\n    /**\n     * Updates and gets float\n     * @param updateValue destination value\n     * @return interpolated value\n     */\n    public float updateAndGet(float updateValue) {\n        var req = requiredTime.getAsFloat();\n        if (req <= 0 || first) {\n            first = false;\n            return storedValue = updateValue;\n        }\n        var current = System.currentTimeMillis();\n        var alpha = Math.clamp((float) (current - time) / req, 0, 1);\n        time = current;\n        return storedValue = InterpolationUtil.lerp(\n            storedValue,\n            updateValue,\n            alpha\n        );\n    }\n\n    /**\n     * Sets stored value\n     * @param storedValue new value\n     */\n    public void storedValue(float storedValue) {\n        this.storedValue = storedValue;\n        time = System.currentTimeMillis();\n    }\n\n    /**\n     * Gets lazy provider of vector\n     * @param requiredTime required time\n     * @param delegate source provider\n     * @return lazy provider\n     */\n    public static @NotNull Supplier<Vector3f> ofVector(@NotNull FloatSupplier requiredTime, @NotNull Supplier<Vector3f> delegate) {\n        Objects.requireNonNull(requiredTime);\n        Objects.requireNonNull(delegate);\n        var xLazy = new LazyFloatProvider(requiredTime);\n        var yLazy = new LazyFloatProvider(requiredTime);\n        var zLazy = new LazyFloatProvider(requiredTime);\n        return () -> {\n            var get = delegate.get();\n            get.x = xLazy.updateAndGet(get.x);\n            get.y = yLazy.updateAndGet(get.y);\n            get.z = zLazy.updateAndGet(get.z);\n            return get;\n        };\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/lock/DuplexLock.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.lock;\n\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\nimport java.util.function.Supplier;\n\n/**\n * Duplex lock\n */\n@ApiStatus.Internal\npublic final class DuplexLock {\n    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();\n\n    /**\n     * Access to read lock\n     * @param supplier supplier\n     * @return value\n     * @param <T> type\n     */\n    public <T> T accessToReadLock(@NotNull Supplier<T> supplier) {\n        var readLock = lock.readLock();\n        readLock.lock();\n        try {\n            return supplier.get();\n        } finally {\n            readLock.unlock();\n        }\n    }\n\n    /**\n     * Access to write lock\n     * @param supplier supplier\n     * @return value\n     * @param <T> type\n     */\n    public <T> T accessToWriteLock(@NotNull Supplier<T> supplier) {\n        var writeLock = lock.writeLock();\n        writeLock.lock();\n        try {\n            return supplier.get();\n        } finally {\n            writeLock.unlock();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/util/lock/SingleLock.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.util.lock;\n\nimport org.jetbrains.annotations.ApiStatus;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.function.Supplier;\n\n@ApiStatus.Internal\npublic final class SingleLock {\n\n    private final Object lock;\n\n    public SingleLock() {\n        this(null);\n    }\n\n    public SingleLock(@Nullable Object lock) {\n        this.lock = lock != null ? lock : this;\n    }\n\n    public <T> T accessToLock(@NotNull Supplier<T> supplier) {\n        synchronized (lock) {\n            return supplier.get();\n        }\n    }\n}\n"
  },
  {
    "path": "api/src/main/java/kr/toxicity/model/api/version/MinecraftVersion.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.api.version;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.semver4j.Semver;\n\nimport java.util.Comparator;\nimport java.util.Objects;\n\n/**\n * Minecraft version.\n * @param major title\n * @param minor main update\n * @param patch minor update\n */\npublic record MinecraftVersion(int major, int minor, int patch) implements Comparable<MinecraftVersion> {\n    /**\n     * 26.1.2\n     */\n    public static final MinecraftVersion V26_1_2 = of(26, 1, 2);\n    /**\n     * 26.1.1\n     */\n    public static final MinecraftVersion V26_1_1 = of(26, 1, 1);\n    /**\n     * 26.1\n     */\n    public static final MinecraftVersion V26_1 = of(26, 1, 0);\n    /**\n     * 1.21.11\n     */\n    public static final MinecraftVersion V1_21_11 = of(1, 21, 11);\n    /**\n     * 1.21.10\n     */\n    public static final MinecraftVersion V1_21_10 = of(1, 21, 10);\n    /**\n     * 1.21.9\n     */\n    public static final MinecraftVersion V1_21_9 = of(1, 21, 9);\n    /**\n     * 1.21.8\n     */\n    public static final MinecraftVersion V1_21_8 = of(1, 21, 8);\n    /**\n     * 1.21.7\n     */\n    public static final MinecraftVersion V1_21_7 = of(1, 21, 7);\n    /**\n     * 1.21.6\n     */\n    public static final MinecraftVersion V1_21_6 = of(1, 21, 6);\n    /**\n     * 1.21.5\n     */\n    public static final MinecraftVersion V1_21_5 = of(1, 21, 5);\n    /**\n     * 1.21.4\n     */\n    public static final MinecraftVersion V1_21_4 = of(1, 21, 4);\n\n    /**\n     * Comparator\n     */\n    private static final Comparator<MinecraftVersion> COMPARATOR = Comparator.comparing(MinecraftVersion::major)\n        .thenComparing(MinecraftVersion::minor)\n        .thenComparing(MinecraftVersion::patch);\n\n    /**\n     * Parses version from string\n     * @param version version like \"1.21.11\"\n     * @return parsed version\n     */\n    public static @NotNull MinecraftVersion parse(@NotNull String version) {\n        var split = Objects.requireNonNull(Semver.coerce(version));\n        return of(split.getMajor(), split.getMinor(), split.getPatch());\n    }\n\n    /**\n     * Creates version\n     * @param major major\n     * @param minor minor\n     * @param patch patch\n     * @return Minecraft version\n     */\n    public static @NotNull MinecraftVersion of(int major, int minor, int patch) {\n        return new MinecraftVersion(major, minor, patch);\n    }\n\n    @Override\n    public int compareTo(@NotNull MinecraftVersion o) {\n        return COMPARATOR.compare(this, o);\n    }\n\n    @NotNull\n    @Override\n    public String toString() {\n        return major + \".\" + minor + \".\" + patch;\n    }\n}\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "import io.papermc.hangarpublishplugin.model.Platforms\n\nplugins {\n    alias(libs.plugins.convention.standard)\n    alias(libs.plugins.hangar)\n    id(\"xyz.jpenilla.run-paper\") version \"3.0.2\"\n}\n\nval minecraft = property(\"minecraft_version\").toString()\nval versionString = version.toString()\nval groupString = group.toString()\n\nval javadocJar by tasks.registering(Jar::class) {\n    dependsOn(tasks.dokkaGenerate)\n    archiveClassifier = \"javadoc\"\n    from(layout.buildDirectory.dir(\"dokka/html\").orNull?.asFile)\n}\n\nrunPaper {\n    disablePluginJarDetection()\n}\n\ntasks {\n    runServer {\n        pluginJars(fileTree(\"plugins\"))\n        pluginJars(project(\":platform:bettermodel-paper\").tasks.named<Jar>(\"shadowJar\").flatMap {\n            it.archiveFile\n        })\n        pluginJars(project(\":test-plugin\").tasks.jar.flatMap {\n            it.archiveFile\n        })\n        minecraftVersion(minecraft)\n        downloadPlugins {\n            hangar(\"ViaVersion\", \"5.9.0\")\n            hangar(\"ViaBackwards\", \"5.9.0\")\n            hangar(\"Skript\", \"2.15.0\")\n        }\n    }\n    build {\n        finalizedBy(\n            javadocJar\n        )\n    }\n    jar {\n        enabled = false\n    }\n}\n\nhangarPublish {\n    publications.register(\"plugin\") {\n        version = project.version as String\n        id = \"BetterModel\"\n        apiKey = System.getenv(\"HANGAR_API_TOKEN\")\n        val log = System.getenv(\"COMMIT_MESSAGE\")\n        if (log != null) {\n            changelog = log\n            channel = \"Snapshot\"\n        } else {\n            changelog = rootProject.file(\"changelog/$versionString.md\").readText()\n            channel = \"Release\"\n        }\n        platforms {\n            register(Platforms.PAPER) {\n                jar = project(\":platform:bettermodel-paper\").tasks.named<Jar>(\"shadowJar\").flatMap {\n                    it.archiveFile\n                }\n                platformVersions = SUPPORTED_VERSIONS\n                dependencies {\n                    hangar(\"SkinsRestorer\") { required = false }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/build.gradle.kts",
    "content": "plugins {\n    `kotlin-dsl`\n}\n\nrepositories {\n    mavenCentral()\n    gradlePluginPortal()\n}\n\ndependencies {\n    implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))\n\n    implementation(libs.build.kotlin.jvm)\n    implementation(libs.build.shadow)\n    implementation(libs.build.hangarPublish)\n    implementation(libs.build.minotaur)\n    implementation(libs.build.resourcefactory)\n    implementation(libs.build.paperweight)\n\n    implementation(\"org.jetbrains.dokka:dokka-gradle-plugin:2.2.0\")\n    implementation(\"dev.yumi.gradle.licenser:dev.yumi.gradle.licenser.gradle.plugin:3.0.1\")\n    implementation(\"com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin:0.36.0\")\n}\n"
  },
  {
    "path": "buildSrc/settings.gradle.kts",
    "content": "rootProject.name = \"buildSrc\"\n\ndependencyResolutionManagement {\n    versionCatalogs {\n        create(\"libs\") {\n            from(files(\"../gradle/libs.versions.toml\"))\n        }\n    }\n}"
  },
  {
    "path": "buildSrc/src/main/kotlin/Extensions.kt",
    "content": "import org.gradle.accessors.dm.LibrariesForLibs\nimport org.gradle.api.Project\n\nconst val JAVA_VERSION = 25\nval BUILD_NUMBER: String? = System.getenv(\"BUILD_NUMBER\")\n\nval Project.libs\n    get() = rootProject.extensions.getByName(\"libs\") as LibrariesForLibs\n\nval LATEST_VERSION = listOf(\n    \"26.1\",\n    \"26.1.1\",\n    \"26.1.2\"\n)\n\nval SUPPORTED_VERSIONS = buildList {\n    addAll(listOf(\n        \"1.21.4\",\n        \"1.21.5\",\n        \"1.21.6\",\n        \"1.21.7\",\n        \"1.21.8\",\n        \"1.21.9\",\n        \"1.21.10\",\n        \"1.21.11\",\n    ))\n    addAll(LATEST_VERSION)\n}\n\nval BUKKIT_LOADERS = listOf(\"spigot\")\nval PAPER_LOADERS = listOf(\"paper\", \"purpur\", \"folia\")\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/bukkit-conventions.gradle.kts",
    "content": "plugins {\n    id(\"standard-conventions\")\n}\n\nval minecraft = property(\"minecraft_version\").toString()\n\ndependencies {\n    compileOnly(\"io.papermc.paper:paper-api:$minecraft.build.+\")\n    testImplementation(\"io.papermc.paper:paper-api:$minecraft.build.+\")\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/modrinth-conventions.gradle.kts",
    "content": "plugins {\n    id(\"com.modrinth.minotaur\")\n}\n\nval versionString = version.toString()\nval classifier = project.name\n    .substringAfterLast('-')\n    .replaceFirstChar { it.uppercase() }\n\nmodrinth {\n    token = System.getenv(\"MODRINTH_API_TOKEN\")\n    projectId = \"bettermodel\"\n    syncBodyFrom = rootProject.file(\"BANNER.md\").readText()\n    val log = System.getenv(\"COMMIT_MESSAGE\")\n    if (log != null) {\n        versionType = \"beta\"\n        changelog = log\n    } else {\n        versionType = \"release\"\n        changelog = rootProject.file(\"changelog/$versionString.md\").readText()\n    }\n    additionalFiles {\n        javadocJar(rootProject.layout.buildDirectory.file(\"libs/${rootProject.name}-$versionString-javadoc.jar\"))\n    }\n    versionNumber = versionString\n    versionName = \"BetterModel $versionString for $classifier\"\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/paperweight-conventions.gradle.kts",
    "content": "plugins {\n    id(\"standard-conventions\")\n    id(\"io.papermc.paperweight.userdev\")\n}\n\ndependencies {\n    compileOnly(project(\":bettermodel-api\"))\n    compileOnly(project(\":bettermodel-api:bettermodel-bukkit-api\"))\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/plugin-conventions.gradle.kts",
    "content": "plugins {\n    id(\"bukkit-conventions\")\n    id(\"modrinth-conventions\")\n    id(\"com.gradleup.shadow\")\n}\n\nval shade: Configuration = configurations.getByName(\"shade\")\nval versionString = version.toString()\nval groupString = group.toString()\nval classifier: String = project.name.substringAfterLast('-')\n\ndependencies {\n    compileOnly(project(\":bettermodel-api\"))\n    compileOnly(project(\":bettermodel-api:bettermodel-bukkit-api\"))\n    compileOnly(project(\":bettermodel-core\"))\n    shade(project(\":bettermodel-core:bettermodel-bukkit-core\")) {\n        exclude(\"org.jetbrains.kotlin\")\n    }\n}\n\ntasks {\n    jar {\n        finalizedBy(shadowJar)\n    }\n    shadowJar {\n        configurations.set(listOf(shade))\n        manifest {\n            attributes(mapOf(\n                \"Dev-Build\" to (BUILD_NUMBER ?: -1),\n                \"Version\" to versionString,\n                \"Author\" to \"toxicity188\",\n                \"Url\" to \"https://github.com/toxicity188/BetterModel\",\n                \"Created-By\" to \"Gradle $gradle\",\n                \"Build-Jdk\" to \"${System.getProperty(\"java.vendor\")} ${System.getProperty(\"java.version\")}\",\n                \"Build-OS\" to \"${System.getProperty(\"os.arch\")} ${System.getProperty(\"os.name\")}\"\n            ) + libs.bundles.manifestLibrary.get().associate {\n                \"library-${it.name}\" to it.version\n            })\n        }\n        archiveBaseName = rootProject.name\n        archiveClassifier = classifier\n        destinationDirectory = rootProject.layout.buildDirectory.dir(\"libs\")\n        dependencies {\n            exclude(dependency(\"org.jetbrains:annotations:26.0.2\"))\n        }\n        fun prefix(pattern: String) {\n            relocate(pattern, \"$groupString.shaded.$pattern\")\n        }\n        prefix(\"kotlin\")\n        prefix(\"kr.toxicity.library.armormodel\")\n        prefix(\"org.incendo.cloud\")\n        prefix(\"org.bstats\")\n        prefix(\"net.byteflux.libby\")\n    }\n}\n\nmodrinth {\n    uploadFile.set(tasks.shadowJar)\n    dependencies {\n        optional.project(\n            \"mythicmobs\",\n            \"skinsrestorer\"\n        )\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/publish-conventions.gradle.kts",
    "content": "import com.vanniktech.maven.publish.JavaLibrary\nimport com.vanniktech.maven.publish.JavadocJar\nimport com.vanniktech.maven.publish.SourcesJar\nimport kotlin.io.encoding.Base64\n\nplugins {\n    id(\"standard-conventions\")\n    id(\"com.vanniktech.maven.publish\")\n    signing\n}\n\nval artifactBaseId = name\nval artifactVersion = project.version.toString().run {\n    BUILD_NUMBER?.let { substringBeforeLast(\"-$it\") } ?: this\n}\n\nsigning {\n    val key = System.getenv(\"SIGNING_KEY\")?.let {\n        Base64.decode(it.toByteArray()).toString(Charsets.UTF_8)\n    }\n    val password = System.getenv(\"SIGNING_PASSWORD\")\n    if (!key.isNullOrEmpty() && !password.isNullOrEmpty()) {\n        useInMemoryPgpKeys(\n            key,\n            password\n        )\n    } else useGpgCmd()\n}\n\ndependencies {\n    api(libs.bundles.library)\n\n    compileOnly(libs.lombok)\n    annotationProcessor(libs.lombok)\n\n    testCompileOnly(libs.lombok)\n    testAnnotationProcessor(libs.lombok)\n}\n\nmavenPublishing {\n    publishToMavenCentral()\n    signAllPublications()\n    coordinates(\"io.github.toxicity188\", artifactBaseId, artifactVersion)\n    configure(JavaLibrary(\n        javadocJar = JavadocJar.Javadoc(),\n        sourcesJar = SourcesJar.Sources(),\n    ))\n    pom {\n        name = artifactBaseId\n        description = \"Modern Bedrock model engine for Minecraft Java Edition\"\n        inceptionYear = \"2024\"\n        url = \"https://github.com/toxicity188/BetterModel/\"\n        licenses {\n            license {\n                name = \"MIT License\"\n                url = \"https://mit-license.org/\"\n            }\n        }\n        developers {\n            developer {\n                id = \"toxicity188\"\n                name = \"toxicity188\"\n                url = \"https://github.com/toxicity188/\"\n            }\n        }\n        scm {\n            url = \"https://github.com/toxicity188/BetterModel/\"\n            connection = \"scm:git:git://github.com/toxicity188/BetterModel.git\"\n            developerConnection = \"scm:git:ssh://git@github.com/toxicity188/BetterModel.git\"\n        }\n    }\n}\n\npublishing {\n    repositories {\n        maven {\n            name = \"GitHubPackages\"\n            url = uri(\"https://maven.pkg.github.com/toxicity188/${rootProject.name}\")\n            credentials {\n                username = \"toxicity188\"\n                password = System.getenv(\"PACKAGES_API_TOKEN\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/standard-conventions.gradle.kts",
    "content": "plugins {\n    java\n    kotlin(\"jvm\")\n    id(\"org.jetbrains.dokka\")\n    id(\"dev.yumi.gradle.licenser\")\n}\n\ngroup = \"kr.toxicity.model\"\nversion = property(\"project_version\").toString() + (BUILD_NUMBER?.let { \"-SNAPSHOT-$it\" } ?: \"\")\n\nval shade = configurations.create(\"shade\")\n\nconfigurations.implementation {\n    extendsFrom(shade)\n}\n\nrootProject.dependencies.dokka(project)\n\ndependencies {\n    testImplementation(kotlin(\"test\"))\n\n    compileOnly(libs.bundles.library)\n    testImplementation(libs.bundles.library)\n}\n\ntasks {\n    test {\n        useJUnitPlatform()\n    }\n    compileJava {\n        options.encoding = Charsets.UTF_8.name()\n    }\n}\n\nlicense {\n    rule(rootProject.file(\"LICENSE_HEADER\"))\n    include(\"**/*.java\", \"**/*.kt\")\n    exclude(\"**/*.properties\")\n}\n\njava {\n    disableAutoTargetJvm()\n    toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)\n}\n\nkotlin {\n    jvmToolchain(JAVA_VERSION)\n}\n\ndokka {\n    moduleName = project.name\n    dokkaSourceSets.configureEach {\n        displayName = project.name\n    }\n}\n"
  },
  {
    "path": "changelog/3.0.1.md",
    "content": "## 🔧 Fixes\n- fix: PriorityMap\n\n## 🧹 Chores\n- chore: update net.fabricmc:fabric-loader to 0.19.2\n- chore: update net.fabricmc.fabric-loom-repositories to 1.16-SNAPSHOT\n- chore: update Skript to v2.15.0 for test server\n- chore: update fabric-api to v0.146.1+26.1.2\n- chore: update eu.pb4:polymer-resource-pack to v0.16.3+26.1.2\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/3.0.0...3.0.1)\n"
  },
  {
    "path": "changelog/3.0.2.md",
    "content": "[Full change log](https://github.com/toxicity188/BetterModel/compare/3.0.1...3.0.2)\n"
  },
  {
    "path": "changelog/v1/1.10.0.md",
    "content": "# BetterModel 1.10.0\n\n### Add\n- new entity rotation API\n- 'bodyrotation' MythicMobs mechanic\n\n### Fix\n- handle equipment change of player\n- entity invisibility\n- traffic optimization\n- animation accuracy\n- parallel animation packet bundling\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.9.3...1.10.0)"
  },
  {
    "path": "changelog/v1/1.10.1.md",
    "content": "# BetterModel 1.10.1\n\n### Notice\nNow support about 1.20.4 is dropped.\n\n### Add\n- passengers API in EntityTrackerRegistry\n\n### Fix\n- entity body and head rotation\n- MythicMobs mechanics\n- hitbox rendering\n- inventory handling\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.10.0...1.10.1)"
  },
  {
    "path": "changelog/v1/1.10.2.md",
    "content": "# BetterModel 1.10.2\n\n### Fix\n- optimize animation interpolation and keyframe\n- an attribute packet about hitbox\n- Folia compatibility\n- death animation\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.10.1...1.10.2)"
  },
  {
    "path": "changelog/v1/1.10.3.md",
    "content": "# BetterModel 1.10.3\n\n### Add\n- initial molang support\n- expose id and uuid of item-display\n\n### Fix\n- equipment in the left hand\n- animation signal\n- packet bundler\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.10.2...1.10.3)"
  },
  {
    "path": "changelog/v1/1.11.0.md",
    "content": "# BetterModel 1.11.0\n\n### Add\n- API for [per-player animation](https://github.com/toxicity188/BetterModel/wiki/Per%E2%80%90player-animation)\n- rewrite animation packet packer\n- 'remapmodel' MythicMobs mechanic\n- [custom nametag](https://github.com/toxicity188/BetterModel/wiki/Configuring-bone-tag)\n\n### Fix\n- [molang support](https://github.com/toxicity188/BetterModel/wiki/Math-animation)\n- exception with some bone\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.10.3...1.11.0)\n* * *\n![](https://github.com/user-attachments/assets/39e72652-d484-48a6-85d0-47e18d382275)\n* * *"
  },
  {
    "path": "changelog/v1/1.11.1.md",
    "content": "# BetterModel 1.11.1\n\n## Notice\nNow API repository is moved.\n#### Release\n```kotlin\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel:VERSION\")\n}\n```\n#### Snapshot\n```kotlin\nrepositories {\n    maven(\"https://maven.pkg.github.com/toxicity188/BetterModel\")\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel:VERSION-SNAPSHOT\")\n}\n```\n\n## Add\n- support [shadow bone](https://github.com/toxicity188/BetterModel/wiki/Configuring-bone-tag#custom-shadow) to sync ModelEngine's usage\n\n## Fix\n- math animation interpolation\n- ModelRenderer#create(Entity, GameProfile, boolean)\n- player nametag\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.11.0...1.11.1)"
  },
  {
    "path": "changelog/v1/1.11.2.md",
    "content": "# BetterModel 1.11.2\n\n## Add\n- Improved reload performance\n- Reload indicator bar\n\n## Change\n- Kotlin 2.2.10\n- player's left/right item bone tag (pli, pri) -> entity's left/right item bone tag (li, ri)\n- ModelRenderer#create(Location, Player) -> ModelRenderer#create(Location, OfflinePlayer)\n\n## Fix\n- MythicMobs mechanics\n- Keyframe generation\n- Interpolation duration\n- Hiding player's armor when disguised\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.11.1...1.11.2)"
  },
  {
    "path": "changelog/v1/1.11.3.md",
    "content": "# BetterModel 1.11.3\n\n## Notice\n- It will be helpful to check 'template texture' docs to ignore resource pack generation.\n- There are some deprecated API in ModelManager and PlayerManager.\n\n## Add\n- allow textures in BetterModel/players with [template texture](https://github.com/toxicity188/BetterModel/wiki/Template-texture)\n- add player/monster nametag (ptag_, mtag_)\n- ModelRenderer API\n- more BetterModel API\n- improved reload speed\n```\nBetterModel.modelOrNull(\"name\"); //Gets nullable model renderer\nBetterModel.model(\"name\"); //Gets optional model renderer\n```\n\n## Fix\n- avoid classloader conflict of Adventure in Paper.\n- tracker remove refreshment\n- minor optimized memory cost\n- animation script\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.11.2...1.11.3)"
  },
  {
    "path": "changelog/v1/1.11.4.md",
    "content": "# BetterModel 1.11.4\n\n## Add\n- refresh plugin jar\n- use caffeine\n- add hide argument in /bm play (#134)\n- lerp-frame-time 5 -> 3\n- zip hash\n- Auto-merge with Nexo\n\n## Fix\n- animation interpolation\n- plugin lifecycle\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.11.3...1.11.4)"
  },
  {
    "path": "changelog/v1/1.12.0.md",
    "content": "# BetterModel 1.12.0\n\n## Add\n- billboard MythicMobs mechanic\n- 5 of [animation script](https://github.com/toxicity188/BetterModel/wiki/Animation-Script)\n- TrackerUpdateAction#composite\n- ModelEngine blueprints migration\n```\nchangepart\npartvis\nenchant\nremap\ntint\n```\n\n## Change\n- Kotlin 2.2.20\n\n## Fix\n- SkinsRestorer compatibility\n- mount hitbox cache\n- hitbox exception\n- minor keyframe optimization\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.11.4...1.12.0)"
  },
  {
    "path": "changelog/v1/1.12.1.md",
    "content": "# BetterModel 1.12.1\n\n## Notice\nThis is a simple bug fix version for preparing 1.21.9 update.\n\n## Add\n- More comfortable help command (/bm)\n- API update related to 1.21.9\n\n## Fix\n- Citizens trait\n- NPE in animation script\n- Boot issue with some version like 1.21.3\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.12.0...1.12.1)"
  },
  {
    "path": "changelog/v1/1.13.0.md",
    "content": "# BetterModel 1.13.0\n\n## Add\n- 1.21.9 support\n- component logging\n\n## Change\n- optimize state handler\n\n## Fix\n- animation script\n- 'hold on last' animation type\n- model resolution\n- step interpolation\n- exception handling on tick\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.12.1...1.13.0)"
  },
  {
    "path": "changelog/v1/1.13.1.md",
    "content": "# BetterModel 1.13.1\n\n## Feat\n- official 1.21.10 support\n- use paper-plugin.yml in Paper platform\n- make CommandAPI as runtime\n- 'pairmodel' mechanics (experimental)\n- 'use-obfuscation' config option\n\n![](https://github.com/user-attachments/assets/5934c271-1397-4b5a-8f58-c5eff46c10d2)\n\n## Fix\n- ignore group name's case\n- add minimum keyframe time\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.13.0...1.13.1)"
  },
  {
    "path": "changelog/v1/1.13.2.md",
    "content": "# BetterModel 1.13.2\n\n## Notice\n- This is a hotfix of 1.13.1\n\n## Feat\n- support obfuscation in player limb\n\n## Fix\n- properly supporting minecraft 1.21.10\n- plugin loading issue with some legacy Paper version\n- some model's loading issue\n- invalid hitbox bounding box\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.13.1...1.13.2)"
  },
  {
    "path": "changelog/v1/1.13.3.md",
    "content": "# BetterModel 1.13.3\n\n## Notice\nThis is a version for avoid confusion with BlockBench 5\n\n## Feat\n- initial BlockBench 5 support\n- remove blueprint reference from renderer for gc\n- reuse IO buffer\n\n## Fix\n- Tracker#show\n- animation replacement (MythicMobs mechanic 'defaultstate')\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.13.2...1.13.3)"
  },
  {
    "path": "changelog/v1/1.13.4.md",
    "content": "# BetterModel 1.13.4\n\n## Feat\n- cape support in player limb (bone tag 'cape_')\n- 'enable-strict-loading' config\n- Kotlin 2.2.21\n\n## Fix\n- UV size\n- 'shadow' bone tag\n- exception in animation script\n- position convert in BlockBench 5.0\n- 'false' texture loading error\n- filter dummy data in model file\n- animation sync of each model part\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.13.3...1.13.4)"
  },
  {
    "path": "changelog/v1/1.14.0.md",
    "content": "# ⚡ BetterModel 1.14.0\n\n## 📗 Notice\nThis update introduces significant API changes to the entity adapter.  \nPlease verify the compatibility of all plugins that depend on BetterModel.\n\n## 🔥 Feat\n### Armor in player model (experimental)\n![](https://github.com/user-attachments/assets/abc277b3-b811-49e7-92e6-9847d73f39db)\n* * *\nThe player model now supports vanilla armor.  \nArmor textures can be customized in the BetterModel/armors directory.\n\n### Global texture (global_)\n![](https://github.com/user-attachments/assets/a16e066a-3f4c-46eb-a569-ed58489dc2e7)\n* * *\nAny texture prefixed with `global_` is recognized as a global texture.  \nThese textures have no namespace and can be shared between multiple models, avoiding duplicate texture creation.\n\n### Other features\n- remove image process\n- 'loop_mode' argument in /npc animate\n\n## 🔧 Fix\n- move duration\n- parsing bone tag\n- NoSuchElementException in ModelAnimation\n\n## 🧹 Chores\n- deps: update dependency com.github.ben-manes.caffeine:caffeine to v3.2.3\n- deps: update dependency io.github.toxicity188:dynamicuv to v1.1.0\n- deps: update gradle to v9.2.0\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.13.4...1.14.0)"
  },
  {
    "path": "changelog/v1/1.14.1.md",
    "content": "## 🔥 Feat\n### IK rig (experimental)\n![](https://github.com/user-attachments/assets/8eae5c72-9403-44af-8a1d-746b1e94f7d6)  \n![](https://github.com/user-attachments/assets/10d6d5af-7af7-44bc-ad62-fe1396713fa0)  \n* * *\nNow BetterModel supports IK rig. (locator, null object)\n\n### Rotation in global space\n![](https://github.com/user-attachments/assets/61b04973-34fb-410d-ba7a-53936f250568)\n* * *\nNow animator option 'rotation in global space' is available.\n\n### Other features\n- 'brightness' script\n- namespace in MythicMobs script (bm)\n\n## 🔧 Fix\n- default leather armor color\n- animation placeholder\n- exception handling in missing textures\n- stability with SkinsRestorer\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.14.0...1.14.1)"
  },
  {
    "path": "changelog/v1/1.14.2.md",
    "content": "## 🔥 Feat\n- more accurate ik rig\n- support client skin customisation for player limb (cape)\n\n## 🔧 Fix\n- RenderedBone#worldPosition ('ModelPart' MythicMobs targeter)\n- 'camera' element type\n- 'bm test' command in Spigot platform\n- step interpolation\n- bezier interpolation\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.14.1...1.14.2)"
  },
  {
    "path": "changelog/v1/1.15.0.md",
    "content": "## 📗 Notice\nDue to Mojang’s [new version numbering system](https://www.minecraft.net/en-us/article/minecraft-new-version-numbering-system) (e.g., 1.21.x → 26.x), the next BetterModel feature release will begin at `2.0.0` to avoid confusion with Minecraft versions.  \nThis also marks the completion of the BetterModel 1.x series, as its core APIs and features have now reached a stable, finalized state.\n\nSeveral technical questions are expected to emerge in the near future, and they may influence the evolution of BetterModel 2.0.0:\n\n- Will Mojang upgrade Minecraft’s Java requirement to [version 25](https://openjdk.org/projects/jdk/25/)?\n- Will Spigot platform compatibility be phased out based on [future policy decisions by the Paper team](https://github.com/PaperMC/Paper/pull/13135#issuecomment-3368037505)?\n- How will NMS be organized under Mojang’s new version numbering system?\n\n## 🔥 Feat\n![](https://github.com/user-attachments/assets/17aa49c1-abf9-4796-a369-9bb7600e94ff)  \n- [Minecraft 1.21.11](https://minecraft.wiki/w/Java_Edition_1.21.11) support\n- new profile API to handle uncompleted profile\n```java\n// ModelProfile#of\n// Can be offline player, uuid\nBetterModel.model(\"model\").ifPresent(model -> model.create(location, ModelProfile.of(player)));\n```\n- [CommandAPI](https://github.com/CommandAPI/CommandAPI) is no longer used. Now [cloud](https://github.com/Incendo/cloud) is used to implement commands.\n- parallel json build\n- optimized IK\n- optimized texture load\n- BonePredicate builder\n```java\n//Or chain\nBonePredicate.name(\"hitbox\")\n    .or(BonePredicate.tag(BoneTags.HITBOX))\n    .or(b -> b.getGroup().getMountController().canMount())\n    .notSet();\n\n//Apply with children\nBonePredicate.tag(BoneTags.HEAD_WITH_CHILDREN).withChildren();\n```\n- added 'loop_type' to '/bm limb' command\n- added 'scaling' to 'bm disguise' command\n\n## ⚡ Refactor\n- ModelChildren -> ModelOutliner\n- BaseEntity\n- Float3 and Float4\n- ModelBlueprint\n\n## 🔧 Fix\n- Tracker#hide on init\n- resource pack load error\n- Citizens trait\n- UV issue regarding right forearm\n\n## 🧹 Chores\n- fix(deps): update dependency io.lumine:mythic-dist to v5.11.1\n- fix(deps): update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.3.0\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.14.2...1.15.0)\n"
  },
  {
    "path": "changelog/v1/1.15.1.md",
    "content": "## 🔥 Feat\n- feat: per-player animation in MythicMobs state mechanic\n- feat: TrackerUpdateAction#perBone\n- feat: optimize memory usage\n\n## 🔧 Fix\n- fix: model disappearance when plugin is reloaded\n- fix: mount controller strafing\n- fix: Kotlin 2.3.0 compatibility\n\n## ⚡ Refactor\n- refactor: ModelManagerImpl.kt\n\n## 🧹 Chores\n- chore: update Kotlin to 2.3.0\n- chore: update citizens-main to 2.0.41-SNAPSHOT\n- chore: update cloud-paper to 2.0.0-beta.14\n- fix(deps): update dependency net.kyori:adventure-api to v4.26.1\n- fix(deps): update dependency com.nexomc:nexo to v1.16.1\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.15.0...1.15.1)\n"
  },
  {
    "path": "changelog/v1/1.15.2.md",
    "content": "## 🔥 Feat\n- feat: add frame time and frame interpolate in texture\n\n## 🔧 Fix\n- fix: skin cache expiration\n- fix: disable EntitiesLoadEvent listener (#239)\n- fix: equality check with v6 authlib (#238)\n- fix: optimize memory allocation\n- fix: invalid type inference of compiler (#245)\n- fix: empty data save\n\n## ⚡ Refactor\n- refactor: simplify and rename some code\n- refactor: clear unnecessary hitbox code\n\n## 🧹 Chores\n- chore: update license to 2026\n- fix(deps): update dependency net.skinsrestorer:skinsrestorer-api to v15.9.2\n- fix(deps): update dependency com.nexomc:nexo to v1.17.0\n- fix(deps): update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.3.1\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.15.1...1.15.2)\n"
  },
  {
    "path": "changelog/v1/1.3.2.md",
    "content": "# BetterModel 1.3.2\n\n### Fix\n- Fix scale calculation\n- Fix player limb\n- Expand sight degree\n\n### Add\n- Add 'disable-generating-legacy-models' config\n- Add more API"
  },
  {
    "path": "changelog/v1/1.3.3.md",
    "content": "# BetterModel 1.3.3\n\n### Fix\n- Fix texture rendering issue.\n- Fix hitbox removing.\n- Adds help command description."
  },
  {
    "path": "changelog/v1/1.4.1.md",
    "content": "# BetterModel 1.4.1\n\n### Fix\n- Improve sight trace\n- Improve command\n- Improve walk speed calculation\n- Force resources name to lower case\n- Fix Minecraft <=1.21.3 resource\n- Fix entity remove/hide\n\n### Add\n- Add animation script\n- Add .mcmeta animation\n- Add 'follow-mob-invisibility' option"
  },
  {
    "path": "changelog/v1/1.4.2.md",
    "content": "# BetterModel 1.4.2\n\n### Fix\n- Fix interpolation\n- Fix tick frame\n- Fix entity scale attribute\n- Fix hitbox logic to match Minecraft vanilla\n- Implement more accurate movement and animation\n- Hide player head in first person camera\n\n### Add\n- Add 'module' config\n- Support 'smooth' interpolation\n- Add 'damage-effect(de)' parameter in model mechanic(boolean)\n\n![1](https://github.com/user-attachments/assets/9b7d596a-db1a-41c7-8693-56342f0c0045)  \n![2](https://github.com/user-attachments/assets/128a3508-b8a0-4154-b59e-e1487a8804af)  "
  },
  {
    "path": "changelog/v1/1.4.3.md",
    "content": "# BetterModel 1.4.3\n\n### Add\n- 1.21.5 client, server support\n- Support Purpur AFK\n- World position API\n- More optimization\n\n### Fix\n- More smooth animation\n- Fake player like Citizens\n- Hit box\n- Animated texture\n- World change\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.4.2...1.4.3)"
  },
  {
    "path": "changelog/v1/1.4.md",
    "content": "# BetterModel 1.4\n\n### Add\n- Add texture path validator\n- Add @ModelPart targeter (MythicMobs)\n- Add changepart mechanic (MythicMobs)\n- Add debug config\n- Free cube rotation (>=1.21.4)\n- Kotlin 2.1.10\n- Sync invisibility and glowing to tracker entity\n\n![1](https://github.com/user-attachments/assets/aceda548-1b3f-4ed9-89ff-39ec68cfbefd)  \n![2](https://github.com/user-attachments/assets/a70f5049-77b0-4f47-b2ef-7bbd6896eb11)\n"
  },
  {
    "path": "changelog/v1/1.5.1.md",
    "content": "# BetterModel 1.5.1\n\n### Notice\n- This version is a hotfix of BetterModel 1.5.\n\n### Add\n- config 'version-check'\n\n### Fix\n- Spigot support\n- loading issue with legacy server\n- incorrect world handling\n\n### Change\n- The default pack builder is now 'zip'\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.5...1.5.1)"
  },
  {
    "path": "changelog/v1/1.5.2.md",
    "content": "# BetterModel 1.5.2\n\n### Add\n- 'bindhitbox' mechanic\n- 'mountmodel' mechanic\n- 'dismountmodel' mechanic\n- 'dismountallmodel' mechanic\n- use interaction entity\n- 'override' property in animation\n- 'loop mode' property in animation\n- 'smooth' interpolation\n\n### Fix\n- make hitbox listener entity to bukkit entity.\n- optimize animation update\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.5.1...1.5.2)"
  },
  {
    "path": "changelog/v1/1.5.3.md",
    "content": "# BetterModel 1.5.3\n\n### Fix\n- Bukkit hitbox event\n- update checker\n- optimize packet listener\n\n### Add\n- multi tag support\n- new player limb tag and API\n```\nhead (ph)\nright arm (pra)\nright forearm (prfa)\nleft arm (pla)\nleft forearm (plfa)\nhip (phip)\nwaist (pw)\nchest (pc)\nright leg (prl)\nright foreleg (prfl)\nleft leg (pll)\nleft foreleg (plfl)\nleft item (pli)\nright item (pri)\n```\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.5.2...1.5.3)"
  },
  {
    "path": "changelog/v1/1.5.4.md",
    "content": "# BetterModel 1.5.4\n\n### Notice\nthis is a hotfix of 1.5.3.\n\n### Fix\n- optimize packet listener and packet data update\n- checking entity dead\n- fix, clean and optimize animation logic\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.5.3...1.5.4)"
  },
  {
    "path": "changelog/v1/1.5.5.md",
    "content": "# BetterModel 1.5.5\n\n### Notice\n- Support about 1.20.2 is dropped.\n\n### Fix\n- tracker view filter\n- legacy version support of player animation\n- improved animation key frame\n- hit-box despawn\n- call PlayerInteractAtEntityEvent of base entity\n- optimize resource pack\n- improved command\n- improved resource pack speed, size, and logging\n- legacy version test\n\n### Add\n- HMCCosmetics backpack support (bone tag: hmc_bp)\n- Default model for test and learn (demon_knight.bbmodel)\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.5.4...1.5.5)"
  },
  {
    "path": "changelog/v1/1.5.md",
    "content": "# BetterModel 1.5\n\n### Add\n- 'glow' and 'enchant' mechanic\n- update checker\n- more API\n\n### Fix\n- entity scale sync\n- entity shadow\n- some compatibility issues with MythicMobs\n- some platform support\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/c70a86c0...1.5)"
  },
  {
    "path": "changelog/v1/1.6.0.md",
    "content": "# BetterModel 1.6.0\n\n### Feat\n- Tracker#hide and Tracker#show API\n- TrackerModifier#hideOption\n- Now player animation is compatible with Shaderspack (requires 1.21.4 or upper client and server)\n\n### Fix\n- Fix hit-box collision and interaction with 1.21.5\n- Tinted item cache\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.5.5...1.6.0)\n\n![0](https://github.com/user-attachments/assets/24a1e4ca-a2da-481e-ba86-14c4cc18fc3c)"
  },
  {
    "path": "changelog/v1/1.6.1.md",
    "content": "# BetterModel 1.6.1\n\n### Fix\n- Optimized vector calculation\n- Head rotation tag (h, hi)\n- Hitbox size and position\n- Entity scale attribute\n- Player animation model size\n- Animation frame quality\n\n### Change\n- Simplify resource pack generation\n- MythicMobs 5.9.0 compatibility (bindhitbox)\n\n### Add\n- Skin event API\n \n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.6.0...1.6.1)"
  },
  {
    "path": "changelog/v1/1.7.0.md",
    "content": "# BetterModel 1.7.0\n\n### Notice\n- This update has HUGE API change and refactor, You should check all of your system related to BetterModel.\n\n### Add\n- 1.21.6 server support\n- HUGE API refactor, change and update.\n- entity model data serialization\n- bezier, step interpolation\n- optimize bone item build\n- tint for player animation\n- offline-mode skin loading\n- support multiple models per one entity\n- minor optimization\n- prevent sending an empty packet\n- new player animation example (roll)\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.6.1...1.7.0)"
  },
  {
    "path": "changelog/v1/1.8.0.md",
    "content": "# BetterModel 1.8.0\n\n### Add\n- Minecraft 1.21.7 server support\n- Write more Javadocs\n- Thread lifecycle based on viewed player\n- Entity id tracking\n- More accurate spawn handling\n- Refactor and optimize some API\n- Head rotation delay\n\n### Fix\n- 'smooth interpolation' with low keyframe animation\n- Unnecessary tick thread task allocation\n- Animation with only a single keyframe.\n- MythicMobs mechanic\n- Packet handling issue with 1.20.4\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.7.0...1.8.0)"
  },
  {
    "path": "changelog/v1/1.8.1.md",
    "content": "# BetterModel 1.8.1\n\n### Notice\nThis is a simple hotfix for optimization and compatibility.\n\n### Fix\n- huge packet traffic optimization\n- optimize packet handler with my external plugin (BetterHealthBar, BetterDamage)\n- compatibility with Folia\n- 'lockmodel' mechanic"
  },
  {
    "path": "changelog/v1/1.9.0.md",
    "content": "# BetterModel 1.9.0\n\n## Add\n- API BetterModelConfig\n- API EntityHideOption\n- new pack API and more detailed resource pack generation\n```yaml\npack:\n  generate-modern-model: true\n  generate-legacy-model: true\n```\n\n### Fix\n- minor packet optimization\n- entity despawn handling\n- animation script\n- equipment packet\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.8.1...1.9.0)"
  },
  {
    "path": "changelog/v1/1.9.1.md",
    "content": "# BetterModel 1.9.1\n\n### Notice\nThis is a hotfix about version 1.9.0.\n\n### Add\n- API Tracker#billboard\n\n### Fix\n- item model usage in 1.21.2-1.21.3\n- unnecessary condition check\n- entity data serialization\n- hide option resetting\n- equipment hiding\n- entity spawning issue when quit and rejoin\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.9.0...1.9.1)"
  },
  {
    "path": "changelog/v1/1.9.2.md",
    "content": "# BetterModel 1.9.2\n\n### Notice\nIt may be simple but huge update for optimization and animation quality.\n\n### Fix\n- registry creation when spawning\n- model spawn issue with teleport\n- entity tracker registry optimization\n- large traffic optimization\n- implements more accurate animation\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.9.1...1.9.2)"
  },
  {
    "path": "changelog/v1/1.9.3.md",
    "content": "# BetterModel 1.9.3\n\n### Add\n- 1.21.8 server support\n- New API for updating tracker (Tracker#update)\n\n### Fix\n- entity yaw/pitch sync\n- data packet synchronization\n- improved animation accuracy\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.9.2...1.9.3)"
  },
  {
    "path": "changelog/v2/2.0.0-pre1.md",
    "content": "## 📚 Notices\n\n*This is a pre-release version distributed to assist in migrating to the new API.*  \n*Content in this document is subject to change before the official 2.0.0 release.*\n\nStarting with this version, BetterModel is officially designated as v2.0.0.  \nPlease be aware that this update introduces several Breaking Changes.\n\n## ✨ Feats\n### Fabric platform port\nBetterModel now supports the Fabric platform as a server-side mod.\n\n- Supported: Dedicated Server, Integrated Server (Singleplayer)\n- Unsupported: Hybrid servers (e.g., Arclight, Mohist)\n\n## 🚀 Breaking Changes\n### Publishing & Dependencies\nThe legacy `io.github.toxicity188:bettermodel` package is no longer published.\n\nStarting with v2.0.0, the project is split into the following modules:\n- `bettermodel-api`: Common API module.\n- `bettermodel-core`: Internal core module (Internal use only).\n- `bettermodel-bukkit-api`: API for Bukkit-based environments (Spigot, Paper, etc.).\n- `bettermodel-fabric`: Server-side mod implementation for Fabric.\n\nPlease refer to `README.md` for detailed build script instructions.\n\n<details>\n<summary>Gradle (Kotlin)</summary>\n\n#### Release\n```kotlin\nrepositories {\n    mavenCentral()\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION\") // bukkit(spigot, paper, etc) api\n    //modApi(\"io.github.toxicity188:bettermodel-fabric:VERSION\") // mod(fabric)\n}\n```\n\n#### Snapshot\n```kotlin\nrepositories {\n    maven(\"https://maven.pkg.github.com/toxicity188/BetterModel\") {\n        credentials {\n            username = YOUR_GITHUB_USERNAME\n            password = YOUR_GITHUB_TOKEN\n        }\n    }\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION-SNAPSHOT\") // bukkit(spigot, paper, etc) api\n    //modApi(\"io.github.toxicity188:bettermodel-fabric:VERSION-SNAPSHOT\") // mod(fabric)\n}\n```\n</details>\n\n### API & Event System\n- **Platform Adapters**: You must now use the appropriate adapter for your platform (e.g., `BukkitAdapter`, `FabricAdapter`).\n- **Event Bus**: `BetterModelEventBus` (accessible via BetterModel#eventBus) has been introduced. Bukkit's Event API is no longer supported.\n\n<details open>\n<summary>Adapter API Usage (Bukkit)</summary>\n\n```java\nEntityTracker tracker = BetterModel.model(\"demon_knight\")\n    .map(r -> r.create(BukkitAdapter.adapt(entity), TrackerModifier.DEFAULT, t -> t.update(TrackerUpdateAction.tint(0x0026FF))))\n    .orElse(null);\n```\n</details>\n\n<details>\n<summary>Event API Usage (Bukkit)</summary>\n\n```java\nimport org.bukkit.plugin.java.JavaPlugin;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.data.ModelAsset;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport kr.toxicity.model.api.event.ModelAssetsEvent;\n\nimport java.util.*;\n\npublic class YourPlugin extends JavaPlugin {\n    @Override\n    public void onEnable() {\n        BetterModel.eventBus().subscribe(ModelAssetsEvent.class, event -> {\n            if (event.type() == ModelRenderer.Type.PLAYER) event.addAsset(ModelAsset.of(\n                \"knight\",\n                () -> Objects.requireNonNull(getResource(\"knight.bbmodel\"))\n            ));\n        });\n    }\n}\n```\n</details>\n\n### Bukkit Compatibility\nTo optimize the project, support for the following versions (which showed low usage) has been removed:\n- 1.20.5 ~ 1.20.6\n- 1.21.2 ~ 1.21.3\n\n## 🧹 Chores\n- chore(deps): update gradle to v9.3.0\n- chore(deps): update plugin com.vanniktech.maven.publish to v0.36.0\n- chore: update dependency io.github.toxicity188:armormodel to v1.0.2\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.15.2...2.0.0-pre1)\n"
  },
  {
    "path": "changelog/v2/2.0.0-pre2.md",
    "content": "## 📚 Notices\n\n*This is a pre-release version distributed to assist in migrating to the new API.*  \n*Content in this document is subject to change before the official 2.0.0 release.*\n\nStarting with this version, BetterModel is officially designated as v2.0.0.  \nPlease be aware that this update introduces several Breaking Changes.\n\n## ✨ Feats\n### Fabric platform port\nBetterModel now supports the Fabric platform as a server-side mod.\n\n- Supported: Dedicated Server, Integrated Server (Singleplayer)\n- Unsupported: Hybrid servers (e.g., Arclight, Mohist)\n\n### Keyframe optimization\n`AnimationMovement` has been deprecated and replaced by the highly optimized `AnimationKeyframe`.  \nThis change introduces a more efficient data access structure, significantly improving animation processing performance.\n\n### ModelAssetsEvent\nThe new `ModelAssetsEvent` allows you to inject BlockBench models from external resources directly into the BetterModel reload pipeline.  \nThis enables seamless integration of custom assets within the engine's internal build process.\n\n## 🚀 Breaking Changes\n### Publishing & Dependencies\nThe legacy `io.github.toxicity188:bettermodel` package is no longer published.\n\nStarting with v2.0.0, the project is split into the following modules:\n- `bettermodel-api`: Common API module.\n- `bettermodel-core`: Internal core module (Internal use only).\n- `bettermodel-bukkit-api`: API for Bukkit-based environments (Spigot, Paper, etc.).\n- `bettermodel-fabric`: Server-side mod implementation for Fabric.\n\nPlease refer to `README.md` for detailed build script instructions.\n\n<details>\n<summary>Gradle (Kotlin)</summary>\n\n#### Release\n```kotlin\nrepositories {\n    mavenCentral()\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION\") // bukkit(spigot, paper, etc) api\n    //modApi(\"io.github.toxicity188:bettermodel-fabric:VERSION\") // mod(fabric)\n}\n```\n\n#### Snapshot\n```kotlin\nrepositories {\n    maven(\"https://maven.pkg.github.com/toxicity188/BetterModel\") {\n        credentials {\n            username = YOUR_GITHUB_USERNAME\n            password = YOUR_GITHUB_TOKEN\n        }\n    }\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION-SNAPSHOT\") // bukkit(spigot, paper, etc) api\n    //modApi(\"io.github.toxicity188:bettermodel-fabric:VERSION-SNAPSHOT\") // mod(fabric)\n}\n```\n</details>\n\n### API & Event System\n- **Platform Adapters**: You must now use the appropriate adapter for your platform (e.g., `BukkitAdapter`, `FabricAdapter`).\n- **Event Bus**: `BetterModelEventBus` (accessible via BetterModel#eventBus) has been introduced.\n- **New Entrypoints**: Dedicated entrypoints for each platform have been introduced to provide platform-specific functionalities:\n  - `BetterModelBukkit`: Use `BetterModelBukkit#platform()` to access Bukkit-specific APIs.\n  - `BetterModelFabric`: Use `BetterModelFabric#platform()` to access Fabric-specific APIs.\n\n<details open>\n<summary>Adapter API Usage (Bukkit)</summary>\n\n```java\nEntityTracker tracker = BetterModel.model(\"demon_knight\")\n    .map(r -> r.create(BukkitAdapter.adapt(entity), TrackerModifier.DEFAULT, t -> t.update(TrackerUpdateAction.tint(0x0026FF))))\n    .orElse(null);\n```\n</details>\n\n<details>\n<summary>Event API Usage (Bukkit)</summary>\n\n```java\nimport org.bukkit.plugin.java.JavaPlugin;\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit;\nimport kr.toxicity.model.api.data.ModelAsset;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport kr.toxicity.model.api.event.ModelAssetsEvent;\n\nimport java.util.*;\n\npublic class YourPlugin extends JavaPlugin {\n    @Override\n    public void onEnable() {\n        BetterModelBukkit.platform().eventBus().subscribe(this, ModelAssetsEvent.class, event -> {\n            if (event.type() == ModelRenderer.Type.PLAYER) event.addAsset(ModelAsset.of(\n                \"knight\",\n                () -> Objects.requireNonNull(getResource(\"knight.bbmodel\"))\n            ));\n        });\n    }\n}\n```\n</details>\n\n### Bukkit-specific Event Support\nWhile the core Event API is now platform-agnostic, you can still utilize Bukkit's Event API via **`BetterModelBukkitEvent`**.  \nThis ensures seamless integration with the existing Bukkit event lifecycle, which is particularly useful for tools like **Skript** or other plugins that rely on Bukkit-native events.\n\n<details open>\n<summary>Event handling with Bukkit (Skript)</summary>\n\n```\nimport:\n    kr.toxicity.model.api.bukkit.event.BetterModelBukkitEvent\n    kr.toxicity.model.api.event.CreateEntityTrackerEvent\n\non BetterModelBukkitEvent:\n    set {_original} to event.as(CreateEntityTrackerEvent.class)\n    {_original} is set\n\n    broadcast {_original}.tracker().name()\n```\n</details>\n\n### Bukkit Compatibility\nTo optimize the project, support for the following versions (which showed low usage) has been removed:\n- 1.20.5 ~ 1.20.6\n- 1.21.2 ~ 1.21.3\n\n## 🧹 Chores\n- chore(deps): update gradle to v9.3.0\n- chore(deps): update plugin com.vanniktech.maven.publish to v0.36.0\n- chore: update dependency io.github.toxicity188:armormodel to v1.0.2\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/2.0.0-pre1...2.0.0-pre2)\n"
  },
  {
    "path": "changelog/v2/2.0.0.md",
    "content": "## 📚 Notices\n\nStarting with this version, BetterModel is officially designated as v2.0.0.  \nPlease be aware that this update introduces several Breaking Changes.\n\n## ✨ Feats\n### Fabric platform port\nBetterModel now supports the Fabric platform as a server-side mod.\n\n- Supported: Dedicated Server, Integrated Server (Singleplayer)\n- Unsupported: Hybrid servers (e.g., Arclight, Mohist)\n\n### Keyframe optimization\n`AnimationMovement` has been deprecated and replaced by the highly optimized `AnimationKeyframe`.  \nThis change introduces a more efficient data access structure, significantly improving animation processing performance.\n\n### ModelAssetsEvent\nThe new `ModelAssetsEvent` allows you to inject BlockBench models from external resources directly into the BetterModel reload pipeline.  \nThis enables seamless integration of custom assets within the engine's internal build process.\n\n### Others\n- feat: optimize IK solver\n\n## 🚀 Breaking Changes\n### Publishing & Dependencies\nThe legacy `io.github.toxicity188:bettermodel` package is no longer published.\n\nStarting with v2.0.0, the project is split into the following modules:\n- `bettermodel-api`: Common API module.\n- `bettermodel-core`: Internal core module (Internal use only).\n- `bettermodel-bukkit-api`: API for Bukkit-based environments (Spigot, Paper, etc.).\n- `bettermodel-fabric`: Server-side mod implementation for Fabric.\n\nPlease refer to `README.md` for detailed build script instructions.\n\n<details>\n<summary>Gradle (Kotlin)</summary>\n\n#### Release\n```kotlin\nrepositories {\n    mavenCentral()\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION\") // bukkit(spigot, paper, etc) api\n    //modApi(\"io.github.toxicity188:bettermodel-fabric:VERSION\") // mod(fabric)\n}\n```\n\n#### Snapshot\n```kotlin\nrepositories {\n    maven(\"https://maven.pkg.github.com/toxicity188/BetterModel\") {\n        credentials {\n            username = YOUR_GITHUB_USERNAME\n            password = YOUR_GITHUB_TOKEN\n        }\n    }\n    maven(\"https://maven.blamejared.com/\") // For transitive dependency in bettermodel-fabric\n    maven(\"https://maven.nucleoid.xyz/\") // For transitive dependency in bettermodel-fabric\n}\n\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-bukkit-api:VERSION-SNAPSHOT\") // bukkit(spigot, paper, etc) api\n    //modApi(\"io.github.toxicity188:bettermodel-fabric:VERSION-SNAPSHOT\") // mod(fabric)\n}\n```\n</details>\n\n### API & Event System\n- **Platform Adapters**: You must now use the appropriate adapter for your platform (e.g., `BukkitAdapter`, `FabricAdapter`).\n- **Event Bus**: `BetterModelEventBus` (accessible via BetterModel#eventBus) has been introduced.\n- **New Entrypoints**: Dedicated entrypoints for each platform have been introduced to provide platform-specific functionalities:\n    - `BetterModelBukkit`: Use `BetterModelBukkit#platform()` to access Bukkit-specific APIs.\n    - `BetterModelFabric`: Use `BetterModelFabric#platform()` to access Fabric-specific APIs.\n\n<details open>\n<summary>Adapter API Usage (Bukkit)</summary>\n\n```java\nEntityTracker tracker = BetterModel.model(\"demon_knight\")\n    .map(r -> r.create(BukkitAdapter.adapt(entity), TrackerModifier.DEFAULT, t -> t.update(TrackerUpdateAction.tint(0x0026FF))))\n    .orElse(null);\n```\n</details>\n\n<details>\n<summary>Event API Usage (Bukkit)</summary>\n\n```java\nimport org.bukkit.plugin.java.JavaPlugin;\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit;\nimport kr.toxicity.model.api.data.ModelAsset;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport kr.toxicity.model.api.event.ModelAssetsEvent;\n\nimport java.util.*;\n\npublic class YourPlugin extends JavaPlugin {\n    @Override\n    public void onEnable() {\n        BetterModelBukkit.platform().eventBus().subscribe(this, ModelAssetsEvent.class, event -> {\n            if (event.type() == ModelRenderer.Type.PLAYER) event.addAsset(ModelAsset.of(\n                \"knight\",\n                () -> Objects.requireNonNull(getResource(\"knight.bbmodel\"))\n            ));\n        });\n    }\n}\n```\n</details>\n\n### Bukkit-specific Event Support\nWhile the core Event API is now platform-agnostic, you can still utilize Bukkit's Event API via **`BetterModelBukkitEvent`**.  \nThis ensures seamless integration with the existing Bukkit event lifecycle, which is particularly useful for tools like **Skript** or other plugins that rely on Bukkit-native events.\n\n<details open>\n<summary>Event handling with Bukkit (Skript)</summary>\n\n```\nimport:\n    kr.toxicity.model.api.bukkit.event.BetterModelBukkitEvent\n    kr.toxicity.model.api.event.CreateEntityTrackerEvent\n\non BetterModelBukkitEvent:\n    set {_original} to event.as(CreateEntityTrackerEvent.class)\n    {_original} is set\n\n    broadcast {_original}.tracker().name()\n```\n</details>\n\n### Bukkit Compatibility\nTo optimize the project, support for the following versions (which showed low usage) has been removed:\n- 1.20.5 ~ 1.20.6\n- 1.21.2 ~ 1.21.3\n\n## 🧹 Chores\n- chore(deps): update gradle to v9.3.1\n- fix(deps): update dependency com.nexomc:nexo to v1.18.0\n- chore(deps): update plugin com.vanniktech.maven.publish to v0.36.0\n- chore: update dependency io.github.toxicity188:armormodel to v1.0.2\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/1.15.2...2.0.0)\n"
  },
  {
    "path": "changelog/v2/2.0.1.md",
    "content": "## 🔧 Fixes\n- player animation in NPC\n- Fabric event bus\n\n## 🧹 Chores\n- chore: update kotlin to v2.3.10\n- chore: update fabric api to v0.141.3+1.21.11\n- chore: update MythicMobs to v5.11.2\n- chore: update fabric-language-kotlin to v1.13.9+kotlin.2.3.10\n- chore: update polymer-resource-pack to v0.15.2+1.21.11\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/2.0.0...2.0.1)\n"
  },
  {
    "path": "changelog/v2/2.1.0.md",
    "content": "## ✨ Feats\n- optimize most of runtime calculation\n- improved body rotator\n- API `Tracker#listenHitBox` for handle each tracker's hitbox individually\n- new example model: `blue_wizard.bbmodel`\n- `glow_` tag for enable light emission both of group and cube (#284)\n\n```java\ntracker.listenHitBox(HitBoxInteractEvent.class, event -> {\n    HitBox hitBox = event.getHitBox();\n    // Do something with hitbox\n});\n```\n\n## 🔧 Fixes\n- ik rig\n- non hide player filtering\n\n## 🧹 Chores\n- fix(deps): update dependency org.bstats:bstats-bukkit to v3.2.1\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/2.0.1...2.1.0)\n"
  },
  {
    "path": "changelog/v2/2.2.0.md",
    "content": "## ✨ Feats\n- optimize model realtime thread (3~5x lightweight than BM 2.0.1)\n- optimize `BoneName`\n- add `TrackerAnimation` API (`TrackerBuiltInAnimation`, `TrackerExtraAnimation`)\n- remove unused class (`AnimationEventHandler`)\n```java\npublic static final TrackerAnimation<EntityTracker> DEATH = TrackerAnimation.builder(\"death\")\n    .type(EntityTracker.class)\n    .modifier(tracker -> AnimationModifier.DEFAULT_WITH_PLAY_ONCE)\n    .onRemove(Tracker::close)\n    .onSuccess(tracker -> tracker.forRemoval(true))\n    .build();\n```\n\n## 🔧 Fixes\n- Molang support in Spigot platform\n\n## 🚀 Changes\n- config `pack.use-obfuscation` is now true by default\n\n## 🧹 Chores\n- fix(deps): update dependency com.nexomc:nexo to v1.20.1\n- fix(deps): update dependency net.skinsrestorer:skinsrestorer-api to v15.10.1\n- fix(deps): update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.3.2\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/2.1.0...2.2.0)\n"
  },
  {
    "path": "changelog/v3/3.0.0.md",
    "content": "## 📚 Notices\n\nWith the official release of Minecraft 26.1, BetterModel has decided to deploy a major version update to `3.0.0`.  \nAs with 2.0.0, there are several **Breaking Changes**, so please take note when using it.\n\n---\n\n## ✨ Feats\n\n### [Minecraft 26.1.x](https://www.minecraft.net/en-us/article/minecraft-java-edition-26-1-2) Support\n\n![](https://github.com/user-attachments/assets/0153e97a-e320-41d1-8d94-ed626127b0cc)\n\n\nFrom now on, BetterModel supports running on Minecraft 26.1.x Servers.\n\n---\n\n### [Mesh Element](https://en.wikipedia.org/wiki/Triangle_mesh) Support (Experiment)\n\n![](https://github.com/user-attachments/assets/facf34a2-31d3-4ebd-a7ff-9fe86c651ff8)\n\nWe now support client resource pack conversion for Mesh Elements from [BlockBench](https://www.blockbench.net/).  \nIt is available on Minecraft clients version 26.1 and above.\n\n---\n\n### Others\n\n- Support meg-client-mod.\n- A `priority` property has been added to the `AnimationModifier` class, allowing you to adjust the application order of animations.\n- perf: minor optimization for some method\n- refactor: remove unused code\n\n---\n\n## 🚀 Breaking Changes\n\n### [Java 25](https://openjdk.org/projects/jdk/25/) Usage\n\nBetterModel is now built in a Java 25 environment, and therefore the required Java version to run it on a server has also been increased to 25.\n\n---\n\n### [Deobfuscation](https://www.minecraft.net/en-us/article/removing-obfuscation-in-java-edition) Porting\n\nAs obfuscation has been removed from the Minecraft jar starting from 26.1, tooling for mod platforms—which are most affected by mappings—has been changed.\n\n- `bettermodel-fabric`: Starting from 3.0.0, this is separated from the mod platform's API, `bettermodel-mod-api`.\n\n<details open>\n<summary>build.gradle.kts</summary>\n\n```kotlin\n// Use the following dependency when referencing only the API.\n// There is no need to use a separate remap configuration such as modCompileOnly.\ndependencies {\n    compileOnly(\"io.github.toxicity188:bettermodel-mod-api:3.0.0\")\n}\n```\n\n```kotlin\n// Use this when importing all Fabric platform modules to automate test servers, etc.\n// There is no need to use a separate remap configuration such as modApi.\ndependencies {\n    api(\"io.github.toxicity188:bettermodel-fabric:3.0.0\")\n}\n```\n</details>\n\n---\n\n### Deprecation of 1.21.3 Support\nTo keep the project lightweight, support for versions 1.21.3 and below will be discontinued. Therefore, operation is only guaranteed on servers and clients version 1.21.4 or higher.\n\n---\n\n## 🔧 Fixes\n\n- fix: swap unsupported char to hashcode\n- fix: unnecessary decimal value (#299)\n- fix: global rot (#325)\n- fix: close limb when reloading\n\n---\n\n## 🧹 Chores\n\n- chore: update cloud\n- chore: update Purpur api\n- chore: com.vdurmont:semver4j -> org.semver4j:semver4j\n- fix(deps): update dependency com.nexomc:nexo to v1.21.0\n- chore(deps): update gradle to v9.4.1\n- fix(deps): update dependency com.gradleup.shadow:com.gradleup.shadow.gradle.plugin to v9.4.1\n- fix(deps): update dependency org.jetbrains.dokka:dokka-gradle-plugin to v2.2.0\n- fix(deps): update kotlin monorepo to v2.3.20\n- fix(deps): update dependency net.fabricmc:fabric-language-kotlin to v1.13.10+kotlin.2.3.20\n- fix(deps): update dependency net.fabricmc:fabric-loader to v0.19.1\n- fix(deps): update dependency com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin to v2.9.0\n- fix(deps): update dependency org.projectlombok:lombok to v1.18.44\n- fix(deps): update dependency net.skinsrestorer:skinsrestorer-api to v15.12.0\n- chore: update net.citizensnpcs:citizens-main to 2.0.42-SNAPSHOT\n- chore: update polymer-resource-pack to 0.16.2+26.1.1\n- fix(deps): update dependency com.google.guava:guava to v33.6.0-jre\n- fix(deps): update dependency net.kyori:adventure-platform-fabric to v6.9.0\n\n---\n\n[Full change log](https://github.com/toxicity188/BetterModel/compare/2.2.0...3.0.0)\n"
  },
  {
    "path": "core/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.publish)\n}\n\ndependencies {\n    api(project(\":bettermodel-api\"))\n\n    compileOnly(libs.bundles.minecraft)\n    compileOnly(\"com.mojang:authlib:7.0.61\")\n\n    compileOnly(libs.bundles.core)\n    compileOnly(libs.cloud.core)\n}\n"
  },
  {
    "path": "core/bukkit-core/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.bukkit)\n}\n\ndependencies {\n    shade(project(\":bettermodel-api\")) { isTransitive = false }\n    shade(project(\":bettermodel-api:bettermodel-bukkit-api\")) { isTransitive = false }\n    shade(project(\":bettermodel-core\")) { isTransitive = false }\n\n    shade(project(\":purpur\"))\n    rootProject.project(\"nms\").subprojects.forEach {\n        compileOnly(it)\n    }\n\n    shade(libs.bundles.shadedLibrary) {\n        exclude(\"net.kyori\")\n        exclude(\"org.ow2.asm\")\n        exclude(\"io.leangen.geantyref\")\n    }\n\n    compileOnly(libs.bundles.manifestLibrary)\n\n    compileOnly(\"net.citizensnpcs:citizens-main:2.0.42-SNAPSHOT\") {\n        exclude(\"net.byteflux\")\n    }\n    compileOnly(\"net.skinsrestorer:skinsrestorer-api:15.12.0\")\n    compileOnly(\"io.lumine:Mythic-Dist:5.11.2\")\n    compileOnly(\"com.nexomc:nexo:1.21.0\")\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/java/kr/toxicity/model/bukkit/AbstractBetterModelPlugin.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit;\n\nimport kr.toxicity.model.BetterModelPlatformImpl;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.BetterModelLogger;\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit;\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter;\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.text.logger.slf4j.ComponentLogger;\nimport org.bukkit.plugin.java.JavaPlugin;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.jar.Attributes;\nimport java.util.jar.Manifest;\n\npublic abstract class AbstractBetterModelPlugin extends JavaPlugin implements BetterModelPlatformImpl, BetterModelBukkit {\n\n    protected boolean skipInitialReload;\n    protected final AtomicBoolean onReload = new AtomicBoolean();\n    protected final AtomicBoolean firstLoad = new AtomicBoolean();\n    protected final BukkitAdapter adapter = new BukkitAdapter();\n    protected final BetterModelLogger logger = new BetterModelLogger() {\n\n        private volatile ComponentLogger internalLogger;\n\n        private @NotNull ComponentLogger logger() {\n            ComponentLogger logger;\n            if ((logger = internalLogger) != null) return logger;\n            synchronized (this) {\n                if ((logger = internalLogger) != null) return logger;\n                return internalLogger = ComponentLogger.logger(getLogger().getName());\n            }\n        }\n\n        @Override\n        public void info(@NotNull Component... message) {\n            var log = logger();\n            synchronized (this) {\n                for (Component s : message) {\n                    log.info(s);\n                }\n            }\n        }\n\n        @Override\n        public void warn(@NotNull Component... message) {\n            var log = logger();\n            synchronized (this) {\n                for (Component s : message) {\n                    log.warn(s);\n                }\n            }\n        }\n    };\n    private @Nullable Attributes attributes;\n\n    public void onLoad() {\n        new BetterModelLibrary().load(this);\n        BetterModel.register(this);\n    }\n\n    public void skipInitialReload() {\n        this.skipInitialReload = true;\n    }\n\n    @Override\n    public void saveResource(@NotNull String resourcePath) {\n        saveResource(resourcePath, false);\n    }\n\n    @Override\n    @NotNull\n    public BukkitAdapter adapter() {\n        return adapter;\n    }\n\n    public @NotNull Attributes attributes() {\n        if (attributes != null) return attributes;\n        synchronized (this) {\n            if (attributes != null) return attributes;\n            try (\n                var stream = Objects.requireNonNull(getClassLoader().getResourceAsStream(\"META-INF/MANIFEST.MF\"))\n            ) {\n                return attributes = new Manifest(stream).getMainAttributes();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/java/kr/toxicity/model/bukkit/BetterModelLibrary.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit;\n\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit;\nimport kr.toxicity.model.api.util.function.BooleanConstantSupplier;\nimport net.byteflux.libby.Library;\nimport net.byteflux.libby.relocation.Relocation;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.*;\nimport java.util.function.BooleanSupplier;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\n@SuppressWarnings(\"unused\")\npublic final class BetterModelLibrary {\n\n    private static final String KOTLIN_RELOCATED = \"_kotlin\".substring(1);\n    private static final List<LibraryData> LIBRARY_DATA = new ArrayList<>();\n\n    public static final LibraryData KOTLIN = register(\n        \"org{}jetbrains{}kotlin\",\n        KOTLIN_RELOCATED + \"-stdlib\",\n        builder -> builder.relocation(KOTLIN_RELOCATED)\n    );\n    public static final LibraryData BSTATS = register(\n        \"org{}bstats\",\n        \"bstats-bukkit\",\n        builder -> builder.relocation(\"org{}bstats\")\n            .subModules(\n                \"bstats-base\"\n            )\n    );\n    public static final LibraryData CLOUD = register(\n        \"org{}incendo\",\n        \"cloud-paper\",\n        builder -> builder\n            .subModules(\n                \"cloud-brigadier\",\n                \"cloud-bukkit\"\n            )\n            .relocation(\"org{}incendo{}cloud\")\n    );\n    public static final LibraryData CLOUD_CORE = register(\n        \"org{}incendo\",\n        \"cloud-core\",\n        builder -> builder\n            .subModules(\n                \"cloud-services\"\n            )\n            .relocation(\"org{}incendo{}cloud\")\n    );\n    public static final LibraryData GEANTYREF = register(\n        \"io{}leangen{}geantyref\",\n        \"geantyref\",\n        builder -> builder.predicate(BooleanConstantSupplier.of(!BetterModelBukkit.IS_PAPER))\n    );\n    public static final LibraryData MOLANG_COMPILER = register(\n        \"gg{}moonflower\",\n        \"molang-compiler\",\n        builder -> builder\n    );\n    public static final LibraryData ADVENTURE_API = register(\n        \"net{}kyori\",\n        \"adventure-api\",\n        builder -> builder\n            .subModules(\n                \"adventure-key\",\n                \"adventure-text-logger-slf4j\",\n                \"adventure-text-serializer-legacy\",\n                \"adventure-nbt\",\n                \"adventure-text-serializer-gson\",\n                \"adventure-text-serializer-gson-legacy-impl\",\n                \"adventure-text-serializer-json\",\n                \"adventure-text-serializer-json-legacy-impl\"\n            )\n            .predicate(BooleanConstantSupplier.of(!BetterModelBukkit.IS_PAPER))\n    );\n    public static final LibraryData EXAMINATION_API = register(\n        \"net{}kyori\",\n        \"examination-api\",\n        builder -> builder\n            .subModules(\n                \"examination-string\"\n            )\n            .predicate(BooleanConstantSupplier.of(!BetterModelBukkit.IS_PAPER))\n    );\n    public static final LibraryData OPTION = register(\n        \"net{}kyori\",\n        \"option\",\n        builder -> builder.predicate(BooleanConstantSupplier.of(!BetterModelBukkit.IS_PAPER))\n    );\n    public static final LibraryData ADVENTURE_PLATFORM = register(\n        \"net{}kyori\",\n        \"adventure-platform-bukkit\",\n        builder -> builder\n            .subModules(\n                \"adventure-platform-api\",\n                \"adventure-platform-facet\",\n                \"adventure-platform-viaversion\",\n                \"adventure-text-serializer-bungeecord\"\n            )\n            .predicate(BooleanConstantSupplier.of(!BetterModelBukkit.IS_PAPER))\n    );\n    public static final LibraryData ASM_TREE = register(\n        \"org{}ow2{}asm\",\n        \"asm-tree\",\n        builder -> builder.predicate(BooleanConstantSupplier.of(!BetterModelBukkit.IS_PAPER))\n    );\n\n    public void load(@NotNull AbstractBetterModelPlugin plugin) {\n        var manager = new BetterModelLibraryManager(plugin);\n        manager.addRepository(\"https://maven-central.storage-download.googleapis.com/maven2/\");\n        manager.addRepository(\"https://maven.blamejared.com/\");\n        manager.addMavenCentral();\n        LIBRARY_DATA.stream()\n            .filter(LibraryData::isLoaded)\n            .flatMap(library -> library.toLibby(plugin))\n            .forEach(manager::loadLibrary);\n    }\n\n    private static @NotNull LibraryData register(@NotNull String group, @NotNull String artifact, @NotNull Function<LibraryData.Builder, LibraryData.Builder> function) {\n        var build = function.apply(new LibraryData.Builder(group, artifact)).build();\n        LIBRARY_DATA.add(build);\n        return build;\n    }\n\n    public record LibraryData(\n        @NotNull String group,\n        @NotNull String artifact,\n        @Nullable String relocation,\n        @NotNull String versionRef,\n        @NotNull @Unmodifiable Set<String> subModules,\n        @NotNull BooleanSupplier predicate\n    ) {\n\n        private static class Builder {\n            final String group;\n            final String artifact;\n            @Nullable String relocation;\n            @NotNull String versionRef;\n            @NotNull @Unmodifiable Set<String> subModules = Collections.emptySet();\n            @NotNull BooleanSupplier predicate = BooleanConstantSupplier.TRUE;\n\n            Builder(@NotNull String group, @NotNull String artifact) {\n                this.group = group;\n                this.artifact = this.versionRef = artifact;\n            }\n\n            @NotNull Builder predicate(@NotNull BooleanSupplier predicate) {\n                this.predicate = Objects.requireNonNull(predicate);\n                return this;\n            }\n\n            @NotNull Builder subModules(@NotNull String... subModules) {\n                this.subModules = Set.of(Objects.requireNonNull(subModules));\n                return this;\n            }\n\n            @NotNull Builder relocation(@Nullable String relocation) {\n                this.relocation = Objects.requireNonNull(relocation);\n                return this;\n            }\n\n            @NotNull Builder versionRef(@NotNull String versionRef) {\n                this.versionRef = Objects.requireNonNull(versionRef);\n                return this;\n            }\n\n            @NotNull LibraryData build() {\n                return new LibraryData(\n                    group,\n                    artifact,\n                    relocation,\n                    versionRef,\n                    subModules,\n                    predicate\n                );\n            }\n        }\n\n        public boolean isLoaded() {\n            return predicate.getAsBoolean();\n        }\n\n        private @NotNull Stream<Library> toLibby(@NotNull AbstractBetterModelPlugin plugin) {\n            var version = plugin.attributes().getValue(\"library-\" + versionRef);\n            return Stream.concat(\n                Stream.of(artifact),\n                subModules.stream()\n            ).map(name -> {\n                var libs = Library.builder()\n                    .groupId(group)\n                    .artifactId(name)\n                    .version(version);\n                if (relocation != null) libs.relocate(new Relocation(relocation, \"kr{}toxicity{}model{}shaded{}\" + relocation));\n                return libs.build();\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/java/kr/toxicity/model/bukkit/BetterModelLibraryManager.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit;\n\nimport net.byteflux.libby.LibraryManager;\nimport net.byteflux.libby.classloader.URLClassLoaderHelper;\nimport net.byteflux.libby.logging.adapters.JDKLogAdapter;\nimport org.bukkit.plugin.Plugin;\n\nimport java.net.URLClassLoader;\nimport java.nio.file.Path;\nimport java.util.Objects;\n\nfinal class BetterModelLibraryManager extends LibraryManager {\n\n    private final URLClassLoaderHelper classLoader;\n\n    BetterModelLibraryManager(Plugin plugin) {\n        super(new JDKLogAdapter(Objects.requireNonNull(plugin, \"plugin\").getLogger()), plugin.getDataFolder().toPath(), \".libs\");\n        var pluginLoader = plugin.getClass().getClassLoader();\n        URLClassLoader loader;\n        try {\n            var field = pluginLoader.getClass().getDeclaredField(\"libraryLoader\");\n            field.setAccessible(true);\n            loader = (URLClassLoader) field.get(pluginLoader);\n        } catch (Exception ignored) {\n            loader = (URLClassLoader) pluginLoader;\n        }\n        this.classLoader = new URLClassLoaderHelper(loader, this);\n    }\n\n    protected void addToClasspath(Path file) {\n        this.classLoader.addToClasspath(file);\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/BetterModelConfigImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit\n\nimport kr.toxicity.model.api.BetterModelConfig\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.config.IndicatorConfig\nimport kr.toxicity.model.api.config.ModuleConfig\nimport kr.toxicity.model.api.config.PackConfig\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.mount.MountControllers\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.util.EntityUtil\nimport kr.toxicity.model.util.ifNull\nimport kr.toxicity.model.util.toPackName\nimport org.bukkit.Material\nimport org.bukkit.configuration.ConfigurationSection\nimport org.bukkit.inventory.ItemStack\nimport java.io.File\nimport java.util.function.Supplier\n\nclass BetterModelConfigImpl(yaml: ConfigurationSection) : BetterModelConfig {\n\n    private val debug = yaml.getConfigurationSection(\"debug\")?.let {\n        DebugConfig.from(it::getBoolean)\n    } ?: DebugConfig.DEFAULT\n    private val indicator = yaml.getConfigurationSection(\"indicator\")?.let {\n        IndicatorConfig.from(it::getBoolean)\n    } ?: IndicatorConfig.DEFAULT\n    private val module = yaml.getConfigurationSection(\"module\")?.let {\n        ModuleConfig.from(it::getBoolean)\n    } ?: ModuleConfig.DEFAULT\n    private val pack = yaml.getConfigurationSection(\"pack\")?.let {\n        PackConfig.from(it::getBoolean)\n    } ?: PackConfig.DEFAULT\n    private val metrics = yaml.getBoolean(\"metrics\", true)\n    private val sightTrace = yaml.getBoolean(\"sight-trace\", true)\n    private val mergeWithExternalResources = yaml.getBoolean(\"merge-with-external-resources\", true)\n    private val itemModel = yaml.getString(\"item\")?.let {\n        runCatching {\n            Material.getMaterial(it.uppercase()).ifNull { \"This item doesn't exist: $it\" }\n        }.getOrDefault(Material.LEATHER_HORSE_ARMOR)\n    } ?: Material.LEATHER_HORSE_ARMOR\n    private val item = Supplier { BukkitAdapter.adapt(ItemStack(itemModel)) }\n    private val itemNamespace = yaml.getString(\"item-namespace\")?.toPackName() ?: \"bm_models\"\n    private val maxSight = yaml.getDouble(\"max-sight\", -1.0).run {\n        if (this <= 0.0) EntityUtil.renderDistance() else this\n    }\n    private val minSight = yaml.getDouble(\"min-sight\", 5.0)\n    private val namespace = yaml.getString(\"namespace\") ?: \"bettermodel\"\n    private val packType = yaml.getString(\"pack-type\")?.let {\n        runCatching {\n            BetterModelConfig.PackType.valueOf(it.uppercase())\n        }.getOrNull()\n    } ?: BetterModelConfig.PackType.ZIP\n    private val buildFolderLocation = (yaml.getString(\"build-folder-location\") ?: \"BetterModel/build\").replace('/', File.separatorChar)\n    private val followMobInvisibility = yaml.getBoolean(\"follow-mob-invisibility\", true)\n    private val usePurpurAfk = yaml.getBoolean(\"use-purpur-afk\", true)\n    private val versionCheck = yaml.getBoolean(\"version-check\", true)\n    private val defaultMountController = when (yaml.getString(\"default-mount-controller\")?.lowercase()) {\n        \"invalid\" -> MountControllers.INVALID\n        \"none\" -> MountControllers.NONE\n        \"fly\" -> MountControllers.FLY\n        else -> MountControllers.WALK\n    }\n    private val lerpFrameTime = yaml.getInt(\"lerp-frame-time\", 5)\n    private val cancelPlayerModelInventory = yaml.getBoolean(\"cancel-player-model-inventory\")\n    private val playerHideDelay = yaml.getLong(\"player-hide-delay\", 3L).coerceAtLeast(1L)\n    private val packetBundlingSize = yaml.getInt(\"packet-bundling-size\", 16)\n    private val enableStrictLoading = yaml.getBoolean(\"enable-strict-loading\")\n\n    override fun debug(): DebugConfig = debug\n    override fun indicator(): IndicatorConfig = indicator\n    override fun module(): ModuleConfig = module\n    override fun pack(): PackConfig = pack\n    override fun item(): Supplier<PlatformItemStack> = item\n    override fun itemModel(): String = itemModel.name\n    override fun itemNamespace(): String = itemNamespace\n    override fun metrics(): Boolean = metrics\n    override fun sightTrace(): Boolean = sightTrace\n    override fun mergeWithExternalResources(): Boolean = mergeWithExternalResources\n    override fun maxSight(): Double = maxSight\n    override fun minSight(): Double = minSight\n    override fun namespace(): String = namespace\n    override fun packType(): BetterModelConfig.PackType = packType\n    override fun buildFolderLocation(): String = buildFolderLocation\n    override fun followMobInvisibility(): Boolean = followMobInvisibility\n    override fun usePurpurAfk(): Boolean = usePurpurAfk\n    override fun versionCheck(): Boolean = versionCheck\n    override fun defaultMountController(): MountController = defaultMountController\n    override fun lerpFrameTime(): Int = lerpFrameTime\n    override fun cancelPlayerModelInventory(): Boolean = cancelPlayerModelInventory\n    override fun playerHideDelay(): Long = playerHideDelay\n    override fun packetBundlingSize(): Int = packetBundlingSize\n    override fun enableStrictLoading(): Boolean = enableStrictLoading\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/BetterModelPlugin.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit\n\nimport kr.toxicity.model.api.BetterModelConfig\nimport kr.toxicity.model.api.BetterModelEvaluator\nimport kr.toxicity.model.api.BetterModelLogger\nimport kr.toxicity.model.api.BetterModelPlatform.ReloadResult\nimport kr.toxicity.model.api.BetterModelPlatform.ReloadResult.*\nimport kr.toxicity.model.api.bukkit.BukkitModelEventBus\nimport kr.toxicity.model.api.bukkit.scheduler.BukkitModelScheduler\nimport kr.toxicity.model.api.manager.*\nimport kr.toxicity.model.api.nms.NMS\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.version.MinecraftVersion\nimport kr.toxicity.model.bukkit.command.startBukkitCommand\nimport kr.toxicity.model.bukkit.configuration.PluginConfiguration\nimport kr.toxicity.model.bukkit.manager.PlayerManagerImpl\nimport kr.toxicity.model.bukkit.util.ADVENTURE_PLATFORM\nimport kr.toxicity.model.bukkit.util.audience\nimport kr.toxicity.model.bukkit.util.registerListener\nimport kr.toxicity.model.manager.*\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.text.format.NamedTextColor.*\nimport org.bukkit.Bukkit\nimport org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\nimport org.bukkit.event.server.ServerLoadEvent\nimport org.semver4j.Semver\nimport java.io.File\nimport java.io.InputStream\nimport java.util.function.BiConsumer\nimport java.util.function.Consumer\nimport java.util.jar.JarEntry\nimport java.util.jar.JarFile\n\nabstract class BetterModelPlugin : AbstractBetterModelPlugin() {\n\n    private lateinit var props: BetterModelProperties\n\n    override fun onLoad() {\n        super.onLoad()\n        props = runCatching {\n            BetterModelProperties(this)\n        }.getOrElse {\n            warn(\n                \"Unable to start BetterModel.\".toComponent(),\n                \"Reason: ${it.message ?: \"Unknown\"}\".toComponent(RED),\n                \"Stack trace: ${it.stackTraceToString()}\".toComponent(RED),\n                \"Plugin will be automatically disabled.\".toComponent(DARK_RED)\n            )\n            return Bukkit.getPluginManager().disablePlugin(this)\n        }\n    }\n\n    override fun onEnable() {\n        props.managers.forEach(GlobalManager::start)\n        if (isSnapshot) warn(\n            \"This build is dev version: be careful to use it!\".toComponent(),\n            \"Build number: ${props.snapshot}\".toComponent(LIGHT_PURPLE)\n        )\n        startBukkitCommand()\n        registerListener(object : Listener {\n            @EventHandler\n            fun PlayerJoinEvent.join() {\n                if (!player.isOp || !config().versionCheck()) return\n                props.scheduler.asyncTask {\n                    val result = LATEST_VERSION\n                    player.audience().infoNotNull(\n                        result.release\n                            ?.takeIf { props.semver < it.versionNumber() }\n                            ?.let { version -> componentOf(\"New BetterModel release found: \") { append(version.toURLComponent()) } },\n                        result.snapshot\n                            ?.takeIf { props.semver < it.versionNumber() }\n                            ?.let { version -> componentOf(\"New BetterModel snapshot found: \") { append(version.toURLComponent()) } }\n                    )\n                }\n            }\n\n            @EventHandler\n            fun ServerLoadEvent.load() {\n                if (skipInitialReload || type != ServerLoadEvent.LoadType.STARTUP) return\n                when (val result = reload(ReloadInfo(true, Audience.empty()))) {\n                    is Failure -> result.throwable.handleException(\"Unable to load plugin properly.\")\n                    is OnReload -> throw RuntimeException(\"Plugin load failed.\")\n                    is Success -> info(\n                        \"Plugin is loaded. (${result.totalTime().withComma()} ms)\".toComponent(GREEN),\n                        \"Minecraft version: ${props.version}, NMS version: ${props.nms.version()}\".toComponent(AQUA),\n                        \"Platform: ${\n                            when {\n                                IS_FOLIA -> \"Folia\"\n                                IS_PURPUR -> \"Purpur\"\n                                IS_PAPER -> \"Paper\"\n                                else -> \"Bukkit\"\n                            }\n                        }\".toComponent(AQUA)\n                    )\n                }\n            }\n        })\n    }\n\n    override fun onDisable() {\n        if (!firstLoad.get()) return\n        props.managers.forEach(GlobalManager::end)\n        ADVENTURE_PLATFORM?.close()\n    }\n\n    override fun reload(info: ReloadInfo): ReloadResult {\n        if (!onReload.compareAndSet(false, true)) return OnReload.INSTANCE\n        return runCatching {\n            if (!info.skipConfig) props.config = BetterModelConfigImpl(PluginConfiguration.CONFIG.create())\n            val zipper = PackZipper.zipper().also(props.reloadStartTask)\n            ReloadPipeline(\n                config().indicator().options.toIndicator(info)\n            ).use { pipeline ->\n                val time = System.currentTimeMillis()\n                props.managers.forEach {\n                    it.reload(pipeline, zipper)\n                }\n                Success(\n                    firstLoad.compareAndSet(false, true),\n                    System.currentTimeMillis() - time,\n                    config().packType().toGenerator().create(zipper, pipeline.apply {\n                        status = \"Generating files...\"\n                        goal = zipper.size()\n                    })\n                )\n            }\n        }.getOrElse {\n            Failure(it)\n        }.apply {\n            onReload.set(false)\n        }.also(props.reloadEndTask)\n    }\n\n    override fun loadAssets(pipeline: ReloadPipeline, prefix: String, consumer: BiConsumer<String, InputStream>) {\n        JarFile(file).use {\n            pipeline.forEachParallel(it.entries()\n                .asSequence()\n                .filter { entry ->\n                    entry.name.startsWith(prefix)\n                        && entry.name.length > prefix.length + 1\n                        && !entry.isDirectory\n                }\n                .toList(),\n                JarEntry::getSize\n            ) { entry ->\n                it.getInputStream(entry).use { stream ->\n                    consumer.accept(entry.name.substring(prefix.length + 1), stream)\n                }\n            }\n        }\n    }\n\n    override fun dataFolder(): File = dataFolder\n    override fun logger(): BetterModelLogger = logger\n    override fun scheduler(): BukkitModelScheduler = props.scheduler\n    override fun evaluator(): BetterModelEvaluator = props.evaluator\n    override fun eventBus(): BukkitModelEventBus = props.eventbus\n    override fun modelManager(): ModelManager = ModelManagerImpl\n    override fun playerManager(): PlayerManager = PlayerManagerImpl\n    override fun scriptManager(): ScriptManager = ScriptManagerImpl\n    override fun skinManager(): SkinManager = SkinManagerImpl\n    override fun profileManager(): ProfileManager = ProfileManagerImpl\n\n    override fun config(): BetterModelConfig = props.config\n    override fun version(): MinecraftVersion = props.version\n    override fun semver(): Semver = props.semver\n    override fun nms(): NMS = props.nms\n    override fun isSnapshot(): Boolean = props.snapshot > 0\n\n    @Synchronized\n    override fun addReloadStartHandler(consumer: Consumer<PackZipper>) {\n        val previous = props.reloadStartTask\n        props.reloadStartTask = {\n            previous(it)\n            consumer.accept(it)\n        }\n    }\n\n    @Synchronized\n    override fun addReloadEndHandler(consumer: Consumer<ReloadResult>) {\n        val previous = props.reloadEndTask\n        props.reloadEndTask = {\n            previous(it)\n            consumer.accept(it)\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/BetterModelProperties.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit\n\nimport kr.toxicity.model.BetterModelEvaluatorImpl\nimport kr.toxicity.model.api.BetterModelConfig\nimport kr.toxicity.model.api.BetterModelPlatform.ReloadResult\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.event.PluginEndReloadEvent\nimport kr.toxicity.model.api.event.PluginStartReloadEvent\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.version.MinecraftVersion.*\nimport kr.toxicity.model.bukkit.configuration.PluginConfiguration\nimport kr.toxicity.model.bukkit.manager.CompatibilityManager\nimport kr.toxicity.model.bukkit.manager.EntityManager\nimport kr.toxicity.model.bukkit.manager.PlayerManagerImpl\nimport kr.toxicity.model.bukkit.scheduler.BukkitScheduler\nimport kr.toxicity.model.bukkit.scheduler.PaperScheduler\nimport kr.toxicity.model.manager.*\nimport kr.toxicity.model.util.*\nimport org.bstats.bukkit.Metrics\nimport org.bukkit.Bukkit\nimport org.semver4j.Semver\n\nprivate typealias Latest = kr.toxicity.model.bukkit.nms.v26_R1.NMSImpl\n\ninternal class BetterModelProperties(\n    private val plugin: AbstractBetterModelPlugin\n) {\n    private lateinit var _config: BetterModelConfig\n    private var _metrics: Metrics? = null\n\n    val version = parse(Bukkit.getBukkitVersion().substringBefore('-'))\n    val nms = when (version) {\n        V26_1, V26_1_1, V26_1_2 -> Latest()\n        V1_21_11 -> kr.toxicity.model.bukkit.nms.v1_21_R7.NMSImpl()\n        V1_21_9, V1_21_10 -> kr.toxicity.model.bukkit.nms.v1_21_R6.NMSImpl()\n        V1_21_6, V1_21_7, V1_21_8 -> kr.toxicity.model.bukkit.nms.v1_21_R5.NMSImpl()\n        V1_21_5 -> kr.toxicity.model.bukkit.nms.v1_21_R4.NMSImpl()\n        V1_21_4 -> kr.toxicity.model.bukkit.nms.v1_21_R3.NMSImpl()\n        else -> {\n            warn(\n                \"Note: this version is officially untested.\".toComponent(),\n                \"So be careful to use!\".toComponent()\n            )\n            Latest()\n        }\n    }\n    val scheduler = if (BetterModelBukkit.IS_FOLIA) PaperScheduler() else BukkitScheduler()\n    val evaluator = BetterModelEvaluatorImpl()\n    val eventbus = BukkitModelEventBusImpl()\n    @Suppress(\"DEPRECATION\") //To support Spigot :(\n    val semver = Semver.coerce(plugin.description.version).ifNull { \"Unable to load BetterModel's sermver.\" }\n    val snapshot = runCatching {\n        plugin.attributes().getValue(\"Dev-Build\").toInt()\n    }.getOrElse {\n        it.handleException(\"Unable to parse manifest's build data\")\n        -1\n    }\n    var config\n        get() = _config\n        set(value) {\n            _config = value.apply {\n                if (metrics()) {\n                    if (_metrics == null) _metrics = Metrics(plugin, 24237)\n                } else {\n                    _metrics?.shutdown()\n                    _metrics = null\n                }\n            }\n        }\n    val managers by lazy {\n        listOf(\n            CompatibilityManager,\n            ArmorManager,\n            ProfileManagerImpl,\n            SkinManagerImpl,\n            ModelManagerImpl,\n            PlayerManagerImpl,\n            EntityManager,\n            ScriptManagerImpl\n        )\n    }\n\n    var reloadStartTask: (PackZipper) -> Unit = { callEvent { PluginStartReloadEvent(it) } }\n    var reloadEndTask: (ReloadResult) -> Unit = { callEvent { PluginEndReloadEvent(it) } }\n\n    init {\n        config = BetterModelConfigImpl(PluginConfiguration.CONFIG.create())\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/BukkitModelEventBusImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit\n\nimport kr.toxicity.model.BetterModelEventBusImpl\nimport kr.toxicity.model.api.BetterModelEventBus\nimport kr.toxicity.model.api.bukkit.BukkitModelEventBus\nimport kr.toxicity.model.api.bukkit.event.BetterModelBukkitEvent\nimport kr.toxicity.model.api.event.CancellableEvent\nimport org.bukkit.Bukkit\n\nclass BukkitModelEventBusImpl : BukkitModelEventBus, BetterModelEventBus by BetterModelEventBusImpl({ eventClass, supplier ->\n    BetterModelBukkitEvent(eventClass, supplier).apply {\n        Bukkit.getPluginManager().callEvent(this)\n    }.source()?.let { event ->\n        if (event !is CancellableEvent || !event.isCancelled()) BetterModelEventBus.Result.SUCCESS else BetterModelEventBus.Result.FAIL\n    } ?: BetterModelEventBus.Result.NO_EVENT_HANDLER\n})\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/audience/AudiencePlayer.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.audience\n\nimport kr.toxicity.model.bukkit.util.audience\nimport net.kyori.adventure.bossbar.BossBar\nimport net.kyori.adventure.text.Component\nimport org.bukkit.entity.Player\n\nclass AudiencePlayer(\n    override val sender: Player\n) : BukkitAudience {\n\n    private val audience = sender.audience()\n\n    override fun sendMessage(message: Component) {\n        audience.sendMessage(message)\n    }\n\n    override fun showBossBar(bar: BossBar) {\n        audience.showBossBar(bar)\n    }\n\n    override fun hideBossBar(bar: BossBar) {\n        audience.hideBossBar(bar)\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/audience/AudienceSender.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.audience\n\nimport kr.toxicity.model.bukkit.util.audience\nimport net.kyori.adventure.bossbar.BossBar\nimport net.kyori.adventure.text.Component\nimport org.bukkit.command.CommandSender\n\nclass AudienceSender(\n    override val sender: CommandSender\n) : BukkitAudience {\n\n    private val audience = sender.audience()\n\n    override fun sendMessage(message: Component) {\n        audience.sendMessage(message)\n    }\n\n    override fun showBossBar(bar: BossBar) {\n        audience.showBossBar(bar)\n    }\n\n    override fun hideBossBar(bar: BossBar) {\n        audience.hideBossBar(bar)\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/audience/BukkitAudience.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.audience\n\nimport net.kyori.adventure.audience.Audience\nimport org.bukkit.command.CommandSender\n\ninterface BukkitAudience : Audience {\n    val sender: CommandSender\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/command/Commands.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.command\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.BetterModelPlatform.ReloadResult.*\nimport kr.toxicity.model.api.animation.AnimationIterator\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.tracker.EntityHideOption\nimport kr.toxicity.model.api.tracker.ModelScaler\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerModifier\nimport kr.toxicity.model.bukkit.audience.AudiencePlayer\nimport kr.toxicity.model.bukkit.audience.AudienceSender\nimport kr.toxicity.model.bukkit.audience.BukkitAudience\nimport kr.toxicity.model.bukkit.util.PLUGIN\nimport kr.toxicity.model.bukkit.util.toRegistry\nimport kr.toxicity.model.bukkit.util.toTracker\nimport kr.toxicity.model.bukkit.util.wrap\nimport kr.toxicity.model.command.*\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.text.format.NamedTextColor.*\nimport org.bukkit.command.CommandSender\nimport org.bukkit.entity.EntityType\nimport org.bukkit.entity.Player\nimport org.bukkit.util.Vector\nimport org.incendo.cloud.SenderMapper\nimport org.incendo.cloud.bukkit.BukkitCommandMeta\nimport org.incendo.cloud.bukkit.CloudBukkitCapabilities\nimport org.incendo.cloud.bukkit.data.MultipleEntitySelector\nimport org.incendo.cloud.bukkit.parser.PlayerParser.playerParser\nimport org.incendo.cloud.bukkit.parser.location.LocationParser.locationParser\nimport org.incendo.cloud.bukkit.parser.selector.MultipleEntitySelectorParser.multipleEntitySelectorParser\nimport org.incendo.cloud.context.CommandContext\nimport org.incendo.cloud.execution.ExecutionCoordinator\nimport org.incendo.cloud.paper.LegacyPaperCommandManager\nimport org.incendo.cloud.parser.standard.BooleanParser.booleanParser\nimport org.incendo.cloud.parser.standard.DoubleParser.doubleParser\nimport org.incendo.cloud.parser.standard.EnumParser.enumParser\nimport org.incendo.cloud.parser.standard.StringParser.stringParser\nimport org.incendo.cloud.suggestion.SuggestionProvider.blockingStrings\n\nprivate val MODEL_SUGGESTION = blockingStrings<Audience> { _, _ -> BetterModel.modelKeys() }\nprivate val LIMB_SUGGESTION = blockingStrings<Audience> { _, _ -> BetterModel.limbKeys() }\n\nfun startBukkitCommand() {\n    LegacyPaperCommandManager(\n        PLUGIN,\n        ExecutionCoordinator.simpleCoordinator(),\n        SenderMapper.create<CommandSender, Audience>(\n            { sender -> if (sender is Player) AudiencePlayer(sender) else AudienceSender(sender) },\n            { audience -> (audience as BukkitAudience).sender }\n        )\n    ).apply {\n        if (hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {\n            registerBrigadier()\n            brigadierManager().setNativeNumberSuggestions(true)\n        } else if (hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) registerAsynchronousCompletions()\n    }.register(\n        \"bettermodel\",\n        \"All-related command.\",\n        { it.meta(BukkitCommandMeta.BUKKIT_DESCRIPTION, info.description.textDescription()) },\n        \"bm\", \"model\"\n    ) {\n        create(\n            \"reload\",\n            \"Reloads BetterModel.\",\n            \"re\", \"rl\"\n        ) {\n            handler(::reload)\n        }\n        create(\n            \"spawn\",\n            \"Summons some model to given type\",\n            \"s\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .optional(\"type\", enumParser(EntityType::class.java))\n                .optional(\"scale\", doubleParser(0.0625, 16.0))\n                .optional(\"location\", locationParser())\n                .senderType(AudiencePlayer::class.java)\n                .handler(::spawn)\n        }\n        create(\n            \"test\",\n            \"Tests some model's animation to specific player\",\n            \"t\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .required(\n                    \"animation\",\n                    stringParser(),\n                    blockingStrings { ctx, _ -> ctx.nullableString(\"model\") { BetterModel.modelOrNull(it)?.animations()?.keys } ?: emptySet()  }\n                )\n                .optional(\"player\", playerParser())\n                .optional(\"location\", locationParser())\n                .handler(::test)\n        }\n        create(\n            \"disguise\",\n            \"Disguises self.\",\n            \"d\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .optional(\"scaling\", booleanParser())\n                .senderType(AudiencePlayer::class.java)\n                .handler(::disguise)\n        }\n        create(\n            \"undisguise\",\n            \"Undisguises self.\",\n            \"ud\"\n        ) {\n            senderType(AudiencePlayer::class.java)\n                .optional(\"model\", stringParser(), blockingStrings { ctx, _ -> ctx.sender().sender.toRegistry()?.trackers()?.map(Tracker::name) ?: emptyList() })\n                .handler(::undisguise)\n        }\n        create(\n            \"play\",\n            \"Plays player animation\",\n            \"p\"\n        ) {\n            required(\"limb\", stringParser(), LIMB_SUGGESTION)\n                .required(\n                    \"animation\",\n                    stringParser(),\n                    blockingStrings { ctx, _ -> ctx.nullableString(\"limb\") { BetterModel.limbOrNull(it)?.animations()?.keys } ?: emptySet()  }\n                )\n                .optional(\"loop_type\", enumParser(AnimationIterator.Type::class.java))\n                .optional(\"hide\", booleanParser())\n                .senderType(AudiencePlayer::class.java)\n                .handler(::play)\n        }\n        create(\n            \"hide\",\n            \"Hides some entities from target player.\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .required(\"player\", playerParser())\n                .required(\"entities\", multipleEntitySelectorParser())\n                .handler(::hide)\n        }\n        create(\n            \"show\",\n            \"Shows some entities to target player.\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .required(\"player\", playerParser())\n                .required(\"entities\", multipleEntitySelectorParser())\n                .handler(::show)\n        }\n        create(\n            \"version\",\n            \"Checks BetterModel's version\",\n            \"v\"\n        ) {\n            handler(::version)\n        }\n    }\n}\n\nprivate fun hide(context: CommandContext<Audience>) {\n    val sender = context.sender()\n    val model = context.get<String>(\"model\")\n    val player = context.get<Player>(\"player\").wrap()\n    var success = false\n    context.get<MultipleEntitySelector>(\"entities\").values().forEach {\n        if (it.toRegistry()?.tracker(model)?.hide(player) == true) success = true\n    }\n    if (!success) sender.warn(\"Failed to hide any of provided entities.\")\n}\n\nprivate fun show(context: CommandContext<Audience>) {\n    val sender = context.sender()\n    val model = context.get<String>(\"model\")\n    val player = context.get<Player>(\"player\").wrap()\n    var success = false\n    context.get<MultipleEntitySelector>(\"entities\").values().forEach {\n        if (it.toRegistry()?.tracker(model)?.show(player) == true) success = true\n    }\n    if (!success) sender.warn(\"Failed to show any of provided entities.\")\n}\n\nprivate fun disguise(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.sender\n    val scaling = if (context.getOrDefault(\"scaling\", true)) ModelScaler.entity() else ModelScaler.defaultScaler()\n    context.model(\"model\") { return audience.warn(\"Unable to find this model: $it\") }.getOrCreate(player.wrap(), TrackerModifier.DEFAULT) {\n        it.scaler(scaling)\n    }\n}\n\nprivate fun undisguise(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.sender\n    val model = context.nullable<String>(\"model\")\n    if (model != null) {\n        player.toTracker(model)?.close() ?: audience.warn(\"Cannot find this model to undisguise: $model\")\n    } else player.toRegistry()?.close() ?: audience.warn(\"Cannot find any model to undisguise\")\n}\n\nprivate fun spawn(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.sender\n    val model = context.model(\"model\") { return audience.warn(\"Unable to find this model: $it\") }\n    val type = context.nullable(\"type\", EntityType.HUSK)\n    val scale = context.nullable(\"scale\", 1.0)\n    val loc = context.nullable(\"location\") { player.location }\n    loc.run {\n        (world ?: player.world).spawnEntity(\n            this,\n            type\n        )\n    }.takeIf {\n        it.isValid\n    }?.let { entity ->\n        model.create(entity.wrap(), TrackerModifier.DEFAULT) { tracker -> tracker.scaler(ModelScaler.entity().multiply(scale.toFloat())) }\n    } ?: audience.warn(\"Entity spawning has been blocked.\")\n}\n\nprivate fun version(context: CommandContext<Audience>) {\n    val sender = context.sender()\n    sender.info(\"Searching version, please wait...\")\n    PLATFORM.scheduler().asyncTask {\n        val version = LATEST_VERSION\n        sender.infoNotNull(\n            emptyComponentOf(),\n            \"Current: ${PLATFORM.semver()}\".toComponent(),\n            version.release?.let { version -> componentOf(\"Latest release: \") { append(version.toURLComponent()) } },\n            version.snapshot?.let { version -> componentOf(\"Latest snapshot: \") { append(version.toURLComponent()) } }\n        )\n    }\n}\n\nprivate fun reload(context: CommandContext<Audience>) {\n    val audience = context.sender()\n    PLATFORM.scheduler().asyncTask {\n        audience.info(\"Start reloading. please wait...\")\n        when (val result = PLATFORM.reload(audience)) {\n            is OnReload -> audience.warn(\"BetterModel is still on reload!\")\n            is Success -> {\n                audience.info(\n                    emptyComponentOf(),\n                    \"Reload completed. (${result.totalTime().withComma()}ms)\".toComponent(GREEN),\n                    \"Assets reload time - ${result.assetsTime().withComma()}ms\".toComponent {\n                        color(GRAY)\n                        hoverEvent(\"Reading all config and model.\".toComponent().toHoverEvent())\n                    },\n                    \"Packing time - ${result.packingTime().withComma()}ms\".toComponent {\n                        color(GRAY)\n                        hoverEvent(\"Packing all model to resource pack.\".toComponent().toHoverEvent())\n                    },\n                    \"${BetterModel.models().size.withComma()} of models are loaded successfully. (${result.length().toByteFormat()})\".toComponent(YELLOW),\n                    (if (result.packResult.changed()) \"${result.packResult.size().withComma()} of files are zipped.\" else \"Zipping is skipped due to the same result.\").toComponent(YELLOW),\n                    emptyComponentOf()\n                )\n            }\n            is Failure -> {\n                audience.warn(\n                    emptyComponentOf(),\n                    \"Reload failed.\".toComponent(),\n                    \"Please read the log to find the problem.\".toComponent(),\n                    emptyComponentOf()\n                )\n                audience.warn()\n                result.throwable.handleException(\"Reload failed.\")\n            }\n        }\n    }\n}\n\nprivate fun play(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.sender\n    val limb = context.limb(\"limb\") { return audience.warn(\"Unable to find this limb: $it\") }\n    val animation = context.string(\"animation\") { limb.animation(it).orElse(null) ?: return audience.warn(\"Unable to find this animation: $it\") }\n    val loopType = context.nullable(\"loop_type\", AnimationIterator.Type.PLAY_ONCE)\n    val hide = context.nullable<Boolean>(\"hide\") != false\n    limb.getOrCreate(player.wrap(), TrackerModifier.DEFAULT) {\n        it.hideOption(if (hide) EntityHideOption.DEFAULT else EntityHideOption.FALSE)\n    }.run {\n        if (!animate(animation, AnimationModifier(0, 0, loopType), ::close)) close()\n    }\n}\n\nprivate fun test(context: CommandContext<Audience>) {\n    val audience = context.sender()\n    val model = context.model(\"model\") { return audience.warn(\"Unable to find this model: $it\") }\n    val animation = context.string(\"animation\") { str -> model.animation(str).orElse(null) ?: return audience.warn(\"Unable to find this animation: $str\") }\n    val player = context.nullable(\"player\") { (audience as? AudiencePlayer)?.sender ?: return audience.warn(\"Unable to find target player.\") }\n    val location = context.nullable(\"location\") {\n        player.location.apply {\n            add(Vector(0, 0, 10).rotateAroundY(-Math.toRadians(yaw.toDouble())))\n            yaw += 180\n        }\n    }\n    model.create(location.wrap()).run {\n        spawn(player.wrap())\n        animate(animation, AnimationModifier(0, 0, AnimationIterator.Type.PLAY_ONCE), ::close)\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/Compatibility.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility\n\nfun interface Compatibility {\n    fun start()\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/citizens/CitizensCompatibility.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.citizens\n\nimport kr.toxicity.model.bukkit.compatibility.Compatibility\nimport kr.toxicity.model.bukkit.compatibility.citizens.command.AnimateCommand\nimport kr.toxicity.model.bukkit.compatibility.citizens.command.LimbCommand\nimport kr.toxicity.model.bukkit.compatibility.citizens.command.ModelCommand\nimport kr.toxicity.model.bukkit.compatibility.citizens.trait.ModelTrait\nimport net.citizensnpcs.api.CitizensAPI\nimport net.citizensnpcs.api.trait.TraitInfo\n\nclass CitizensCompatibility : Compatibility {\n    override fun start() {\n        CitizensAPI.getTraitFactory()\n            .registerTrait(TraitInfo.create(ModelTrait::class.java))\n        CitizensAPI.getCommandManager().run {\n            register(ModelCommand::class.java)\n            register(AnimateCommand::class.java)\n            register(LimbCommand::class.java)\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/citizens/command/AnimateCommand.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.citizens.command\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.animation.AnimationIterator\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.util.function.FloatSupplier\nimport kr.toxicity.model.bukkit.util.wrap\nimport net.citizensnpcs.api.CitizensAPI\nimport net.citizensnpcs.api.command.Arg\nimport net.citizensnpcs.api.command.Command\nimport net.citizensnpcs.api.command.CommandContext\nimport net.citizensnpcs.api.npc.NPC\nimport org.bukkit.Bukkit\nimport org.bukkit.command.CommandSender\n\nclass AnimateCommand {\n    @Command(\n        aliases = [\"npc\"],\n        usage = \"animate <id> <animation> [loop_type] [speed] [player]\",\n        desc = \"\",\n        modifiers = [\"animate\"],\n        min = 3,\n        max = 6,\n        permission = \"citizens.npc.animate\"\n    )\n    @Suppress(\"UNUSED\")\n    fun animate(\n        args: CommandContext,\n        sender: CommandSender,\n        npc: NPC?,\n        @Arg(1) id: String,\n        @Arg(2) animation: String,\n        @Arg(3) loopType: String?,\n        @Arg(4) speed: String?,\n        @Arg(5) player: String?\n    ) {\n        val targetNpc = CitizensAPI.getNPCRegistry().getById(id.toIntOrNull() ?: return) ?: return\n        val modifier = AnimationModifier.builder()\n            .player(player?.let(Bukkit::getPlayer)?.wrap())\n            .start(0)\n            .end(0)\n            .speed(speed?.toFloatOrNull()?.let(FloatSupplier::of))\n            .type(loopType?.runCatching {\n                AnimationIterator.Type.valueOf(uppercase())\n            }?.getOrNull() ?: AnimationIterator.Type.PLAY_ONCE)\n            .build()\n        BetterModel.registryOrNull(targetNpc.entity.uniqueId)?.trackers()?.forEach {\n            it.animate(animation, modifier)\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/citizens/command/LimbCommand.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.citizens.command\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.animation.AnimationIterator\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.tracker.TrackerModifier\nimport kr.toxicity.model.bukkit.util.wrap\nimport net.citizensnpcs.api.CitizensAPI\nimport net.citizensnpcs.api.command.Arg\nimport net.citizensnpcs.api.command.Command\nimport net.citizensnpcs.api.command.CommandContext\nimport net.citizensnpcs.api.npc.NPC\nimport org.bukkit.Bukkit\nimport org.bukkit.command.CommandSender\nimport org.bukkit.entity.Player\n\nclass LimbCommand {\n    @Command(\n        aliases = [\"npc\"],\n        usage = \"limb <id> <model> <animation> [loop_type] [player]\",\n        desc = \"\",\n        modifiers = [\"limb\"],\n        min = 4,\n        max = 6,\n        permission = \"citizens.npc.animate\"\n    )\n    @Suppress(\"UNUSED\")\n    fun animate(\n        args: CommandContext,\n        sender: CommandSender,\n        npc: NPC?,\n        @Arg(1) id: String,\n        @Arg(2) model: String,\n        @Arg(3) animation: String,\n        @Arg(4) type: String?,\n        @Arg(5) player: String?\n    ) {\n        val targetNpc = CitizensAPI.getNPCRegistry().getById(id.toIntOrNull() ?: return) ?: return\n        val npcEntity = (targetNpc.entity as? Player)?.wrap() ?: return\n        val targetPlayer = player?.let(Bukkit::getPlayer)?.wrap()\n\n        val animType = type\n            ?.let { value ->\n                runCatching {\n                    AnimationIterator.Type.valueOf(value.uppercase())\n                }.getOrNull()\n            }\n            ?: AnimationIterator.Type.PLAY_ONCE\n\n        BetterModel.limb(model)\n            .map { renderer ->\n                renderer.getOrCreate(npcEntity, TrackerModifier.DEFAULT) { tracker ->\n                    if (targetPlayer != null) {\n                        tracker.markPlayerForSpawn(targetPlayer)\n                    }\n                }\n            }\n            .ifPresent { tracker ->\n                val success = tracker.animate(\n                    animation,\n                    AnimationModifier.builder()\n                        .start(0)\n                        .player(targetPlayer)\n                        .type(animType)\n                        .build()\n                ) {\n                    if (targetPlayer != null) {\n                        tracker.unmarkPlayerForSpawn(targetPlayer)\n                        tracker.registry().remove(targetPlayer)\n                        if (tracker.playerCount() == 0) tracker.close()\n                    } else {\n                        tracker.close()\n                    }\n                }\n\n                if (!success) {\n                    tracker.close()\n                    return@ifPresent\n                }\n\n                if (targetPlayer != null && !tracker.isSpawned(targetPlayer)) {\n                    tracker.markPlayerForSpawn(targetPlayer)\n                    tracker.registry().spawnIfNotSpawned(targetPlayer)\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/citizens/command/ModelCommand.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.citizens.command\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.bukkit.compatibility.citizens.trait.ModelTrait\nimport net.citizensnpcs.api.command.Arg\nimport net.citizensnpcs.api.command.Arg.CompletionsProvider\nimport net.citizensnpcs.api.command.Command\nimport net.citizensnpcs.api.command.CommandContext\nimport net.citizensnpcs.api.command.CommandMessages\nimport net.citizensnpcs.api.npc.NPC\nimport net.citizensnpcs.api.util.Messaging\nimport org.bukkit.command.CommandSender\n\nclass ModelCommand {\n    @Command(\n        aliases = [\"npc\"],\n        usage = \"model [model]\",\n        desc = \"\",\n        modifiers = [\"model\"],\n        min = 1,\n        max = 2,\n        permission = \"citizens.npc.model\"\n    )\n    @Suppress(\"UNUSED\")\n    fun model(args: CommandContext, sender: CommandSender, npc: NPC?, @Arg(1, completionsProvider = TabComplete::class) model: String?) {\n        if (npc == null) return Messaging.sendTr(sender, CommandMessages.MUST_HAVE_SELECTED)\n        npc.getOrAddTrait(ModelTrait::class.java).renderer = model?.let {\n            BetterModel.modelOrNull(it)\n        }\n        sender.sendMessage(\"Set ${npc.name}'s model to $model.\")\n    }\n\n    private class TabComplete : CompletionsProvider {\n        override fun getCompletions(p0: CommandContext?, p1: CommandSender?, p2: NPC?): Collection<String> = BetterModel.modelKeys()\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/citizens/trait/ModelTrait.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.citizens.trait\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.data.renderer.ModelRenderer\nimport kr.toxicity.model.bukkit.util.wrap\nimport net.citizensnpcs.api.trait.Trait\nimport net.citizensnpcs.api.trait.TraitName\nimport net.citizensnpcs.api.util.DataKey\n\n@TraitName(\"model\")\nclass ModelTrait : Trait(\"model\") {\n    private var _renderer: ModelRenderer? = null\n    var renderer\n        get() = _renderer\n        set(value) {\n            npc?.entity?.let {\n                value?.create(it.wrap()) ?: BetterModel.registryOrNull(it.uniqueId)?.close()\n            }\n            _renderer = value\n        }\n\n    override fun load(key: DataKey) {\n        key.getString(\"\")?.let {\n            BetterModel.modelOrNull(it)?.let { model ->\n                renderer = model\n            }\n        }\n    }\n\n    override fun save(key: DataKey) {\n        npc?.entity?.uniqueId?.let { uuid ->\n            key.setString(\"\", BetterModel.registryOrNull(uuid)?.first()?.name())\n        }\n    }\n\n    override fun onSpawn() {\n        npc?.entity?.let {\n            if (BetterModel.registryOrNull(it.uniqueId) == null) {\n                renderer?.create(it.wrap())\n            }\n        }\n    }\n\n    override fun onCopy() {\n        onSpawn()\n    }\n\n    override fun onDespawn() {\n        npc?.entity?.uniqueId?.let {\n            BetterModel.registryOrNull(it)?.close()\n        }\n    }\n\n    override fun onRemove() {\n        npc?.entity?.uniqueId?.let {\n            BetterModel.registryOrNull(it)?.close()\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/MythicMobsCompatibility.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs\n\nimport io.lumine.mythic.bukkit.MythicBukkit\nimport io.lumine.mythic.bukkit.events.MythicConditionLoadEvent\nimport io.lumine.mythic.bukkit.events.MythicMechanicLoadEvent\nimport io.lumine.mythic.bukkit.events.MythicTargeterLoadEvent\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.tracker.EntityTracker\nimport kr.toxicity.model.bukkit.compatibility.Compatibility\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.condition.ModelHasPassengerCondition\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic.*\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.targeter.ModelPartTargeter\nimport kr.toxicity.model.bukkit.util.registerListener\nimport kr.toxicity.model.manager.ScriptManagerImpl\nimport kr.toxicity.model.util.CONFIG\nimport kr.toxicity.model.util.componentOf\nimport kr.toxicity.model.util.toComponent\nimport kr.toxicity.model.util.warn\nimport net.kyori.adventure.text.format.NamedTextColor\nimport org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\n\nclass MythicMobsCompatibility : Compatibility {\n\n    private companion object {\n        const val NAMESPACE = \"bm:\"\n    }\n\n    override fun start() {\n        ScriptManagerImpl.addBuilder(\"mm\") { name ->\n            val args = name.args() ?: return@addBuilder AnimationScript.EMPTY\n            AnimationScript.of(BetterModelBukkit.IS_FOLIA) script@ { tracker ->\n                if (!CONFIG.module().model) return@script\n                if (tracker !is EntityTracker) return@script\n                val entity = (tracker.registry().entity() as? BaseBukkitEntity ?: return@script).entity()\n                if (!MythicBukkit.inst().apiHelper.castSkill(\n                        entity,\n                        args,\n                    MythicBukkit.inst().apiHelper.getMythicMobInstance(entity)?.power ?: 1F\n                ) {\n                    name.metadata.toMap().forEach { (key, value) ->\n                        it.parameters[key] = value.toString()\n                    }\n                }) warn(componentOf(\n                    \"Unknown MythicMobs skill name: \".toComponent(),\n                    args.toComponent(NamedTextColor.RED)\n                ))\n            }\n        }\n        registerListener(object : Listener {\n            @EventHandler\n            fun MythicMechanicLoadEvent.load() {\n                if (!CONFIG.module().model) return\n                when (mechanicName.lowercase().substringAfter(NAMESPACE)) {\n                    \"playlimbanim\" -> register(PlayLimbAnimMechanic(config))\n                    \"model\" -> register(ModelMechanic(config))\n                    \"state\", \"animation\" -> register(StateMechanic(config))\n                    \"defaultstate\", \"defaultanimation\" -> register(DefaultStateMechanic(config))\n                    \"partvisibility\", \"partvis\" -> register(PartVisibilityMechanic(config))\n                    \"bindhitbox\" -> register(BindHitBoxMechanic(config))\n                    \"changepart\" -> register(ChangePartMechanic(config))\n                    \"tint\", \"color\" -> register(TintMechanic(config))\n                    \"brightness\", \"light\" -> register(BrightnessMechanic(config))\n                    \"enchant\" -> register(EnchantMechanic(config))\n                    \"billboard\" -> register(BillboardMechanic(config))\n                    \"glow\", \"glowbone\" -> register(GlowMechanic(config))\n                    \"mountmodel\" -> register(MountModelMechanic(config))\n                    \"dismountmodel\" -> register(DismountModelMechanic(config))\n                    \"dismountallmodel\", \"dismountall\" -> register(DismountAllModelMechanic(config))\n                    \"lockmodel\", \"lockrotation\" -> register(LockModelMechanic(config))\n                    \"bodyrotation\", \"bodyclamp\" -> register(BodyRotationMechanic(config))\n                    \"remapmodel\", \"remap\" -> register(RemapModelMechanic(config))\n                    \"pairmodel\" -> register(PairModelMechanic(config))\n                }\n            }\n\n            @EventHandler\n            fun MythicConditionLoadEvent.load() {\n                if (!CONFIG.module().model) return\n                when (conditionName.lowercase().substringAfter(NAMESPACE)) {\n                    \"modelhaspassenger\" -> register(ModelHasPassengerCondition(config))\n                }\n            }\n\n            @EventHandler\n            fun MythicTargeterLoadEvent.load() {\n                if (!CONFIG.module().model) return\n                when (targeterName.lowercase().substringAfter(NAMESPACE)) {\n                    \"modelpart\" -> register(ModelPartTargeter(config))\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/MythicMobsValue.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport kr.toxicity.model.api.util.function.BonePredicate\nimport kr.toxicity.model.bukkit.util.toRegistry\nimport kr.toxicity.model.bukkit.util.toTracker\nimport kr.toxicity.model.util.boneName\nimport kr.toxicity.model.util.toPackName\n\nval MM_MODEL_ID = arrayOf(\"mid\", \"m\", \"model\")\nval MM_PART_ID = arrayOf(\"partid\", \"p\", \"pid\", \"part\")\nval MM_CHILDREN = arrayOf(\"children\", \"child\")\nval MM_EXACT_MATCH = arrayOf(\"exactmatch\", \"em\", \"exact\", \"match\")\nval MM_SEAT = arrayOf(\"seat\", \"p\", \"pbone\")\n\nfun SkillMetadata.toRegistry() = caster.entity.toRegistry()\nfun SkillMetadata.toTracker(model: String?) = caster.entity.toTracker(model)\nfun AbstractEntity.toTracker(model: String?) = bukkitEntity.toTracker(model)\nfun AbstractEntity.toRegistry() = bukkitEntity.toRegistry()\n\nfun MythicLineConfig.toPlaceholderString(array: Array<String>, defaultValue: String? = null) = toPlaceholderString(array, defaultValue) { it }\nfun <T> MythicLineConfig.toPlaceholderStringList(array: Array<String>, mapper: (List<String>) -> T) = toPlaceholderString(array) {\n    mapper(it?.split(\",\") ?: emptyList())\n}\nfun <T> MythicLineConfig.toPlaceholderString(array: Array<String>, defaultValue: String? = null, mapper: (String?) -> T): (PlaceholderArgument) -> T {\n    return getPlaceholderString(array, defaultValue)?.let {\n        { meta ->\n            mapper(when (meta) {\n                is PlaceholderArgument.None -> it.get()\n                is PlaceholderArgument.SkillMeta -> it[meta.meta]\n                is PlaceholderArgument.TargetedSkillMeta -> it.get(meta.meta, meta.target)\n                is PlaceholderArgument.Entity -> it[meta.entity]\n            })\n        }\n    } ?: mapper(null).let { mapped ->\n        {\n            mapped\n        }\n    }\n}\nfun MythicLineConfig.toPlaceholderInteger(array: Array<String>, defaultValue: Int = 0) = toPlaceholderInteger(array, defaultValue) { it ?: defaultValue }\nfun MythicLineConfig.toNullablePlaceholderInteger(array: Array<String>) = toPlaceholderInteger(array, null) { it }\nfun <T> MythicLineConfig.toPlaceholderInteger(array: Array<String>, defaultValue: Int? = null, mapper: (Int?) -> T): (PlaceholderArgument) -> T {\n    return getPlaceholderInteger(array, defaultValue?.toString())?.let {\n        { meta ->\n            mapper(when (meta) {\n                is PlaceholderArgument.None -> it.get()\n                is PlaceholderArgument.SkillMeta -> it[meta.meta]\n                is PlaceholderArgument.TargetedSkillMeta -> it.get(meta.meta, meta.target)\n                is PlaceholderArgument.Entity -> it[meta.entity]\n            })\n        }\n    } ?: mapper(null).let { mapped ->\n        {\n            mapped\n        }\n    }\n}\nfun MythicLineConfig.toPlaceholderFloat(array: Array<String>, defaultValue: Float = 0F) = toPlaceholderFloat(array, defaultValue) { it ?: defaultValue }\nfun MythicLineConfig.toNullablePlaceholderFloat(array: Array<String>) = toPlaceholderFloat(array, null) { it }\nfun <T> MythicLineConfig.toPlaceholderFloat(array: Array<String>, defaultValue: Float? = null, mapper: (Float?) -> T): (PlaceholderArgument) -> T {\n    return getPlaceholderFloat(array, defaultValue?.toString())?.let {\n        { meta ->\n            mapper(when (meta) {\n                is PlaceholderArgument.None -> it.get()\n                is PlaceholderArgument.SkillMeta -> it[meta.meta]\n                is PlaceholderArgument.TargetedSkillMeta -> it.get(meta.meta, meta.target)\n                is PlaceholderArgument.Entity -> it[meta.entity]\n            })\n        }\n    } ?: mapper(null).let { mapped ->\n        {\n            mapped\n        }\n    }\n}\nfun MythicLineConfig.toPlaceholderBoolean(array: Array<String>, defaultValue: Boolean? = null) = toPlaceholderBoolean(array, defaultValue) { it == true }\nfun MythicLineConfig.toNullablePlaceholderBoolean(array: Array<String>, defaultValue: Boolean? = null) = toPlaceholderBoolean(array, defaultValue) { it }\nfun <T> MythicLineConfig.toPlaceholderBoolean(array: Array<String>, defaultValue: Boolean? = null, mapper: (Boolean?) -> T): (PlaceholderArgument) -> T {\n    return getPlaceholderBoolean(array, defaultValue)?.let {\n        { meta ->\n            mapper(when (meta) {\n                is PlaceholderArgument.None -> it.get()\n                is PlaceholderArgument.SkillMeta -> it[meta.meta]\n                is PlaceholderArgument.TargetedSkillMeta -> it.get(meta.meta, meta.target)\n                is PlaceholderArgument.Entity -> it[meta.entity]\n            })\n        }\n    } ?: mapper(null).let { mapped ->\n        {\n            mapped\n        }\n    }\n}\nfun MythicLineConfig.toPlaceholderColor(array: Array<String>, defaultValue: String = \"FFFFFF\") = toPlaceholderColor(array, defaultValue) { it }\nfun <T> MythicLineConfig.toPlaceholderColor(array: Array<String>, defaultValue: String = \"FFFFFF\", mapper: (Int?) -> T): (PlaceholderArgument) -> T {\n    return toPlaceholderString(array, defaultValue) {\n        mapper(it?.toIntOrNull(16))\n    }\n}\n\nval MythicLineConfig.bonePredicateNullable\n    get() = toBonePredicate(BonePredicate.TRUE)\nval MythicLineConfig.bonePredicate\n    get() = toBonePredicate(BonePredicate.FALSE)\n\nval MythicLineConfig.modelPlaceholder\n    get() = toPlaceholderString(MM_MODEL_ID) {\n        it?.toPackName()\n    }\n\nfun MythicLineConfig.toBonePredicate(defaultPredicate: BonePredicate): (PlaceholderArgument) -> BonePredicate {\n    val match = toPlaceholderBoolean(MM_EXACT_MATCH, true)\n    val children = toPlaceholderBoolean(MM_CHILDREN, false)\n    val partSupplier = toPlaceholderString(MM_PART_ID) {\n        it?.boneName?.name\n    }\n    return { meta ->\n        val part = partSupplier(meta)\n        if (part == null) defaultPredicate else {\n            BonePredicate.of(if (children(meta)) BonePredicate.State.TRUE else BonePredicate.State.FALSE, if (match(meta)) {\n                { b ->\n                    b.name().name == part\n                }\n            } else {\n                { b ->\n                    b.name().name.contains(part, ignoreCase = true)\n                }\n            })\n        }\n    }\n}\n\nfun SkillMetadata.toPlaceholderArgs() = PlaceholderArgument.SkillMeta(this)\nfun AbstractEntity.toPlaceholderArgs() = PlaceholderArgument.Entity(this)\nfun toPlaceholderArgs(meta: SkillMetadata, target: AbstractEntity) = PlaceholderArgument.TargetedSkillMeta(meta, target)\n\nsealed interface PlaceholderArgument {\n    data object None : PlaceholderArgument\n    data class SkillMeta(val meta: SkillMetadata) : PlaceholderArgument\n    data class TargetedSkillMeta(val meta: SkillMetadata, val target: AbstractEntity) : PlaceholderArgument\n    data class Entity(val entity: AbstractEntity) : PlaceholderArgument\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/condition/ModelHasPassengerCondition.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.condition\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.conditions.IEntityCondition\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.MM_SEAT\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderArgs\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderStringList\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toRegistry\nimport kr.toxicity.model.util.boneName\n\nclass ModelHasPassengerCondition(mlc: MythicLineConfig) : IEntityCondition {\n\n    private val seat = mlc.toPlaceholderStringList(MM_SEAT) {\n        it.map { s -> s.boneName.name }.toSet()\n    }\n\n    override fun check(p0: AbstractEntity): Boolean {\n        val args = p0.toPlaceholderArgs()\n        val set = seat(args)\n        return p0.toRegistry()?.let {\n            if (set.isEmpty()) it.hasPassenger() else set.any { seat ->\n                it.mountedHitBox().values.any { box ->\n                    box.hitBox().positionSource().name().name == seat\n                }\n            }\n        } == true\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/AbstractSkillMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.bukkit.MythicBukkit\nimport io.lumine.mythic.core.skills.SkillMechanic\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\n\nabstract class AbstractSkillMechanic(mlc: MythicLineConfig) : SkillMechanic(MythicBukkit.inst().skillManager, null, null, mlc) {\n    init {\n        isAsyncSafe = !BetterModelBukkit.IS_FOLIA\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/BillboardMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\n\nclass BillboardMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicateNullable\n    private val billboard = mlc.toPlaceholderString(arrayOf(\"billboard\", \"bb\"), \"fixed\") {\n        it?.runCatching {\n            PlatformBillboard.valueOf(uppercase())\n        }?.getOrNull() ?: PlatformBillboard.FIXED\n    }\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            it.update(\n                TrackerUpdateAction.billboard(billboard(args)),\n                predicate(args)\n            )\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/BindHitBoxMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport io.lumine.mythic.bukkit.MythicBukkit\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.bukkit.util.unwarp\nimport org.bukkit.entity.Damageable\nimport org.bukkit.entity.Entity\n\nclass BindHitBoxMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicate\n    private val type = mlc.toPlaceholderString(arrayOf(\"type\", \"t\", \"mob\", \"m\")) {\n        if (it != null) MythicBukkit.inst().mobManager.getMythicMob(it).orElse(null) else null\n    }\n\n    init {\n        isAsyncSafe = false\n    }\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            val e = type(args) ?: return SkillResult.CONDITION_FAILED\n            val spawned = e.spawn(p0.caster.location, p0.caster.level).apply {\n                setParent(p0.caster)\n                setOwnerUUID(p0.caster.entity.uniqueId)\n            }.entity.bukkitEntity\n            it.createHitBox(HitBoxListener.builder()\n                .sync { hitBox ->\n                    if (!spawned.isValid) hitBox.removeHitBox()\n                    else spawned.teleportAsync((hitBox as Entity).location)\n                }\n                .damage { event ->\n                    if (spawned is Damageable) {\n                        spawned.damage(event.damage.toDouble(), event.source.causingEntity?.unwarp())\n                        event.isCancelled = true\n                    }\n                }\n                .remove {\n                    spawned.remove()\n                }\n                .build(), predicate(args))\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/BodyRotationMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\n\nclass BodyRotationMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val headUneven = mlc.toNullablePlaceholderBoolean(arrayOf(\"headuneven\", \"hu\", \"head\"))\n    private val maxHead = mlc.toNullablePlaceholderFloat(arrayOf(\"maxhead\", \"mh\", \"mxh\"))\n    private val minHead = mlc.toNullablePlaceholderFloat(arrayOf(\"minhead\", \"mnh\"))\n    private val bodyUneven = mlc.toNullablePlaceholderBoolean(arrayOf(\"bodyuneven\", \"bu\", \"body\"))\n    private val maxBody = mlc.toNullablePlaceholderFloat(arrayOf(\"maxbody\", \"mb\", \"mxb\"))\n    private val minBody = mlc.toNullablePlaceholderFloat(arrayOf(\"minbody\", \"mnb\"))\n    private val playerMode = mlc.toNullablePlaceholderBoolean(arrayOf(\"playermode\", \"m\", \"mode\", \"player\"))\n    private val stable = mlc.toNullablePlaceholderFloat(arrayOf(\"stable\", \"s\"))\n    private val rDelay = mlc.toNullablePlaceholderInteger(arrayOf(\"rdelay\", \"rde\"))\n    private val rDuration = mlc.toNullablePlaceholderInteger(arrayOf(\"rduration\", \"rdu\"))\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.bodyRotator()?.run {\n            setValue { setter ->\n                headUneven(args)?.let { setter.setHeadUneven(it) }\n                maxHead(args)?.let { setter.setMaxHead(it) }\n                minHead(args)?.let { setter.setMinHead(it) }\n                bodyUneven(args)?.let { setter.setBodyUneven(it) }\n                maxBody(args)?.let { setter.setMaxBody(it) }\n                minBody(args)?.let { setter.setMinBody(it) }\n                playerMode(args)?.let { setter.setPlayerMode(it) }\n                stable(args)?.let { setter.setStable(it) }\n                rDelay(args)?.let { setter.setRotationDelay(it) }\n                rDuration(args)?.let { setter.setRotationDuration(it) }\n            }\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/BrightnessMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.script.BrightnessScript\n\nclass BrightnessMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicateNullable\n    private val block = mlc.toPlaceholderInteger(arrayOf(\"block\", \"b\")) {\n        (it ?: -1).coerceAtLeast(-1).coerceAtMost(15)\n    }\n    private val sky = mlc.toPlaceholderInteger(arrayOf(\"sky\", \"s\")) {\n        (it ?: -1).coerceAtLeast(-1).coerceAtMost(15)\n    }\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            BrightnessScript(\n                predicate(args),\n                block(args),\n                sky(args)\n            ).accept(it)\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/ChangePartMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.script.ChangePartScript\nimport kr.toxicity.model.util.boneName\n\nclass ChangePartMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicate\n    private val nmid = mlc.toPlaceholderString(arrayOf(\"newmodelid\", \"nm\", \"nmid\", \"newmodel\"))\n    private val newPart = mlc.toPlaceholderString(arrayOf(\"newpart\", \"np\", \"npid\")) {\n        it?.boneName\n    }\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            ChangePartScript(\n                predicate(args),\n                nmid(args) ?: return SkillResult.CONDITION_FAILED,\n                newPart(args) ?: return SkillResult.CONDITION_FAILED\n            ).accept(it)\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/DefaultStateMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.util.function.FloatSupplier\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\n\nclass DefaultStateMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val type = mlc.toPlaceholderString(arrayOf(\"t\", \"type\"))\n    private val state = mlc.toPlaceholderString(arrayOf(\"state\", \"s\"))\n    private val li = mlc.toNullablePlaceholderInteger(arrayOf(\"li\"))\n    private val lo = mlc.toNullablePlaceholderInteger(arrayOf(\"lo\"))\n    private val sp = mlc.toNullablePlaceholderFloat(arrayOf(\"speed\", \"sp\"))\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            val t = type(args)\n            val s = state(args)\n            if (t == null) return SkillResult.CONDITION_FAILED\n            it.replace(t, s ?: t, AnimationModifier.builder()\n                .start(li(args) ?: -1)\n                .end(lo(args) ?: -1)\n                .speed(sp(args)?.let(FloatSupplier::of))\n                .build())\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/DismountAllModelMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.ITargetedEntitySkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.MM_SEAT\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderArgs\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderStringList\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toRegistry\nimport kr.toxicity.model.util.boneName\n\nclass DismountAllModelMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), ITargetedEntitySkill {\n\n    private val seat = mlc.toPlaceholderStringList(MM_SEAT) {\n        it.map { s -> s.boneName.name }.toSet()\n    }\n\n    init {\n        isAsyncSafe = false\n    }\n\n    override fun castAtEntity(p0: SkillMetadata, p1: AbstractEntity): SkillResult {\n        val args = toPlaceholderArgs(p0, p1)\n        return p0.toRegistry()?.let { registry ->\n            val set = seat(args)\n            registry.mountedHitBox()\n                .values\n                .asSequence()\n                .filter {\n                    set.isEmpty() || set.contains(it.hitBox.positionSource().name().name)\n                }\n                .forEach {\n                    it.dismountAll()\n                }\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/DismountModelMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.ITargetedEntitySkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.util.boneName\n\nclass DismountModelMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), ITargetedEntitySkill {\n\n    private val driver = mlc.toPlaceholderBoolean(arrayOf(\"driver\", \"d\", \"drive\"), true)\n    private val seat = mlc.toPlaceholderStringList(MM_SEAT) {\n        it.map { s -> s.boneName.name }.toSet()\n    }\n\n    init {\n        isAsyncSafe = false\n    }\n\n    override fun castAtEntity(p0: SkillMetadata, p1: AbstractEntity): SkillResult {\n        val args = toPlaceholderArgs(p0, p1)\n        return p0.toRegistry()?.let { registry ->\n            val set = seat(args)\n            val d = driver(args)\n            registry.mountedHitBox()[p1.bukkitEntity.uniqueId]?.takeIf {\n                set.isEmpty() || (set.contains(it.hitBox.positionSource().name().name) && (!d || it.hitBox.mountController().canControl()))\n            }?.dismount()\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/EnchantMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.script.EnchantScript\n\nclass EnchantMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicateNullable\n    private val enchant = mlc.toPlaceholderBoolean(arrayOf(\"enchant\", \"en\"), true)\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            EnchantScript(\n                predicate(args),\n                enchant(args)\n            ).accept(it)\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/GlowMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\n\nclass GlowMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicateNullable\n    private val glow = mlc.toNullablePlaceholderBoolean(arrayOf(\"glow\", \"g\"), true)\n    private val color = mlc.toPlaceholderColor(arrayOf(\"color\", \"c\"))\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            val predicate = predicate(args)\n            glow(args)?.let { glow ->\n                it.update(\n                    TrackerUpdateAction.glow(glow),\n                    predicate\n                )\n            }\n            color(args)?.let { c ->\n                it.update(\n                    TrackerUpdateAction.glowColor(c),\n                    predicate\n                )\n            }\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/LockModelMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.modelPlaceholder\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderArgs\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderBoolean\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toTracker\n\nclass LockModelMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val lock = mlc.toPlaceholderBoolean(arrayOf(\"lock\", \"l\"), true)\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.bodyRotator()?.let {\n            it.lockRotation(lock(args))\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/ModelMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.tracker.ModelScaler\nimport kr.toxicity.model.api.tracker.TrackerModifier\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.modelPlaceholder\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderArgs\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderBoolean\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderFloat\nimport kr.toxicity.model.bukkit.util.wrap\n\nclass ModelMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val mid = mlc.modelPlaceholder\n    private val s = mlc.toPlaceholderFloat(arrayOf(\"scale\", \"s\"), 1F)\n    private val st = mlc.toPlaceholderBoolean(arrayOf(\"sight-trace\", \"st\"), true)\n    private val da = mlc.toPlaceholderBoolean(arrayOf(\"damageanimation\", \"da\", \"animation\"), false)\n    private val dt = mlc.toPlaceholderBoolean(arrayOf(\"damagetint\", \"tint\", \"dt\"), true)\n    private val r = mlc.toPlaceholderBoolean(arrayOf(\"remove\", \"r\"), false)\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        val e = p0.caster.entity.bukkitEntity\n        return if (r(args)) {\n            if (mid(args)?.let {\n                BetterModel.registryOrNull(e.uniqueId)?.remove(it)\n            } == true) SkillResult.SUCCESS else SkillResult.CONDITION_FAILED\n        } else {\n            BetterModel.modelOrNull(mid(args) ?: return SkillResult.CONDITION_FAILED)?.let {\n                it.create(e.wrap(), TrackerModifier(\n                    st(args),\n                    da(args),\n                    dt(args)\n                )) { t ->\n                    t.scaler(ModelScaler.entity().multiply(s(args)))\n                }\n                SkillResult.SUCCESS\n            } ?: SkillResult.CONDITION_FAILED\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/MountModelMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.ITargetedEntitySkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.api.mount.MountControllers\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.bukkit.util.wrap\n\nclass MountModelMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), ITargetedEntitySkill {\n\n    companion object {\n        private val dismountListener = HitBoxListener.builder()\n            .dismount { h, _ ->\n                h.removeHitBox()\n            }\n            .build()\n    }\n\n    private val model = mlc.modelPlaceholder\n    private val driver = mlc.toPlaceholderBoolean(arrayOf(\"driver\", \"d\", \"drive\"), true)\n    private val damagemount = mlc.toPlaceholderBoolean(arrayOf(\"damagemount\", \"dmg\"), false)\n    private val interact = mlc.toPlaceholderString(arrayOf(\"mode\", \"m\")) exec@ {\n        when (it) {\n            \"walking\" -> MountControllers.WALK.modifier()\n            \"force_walking\" -> MountControllers.WALK.modifier()\n                .canDismountBySelf(false)\n            \"flying\" -> MountControllers.FLY.modifier()\n            \"force_flying\" -> MountControllers.FLY.modifier()\n                .canDismountBySelf(false)\n            else -> null\n        }?.canMount(false)\n    }\n\n    private val seat = mlc.toPlaceholderStringList(MM_SEAT) {\n        it.toSet()\n    }\n\n    init {\n        isAsyncSafe = false\n    }\n\n    override fun castAtEntity(p0: SkillMetadata, p1: AbstractEntity): SkillResult {\n        val args = toPlaceholderArgs(p0, p1)\n        return p0.toTracker(model(args))?.let { tracker ->\n            val set = seat(args)\n            tracker.hitbox(dismountListener) {\n                (set.isEmpty() || set.contains(it.name().name)) && it.hitBox?.hasMountDriver() != true\n            }?.let { hitBox ->\n                hitBox.mountController(interact(args)\n                    ?.canControl(driver(args))\n                    ?.canBeDamagedByRider(damagemount(args))\n                    ?.build()\n                    ?: MountControllers.WALK)\n                hitBox.mount(p1.bukkitEntity.wrap())\n                SkillResult.SUCCESS\n            }\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/PairModelMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.ITargetedEntitySkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.modelPlaceholder\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderArgs\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderBoolean\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toTracker\nimport kr.toxicity.model.bukkit.util.wrap\nimport org.bukkit.entity.Player\n\nclass PairModelMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), ITargetedEntitySkill {\n\n    private val model = mlc.modelPlaceholder\n    private val remove = mlc.toPlaceholderBoolean(arrayOf(\"remove\", \"r\"), false)\n\n    override fun castAtEntity(p0: SkillMetadata, p1: AbstractEntity): SkillResult {\n        val target = p1.bukkitEntity as? Player ?: return SkillResult.CONDITION_FAILED\n        val args = toPlaceholderArgs(p0, p1)\n        p0.toTracker(model(args))?.let {\n            if (remove(args)) {\n                it.show(target.wrap())\n            } else {\n                it.hide(target.wrap())\n            }\n        }\n        return SkillResult.SUCCESS\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/PartVisibilityMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.script.PartVisibilityScript\n\nclass PartVisibilityMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicateNullable\n    private val v = mlc.toPlaceholderBoolean(arrayOf(\"visibility\", \"visible\", \"v\"), true)\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            PartVisibilityScript(\n                predicate(args),\n                v(args)\n            ).accept(it)\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/PlayLimbAnimMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.ITargetedEntitySkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.animation.AnimationIterator\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.util.function.FloatSupplier\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.bukkit.util.wrap\nimport kr.toxicity.model.util.componentOf\nimport kr.toxicity.model.util.toComponent\nimport kr.toxicity.model.util.warn\nimport net.kyori.adventure.text.format.NamedTextColor\nimport org.bukkit.entity.Player\n\nclass PlayLimbAnimMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), ITargetedEntitySkill {\n\n    private val modelId = mlc.modelPlaceholder\n    private val animationId = mlc.toPlaceholderString(arrayOf(\"animation\", \"anim\", \"a\"))\n    private val speed = mlc.toNullablePlaceholderFloat(arrayOf(\"speed\", \"sp\"))\n    private val remove = mlc.toPlaceholderBoolean(arrayOf(\"remove\", \"r\"), false)\n    private val mode = mlc.toPlaceholderString(arrayOf(\"mode\", \"loop\"), \"once\") {\n        when (it?.lowercase()) {\n            \"loop\" -> AnimationIterator.Type.LOOP\n            \"hold\" -> AnimationIterator.Type.HOLD_ON_LAST\n            else -> AnimationIterator.Type.PLAY_ONCE\n        }\n    }\n\n    override fun castAtEntity(data: SkillMetadata, target: AbstractEntity): SkillResult {\n        val targetPlayer = target.bukkitEntity as? Player ?: return SkillResult.CONDITION_FAILED\n        val args = toPlaceholderArgs(data, target)\n\n        val removal = remove(args)\n        val currentModelId = modelId(args) ?: return SkillResult.INVALID_CONFIG\n        val currentAnimationId = animationId(args) ?: if (!removal) return SkillResult.INVALID_CONFIG else \"\"\n\n        if (removal) {\n            BetterModel.registryOrNull(targetPlayer.uniqueId)?.remove(currentModelId)\n        } else {\n            val renderer = BetterModel.limb(currentModelId).orElse(null) ?: return SkillResult.CONDITION_FAILED.apply {\n                warn(componentOf(\n                    \"Error: Player not found: \".toComponent(),\n                    currentModelId.toComponent(NamedTextColor.RED)\n                ))\n            }\n            val loopType = mode(args)\n            val modifier = AnimationModifier(0, 0, loopType, speed(args)?.let(FloatSupplier::of))\n            renderer.getOrCreate(targetPlayer.wrap()).run {\n                if (!animate(\n                        currentAnimationId,\n                        modifier,\n                        if (loopType == AnimationIterator.Type.PLAY_ONCE) {\n                            { close() }\n                        } else {\n                            {}\n                        }\n                )) close()\n            }\n\n        }\n        return SkillResult.SUCCESS\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/RemapModelMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.modelPlaceholder\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderArgs\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toPlaceholderString\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.toTracker\nimport kr.toxicity.model.script.RemapScript\n\nclass RemapModelMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val modelId = mlc.modelPlaceholder\n    private val newModelId = mlc.toPlaceholderString(arrayOf(\"newmodelid\", \"n\", \"nid\", \"newmodel\"))\n    private val map = mlc.toPlaceholderString(arrayOf(\"map\"))\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        val m = modelId(args) ?: return SkillResult.INVALID_CONFIG\n        return p0.toTracker(m)?.let {\n            RemapScript(\n                newModelId(args) ?: return SkillResult.INVALID_CONFIG,\n                map(args)\n            ).accept(it)\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/StateMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.adapters.AbstractEntity\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.ITargetedEntitySkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.util.function.FloatSupplier\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.bukkit.util.wrap\nimport org.bukkit.entity.Player\n\nclass StateMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill, ITargetedEntitySkill {\n\n    private val model = mlc.modelPlaceholder\n    private val state = mlc.toPlaceholderString(arrayOf(\"state\", \"s\"))\n    private val li = mlc.toPlaceholderInteger(arrayOf(\"li\"), 1)\n    private val lo = mlc.toPlaceholderInteger(arrayOf(\"lo\"))\n    private val sp = mlc.toNullablePlaceholderFloat(arrayOf(\"speed\", \"sp\"))\n    private val remove = mlc.toPlaceholderBoolean(arrayOf(\"remove\", \"r\"))\n    private val priority = mlc.toPlaceholderInteger(arrayOf(\"p\", \"pr\", \"priority\"))\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        return cast(null, p0)\n    }\n\n    override fun castAtEntity(p0: SkillMetadata, p1: AbstractEntity): SkillResult {\n        return cast(p1.bukkitEntity as? Player, p0)\n    }\n\n    private fun cast(player: Player?, meta: SkillMetadata): SkillResult {\n        val args = meta.toPlaceholderArgs()\n        return meta.toTracker(model(args))?.let {\n            val s = state(args) ?: return SkillResult.CONDITION_FAILED\n            if (remove(args)) it.stopAnimation(s) else it.animate(s, AnimationModifier.builder()\n                .start(li(args))\n                .end(lo(args))\n                .speed(sp(args)?.let(FloatSupplier::of))\n                .player(player?.wrap())\n                .priority(priority(args))\n                .build())\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/mechanic/TintMechanic.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.mechanic\n\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.INoTargetSkill\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.SkillResult\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.script.TintScript\n\nclass TintMechanic(mlc: MythicLineConfig) : AbstractSkillMechanic(mlc), INoTargetSkill {\n\n    private val model = mlc.modelPlaceholder\n    private val predicate = mlc.bonePredicateNullable\n    private val damageTint = mlc.toPlaceholderBoolean(arrayOf(\"damagetint\", \"d\", \"dmg\", \"damage\"))\n    private val color = mlc.toPlaceholderColor(arrayOf(\"color\", \"c\")) {\n        it ?: 0xFFFFFF\n    }\n\n    override fun cast(p0: SkillMetadata): SkillResult {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.let {\n            TintScript(\n                predicate(args),\n                color(args),\n                damageTint(args)\n            ).accept(it)\n            SkillResult.SUCCESS\n        } ?: SkillResult.CONDITION_FAILED\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/mythicmobs/targeter/ModelPartTargeter.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.mythicmobs.targeter\n\nimport io.lumine.mythic.api.adapters.AbstractLocation\nimport io.lumine.mythic.api.config.MythicLineConfig\nimport io.lumine.mythic.api.skills.SkillMetadata\nimport io.lumine.mythic.api.skills.targeters.ILocationTargeter\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.*\nimport kr.toxicity.model.util.boneName\n\nclass ModelPartTargeter(mlc: MythicLineConfig) : ILocationTargeter {\n\n    private val model = mlc.modelPlaceholder\n    private val part = mlc.toPlaceholderString(MM_PART_ID) {\n        it?.boneName?.name\n    }\n\n    override fun getLocations(p0: SkillMetadata): Collection<AbstractLocation> {\n        val args = p0.toPlaceholderArgs()\n        return p0.toTracker(model(args))?.bone(part(args) ?: return emptyList())?.hitBoxPosition()?.let {\n            listOf(p0.caster.entity.location.add(\n                it.x.toDouble(),\n                it.y.toDouble(),\n                it.z.toDouble()\n            ))\n        } ?: emptyList()\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/nexo/NexoCompatibility.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.nexo\n\nimport com.nexomc.nexo.api.events.resourcepack.NexoPrePackGenerateEvent\nimport kr.toxicity.model.api.BetterModelPlatform\nimport kr.toxicity.model.bukkit.compatibility.Compatibility\nimport kr.toxicity.model.bukkit.util.PLUGIN\nimport kr.toxicity.model.bukkit.util.registerListener\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.text.format.NamedTextColor\nimport org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\n\nclass NexoCompatibility : Compatibility {\n    override fun start() {\n        if (CONFIG.mergeWithExternalResources()) PLUGIN.skipInitialReload()\n        registerListener(object : Listener {\n            @EventHandler\n            fun NexoPrePackGenerateEvent.generate() {\n                if (!CONFIG.mergeWithExternalResources()) return\n                when (val result = PLATFORM.reload()) {\n                    is BetterModelPlatform.ReloadResult.Success -> {\n                        result.packResult().directory()?.let {\n                            addResourcePack(it)\n                            info(\"Successfully merged with Nexo.\".toComponent(NamedTextColor.GREEN))\n                        }\n                    }\n\n                    is BetterModelPlatform.ReloadResult.OnReload -> {\n                        warn(\"BetterModel is still on reload!\".toComponent(NamedTextColor.RED))\n                    }\n\n                    is BetterModelPlatform.ReloadResult.Failure -> {\n                        result.throwable.handleException(\"Unable to merge with Nexo.\")\n                    }\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/compatibility/skinsrestorer/SkinsRestorerCompatibility.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.compatibility.skinsrestorer\n\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.bukkit.compatibility.Compatibility\nimport kr.toxicity.model.bukkit.util.wrap\nimport kr.toxicity.model.manager.ProfileManagerImpl\nimport kr.toxicity.model.manager.SkinManagerImpl\nimport kr.toxicity.model.util.PLATFORM\nimport net.skinsrestorer.api.SkinsRestorerProvider\nimport net.skinsrestorer.api.event.SkinApplyEvent\nimport org.bukkit.Bukkit\nimport org.bukkit.entity.Player\nimport java.util.concurrent.CompletableFuture\n\nclass SkinsRestorerCompatibility : Compatibility {\n\n    private val manager = SkinsRestorerProvider.get()\n\n    override fun start() {\n        manager.eventBus.subscribe(PLATFORM, SkinApplyEvent::class.java) {\n            val player = it.getPlayer(Player::class.java)\n            SkinManagerImpl.removeCache(ModelProfile.of(player.wrap()))\n        }\n        ProfileManagerImpl.supplier {\n            SkinsRestorerProfile(it)\n        }\n    }\n\n    private inner class SkinsRestorerProfile(\n        private val info: ModelProfileInfo\n    ) : ModelProfile.Uncompleted {\n        override fun info(): ModelProfileInfo = info\n\n        override fun complete(): CompletableFuture<ModelProfile> = CompletableFuture.supplyAsync {\n            manager.playerStorage\n                .getSkinForPlayer(\n                    info.id,\n                    info.name,\n                    Bukkit.getOnlineMode()\n                ).map { skin ->\n                    ModelProfile.of(\n                        info,\n                        ProfileManagerImpl.skin(skin.value)\n                    )\n                }.orElse(null)\n        }\n\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/configuration/PluginConfiguration.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.configuration\n\nimport kr.toxicity.model.bukkit.util.toYaml\nimport kr.toxicity.model.util.DATA_FOLDER\nimport kr.toxicity.model.util.PLATFORM\nimport kr.toxicity.model.util.ifNull\nimport org.bukkit.configuration.file.YamlConfiguration\nimport java.io.File\n\nenum class PluginConfiguration(\n    private val dir: String\n) {\n    CONFIG(\"config.yml\"),\n    ;\n\n    fun create(): YamlConfiguration {\n        val file = File(DATA_FOLDER, dir)\n        val exists = file.exists()\n        if (!exists) PLATFORM.saveResource(dir)\n        val yaml = file.toYaml()\n        val newYaml = PLATFORM.getResource(dir).ifNull { \"Resource '$dir' not found.\" }.use {\n            it.toYaml()\n        }\n        yaml.getKeys(true).forEach {\n            if (!newYaml.contains(it)) yaml.set(it, null)\n        }\n        newYaml.getKeys(true).forEach {\n            if (!yaml.contains(it)) yaml.set(it, newYaml.get(it))\n            yaml.setComments(it ,newYaml.getComments(it))\n        }\n        return yaml.apply {\n            save(file)\n        }\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/manager/CompatibilityManager.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.manager\n\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.bukkit.compatibility.citizens.CitizensCompatibility\nimport kr.toxicity.model.bukkit.compatibility.mythicmobs.MythicMobsCompatibility\nimport kr.toxicity.model.bukkit.compatibility.nexo.NexoCompatibility\nimport kr.toxicity.model.bukkit.compatibility.skinsrestorer.SkinsRestorerCompatibility\nimport kr.toxicity.model.bukkit.purpur.PurpurHook\nimport kr.toxicity.model.bukkit.util.registerListener\nimport kr.toxicity.model.manager.GlobalManager\nimport kr.toxicity.model.manager.ReloadPipeline\nimport kr.toxicity.model.util.info\nimport kr.toxicity.model.util.toComponent\nimport net.kyori.adventure.text.format.NamedTextColor\nimport org.bukkit.Bukkit\nimport org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.server.PluginEnableEvent\n\nobject CompatibilityManager : GlobalManager {\n\n    private val compatibilities = mutableMapOf(\n        \"MythicMobs\" to {\n            MythicMobsCompatibility()\n        },\n        \"Citizens\" to {\n            CitizensCompatibility()\n        },\n        \"SkinsRestorer\" to {\n            SkinsRestorerCompatibility()\n        },\n        \"Nexo\" to {\n            NexoCompatibility()\n        }\n    )\n\n    override fun start() {\n        if (BetterModelBukkit.IS_PURPUR) PurpurHook.start()\n        Bukkit.getPluginManager().run {\n            compatibilities.entries.removeIf { (k, v) ->\n                if (isPluginEnabled(k)) {\n                    v().start()\n                    k.hookMessage()\n                    true\n                } else false\n            }\n        }\n        registerListener(object : Listener {\n            @EventHandler\n            fun PluginEnableEvent.enable() {\n                val name = plugin.name\n                compatibilities.remove(name)?.let {\n                    it().start()\n                    name.hookMessage()\n                }\n            }\n        })\n    }\n\n    private fun String.hookMessage() = info(\"Plugin hooks $this\".toComponent(NamedTextColor.AQUA))\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/manager/EntityManager.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.manager\n\nimport com.destroystokyo.paper.event.entity.EntityAddToWorldEvent\nimport com.destroystokyo.paper.event.entity.EntityJumpEvent\nimport com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent\nimport com.destroystokyo.paper.event.player.PlayerJumpEvent\nimport it.unimi.dsi.fastutil.objects.ReferenceSet\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.tracker.EntityTracker\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerExtraAnimation\nimport kr.toxicity.model.bukkit.util.registerListener\nimport kr.toxicity.model.bukkit.util.wrap\nimport kr.toxicity.model.manager.GlobalManager\nimport kr.toxicity.model.manager.ReloadPipeline\nimport kr.toxicity.model.util.PLATFORM\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.Player\nimport org.bukkit.event.EventHandler\nimport org.bukkit.event.EventPriority\nimport org.bukkit.event.Listener\nimport org.bukkit.event.entity.*\nimport org.bukkit.event.player.PlayerChangedWorldEvent\nimport org.bukkit.event.player.PlayerInteractEntityEvent\nimport org.bukkit.event.player.PlayerQuitEvent\nimport org.bukkit.event.world.EntitiesUnloadEvent\nimport org.bukkit.inventory.EquipmentSlot\nimport org.bukkit.potion.PotionEffectType\n\nobject EntityManager : GlobalManager {\n\n    private val effectSet = ReferenceSet.of(\n        PotionEffectType.GLOWING,\n        PotionEffectType.INVISIBILITY\n    )\n\n    private class PaperListener : Listener { //More accurate world change event for Paper\n        @EventHandler(priority = EventPriority.MONITOR)\n        fun EntityRemoveFromWorldEvent.remove() {\n            BetterModel.registryOrNull(entity.uniqueId)?.despawn()\n        }\n        @EventHandler(priority = EventPriority.MONITOR)\n        fun EntityAddToWorldEvent.add() {\n            BetterModel.registryOrNull(entity.wrap())?.refresh()\n        }\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun EntityJumpEvent.jump() {\n            entity.forEachTracker { it.animate(TrackerExtraAnimation.JUMP) }\n        }\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun PlayerJumpEvent.jump() {\n            player.forEachTracker { it.animate(TrackerExtraAnimation.JUMP) }\n        }\n    }\n\n    private class SpigotListener : Listener { //Portal event for Spigot\n        @EventHandler(priority = EventPriority.MONITOR)\n        fun EntityRemoveEvent.remove() {\n            BetterModel.registryOrNull(entity.uniqueId)?.despawn()\n        }\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun EntitySpawnEvent.spawn() {\n            BetterModel.registryOrNull(entity.wrap())?.refresh()\n        }\n        @EventHandler(priority = EventPriority.MONITOR)\n        fun PlayerChangedWorldEvent.change() {\n            BetterModel.registryOrNull(player.uniqueId)?.let {\n                it.despawn()\n                it.refresh()\n            }\n        }\n    }\n\n    //Event handlers\n    private val standardListener = object : Listener {\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun EntityPotionEffectEvent.potion() { //Apply potion effect\n            if (action == EntityPotionEffectEvent.Action.CHANGED) return\n            if (oldEffect?.let { it.type in effectSet } == true || newEffect?.let { it.type in effectSet } == true) entity.forEachTracker { it.updateBaseEntity() }\n        }\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun EntityDismountEvent.dismount() { //Dismount\n            val e = dismounted\n            isCancelled = e is HitBox && (e.mountController().canFly() || !e.mountController().canDismountBySelf()) && !e.forceDismount()\n        }\n        @EventHandler(priority = EventPriority.MONITOR)\n        fun PlayerQuitEvent.quit() { //Quit\n            val wrap = player.wrap()\n            BetterModel.registryOrNull(wrap.uuid())?.close()\n            PLATFORM.scheduler().asyncTask {\n                EntityTrackerRegistry.registries { registry -> registry.remove(wrap) }\n            }\n            (player.vehicle as? HitBox)?.dismount(wrap)\n        }\n        @EventHandler(priority = EventPriority.MONITOR)\n        fun PlayerDeathEvent.death() {\n            BetterModel.registryOrNull(entity.uniqueId)?.despawn()\n        }\n        @EventHandler(priority = EventPriority.MONITOR)\n        fun EntitiesUnloadEvent.unload() { //Chunk unload\n            entities.forEach { entity ->\n                BetterModel.registryOrNull(entity.uniqueId)?.despawn()\n            }\n        }\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun EntityDeathEvent.death() { //Death\n            entity.forEachTracker {\n                it.animate(TrackerExtraAnimation.DEATH)\n            }\n        }\n\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun PlayerInteractEntityEvent.interact() { //Interact base entity based on interaction entity\n            (rightClicked as? HitBox)?.let {\n                if (hand == EquipmentSlot.HAND && !player.triggerDismount(rightClicked)) player.triggerMount(it)\n            }\n        }\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n        fun EntityDamageEvent.damage() { //Damage\n            if (this is EntityDamageByEntityEvent) {\n                val victim = entity.run {\n                    if (this is HitBox) source().uuid() else uniqueId\n                }\n                val v = damager.vehicle\n                if (v is HitBox && !v.mountController().canBeDamagedByRider() && v.source().uuid() == victim) {\n                    isCancelled = true\n                    return\n                }\n//                    if (cause == EntityDamageEvent.DamageCause.ENTITY_ATTACK) {\n//                        EntityTracker.tracker(damager)?.animate(\"attack\", AnimationModifier.DEFAULT_WITH_PLAY_ONCE)\n//                    }\n            }\n            entity.forEachTracker {\n                it.animate(TrackerExtraAnimation.DAMAGE)\n                it.damageTint()\n            }\n        }\n    }\n    private val platformListener = if (BetterModelBukkit.IS_PAPER) PaperListener() else SpigotListener()\n\n    //Lifecycles\n    override fun start() {\n        registerListener(standardListener)\n        registerListener(platformListener)\n    }\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n        EntityTrackerRegistry.registries(EntityTrackerRegistry::reload)\n    }\n\n    override fun end() {\n        EntityTrackerRegistry.registries {\n            it.save()\n            it.close(Tracker.CloseReason.PLUGIN_DISABLE)\n        }\n    }\n\n    //Extension\n    private fun Entity.forEachTracker(block: (EntityTracker) -> Unit) {\n        BetterModel.registryOrNull(uniqueId)?.trackers()?.forEach(block)\n    }\n\n    private fun Player.triggerDismount(e: Entity): Boolean {\n        val previous = vehicle\n        if (previous !is HitBox) return false\n        val uuid = if (e is HitBox) e.source().uuid() else e.uniqueId\n        if (previous.source().uuid() == uuid && previous.mountController().canDismountBySelf()) {\n            previous.dismount(wrap())\n            return true\n        }\n        return false\n    }\n\n    private fun Player.triggerMount(hitBox: HitBox) {\n        if (hitBox.mountController().canMount()) hitBox.mount(wrap())\n    }\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/manager/PlayerManagerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.manager\n\nimport kr.toxicity.model.api.manager.PlayerManager\nimport kr.toxicity.model.api.nms.PlayerChannelHandler\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.bukkit.util.registerListener\nimport kr.toxicity.model.bukkit.util.wrap\nimport kr.toxicity.model.manager.GlobalManager\nimport kr.toxicity.model.manager.ReloadPipeline\nimport kr.toxicity.model.manager.SkinManagerImpl\nimport kr.toxicity.model.util.PLATFORM\nimport kr.toxicity.model.util.handleFailure\nimport org.bukkit.event.EventHandler\nimport org.bukkit.event.EventPriority\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\nimport org.bukkit.event.player.PlayerQuitEvent\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\nobject PlayerManagerImpl : PlayerManager, GlobalManager {\n\n    private val playerMap = ConcurrentHashMap<UUID, PlayerChannelHandler>()\n\n    override fun start() {\n        registerListener(object : Listener {\n            @EventHandler(priority = EventPriority.HIGHEST)\n            fun PlayerJoinEvent.join() {\n                if (player.isOnline) runCatching { //For fake player\n                    player.wrap().register()\n                }.handleFailure {\n                    \"Unable to load ${player.name}'s data.\"\n                }\n            }\n            @EventHandler(priority = EventPriority.MONITOR)\n            fun PlayerQuitEvent.quit() {\n                playerMap.remove(player.uniqueId)?.use {\n                    SkinManagerImpl.removeCache(it.base().profile())\n                }\n            }\n        })\n    }\n\n    private fun PlatformPlayer.register() = playerMap.computeIfAbsent(uuid()) {\n        PLATFORM.nms().inject(this)\n    }.apply {\n        SkinManagerImpl.complete(base().profile().asUncompleted())\n    }\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n    }\n\n    override fun end() {\n        playerMap.values.removeIf {\n            it.use { used -> SkinManagerImpl.removeCache(used.base().profile()) }\n            true\n        }\n    }\n\n\n    override fun player(uuid: UUID): PlayerChannelHandler? = playerMap[uuid]\n    override fun player(player: PlatformPlayer): PlayerChannelHandler = player.register()\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/scheduler/BukkitScheduler.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.scheduler\n\nimport kr.toxicity.model.api.bukkit.scheduler.BukkitModelScheduler\nimport kr.toxicity.model.api.scheduler.ModelTask\nimport kr.toxicity.model.bukkit.util.PLUGIN\nimport org.bukkit.Bukkit\nimport org.bukkit.Location\nimport org.bukkit.scheduler.BukkitTask\n\nclass BukkitScheduler : BukkitModelScheduler {\n\n    private fun BukkitTask.wrap() = object : ModelTask {\n        override fun isCancelled(): Boolean = this@wrap.isCancelled\n        override fun cancel() {\n            this@wrap.cancel()\n        }\n    }\n\n    private fun ifEnabled(block: () -> ModelTask?): ModelTask? {\n        return if (PLUGIN.isEnabled) block() else null\n    }\n\n    override fun task(location: Location, runnable: Runnable) = ifEnabled {\n        Bukkit.getScheduler().runTask(PLUGIN, runnable).wrap()\n    }\n    override fun taskLater(location: Location, delay: Long, runnable: Runnable) = ifEnabled {\n        Bukkit.getScheduler().runTaskLater(PLUGIN, runnable, delay).wrap()\n    }\n    override fun asyncTask(runnable: Runnable) = Bukkit.getScheduler().runTaskAsynchronously(PLUGIN, runnable).wrap()\n    override fun asyncTaskLater(delay: Long, runnable: Runnable) = Bukkit.getScheduler().runTaskLaterAsynchronously(PLUGIN, runnable, delay).wrap()\n    override fun asyncTaskTimer(delay: Long, period: Long, runnable: Runnable) = Bukkit.getScheduler().runTaskTimerAsynchronously(PLUGIN, runnable, delay, period).wrap()\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/scheduler/PaperScheduler.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.scheduler\n\nimport io.papermc.paper.threadedregions.scheduler.ScheduledTask\nimport kr.toxicity.model.api.bukkit.scheduler.BukkitModelScheduler\nimport kr.toxicity.model.api.scheduler.ModelTask\nimport kr.toxicity.model.bukkit.util.PLUGIN\nimport org.bukkit.Bukkit\nimport org.bukkit.Location\nimport java.util.concurrent.TimeUnit\n\nclass PaperScheduler : BukkitModelScheduler {\n\n    private fun ScheduledTask.wrap() = object : ModelTask {\n        override fun isCancelled(): Boolean = this@wrap.isCancelled\n        override fun cancel() {\n            this@wrap.cancel()\n        }\n    }\n\n    private fun ifEnabled(block: () -> ModelTask?): ModelTask? {\n        return if (PLUGIN.isEnabled) block() else null\n    }\n\n    override fun task(location: Location, runnable: Runnable): ModelTask? = ifEnabled {\n        Bukkit.getRegionScheduler().run(PLUGIN, location) {\n            runnable.run()\n        }.wrap()\n    }\n\n    override fun taskLater(location: Location, delay: Long, runnable: Runnable): ModelTask? = ifEnabled {\n        Bukkit.getRegionScheduler().runDelayed(PLUGIN, location, {\n            runnable.run()\n        }, delay).wrap()\n    }\n\n    override fun asyncTask(runnable: Runnable) = Bukkit.getAsyncScheduler().runNow(PLUGIN) {\n        runnable.run()\n    }.wrap()\n\n    override fun asyncTaskLater(delay: Long, runnable: Runnable) = Bukkit.getAsyncScheduler().runDelayed(PLUGIN, {\n        runnable.run()\n    }, (delay * 50).coerceAtLeast(1), TimeUnit.MILLISECONDS).wrap()\n\n    override fun asyncTaskTimer(delay: Long, period: Long, runnable: Runnable) = Bukkit.getAsyncScheduler().runAtFixedRate(PLUGIN, {\n        runnable.run()\n    }, (delay * 50).coerceAtLeast(1), (period * 50).coerceAtLeast(1), TimeUnit.MILLISECONDS).wrap()\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/util/BukkitWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.util\n\nimport kr.toxicity.model.api.bukkit.platform.*\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter.adapt\nimport kr.toxicity.model.api.platform.*\nimport org.bukkit.Location\nimport org.bukkit.OfflinePlayer\nimport org.bukkit.World\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.LivingEntity\nimport org.bukkit.entity.Player\nimport org.bukkit.inventory.ItemStack\n\nfun Entity.wrap() = adapt(this)\nfun LivingEntity.wrap() = adapt(this)\nfun OfflinePlayer.wrap() = adapt(this)\nfun Player.wrap() = adapt(this)\nfun Location.wrap() = adapt(this)\nfun World.wrap() = adapt(this)\nfun ItemStack.wrap() = adapt(this)\n\nfun PlatformEntity.unwarp(): Entity = (this as BukkitEntity).source()\nfun PlatformLivingEntity.unwarp(): LivingEntity = (this as BukkitLivingEntity).source()\nfun PlatformOfflinePlayer.unwarp(): OfflinePlayer = (this as BukkitOfflinePlayer).source()\nfun PlatformPlayer.unwarp(): Player = (this as BukkitPlayer).source()\nfun PlatformLocation.unwarp(): Location = (this as BukkitLocation).source()\nfun PlatformWorld.unwarp(): World = (this as BukkitWorld).source()\nfun PlatformItemStack.unwarp(): ItemStack = (this as BukkitItemStack).source()\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/util/Entities.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.util\n\nimport kr.toxicity.model.api.BetterModel\nimport org.bukkit.entity.Entity\n\nfun Entity.toTracker(model: String?) = toRegistry()?.tracker(model)\nfun Entity.toRegistry() = BetterModel.registryOrNull(uniqueId)\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/util/Events.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.util\n\nimport org.bukkit.Bukkit\nimport org.bukkit.event.Listener\n\nfun registerListener(listener: Listener) {\n    Bukkit.getPluginManager().registerEvents(listener, PLUGIN)\n}\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/util/Plugins.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.util\n\nimport kr.toxicity.model.bukkit.BetterModelPlugin\nimport kr.toxicity.model.util.PLATFORM\n\nval PLUGIN get() = PLATFORM as BetterModelPlugin\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/util/Senders.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.util\n\nimport kr.toxicity.model.bukkit.BetterModelLibrary\nimport net.kyori.adventure.platform.bukkit.BukkitAudiences\nimport org.bukkit.command.CommandSender\n\nval ADVENTURE_PLATFORM = if (BetterModelLibrary.ADVENTURE_PLATFORM.isLoaded) BukkitAudiences.create(PLUGIN) else null\n\nfun CommandSender.audience() = ADVENTURE_PLATFORM?.sender(this) ?: this\n"
  },
  {
    "path": "core/bukkit-core/src/main/kotlin/kr/toxicity/model/bukkit/util/Yamls.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.util\n\nimport org.bukkit.configuration.file.YamlConfiguration\nimport java.io.File\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.nio.charset.StandardCharsets\n\nfun File.toYaml() = YamlConfiguration.loadConfiguration(this)\nfun InputStream.toYaml() = InputStreamReader(this, StandardCharsets.UTF_8).use { reader ->\n    reader.buffered().use(YamlConfiguration::loadConfiguration)\n}\n"
  },
  {
    "path": "core/src/main/java/kr/toxicity/model/BetterModelPlatformImpl.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model;\n\nimport kr.toxicity.model.api.BetterModelPlatform;\nimport kr.toxicity.model.manager.ReloadPipeline;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.InputStream;\nimport java.util.function.BiConsumer;\n\npublic interface BetterModelPlatformImpl extends BetterModelPlatform {\n\n    void saveResource(@NotNull String resourcePath);\n\n    void loadAssets(@NotNull ReloadPipeline pipeline, @NotNull String prefix, @NotNull BiConsumer<String, InputStream> consumer);\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/BetterModelEvaluatorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model\n\nimport gg.moonflower.molangcompiler.api.MolangCompiler\nimport gg.moonflower.molangcompiler.api.MolangRuntime\nimport kr.toxicity.model.api.BetterModelEvaluator\nimport kr.toxicity.model.api.util.function.Float2FloatFunction\n\nclass BetterModelEvaluatorImpl : BetterModelEvaluator {\n\n    private val molang = MolangCompiler.create(MolangCompiler.DEFAULT_FLAGS, javaClass.classLoader)\n\n    private fun Float.query() = MolangRuntime.runtime()\n        .setQuery(\"life_time\", this)\n        .setQuery(\"anim_time\", this)\n        .create()\n\n    override fun compile(expression: String): Float2FloatFunction {\n        val compiled = molang.compile(expression)\n        return Float2FloatFunction {\n            it.query().safeResolve(compiled)\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/BetterModelEventBusImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model\n\nimport kr.toxicity.model.api.BetterModelEventBus\nimport kr.toxicity.model.api.event.CancellableEvent\nimport kr.toxicity.model.api.event.ModelEvent\nimport kr.toxicity.model.api.event.ModelEventApplication\nimport kr.toxicity.model.api.event.ModelEventListener\nimport kr.toxicity.model.api.util.lock.DuplexLock\nimport kr.toxicity.model.util.handleFailure\nimport java.lang.ref.WeakReference\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.function.Consumer\nimport java.util.function.Supplier\n\nclass BetterModelEventBusImpl(\n    private val externalCallback: (Class<out ModelEvent>, Supplier<out ModelEvent>) -> BetterModelEventBus.Result = { _, _ -> BetterModelEventBus.Result.NO_EVENT_HANDLER }\n) : BetterModelEventBus {\n\n    private val subscribers = ConcurrentHashMap<Class<*>, BusManager>()\n\n    override fun <T : ModelEvent> subscribe(application: ModelEventApplication, eventClass: Class<T>, consumer: Consumer<T>): ModelEventListener {\n        return subscribers.computeIfAbsent(eventClass) { BusManager(it) }\n            .register(application, consumer)\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T : ModelEvent> call(eventClass: Class<out T>, eventSupplier: Supplier<T>): BetterModelEventBus.Result {\n        return subscribers[eventClass]?.let { manager ->\n            eventSupplier\n                .get()\n                .also(manager)\n                .also { externalCallback(eventClass) { it } }\n                .let { event ->\n                    if (event !is CancellableEvent || !event.isCancelled()) BetterModelEventBus.Result.SUCCESS else BetterModelEventBus.Result.FAIL\n                }\n        } ?: run {\n            externalCallback(eventClass) { eventSupplier.get() }\n        }\n    }\n\n    private inner class BusManager(\n        private val clazz: Class<*>\n    ) : (ModelEvent) -> Unit {\n        private val map = ConcurrentHashMap<ModelEventApplication, ListenerRegistry>()\n\n        fun <T : ModelEvent> register(application: ModelEventApplication, consumer: Consumer<T>): ModelEventListener {\n            if (!application.isEnabled) return ModelEventListener.NONE\n            return map.compute(application) { k, v -> v?.takeIf { k.isEnabled } ?: ListenerRegistry() }?.add(consumer) ?: ModelEventListener.NONE\n        }\n\n        override fun invoke(p1: ModelEvent) {\n            synchronized(this) {\n                map.entries.removeIf { (application, registry) ->\n                    !application.isEnabled || runCatching {\n                        registry(p1)\n                    }.handleFailure {\n                        \"Unable to pass this event: ${clazz.simpleName}\"\n                    }.isFailure\n                }\n                if (map.isEmpty()) subscribers.remove(clazz, this)\n            }\n        }\n    }\n\n    private class ListenerRegistry : (ModelEvent) -> Unit {\n        val map = IdentityHashMap<ModelEventListener, Consumer<*>>()\n        val lock = DuplexLock()\n\n        fun add(consumer: Consumer<*>): ModelEventListener = ListenerImpl(this, consumer)\n\n        @Suppress(\"UNCHECKED_CAST\")\n        override fun invoke(p1: ModelEvent) {\n            lock.accessToReadLock {\n                map.values.forEach { consumer ->\n                    (consumer as Consumer<ModelEvent>).accept(p1)\n                }\n            }\n        }\n    }\n\n    private class ListenerImpl(\n        registry: ListenerRegistry,\n        consumer: Consumer<*>\n    ) : ModelEventListener {\n\n        private val ref = WeakReference(registry)\n\n        init {\n            registry.lock.accessToWriteLock {\n                registry.map[this] = consumer\n            }\n        }\n\n        override fun unregister() {\n            ref.get()?.let {\n                it.lock.accessToWriteLock { it.map.remove(this) }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/command/CommandBuildContext.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.command\n\nimport net.kyori.adventure.audience.Audience\nimport org.incendo.cloud.Command\nimport org.incendo.cloud.CommandManager\nimport org.incendo.cloud.description.Description\n\nclass CommandBuildContext(\n    val manager: CommandManager<Audience>,\n    val commandMapper: CommandBuilder.(Command.Builder<Audience>) -> Command.Builder<Audience>,\n    name: String,\n    description: String,\n    vararg aliases: String,\n) {\n    val root = CommandBuilder(\n        null,\n        this,\n        CommandBuilder.Info(name, Description.description(description), aliases.toList())\n    )\n\n    fun build() {\n        root.build().forEach {\n            manager.command(it)\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/command/CommandBuilder.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.command\n\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.text.TextComponent\nimport net.kyori.adventure.text.event.ClickEvent\nimport net.kyori.adventure.text.format.NamedTextColor.*\nimport net.kyori.adventure.text.format.TextDecoration\nimport org.incendo.cloud.Command\nimport org.incendo.cloud.description.Description\nimport org.incendo.cloud.parser.standard.IntegerParser\n\nclass CommandBuilder(\n    val parent: CommandBuilder?,\n    val context: CommandBuildContext,\n    val info: Info\n) : CommandLike {\n\n    private companion object {\n        const val PAGE_SPLIT_INDEX = 5\n\n        val prefix = listOf(\n            emptyComponentOf(),\n            \"------ BetterModel ${PLATFORM.semver()} ------\".toComponent(GRAY),\n            emptyComponentOf()\n        )\n\n        val fullPrefix = listOf(\n            prefix,\n            listOf(\n                componentOf {\n                    decorate(TextDecoration.BOLD)\n                    append(spaceComponentOf())\n                    append(\"[Wiki]\".toComponent {\n                        color(AQUA)\n                        toURLComponent(\"https://github.com/toxicity188/BetterModel/wiki\")\n                    })\n                    append(spaceComponentOf())\n                    append(\"[Download]\".toComponent {\n                        color(GREEN)\n                        toURLComponent(\"https://modrinth.com/plugin/bettermodel/versions\")\n                    })\n                    append(spaceComponentOf())\n                    append(\"[Discord]\".toComponent {\n                        color(BLUE)\n                        toURLComponent(\"https://discord.com/invite/rePyFESDbk\")\n                    })\n                },\n                emptyComponentOf()\n            )\n        ).flatten()\n\n        fun TextComponent.Builder.toURLComponent(url: String) = hoverEvent(componentOf(\n            url.toComponent(DARK_AQUA),\n            lineComponentOf(),\n            lineComponentOf(),\n            \"Click to open link.\".toComponent()\n        ).toHoverEvent()).clickEvent(ClickEvent.openUrl(url))\n    }\n\n    private val root: CommandBuilder = parent?.root ?: this\n    private val suggest: String = parent?.let { \"${it.suggest} ${info.name}\" } ?: info.simpleName\n    private val permission: String = parent?.let { \"${it.permission}.${info.name}\" } ?: info.name\n    private val children = mutableListOf<CommandLike>()\n    private val helpCommand by lazy {\n        val maxPage = children.size / PAGE_SPLIT_INDEX + 1\n        val helpComponents = (1..maxPage).map { index ->\n            (if (index == 1) fullPrefix else prefix).toMutableList()\n                .also { list ->\n                    children.subList(PAGE_SPLIT_INDEX * (index - 1), (PAGE_SPLIT_INDEX * index).coerceAtMost(children.size)).forEach {\n                        list += it.toComponent()\n                    }\n                    list += \"/$suggest [help] [page] - help command.\".toComponent(LIGHT_PURPLE)\n                    list += emptyComponentOf()\n                    list += \"---------< Page $index / $maxPage >---------\".toComponent(GRAY)\n                }.toTypedArray()\n        }\n        val builder = createBuilder()\n            .permission(\"$permission.help\")\n            .handler { ctx ->\n                val page = ctx.getOrDefault(\"page\", 1)\n                    .coerceAtLeast(1)\n                    .coerceAtMost(maxPage)\n                ctx.sender().info(*helpComponents[page - 1])\n            }\n        listOf(\n            builder\n                .optional(\"page\", IntegerParser.integerParser(1, maxPage))\n                .build(),\n            builder.literal(\"help\", \"h\")\n                .optional(\"page\", IntegerParser.integerParser(1, maxPage))\n                .build()\n        )\n    }\n\n    fun create(\n        name: String,\n        description: String,\n        vararg aliases: String,\n        builder: Command.Builder<Audience>.() -> Command.Builder<out Audience>\n    ) {\n        children += CommandLike.Cloud(createBuilder()\n            .mapInfo(Info(name, Description.description(description), aliases.toList()))\n            .run(builder)\n            .build())\n    }\n\n    data class Info(\n        val name: String,\n        val description: Description,\n        val aliases: List<String>\n    ) {\n        val simpleName get() = if (aliases.isNotEmpty()) aliases.minBy { it.length } else name\n    }\n\n    override fun toComponent(): TextComponent {\n        TODO(\"Not yet implemented\")\n    }\n\n    override fun build(): List<Command<out Audience>> = buildList {\n        children.flatMapTo(this) { it.build() }\n        addAll(helpCommand)\n    }\n\n    private fun Command.Builder<Audience>.mapInfo(info: Info) = literal(info.name, *info.aliases.toTypedArray())\n        .commandDescription(info.description)\n        .permission(\"$permission.${info.name}\")\n\n    private fun createBuilder(): Command.Builder<Audience> = parent?.createBuilder()?.mapInfo(info) ?: context.commandMapper(this, context.manager.commandBuilder(\n        info.name,\n        info.description,\n        *info.aliases.toTypedArray()\n    ))\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/command/CommandExtensions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.command\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.data.renderer.ModelRenderer\nimport net.kyori.adventure.audience.Audience\nimport org.incendo.cloud.Command\nimport org.incendo.cloud.CommandManager\nimport org.incendo.cloud.context.CommandContext\n\nfun CommandManager<Audience>.register(\n    name: String,\n    description: String,\n    commandMapper: CommandBuilder.(Command.Builder<Audience>) -> Command.Builder<Audience>,\n    vararg aliases: String,\n    block: CommandBuilder.() -> Unit\n) = CommandBuildContext(this, commandMapper, name, description, *aliases).run {\n    root.block()\n    build()\n}\n\ninline fun CommandContext<*>.limb(key: String, notFound: (String) -> ModelRenderer) = optional<String>(key).flatMap {\n    BetterModel.limb(it)\n}.orElse(null) ?: notFound(key)\n\ninline fun CommandContext<*>.model(key: String, notFound: (String) -> ModelRenderer) = optional<String>(key).flatMap {\n    BetterModel.model(it)\n}.orElse(null) ?: notFound(key)\n\ninline fun <T> CommandContext<*>.string(key: String, mapper: (String) -> T) = mapper(get(key))\n\nfun <T> CommandContext<*>.nullableString(key: String, mapper: (String) -> T): T? = optional<String>(key).map { mapper(it) }.orElse(null)\n\ninline fun <reified T : Any> CommandContext<*>.nullable(key: String): T? = optional<T>(key).orElse(null)\ninline fun <reified T : Any> CommandContext<*>.nullable(key: String, ifNotFound: T): T = optional<T>(key).orElse(null) ?: ifNotFound\ninline fun <reified T : Any> CommandContext<*>.nullable(key: String, ifNotFound: () -> T): T = optional<T>(key).orElse(null) ?: ifNotFound()\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/command/CommandLike.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.command\n\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.text.TextComponent\nimport net.kyori.adventure.text.event.ClickEvent\nimport net.kyori.adventure.text.format.NamedTextColor.*\nimport net.kyori.adventure.text.format.TextDecoration\nimport org.incendo.cloud.Command\nimport org.incendo.cloud.component.CommandComponent\nimport org.incendo.cloud.component.CommandComponent.ComponentType.*\n\ninterface CommandLike {\n\n    fun toComponent(): TextComponent\n\n    fun build(): List<Command<out Audience>>\n\n    data class Cloud(\n        private val command: Command<out Audience>\n    ) : CommandLike {\n\n        override fun toComponent(): TextComponent = command.toComponent()\n\n        private fun Command<out Audience>.toComponent() = componentOf {\n            append(\"/\".toComponent())\n            components().forEachIndexed { i, comp ->\n                append(comp.toComponent(i == 0))\n                if (i < components().size) append(spaceComponentOf())\n            }\n            append(lineComponentOf())\n            append(\"  |  \".toComponent { color(GREEN).decorate(TextDecoration.BOLD) })\n            append(\" └ \".toComponent())\n            append(commandDescription().description().textDescription().toComponent(GRAY))\n            hoverEvent(componentOf(\n                \"Permission:\".toComponent(DARK_AQUA),\n                lineComponentOf(),\n                commandPermission().permissionString().toComponent(),\n                lineComponentOf(),\n                lineComponentOf(),\n                \"Click to suggest command.\".toComponent()\n            ).toHoverEvent())\n            clickEvent(ClickEvent.suggestCommand(\"/\" + components().filter {\n                it.type() == LITERAL\n            }.joinToString(\" \") {\n                it.name()\n            }))\n        }\n\n        private fun CommandComponent<out Audience>.toComponent(root: Boolean): TextComponent = componentOf {\n            val n = if (root) aliases().minBy { it.length } else name()\n            when (type()) {\n                LITERAL -> content(n).color(YELLOW)\n                REQUIRED_VARIABLE -> content(\"<$n>\").color(RED)\n                OPTIONAL_VARIABLE -> content(\"[$n]\").color(DARK_AQUA)\n                FLAG -> content(\"-$n\").color(LIGHT_PURPLE)\n            }\n            hoverEvent(componentOf {\n                if (aliases().isNotEmpty()) {\n                    append(componentOf(\n                        \"Aliases:\".toComponent(DARK_AQUA),\n                        lineComponentOf(),\n                        componentWithLineOf(*aliases().map(String::toComponent).toTypedArray()),\n                        lineComponentOf(),\n                        lineComponentOf()\n                    ))\n                }\n                append(\"Click to suggest command.\".toComponent())\n            }.toHoverEvent())\n        }\n\n        override fun build(): List<Command<out Audience>> = listOf(command)\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/ArmorManager.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager\n\nimport kr.toxicity.library.armormodel.ArmorImage\nimport kr.toxicity.library.armormodel.ArmorModel\nimport kr.toxicity.library.armormodel.ArmorNameMapper\nimport kr.toxicity.library.armormodel.ArmorPaletteImage\nimport kr.toxicity.model.api.pack.PackObfuscator\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.text.format.NamedTextColor\nimport java.io.File\nimport java.net.URI\nimport java.net.http.HttpResponse\nimport java.util.concurrent.CompletableFuture\nimport java.util.jar.JarFile\nimport java.util.zip.ZipEntry\nimport kotlin.io.path.createTempFile\n\nobject ArmorManager : GlobalManager {\n\n    private val ARMOR_PATH = \"assets/minecraft/textures/entity/equipment/humanoid\" to \"assets/minecraft/textures/entity/equipment/humanoid_leggings\"\n    private val ARMOR_TRIM_PATH = \"assets/minecraft/textures/trims/entity/humanoid\" to \"assets/minecraft/textures/trims/entity/humanoid_leggings\"\n    private const val ARMOR_PALETTE_PATH = \"assets/minecraft/textures/trims/color_palettes\"\n\n    private val armors = setOf(\n        \"chainmail\",\n        \"copper\",\n        \"diamond\",\n        \"gold\",\n        \"iron\",\n        \"leather\",\n        \"netherite\"\n    )\n\n    private val palettes = setOf(\n        \"amethyst\",\n        \"copper\",\n        \"copper_darker\",\n        \"diamond\",\n        \"diamond_darker\",\n        \"emerald\",\n        \"gold\",\n        \"gold_darker\",\n        \"iron\",\n        \"iron_darker\",\n        \"lapis\",\n        \"netherite\",\n        \"netherite_darker\",\n        \"quartz\",\n        \"redstone\",\n        \"resin\"\n    )\n\n    private val trims = setOf(\n        \"bolt\",\n        \"coast\",\n        \"dune\",\n        \"eye\",\n        \"flow\",\n        \"host\",\n        \"raiser\",\n        \"rib\",\n        \"sentry\",\n        \"shaper\",\n        \"silence\",\n        \"snout\",\n        \"spire\",\n        \"tide\",\n        \"vex\",\n        \"ward\",\n        \"wayfinder\",\n        \"wild\"\n    )\n\n    var armor: ArmorModel = ArmorModel.EMPTY\n        private set\n\n    private data class VersionManifest(\n        val latest: ManifestLatest,\n        val versions: List<ManifestVersion>\n    ) {\n        val manifest get() = versions.associateBy { it.id }[latest.release]!!\n    }\n\n    private data class ManifestLatest(\n        val release: String\n    )\n\n    private data class ManifestVersion(\n        val id: String,\n        val url: String\n    ) {\n        fun toURI(): URI = URI.create(url)\n    }\n\n    private data class VersionHash(\n        val downloads: Map<String, HashDownload>\n    ) {\n        val client by downloads\n    }\n\n    private data class HashDownload(\n        val url: String\n    ) {\n        fun toURI(): URI = URI.create(url)\n    }\n\n    private data class MinecraftClient(\n        val version: String,\n        val file: File\n    )\n\n    private fun downloadMinecraftClient(): CompletableFuture<MinecraftClient?> = httpClient {\n        val cacheFolder = DATA_FOLDER.getOrCreateDirectory(\".cache\")\n        sendAsync(\n            buildHttpRequest {\n                GET()\n                uri(URI.create(\"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json\"))\n            },\n            HttpResponse.BodyHandlers.ofInputStream()\n        ).thenComposeAsync { response1 ->\n            val manifest = response1.toJson(VersionManifest::class.java).manifest\n            val cache = File(cacheFolder, \"${manifest.id}.jar\")\n            if (cache.exists() && cache.length() > 0) CompletableFuture.supplyAsync { MinecraftClient(manifest.id, cache) }\n            else sendAsync(\n                buildHttpRequest {\n                    GET()\n                    uri(manifest.toURI())\n                },\n                HttpResponse.BodyHandlers.ofInputStream()\n            ).thenComposeAsync { response2 ->\n                sendAsync(\n                    buildHttpRequest {\n                        GET()\n                        uri(response2.toJson(VersionHash::class.java).client.toURI())\n                    },\n                    HttpResponse.BodyHandlers.ofInputStream()\n                ).thenComposeAsync { response3 ->\n                    val temp = createTempFile(cache.parentFile.toPath(), manifest.id, \".tmp\").toFile()\n                    response3.body().use { input ->\n                        temp.outputStream().buffered().use(input::copyTo)\n                    }\n                    temp.renameTo(cache)\n                    CompletableFuture.supplyAsync { MinecraftClient(manifest.id, cache) }\n                }\n            }\n        }\n    }.orElse {\n        CompletableFuture.completedFuture(null)\n    }\n\n    private class ArmorImageCache(\n        val name: String,\n        val armor: ByteArray,\n        val leggings: ByteArray\n    ) {\n        val size: Long get() = armor.size.toLong() + leggings.size.toLong()\n\n        fun write(target: File) {\n            val file = File(target, name).apply { mkdirs() }\n            File(file, \"armor.png\").outputStream().buffered().use { it.write(armor) }\n            File(file, \"leggings.png\").outputStream().buffered().use { it.write(leggings) }\n        }\n    }\n\n    private fun JarFile.loadArmorImage(name: String, pathPair: Pair<String, String>) = ArmorImageCache(\n        name,\n        loadImage(pathPair.first, name),\n        loadImage(pathPair.second, name)\n    )\n\n    private fun JarFile.loadImage(path: String, name: String) = getInputStream(ZipEntry(\"$path/$name.png\")).use { it.readAllBytes() }\n\n    override fun reload(\n        pipeline: ReloadPipeline,\n        zipper: PackZipper\n    ) {\n        if (!CONFIG.module().playerAnimation) {\n            armor = ArmorModel.EMPTY\n            return\n        }\n        val folder = DATA_FOLDER.getOrCreateDirectory(\"armors\") {\n            info(\"Downloading client jar...\".toComponent())\n            val armorsFile = File(it, \"armors\")\n            val trimsFile = File(it, \"armor_trims\")\n            val palettesFile = File(it, \"palettes\").apply { mkdirs() }\n            runCatching {\n                downloadMinecraftClient().join()?.let { client ->\n                    JarFile(client.file).use { jar ->\n                        pipeline.forEachParallel(\n                            armors.map { name -> jar.loadArmorImage(name, ARMOR_PATH) },\n                                ArmorImageCache::size\n                        ) { image -> image.write(armorsFile) }\n                        pipeline.forEachParallel(\n                            trims.map { name -> jar.loadArmorImage(name, ARMOR_TRIM_PATH) },\n                            ArmorImageCache::size\n                        ) { image -> image.write(trimsFile) }\n                        pipeline.forEachParallel(\n                            palettes.map { name -> name to jar.loadImage(ARMOR_PALETTE_PATH, name) },\n                            { image -> image.second.size.toLong() },\n                        ) { image -> File(palettesFile, \"${image.first}.png\").writeBytes(image.second)}\n                    }\n                }\n                info(\"Download success!\".toComponent(NamedTextColor.LIGHT_PURPLE))\n            }.handleFailure {\n                \"Unable to download default armor assets.\"\n            }\n        }\n        val textures = PackObfuscator.order()\n        val models = PackObfuscator.order()\n        armor = ArmorModel.builder()\n            .namespace(CONFIG.namespace())\n            .streamLoader { path -> PLATFORM.getResource(path)!! }\n            .armors(pipeline\n                .mapParallel(File(folder, \"armors\").subFiles(), File::length) { it.toArmorImage() }\n                .sortedBy { it.name }\n            )\n            .armorTrims(pipeline\n                .mapParallel(File(folder, \"armor_trims\").subFiles(), File::length) { it.toArmorImage() }\n                .sortedBy { it.name }\n            )\n            .palettes(pipeline\n                .mapParallel(File(folder, \"palettes\").subFiles(), File::length) { it.toPaletteImage() }\n                .sortedBy { it.name }\n            )\n            .nameMapper(ArmorNameMapper(\n                { textures.obfuscate(it) },\n                { models.obfuscate(it) }\n            ))\n            .flush(false)\n            .build()\n        armor.builders().forEach {\n            zipper.modern().add(\n                it.path(),\n                256\n            ) {\n                it.get()\n            }\n        }\n    }\n\n    private fun File.toArmorImage() = runCatching {\n        ArmorImage(\n            nameWithoutExtension,\n            File(this, \"armor.png\").toImage(),\n            File(this, \"leggings.png\").toImage()\n        )\n    }.handleFailure {\n        \"Unable to load this armor image: $path\"\n    }.getOrNull()\n\n    private fun File.toPaletteImage() = runCatching {\n        ArmorPaletteImage(nameWithoutExtension, toImage())\n    }.handleFailure {\n        \"Unable to load this palette image: $path\"\n    }.getOrNull()\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/GlobalManager.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager\n\nimport kr.toxicity.model.api.pack.PackZipper\n\ninterface GlobalManager {\n    fun start() {}\n    fun reload(pipeline: ReloadPipeline, zipper: PackZipper)\n    fun end() {}\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/ModelManagerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager\n\nimport com.google.gson.JsonArray\nimport com.google.gson.JsonObject\nimport kr.toxicity.model.api.bone.BoneItemMapper\nimport kr.toxicity.model.api.data.ModelAsset\nimport kr.toxicity.model.api.data.blueprint.BlueprintElement\nimport kr.toxicity.model.api.data.blueprint.BlueprintJson\nimport kr.toxicity.model.api.data.blueprint.ModelBlueprint\nimport kr.toxicity.model.api.data.renderer.ModelRenderer\nimport kr.toxicity.model.api.data.renderer.RendererGroup\nimport kr.toxicity.model.api.event.ModelAssetsEvent\nimport kr.toxicity.model.api.event.ModelImportedEvent\nimport kr.toxicity.model.api.manager.ModelManager\nimport kr.toxicity.model.api.pack.PackBuilder\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.platform.PlatformNamespace\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.text.format.NamedTextColor.*\nimport java.io.File\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.io.path.extension\n\nobject ModelManagerImpl : ModelManager, GlobalManager {\n\n    private lateinit var itemModelNamespace: PlatformNamespace\n    private val generalModelMap = addressingMapOf<String, ModelRenderer>()\n    private val generalModelView = generalModelMap.toImmutableView()\n    private val playerModelMap = addressingMapOf<String, ModelRenderer>()\n    private val playerModelView = playerModelMap.toImmutableView()\n    private val modelExtensions = setOf(\"bbmodel\", \"ajmodel\")\n\n    private fun importModels(\n        type: ModelRenderer.Type,\n        pipeline: ReloadPipeline,\n        dir: File\n    ): Sequence<ImportedModel> {\n        val targetAssets = ModelAssetsEvent(type, dir.fileTrees().use { stream ->\n            stream.filter { it.extension.lowercase() in modelExtensions }\n                .map(ModelAsset::of)\n                .toMutableSet()\n        }).apply { call() }\n            .assets\n            .ifEmpty { return emptySequence() }\n            .toList()\n        val modelFileMap = ConcurrentHashMap<String, Pair<ModelAsset, ModelBlueprint>>(targetAssets.size)\n        val typeName = type.name.lowercase()\n        pipeline.apply {\n            status = \"Importing $typeName models...\"\n            goal = targetAssets.size\n        }.forEachParallel(targetAssets, ModelAsset::sizeAssume) {\n            val index = pipeline.progress()\n            val load = it.toTexturedModel() ?: return@forEachParallel\n            modelFileMap.compute(load.name) { _, v ->\n                if (v != null) {\n                    // A model with the same name already exists from a different file\n                    warn(\n                        \"Duplicate $typeName model name '${load.name}'.\".toComponent(),\n                        \"Duplicated file: $it\".toComponent(RED),\n                        \"And: ${v.first}\".toComponent(RED)\n                    )\n                    if (v.first < it) return@compute v\n                }\n                debugPack {\n                    componentOf(\n                        \"$typeName model file successfully loaded: \".toComponent(),\n                        it.toString().toComponent(GREEN),\n                        \" ($index/${pipeline.goal})\".toComponent(DARK_GRAY)\n                    )\n                }\n                it to load\n            }\n        }\n        return modelFileMap.values\n            .asSequence()\n            .sortedBy { it.first }\n            .map {\n                ImportedModel(\n                    it.first.sizeAssume - it.second.textures.sumOf { tex -> tex.image.size },\n                    type,\n                    it.second\n                )\n            }\n    }\n\n    private fun loadModels(pipeline: ReloadPipeline, zipper: PackZipper) {\n        ModelPipeline(zipper).use {\n            if (CONFIG.module().model) it.addModelTo(\n                generalModelMap,\n                importModels(ModelRenderer.Type.GENERAL, pipeline, DATA_FOLDER.getOrCreateDirectory(\"models\") { folder ->\n                    File(DATA_FOLDER.parent, \"ModelEngine/blueprints\")\n                        .takeIf(File::isDirectory)\n                        ?.run {\n                            copyRecursively(folder, overwrite = true)\n                            info(\"ModelEngine's models are successfully migrated.\".toComponent(GREEN))\n                        } ?: run {\n                        folder.addResource(\"demon_knight.bbmodel\")\n                        folder.addResource(\"blue_wizard.bbmodel\")\n                    }\n                })\n            )\n            if (CONFIG.module().playerAnimation) it.addModelTo(\n                playerModelMap,\n                importModels(ModelRenderer.Type.PLAYER, pipeline, DATA_FOLDER.getOrCreateDirectory(\"players\") { folder ->\n                    folder.addResource(\"steve.bbmodel\")\n                })\n            )\n        }\n    }\n\n    private data class ImportedModel(\n        val jsonSize: Long,\n        val type: ModelRenderer.Type,\n        val blueprint: ModelBlueprint\n    )\n\n    private class ModelPipeline(\n        zipper: PackZipper\n    ) : AutoCloseable {\n\n        private var indexer = 1\n        private var estimatedSize = 0L\n        private val textures = zipper.assets().bettermodel().textures()\n\n        private val legacyModel = ModelBuilder(\n            models = zipper.legacy().bettermodel().models().resolve(\"item\"),\n            available = CONFIG.pack().generateLegacyModel,\n            onBuild = { blueprints, _, size ->\n                val json = blueprints.first()\n                entries += jsonObjectOf(\n                    \"predicate\" to jsonObjectOf(\"custom_model_data\" to indexer),\n                    \"model\" to \"${CONFIG.namespace()}:item/${json.name}\"\n                )\n                models.add(json.jsonName(), size) {\n                    json.buildJson().toByteArray()\n                }\n            },\n            onClose = {\n                val itemName = CONFIG.itemModel().lowercase()\n                jsonObjectOf(\n                    \"parent\" to \"minecraft:item/generated\",\n                    \"textures\" to jsonObjectOf(\"layer0\" to \"minecraft:item/$itemName\"),\n                    \"overrides\" to entries\n                ).run {\n                    models.add(\"${CONFIG.itemNamespace()}.json\", estimatedSize) { toByteArray() }\n                    zipper.legacy().minecraft().models().resolve(\"item\").add(\"$itemName.json\", estimatedSize) { toByteArray() }\n                }\n            }\n        )\n\n        private val modernModel = ModelBuilder(\n            models = zipper.modern().bettermodel().models().resolve(\"modern_item\"),\n            available = CONFIG.pack().generateModernModel,\n            onBuild = { blueprints, json, size ->\n                entries += jsonObjectOf(\n                    \"threshold\" to indexer,\n                    \"model\" to blueprints.toModernJson(json)\n                )\n                blueprints.forEach { json ->\n                    models.add(json.jsonName(), size / blueprints.size) {\n                        json.buildJson().toByteArray()\n                    }\n                }\n            },\n            onClose = {\n                zipper.modern().bettermodel().items().add(\"${CONFIG.itemNamespace()}.json\", estimatedSize) {\n                    jsonObjectOf(\"model\" to jsonObjectOf(\n                        \"type\" to \"range_dispatch\",\n                        \"property\" to \"custom_model_data\",\n                        \"fallback\" to jsonObjectOf(\n                            \"type\" to \"empty\"\n                        ),\n                        \"entries\" to entries\n                    )).toByteArray()\n                }\n            }\n        )\n\n        override fun close() {\n            modernModel.close()\n            legacyModel.close()\n        }\n\n        fun addModelTo(\n            targetMap: MutableMap<String, ModelRenderer>,\n            model: Sequence<ImportedModel>\n        ) {\n            model.forEach { addModelTo(targetMap, it) }\n        }\n\n        private fun addModelTo(\n            targetMap: MutableMap<String, ModelRenderer>,\n            importedModel: ImportedModel\n        ) {\n            val (size, type, blueprint) = importedModel\n            val context = blueprint.context()\n            targetMap[blueprint.name] = blueprint.toRenderer(type) render@ { group ->\n                if (!context.canBeRendered()) return@render null\n                listOfNotNull(\n                    modernModel.ifAvailable {\n                        val json = group.buildModernJson(obfuscator, context)\n                        val itemModel = group.buildMeshItemModel(context)\n                        if (json != null || itemModel != null) {\n                            build(json ?: emptyList(), itemModel, if (json != null) size / json.size else 0)\n                        } else null\n                    },\n                    legacyModel.ifAvailable {\n                        group.buildLegacyJson(obfuscator, context)\n                            ?.let { build(listOf(it), null, size) }\n                    }\n                ).run {\n                    if (isNotEmpty()) indexer++ else null\n                }\n            }.apply {\n                debugPack {\n                    componentOf(\n                        \"This model was successfully imported: \".toComponent(),\n                        blueprint.name.toComponent(GREEN)\n                    )\n                }\n                callEvent { ModelImportedEvent(blueprint, this) }\n            }\n            context.buildImage(textures.obfuscator()).forEach { image ->\n                textures.add(image.pngName(), image.estimatedSize()) {\n                    image.toByteArray()\n                }\n                image.mcmeta()?.let { meta ->\n                    textures.add(image.mcmetaName(), -1) {\n                        meta.toByteArray()\n                    }\n                }\n            }\n            estimatedSize += size\n        }\n\n        inner class ModelBuilder(\n            val models: PackBuilder,\n            private val available: Boolean,\n            private val onBuild: ModelBuilder.(List<BlueprintJson>, JsonObject?, Long) -> Unit,\n            private val onClose: ModelBuilder.() -> Unit\n        ) : AutoCloseable {\n            val entries = jsonArrayOf()\n            val obfuscator = textures.obfuscator().withModels(models.obfuscator())\n\n            inline fun <T> ifAvailable(block: ModelBuilder.() -> T): T? {\n                return if (available) block() else null\n            }\n\n            fun build(list: List<BlueprintJson>, json: JsonObject?, size: Long) {\n                onBuild(list, json, size)\n            }\n\n            override fun close() {\n                ifAvailable {\n                    if (!entries.isEmpty) onClose()\n                }\n            }\n        }\n\n        private fun List<BlueprintJson>.toModernJson(plus: JsonObject?) = if (size == 1) first().toModernJson() else jsonObjectOf(\n            \"type\" to \"composite\",\n            \"models\" to fold(JsonArray(size + (if (plus != null) 1 else 0)).apply {\n                plus?.run(::add)\n            }) { array, element -> array.apply { add(element.toModernJson()) } }\n        )\n\n        private fun BlueprintJson.toModernJson() = jsonObjectOf(\n            \"type\" to \"model\",\n            \"model\" to \"${CONFIG.namespace()}:modern_item/$name\",\n            \"tints\" to jsonArrayOf(\n                jsonObjectOf(\n                    \"type\" to \"custom_model_data\",\n                    \"default\" to 0xFFFFFF\n                )\n            )\n        )\n\n        private fun ModelBlueprint.toRenderer(type: ModelRenderer.Type, builder: (BlueprintElement.Group) -> Int?): ModelRenderer {\n            fun <T> Collection<BlueprintElement>.toBoneMap(mapper: (BlueprintElement.Bone) -> T) = filterIsInstance<BlueprintElement.Bone>().let { bone ->\n                bone.associateTo(sequencedAddressingMapOf(bone.size)) { it.name() to mapper(it) }\n            }.toImmutableView()\n            fun BlueprintElement.Bone.parse(): RendererGroup {\n                if (this !is BlueprintElement.Group) return RendererGroup(1.0F, null, this, emptySequencedMap(), null)\n                return RendererGroup(\n                    scale(),\n                    if (name.toItemMapper() !== BoneItemMapper.EMPTY) null else builder(this)?.let { i ->\n                        CONFIG.item().get().modelData(i, itemModelNamespace)\n                    },\n                    this,\n                    children.toBoneMap { it.parse() },\n                    hitBox(),\n                )\n            }\n            return ModelRenderer(\n                name,\n                type,\n                elements.toBoneMap { it.parse() },\n                animations\n            )\n        }\n    }\n\n    override fun start() {\n    }\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n        itemModelNamespace = PlatformNamespace(CONFIG.namespace(), CONFIG.itemNamespace())\n        generalModelMap.clear()\n        playerModelMap.clear()\n        loadModels(pipeline, zipper)\n    }\n\n    override fun model(name: String): ModelRenderer? = generalModelView[name]\n    override fun models(): Collection<ModelRenderer> = generalModelView.values\n    override fun modelKeys(): Set<String> = generalModelView.keys\n    override fun limb(name: String): ModelRenderer? = playerModelView[name]\n    override fun limbs(): Collection<ModelRenderer> = playerModelView.values\n    override fun limbKeys(): Set<String> = playerModelView.keys\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/ProfileManagerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager\n\nimport com.google.gson.GsonBuilder\nimport com.google.gson.annotations.SerializedName\nimport kr.toxicity.model.api.manager.ProfileManager\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.profile.ModelProfileSkin\nimport kr.toxicity.model.api.profile.ModelProfileSupplier\nimport kr.toxicity.model.profile.DefaultHttpModelProfileSupplier\nimport kr.toxicity.model.profile.HttpModelProfileSupplier\nimport kr.toxicity.model.util.PLATFORM\nimport java.net.URI\nimport java.util.*\n\nobject ProfileManagerImpl : ProfileManager, GlobalManager {\n\n    private val gson = GsonBuilder().create()\n    private lateinit var supplier: ModelProfileSupplier\n\n    override fun supplier(): ModelProfileSupplier = supplier\n\n    override fun supplier(supplier: ModelProfileSupplier) {\n        this.supplier = supplier\n    }\n\n    override fun skin(rawTextures: String): ModelProfileSkin {\n        return gson.fromJson(Base64.getDecoder().decode(rawTextures).toString(Charsets.UTF_8), Profile::class.java).run {\n            ModelProfileSkin(\n                textures.skin?.toURI(),\n                textures.cape?.toURI(),\n                textures.skin?.metadata?.slim == true,\n                rawTextures\n            )\n        }\n    }\n\n    private data class Profile(\n        val textures: ProfileTextures\n    )\n\n    private data class ProfileTextures(\n        @SerializedName(\"SKIN\") val skin: ProfileSkin?,\n        @SerializedName(\"CAPE\") val cape: ProfileSkin?,\n    )\n\n    private data class ProfileSkin(\n        val url: String,\n        val metadata: ProfileMetadata\n    ) {\n        fun toURI(): URI = URI.create(url)\n    }\n\n    private data class ProfileMetadata(\n        val model: String\n    ) {\n        val slim get() = model == \"slim\"\n    }\n\n    override fun start() {\n        supplier = if (PLATFORM.nms().isProxyOnlineMode) DefaultHttpModelProfileSupplier() else HttpModelProfileSupplier()\n    }\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/ReloadPipeline.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager\n\nimport kr.toxicity.model.manager.debug.ReloadIndicator\nimport kr.toxicity.model.util.PLATFORM\nimport kr.toxicity.model.util.parallelIOThreadPool\nimport java.util.concurrent.CopyOnWriteArrayList\nimport java.util.concurrent.atomic.AtomicInteger\n\nclass ReloadPipeline(\n    private val indicators: List<ReloadIndicator>\n) : AutoCloseable {\n\n    var status = \"Starting...\"\n    private val pool = parallelIOThreadPool()\n\n    private val current = AtomicInteger()\n    var goal = 0\n        set(value) {\n            field = value\n            current.set(0)\n        }\n\n    fun progress() = current.incrementAndGet()\n\n    fun <T> forEachParallel(list: List<T>, sizeAssume: (T) -> Long, block: (T) -> Unit) {\n        pool.forEachParallel(list, sizeAssume, block)\n    }\n\n    fun <T, R> mapParallel(list: List<T>, sizeAssume: (T) -> Long, block: (T) -> R?): List<R> {\n        return CopyOnWriteArrayList<R>().apply {\n            forEachParallel(list, sizeAssume) { t: T ->\n                block(t)?.let { add(it) }\n            }\n        }\n    }\n\n    private val task = PLATFORM.scheduler().asyncTaskTimer(1, 1) {\n        current.get().run {\n            Status(\n                if (goal > 0) toFloat() / goal.toFloat() else 0F,\n                this,\n                goal,\n                status\n            )\n        }.run {\n            indicators.forEach {\n                it status this\n            }\n        }\n    }\n\n    data class Status(\n        val progress: Float,\n        val current: Int,\n        val goal: Int,\n        val status: String\n    )\n\n    override fun close() {\n        task.cancel()\n        indicators.forEach(ReloadIndicator::close)\n        pool.close()\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/ScriptManagerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager\n\nimport kr.toxicity.model.api.event.AnimationSignalEvent\nimport kr.toxicity.model.api.manager.ScriptManager\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.script.ScriptBuilder\nimport kr.toxicity.model.script.*\nimport kr.toxicity.model.util.boneName\nimport kr.toxicity.model.util.bonePredicate\nimport java.util.regex.Matcher\nimport java.util.regex.Pattern\n\nobject ScriptManagerImpl : ScriptManager, GlobalManager {\n\n    private val scriptMap = hashMapOf<String, ScriptBuilder>()\n    private val scriptPattern = Pattern.compile(\"^(?<name>[a-zA-Z]+)(:(?<argument>([\\\\w_\\\\-])+))?(\\\\{(?<metadata>([\\\\w\\\\W])+)})?$\")\n    private val validatePattern = Pattern.compile(\"^[a-z]+$\")\n\n    init {\n        addBuilder(\"signal\") {\n            val args = it.args() ?: return@addBuilder AnimationScript.EMPTY\n            AnimationScript.of { tracker ->\n                tracker.pipeline.allPlayer()\n                    .map { channel -> channel.player() }\n                    .forEach { player -> AnimationSignalEvent(player, args).call() }\n            }\n        }\n\n        addBuilder(\"tint\") {\n            TintScript(\n                it.metadata.bonePredicate,\n                it.metadata.asNumber(\"color\")?.toInt() ?: return@addBuilder AnimationScript.EMPTY,\n                it.metadata.asBoolean(\"damage\") == true\n            )\n        }\n        addBuilder(\"partvis\") {\n            PartVisibilityScript(\n                it.metadata.bonePredicate,\n                it.metadata.asBoolean(\"visible\") ?: return@addBuilder AnimationScript.EMPTY,\n            )\n        }\n        addBuilder(\"partbright\") {\n            BrightnessScript(\n                it.metadata.bonePredicate,\n                it.metadata().asNumber(\"block\")?.toInt() ?: return@addBuilder AnimationScript.EMPTY,\n                it.metadata().asNumber(\"sky\")?.toInt() ?: return@addBuilder AnimationScript.EMPTY,\n            )\n        }\n        addBuilder(\"enchant\") {\n            EnchantScript(\n                it.metadata.bonePredicate,\n                it.metadata.asBoolean(\"enchant\") ?: return@addBuilder AnimationScript.EMPTY,\n            )\n        }\n        addBuilder(\"changepart\") {\n            ChangePartScript(\n                it.metadata.bonePredicate,\n                it.metadata.asString(\"nmodel\") ?: return@addBuilder AnimationScript.EMPTY,\n                it.metadata.asString(\"npart\")?.boneName ?: return@addBuilder AnimationScript.EMPTY\n            )\n        }\n        addBuilder(\"remap\") {\n            RemapScript(\n                it.metadata.asString(\"model\") ?: return@addBuilder AnimationScript.EMPTY,\n                it.metadata.asString(\"map\")\n            )\n        }\n    }\n\n    override fun build(script: String): AnimationScript? = script.toScript()\n\n    override fun addBuilder(name: String, script: ScriptBuilder) {\n        if (!validatePattern.matcher(name).find()) throw RuntimeException(\"name must be in [a-z]\")\n        scriptMap[name] = script\n    }\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n    }\n\n    private fun String.toScript(): AnimationScript? = scriptPattern.matcher(this)\n        .takeIf(Matcher::find)\n        ?.let {\n            scriptMap[it.group(\"name\").lowercase()]?.build(ScriptBuilder.ScriptData(it.group(\"argument\"),\n                ScriptMetaDataImpl(it.group(\"metadata\")\n                    ?.split(';')\n                    ?.associate { pair ->\n                        pair.split('=', limit = 2).let { arr -> arr[0] to arr[1] }\n                    }\n                    ?: emptyMap()\n                )\n            ))\n        }\n\n    private class ScriptMetaDataImpl(\n        private val map: Map<String, String>\n    ) : ScriptBuilder.ScriptMetaData {\n        override fun toMap(): Map<String, String> = map\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/SkinManagerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager\n\nimport com.github.benmanes.caffeine.cache.Caffeine\nimport com.github.benmanes.caffeine.cache.RemovalCause\nimport it.unimi.dsi.fastutil.ints.IntList\nimport kr.toxicity.library.armormodel.ArmorResource\nimport kr.toxicity.library.dynamicuv.*\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.event.CreatePlayerSkinEvent\nimport kr.toxicity.model.api.event.RemovePlayerSkinEvent\nimport kr.toxicity.model.api.manager.SkinManager\nimport kr.toxicity.model.api.pack.PackObfuscator\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.skin.SkinData\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport kr.toxicity.model.util.*\nimport org.joml.Vector3f\nimport java.awt.image.BufferedImage\nimport java.net.URI\nimport java.net.http.HttpResponse\nimport java.util.*\nimport java.util.concurrent.CompletableFuture\nimport java.util.concurrent.TimeUnit\nimport javax.imageio.ImageIO\n\nobject SkinManagerImpl : SkinManager, GlobalManager {\n\n    private const val DIV_FACTOR = 16F / 0.9375F\n\n    private var uvNamespace = UVNamespace(\n        CONFIG.namespace(),\n        \"player_limb\"\n    )\n\n    private val HEAD = UVModel(\n        { uvNamespace },\n        \"head\"\n    ).addElement(\n        UVElement(\n            ElementVector(8F, 8F, 8F).div(DIV_FACTOR),\n            ElementVector(0f, 4F, 0f).div(DIV_FACTOR),\n            UVSpace(8, 8, 8),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(8, 8),\n                UVFace.SOUTH to UVPos(24, 8),\n                UVFace.EAST to UVPos(0, 8),\n                UVFace.WEST to UVPos(16, 8),\n                UVFace.UP to UVPos(8, 0),\n                UVFace.DOWN to UVPos(16, 0)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(8F, 8F, 8F).div(DIV_FACTOR).inflate(0.5f),\n            ElementVector(0f, 4F, 0f).div(DIV_FACTOR),\n            UVSpace(8, 8, 8),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(8 + 32, 8),\n                UVFace.SOUTH to UVPos(24 + 32, 8),\n                UVFace.EAST to UVPos(32, 8),\n                UVFace.WEST to UVPos(16 + 32, 8),\n                UVFace.UP to UVPos(8 + 32, 0),\n                UVFace.DOWN to UVPos(16 + 32, 0)\n            )\n        )\n    )\n    private val CHEST = UVModel(\n        { uvNamespace },\n        \"chest\"\n    ).addElement(\n        UVElement(\n            ElementVector(8f, 4f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, 2f, 0f).div(DIV_FACTOR),\n            UVSpace(8, 4, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 20),\n                UVFace.SOUTH to UVPos(32, 20),\n                UVFace.EAST to UVPos(16, 20),\n                UVFace.WEST to UVPos(28, 20),\n                UVFace.UP to UVPos(20, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(8f, 4f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, 2f, 0f).div(DIV_FACTOR),\n            UVSpace(8, 4, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 20 + 16),\n                UVFace.SOUTH to UVPos(32, 20 + 16),\n                UVFace.EAST to UVPos(16, 20 + 16),\n                UVFace.WEST to UVPos(28, 20 + 16),\n                UVFace.UP to UVPos(20, 16 + 16)\n            )\n        )\n    )\n    private val WAIST = UVModel(\n        { uvNamespace },\n        \"waist\"\n    ).addElement(\n        UVElement(\n            ElementVector(8f, 4f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, 2f, 0f).div(DIV_FACTOR),\n            UVSpace(8, 4, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 24),\n                UVFace.SOUTH to UVPos(32, 24),\n                UVFace.EAST to UVPos(16, 24),\n                UVFace.WEST to UVPos(28, 24)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(8f, 4f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, 2f, 0f).div(DIV_FACTOR),\n            UVSpace(8, 4, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 24 + 16),\n                UVFace.SOUTH to UVPos(32, 24 + 16),\n                UVFace.EAST to UVPos(16, 24 + 16),\n                UVFace.WEST to UVPos(28, 24 + 16)\n            )\n        )\n    )\n    private val HIP = UVModel(\n        { uvNamespace },\n        \"hip\"\n    ).addElement(\n        UVElement(\n            ElementVector(8f, 4f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, 2f, 0f).div(DIV_FACTOR),\n            UVSpace(8, 4, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 28),\n                UVFace.SOUTH to UVPos(32, 28),\n                UVFace.EAST to UVPos(16, 28),\n                UVFace.WEST to UVPos(28, 28),\n                UVFace.DOWN to UVPos(28, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(8f, 4f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, 2f, 0f).div(DIV_FACTOR),\n            UVSpace(8, 4, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 28 + 16),\n                UVFace.SOUTH to UVPos(32, 28 + 16),\n                UVFace.EAST to UVPos(16, 28 + 16),\n                UVFace.WEST to UVPos(28, 28 + 16),\n                UVFace.DOWN to UVPos(28, 16 + 16)\n            )\n        )\n    )\n    private val LEFT_LEG = UVModel(\n        { uvNamespace },\n        \"left_leg\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 52),\n                UVFace.SOUTH to UVPos(28, 52),\n                UVFace.EAST to UVPos(16, 52),\n                UVFace.WEST to UVPos(24, 52),\n                UVFace.UP to UVPos(20, 48)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20 - 16, 52),\n                UVFace.SOUTH to UVPos(28 - 16, 52),\n                UVFace.EAST to UVPos(0, 52),\n                UVFace.WEST to UVPos(24 - 16, 52),\n                UVFace.UP to UVPos(20 - 16, 48)\n            )\n        )\n    )\n    private val LEFT_FORELEG = UVModel(\n        { uvNamespace },\n        \"left_foreleg\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20, 58),\n                UVFace.SOUTH to UVPos(28, 58),\n                UVFace.EAST to UVPos(16, 58),\n                UVFace.WEST to UVPos(24, 58),\n                UVFace.DOWN to UVPos(24, 48)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(20 - 16, 58),\n                UVFace.SOUTH to UVPos(28 - 16, 58),\n                UVFace.EAST to UVPos(0, 58),\n                UVFace.WEST to UVPos(24 - 16, 58),\n                UVFace.DOWN to UVPos(24 - 16, 48)\n            )\n        )\n    )\n    private val RIGHT_LEG = UVModel(\n        { uvNamespace },\n        \"right_leg\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(4, 20),\n                UVFace.SOUTH to UVPos(12, 20),\n                UVFace.EAST to UVPos(0, 20),\n                UVFace.WEST to UVPos(8, 20),\n                UVFace.UP to UVPos(4, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(4, 20 + 16),\n                UVFace.SOUTH to UVPos(12, 20 + 16),\n                UVFace.EAST to UVPos(0, 20 + 16),\n                UVFace.WEST to UVPos(8, 20 + 16),\n                UVFace.UP to UVPos(4, 16 + 16)\n            )\n        )\n    )\n    private val RIGHT_FORELEG = UVModel(\n        { uvNamespace },\n        \"right_foreleg\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(4, 26),\n                UVFace.SOUTH to UVPos(12, 26),\n                UVFace.EAST to UVPos(0, 26),\n                UVFace.WEST to UVPos(8, 26),\n                UVFace.DOWN to UVPos(8, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(4, 26 + 16),\n                UVFace.SOUTH to UVPos(12, 26 + 16),\n                UVFace.EAST to UVPos(0, 26 + 16),\n                UVFace.WEST to UVPos(8, 26 + 16),\n                UVFace.DOWN to UVPos(8, 16 + 16)\n            )\n        )\n    )\n    private val LEFT_ARM = UVModel(\n        { uvNamespace },\n        \"left_arm\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36, 52),\n                UVFace.SOUTH to UVPos(44, 52),\n                UVFace.EAST to UVPos(32, 52),\n                UVFace.WEST to UVPos(40, 52),\n                UVFace.UP to UVPos(36, 48)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36 + 16, 52),\n                UVFace.SOUTH to UVPos(44 + 16, 52),\n                UVFace.EAST to UVPos(32 + 16, 52),\n                UVFace.WEST to UVPos(40 + 16, 52),\n                UVFace.UP to UVPos(36 + 16, 48)\n            )\n        )\n    )\n    private val LEFT_FOREARM = UVModel(\n        { uvNamespace },\n        \"left_forearm\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36, 58),\n                UVFace.SOUTH to UVPos(44, 58),\n                UVFace.EAST to UVPos(32, 58),\n                UVFace.WEST to UVPos(40, 58),\n                UVFace.DOWN to UVPos(40, 48)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36 + 16, 58),\n                UVFace.SOUTH to UVPos(44 + 16, 58),\n                UVFace.EAST to UVPos(32 + 16, 58),\n                UVFace.WEST to UVPos(40 + 16, 58),\n                UVFace.DOWN to UVPos(40 + 16, 48)\n            )\n        )\n    )\n    private val RIGHT_ARM = UVModel(\n        { uvNamespace },\n        \"right_arm\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 20),\n                UVFace.SOUTH to UVPos(52, 20),\n                UVFace.EAST to UVPos(40, 20),\n                UVFace.WEST to UVPos(48, 20),\n                UVFace.UP to UVPos(44, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 20 + 16),\n                UVFace.SOUTH to UVPos(52, 20 + 16),\n                UVFace.EAST to UVPos(40, 20 + 16),\n                UVFace.WEST to UVPos(48, 20 + 16),\n                UVFace.UP to UVPos(44, 16 + 16)\n            )\n        )\n    )\n    private val RIGHT_FOREARM = UVModel(\n        { uvNamespace },\n        \"right_forearm\"\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 26),\n                UVFace.SOUTH to UVPos(52, 26),\n                UVFace.EAST to UVPos(40, 26),\n                UVFace.WEST to UVPos(48, 26),\n                UVFace.DOWN to UVPos(48, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(4f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(4, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 26 + 16),\n                UVFace.SOUTH to UVPos(52, 26 + 16),\n                UVFace.EAST to UVPos(40, 26 + 16),\n                UVFace.WEST to UVPos(48, 26 + 16),\n                UVFace.DOWN to UVPos(48, 16 + 16)\n            )\n        )\n    )\n    private val SLIM_LEFT_ARM = UVModel(\n        { uvNamespace },\n        \"left_slim_arm\"\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36, 52),\n                UVFace.SOUTH to UVPos(43, 52),\n                UVFace.EAST to UVPos(32, 52),\n                UVFace.WEST to UVPos(39, 52),\n                UVFace.UP to UVPos(36, 48)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36 + 16, 52),\n                UVFace.SOUTH to UVPos(43 + 16, 52),\n                UVFace.EAST to UVPos(32 + 16, 52),\n                UVFace.WEST to UVPos(39 + 16, 52),\n                UVFace.UP to UVPos(36 + 16, 48)\n            )\n        )\n    )\n    private val SLIM_LEFT_FOREARM = UVModel(\n        { uvNamespace },\n        \"left_slim_forearm\"\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36, 58),\n                UVFace.SOUTH to UVPos(43, 58),\n                UVFace.EAST to UVPos(32, 58),\n                UVFace.WEST to UVPos(39, 58),\n                UVFace.DOWN to UVPos(39, 48)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(36 + 16, 58),\n                UVFace.SOUTH to UVPos(43 + 16, 58),\n                UVFace.EAST to UVPos(32 + 16, 58),\n                UVFace.WEST to UVPos(39 + 16, 58),\n                UVFace.DOWN to UVPos(39 + 16, 48)\n            )\n        )\n    )\n    private val SLIM_RIGHT_ARM = UVModel(\n        { uvNamespace },\n        \"right_slim_arm\"\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 20),\n                UVFace.SOUTH to UVPos(51, 20),\n                UVFace.EAST to UVPos(40, 20),\n                UVFace.WEST to UVPos(47, 20),\n                UVFace.UP to UVPos(44, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 20 + 16),\n                UVFace.SOUTH to UVPos(51, 20 + 16),\n                UVFace.EAST to UVPos(40, 20 + 16),\n                UVFace.WEST to UVPos(47, 20 + 16),\n                UVFace.UP to UVPos(44, 16 + 16)\n            )\n        )\n    )\n    private val SLIM_RIGHT_FOREARM = UVModel(\n        { uvNamespace },\n        \"right_slim_forearm\"\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 26),\n                UVFace.SOUTH to UVPos(51, 26),\n                UVFace.EAST to UVPos(40, 26),\n                UVFace.WEST to UVPos(47, 26),\n                UVFace.DOWN to UVPos(47, 16)\n            )\n        )\n    ).addElement(\n        UVElement(\n            ElementVector(3f, 6f, 4f).div(DIV_FACTOR).inflate(0.25f),\n            ElementVector(0f, -3f, 0f).div(DIV_FACTOR),\n            UVSpace(3, 6, 4),\n            UVElement.ColorType.COMPLEX_ARGB,\n            mapOf(\n                UVFace.NORTH to UVPos(44, 26 + 16),\n                UVFace.SOUTH to UVPos(51, 26 + 16),\n                UVFace.EAST to UVPos(40, 26 + 16),\n                UVFace.WEST to UVPos(47, 26 + 16),\n                UVFace.DOWN to UVPos(47, 16 + 16)\n            )\n        )\n    )\n    private val CAPE = UVModel(\n        { uvNamespace },\n        \"cape\"\n    ).addElement(\n        UVElement(\n            ElementVector(10f, 16f, 1f).div(DIV_FACTOR),\n            ElementVector(0f, -8f, 0.5f).div(DIV_FACTOR),\n            UVSpace(10, 16, 1),\n            UVElement.ColorType.RGB,\n            mapOf(\n                UVFace.NORTH to UVPos(12, 1),\n                UVFace.SOUTH to UVPos(1, 1),\n                UVFace.EAST to UVPos(11, 1),\n                UVFace.WEST to UVPos(0, 1),\n                UVFace.UP to UVPos(1, 0),\n                UVFace.DOWN to UVPos(11, 0)\n            )\n        )\n    )\n\n    private data class SkinModelData(\n        private val model: UVModel,\n        val data: UVModelData\n    ) {\n        var namespace = model.itemModelNamespace()\n            private set\n\n        fun refresh() {\n            namespace = model.itemModelNamespace()\n        }\n    }\n\n    private fun UVModel.asModelData(image: BufferedImage): SkinModelData {\n        val data = write(image)\n        return SkinModelData(\n            this,\n            data\n        )\n    }\n\n    private val whiteList = IntList.of(*(0..8).map { 0xFFFFFF }.toIntArray())\n\n    private fun SkinModelData.asItem(colors: IntList = IntList.of()): TransformedItemStack = PLATFORM.nms().createSkinItem(\n        namespace,\n        data.floats,\n        data.flags,\n        emptyList(),\n        colors + data.colors\n    )\n\n    private fun SkinModelData.asItem(resource: ArmorResource, item: ArmorItem? = null): TransformedItemStack {\n        if (item == null) return asItem(whiteList)\n        val armorData = ArmorManager.armor.resource(resource).run {\n            item.trim()?.let {\n                customModelData(item.type, item.tint, it, item.palette?.let { p -> ArmorManager.armor.colors()[p] })\n            } ?: customModelData(item.type, item.tint)\n        }\n        return PLATFORM.nms().createSkinItem(\n            namespace,\n            data.floats,\n            data.flags,\n            armorData.strings,\n            armorData.colors + data.colors\n        )\n    }\n\n    fun write(block: (UVByteBuilder) -> Unit) {\n        val itemObf = PackObfuscator.order()\n        val modelObf = PackObfuscator.order()\n        fun UVModel.write(armorResource: ArmorResource? = null) {\n            val model = modelName()\n            packName(itemObf.obfuscate(model))\n            asJson(UVLoadContext(\n                UVTextureName.DEFAULT,\n                { modelObf.obfuscate(\"${model}_$it\") },\n                { indexer, _, array ->\n                    armorResource?.let { ArmorManager.armor.resource(it) }?.let {\n                        array += it.toJson()\n                        indexer.shiftColor(9)\n                    }\n                }\n            )).forEach {\n                block(it)\n            }\n        }\n        HEAD.write(ArmorResource.HELMET)\n        CHEST.write(ArmorResource.CHEST)\n        WAIST.write(ArmorResource.WAIST)\n        HIP.write(ArmorResource.HIP)\n        LEFT_LEG.write(ArmorResource.LEFT_LEG)\n        LEFT_FORELEG.write(ArmorResource.LEFT_FORELEG)\n        RIGHT_LEG.write(ArmorResource.RIGHT_LEG)\n        RIGHT_FORELEG.write(ArmorResource.RIGHT_FORELEG)\n        LEFT_ARM.write(ArmorResource.LEFT_ARM)\n        LEFT_FOREARM.write()\n        RIGHT_ARM.write(ArmorResource.RIGHT_ARM)\n        RIGHT_FOREARM.write()\n        SLIM_LEFT_ARM.write(ArmorResource.LEFT_ARM)\n        SLIM_LEFT_FOREARM.write()\n        SLIM_RIGHT_ARM.write(ArmorResource.RIGHT_ARM)\n        SLIM_RIGHT_FOREARM.write()\n        CAPE.write()\n\n        block(UVTextureName.DEFAULT.normalPixel(uvNamespace))\n        block(UVTextureName.DEFAULT.translucentPixel(uvNamespace))\n    }\n\n    private val profileCache = Caffeine.newBuilder()\n        .expireAfterAccess(5, TimeUnit.MINUTES)\n        .removalListener<UUID, SkinDataImpl> { key, value, cause ->\n            if (cause == RemovalCause.EXPIRED && key != null && value != null) {\n                handleExpiration(key, value)\n            }\n        }\n        .build<UUID, SkinDataImpl>()\n\n    private val fallback by lazy {\n        PLATFORM.getResource(\"fallback_skin.png\")!!.use {\n            SkinDataImpl(ModelProfile.UNKNOWN, ImageIO.read(it), null)\n        }\n    }\n\n    private fun handleExpiration(key: UUID, skin: SkinDataImpl) {\n        skin.profile().let {\n            if (!callEvent { RemovePlayerSkinEvent(it) } || it.playerEquals()) profileCache.put(key, skin)\n        }\n    }\n\n    private fun ModelProfile.playerEquals() = player()?.let { player ->\n        ModelProfile.of(player).info()\n    } == info()\n\n    override fun fallback(): SkinData = fallback\n\n    override fun complete(profile: ModelProfile.Uncompleted): CompletableFuture<out SkinData> {\n        if (profile.info() == ModelProfileInfo.UNKNOWN) return CompletableFuture.completedFuture(fallback)\n        return profileCache.getIfPresent(profile.info().id)?.let { CompletableFuture.completedFuture(it) } ?: profile.complete().thenApply { provided ->\n            CreatePlayerSkinEvent(provided).run {\n                call()\n                modelProfile\n            }\n        }.thenComposeAsync compose@ { selected ->\n            val skin = selected.skin().skin ?: return@compose CompletableFuture.completedFuture(fallback)\n            val cape = selected.skin().cape\n            httpClient {\n                fun URI.toFuture() = sendAsync(\n                    buildHttpRequest {\n                        uri(this@toFuture)\n                        GET()\n                    },\n                    HttpResponse.BodyHandlers.ofInputStream()\n                ).thenComposeAsync { request ->\n                    CompletableFuture.supplyAsync { request.body().use { ImageIO.read(it) } }\n                }\n                skin.toFuture().thenCombine(cape?.toFuture() ?: CompletableFuture.completedFuture(null)) { skin, cape ->\n                    SkinDataImpl(\n                        selected,\n                        skin.convertLegacy(),\n                        cape\n                    ).apply {\n                        profileCache.put(profile.info().id, this)\n                    }\n                }\n            }.orElse {\n                it.handleException(\"Unable to read this skin: ${selected.info().name}\")\n                CompletableFuture.completedFuture(fallback)\n            }\n        }.exceptionally {\n            it.handleException(\"unable to read this skin: ${profile.info().name}\")\n            profileCache.invalidate(profile.info().id)\n            null\n        }\n    }\n\n    override fun removeCache(profile: ModelProfile) = profileCache.invalidate(profile.info().id)\n\n    private fun BufferedImage.convertLegacy() = if (height == 64) this else BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB).also { newImage ->\n        fun drawTo(from: UVPos, to: UVPos, xr: IntRange, zr: IntRange) {\n            val maxX = xr.last + xr.first\n            for (x in xr) {\n                for (z in zr) {\n                    newImage.setRGB(\n                        to.x + maxX - x,\n                        to.z + z,\n                        getRGB(from.x + x, from.z + z)\n                    )\n                }\n            }\n        }\n        fun drawTo(from: UVPos, to: UVPos) {\n            drawTo(from, to, 0..<4, 4..<16)\n            drawTo(from, to, 4..<8, 0..<16)\n            drawTo(from, to, 8..<12, 0..<16)\n            drawTo(from, to, 12..<16, 4..<16)\n        }\n        newImage.createGraphics().let {\n            it.drawImage(this, 0, 0, null)\n            it.dispose()\n        }\n        drawTo(UVPos(0, 16), UVPos(16, 48))\n        drawTo(UVPos(40, 16), UVPos(32, 48))\n    }\n\n    private class SkinDataImpl(\n        private val profile: ModelProfile,\n        private val head: SkinModelData,\n        private val hip: SkinModelData,\n        private val waist: SkinModelData,\n        private val chest: SkinModelData,\n        private val leftArm: SkinModelData,\n        private val rightArm: SkinModelData,\n        private val leftLeg: SkinModelData,\n        private val leftForeLeg: SkinModelData,\n        private val rightLeg: SkinModelData,\n        private val rightForeLeg: SkinModelData,\n        private val leftForeArm: TransformedItemStack,\n        private val rightForeArm: TransformedItemStack,\n        private val cape: TransformedItemStack?\n    ) : SkinData {\n\n        constructor(\n            profile: ModelProfile,\n            skinImage: BufferedImage,\n            capeImage: BufferedImage?\n        ) : this(\n            profile,\n            HEAD.asModelData(skinImage),\n            HIP.asModelData(skinImage),\n            WAIST.asModelData(skinImage),\n            CHEST.asModelData(skinImage),\n            (if (profile.skin().slim) SLIM_LEFT_ARM else LEFT_ARM).asModelData(skinImage),\n            (if (profile.skin().slim) SLIM_RIGHT_ARM else RIGHT_ARM).asModelData(skinImage) ,\n            LEFT_LEG.asModelData(skinImage),\n            LEFT_FORELEG.asModelData(skinImage),\n            RIGHT_LEG.asModelData(skinImage),\n            RIGHT_FORELEG.asModelData(skinImage),\n            (if (profile.skin().slim) SLIM_LEFT_FOREARM else LEFT_FOREARM).asModelData(skinImage).asItem(),\n            (if (profile.skin().slim) SLIM_RIGHT_FOREARM else RIGHT_FOREARM).asModelData(skinImage).asItem(),\n            capeImage?.let { CAPE.asModelData(it).asItem() }\n        )\n        override fun profile(): ModelProfile = profile\n        override fun head(armor: PlayerArmor): TransformedItemStack = head.asItem(ArmorResource.HELMET, armor.helmet())\n        override fun hip(armor: PlayerArmor): TransformedItemStack = hip.asItem(ArmorResource.HIP, armor.leggings())\n        override fun waist(armor: PlayerArmor): TransformedItemStack = waist.asItem(ArmorResource.WAIST, armor.chestplate())\n        override fun chest(armor: PlayerArmor): TransformedItemStack = chest.asItem(ArmorResource.CHEST, armor.chestplate())\n        override fun leftArm(armor: PlayerArmor): TransformedItemStack = leftArm.asItem(ArmorResource.LEFT_ARM, armor.chestplate())\n        override fun rightArm(armor: PlayerArmor): TransformedItemStack = rightArm.asItem(ArmorResource.RIGHT_ARM, armor.chestplate())\n        override fun leftLeg(armor: PlayerArmor): TransformedItemStack = leftLeg.asItem(ArmorResource.LEFT_LEG, armor.leggings())\n        override fun rightLeg(armor: PlayerArmor): TransformedItemStack = rightLeg.asItem(ArmorResource.RIGHT_LEG, armor.leggings())\n        override fun leftForeLeg(armor: PlayerArmor): TransformedItemStack = leftForeLeg.asItem(ArmorResource.LEFT_FORELEG, armor.boots())\n        override fun rightForeLeg(armor: PlayerArmor): TransformedItemStack = rightForeLeg.asItem(ArmorResource.RIGHT_FORELEG, armor.boots())\n        override fun leftForeArm(): TransformedItemStack = leftForeArm\n        override fun rightForeArm(): TransformedItemStack = rightForeArm\n        override fun cape(armor: PlayerArmor): TransformedItemStack? = if (armor.chestplate() != null) cape?.offset(Vector3f(0F, 0F, -1F / 16F)) else cape\n\n        fun refresh() {\n            head.refresh()\n            hip.refresh()\n            waist.refresh()\n            chest.refresh()\n            leftArm.refresh()\n            rightArm.refresh()\n            leftLeg.refresh()\n            leftForeLeg.refresh()\n            rightLeg.refresh()\n            rightForeLeg.refresh()\n        }\n    }\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n        uvNamespace = UVNamespace(\n            CONFIG.namespace(),\n            \"player_limb\"\n        )\n        if (!CONFIG.module().playerAnimation) return\n        write { resource ->\n            zipper.modern().add(resource.path(), resource.estimatedSize()) {\n                resource.build()\n            }\n        }\n        profileCache.asMap().entries.forEach {\n            it.value.refresh()\n        }\n    }\n\n    override fun end() {\n        profileCache.cleanUp()\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/debug/BossBarIndicator.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager.debug\n\nimport kr.toxicity.model.manager.ReloadPipeline\nimport kr.toxicity.model.util.componentOf\nimport kr.toxicity.model.util.emptyComponentOf\nimport kr.toxicity.model.util.toComponent\nimport kr.toxicity.model.util.withComma\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.bossbar.BossBar\nimport net.kyori.adventure.text.format.NamedTextColor\n\nclass BossBarIndicator(\n    private val audience: Audience\n) : ReloadIndicator {\n\n    private var showed = false\n    private val bossBar by lazy {\n        BossBar.bossBar(\n            emptyComponentOf(),\n            0F,\n            BossBar.Color.GREEN,\n            BossBar.Overlay.PROGRESS\n        ).apply {\n            showed = true\n            audience.showBossBar(this)\n        }\n    }\n\n    override fun status(status: ReloadPipeline.Status) {\n        bossBar.run {\n            name(componentOf(status.status) {\n                append(\" (${status.current.withComma()} / ${status.goal.withComma()})\".toComponent(NamedTextColor.YELLOW))\n            })\n            progress(status.progress)\n        }\n    }\n\n    override fun close() {\n        if (showed) audience.hideBossBar(bossBar)\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/manager/debug/ReloadIndicator.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.manager.debug\n\nimport kr.toxicity.model.manager.ReloadPipeline\n\ninterface ReloadIndicator {\n    infix fun status(status: ReloadPipeline.Status)\n    fun close()\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/profile/DefaultHttpModelProfileSupplier.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.profile\n\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSupplier\nimport kr.toxicity.model.util.PLATFORM\n\nclass DefaultHttpModelProfileSupplier : ModelProfileSupplier {\n\n    private val http = HttpModelProfileSupplier()\n\n    override fun supply(info: ModelProfileInfo): ModelProfile.Uncompleted {\n        val player = PLATFORM.adapter().offlinePlayer(info.id)\n        return if (player is PlatformPlayer) ModelProfile.of(player).asUncompleted() else http.supply(info)\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/profile/HttpModelProfileSupplier.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.profile\n\nimport com.github.benmanes.caffeine.cache.Caffeine\nimport com.google.gson.GsonBuilder\nimport com.google.gson.JsonParser\nimport com.mojang.authlib.properties.PropertyMap\nimport com.mojang.util.UUIDTypeAdapter\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\nimport kr.toxicity.model.api.profile.ModelProfileSupplier\nimport kr.toxicity.model.manager.ProfileManagerImpl\nimport kr.toxicity.model.util.buildHttpRequest\nimport kr.toxicity.model.util.handleException\nimport kr.toxicity.model.util.httpClient\nimport java.io.Reader\nimport java.net.URI\nimport java.net.http.HttpResponse\nimport java.util.*\nimport java.util.concurrent.CompletableFuture\nimport java.util.concurrent.TimeUnit\n\n/**\n * This source file is part of BetterModel.\n * Copyright (c) 2024–2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\nclass HttpModelProfileSupplier : ModelProfileSupplier {\n\n    private val profileCache = Caffeine.newBuilder()\n        .expireAfterAccess(5, TimeUnit.MINUTES)\n        .build<ModelProfileInfo, ModelProfile>()\n\n    private val serializer = GsonBuilder()\n        .registerTypeAdapter(UUID::class.java, UUIDTypeAdapter())\n        .registerTypeAdapter(PropertyMap::class.java, PropertyMap.Serializer())\n        .create()\n\n    private data class Profile(\n        val id: UUID,\n        val name: String,\n        val properties: PropertyMap\n    )\n\n    private fun read(reader: Reader) = serializer.fromJson(reader, Profile::class.java)\n\n    override fun supply(info: ModelProfileInfo): ModelProfile.Uncompleted {\n        return object : ModelProfile.Uncompleted {\n            override fun info(): ModelProfileInfo = info\n\n            override fun complete(): CompletableFuture<ModelProfile> {\n                return profileCache.getIfPresent(info)?.let { CompletableFuture.completedFuture(it) } ?: httpClient {\n                    (info.name?.let {\n                        sendAsync(\n                            buildHttpRequest {\n                                GET()\n                                uri(URI.create(\"https://api.minecraftservices.com/minecraft/profile/lookup/name/${it}\"))\n                            },\n                            HttpResponse.BodyHandlers.ofInputStream()\n                        ).thenApply { body ->\n                            body.body().use { body ->\n                                body.reader().use(JsonParser::parseReader)\n                            }.asJsonObject\n                                .getAsJsonPrimitive(\"id\")\n                                .asString\n                        }\n                    } ?: CompletableFuture.completedFuture(info.id.toString().replace(\"-\", \"\"))).thenComposeAsync {\n                        sendAsync(\n                            buildHttpRequest {\n                                GET()\n                                uri(URI.create(\"https://sessionserver.mojang.com/session/minecraft/profile/$it\"))\n                            },\n                            HttpResponse.BodyHandlers.ofInputStream()\n                        )\n                    }.thenApplyAsync {\n                        it.body().use { body ->\n                            body.reader().use(::read)\n                        }.let { profile ->\n                            ModelProfile.of(\n                                ModelProfileInfo(profile.id, profile.name),\n                                profile.properties[\"textures\"].firstOrNull()?.let { property ->\n                                    ProfileManagerImpl.skin(property.value)\n                                } ?: ModelProfileSkin.EMPTY\n                            ).apply {\n                                profileCache.put(info, this)\n                            }\n                        }\n                    }.exceptionally {\n                        it.handleException(\"Unable to get ${info.name}'s skin data.\")\n                        fallback()\n                    }\n                }.orElse {\n                    it.handleException(\"Unable to get ${info.name}'s user data.\")\n                    CompletableFuture.completedFuture(fallback())\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/script/BrightnessScript.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.script\n\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.function.BonePredicate\n\nclass BrightnessScript(\n    val predicate: BonePredicate,\n    val block: Int,\n    val sky: Int\n) : AnimationScript {\n\n    override fun accept(tracker: Tracker) {\n        tracker.update(\n            TrackerUpdateAction.brightness(block, sky),\n            predicate\n        )\n    }\n\n    override fun isSync(): Boolean = false\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/script/ChangePartScript.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.script\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneName\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.function.BonePredicate\n\nclass ChangePartScript(\n    val predicate: BonePredicate,\n    newModel: String,\n    newPart: BoneName\n) : AnimationScript {\n\n    private val model by lazy {\n        BetterModel.modelOrNull(newModel)?.groupByTree(newPart)?.itemStack\n    }\n\n    override fun accept(tracker: Tracker) {\n        model?.let {\n            tracker.update(\n                TrackerUpdateAction.itemStack(it),\n                predicate\n            )\n        }\n    }\n\n    override fun isSync(): Boolean = false\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/script/EnchantScript.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.script\n\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.function.BonePredicate\n\nclass EnchantScript(\n    val predicate: BonePredicate,\n    val enchant: Boolean\n) : AnimationScript {\n\n    override fun accept(tracker: Tracker) {\n        tracker.update(\n            TrackerUpdateAction.enchant(enchant),\n            predicate\n        )\n    }\n\n    override fun isSync(): Boolean = false\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/script/PartVisibilityScript.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.script\n\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.function.BonePredicate\n\nclass PartVisibilityScript(\n    val predicate: BonePredicate,\n    val visible: Boolean\n) : AnimationScript {\n\n    override fun accept(tracker: Tracker) {\n        tracker.update(\n            TrackerUpdateAction.togglePart(visible),\n            predicate\n        )\n    }\n\n    override fun isSync(): Boolean = false\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/script/RemapScript.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.script\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.util.toPackName\nimport kr.toxicity.model.util.toSet\n\nclass RemapScript(\n    model: String,\n    map: String?\n) : AnimationScript {\n\n    private val newModel by lazy {\n        model.toPackName().let {\n            BetterModel.modelOrNull(it)\n        }\n    }\n    private val filter by lazy {\n        map?.let {\n            BetterModel.modelOrNull(it.toPackName())?.flatten()?.map { group ->\n                group.name()\n            }?.toSet()\n        }\n    }\n\n    override fun accept(tracker: Tracker) {\n        val f = filter\n        newModel?.run {\n            tracker.update(TrackerUpdateAction.perBone {\n                (if (f == null || f.contains(it.name())) {\n                    groupByTree(it.name())?.itemStack?.let { item ->\n                        TrackerUpdateAction.itemStack(item)\n                    }\n                } else null) ?: TrackerUpdateAction.none()\n            })\n        }\n    }\n\n    override fun isSync(): Boolean = false\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/script/TintScript.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.script\n\nimport kr.toxicity.model.api.script.AnimationScript\nimport kr.toxicity.model.api.tracker.EntityTracker\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.function.BonePredicate\n\nclass TintScript(\n    val predicate: BonePredicate,\n    val color: Int,\n    val damageTint: Boolean\n) : AnimationScript {\n\n    override fun accept(tracker: Tracker) {\n        if (damageTint && tracker is EntityTracker) {\n            tracker.damageTintValue(color)\n        } else {\n            if (tracker is EntityTracker) tracker.cancelDamageTint()\n            tracker.update(\n                TrackerUpdateAction.tint(color),\n                predicate\n            )\n        }\n    }\n\n    override fun isSync(): Boolean = false\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Buffers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport com.google.gson.JsonElement\nimport kr.toxicity.model.api.data.blueprint.BlueprintImage\nimport kr.toxicity.model.api.data.raw.ModelData\nimport java.io.ByteArrayOutputStream\nimport java.io.OutputStreamWriter\nimport java.nio.charset.StandardCharsets\n\nprivate val IO_BUFFER = ThreadLocal.withInitial { ByteArrayOutputStream(1024) }\n\nfun BlueprintImage.toByteArray(): ByteArray {\n    return image\n}\n\nfun JsonElement.toByteArray(): ByteArray {\n    return IO_BUFFER.get().let { buffer ->\n        buffer.reset()\n        OutputStreamWriter(buffer, StandardCharsets.UTF_8).use {\n            ModelData.GSON.toJson(this, it)\n        }\n        buffer.toByteArray()\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Collections.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport it.unimi.dsi.fastutil.objects.*\nimport kr.toxicity.model.api.util.CollectionUtil\nimport java.util.*\nimport java.util.concurrent.CompletableFuture\nimport java.util.concurrent.Executors\nimport java.util.concurrent.atomic.AtomicInteger\nimport java.util.stream.Collectors\nimport java.util.stream.Stream\n\nfun <K, V> addressingMapOf() = CollectionUtil.newAddressingMap<K, V>()\nfun <K, V> sequencedAddressingMapOf() = CollectionUtil.newSequencedAddressingMap<K, V>()\nfun <K, V> addressingMapOf(capacity: Int) = CollectionUtil.newAddressingMap<K, V>(capacity)\nfun <K, V> sequencedAddressingMapOf(capacity: Int) = CollectionUtil.newSequencedAddressingMap<K, V>(capacity)\n\nfun <K, V> emptySequencedMap(): SequencedMap<K, V> = Collections.emptyNavigableMap()\n\nfun <K, V> MutableMap<K, V>.toImmutableView(): Map<K, V> = when (this) {\n    is Object2ObjectMap<K, V> -> Object2ObjectMaps.unmodifiable(this)\n    is Object2ReferenceMap<K, V> -> Object2ReferenceMaps.unmodifiable(this)\n    is Reference2ObjectMap<K, V> -> Reference2ObjectMaps.unmodifiable(this)\n    is Reference2ReferenceMap<K, V> -> Reference2ReferenceMaps.unmodifiable(this)\n    else -> Collections.unmodifiableMap(this)\n}\n\nfun <K, V> SequencedMap<K, V>.toImmutableView(): SequencedMap<K, V> = when (this) {\n    is Object2ObjectSortedMap<K, V> -> Object2ObjectSortedMaps.unmodifiable(this)\n    is Object2ReferenceSortedMap<K, V> -> Object2ReferenceSortedMaps.unmodifiable(this)\n    is Reference2ObjectSortedMap<K, V> -> Reference2ObjectSortedMaps.unmodifiable(this)\n    is Reference2ReferenceSortedMap<K, V> -> Reference2ReferenceSortedMaps.unmodifiable(this)\n    else -> Collections.unmodifiableSequencedMap(this)\n}\n\nfun <T> Stream<T>.toSet(): Set<T> = collect(Collectors.toUnmodifiableSet())\nfun <T> Stream<T>.toMutableSet(): MutableSet<T> = collect(Collectors.toSet())\n\nfun parallelIOThreadPool() = try {\n    ParallelIOThreadPool()\n} catch (error: OutOfMemoryError) {\n    throw RuntimeException(\"You have to set your Linux max thread limit!\", error)\n}\n\nclass ParallelIOThreadPool : AutoCloseable {\n    private val available = Runtime.getRuntime().availableProcessors() * 2\n    private val integer = AtomicInteger()\n    private val pool = Executors.newFixedThreadPool(available) {\n        Thread(it).apply {\n            isDaemon = true\n            name = \"BetterModel-IO-Worker-${integer.andIncrement}\"\n            uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { thread, exception ->\n                exception.handleException(\"A error has been occurred in ${thread.name}\")\n            }\n        }\n    }\n\n    override fun close() {\n        pool.close()\n    }\n\n    fun <T> forEachParallel(list: List<T>, sizeAssume: (T) -> Long, block: (T) -> Unit) {\n        if (list.isEmpty()) return\n        val size = list.size\n        val lastIndex = list.lastIndex\n        val tasks = if (available >= size) {\n            list.map {\n                {\n                    block(it)\n                }\n            }\n        } else {\n            val sorted = list.sortedBy(sizeAssume)\n            val queue = arrayListOf<() -> Unit>()\n            var i = 0\n            val add = (size.toDouble() / available).toInt()\n            while (i <= size) {\n                val list = ArrayList<T>(add)\n                for (t in i..<(i + add).coerceAtMost(size)) {\n                    val ht = t / 2\n                    list += sorted[if (t % 2 == 0) ht else lastIndex - ht]\n                }\n                queue += {\n                    list.forEach(block)\n                }\n                i += add\n            }\n            queue\n        }\n        CompletableFuture.allOf(\n            *tasks.map {\n                CompletableFuture.runAsync({\n                    it()\n                }, pool)\n            }.toTypedArray()\n        ).join()\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Events.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport kr.toxicity.model.api.event.ModelEvent\nimport kr.toxicity.model.api.util.EventUtil\n\ninline fun <reified T : ModelEvent> callEvent(noinline block: () -> T): Boolean = EventUtil.call(T::class.java) { block() }.triggered()\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Files.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport java.awt.image.BufferedImage\nimport java.io.File\nimport java.io.InputStream\nimport java.nio.file.Files\nimport java.nio.file.Path\nimport java.util.stream.Stream\nimport javax.imageio.ImageIO\n\ninline fun File.getOrCreateDirectory(name: String, initialConsumer: (File) -> Unit = {}) = File(this, name).also { target ->\n    if (!target.exists()) {\n        target.mkdirs()\n        initialConsumer(target)\n    }\n}\n\nfun File.subFiles(): List<File> = listFiles()?.toList() ?: emptyList()\n\ninline fun copyResourceAs(name: String, block: (InputStream) -> Unit) {\n    PLATFORM.getResource(name)?.use(block)\n}\n\nfun File.toImage(): BufferedImage = ImageIO.read(this)\n\nfun File.fileTrees(): Stream<Path> = Files.find(\n    toPath(),\n    Int.MAX_VALUE,\n    { _, attr ->\n        !attr.isDirectory\n    }\n)\n\nfun File.addResource(name: String) {\n    copyResourceAs(name) { input ->\n        File(this, name).outputStream().use {\n            it.buffered().use { output -> input.copyTo(output) }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport kr.toxicity.model.api.bone.BoneName\nimport java.math.BigDecimal\nimport java.text.DecimalFormat\n\nval BYTE_UNIT = BigDecimal(\"1024.000\")\nval COMMA_FORMAT = DecimalFormat(\"#,###\")\nval COMMA_DECIMAL_FORMAT = DecimalFormat(\"#,###.000\")\n\ninline fun <T> T?.ifNull(lazyMessage: () -> String): T & Any = this ?: throw RuntimeException(lazyMessage())\n\nfun Number.withComma(): String = COMMA_FORMAT.format(this)\nval String.boneName get() = BoneName.of(this)\n\nfun Long.toByteFormat(): String {\n    var value = BigDecimal(\"$this.000\")\n    for (format in LengthFormat.entries) {\n        if (value < BYTE_UNIT) return \"${COMMA_DECIMAL_FORMAT.format(value)} ${format.name}\"\n        value /= BYTE_UNIT\n    }\n    return \"${COMMA_DECIMAL_FORMAT.format(value)} ${LengthFormat.entries.last().name}\"\n}\n\nenum class LengthFormat {\n    B,\n    KB,\n    MB,\n    GB\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Gsons.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2024 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport com.google.gson.JsonArray\nimport com.google.gson.JsonElement\nimport com.google.gson.JsonObject\nimport com.google.gson.JsonPrimitive\nimport kr.toxicity.model.api.data.ModelAsset\nimport kr.toxicity.model.api.data.blueprint.ModelBlueprint\n\nfun ModelAsset.toTexturedModel(): ModelBlueprint? = runCatching {\n    toResult().let { result ->\n        if (result.errors.isNotEmpty()) warn(\n            *buildList {\n                add(\"Error has been occurred while parsing this model: ${result.blueprint.name}\")\n                addAll(result.errors)\n            }.map { error -> error.toComponent() }.toTypedArray()\n        )\n        result.blueprint\n    }\n}.handleFailure {\n    \"Unable to load this model: $name\"\n}.getOrNull()\n\nfun buildJsonArray(capacity: Int = 10, block: JsonArray.() -> Unit) = JsonArray(capacity).apply(block)\nfun buildJsonObject(block: JsonObject.() -> Unit) = JsonObject().apply(block)\n\nfun jsonArrayOf(vararg element: Any?) = buildJsonArray {\n    element.filterNotNull().forEach {\n        add(it.toJsonElement())\n    }\n}\n\nfun jsonObjectOf(vararg element: Pair<String, Any>) = buildJsonObject {\n    element.forEach {\n        add(it.first, it.second.toJsonElement())\n    }\n}\n\noperator fun JsonArray.plusAssign(other: JsonElement) {\n    add(other)\n}\n\nfun Any.toJsonElement(): JsonElement = when (this) {\n    is String -> JsonPrimitive(this)\n    is Char -> JsonPrimitive(this)\n    is Number -> JsonPrimitive(this)\n    is Boolean -> JsonPrimitive(this)\n    is JsonElement -> this\n    is List<*> -> run {\n        val map = mapNotNull {\n            it?.toJsonElement()\n        }\n        buildJsonArray(map.size) {\n            map.forEach {\n                add(it)\n            }\n        }\n    }\n    is Map<*, *> -> buildJsonObject {\n        forEach {\n            add(it.key?.toString() ?: return@forEach, it.value?.toJsonElement() ?: return@forEach)\n        }\n    }\n    else -> throw RuntimeException(\"Unsupported type: ${javaClass.name}\")\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Indicators.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport kr.toxicity.model.api.config.IndicatorConfig\nimport kr.toxicity.model.api.manager.ReloadInfo\nimport kr.toxicity.model.manager.debug.BossBarIndicator\nimport kr.toxicity.model.manager.debug.ReloadIndicator\nimport java.util.*\n\nprivate typealias Type = IndicatorConfig.IndicatorOption\n\nprivate val INDICATOR_MAP = EnumMap<Type, (ReloadInfo) -> ReloadIndicator?>(Type::class.java).apply {\n    put(Type.PROGRESS_BAR) {\n        BossBarIndicator(it.sender)\n    }\n}\n\nfun Type.toIndicator(info: ReloadInfo) = INDICATOR_MAP[this]?.invoke(info)\nfun Iterable<Type>.toIndicator(info: ReloadInfo) = mapNotNull {\n    it.toIndicator(info)\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Packs.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport kr.toxicity.model.api.BetterModelConfig\nimport kr.toxicity.model.api.BetterModelConfig.PackType.*\nimport kr.toxicity.model.api.pack.*\nimport kr.toxicity.model.manager.ReloadPipeline\nimport net.kyori.adventure.text.format.NamedTextColor\nimport java.io.File\nimport java.nio.file.Files\nimport java.nio.file.Path\nimport java.util.concurrent.atomic.AtomicBoolean\nimport java.util.zip.Deflater\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipOutputStream\nimport kotlin.io.path.pathString\n\nfun BetterModelConfig.PackType.toGenerator() = when (this) {\n    FOLDER -> FolderGenerator()\n    ZIP -> ZipGenerator()\n    NONE -> NoneGenerator()\n}\n\ninterface PackGenerator {\n    val exists: Boolean\n    fun create(zipper: PackZipper, pipeline: ReloadPipeline): PackResult\n    fun hashEquals(result: PackResult): Boolean {\n        val hash = result.hash().toString()\n        return File(DATA_FOLDER.getOrCreateDirectory(\".cache\"), \"zip-hash.txt\").run {\n            if (!exists || !exists() || readText() != hash) {\n                writeText(hash)\n                true\n            } else false\n        }\n    }\n}\n\nclass FolderGenerator : PackGenerator {\n    private val file = File(DATA_FOLDER.parent, CONFIG.buildFolderLocation())\n    override val exists: Boolean = file.exists()\n    private val fileTree by lazy {\n        sortedMapOf<String, Path>(reverseOrder()).apply {\n            val after = CONFIG.buildFolderLocation() + File.separatorChar\n            Files.walk(file.apply {\n                mkdirs()\n            }.toPath()).use { stream ->\n                stream.forEach {\n                    put(it.pathString.substringAfter(after), it)\n                }\n            }\n        }\n    }\n\n    private fun PackPath.toFile(): File {\n        val replaced = path.replace('/', File.separatorChar)\n        return synchronized(fileTree) {\n            fileTree.remove(replaced)?.toFile()\n        } ?: File(file, replaced).apply {\n            parentFile.mkdirs()\n        }\n    }\n\n    override fun create(zipper: PackZipper, pipeline: ReloadPipeline): PackResult {\n        val build = zipper.build()\n        val pack = PackResult(build.meta(), file)\n        val changed = AtomicBoolean()\n        pipeline.forEachParallel(build.resources(), PackResource::estimatedSize) {\n            val bytes = it.get()\n            pack[it.overlay()] = PackByte(it.path(), bytes)\n            val file = it.path().toFile()\n            val index = pipeline.progress()\n            if (file.length() != bytes.size.toLong()) {\n                file.writeBytes(bytes)\n                changed.set(true)\n                debugPack {\n                    componentOf(\n                        \"This file was successfully generated: \".toComponent(),\n                        it.path().path.toComponent(NamedTextColor.GREEN),\n                        \" ($index/${pipeline.goal})\".toComponent(NamedTextColor.DARK_GRAY)\n                    )\n                }\n            }\n        }\n        fileTree.values.forEach {\n            it.toFile().delete()\n        }\n        return pack.apply {\n            freeze(changed.get())\n        }\n    }\n}\n\nclass ZipGenerator : PackGenerator {\n    private val file = File(DATA_FOLDER.parent, \"${CONFIG.buildFolderLocation()}.zip\")\n    override val exists: Boolean = file.exists()\n\n    override fun create(zipper: PackZipper, pipeline: ReloadPipeline): PackResult {\n        return zipper.writeToResult(pipeline, file).apply {\n            freeze(hashEquals(this))\n        }.apply {\n            if (!changed()) return this\n            fun zip(zip: ZipOutputStream) {\n                zip.setLevel(Deflater.BEST_COMPRESSION)\n                zip.setComment(\"BetterModel's generated resource pack.\")\n                stream().forEach {\n                    zip.putNextEntry(ZipEntry(it.path().path()))\n                    zip.write(it.bytes())\n                    zip.closeEntry()\n                }\n            }\n            file.outputStream().use {\n                it.buffered().use { buffered ->\n                    ZipOutputStream(buffered).use(::zip)\n                }\n            }\n        }\n    }\n}\n\nclass NoneGenerator : PackGenerator {\n    override val exists: Boolean = false\n    override fun create(zipper: PackZipper, pipeline: ReloadPipeline): PackResult {\n        return zipper.writeToResult(pipeline).apply {\n            freeze()\n        }\n    }\n}\n\nfun PackZipper.writeToResult(pipeline: ReloadPipeline, dir: File? = null): PackResult {\n    val build = build()\n    return PackResult(build.meta(), dir).apply {\n        pipeline.forEachParallel(build.resources(), PackResource::estimatedSize) {\n            set(it.overlay(), PackByte(it.path(), it.get()))\n            val index = pipeline.progress()\n            debugPack {\n                componentOf(\n                    \"This file was successfully zipped: \".toComponent(),\n                    it.path().path.toComponent(NamedTextColor.GREEN),\n                    \" ($index/${pipeline.goal})\".toComponent(NamedTextColor.DARK_GRAY)\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Platforms.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport com.github.benmanes.caffeine.cache.Caffeine\nimport com.google.gson.GsonBuilder\nimport kr.toxicity.model.BetterModelPlatformImpl\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.util.HttpUtil\nimport kr.toxicity.model.api.util.LogUtil\nimport kr.toxicity.model.api.util.PackUtil\nimport net.kyori.adventure.text.Component\nimport net.kyori.adventure.text.format.NamedTextColor\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.net.http.HttpClient\nimport java.net.http.HttpRequest\nimport java.net.http.HttpResponse\nimport java.nio.charset.StandardCharsets\nimport java.util.concurrent.TimeUnit\n\nval PLATFORM\n    get() = BetterModel.platform() as BetterModelPlatformImpl\nval CONFIG\n    get() = BetterModel.config()\nval DATA_FOLDER\n    get() = PLATFORM.dataFolder()\n\nprivate val LATEST_VERSION_CACHE = Caffeine.newBuilder()\n    .expireAfterWrite(5, TimeUnit.MINUTES)\n    .build<Any, HttpUtil.LatestVersion> { HttpUtil.versionList() }\n\nprivate val GSON = GsonBuilder().disableHtmlEscaping().create()\n\nval LATEST_VERSION: HttpUtil.LatestVersion get() = LATEST_VERSION_CACHE.get(Unit)\n\nfun info(vararg message: Component) = PLATFORM.logger().info(*message)\nfun warn(vararg message: Component) = PLATFORM.logger().warn(*message)\ninline fun debugPack(lazyMessage: () -> Component) {\n    if (CONFIG.debug().has(DebugConfig.DebugOption.PACK)) info(componentOf(\n        \"[${Thread.currentThread().name}] \".toComponent(NamedTextColor.YELLOW),\n        lazyMessage()\n    ))\n}\n\nfun Throwable.handleException(message: String) = LogUtil.handleException(message, this)\n\ninline fun <T> Result<T>.handleFailure(lazyMessage: () -> String) = onFailure {\n    it.handleException(lazyMessage())\n}\n\nfun String.toPackName() = PackUtil.toPackName(this)\n\nfun <T : Any> httpClient(block: HttpClient.() -> T): HttpUtil.Result<T> = HttpUtil.client {\n    it.block()\n}\n\nfun buildHttpRequest(builder: HttpRequest.Builder.() -> Unit): HttpRequest = HttpRequest.newBuilder().apply(builder).build()\n\n\nfun <T> HttpResponse<InputStream>.toJson(clazz: Class<T>): T = body().use {\n    InputStreamReader(it, StandardCharsets.UTF_8).use { reader -> GSON.fromJson(reader, clazz) }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Scripts.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport kr.toxicity.model.api.script.ScriptBuilder\nimport kr.toxicity.model.api.util.function.BonePredicate\n\nval ScriptBuilder.ScriptMetaData.bonePredicate get(): BonePredicate {\n    val match = asBoolean(\"exact\") != false\n    val children = asBoolean(\"children\") == true\n    val part = asString(\"part\")?.boneName?.name\n    return if (part == null) BonePredicate.TRUE else {\n        BonePredicate.of(if (children) BonePredicate.State.TRUE else BonePredicate.State.FALSE, if (match) {\n            { b ->\n                b.name().name == part\n            }\n        } else {\n            { b ->\n                b.name().name.contains(part, ignoreCase = true)\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "core/src/main/kotlin/kr/toxicity/model/util/Senders.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.util\n\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.text.Component\nimport net.kyori.adventure.text.ComponentLike\nimport net.kyori.adventure.text.TextComponent\nimport net.kyori.adventure.text.event.HoverEvent\nimport net.kyori.adventure.text.format.NamedTextColor\nimport net.kyori.adventure.text.format.TextColor\nimport net.kyori.adventure.text.format.TextDecoration\n\nval INFO = \" [!] \".toComponent {\n    decorate(TextDecoration.BOLD).color(NamedTextColor.GREEN)\n}\nval WARN = \" [!] \".toComponent {\n    decorate(TextDecoration.BOLD).color(NamedTextColor.RED)\n}\n\ninline fun String.toComponent(builder: TextComponent.Builder.() -> TextComponent.Builder = { this }) = componentOf(this, builder)\nfun String.toComponent(color: TextColor) = componentOf(this) {\n    color(color)\n}\n\nfun componentOf() = Component.text()\nfun spaceComponentOf() = Component.space()\nfun emptyComponentOf() = Component.empty()\nfun lineComponentOf() = Component.newline()\nfun componentOf(vararg like: ComponentLike) = componentOf {\n    append(*like)\n}\nfun componentWithLineOf(vararg like: ComponentLike) = componentOf {\n    like.forEachIndexed { i, l ->\n        append(l)\n        if (i < like.lastIndex) append(lineComponentOf())\n    }\n    this\n}\ninline fun componentOf(content: String, builder: TextComponent.Builder.() -> TextComponent.Builder) = componentOf {\n    content(content).let(builder)\n}\ninline fun componentOf(builder: TextComponent.Builder.() -> TextComponent.Builder) = componentOf().let(builder).build()\nfun ComponentLike.toHoverEvent() = HoverEvent.showText(this)\n\nfun Audience.info(message: String) = info(message.toComponent())\nfun Audience.warn(message: String) = warn(message.toComponent())\nfun Audience.infoNotNull(vararg messages: ComponentLike?): Unit = info(*messages.filterNotNull().ifEmpty {\n    return\n}.toTypedArray())\nfun Audience.info(vararg messages: ComponentLike) = sendMessage(componentWithLineOf(*messages.map { componentOf(INFO, it) }.toTypedArray()))\nfun Audience.warn(vararg messages: ComponentLike) = sendMessage(componentWithLineOf(*messages.map { componentOf(WARN, it) }.toTypedArray()))\nfun Audience.info(message: ComponentLike) = sendMessage(componentOf(INFO, message))\nfun Audience.warn(message: ComponentLike) = sendMessage(componentOf(WARN, message))\n"
  },
  {
    "path": "core/src/main/resources/blue_wizard.bbmodel",
    "content": "{\"meta\":{\"format_version\":\"5.0\",\"model_format\":\"free\",\"box_uv\":false},\"name\":\"blue_wizard\",\"model_identifier\":\"\",\"visible_box\":[1,1,0],\"variable_placeholders\":\"\",\"variable_placeholder_buttons\":[],\"timeline_setups\":[],\"unhandled_root_fields\":{},\"resolution\":{\"width\":128,\"height\":128},\"elements\":[{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.66264,27.39,-1.19375],\"to\":[0.53736,28.51,0.84625],\"autouv\":0,\"color\":1,\"origin\":[-0.06264,28.19,-0.39375],\"faces\":{\"north\":{\"uv\":[62,33,64,34],\"texture\":0},\"east\":{\"uv\":[24,11,27,12],\"texture\":0},\"south\":{\"uv\":[62,36,64,37],\"texture\":0},\"west\":{\"uv\":[36,10,39,11],\"texture\":0},\"up\":{\"uv\":[15,20,13,17],\"texture\":0},\"down\":{\"uv\":[49,41,47,44],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3ba71253-feb2-8e7b-4548-01cd3e1df6ab\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.15736,27.63,-1.21375],\"to\":[2.15736,28.83,0.86625],\"autouv\":0,\"color\":1,\"rotation\":[0,0,22.5],\"origin\":[0.95736,28.43,-0.39375],\"faces\":{\"north\":{\"uv\":[50,29,53,31],\"texture\":0},\"east\":{\"uv\":[50,31,53,33],\"texture\":0},\"south\":{\"uv\":[32,50,35,52],\"texture\":0},\"west\":{\"uv\":[50,33,53,35],\"texture\":0},\"up\":{\"uv\":[3,44,0,41],\"texture\":0},\"down\":{\"uv\":[44,6,41,9],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d4d3e984-46f1-f6da-5c1f-cae44bfd11b3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.28264,27.63,-1.21375],\"to\":[-0.28264,28.83,0.86625],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-22.5],\"origin\":[-1.08264,28.43,-0.39375],\"faces\":{\"north\":{\"uv\":[51,6,54,8],\"texture\":0},\"east\":{\"uv\":[10,51,13,53],\"texture\":0},\"south\":{\"uv\":[51,14,54,16],\"texture\":0},\"west\":{\"uv\":[51,16,54,18],\"texture\":0},\"up\":{\"uv\":[44,17,41,14],\"texture\":0},\"down\":{\"uv\":[44,17,41,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0f36a065-bbfb-34aa-9cd2-94c66b683a68\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.55736,28.47,-1.21375],\"to\":[3.19736,29.67,0.86625],\"autouv\":0,\"color\":1,\"rotation\":[0,0,45],\"origin\":[2.35736,29.27,-0.39375],\"faces\":{\"north\":{\"uv\":[57,4,59,6],\"texture\":0},\"east\":{\"uv\":[35,50,38,52],\"texture\":0},\"south\":{\"uv\":[8,57,10,59],\"texture\":0},\"west\":{\"uv\":[50,35,53,37],\"texture\":0},\"up\":{\"uv\":[52,40,50,37],\"texture\":0},\"down\":{\"uv\":[45,50,43,53],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"de0f931c-847a-9947-b487-f6a601951d69\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.30264,28.47,-1.21375],\"to\":[-1.68264,29.67,0.86625],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-45],\"origin\":[-2.48264,29.27,-0.39375],\"faces\":{\"north\":{\"uv\":[57,39,59,41],\"texture\":0},\"east\":{\"uv\":[45,50,48,52],\"texture\":0},\"south\":{\"uv\":[57,41,59,43],\"texture\":0},\"west\":{\"uv\":[48,50,51,52],\"texture\":0},\"up\":{\"uv\":[2,54,0,51],\"texture\":0},\"down\":{\"uv\":[10,51,8,54],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"72a8e5fb-7b1b-994b-fc27-349d355d138b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.64264,29.29,-1.19375],\"to\":[-2.82264,32.91,0.40625],\"autouv\":0,\"color\":1,\"origin\":[2.89736,29.71,-0.39375],\"faces\":{\"north\":{\"uv\":[21,54,22,59],\"texture\":0},\"east\":{\"uv\":[34,6,36,11],\"texture\":0},\"south\":{\"uv\":[22,54,23,59],\"texture\":0},\"west\":{\"uv\":[8,34,10,39],\"texture\":0},\"up\":{\"uv\":[15,14,14,12],\"texture\":0},\"down\":{\"uv\":[13,21,12,23],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fdcdff03-57c1-e071-5769-588fbc314386\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.80264,28.91,0.40625],\"to\":[2.69736,32.91,1.20625],\"autouv\":0,\"color\":1,\"origin\":[2.89736,29.71,-0.39375],\"faces\":{\"north\":{\"uv\":[0,0,7,5],\"texture\":0},\"east\":{\"uv\":[2,55,3,60],\"texture\":0},\"south\":{\"uv\":[0,5,7,10],\"texture\":0},\"west\":{\"uv\":[3,55,4,60],\"texture\":0},\"up\":{\"uv\":[31,48,24,47],\"texture\":0},\"down\":{\"uv\":[54,28,47,29],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3072f681-6e4b-aef4-27b4-6990650a7f1b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.00264,28.71,-0.21375],\"to\":[1.89736,29.69,0.64625],\"autouv\":0,\"color\":1,\"rotation\":[22.5,0,0],\"origin\":[2.89736,27.89,-1.01375],\"faces\":{\"north\":{\"uv\":[55,25,60,26],\"texture\":0},\"east\":{\"uv\":[36,65,37,66],\"texture\":0},\"south\":{\"uv\":[26,55,31,56],\"texture\":0},\"west\":{\"uv\":[37,65,38,66],\"texture\":0},\"up\":{\"uv\":[60,27,55,26],\"texture\":0},\"down\":{\"uv\":[60,27,55,28],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9a276e96-6c77-ddd3-368c-f181c5dfb929\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.00264,29.71,0.40625],\"to\":[1.89736,32.91,1.20625],\"autouv\":0,\"color\":1,\"origin\":[2.89736,29.71,-1.19375],\"faces\":{\"north\":{\"uv\":[8,17,13,21],\"texture\":0},\"east\":{\"uv\":[13,6,14,10],\"texture\":0},\"south\":{\"uv\":[18,5,23,9],\"texture\":0},\"west\":{\"uv\":[44,56,45,60],\"texture\":0},\"up\":{\"uv\":[60,24,55,23],\"texture\":0},\"down\":{\"uv\":[60,24,55,25],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1812dc1c-20ad-522f-4394-729108bec75e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.55736,29.83,-1.59375],\"to\":[3.53736,31.55,0.84625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[2.07736,29.15,-0.39375],\"faces\":{\"north\":{\"uv\":[21,50,22,52],\"texture\":0},\"east\":{\"uv\":[13,48,16,50],\"texture\":0},\"south\":{\"uv\":[38,50,39,52],\"texture\":0},\"west\":{\"uv\":[48,14,51,16],\"texture\":0},\"up\":{\"uv\":[12,63,11,60],\"texture\":0},\"down\":{\"uv\":[13,60,12,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c801537a-5f55-042c-3435-e557b199c416\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.41736,28.91,0.58625],\"to\":[1.95736,32.91,1.44625],\"autouv\":0,\"color\":1,\"rotation\":[0,45,0],\"origin\":[1.77736,29.71,-0.21375],\"faces\":{\"north\":{\"uv\":[4,55,5,60],\"texture\":0},\"east\":{\"uv\":[5,55,6,60],\"texture\":0},\"south\":{\"uv\":[55,9,56,14],\"texture\":0},\"west\":{\"uv\":[10,55,11,60],\"texture\":0},\"up\":{\"uv\":[34,66,33,65],\"texture\":0},\"down\":{\"uv\":[66,34,65,35],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"cc3612bb-4f96-dc1e-46ad-d21b86d4df71\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.59736,31.79,-1.79375],\"to\":[4.37736,32.89,0.60625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,45],\"origin\":[2.89736,32.11,-0.39375],\"faces\":{\"north\":{\"uv\":[62,39,64,40],\"texture\":0},\"east\":{\"uv\":[39,32,42,33],\"texture\":0},\"south\":{\"uv\":[62,40,64,41],\"texture\":0},\"west\":{\"uv\":[40,3,43,4],\"texture\":0},\"up\":{\"uv\":[49,50,47,47],\"texture\":0},\"down\":{\"uv\":[2,48,0,51],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"83e3beb0-d1dd-6e57-57ce-dee9c9bdd8b9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.06264,28.91,0.58625],\"to\":[-1.54264,32.91,1.44625],\"autouv\":0,\"color\":1,\"rotation\":[0,-45,0],\"origin\":[-1.90264,29.71,-0.21375],\"faces\":{\"north\":{\"uv\":[11,55,12,60],\"texture\":0},\"east\":{\"uv\":[12,55,13,60],\"texture\":0},\"south\":{\"uv\":[13,55,14,60],\"texture\":0},\"west\":{\"uv\":[14,55,15,60],\"texture\":0},\"up\":{\"uv\":[36,66,35,65],\"texture\":0},\"down\":{\"uv\":[66,35,65,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2d2daeae-d4e6-d6a9-d0d7-ac5a204040fe\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.62264,30.45,-1.59375],\"to\":[-2.74264,32.51,0.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[-2.22264,30.11,-0.39375],\"faces\":{\"north\":{\"uv\":[60,40,61,43],\"texture\":0},\"east\":{\"uv\":[31,40,34,43],\"texture\":0},\"south\":{\"uv\":[60,43,61,46],\"texture\":0},\"west\":{\"uv\":[34,40,37,43],\"texture\":0},\"up\":{\"uv\":[45,63,44,60],\"texture\":0},\"down\":{\"uv\":[52,60,51,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7f7169fb-d626-e57e-2176-8ade273cc9f4\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.42264,30.65,-1.79375],\"to\":[-2.72264,32.13,0.58625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[-3.02264,32.11,-0.39375],\"faces\":{\"north\":{\"uv\":[0,57,2,59],\"texture\":0},\"east\":{\"uv\":[13,50,16,52],\"texture\":0},\"south\":{\"uv\":[57,2,59,4],\"texture\":0},\"west\":{\"uv\":[16,50,19,52],\"texture\":0},\"up\":{\"uv\":[21,53,19,50],\"texture\":0},\"down\":{\"uv\":[26,50,24,53],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d47bc743-9279-3d45-6344-f7035899232d\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.39736,32.11,-1.99375],\"to\":[3.55736,35.09,0.60625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[1.74736,32.91,-0.69375],\"faces\":{\"north\":{\"uv\":[16,23,19,27],\"texture\":0},\"east\":{\"uv\":[0,30,3,34],\"texture\":0},\"south\":{\"uv\":[3,30,6,34],\"texture\":0},\"west\":{\"uv\":[6,30,9,34],\"texture\":0},\"up\":{\"uv\":[28,43,25,40],\"texture\":0},\"down\":{\"uv\":[31,40,28,43],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e81b7843-20f5-c76e-a1a1-e0e6e87d6767\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.20264,31.71,-1.99375],\"to\":[1.15736,33.71,-1.19375],\"autouv\":0,\"color\":9,\"rotation\":[0,0,45],\"origin\":[-0.05264,32.51,-2.49375],\"faces\":{\"north\":{\"uv\":[50,8,52,11],\"texture\":0},\"east\":{\"uv\":[36,60,37,63],\"texture\":0},\"south\":{\"uv\":[50,11,52,14],\"texture\":0},\"west\":{\"uv\":[37,60,38,63],\"texture\":0},\"up\":{\"uv\":[65,11,63,10],\"texture\":0},\"down\":{\"uv\":[65,11,63,12],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5729025f-74e3-e5dc-3d49-8d8ec85d1766\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.97736,33.31,0.60625],\"to\":[4.77736,35.11,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,45],\"origin\":[3.04736,32.71,1.40625],\"faces\":{\"north\":{\"uv\":[56,13,58,15],\"texture\":0},\"east\":{\"uv\":[56,15,58,17],\"texture\":0},\"south\":{\"uv\":[56,17,58,19],\"texture\":0},\"west\":{\"uv\":[19,56,21,58],\"texture\":0},\"up\":{\"uv\":[58,21,56,19],\"texture\":0},\"down\":{\"uv\":[58,21,56,23],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"571974f4-bc0d-5b57-c405-e5653335ee06\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.96264,31.19,2.40625],\"to\":[-0.42264,32.39,3.82625],\"autouv\":0,\"color\":5,\"origin\":[2.29736,27.15,-0.39375],\"faces\":{\"north\":{\"uv\":[48,21,51,23],\"texture\":0},\"east\":{\"uv\":[56,0,58,2],\"texture\":0},\"south\":{\"uv\":[48,23,51,25],\"texture\":0},\"west\":{\"uv\":[6,56,8,58],\"texture\":0},\"up\":{\"uv\":[27,50,24,48],\"texture\":0},\"down\":{\"uv\":[51,25,48,27],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"676e5395-2ca6-1959-efe3-e41270439957\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.40264,28.51,0.10625],\"to\":[2.29736,30.89,1.52625],\"autouv\":0,\"color\":1,\"rotation\":[45,0,0],\"origin\":[-0.10264,29.35,0.83625],\"faces\":{\"north\":{\"uv\":[13,20,19,23],\"texture\":0},\"east\":{\"uv\":[22,49,24,52],\"texture\":0},\"south\":{\"uv\":[21,0,27,3],\"texture\":0},\"west\":{\"uv\":[39,49,41,52],\"texture\":0},\"up\":{\"uv\":[25,31,19,29],\"texture\":0},\"down\":{\"uv\":[31,29,25,31],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8c0b6782-b9e2-cf2d-31b4-c96d97905fd0\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.71736,29.85,-0.79375],\"to\":[3.39736,31.91,0.00625],\"autouv\":0,\"color\":9,\"origin\":[2.19736,29.51,-0.19375],\"faces\":{\"north\":{\"uv\":[13,60,14,63],\"texture\":0},\"east\":{\"uv\":[14,60,15,63],\"texture\":0},\"south\":{\"uv\":[15,60,16,63],\"texture\":0},\"west\":{\"uv\":[16,60,17,63],\"texture\":0},\"up\":{\"uv\":[17,66,16,65],\"texture\":0},\"down\":{\"uv\":[66,16,65,17],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2d82ea57-745f-beb3-0c88-6c509f7a74ed\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.67736,29.29,-0.79375],\"to\":[3.03736,30.11,0.00625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[2.59736,29.08,-0.19375],\"faces\":{\"north\":{\"uv\":[17,65,18,66],\"texture\":0},\"east\":{\"uv\":[65,17,66,18],\"texture\":0},\"south\":{\"uv\":[18,65,19,66],\"texture\":0},\"west\":{\"uv\":[19,65,20,66],\"texture\":0},\"up\":{\"uv\":[66,20,65,19],\"texture\":0},\"down\":{\"uv\":[66,20,65,21],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9a8f1ae5-db04-1afe-d3e5-624faaad48e9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.34264,35.75,-1.97375],\"to\":[-0.62264,36.59,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[0.11736,36.03,0.21625],\"faces\":{\"north\":{\"uv\":[62,48,64,49],\"texture\":0},\"east\":{\"uv\":[26,54,31,55],\"texture\":0},\"south\":{\"uv\":[62,54,64,55],\"texture\":0},\"west\":{\"uv\":[54,28,59,29],\"texture\":0},\"up\":{\"uv\":[27,40,25,35],\"texture\":0},\"down\":{\"uv\":[29,35,27,40],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"448668fe-7911-1061-80f2-3d5c40d07984\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.56264,33.23,-1.97375],\"to\":[-1.62264,35.09,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,45],\"origin\":[-2.62264,34.49,0.60625],\"faces\":{\"north\":{\"uv\":[6,44,10,46],\"texture\":0},\"east\":{\"uv\":[34,11,39,13],\"texture\":0},\"south\":{\"uv\":[44,6,48,8],\"texture\":0},\"west\":{\"uv\":[34,13,39,15],\"texture\":0},\"up\":{\"uv\":[19,20,15,15],\"texture\":0},\"down\":{\"uv\":[21,0,17,5],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2ebc9aa9-73df-e4ed-0a7f-f22cc51c6feb\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.00264,33.35,-1.97375],\"to\":[0.17736,34.59,-1.19375],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[-0.94264,34.49,0.60625],\"faces\":{\"north\":{\"uv\":[35,0,40,2],\"texture\":0},\"east\":{\"uv\":[20,62,21,64],\"texture\":0},\"south\":{\"uv\":[35,2,40,4],\"texture\":0},\"west\":{\"uv\":[24,62,25,64],\"texture\":0},\"up\":{\"uv\":[58,37,53,36],\"texture\":0},\"down\":{\"uv\":[59,7,54,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8ec78ad0-d84a-0628-5443-fbd1c509e136\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.575,26.75,-0.4],\"to\":[0.875,30,1.025],\"autouv\":0,\"color\":1,\"rotation\":[0,45,0],\"origin\":[0.175,28.4,0.525],\"faces\":{\"north\":{\"uv\":[43,47,44,50],\"texture\":0},\"east\":{\"uv\":[5,60,6,63],\"texture\":0},\"south\":{\"uv\":[45,47,46,50],\"texture\":0},\"west\":{\"uv\":[10,60,11,63],\"texture\":0},\"up\":{\"uv\":[64,38,62,37],\"texture\":0},\"down\":{\"uv\":[64,38,62,39],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"41e7b572-b3cb-756c-9a95-64b8f5f33769\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.54264,31.31,0.60625],\"to\":[-1.26264,32.99,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[-3.17264,32.71,1.40625],\"faces\":{\"north\":{\"uv\":[51,19,54,21],\"texture\":0},\"east\":{\"uv\":[57,57,59,59],\"texture\":0},\"south\":{\"uv\":[51,21,54,23],\"texture\":0},\"west\":{\"uv\":[58,0,60,2],\"texture\":0},\"up\":{\"uv\":[54,25,51,23],\"texture\":0},\"down\":{\"uv\":[54,25,51,27],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9b4e3438-f7a3-6771-95bc-51b94b1899e8\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.34264,32.79,2.62625],\"to\":[2.57736,34.27,3.40625],\"autouv\":0,\"color\":9,\"rotation\":[-45,0,0],\"origin\":[0.11736,33.71,1.25625],\"faces\":{\"north\":{\"uv\":[28,6,34,8],\"texture\":0},\"east\":{\"uv\":[60,62,61,64],\"texture\":0},\"south\":{\"uv\":[28,8,34,10],\"texture\":0},\"west\":{\"uv\":[4,63,5,65],\"texture\":0},\"up\":{\"uv\":[34,11,28,10],\"texture\":0},\"down\":{\"uv\":[54,18,48,19],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0335203d-2335-ce64-0a28-16715076876f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.80264,29.95,0.94625],\"to\":[2.69736,32.75,2.40625],\"autouv\":0,\"color\":9,\"origin\":[2.89736,29.71,-0.39375],\"faces\":{\"north\":{\"uv\":[0,10,7,14],\"texture\":0},\"east\":{\"uv\":[4,23,6,27],\"texture\":0},\"south\":{\"uv\":[7,10,14,14],\"texture\":0},\"west\":{\"uv\":[16,40,18,44],\"texture\":0},\"up\":{\"uv\":[22,29,15,27],\"texture\":0},\"down\":{\"uv\":[29,27,22,29],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f0e1b661-bd04-bf34-5724-e4ce9acc5e75\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.36264,29.95,2.08625],\"to\":[2.57736,32.59,3.12625],\"autouv\":0,\"color\":9,\"rotation\":[22.5,0,0],\"origin\":[0.10736,31.73,2.77625],\"faces\":{\"north\":{\"uv\":[19,18,25,21],\"texture\":0},\"east\":{\"uv\":[34,60,35,63],\"texture\":0},\"south\":{\"uv\":[0,20,6,23],\"texture\":0},\"west\":{\"uv\":[35,60,36,63],\"texture\":0},\"up\":{\"uv\":[55,42,49,41],\"texture\":0},\"down\":{\"uv\":[55,42,49,43],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b3cf2f69-86ff-ed07-8934-90d0425f413e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.56264,31.37,-1.79375],\"to\":[-4.00264,32.95,0.58625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[-3.02264,32.91,-0.39375],\"faces\":{\"north\":{\"uv\":[12,63,13,65],\"texture\":0},\"east\":{\"uv\":[26,50,29,52],\"texture\":0},\"south\":{\"uv\":[13,63,14,65],\"texture\":0},\"west\":{\"uv\":[29,50,32,52],\"texture\":0},\"up\":{\"uv\":[61,40,60,37],\"texture\":0},\"down\":{\"uv\":[39,60,38,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6b77988c-a45d-1c22-dc83-86073939178e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.78652,28.63002,-1.59375],\"to\":[3.68652,29.59002,-0.99375],\"autouv\":0,\"color\":9,\"origin\":[3.22652,29.23002,-1.29375],\"faces\":{\"north\":{\"uv\":[0,65,1,66],\"texture\":0},\"east\":{\"uv\":[65,0,66,1],\"texture\":0},\"south\":{\"uv\":[1,65,2,66],\"texture\":0},\"west\":{\"uv\":[65,1,66,2],\"texture\":0},\"up\":{\"uv\":[3,66,2,65],\"texture\":0},\"down\":{\"uv\":[66,2,65,3],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5573dea6-d06d-26ef-659c-5e9dfee7c967\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.6518,29.35002,-1.59375],\"to\":[-2.8318,30.23002,-0.99375],\"autouv\":0,\"color\":9,\"origin\":[-3.2118,29.41002,-1.29375],\"faces\":{\"north\":{\"uv\":[7,65,8,66],\"texture\":0},\"east\":{\"uv\":[8,65,9,66],\"texture\":0},\"south\":{\"uv\":[9,65,10,66],\"texture\":0},\"west\":{\"uv\":[65,9,66,10],\"texture\":0},\"up\":{\"uv\":[11,66,10,65],\"texture\":0},\"down\":{\"uv\":[66,10,65,11],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b3fd65a2-c4d8-8976-2bd9-eecda71c86b9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.25736,31.53,0.60625],\"to\":[3.67736,33.73,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[1.94736,31.33,1.40625],\"faces\":{\"north\":{\"uv\":[49,47,51,50],\"texture\":0},\"east\":{\"uv\":[50,0,52,3],\"texture\":0},\"south\":{\"uv\":[50,3,52,6],\"texture\":0},\"west\":{\"uv\":[6,50,8,53],\"texture\":0},\"up\":{\"uv\":[38,58,36,56],\"texture\":0},\"down\":{\"uv\":[40,56,38,58],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8ba2c460-3e0d-40be-a9fc-cd7ae4a4574c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.68264,34.05,-0.15375],\"to\":[2.37736,35.51,1.28625],\"autouv\":0,\"color\":9,\"rotation\":[-45,0,0],\"origin\":[4.15736,31.57,-0.39375],\"faces\":{\"north\":{\"uv\":[35,27,40,29],\"texture\":0},\"east\":{\"uv\":[53,55,55,57],\"texture\":0},\"south\":{\"uv\":[36,6,41,8],\"texture\":0},\"west\":{\"uv\":[55,54,57,56],\"texture\":0},\"up\":{\"uv\":[41,10,36,8],\"texture\":0},\"down\":{\"uv\":[41,15,36,17],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"84e0afe0-c3b1-e5ee-63bf-491983d93639\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.06264,34.13,0.46625],\"to\":[-0.68264,34.25,1.42625],\"autouv\":0,\"color\":9,\"rotation\":[-45,0,0],\"origin\":[0.87736,31.01,0.40625],\"faces\":{\"north\":{\"uv\":[60,26,63,27],\"texture\":0},\"east\":{\"uv\":[23,65,24,66],\"texture\":0},\"south\":{\"uv\":[60,27,63,28],\"texture\":0},\"west\":{\"uv\":[65,23,66,24],\"texture\":0},\"up\":{\"uv\":[31,61,28,60],\"texture\":0},\"down\":{\"uv\":[63,30,60,31],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a32e0821-ed45-ccd3-91c6-ac768ec3310c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.54264,33.73,-2.51375],\"to\":[0.91736,34.81,-1.99375],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[-0.81264,33.85,-2.33375],\"faces\":{\"north\":{\"uv\":[60,1,63,2],\"texture\":0},\"east\":{\"uv\":[21,65,22,66],\"texture\":0},\"south\":{\"uv\":[60,8,63,9],\"texture\":0},\"west\":{\"uv\":[65,21,66,22],\"texture\":0},\"up\":{\"uv\":[63,22,60,21],\"texture\":0},\"down\":{\"uv\":[63,22,60,23],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4458cb4c-42b1-5a2f-c0fa-8730c2a5e29b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.01736,32.53,-2.51375],\"to\":[2.09736,34.71,-1.99375],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[0.54736,33.85,-2.33375],\"faces\":{\"north\":{\"uv\":[60,23,61,26],\"texture\":0},\"east\":{\"uv\":[25,60,26,63],\"texture\":0},\"south\":{\"uv\":[26,60,27,63],\"texture\":0},\"west\":{\"uv\":[27,60,28,63],\"texture\":0},\"up\":{\"uv\":[23,66,22,65],\"texture\":0},\"down\":{\"uv\":[66,22,65,23],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"058b2d35-b9d7-23da-a53a-98f0b8533465\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.15736,32.39,0.00625],\"to\":[2.59736,34.59,1.02625],\"autouv\":0,\"color\":9,\"rotation\":[0,45,0],\"origin\":[3.62736,33.49,0.51625],\"faces\":{\"north\":{\"uv\":[2,49,4,52],\"texture\":0},\"east\":{\"uv\":[32,60,33,63],\"texture\":0},\"south\":{\"uv\":[4,49,6,52],\"texture\":0},\"west\":{\"uv\":[33,60,34,63],\"texture\":0},\"up\":{\"uv\":[65,10,63,9],\"texture\":0},\"down\":{\"uv\":[12,63,10,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3d38f265-16fb-b26d-0851-3b04a28b6d64\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.42264,31.97,-1.97375],\"to\":[-3.10264,33.53,2.40625],\"autouv\":0,\"color\":9,\"origin\":[-2.48264,32.93,0.60625],\"faces\":{\"north\":{\"uv\":[49,55,51,57],\"texture\":0},\"east\":{\"uv\":[34,29,39,31],\"texture\":0},\"south\":{\"uv\":[55,52,57,54],\"texture\":0},\"west\":{\"uv\":[34,31,39,33],\"texture\":0},\"up\":{\"uv\":[16,40,14,35],\"texture\":0},\"down\":{\"uv\":[18,35,16,40],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"84d754cf-915e-8061-2884-d5a7dc9f7bd1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.75736,29.31,-1.19375],\"to\":[3.49736,32.91,0.40625],\"autouv\":0,\"color\":1,\"origin\":[2.89736,29.71,-0.39375],\"faces\":{\"north\":{\"uv\":[23,54,24,59],\"texture\":0},\"east\":{\"uv\":[10,34,12,39],\"texture\":0},\"south\":{\"uv\":[54,23,55,28],\"texture\":0},\"west\":{\"uv\":[12,34,14,39],\"texture\":0},\"up\":{\"uv\":[7,44,6,42],\"texture\":0},\"down\":{\"uv\":[13,44,12,46],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ecc83fce-7df5-b985-53a8-a3303f95832f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.88264,29.87,-1.03375],\"to\":[-0.38264,31.91,-1.03375],\"autouv\":0,\"color\":8,\"origin\":[-4.20264,32.31,-0.39375],\"faces\":{\"north\":{\"uv\":[40,0,43,3],\"texture\":0},\"east\":{\"uv\":[0,0,0,3],\"texture\":0},\"south\":{\"uv\":[13,40,16,43],\"texture\":0},\"west\":{\"uv\":[0,0,0,3],\"texture\":0},\"up\":{\"uv\":[3,0,0,0],\"texture\":0},\"down\":{\"uv\":[3,0,0,0],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"350e3a30-4658-77a6-4df2-1d7b331a098b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.38264,28.51,-1.19375],\"to\":[0.25736,32.91,0.40625],\"autouv\":0,\"color\":1,\"origin\":[4.07736,29.71,-0.39375],\"faces\":{\"north\":{\"uv\":[41,47,42,53],\"texture\":0},\"east\":{\"uv\":[15,29,17,35],\"texture\":0},\"south\":{\"uv\":[42,47,43,53],\"texture\":0},\"west\":{\"uv\":[17,29,19,35],\"texture\":0},\"up\":{\"uv\":[8,39,7,37],\"texture\":0},\"down\":{\"uv\":[41,27,40,29],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"21b22b7a-bce2-d3cd-bafe-6bb8ca56111e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.25736,29.87,-1.03375],\"to\":[2.75736,31.91,-1.03375],\"autouv\":0,\"color\":8,\"origin\":[4.07736,32.31,-0.39375],\"faces\":{\"north\":{\"uv\":[39,29,42,32],\"texture\":0},\"east\":{\"uv\":[0,0,0,3],\"texture\":0},\"south\":{\"uv\":[39,39,42,42],\"texture\":0},\"west\":{\"uv\":[0,0,0,3],\"texture\":0},\"up\":{\"uv\":[3,0,0,0],\"texture\":0},\"down\":{\"uv\":[3,0,0,0],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5c446ca3-fa6a-9098-3658-6b2188e41ef4\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.25736,28.51,-1.19375],\"to\":[2.71736,29.87,0.40625],\"autouv\":0,\"color\":1,\"origin\":[4.07736,29.71,-0.39375],\"faces\":{\"north\":{\"uv\":[47,33,50,35],\"texture\":0},\"east\":{\"uv\":[55,41,57,43],\"texture\":0},\"south\":{\"uv\":[47,35,50,37],\"texture\":0},\"west\":{\"uv\":[45,55,47,57],\"texture\":0},\"up\":{\"uv\":[50,39,47,37],\"texture\":0},\"down\":{\"uv\":[50,39,47,41],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"cd5d87f1-1f75-5842-de2a-3eeaf3af1f59\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.84264,28.51,-1.19375],\"to\":[-0.38264,29.87,0.40625],\"autouv\":0,\"color\":1,\"origin\":[-4.20264,29.71,-0.39375],\"faces\":{\"north\":{\"uv\":[31,46,34,48],\"texture\":0},\"east\":{\"uv\":[21,3,23,5],\"texture\":0},\"south\":{\"uv\":[34,46,37,48],\"texture\":0},\"west\":{\"uv\":[55,39,57,41],\"texture\":0},\"up\":{\"uv\":[50,31,47,29],\"texture\":0},\"down\":{\"uv\":[50,31,47,33],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c73b2cca-267a-3b8a-82dc-1eeafcd6927d\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.17736,29.51,-1.19375],\"to\":[3.21736,29.87,-1.03375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,22.5],\"origin\":[1.96736,28.87,-1.15375],\"faces\":{\"north\":{\"uv\":[17,5,18,6],\"texture\":0},\"east\":{\"uv\":[27,6,28,7],\"texture\":0},\"south\":{\"uv\":[14,34,15,35],\"texture\":0},\"west\":{\"uv\":[13,39,14,40],\"texture\":0},\"up\":{\"uv\":[3,45,2,44],\"texture\":0},\"down\":{\"uv\":[45,28,44,29],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ec21fc72-01ba-0601-895f-94d76063b07b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.34264,29.51,-1.19375],\"to\":[-2.30264,29.87,-1.03375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-22.5],\"origin\":[-2.09264,28.87,-1.15375],\"faces\":{\"north\":{\"uv\":[55,64,56,65],\"texture\":0},\"east\":{\"uv\":[64,55,65,56],\"texture\":0},\"south\":{\"uv\":[56,64,57,65],\"texture\":0},\"west\":{\"uv\":[64,56,65,57],\"texture\":0},\"up\":{\"uv\":[58,65,57,64],\"texture\":0},\"down\":{\"uv\":[65,57,64,58],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f322ebee-1063-7c39-2350-8d74edf49a71\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.35201,29.36694,-1.19375],\"to\":[0.30799,30.02694,-1.03375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-45],\"origin\":[0.16799,29.84694,-1.15375],\"faces\":{\"north\":{\"uv\":[56,51,57,52],\"texture\":0},\"east\":{\"uv\":[46,64,47,65],\"texture\":0},\"south\":{\"uv\":[47,64,48,65],\"texture\":0},\"west\":{\"uv\":[64,47,65,48],\"texture\":0},\"up\":{\"uv\":[49,65,48,64],\"texture\":0},\"down\":{\"uv\":[65,48,64,49],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2c80f29a-68d9-2841-a9b3-7de033e342f5\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.51736,31.35,-1.19375],\"to\":[1.55736,31.71,-1.03375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,22.5],\"origin\":[0.30736,30.71,-1.15375],\"faces\":{\"north\":{\"uv\":[49,64,50,65],\"texture\":0},\"east\":{\"uv\":[50,64,51,65],\"texture\":0},\"south\":{\"uv\":[51,64,52,65],\"texture\":0},\"west\":{\"uv\":[64,51,65,52],\"texture\":0},\"up\":{\"uv\":[53,65,52,64],\"texture\":0},\"down\":{\"uv\":[54,64,53,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"85e31557-bf10-290c-931b-a1855a815d1c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.44264,31.35,-1.19375],\"to\":[-0.64264,31.71,-1.03375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-22.5],\"origin\":[-0.43264,30.71,-1.15375],\"faces\":{\"north\":{\"uv\":[6,20,8,21],\"texture\":0},\"east\":{\"uv\":[54,64,55,65],\"texture\":0},\"south\":{\"uv\":[39,14,41,15],\"texture\":0},\"west\":{\"uv\":[64,54,65,55],\"texture\":0},\"up\":{\"uv\":[5,42,3,41],\"texture\":0},\"down\":{\"uv\":[52,40,50,41],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"bacad5c3-b6bc-3fc9-529e-b6f6ff181e82\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.82264,31.77,-1.19375],\"to\":[2.95736,33.23,-1.03375],\"autouv\":0,\"color\":1,\"origin\":[0.30736,31.11,-1.15375],\"faces\":{\"north\":{\"uv\":[27,11,34,13],\"texture\":0},\"east\":{\"uv\":[23,7,24,9],\"texture\":0},\"south\":{\"uv\":[27,13,34,15],\"texture\":0},\"west\":{\"uv\":[18,35,19,37],\"texture\":0},\"up\":{\"uv\":[31,47,24,46],\"texture\":0},\"down\":{\"uv\":[48,46,41,47],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"374c990f-cca9-7642-54f6-8fcc2ae47386\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.37736,31.49,-0.39375],\"to\":[3.41736,31.85,-0.31375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-22.5],\"origin\":[2.16736,30.85,-0.35375],\"faces\":{\"north\":{\"uv\":[24,45,25,46],\"texture\":0},\"east\":{\"uv\":[41,45,42,46],\"texture\":0},\"south\":{\"uv\":[8,50,9,51],\"texture\":0},\"west\":{\"uv\":[55,43,56,44],\"texture\":0},\"up\":{\"uv\":[56,50,55,49],\"texture\":0},\"down\":{\"uv\":[57,2,56,3],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"29d56430-7916-7008-9cc8-b6565185a324\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.04264,30.37,-1.19375],\"to\":[-2.86264,30.91,-1.03375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,22.5],\"origin\":[-2.23264,30.91,-1.15375],\"faces\":{\"north\":{\"uv\":[58,64,59,65],\"texture\":0},\"east\":{\"uv\":[64,58,65,59],\"texture\":0},\"south\":{\"uv\":[64,59,65,60],\"texture\":0},\"west\":{\"uv\":[60,64,61,65],\"texture\":0},\"up\":{\"uv\":[65,61,64,60],\"texture\":0},\"down\":{\"uv\":[62,64,61,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"17633f4b-13a7-8951-8eec-bf2f1952ec70\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.79736,30.37,-1.19375],\"to\":[2.97736,30.91,-1.03375],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-22.5],\"origin\":[2.16736,30.91,-1.15375],\"faces\":{\"north\":{\"uv\":[64,61,65,62],\"texture\":0},\"east\":{\"uv\":[62,64,63,65],\"texture\":0},\"south\":{\"uv\":[64,62,65,63],\"texture\":0},\"west\":{\"uv\":[63,64,64,65],\"texture\":0},\"up\":{\"uv\":[65,64,64,63],\"texture\":0},\"down\":{\"uv\":[65,64,64,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"42c42a1c-3468-5c32-6bfe-36250a72b9af\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.41736,32.71,-0.21375],\"to\":[3.97736,34.07,2.08625],\"autouv\":0,\"color\":9,\"rotation\":[-22.5,0,0],\"origin\":[8.15736,30.21,2.00625],\"faces\":{\"north\":{\"uv\":[5,63,6,65],\"texture\":0},\"east\":{\"uv\":[19,48,22,50],\"texture\":0},\"south\":{\"uv\":[63,7,64,9],\"texture\":0},\"west\":{\"uv\":[48,19,51,21],\"texture\":0},\"up\":{\"uv\":[18,63,17,60],\"texture\":0},\"down\":{\"uv\":[19,60,18,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a20d7470-bd5d-94a6-8bed-c93cf0d2d161\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.36264,32.39,2.40625],\"to\":[2.57736,34.59,3.42625],\"autouv\":0,\"color\":9,\"origin\":[2.89736,29.75,-0.39375],\"faces\":{\"north\":{\"uv\":[18,9,24,12],\"texture\":0},\"east\":{\"uv\":[31,60,32,63],\"texture\":0},\"south\":{\"uv\":[19,15,25,18],\"texture\":0},\"west\":{\"uv\":[60,31,61,34],\"texture\":0},\"up\":{\"uv\":[54,28,48,27],\"texture\":0},\"down\":{\"uv\":[54,46,48,47],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"27defe3c-bdd3-18bb-85d3-6405e21b1e03\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.96264,28.99,2.60625],\"to\":[-1.50264,31.19,3.62625],\"autouv\":0,\"color\":9,\"origin\":[1.89736,27.15,-0.19375],\"faces\":{\"north\":{\"uv\":[51,47,53,50],\"texture\":0},\"east\":{\"uv\":[52,60,53,63],\"texture\":0},\"south\":{\"uv\":[51,50,53,53],\"texture\":0},\"west\":{\"uv\":[55,60,56,63],\"texture\":0},\"up\":{\"uv\":[65,20,63,19],\"texture\":0},\"down\":{\"uv\":[65,21,63,22],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c0d88dd0-a196-b17c-e123-954163d428e7\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.70264,31.79,2.40625],\"to\":[-2.28264,32.61,3.42625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[-2.10264,32.39,2.91625],\"faces\":{\"north\":{\"uv\":[30,65,31,66],\"texture\":0},\"east\":{\"uv\":[65,30,66,31],\"texture\":0},\"south\":{\"uv\":[31,65,32,66],\"texture\":0},\"west\":{\"uv\":[65,31,66,32],\"texture\":0},\"up\":{\"uv\":[33,66,32,65],\"texture\":0},\"down\":{\"uv\":[66,32,65,33],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2e41d6ce-8800-9b74-6f26-94e295cac2bf\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.29736,31.19,2.40625],\"to\":[2.83736,32.39,3.82625],\"autouv\":0,\"color\":5,\"origin\":[-2.42264,27.15,-0.39375],\"faces\":{\"north\":{\"uv\":[27,48,30,50],\"texture\":0},\"east\":{\"uv\":[56,9,58,11],\"texture\":0},\"south\":{\"uv\":[30,48,33,50],\"texture\":0},\"west\":{\"uv\":[56,11,58,13],\"texture\":0},\"up\":{\"uv\":[36,50,33,48],\"texture\":0},\"down\":{\"uv\":[39,48,36,50],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"512ee88e-a107-34c4-b782-b3cf94747612\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.37736,29.59,2.60625],\"to\":[2.83736,31.19,3.62625],\"autouv\":0,\"color\":9,\"origin\":[-2.02264,27.15,-0.19375],\"faces\":{\"north\":{\"uv\":[58,21,60,23],\"texture\":0},\"east\":{\"uv\":[23,63,24,65],\"texture\":0},\"south\":{\"uv\":[25,58,27,60],\"texture\":0},\"west\":{\"uv\":[25,63,26,65],\"texture\":0},\"up\":{\"uv\":[65,23,63,22],\"texture\":0},\"down\":{\"uv\":[28,63,26,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"58c102f9-86e6-7ce8-e0ca-7b8d31dbf167\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.15736,31.79,2.40625],\"to\":[2.57736,32.61,3.42625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,45],\"origin\":[1.97736,32.39,2.91625],\"faces\":{\"north\":{\"uv\":[65,24,66,25],\"texture\":0},\"east\":{\"uv\":[25,65,26,66],\"texture\":0},\"south\":{\"uv\":[65,26,66,27],\"texture\":0},\"west\":{\"uv\":[65,27,66,28],\"texture\":0},\"up\":{\"uv\":[29,66,28,65],\"texture\":0},\"down\":{\"uv\":[30,65,29,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"876f030b-55de-7dd6-e835-00488a4996f9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.63736,30.63,2.60625],\"to\":[1.61736,31.59,3.62625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[1.36736,30.86,3.11625],\"faces\":{\"north\":{\"uv\":[65,56,66,57],\"texture\":0},\"east\":{\"uv\":[57,65,58,66],\"texture\":0},\"south\":{\"uv\":[65,57,66,58],\"texture\":0},\"west\":{\"uv\":[58,65,59,66],\"texture\":0},\"up\":{\"uv\":[66,59,65,58],\"texture\":0},\"down\":{\"uv\":[60,65,59,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"eab62980-956f-9ac3-e675-d862905ecc98\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.74264,30.63,2.60625],\"to\":[-0.76264,31.59,3.62625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,45],\"origin\":[-1.49264,30.86,3.11625],\"faces\":{\"north\":{\"uv\":[65,48,66,49],\"texture\":0},\"east\":{\"uv\":[49,65,50,66],\"texture\":0},\"south\":{\"uv\":[65,49,66,50],\"texture\":0},\"west\":{\"uv\":[50,65,51,66],\"texture\":0},\"up\":{\"uv\":[66,51,65,50],\"texture\":0},\"down\":{\"uv\":[52,65,51,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ae538734-efc4-2f34-17c4-995c75624993\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.58264,25.59,2.76625],\"to\":[-1.88264,28.99,3.46625],\"autouv\":0,\"color\":5,\"rotation\":[0,45,0],\"origin\":[-2.23264,28.89,3.11625],\"faces\":{\"north\":{\"uv\":[6,58,7,62],\"texture\":0},\"east\":{\"uv\":[7,58,8,62],\"texture\":0},\"south\":{\"uv\":[58,9,59,13],\"texture\":0},\"west\":{\"uv\":[58,13,59,17],\"texture\":0},\"up\":{\"uv\":[66,52,65,51],\"texture\":0},\"down\":{\"uv\":[53,65,52,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"152d16d5-b078-564f-133e-d531ffcc2818\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.42264,25.39,2.92625],\"to\":[-2.04264,25.59,3.30625],\"autouv\":0,\"color\":9,\"rotation\":[0,45,0],\"origin\":[-2.23264,28.89,3.11625],\"faces\":{\"north\":{\"uv\":[65,52,66,53],\"texture\":0},\"east\":{\"uv\":[53,65,54,66],\"texture\":0},\"south\":{\"uv\":[65,53,66,54],\"texture\":0},\"west\":{\"uv\":[54,65,55,66],\"texture\":0},\"up\":{\"uv\":[66,55,65,54],\"texture\":0},\"down\":{\"uv\":[56,65,55,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"25b70ee3-36dc-db96-bcdb-b06c141eff67\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.50264,21.99,2.84625],\"to\":[-1.96264,25.39,3.38625],\"autouv\":0,\"color\":5,\"origin\":[-2.23264,25.29,3.11625],\"faces\":{\"north\":{\"uv\":[58,17,59,21],\"texture\":0},\"east\":{\"uv\":[19,58,20,62],\"texture\":0},\"south\":{\"uv\":[20,58,21,62],\"texture\":0},\"west\":{\"uv\":[24,58,25,62],\"texture\":0},\"up\":{\"uv\":[66,56,65,55],\"texture\":0},\"down\":{\"uv\":[57,65,56,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"15d2938a-7b52-b9b8-5e46-b1591e2f5234\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.75736,26.99,2.76625],\"to\":[2.45736,29.59,3.46625],\"autouv\":0,\"color\":5,\"rotation\":[0,-45,0],\"origin\":[2.10736,29.49,3.11625],\"faces\":{\"north\":{\"uv\":[56,60,57,63],\"texture\":0},\"east\":{\"uv\":[60,59,61,62],\"texture\":0},\"south\":{\"uv\":[0,61,1,64],\"texture\":0},\"west\":{\"uv\":[1,61,2,64],\"texture\":0},\"up\":{\"uv\":[66,60,65,59],\"texture\":0},\"down\":{\"uv\":[61,65,60,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9eaf4c53-3dbf-fcda-fd62-0a7b2682ac03\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.91736,26.79,2.92625],\"to\":[2.29736,26.99,3.30625],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[2.10736,30.29,3.11625],\"faces\":{\"north\":{\"uv\":[65,60,66,61],\"texture\":0},\"east\":{\"uv\":[61,65,62,66],\"texture\":0},\"south\":{\"uv\":[65,61,66,62],\"texture\":0},\"west\":{\"uv\":[62,65,63,66],\"texture\":0},\"up\":{\"uv\":[66,63,65,62],\"texture\":0},\"down\":{\"uv\":[64,65,63,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6a04a94f-b682-b4ec-6f46-89134ba4bb1a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.83736,24.19,2.84625],\"to\":[2.37736,26.79,3.38625],\"autouv\":0,\"color\":5,\"origin\":[2.10736,26.69,3.11625],\"faces\":{\"north\":{\"uv\":[61,2,62,5],\"texture\":0},\"east\":{\"uv\":[8,61,9,64],\"texture\":0},\"south\":{\"uv\":[9,61,10,64],\"texture\":0},\"west\":{\"uv\":[61,9,62,12],\"texture\":0},\"up\":{\"uv\":[66,64,65,63],\"texture\":0},\"down\":{\"uv\":[65,65,64,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0511aed9-00e4-dadf-a777-d5572b593baf\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.75,24.975,-1.25],\"to\":[1.75,26.75,2.25],\"autouv\":0,\"color\":1,\"origin\":[0,24.75,-1.5],\"faces\":{\"north\":{\"uv\":[44,8,48,10],\"texture\":0},\"east\":{\"uv\":[44,10,48,12],\"texture\":0},\"south\":{\"uv\":[44,12,48,14],\"texture\":0},\"west\":{\"uv\":[44,14,48,16],\"texture\":0},\"up\":{\"uv\":[4,27,0,23],\"texture\":0},\"down\":{\"uv\":[27,3,23,7],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"eac3ad48-ae72-b3bc-8b95-018333dc3051\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.525,23.35,-2],\"to\":[0.275,26,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-45],\"origin\":[1.475,23.625,0],\"faces\":{\"north\":{\"uv\":[62,9,63,12],\"texture\":0},\"east\":{\"uv\":[25,18,30,21],\"texture\":0},\"south\":{\"uv\":[62,14,63,17],\"texture\":0},\"west\":{\"uv\":[25,21,30,24],\"texture\":0},\"up\":{\"uv\":[16,60,15,55],\"texture\":0},\"down\":{\"uv\":[17,55,16,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b93cff5c-2a56-1aab-a8c7-ed4d7397794a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.625,23.2,-2.25],\"to\":[3.175,25.725,2.75],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-45],\"origin\":[3.425,23.575,0],\"faces\":{\"north\":{\"uv\":[43,53,45,56],\"texture\":0},\"east\":{\"uv\":[5,27,10,30],\"texture\":0},\"south\":{\"uv\":[53,47,55,50],\"texture\":0},\"west\":{\"uv\":[10,27,15,30],\"texture\":0},\"up\":{\"uv\":[22,44,20,39],\"texture\":0},\"down\":{\"uv\":[39,39,37,44],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e9d83ef5-2045-ab67-4a98-bed4ba14d518\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.25,24.1,-2.8],\"to\":[2.25,26.3,-1.475],\"autouv\":0,\"color\":5,\"rotation\":[45,0,0],\"origin\":[0,24.225,-2.1],\"faces\":{\"north\":{\"uv\":[36,21,41,23],\"texture\":0},\"east\":{\"uv\":[64,16,65,18],\"texture\":0},\"south\":{\"uv\":[36,23,41,25],\"texture\":0},\"west\":{\"uv\":[20,64,21,66],\"texture\":0},\"up\":{\"uv\":[60,36,55,35],\"texture\":0},\"down\":{\"uv\":[41,55,36,56],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2a40ace8-91f0-0a80-b85c-f1bfea213a68\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.925,24,-2],\"to\":[2.75,26.25,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-22.5],\"origin\":[3,24.375,0],\"faces\":{\"north\":{\"uv\":[59,18,61,20],\"texture\":0},\"east\":{\"uv\":[38,4,43,6],\"texture\":0},\"south\":{\"uv\":[21,59,23,61],\"texture\":0},\"west\":{\"uv\":[0,39,5,41],\"texture\":0},\"up\":{\"uv\":[7,42,5,37],\"texture\":0},\"down\":{\"uv\":[25,37,23,42],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"aee73142-be32-3036-4fe2-ddeccf72a5b1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.75,24,-2],\"to\":[-0.925,26.25,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,22.5],\"origin\":[-3,24.375,0],\"faces\":{\"north\":{\"uv\":[59,28,61,30],\"texture\":0},\"east\":{\"uv\":[39,10,44,12],\"texture\":0},\"south\":{\"uv\":[40,59,42,61],\"texture\":0},\"west\":{\"uv\":[39,12,44,14],\"texture\":0},\"up\":{\"uv\":[13,44,11,39],\"texture\":0},\"down\":{\"uv\":[20,39,18,44],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8bfbd4a0-a8fa-aa56-046f-d97347ba5903\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.275,23.35,-2],\"to\":[0.525,26,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,45],\"origin\":[-1.475,23.625,0],\"faces\":{\"north\":{\"uv\":[62,17,63,20],\"texture\":0},\"east\":{\"uv\":[27,0,32,3],\"texture\":0},\"south\":{\"uv\":[19,62,20,65],\"texture\":0},\"west\":{\"uv\":[27,3,32,6],\"texture\":0},\"up\":{\"uv\":[18,60,17,55],\"texture\":0},\"down\":{\"uv\":[19,55,18,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"55a299fb-f460-6263-e740-75923a2d9d86\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.175,23.2,-2.25],\"to\":[-1.625,25.725,2.75],\"autouv\":0,\"color\":5,\"rotation\":[0,0,45],\"origin\":[-3.425,23.575,0],\"faces\":{\"north\":{\"uv\":[53,33,55,36],\"texture\":0},\"east\":{\"uv\":[25,24,30,27],\"texture\":0},\"south\":{\"uv\":[41,53,43,56],\"texture\":0},\"west\":{\"uv\":[0,27,5,30],\"texture\":0},\"up\":{\"uv\":[9,44,7,39],\"texture\":0},\"down\":{\"uv\":[11,39,9,44],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"add2bd23-961a-d6ee-0953-9458433b0e7e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.025,21.775,-1.75],\"to\":[2.1,23.95,2],\"autouv\":0,\"color\":0,\"rotation\":[0,0,-22.5],\"origin\":[2.35,21.375,0],\"faces\":{\"north\":{\"uv\":[64,23,65,25],\"texture\":0},\"east\":{\"uv\":[46,4,50,6],\"texture\":0},\"south\":{\"uv\":[24,64,25,66],\"texture\":0},\"west\":{\"uv\":[6,46,10,48],\"texture\":0},\"up\":{\"uv\":[44,62,43,58],\"texture\":0},\"down\":{\"uv\":[24,59,23,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d9e7e5c3-da02-8477-fd18-15eb3ec1609c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.1,21.775,-1.75],\"to\":[-0.925,23.95,2],\"autouv\":0,\"color\":0,\"rotation\":[0,0,22.5],\"origin\":[-2.35,21.375,0],\"faces\":{\"north\":{\"uv\":[26,64,27,66],\"texture\":0},\"east\":{\"uv\":[12,46,16,48],\"texture\":0},\"south\":{\"uv\":[27,64,28,66],\"texture\":0},\"west\":{\"uv\":[16,46,20,48],\"texture\":0},\"up\":{\"uv\":[40,63,39,59],\"texture\":0},\"down\":{\"uv\":[60,39,59,43],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ac4a4b83-622d-18a3-f3a4-a2a64a04a81a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.25,22.975,-3.175],\"to\":[2.25,24.425,-1.55],\"autouv\":0,\"color\":5,\"rotation\":[22.5,0,0],\"origin\":[0,23.35,-2.45],\"faces\":{\"north\":{\"uv\":[55,37,60,38],\"texture\":0},\"east\":{\"uv\":[64,18,66,19],\"texture\":0},\"south\":{\"uv\":[55,38,60,39],\"texture\":0},\"west\":{\"uv\":[21,64,23,65],\"texture\":0},\"up\":{\"uv\":[41,27,36,25],\"texture\":0},\"down\":{\"uv\":[5,37,0,39],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6e9d44d7-1f0b-5134-4382-e916bf84baba\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.25,21.425,-1.525],\"to\":[2.25,23.225,0.6],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[0,21.475,-0.8],\"faces\":{\"north\":{\"uv\":[18,37,23,39],\"texture\":0},\"east\":{\"uv\":[59,14,61,16],\"texture\":0},\"south\":{\"uv\":[37,33,42,35],\"texture\":0},\"west\":{\"uv\":[59,16,61,18],\"texture\":0},\"up\":{\"uv\":[42,37,37,35],\"texture\":0},\"down\":{\"uv\":[42,37,37,39],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"033aa5bf-e96a-e214-6edd-35746a2e92b5\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.975,23.5,2],\"to\":[2.975,25,2.75],\"autouv\":0,\"color\":0,\"origin\":[0,24.75,-1],\"faces\":{\"north\":{\"uv\":[32,4,38,6],\"texture\":0},\"east\":{\"uv\":[39,63,40,65],\"texture\":0},\"south\":{\"uv\":[31,33,37,35],\"texture\":0},\"west\":{\"uv\":[42,63,43,65],\"texture\":0},\"up\":{\"uv\":[55,44,49,43],\"texture\":0},\"down\":{\"uv\":[59,29,53,30],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8fe284b0-23bf-a450-600b-44047d4f2b61\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.225,21.7,1.75],\"to\":[2.225,23.7,2.5],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[0,22.95,2.125],\"faces\":{\"north\":{\"uv\":[46,0,50,2],\"texture\":0},\"east\":{\"uv\":[44,63,45,65],\"texture\":0},\"south\":{\"uv\":[46,2,50,4],\"texture\":0},\"west\":{\"uv\":[59,63,60,65],\"texture\":0},\"up\":{\"uv\":[63,8,59,7],\"texture\":0},\"down\":{\"uv\":[63,13,59,14],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7ca2ade1-2f88-677d-8126-849c195f075c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.275,21.15,-1.45],\"to\":[2.275,22.05,2],\"autouv\":0,\"color\":0,\"origin\":[0,24.75,-1.5],\"faces\":{\"north\":{\"uv\":[55,30,60,31],\"texture\":0},\"east\":{\"uv\":[62,2,65,3],\"texture\":0},\"south\":{\"uv\":[31,55,36,56],\"texture\":0},\"west\":{\"uv\":[62,3,65,4],\"texture\":0},\"up\":{\"uv\":[27,15,22,12],\"texture\":0},\"down\":{\"uv\":[30,15,25,18],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1416e039-1c93-1f55-854c-2ca9f6651c99\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.275,18.55,-1.45],\"to\":[-0.225,19.3,2],\"autouv\":0,\"color\":1,\"origin\":[0,22,-1.5],\"faces\":{\"north\":{\"uv\":[63,30,65,31],\"texture\":0},\"east\":{\"uv\":[60,49,63,50],\"texture\":0},\"south\":{\"uv\":[31,63,33,64],\"texture\":0},\"west\":{\"uv\":[60,50,63,51],\"texture\":0},\"up\":{\"uv\":[54,6,52,3],\"texture\":0},\"down\":{\"uv\":[6,52,4,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"15fe5bf8-7f4f-e9db-baa8-6822a6077ce7\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.225,18.55,-1.45],\"to\":[2.275,19.3,2],\"autouv\":0,\"color\":1,\"origin\":[0,22,-1.5],\"faces\":{\"north\":{\"uv\":[63,26,65,27],\"texture\":0},\"east\":{\"uv\":[60,34,63,35],\"texture\":0},\"south\":{\"uv\":[63,27,65,28],\"texture\":0},\"west\":{\"uv\":[60,35,63,36],\"texture\":0},\"up\":{\"uv\":[54,3,52,0],\"texture\":0},\"down\":{\"uv\":[4,52,2,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"255c4dc9-0053-0fed-0d04-3030dc27ee40\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.025,19.3,-1.45],\"to\":[-0.225,21.15,2],\"autouv\":0,\"color\":1,\"origin\":[0,24,-1.5],\"faces\":{\"north\":{\"uv\":[59,9,61,11],\"texture\":0},\"east\":{\"uv\":[52,39,55,41],\"texture\":0},\"south\":{\"uv\":[59,11,61,13],\"texture\":0},\"west\":{\"uv\":[10,53,13,55],\"texture\":0},\"up\":{\"uv\":[8,56,6,53],\"texture\":0},\"down\":{\"uv\":[21,53,19,56],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b4336081-325e-dbfb-9cad-39c227e78c67\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.225,18.55,-1.35],\"to\":[0.225,21.15,1.9],\"autouv\":0,\"color\":1,\"origin\":[0,24,-1.5],\"faces\":{\"north\":{\"uv\":[61,37,62,40],\"texture\":0},\"east\":{\"uv\":[41,20,44,23],\"texture\":0},\"south\":{\"uv\":[40,61,41,64],\"texture\":0},\"west\":{\"uv\":[41,23,44,26],\"texture\":0},\"up\":{\"uv\":[62,43,61,40],\"texture\":0},\"down\":{\"uv\":[42,61,41,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f81abf97-3003-da0c-7fa2-018be5cd9b8d\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.225,19.3,-1.45],\"to\":[2.025,21.15,2],\"autouv\":0,\"color\":1,\"origin\":[0,24,-1.5],\"faces\":{\"north\":{\"uv\":[27,58,29,60],\"texture\":0},\"east\":{\"uv\":[52,8,55,10],\"texture\":0},\"south\":{\"uv\":[29,58,31,60],\"texture\":0},\"west\":{\"uv\":[52,10,55,12],\"texture\":0},\"up\":{\"uv\":[15,55,13,52],\"texture\":0},\"down\":{\"uv\":[17,52,15,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6997413b-4ced-3c9b-d695-66afa5722357\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.65,21.75,-1.45],\"to\":[1.825,22.075,2],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-45],\"origin\":[0.925,21.075,0.025],\"faces\":{\"north\":{\"uv\":[65,64,66,65],\"texture\":0},\"east\":{\"uv\":[61,5,64,6],\"texture\":0},\"south\":{\"uv\":[65,65,66,66],\"texture\":0},\"west\":{\"uv\":[61,6,64,7],\"texture\":0},\"up\":{\"uv\":[62,17,61,14],\"texture\":0},\"down\":{\"uv\":[62,17,61,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6d49082a-c952-04f5-6a33-5cf53b88d970\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.825,21.75,-1.45],\"to\":[-1.65,22.075,2],\"autouv\":0,\"color\":1,\"rotation\":[0,0,45],\"origin\":[-0.925,21.075,0.025],\"faces\":{\"north\":{\"uv\":[2,66,3,67],\"texture\":0},\"east\":{\"uv\":[61,28,64,29],\"texture\":0},\"south\":{\"uv\":[66,2,67,3],\"texture\":0},\"west\":{\"uv\":[61,29,64,30],\"texture\":0},\"up\":{\"uv\":[31,64,30,61],\"texture\":0},\"down\":{\"uv\":[62,31,61,34],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"531af403-a2fa-98db-bb4e-16441ceab62e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.65,18.375,-1.45],\"to\":[1.825,18.7,2],\"autouv\":0,\"color\":1,\"rotation\":[0,0,45],\"origin\":[0.925,19.375,0.025],\"faces\":{\"north\":{\"uv\":[1,66,2,67],\"texture\":0},\"east\":{\"uv\":[61,24,64,25],\"texture\":0},\"south\":{\"uv\":[66,1,67,2],\"texture\":0},\"west\":{\"uv\":[61,25,64,26],\"texture\":0},\"up\":{\"uv\":[29,64,28,61],\"texture\":0},\"down\":{\"uv\":[30,61,29,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4225da25-6240-50c1-7d61-035b7cd5d4ef\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.825,18.375,-1.45],\"to\":[-1.65,18.7,2],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-45],\"origin\":[-0.925,19.375,0.025],\"faces\":{\"north\":{\"uv\":[0,66,1,67],\"texture\":0},\"east\":{\"uv\":[61,12,64,13],\"texture\":0},\"south\":{\"uv\":[66,0,67,1],\"texture\":0},\"west\":{\"uv\":[61,23,64,24],\"texture\":0},\"up\":{\"uv\":[22,64,21,61],\"texture\":0},\"down\":{\"uv\":[23,61,22,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"87ccc294-b6b9-e5b8-72dc-6cbcefb36497\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.025,22.575,-1.175],\"to\":[5.425,25.075,1.675],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-45],\"origin\":[3.925,24.075,0.25],\"faces\":{\"north\":{\"uv\":[43,3,46,6],\"texture\":0},\"east\":{\"uv\":[13,43,16,46],\"texture\":0},\"south\":{\"uv\":[25,43,28,46],\"texture\":0},\"west\":{\"uv\":[28,43,31,46],\"texture\":0},\"up\":{\"uv\":[34,46,31,43],\"texture\":0},\"down\":{\"uv\":[37,43,34,46],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"49960836-36d8-fde1-6010-119a680e1792\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.925,21.97145,-1.15],\"to\":[4.925,22.97145,1.65],\"autouv\":0,\"color\":4,\"origin\":[3.925,21.72145,0.25],\"faces\":{\"north\":{\"uv\":[64,41,66,42],\"texture\":0},\"east\":{\"uv\":[62,31,65,32],\"texture\":0},\"south\":{\"uv\":[64,42,66,43],\"texture\":0},\"west\":{\"uv\":[62,32,65,33],\"texture\":0},\"up\":{\"uv\":[56,20,54,17],\"texture\":0},\"down\":{\"uv\":[56,20,54,23],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"70921fe4-f2c5-95e6-5dab-9ba9df357552\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.675,22.34645,-1.15],\"to\":[4.775,24.24645,1.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[3.925,21.72145,0.25],\"faces\":{\"north\":{\"uv\":[57,59,59,61],\"texture\":0},\"east\":{\"uv\":[54,3,57,5],\"texture\":0},\"south\":{\"uv\":[59,57,61,59],\"texture\":0},\"west\":{\"uv\":[54,5,57,7],\"texture\":0},\"up\":{\"uv\":[10,57,8,54],\"texture\":0},\"down\":{\"uv\":[56,14,54,17],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"be833fd0-a635-7db7-63ec-8b3555252f2c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.525,18.47145,-0.15],\"to\":[4.575,22.47145,0.9],\"autouv\":0,\"color\":4,\"rotation\":[0,45,0],\"origin\":[3.925,20.72145,0.25],\"faces\":{\"north\":{\"uv\":[50,59,51,63],\"texture\":0},\"east\":{\"uv\":[59,59,60,63],\"texture\":0},\"south\":{\"uv\":[2,60,3,64],\"texture\":0},\"west\":{\"uv\":[3,60,4,64],\"texture\":0},\"up\":{\"uv\":[68,27,67,26],\"texture\":0},\"down\":{\"uv\":[28,67,27,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b098efed-8cd2-8935-e65b-ec57de1527a6\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.075,21.09645,-0.2],\"to\":[4.525,22.84645,0.725],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[3.925,21.72145,0.25],\"faces\":{\"north\":{\"uv\":[43,64,44,66],\"texture\":0},\"east\":{\"uv\":[64,43,65,45],\"texture\":0},\"south\":{\"uv\":[45,64,46,66],\"texture\":0},\"west\":{\"uv\":[64,45,65,47],\"texture\":0},\"up\":{\"uv\":[68,31,67,30],\"texture\":0},\"down\":{\"uv\":[32,67,31,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"cf7539d4-1cb6-0d62-d9d7-3a5537399b0e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.76264,35.55,-1.97375],\"to\":[1.07736,36.27,2.40625],\"autouv\":0,\"color\":9,\"origin\":[1.69736,35.71,0.21625],\"faces\":{\"north\":{\"uv\":[62,61,64,62],\"texture\":0},\"east\":{\"uv\":[31,54,36,55],\"texture\":0},\"south\":{\"uv\":[62,62,64,63],\"texture\":0},\"west\":{\"uv\":[36,54,41,55],\"texture\":0},\"up\":{\"uv\":[31,40,29,35],\"texture\":0},\"down\":{\"uv\":[33,35,31,40],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"da9a971e-d1c8-574f-1c0c-e63d98c1e9f6\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.71736,34.83,-1.97375],\"to\":[2.33736,35.55,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[2.67736,34.99,0.21625],\"faces\":{\"north\":{\"uv\":[63,0,65,1],\"texture\":0},\"east\":{\"uv\":[54,46,59,47],\"texture\":0},\"south\":{\"uv\":[63,1,65,2],\"texture\":0},\"west\":{\"uv\":[55,8,60,9],\"texture\":0},\"up\":{\"uv\":[35,40,33,35],\"texture\":0},\"down\":{\"uv\":[37,35,35,40],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3f8a8ca3-5437-215f-de7e-08e69187015c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.41736,32.17,0.60625],\"to\":[4.21736,33.73,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[2.48736,33.13,1.40625],\"faces\":{\"north\":{\"uv\":[24,56,26,58],\"texture\":0},\"east\":{\"uv\":[26,56,28,58],\"texture\":0},\"south\":{\"uv\":[28,56,30,58],\"texture\":0},\"west\":{\"uv\":[30,56,32,58],\"texture\":0},\"up\":{\"uv\":[34,58,32,56],\"texture\":0},\"down\":{\"uv\":[36,56,34,58],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c4478cd8-3191-c3b4-69cf-3377cc78af98\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.59736,31.07,-1.79375],\"to\":[4.39736,32.39,0.86625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[2.89736,32.11,-0.39375],\"faces\":{\"north\":{\"uv\":[47,55,49,57],\"texture\":0},\"east\":{\"uv\":[6,48,9,50],\"texture\":0},\"south\":{\"uv\":[55,47,57,49],\"texture\":0},\"west\":{\"uv\":[48,6,51,8],\"texture\":0},\"up\":{\"uv\":[50,11,48,8],\"texture\":0},\"down\":{\"uv\":[11,48,9,51],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"942cc6a2-71a8-88e6-b9dc-70b575a485e7\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.01736,30.81,-1.79375],\"to\":[4.67736,31.71,0.86625],\"autouv\":0,\"color\":9,\"origin\":[2.89736,32.11,-0.39375],\"faces\":{\"north\":{\"uv\":[62,41,64,42],\"texture\":0},\"east\":{\"uv\":[41,9,44,10],\"texture\":0},\"south\":{\"uv\":[62,42,64,43],\"texture\":0},\"west\":{\"uv\":[59,20,62,21],\"texture\":0},\"up\":{\"uv\":[13,51,11,48],\"texture\":0},\"down\":{\"uv\":[50,11,48,14],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2db4c218-3307-c625-7799-b63ef622617a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.25736,30.89,0.60625],\"to\":[3.45736,32.19,2.40625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[1.94736,31.33,1.40625],\"faces\":{\"north\":{\"uv\":[40,56,42,58],\"texture\":0},\"east\":{\"uv\":[42,56,44,58],\"texture\":0},\"south\":{\"uv\":[56,43,58,45],\"texture\":0},\"west\":{\"uv\":[56,49,58,51],\"texture\":0},\"up\":{\"uv\":[53,58,51,56],\"texture\":0},\"down\":{\"uv\":[57,56,55,58],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"667ec079-4b84-0569-1687-845a993a638f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.26264,30.29,-0.11375],\"to\":[-3.26264,31.49,1.28625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[-3.46264,30.69,0.58625],\"faces\":{\"north\":{\"uv\":[63,13,64,15],\"texture\":0},\"east\":{\"uv\":[45,57,47,59],\"texture\":0},\"south\":{\"uv\":[14,63,15,65],\"texture\":0},\"west\":{\"uv\":[47,57,49,59],\"texture\":0},\"up\":{\"uv\":[16,65,15,63],\"texture\":0},\"down\":{\"uv\":[64,15,63,17],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fabe1f10-892d-dcfe-78dc-3bb27af4eb24\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.42264,28.93,-0.27375],\"to\":[-3.18264,30.61,1.44625],\"autouv\":0,\"color\":5,\"origin\":[-2.46264,29.93,0.88625],\"faces\":{\"north\":{\"uv\":[57,47,59,49],\"texture\":0},\"east\":{\"uv\":[49,57,51,59],\"texture\":0},\"south\":{\"uv\":[57,51,59,53],\"texture\":0},\"west\":{\"uv\":[53,57,55,59],\"texture\":0},\"up\":{\"uv\":[59,55,57,53],\"texture\":0},\"down\":{\"uv\":[59,55,57,57],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"831b1360-4fcf-59d3-82d5-df3af6f87c87\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.02264,28.73,0.12625],\"to\":[-3.22264,29.13,1.04625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[-3.62142,28.43,0.58503],\"faces\":{\"north\":{\"uv\":[38,65,39,66],\"texture\":0},\"east\":{\"uv\":[39,65,40,66],\"texture\":0},\"south\":{\"uv\":[65,39,66,40],\"texture\":0},\"west\":{\"uv\":[65,40,66,41],\"texture\":0},\"up\":{\"uv\":[43,66,42,65],\"texture\":0},\"down\":{\"uv\":[66,43,65,44],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"107ef96f-b460-6a0c-a9e2-31d74b77b79a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.06142,27.13,0.14332],\"to\":[-3.18142,28.73,1.02332],\"autouv\":0,\"color\":5,\"rotation\":[0,-45,22.5],\"origin\":[-3.62142,28.43,0.58503],\"faces\":{\"north\":{\"uv\":[16,63,17,65],\"texture\":0},\"east\":{\"uv\":[17,63,18,65],\"texture\":0},\"south\":{\"uv\":[63,17,64,19],\"texture\":0},\"west\":{\"uv\":[18,63,19,65],\"texture\":0},\"up\":{\"uv\":[66,48,65,47],\"texture\":0},\"down\":{\"uv\":[49,65,48,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ece00d0a-b988-98b1-0ce7-c89cef99cdba\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.925,18.04645,-0.4],\"to\":[4.725,18.54645,0.9],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[4.075,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[67,31,68,32],\"texture\":0},\"east\":{\"uv\":[32,67,33,68],\"texture\":0},\"south\":{\"uv\":[67,32,68,33],\"texture\":0},\"west\":{\"uv\":[33,67,34,68],\"texture\":0},\"up\":{\"uv\":[68,34,67,33],\"texture\":0},\"down\":{\"uv\":[35,67,34,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"71dd0d2e-1f35-9c20-f0e4-e7243ef62c59\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.75,17.25,-1.7],\"to\":[1.75,19.05,2.25],\"autouv\":0,\"color\":0,\"origin\":[0,16.5,-0.5],\"faces\":{\"north\":{\"uv\":[16,44,20,46],\"texture\":0},\"east\":{\"uv\":[44,16,48,18],\"texture\":0},\"south\":{\"uv\":[44,18,48,20],\"texture\":0},\"west\":{\"uv\":[44,20,48,22],\"texture\":0},\"up\":{\"uv\":[16,27,12,23],\"texture\":0},\"down\":{\"uv\":[28,7,24,11],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9eacb6d9-6979-c722-5a5a-7f3f755d162d\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.65,12.65,-1.95],\"to\":[3.65,19.05,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,22.5],\"origin\":[2.15,17,-0.5],\"faces\":{\"north\":{\"uv\":[6,21,9,27],\"texture\":0},\"east\":{\"uv\":[13,0,17,6],\"texture\":0},\"south\":{\"uv\":[9,21,12,27],\"texture\":0},\"west\":{\"uv\":[0,14,4,20],\"texture\":0},\"up\":{\"uv\":[28,35,25,31],\"texture\":0},\"down\":{\"uv\":[31,31,28,35],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ee8190eb-8d0c-759e-fb79-bcfac04aaa59\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,16.25,-2.45],\"to\":[1.75,17.8,3],\"autouv\":0,\"color\":5,\"rotation\":[0,0,22.5],\"origin\":[0,18.4,0.275],\"faces\":{\"north\":{\"uv\":[29,27,35,29],\"texture\":0},\"east\":{\"uv\":[36,17,41,19],\"texture\":0},\"south\":{\"uv\":[30,23,36,25],\"texture\":0},\"west\":{\"uv\":[36,19,41,21],\"texture\":0},\"up\":{\"uv\":[13,5,7,0],\"texture\":0},\"down\":{\"uv\":[13,5,7,10],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ecea20f4-a55d-fe20-ac80-243b297f225d\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.675,13,-1.45],\"to\":[1.725,16.8,2],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[0,15.9,0.275],\"faces\":{\"north\":{\"uv\":[9,30,12,34],\"texture\":0},\"east\":{\"uv\":[12,30,15,34],\"texture\":0},\"south\":{\"uv\":[30,15,33,19],\"texture\":0},\"west\":{\"uv\":[30,19,33,23],\"texture\":0},\"up\":{\"uv\":[44,29,41,26],\"texture\":0},\"down\":{\"uv\":[6,42,3,45],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"467613ba-eae9-d8c5-c500-764d19b63bdb\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.575,12.5,-1.95],\"to\":[3.65,14.3,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-22.5],\"origin\":[2.15,12.25,-0.5],\"faces\":{\"north\":{\"uv\":[52,37,55,39],\"texture\":0},\"east\":{\"uv\":[44,26,48,28],\"texture\":0},\"south\":{\"uv\":[38,52,41,54],\"texture\":0},\"west\":{\"uv\":[42,44,46,46],\"texture\":0},\"up\":{\"uv\":[36,23,33,19],\"texture\":0},\"down\":{\"uv\":[25,33,22,37],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c6a80580-5139-f0a6-9f30-9ade3c37c5dc\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.96815,13.21336,-1.95],\"to\":[7.21815,13.91336,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,45],\"origin\":[5.86815,10.96336,0.275],\"faces\":{\"north\":{\"uv\":[63,35,65,36],\"texture\":0},\"east\":{\"uv\":[58,36,62,37],\"texture\":0},\"south\":{\"uv\":[47,63,49,64],\"texture\":0},\"west\":{\"uv\":[39,58,43,59],\"texture\":0},\"up\":{\"uv\":[22,48,20,44],\"texture\":0},\"down\":{\"uv\":[39,44,37,48],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4b3e6c3f-287d-2368-a998-c33d81b96ee4\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.65,12.65,-1.95],\"to\":[-0.65,19.05,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-22.5],\"origin\":[-2.15,17,-0.5],\"faces\":{\"north\":{\"uv\":[19,21,22,27],\"texture\":0},\"east\":{\"uv\":[4,14,8,20],\"texture\":0},\"south\":{\"uv\":[22,21,25,27],\"texture\":0},\"west\":{\"uv\":[14,6,18,12],\"texture\":0},\"up\":{\"uv\":[36,19,33,15],\"texture\":0},\"down\":{\"uv\":[22,33,19,37],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"74f23901-ff08-52ac-dc12-6f1ee74b946a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.21815,13.21336,-1.95],\"to\":[-4.96815,13.91336,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-45],\"origin\":[-5.86815,10.96336,0.275],\"faces\":{\"north\":{\"uv\":[33,63,35,64],\"texture\":0},\"east\":{\"uv\":[56,45,60,46],\"texture\":0},\"south\":{\"uv\":[63,34,65,35],\"texture\":0},\"west\":{\"uv\":[57,6,61,7],\"texture\":0},\"up\":{\"uv\":[2,48,0,44],\"texture\":0},\"down\":{\"uv\":[12,44,10,48],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a66a23f5-34f4-0e99-bfd4-9943ea997a8f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.65,12.5,-1.95],\"to\":[-0.575,14.3,2.5],\"autouv\":0,\"color\":5,\"rotation\":[0,0,22.5],\"origin\":[-2.15,12.25,-0.5],\"faces\":{\"north\":{\"uv\":[52,12,55,14],\"texture\":0},\"east\":{\"uv\":[44,22,48,24],\"texture\":0},\"south\":{\"uv\":[21,52,24,54],\"texture\":0},\"west\":{\"uv\":[44,24,48,26],\"texture\":0},\"up\":{\"uv\":[34,33,31,29],\"texture\":0},\"down\":{\"uv\":[35,0,32,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"59a35e30-c471-c0d5-ff54-b604450c2cce\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.925,10,-0.55],\"to\":[2.625,14,1.15],\"autouv\":0,\"color\":4,\"rotation\":[0,45,0],\"origin\":[1.65,11.25,0.425],\"faces\":{\"north\":{\"uv\":[45,32,47,36],\"texture\":0},\"east\":{\"uv\":[45,36,47,40],\"texture\":0},\"south\":{\"uv\":[39,45,41,49],\"texture\":0},\"west\":{\"uv\":[45,40,47,44],\"texture\":0},\"up\":{\"uv\":[2,61,0,59],\"texture\":0},\"down\":{\"uv\":[61,2,59,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"29e89ffb-c9f6-6591-85c4-4dc2d8d97531\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.675,9.075,-1.525],\"to\":[2.625,10,1.4],\"autouv\":0,\"color\":4,\"origin\":[-0.25,13.4,0.675],\"faces\":{\"north\":{\"uv\":[64,5,66,6],\"texture\":0},\"east\":{\"uv\":[61,55,64,56],\"texture\":0},\"south\":{\"uv\":[64,6,66,7],\"texture\":0},\"west\":{\"uv\":[61,56,64,57],\"texture\":0},\"up\":{\"uv\":[49,55,47,52],\"texture\":0},\"down\":{\"uv\":[51,52,49,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"448df43a-1136-018d-c5ee-785cea750b64\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.675,10.2,-1.5],\"to\":[2.625,10.725,0.25],\"autouv\":0,\"color\":4,\"rotation\":[-22.5,0,0],\"origin\":[1.65,10.225,0.35],\"faces\":{\"north\":{\"uv\":[64,12,66,13],\"texture\":0},\"east\":{\"uv\":[64,13,66,14],\"texture\":0},\"south\":{\"uv\":[64,14,66,15],\"texture\":0},\"west\":{\"uv\":[64,15,66,16],\"texture\":0},\"up\":{\"uv\":[61,6,59,4],\"texture\":0},\"down\":{\"uv\":[10,59,8,61],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f7aa46f3-7631-0bc0-28bb-f612ea592aee\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.475,9.425,-1.55],\"to\":[2.9,10.425,1.4],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[1.925,9.925,0.3],\"faces\":{\"north\":{\"uv\":[37,66,38,67],\"texture\":0},\"east\":{\"uv\":[61,57,64,58],\"texture\":0},\"south\":{\"uv\":[66,37,67,38],\"texture\":0},\"west\":{\"uv\":[61,58,64,59],\"texture\":0},\"up\":{\"uv\":[55,64,54,61],\"texture\":0},\"down\":{\"uv\":[58,61,57,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3d9603af-d62c-a46e-fbfb-abf81268a2d9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.4,9.425,-1.55],\"to\":[0.825,10.425,1.4],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[1.375,9.925,0.3],\"faces\":{\"north\":{\"uv\":[38,66,39,67],\"texture\":0},\"east\":{\"uv\":[61,59,64,60],\"texture\":0},\"south\":{\"uv\":[66,38,67,39],\"texture\":0},\"west\":{\"uv\":[61,60,64,61],\"texture\":0},\"up\":{\"uv\":[59,64,58,61],\"texture\":0},\"down\":{\"uv\":[62,61,61,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"32e01dc5-5dae-1c4c-b1be-35eb3a079515\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[1.275,9.075,-2.6],\"to\":[2.55,10,-1.325],\"autouv\":0,\"color\":4,\"rotation\":[0,45,0],\"origin\":[2.275,9.5375,-1.45],\"faces\":{\"north\":{\"uv\":[34,66,35,67],\"texture\":0},\"east\":{\"uv\":[66,34,67,35],\"texture\":0},\"south\":{\"uv\":[35,66,36,67],\"texture\":0},\"west\":{\"uv\":[66,35,67,36],\"texture\":0},\"up\":{\"uv\":[37,67,36,66],\"texture\":0},\"down\":{\"uv\":[67,36,66,37],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4db14d3d-0f93-84c0-5625-5298d979eb41\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.675,9.7,1.125],\"to\":[2.625,10.55,1.45],\"autouv\":0,\"color\":4,\"rotation\":[22.5,0,0],\"origin\":[1.65,9.5375,-0.075],\"faces\":{\"north\":{\"uv\":[64,7,66,8],\"texture\":0},\"east\":{\"uv\":[33,66,34,67],\"texture\":0},\"south\":{\"uv\":[8,64,10,65],\"texture\":0},\"west\":{\"uv\":[66,33,67,34],\"texture\":0},\"up\":{\"uv\":[66,9,64,8],\"texture\":0},\"down\":{\"uv\":[12,64,10,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"50f40905-4d6d-7098-57ec-25bb1db92c9a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.625,10,-0.55],\"to\":[-0.925,14,1.15],\"autouv\":0,\"color\":4,\"rotation\":[0,-45,0],\"origin\":[-1.65,11.25,0.425],\"faces\":{\"north\":{\"uv\":[2,45,4,49],\"texture\":0},\"east\":{\"uv\":[4,45,6,49],\"texture\":0},\"south\":{\"uv\":[22,45,24,49],\"texture\":0},\"west\":{\"uv\":[45,28,47,32],\"texture\":0},\"up\":{\"uv\":[60,45,58,43],\"texture\":0},\"down\":{\"uv\":[60,49,58,51],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8c485616-0002-04d6-6cf3-9d32de2c3d01\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.625,9.075,-1.525],\"to\":[-0.675,10,1.4],\"autouv\":0,\"color\":4,\"origin\":[0.25,13.4,0.675],\"faces\":{\"north\":{\"uv\":[49,63,51,64],\"texture\":0},\"east\":{\"uv\":[61,43,64,44],\"texture\":0},\"south\":{\"uv\":[63,49,65,50],\"texture\":0},\"west\":{\"uv\":[61,44,64,45],\"texture\":0},\"up\":{\"uv\":[19,55,17,52],\"texture\":0},\"down\":{\"uv\":[47,52,45,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7b67db12-b5ce-3ac3-bdda-5016a85904b8\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.625,9.7,1.125],\"to\":[-0.675,10.55,1.45],\"autouv\":0,\"color\":4,\"rotation\":[22.5,0,0],\"origin\":[-1.65,9.5375,-0.075],\"faces\":{\"north\":{\"uv\":[63,50,65,51],\"texture\":0},\"east\":{\"uv\":[27,66,28,67],\"texture\":0},\"south\":{\"uv\":[51,63,53,64],\"texture\":0},\"west\":{\"uv\":[66,27,67,28],\"texture\":0},\"up\":{\"uv\":[65,53,63,52],\"texture\":0},\"down\":{\"uv\":[65,53,63,54],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"91d99b1a-9f51-fadc-5f4a-8ec6ea9fb9ae\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.55,9.075,-2.6],\"to\":[-1.275,10,-1.325],\"autouv\":0,\"color\":4,\"rotation\":[0,-45,0],\"origin\":[-2.275,9.5375,-1.45],\"faces\":{\"north\":{\"uv\":[28,66,29,67],\"texture\":0},\"east\":{\"uv\":[66,28,67,29],\"texture\":0},\"south\":{\"uv\":[29,66,30,67],\"texture\":0},\"west\":{\"uv\":[66,29,67,30],\"texture\":0},\"up\":{\"uv\":[31,67,30,66],\"texture\":0},\"down\":{\"uv\":[67,30,66,31],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2280ee81-b151-2526-efae-1722409b0821\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.9,9.425,-1.55],\"to\":[-2.475,10.425,1.4],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-1.925,9.925,0.3],\"faces\":{\"north\":{\"uv\":[31,66,32,67],\"texture\":0},\"east\":{\"uv\":[61,45,64,46],\"texture\":0},\"south\":{\"uv\":[66,31,67,32],\"texture\":0},\"west\":{\"uv\":[61,46,64,47],\"texture\":0},\"up\":{\"uv\":[46,64,45,61],\"texture\":0},\"down\":{\"uv\":[47,61,46,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ea1adedd-fb2e-60ce-3ccd-145bac9176cb\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.825,9.425,-1.55],\"to\":[-0.4,10.425,1.4],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[-1.375,9.925,0.3],\"faces\":{\"north\":{\"uv\":[32,66,33,67],\"texture\":0},\"east\":{\"uv\":[61,47,64,48],\"texture\":0},\"south\":{\"uv\":[66,32,67,33],\"texture\":0},\"west\":{\"uv\":[61,51,64,52],\"texture\":0},\"up\":{\"uv\":[62,55,61,52],\"texture\":0},\"down\":{\"uv\":[54,61,53,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"706e638e-8e86-fce7-c6e6-e2789cf10cc5\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.625,10.2,-1.5],\"to\":[-0.675,10.725,0.25],\"autouv\":0,\"color\":4,\"rotation\":[-22.5,0,0],\"origin\":[-1.65,10.225,0.35],\"faces\":{\"north\":{\"uv\":[55,63,57,64],\"texture\":0},\"east\":{\"uv\":[62,63,64,64],\"texture\":0},\"south\":{\"uv\":[0,64,2,65],\"texture\":0},\"west\":{\"uv\":[2,64,4,65],\"texture\":0},\"up\":{\"uv\":[53,60,51,58],\"texture\":0},\"down\":{\"uv\":[57,58,55,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"bdc513a2-6aeb-d4c6-093c-9eebed04c782\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.725,17.69645,-0.4],\"to\":[5.075,18.59645,0.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[3.95,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[67,34,68,35],\"texture\":0},\"east\":{\"uv\":[35,67,36,68],\"texture\":0},\"south\":{\"uv\":[67,35,68,36],\"texture\":0},\"west\":{\"uv\":[36,67,37,68],\"texture\":0},\"up\":{\"uv\":[68,37,67,36],\"texture\":0},\"down\":{\"uv\":[38,67,37,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0ea452bb-ba2b-016e-f030-ec3360d55b63\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.81773,17.24898,0.35],\"to\":[4.56773,17.49898,0.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,43,68,44],\"texture\":0},\"east\":{\"uv\":[44,67,45,68],\"texture\":0},\"south\":{\"uv\":[67,44,68,45],\"texture\":0},\"west\":{\"uv\":[45,67,46,68],\"texture\":0},\"up\":{\"uv\":[68,46,67,45],\"texture\":0},\"down\":{\"uv\":[47,67,46,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"62b9158c-cca3-ecbd-e46c-3407f8d0306e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.81773,17.24898,-0.4],\"to\":[4.56773,17.49898,-0.1],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,55,68,56],\"texture\":0},\"east\":{\"uv\":[56,67,57,68],\"texture\":0},\"south\":{\"uv\":[67,56,68,57],\"texture\":0},\"west\":{\"uv\":[57,67,58,68],\"texture\":0},\"up\":{\"uv\":[68,58,67,57],\"texture\":0},\"down\":{\"uv\":[59,67,58,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5b39d357-c3a2-39d1-1c69-88c062c4ba3a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.81773,17.24898,-0.025],\"to\":[4.56773,17.49898,0.275],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,58,68,59],\"texture\":0},\"east\":{\"uv\":[59,67,60,68],\"texture\":0},\"south\":{\"uv\":[67,59,68,60],\"texture\":0},\"west\":{\"uv\":[60,67,61,68],\"texture\":0},\"up\":{\"uv\":[68,61,67,60],\"texture\":0},\"down\":{\"uv\":[62,67,61,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"376ba5a3-f0b4-f973-ed18-3feb313a350f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.81773,17.49898,0.35],\"to\":[4.09273,17.74898,0.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,52,68,53],\"texture\":0},\"east\":{\"uv\":[53,67,54,68],\"texture\":0},\"south\":{\"uv\":[67,53,68,54],\"texture\":0},\"west\":{\"uv\":[54,67,55,68],\"texture\":0},\"up\":{\"uv\":[68,55,67,54],\"texture\":0},\"down\":{\"uv\":[56,67,55,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"08b8a4dd-9817-f43b-7486-512f7efd2396\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.81773,17.49898,-0.4],\"to\":[4.09273,17.74898,-0.1],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,49,68,50],\"texture\":0},\"east\":{\"uv\":[50,67,51,68],\"texture\":0},\"south\":{\"uv\":[67,50,68,51],\"texture\":0},\"west\":{\"uv\":[51,67,52,68],\"texture\":0},\"up\":{\"uv\":[68,52,67,51],\"texture\":0},\"down\":{\"uv\":[53,67,52,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ba2d481e-f120-fe09-5b95-35e6946f3ef5\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.81773,17.49898,-0.025],\"to\":[4.09273,17.74898,0.275],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,46,68,47],\"texture\":0},\"east\":{\"uv\":[47,67,48,68],\"texture\":0},\"south\":{\"uv\":[67,47,68,48],\"texture\":0},\"west\":{\"uv\":[48,67,49,68],\"texture\":0},\"up\":{\"uv\":[68,49,67,48],\"texture\":0},\"down\":{\"uv\":[50,67,49,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f2c157b6-a525-ba83-2b2b-bcfa39f73d78\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.725,17.69645,0.65],\"to\":[5.025,18.34645,1.05],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[3.95,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[67,37,68,38],\"texture\":0},\"east\":{\"uv\":[38,67,39,68],\"texture\":0},\"south\":{\"uv\":[67,38,68,39],\"texture\":0},\"west\":{\"uv\":[39,67,40,68],\"texture\":0},\"up\":{\"uv\":[68,40,67,39],\"texture\":0},\"down\":{\"uv\":[41,67,40,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"56e24b25-c478-848b-1b04-d7ce1e6f032c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.325,17.69645,0.65],\"to\":[4.725,17.99645,1.05],\"autouv\":0,\"color\":4,\"rotation\":[0,0,-22.5],\"origin\":[3.95,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[67,40,68,41],\"texture\":0},\"east\":{\"uv\":[41,67,42,68],\"texture\":0},\"south\":{\"uv\":[67,41,68,42],\"texture\":0},\"west\":{\"uv\":[42,67,43,68],\"texture\":0},\"up\":{\"uv\":[68,43,67,42],\"texture\":0},\"down\":{\"uv\":[44,67,43,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b36424db-8870-a556-61f4-ec7c1f63a964\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.775,18.22145,-0.15],\"to\":[4.575,18.47145,0.65],\"autouv\":0,\"color\":4,\"origin\":[3.925,20.72145,0.25],\"faces\":{\"north\":{\"uv\":[67,27,68,28],\"texture\":0},\"east\":{\"uv\":[28,67,29,68],\"texture\":0},\"south\":{\"uv\":[67,28,68,29],\"texture\":0},\"west\":{\"uv\":[29,67,30,68],\"texture\":0},\"up\":{\"uv\":[68,30,67,29],\"texture\":0},\"down\":{\"uv\":[31,67,30,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0056ac3d-d6be-eb04-edd0-15cdce2e96a9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.425,22.575,-1.175],\"to\":[-2.025,25.075,1.675],\"autouv\":0,\"color\":4,\"rotation\":[0,0,45],\"origin\":[-3.925,24.075,0.25],\"faces\":{\"north\":{\"uv\":[42,32,45,35],\"texture\":0},\"east\":{\"uv\":[42,35,45,38],\"texture\":0},\"south\":{\"uv\":[42,38,45,41],\"texture\":0},\"west\":{\"uv\":[39,42,42,45],\"texture\":0},\"up\":{\"uv\":[45,44,42,41],\"texture\":0},\"down\":{\"uv\":[46,0,43,3],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c2834cb3-01dd-0214-bc6c-5f5265af0476\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.775,22.34645,-1.15],\"to\":[-2.675,24.24645,1.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-3.925,21.72145,0.25],\"faces\":{\"north\":{\"uv\":[45,59,47,61],\"texture\":0},\"east\":{\"uv\":[53,44,56,46],\"texture\":0},\"south\":{\"uv\":[59,46,61,48],\"texture\":0},\"west\":{\"uv\":[53,50,56,52],\"texture\":0},\"up\":{\"uv\":[53,56,51,53],\"texture\":0},\"down\":{\"uv\":[55,52,53,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a3e39edd-509f-a6e6-2351-ff79b7a61f6c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.925,21.97145,-1.15],\"to\":[-2.925,22.97145,1.65],\"autouv\":0,\"color\":4,\"origin\":[-3.925,21.72145,0.25],\"faces\":{\"north\":{\"uv\":[64,25,66,26],\"texture\":0},\"east\":{\"uv\":[62,4,65,5],\"texture\":0},\"south\":{\"uv\":[28,64,30,65],\"texture\":0},\"west\":{\"uv\":[62,20,65,21],\"texture\":0},\"up\":{\"uv\":[2,57,0,54],\"texture\":0},\"down\":{\"uv\":[56,0,54,3],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5dec9d98-097d-f3e9-9827-c2dcd146712f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.575,18.47145,-0.15],\"to\":[-3.525,22.47145,0.9],\"autouv\":0,\"color\":4,\"rotation\":[0,-45,0],\"origin\":[-3.925,20.72145,0.25],\"faces\":{\"north\":{\"uv\":[42,59,43,63],\"texture\":0},\"east\":{\"uv\":[47,59,48,63],\"texture\":0},\"south\":{\"uv\":[48,59,49,63],\"texture\":0},\"west\":{\"uv\":[49,59,50,63],\"texture\":0},\"up\":{\"uv\":[59,67,58,66],\"texture\":0},\"down\":{\"uv\":[67,58,66,59],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"20c07f3a-08c5-782d-37e8-d229fcfa893a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.575,18.22145,-0.15],\"to\":[-3.775,18.47145,0.65],\"autouv\":0,\"color\":4,\"origin\":[-3.925,20.72145,0.25],\"faces\":{\"north\":{\"uv\":[59,66,60,67],\"texture\":0},\"east\":{\"uv\":[66,59,67,60],\"texture\":0},\"south\":{\"uv\":[60,66,61,67],\"texture\":0},\"west\":{\"uv\":[66,60,67,61],\"texture\":0},\"up\":{\"uv\":[62,67,61,66],\"texture\":0},\"down\":{\"uv\":[67,61,66,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"11bb47a0-0738-66fb-7887-f3499e766f41\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.525,21.09645,-0.2],\"to\":[-4.075,22.84645,0.725],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-3.925,21.72145,0.25],\"faces\":{\"north\":{\"uv\":[34,64,35,66],\"texture\":0},\"east\":{\"uv\":[64,39,65,41],\"texture\":0},\"south\":{\"uv\":[40,64,41,66],\"texture\":0},\"west\":{\"uv\":[41,64,42,66],\"texture\":0},\"up\":{\"uv\":[63,67,62,66],\"texture\":0},\"down\":{\"uv\":[67,62,66,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"faa9c325-4ca4-5292-9be5-eb74a175eadd\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.725,18.04645,-0.4],\"to\":[-3.925,18.54645,0.9],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-4.075,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[63,66,64,67],\"texture\":0},\"east\":{\"uv\":[66,63,67,64],\"texture\":0},\"south\":{\"uv\":[64,66,65,67],\"texture\":0},\"west\":{\"uv\":[66,64,67,65],\"texture\":0},\"up\":{\"uv\":[66,67,65,66],\"texture\":0},\"down\":{\"uv\":[67,65,66,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7779802d-844c-1e58-67ba-9d26d4d7aacb\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.075,17.69645,-0.4],\"to\":[-4.725,18.59645,0.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-3.95,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[66,66,67,67],\"texture\":0},\"east\":{\"uv\":[0,67,1,68],\"texture\":0},\"south\":{\"uv\":[67,0,68,1],\"texture\":0},\"west\":{\"uv\":[1,67,2,68],\"texture\":0},\"up\":{\"uv\":[68,2,67,1],\"texture\":0},\"down\":{\"uv\":[3,67,2,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e0c86f4e-ff80-ead8-9b92-5246c36f8b34\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.025,17.69645,0.65],\"to\":[-4.725,18.34645,1.05],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-3.95,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[67,2,68,3],\"texture\":0},\"east\":{\"uv\":[3,67,4,68],\"texture\":0},\"south\":{\"uv\":[67,3,68,4],\"texture\":0},\"west\":{\"uv\":[4,67,5,68],\"texture\":0},\"up\":{\"uv\":[68,5,67,4],\"texture\":0},\"down\":{\"uv\":[6,67,5,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9e677dad-9f30-868d-f8a6-b4f1ca772402\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.725,17.69645,0.65],\"to\":[-4.325,17.99645,1.05],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-3.95,18.54645,0.25],\"faces\":{\"north\":{\"uv\":[67,5,68,6],\"texture\":0},\"east\":{\"uv\":[6,67,7,68],\"texture\":0},\"south\":{\"uv\":[67,6,68,7],\"texture\":0},\"west\":{\"uv\":[7,67,8,68],\"texture\":0},\"up\":{\"uv\":[68,8,67,7],\"texture\":0},\"down\":{\"uv\":[9,67,8,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ee55ecef-75f5-6169-4093-ca093fe26167\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.56773,17.24898,0.35],\"to\":[-3.81773,17.49898,0.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,8,68,9],\"texture\":0},\"east\":{\"uv\":[9,67,10,68],\"texture\":0},\"south\":{\"uv\":[67,9,68,10],\"texture\":0},\"west\":{\"uv\":[10,67,11,68],\"texture\":0},\"up\":{\"uv\":[68,11,67,10],\"texture\":0},\"down\":{\"uv\":[12,67,11,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b462094a-f846-3cb1-75a7-4432dde1c9a3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.09273,17.49898,-0.025],\"to\":[-3.81773,17.74898,0.275],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,11,68,12],\"texture\":0},\"east\":{\"uv\":[12,67,13,68],\"texture\":0},\"south\":{\"uv\":[67,12,68,13],\"texture\":0},\"west\":{\"uv\":[13,67,14,68],\"texture\":0},\"up\":{\"uv\":[68,14,67,13],\"texture\":0},\"down\":{\"uv\":[15,67,14,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5a8b5a47-d25a-9f4f-c151-22ceffc086f7\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.09273,17.49898,-0.4],\"to\":[-3.81773,17.74898,-0.1],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,14,68,15],\"texture\":0},\"east\":{\"uv\":[15,67,16,68],\"texture\":0},\"south\":{\"uv\":[67,15,68,16],\"texture\":0},\"west\":{\"uv\":[16,67,17,68],\"texture\":0},\"up\":{\"uv\":[68,17,67,16],\"texture\":0},\"down\":{\"uv\":[18,67,17,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2873cfd9-8e30-f048-b028-8a08b6605032\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.09273,17.49898,0.35],\"to\":[-3.81773,17.74898,0.65],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,17,68,18],\"texture\":0},\"east\":{\"uv\":[18,67,19,68],\"texture\":0},\"south\":{\"uv\":[67,18,68,19],\"texture\":0},\"west\":{\"uv\":[19,67,20,68],\"texture\":0},\"up\":{\"uv\":[68,20,67,19],\"texture\":0},\"down\":{\"uv\":[21,67,20,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"150732da-710a-804b-7ad6-0937f1647292\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.56773,17.24898,-0.4],\"to\":[-3.81773,17.49898,-0.1],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,20,68,21],\"texture\":0},\"east\":{\"uv\":[21,67,22,68],\"texture\":0},\"south\":{\"uv\":[67,21,68,22],\"texture\":0},\"west\":{\"uv\":[22,67,23,68],\"texture\":0},\"up\":{\"uv\":[68,23,67,22],\"texture\":0},\"down\":{\"uv\":[24,67,23,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fb92591b-ca3a-8a99-8628-b52142ad0610\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-4.56773,17.24898,-0.025],\"to\":[-3.81773,17.49898,0.275],\"autouv\":0,\"color\":4,\"rotation\":[0,0,22.5],\"origin\":[-4.24273,17.42398,0.125],\"faces\":{\"north\":{\"uv\":[67,23,68,24],\"texture\":0},\"east\":{\"uv\":[24,67,25,68],\"texture\":0},\"south\":{\"uv\":[67,24,68,25],\"texture\":0},\"west\":{\"uv\":[25,67,26,68],\"texture\":0},\"up\":{\"uv\":[68,26,67,25],\"texture\":0},\"down\":{\"uv\":[27,67,26,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dd1b6bb9-1371-7468-c811-690929986466\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.80652,28.23002,-1.59375],\"to\":[3.42652,28.87002,-0.99375],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[3.26652,28.45002,-1.29375],\"faces\":{\"north\":{\"uv\":[3,65,4,66],\"texture\":0},\"east\":{\"uv\":[65,3,66,4],\"texture\":0},\"south\":{\"uv\":[4,65,5,66],\"texture\":0},\"west\":{\"uv\":[65,4,66,5],\"texture\":0},\"up\":{\"uv\":[6,66,5,65],\"texture\":0},\"down\":{\"uv\":[7,65,6,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"17f99990-ab5b-6c49-5c4e-3498f582fd93\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.6518,29.01002,-1.59375],\"to\":[-3.0718,29.59002,-0.99375],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[-3.2418,29.18002,-1.29375],\"faces\":{\"north\":{\"uv\":[11,65,12,66],\"texture\":0},\"east\":{\"uv\":[65,11,66,12],\"texture\":0},\"south\":{\"uv\":[12,65,13,66],\"texture\":0},\"west\":{\"uv\":[13,65,14,66],\"texture\":0},\"up\":{\"uv\":[15,66,14,65],\"texture\":0},\"down\":{\"uv\":[16,65,15,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9f7d1c1a-c9fd-ebd0-5737-915b7bb6942c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.94264,26.73,0.20625],\"to\":[-3.30264,27.13,0.96625],\"autouv\":0,\"color\":9,\"rotation\":[0,0,22.5],\"origin\":[-3.62142,28.43,0.58503],\"faces\":{\"north\":{\"uv\":[44,65,45,66],\"texture\":0},\"east\":{\"uv\":[65,44,66,45],\"texture\":0},\"south\":{\"uv\":[65,45,66,46],\"texture\":0},\"west\":{\"uv\":[46,65,47,66],\"texture\":0},\"up\":{\"uv\":[66,47,65,46],\"texture\":0},\"down\":{\"uv\":[48,65,47,66],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"55a38668-5660-2226-d6e6-6e3e82332eaf\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.075,19.72145,-0.65],\"to\":[-3.175,20.22145,1.15],\"autouv\":0,\"color\":2,\"rotation\":[0,0,22.5],\"origin\":[-4.125,19.97145,0.25],\"faces\":{\"north\":{\"uv\":[64,28,66,29],\"texture\":0},\"east\":{\"uv\":[64,29,66,30],\"texture\":0},\"south\":{\"uv\":[30,64,32,65],\"texture\":0},\"west\":{\"uv\":[32,64,34,65],\"texture\":0},\"up\":{\"uv\":[61,53,59,51],\"texture\":0},\"down\":{\"uv\":[55,59,53,61],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"12f8aa93-1559-25fb-8b0c-6458b326b202\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.075,20.97145,-0.65],\"to\":[-3.175,21.47145,1.15],\"autouv\":0,\"color\":2,\"rotation\":[0,0,22.5],\"origin\":[-4.125,21.22145,0.25],\"faces\":{\"north\":{\"uv\":[64,33,66,34],\"texture\":0},\"east\":{\"uv\":[64,36,66,37],\"texture\":0},\"south\":{\"uv\":[64,37,66,38],\"texture\":0},\"west\":{\"uv\":[64,38,66,39],\"texture\":0},\"up\":{\"uv\":[61,55,59,53],\"texture\":0},\"down\":{\"uv\":[61,55,59,57],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a65ddbfa-a090-fa94-36f9-a83e90783a60\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.975,21.6,3.175],\"to\":[1.975,24.6,3.175],\"autouv\":0,\"color\":5,\"origin\":[0,24.35,-0.575],\"faces\":{\"north\":{\"uv\":[4,34,8,37],\"texture\":0},\"east\":{\"uv\":[4,34,8,37],\"texture\":0},\"south\":{\"uv\":[4,34,8,37],\"texture\":0},\"west\":{\"uv\":[4,34,8,37],\"texture\":0},\"up\":{\"uv\":[4,34,8,37],\"texture\":0},\"down\":{\"uv\":[4,34,8,37],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c7fc4bf2-2bdb-6f57-e1d2-1b55fbadc572\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.475,24.1,2],\"to\":[2.475,24.7,2.75],\"autouv\":0,\"color\":5,\"rotation\":[-45,0,0],\"origin\":[0,24.85,2.375],\"faces\":{\"north\":{\"uv\":[55,31,60,32],\"texture\":0},\"east\":{\"uv\":[39,66,40,67],\"texture\":0},\"south\":{\"uv\":[55,32,60,33],\"texture\":0},\"west\":{\"uv\":[66,39,67,40],\"texture\":0},\"up\":{\"uv\":[60,34,55,33],\"texture\":0},\"down\":{\"uv\":[60,34,55,35],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"064202fa-1ea8-2e3d-08f6-15645a4d4f5c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.475,18.6,3.175],\"to\":[1.475,21.6,3.175],\"autouv\":0,\"color\":5,\"origin\":[0,21.35,-0.575],\"faces\":{\"north\":{\"uv\":[42,29,45,32],\"texture\":0},\"east\":{\"uv\":[42,29,45,32],\"texture\":0},\"south\":{\"uv\":[42,29,45,32],\"texture\":0},\"west\":{\"uv\":[42,29,45,32],\"texture\":0},\"up\":{\"uv\":[42,29,45,32],\"texture\":0},\"down\":{\"uv\":[42,29,45,32],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d084c28f-189e-764b-538f-cf0537a5c344\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1.225,21.025,3.175],\"to\":[-0.775,22.3,3.175],\"autouv\":0,\"color\":5,\"rotation\":[0,0,22.5],\"origin\":[0.125,20.1,3.175],\"faces\":{\"north\":{\"uv\":[66,40,67,41],\"texture\":0},\"east\":{\"uv\":[66,40,67,41],\"texture\":0},\"south\":{\"uv\":[66,40,67,41],\"texture\":0},\"west\":{\"uv\":[66,40,67,41],\"texture\":0},\"up\":{\"uv\":[66,40,67,41],\"texture\":0},\"down\":{\"uv\":[66,40,67,41],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"41d62fcf-9109-d04a-1897-b6d0024546f1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.775,21.025,3.175],\"to\":[1.225,22.3,3.175],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-22.5],\"origin\":[-0.125,20.1,3.175],\"faces\":{\"north\":{\"uv\":[66,41,67,42],\"texture\":0},\"east\":{\"uv\":[66,41,67,42],\"texture\":0},\"south\":{\"uv\":[66,41,67,42],\"texture\":0},\"west\":{\"uv\":[66,41,67,42],\"texture\":0},\"up\":{\"uv\":[66,41,67,42],\"texture\":0},\"down\":{\"uv\":[66,41,67,42],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5f6f0613-a341-4bc1-caa8-71c6a4d9f006\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.975,15.6,3.175],\"to\":[0.975,18.6,3.175],\"autouv\":0,\"color\":5,\"origin\":[0,18.35,-0.575],\"faces\":{\"north\":{\"uv\":[53,30,55,33],\"texture\":0},\"east\":{\"uv\":[53,30,55,33],\"texture\":0},\"south\":{\"uv\":[53,30,55,33],\"texture\":0},\"west\":{\"uv\":[53,30,55,33],\"texture\":0},\"up\":{\"uv\":[53,30,55,33],\"texture\":0},\"down\":{\"uv\":[53,30,55,33],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"96cda888-fe8e-9317-9335-171c7a631c52\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.725,18.025,3.175],\"to\":[-0.275,19.3,3.175],\"autouv\":0,\"color\":5,\"rotation\":[0,0,22.5],\"origin\":[0.625,17.1,3.175],\"faces\":{\"north\":{\"uv\":[66,42,67,43],\"texture\":0},\"east\":{\"uv\":[66,42,67,43],\"texture\":0},\"south\":{\"uv\":[66,42,67,43],\"texture\":0},\"west\":{\"uv\":[66,42,67,43],\"texture\":0},\"up\":{\"uv\":[66,42,67,43],\"texture\":0},\"down\":{\"uv\":[66,42,67,43],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b5702594-4dba-0c01-6343-02d2faac24e1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.275,18.025,3.175],\"to\":[0.725,19.3,3.175],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-22.5],\"origin\":[-0.625,17.1,3.175],\"faces\":{\"north\":{\"uv\":[66,43,67,44],\"texture\":0},\"east\":{\"uv\":[66,43,67,44],\"texture\":0},\"south\":{\"uv\":[66,43,67,44],\"texture\":0},\"west\":{\"uv\":[66,43,67,44],\"texture\":0},\"up\":{\"uv\":[66,43,67,44],\"texture\":0},\"down\":{\"uv\":[66,43,67,44],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"013aa42b-83c2-5f5f-327d-8a74970cdb49\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.475,12.6,3.175],\"to\":[0.475,15.6,3.175],\"autouv\":0,\"color\":5,\"origin\":[0,15.35,-0.575],\"faces\":{\"north\":{\"uv\":[7,62,8,65],\"texture\":0},\"east\":{\"uv\":[7,62,8,65],\"texture\":0},\"south\":{\"uv\":[7,62,8,65],\"texture\":0},\"west\":{\"uv\":[7,62,8,65],\"texture\":0},\"up\":{\"uv\":[7,62,8,65],\"texture\":0},\"down\":{\"uv\":[7,62,8,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8fbc89dc-96ae-1dfb-471a-f121b77bdba1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.225,15.025,3.175],\"to\":[0.225,16.3,3.175],\"autouv\":0,\"color\":5,\"rotation\":[0,0,22.5],\"origin\":[1.125,14.1,3.175],\"faces\":{\"north\":{\"uv\":[66,44,67,45],\"texture\":0},\"east\":{\"uv\":[66,44,67,45],\"texture\":0},\"south\":{\"uv\":[66,44,67,45],\"texture\":0},\"west\":{\"uv\":[66,44,67,45],\"texture\":0},\"up\":{\"uv\":[66,44,67,45],\"texture\":0},\"down\":{\"uv\":[66,44,67,45],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d1867ca7-e2c9-2a22-d67d-6a3117fef579\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-0.225,15.025,3.175],\"to\":[0.225,16.3,3.175],\"autouv\":0,\"color\":5,\"rotation\":[0,0,-22.5],\"origin\":[-1.125,14.1,3.175],\"faces\":{\"north\":{\"uv\":[66,45,67,46],\"texture\":0},\"east\":{\"uv\":[66,45,67,46],\"texture\":0},\"south\":{\"uv\":[66,45,67,46],\"texture\":0},\"west\":{\"uv\":[66,45,67,46],\"texture\":0},\"up\":{\"uv\":[66,45,67,46],\"texture\":0},\"down\":{\"uv\":[66,45,67,46],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f3b7b5ac-f8dd-ee5a-2837-8b886bc89be3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.82264,31.77,-1.11375],\"to\":[2.95736,33.79,-0.95375],\"autouv\":0,\"color\":1,\"origin\":[0.30736,33.11,-1.15375],\"faces\":{\"north\":{\"uv\":[8,14,15,17],\"texture\":0},\"east\":{\"uv\":[22,39,23,42],\"texture\":0},\"south\":{\"uv\":[15,12,22,15],\"texture\":0},\"west\":{\"uv\":[4,60,5,63],\"texture\":0},\"up\":{\"uv\":[53,45,46,44],\"texture\":0},\"down\":{\"uv\":[53,45,46,46],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2c82fef6-2f1c-e5d9-a1b0-10313da9a8cd\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,11.49545,-1.45],\"to\":[-5.2322,13.49545,2],\"autouv\":0,\"color\":5,\"origin\":[-5.2322,10.99545,0.275],\"faces\":{\"north\":{\"uv\":[35,52,38,54],\"texture\":null},\"east\":{\"uv\":[35,52,38,54],\"texture\":0},\"south\":{\"uv\":[35,52,38,54],\"texture\":null},\"west\":{\"uv\":[35,52,38,54],\"texture\":0},\"up\":{\"uv\":[35,52,38,54],\"texture\":null},\"down\":{\"uv\":[35,52,38,54],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"9aaec269-21fa-fc94-f584-04cf13144345\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,12.92045,-0.8],\"to\":[-5.2322,14.17045,-0.35],\"autouv\":0,\"color\":5,\"rotation\":[-22.5,0,0],\"origin\":[-5.2322,10.99545,0.275],\"faces\":{\"north\":{\"uv\":[66,21,67,22],\"texture\":0},\"east\":{\"uv\":[66,21,67,22],\"texture\":0},\"south\":{\"uv\":[66,21,67,22],\"texture\":0},\"west\":{\"uv\":[66,21,67,22],\"texture\":0},\"up\":{\"uv\":[66,21,67,22],\"texture\":0},\"down\":{\"uv\":[66,21,67,22],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5500625f-17c2-64b1-406a-697250abf2ef\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,12.92045,0.875],\"to\":[-5.2322,14.17045,1.325],\"autouv\":0,\"color\":5,\"rotation\":[22.5,0,0],\"origin\":[-5.2322,10.99545,0.25],\"faces\":{\"north\":{\"uv\":[0,0,0,1],\"texture\":0},\"east\":{\"uv\":[22,66,23,67],\"texture\":0},\"south\":{\"uv\":[0,0,0,1],\"texture\":0},\"west\":{\"uv\":[66,22,67,23],\"texture\":0},\"up\":{\"uv\":[0,1,0,0],\"texture\":0},\"down\":{\"uv\":[0,0,0,1],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"31163dd6-e25e-d315-3db0-b755cb62a140\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,9.49545,-0.95],\"to\":[-5.2322,11.49545,1.5],\"autouv\":0,\"color\":5,\"origin\":[-5.2322,8.99545,0.275],\"faces\":{\"north\":{\"uv\":[37,58,39,60],\"texture\":0},\"east\":{\"uv\":[37,58,39,60],\"texture\":0},\"south\":{\"uv\":[37,58,39,60],\"texture\":0},\"west\":{\"uv\":[37,58,39,60],\"texture\":0},\"up\":{\"uv\":[37,58,39,60],\"texture\":0},\"down\":{\"uv\":[37,58,39,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1e8fb086-03d6-eabd-c193-db916605a3c7\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,10.92045,-0.3],\"to\":[-5.2322,12.17045,0.15],\"autouv\":0,\"color\":5,\"rotation\":[-22.5,0,0],\"origin\":[-5.2322,8.99545,0.775],\"faces\":{\"north\":{\"uv\":[66,23,67,24],\"texture\":0},\"east\":{\"uv\":[66,23,67,24],\"texture\":0},\"south\":{\"uv\":[66,23,67,24],\"texture\":0},\"west\":{\"uv\":[66,23,67,24],\"texture\":0},\"up\":{\"uv\":[66,23,67,24],\"texture\":0},\"down\":{\"uv\":[66,23,67,24],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a9e565b5-91ac-43ec-b6e8-fb95376fe40f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,10.92045,0.375],\"to\":[-5.2322,12.17045,0.825],\"autouv\":0,\"color\":5,\"rotation\":[22.5,0,0],\"origin\":[-5.2322,8.99545,-0.25],\"faces\":{\"north\":{\"uv\":[66,24,67,25],\"texture\":0},\"east\":{\"uv\":[66,24,67,25],\"texture\":0},\"south\":{\"uv\":[66,24,67,25],\"texture\":0},\"west\":{\"uv\":[66,24,67,25],\"texture\":0},\"up\":{\"uv\":[66,24,67,25],\"texture\":0},\"down\":{\"uv\":[66,24,67,25],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e7ef1199-ed3e-93ee-adad-e9230cad7d6c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,7.49545,-0.45],\"to\":[-5.2322,9.49545,1],\"autouv\":0,\"color\":5,\"origin\":[-5.2322,6.99545,0.275],\"faces\":{\"north\":{\"uv\":[38,63,39,65],\"texture\":0},\"east\":{\"uv\":[38,63,39,65],\"texture\":0},\"south\":{\"uv\":[38,63,39,65],\"texture\":0},\"west\":{\"uv\":[38,63,39,65],\"texture\":0},\"up\":{\"uv\":[38,63,39,65],\"texture\":0},\"down\":{\"uv\":[38,63,39,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b2b3fdbe-0de5-84ae-df96-288f90b79d94\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,8.92045,0.2],\"to\":[-5.2322,10.17045,0.65],\"autouv\":0,\"color\":5,\"rotation\":[-22.5,0,0],\"origin\":[-5.2322,6.99545,1.275],\"faces\":{\"north\":{\"uv\":[66,25,67,26],\"texture\":0},\"east\":{\"uv\":[66,25,67,26],\"texture\":0},\"south\":{\"uv\":[66,25,67,26],\"texture\":0},\"west\":{\"uv\":[66,25,67,26],\"texture\":0},\"up\":{\"uv\":[66,25,67,26],\"texture\":0},\"down\":{\"uv\":[66,25,67,26],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"91fa364d-41e6-5c82-2bfa-1cc1570fd514\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5.2322,8.92045,-0.125],\"to\":[-5.2322,10.17045,0.325],\"autouv\":0,\"color\":5,\"rotation\":[22.5,0,0],\"origin\":[-5.2322,6.99545,-0.75],\"faces\":{\"north\":{\"uv\":[66,26,67,27],\"texture\":0},\"east\":{\"uv\":[66,26,67,27],\"texture\":0},\"south\":{\"uv\":[66,26,67,27],\"texture\":0},\"west\":{\"uv\":[66,26,67,27],\"texture\":0},\"up\":{\"uv\":[66,26,67,27],\"texture\":0},\"down\":{\"uv\":[66,26,67,27],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f6378b2a-8e1f-a751-beda-9e1f6feda543\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,11.49545,-1.45],\"to\":[5.2322,13.49545,2],\"autouv\":0,\"color\":5,\"origin\":[5.2322,10.99545,0.275],\"faces\":{\"north\":{\"uv\":[26,52,29,54],\"texture\":null},\"east\":{\"uv\":[26,52,29,54],\"texture\":0},\"south\":{\"uv\":[26,52,29,54],\"texture\":null},\"west\":{\"uv\":[26,52,29,54],\"texture\":0},\"up\":{\"uv\":[26,52,29,54],\"texture\":null},\"down\":{\"uv\":[26,52,29,54],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"6afb3580-9d2a-a800-44e0-7ad32af91987\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,12.92045,-0.8],\"to\":[5.2322,14.17045,-0.35],\"autouv\":0,\"color\":5,\"rotation\":[-22.5,0,0],\"origin\":[5.2322,10.99545,0.275],\"faces\":{\"north\":{\"uv\":[15,66,16,67],\"texture\":0},\"east\":{\"uv\":[15,66,16,67],\"texture\":0},\"south\":{\"uv\":[15,66,16,67],\"texture\":0},\"west\":{\"uv\":[15,66,16,67],\"texture\":0},\"up\":{\"uv\":[15,66,16,67],\"texture\":0},\"down\":{\"uv\":[15,66,16,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e885a9ed-6555-231f-5a14-7d4f6dfc8839\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,12.92045,0.875],\"to\":[5.2322,14.17045,1.325],\"autouv\":0,\"color\":5,\"rotation\":[22.5,0,0],\"origin\":[5.2322,10.99545,0.25],\"faces\":{\"north\":{\"uv\":[16,66,17,67],\"texture\":0},\"east\":{\"uv\":[16,66,17,67],\"texture\":0},\"south\":{\"uv\":[16,66,17,67],\"texture\":0},\"west\":{\"uv\":[16,66,17,67],\"texture\":0},\"up\":{\"uv\":[16,66,17,67],\"texture\":0},\"down\":{\"uv\":[16,66,17,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5e4c7faf-af00-d46f-ef74-f7616ec8b21d\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,9.49545,-0.95],\"to\":[5.2322,11.49545,1.5],\"autouv\":0,\"color\":5,\"origin\":[5.2322,8.99545,0.275],\"faces\":{\"north\":{\"uv\":[31,58,33,60],\"texture\":0},\"east\":{\"uv\":[31,58,33,60],\"texture\":0},\"south\":{\"uv\":[31,58,33,60],\"texture\":0},\"west\":{\"uv\":[31,58,33,60],\"texture\":0},\"up\":{\"uv\":[31,58,33,60],\"texture\":0},\"down\":{\"uv\":[31,58,33,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"179fd506-fced-8fc5-f228-b39dfd836eab\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,10.92045,-0.3],\"to\":[5.2322,12.17045,0.15],\"autouv\":0,\"color\":5,\"rotation\":[-22.5,0,0],\"origin\":[5.2322,8.99545,0.775],\"faces\":{\"north\":{\"uv\":[17,66,18,67],\"texture\":0},\"east\":{\"uv\":[17,66,18,67],\"texture\":0},\"south\":{\"uv\":[17,66,18,67],\"texture\":0},\"west\":{\"uv\":[17,66,18,67],\"texture\":0},\"up\":{\"uv\":[17,66,18,67],\"texture\":0},\"down\":{\"uv\":[17,66,18,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dcc13d2a-e8b4-9cf2-fb6a-1222d2c42980\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,10.92045,0.375],\"to\":[5.2322,12.17045,0.825],\"autouv\":0,\"color\":5,\"rotation\":[22.5,0,0],\"origin\":[5.2322,8.99545,-0.25],\"faces\":{\"north\":{\"uv\":[18,66,19,67],\"texture\":0},\"east\":{\"uv\":[18,66,19,67],\"texture\":0},\"south\":{\"uv\":[18,66,19,67],\"texture\":0},\"west\":{\"uv\":[18,66,19,67],\"texture\":0},\"up\":{\"uv\":[18,66,19,67],\"texture\":0},\"down\":{\"uv\":[18,66,19,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9ff9fa0a-03b9-b05d-2344-8fc7bef11e46\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,7.49545,-0.45],\"to\":[5.2322,9.49545,1],\"autouv\":0,\"color\":5,\"origin\":[5.2322,6.99545,0.275],\"faces\":{\"north\":{\"uv\":[35,63,36,65],\"texture\":0},\"east\":{\"uv\":[35,63,36,65],\"texture\":0},\"south\":{\"uv\":[35,63,36,65],\"texture\":0},\"west\":{\"uv\":[35,63,36,65],\"texture\":0},\"up\":{\"uv\":[35,63,36,65],\"texture\":0},\"down\":{\"uv\":[35,63,36,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"60ac24f4-f0d5-2709-8498-d5929693e212\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,8.92045,0.2],\"to\":[5.2322,10.17045,0.65],\"autouv\":0,\"color\":5,\"rotation\":[-22.5,0,0],\"origin\":[5.2322,6.99545,1.275],\"faces\":{\"north\":{\"uv\":[19,66,20,67],\"texture\":0},\"east\":{\"uv\":[19,66,20,67],\"texture\":0},\"south\":{\"uv\":[19,66,20,67],\"texture\":0},\"west\":{\"uv\":[19,66,20,67],\"texture\":0},\"up\":{\"uv\":[19,66,20,67],\"texture\":0},\"down\":{\"uv\":[19,66,20,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"120acc01-548e-637c-0f2f-6fc565133c9a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2322,8.92045,-0.125],\"to\":[5.2322,10.17045,0.325],\"autouv\":0,\"color\":5,\"rotation\":[22.5,0,0],\"origin\":[5.2322,6.99545,-0.75],\"faces\":{\"north\":{\"uv\":[20,66,21,67],\"texture\":0},\"east\":{\"uv\":[20,66,21,67],\"texture\":0},\"south\":{\"uv\":[20,66,21,67],\"texture\":0},\"west\":{\"uv\":[20,66,21,67],\"texture\":0},\"up\":{\"uv\":[20,66,21,67],\"texture\":0},\"down\":{\"uv\":[20,66,21,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3698deba-f32c-bf34-b848-ba738866e0fc\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-2.75,11.5,2.25],\"to\":[2.75,13.1,2.25],\"autouv\":0,\"color\":5,\"origin\":[1,13.65,4.025],\"faces\":{\"north\":{\"uv\":[19,31,25,33],\"texture\":0},\"east\":{\"uv\":[19,31,25,33],\"texture\":null},\"south\":{\"uv\":[19,31,25,33],\"texture\":0},\"west\":{\"uv\":[19,31,25,33],\"texture\":null},\"up\":{\"uv\":[19,31,25,33],\"texture\":null},\"down\":{\"uv\":[19,31,25,33],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"34dfbab7-01ac-2496-8629-e92b0a014b99\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,13.06318,-2.225],\"to\":[-2.75,14.06318,-1.975],\"autouv\":0,\"color\":3,\"rotation\":[0,0,-45],\"origin\":[-3.25,13.56318,-2.1],\"faces\":{\"north\":{\"uv\":[3,66,4,67],\"texture\":0},\"east\":{\"uv\":[66,3,67,4],\"texture\":0},\"south\":{\"uv\":[4,66,5,67],\"texture\":0},\"west\":{\"uv\":[66,4,67,5],\"texture\":0},\"up\":{\"uv\":[6,67,5,66],\"texture\":0},\"down\":{\"uv\":[67,5,66,6],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"75f4c75b-74ad-f2a8-ff6b-e18c7f4fdedf\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.75,13.06318,-2.225],\"to\":[3.75,14.06318,-1.975],\"autouv\":0,\"color\":3,\"rotation\":[0,0,45],\"origin\":[3.25,13.56318,-2.1],\"faces\":{\"north\":{\"uv\":[6,66,7,67],\"texture\":0},\"east\":{\"uv\":[66,6,67,7],\"texture\":0},\"south\":{\"uv\":[7,66,8,67],\"texture\":0},\"west\":{\"uv\":[66,7,67,8],\"texture\":0},\"up\":{\"uv\":[9,67,8,66],\"texture\":0},\"down\":{\"uv\":[67,8,66,9],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d4f3afd2-536b-fd5d-03b6-b453a765ab11\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.75,13.06318,2.525],\"to\":[3.75,14.06318,2.775],\"autouv\":0,\"color\":3,\"rotation\":[0,0,45],\"origin\":[3.25,13.56318,2.65],\"faces\":{\"north\":{\"uv\":[12,66,13,67],\"texture\":0},\"east\":{\"uv\":[66,12,67,13],\"texture\":0},\"south\":{\"uv\":[13,66,14,67],\"texture\":0},\"west\":{\"uv\":[66,13,67,14],\"texture\":0},\"up\":{\"uv\":[15,67,14,66],\"texture\":0},\"down\":{\"uv\":[67,14,66,15],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ca75edbd-a5d8-27c4-cc93-71bbbb4a9711\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,13.06318,2.525],\"to\":[-2.75,14.06318,2.775],\"autouv\":0,\"color\":3,\"rotation\":[0,0,-45],\"origin\":[-3.25,13.56318,2.65],\"faces\":{\"north\":{\"uv\":[9,66,10,67],\"texture\":0},\"east\":{\"uv\":[66,9,67,10],\"texture\":0},\"south\":{\"uv\":[10,66,11,67],\"texture\":0},\"west\":{\"uv\":[66,10,67,11],\"texture\":0},\"up\":{\"uv\":[12,67,11,66],\"texture\":0},\"down\":{\"uv\":[67,11,66,12],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"57155ef8-9d6a-186e-0e88-41fe79b2d637\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,24.31318,-2.475],\"to\":[-2.75,25.31318,-2.225],\"autouv\":0,\"color\":3,\"rotation\":[0,0,-45],\"origin\":[-3.25,24.81318,-2.35],\"faces\":{\"north\":{\"uv\":[46,66,47,67],\"texture\":0},\"east\":{\"uv\":[66,46,67,47],\"texture\":0},\"south\":{\"uv\":[47,66,48,67],\"texture\":0},\"west\":{\"uv\":[66,47,67,48],\"texture\":0},\"up\":{\"uv\":[49,67,48,66],\"texture\":0},\"down\":{\"uv\":[67,48,66,49],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"29745a85-fe96-2a85-74b5-c4c68d141744\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.75,24.31318,-2.475],\"to\":[3.75,25.31318,-2.225],\"autouv\":0,\"color\":3,\"rotation\":[0,0,45],\"origin\":[3.25,24.81318,-2.35],\"faces\":{\"north\":{\"uv\":[49,66,50,67],\"texture\":0},\"east\":{\"uv\":[66,49,67,50],\"texture\":0},\"south\":{\"uv\":[50,66,51,67],\"texture\":0},\"west\":{\"uv\":[66,50,67,51],\"texture\":0},\"up\":{\"uv\":[52,67,51,66],\"texture\":0},\"down\":{\"uv\":[67,51,66,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5b094f76-a7f5-f24e-e320-c58b2ac2b435\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[2.75,24.31318,2.775],\"to\":[3.75,25.31318,3.025],\"autouv\":0,\"color\":3,\"rotation\":[0,0,45],\"origin\":[3.25,24.81318,2.9],\"faces\":{\"north\":{\"uv\":[55,66,56,67],\"texture\":0},\"east\":{\"uv\":[66,55,67,56],\"texture\":0},\"south\":{\"uv\":[56,66,57,67],\"texture\":0},\"west\":{\"uv\":[66,56,67,57],\"texture\":0},\"up\":{\"uv\":[58,67,57,66],\"texture\":0},\"down\":{\"uv\":[67,57,66,58],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f4ec532a-b1be-af15-da29-2dbbe5c35892\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,24.31318,2.775],\"to\":[-2.75,25.31318,3.025],\"autouv\":0,\"color\":3,\"rotation\":[0,0,-45],\"origin\":[-3.25,24.81318,2.9],\"faces\":{\"north\":{\"uv\":[52,66,53,67],\"texture\":0},\"east\":{\"uv\":[66,52,67,53],\"texture\":0},\"south\":{\"uv\":[53,66,54,67],\"texture\":0},\"west\":{\"uv\":[66,53,67,54],\"texture\":0},\"up\":{\"uv\":[55,67,54,66],\"texture\":0},\"down\":{\"uv\":[67,54,66,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"26d54259-68ec-56a5-7d1b-b9e1022233fb\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.48264,31.93,-1.57375],\"to\":[-1.26264,33.15,-1.19375],\"autouv\":0,\"color\":9,\"rotation\":[0,0,45],\"origin\":[-2.34264,33.07,0.60625],\"faces\":{\"north\":{\"uv\":[16,48,19,50],\"texture\":0},\"east\":{\"uv\":[43,62,44,64],\"texture\":0},\"south\":{\"uv\":[48,16,51,18],\"texture\":0},\"west\":{\"uv\":[62,52,63,54],\"texture\":0},\"up\":{\"uv\":[62,49,59,48],\"texture\":0},\"down\":{\"uv\":[63,0,60,1],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4584d514-f657-2cb1-f174-9ddae5cd00e3\"},{\"name\":\"left_hair_locator\",\"position\":[-2.26264,21.99,3.08625],\"rotation\":[0,0,0],\"ignore_inherited_scale\":false,\"visibility\":true,\"locked\":false,\"uuid\":\"2a453afd-be32-1082-9486-3cc8b6d0f61f\",\"type\":\"locator\"},{\"name\":\"right_hair_locator\",\"position\":[2.13736,24.19,3.08625],\"rotation\":[0,0,0],\"ignore_inherited_scale\":false,\"visibility\":true,\"locked\":false,\"uuid\":\"a1cb5820-0374-7914-881a-c780d8340eb4\",\"type\":\"locator\"},{\"name\":\"left_hair_ik\",\"position\":[-2.26264,21.99,3.08625],\"ik_target\":\"2a453afd-be32-1082-9486-3cc8b6d0f61f\",\"ik_source\":\"\",\"lock_ik_target_rotation\":false,\"visibility\":true,\"locked\":false,\"uuid\":\"185b2cad-6cc7-a818-34f9-59c70db1d853\",\"type\":\"null_object\"},{\"name\":\"right_hair_ik\",\"position\":[2.13736,24.19,3.08625],\"ik_target\":\"a1cb5820-0374-7914-881a-c780d8340eb4\",\"ik_source\":\"\",\"lock_ik_target_rotation\":false,\"visibility\":true,\"locked\":false,\"uuid\":\"ac61223e-e43c-ced3-6869-82c8e685056c\",\"type\":\"null_object\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-5,0,-5],\"to\":[5,0,5],\"autouv\":1,\"color\":2,\"visibility\":false,\"origin\":[0,0,0],\"faces\":{\"north\":{\"uv\":[0,2,10,2]},\"east\":{\"uv\":[0,2,10,2]},\"south\":{\"uv\":[0,2,10,2]},\"west\":{\"uv\":[0,2,10,2]},\"up\":{\"uv\":[0,0,10,10]},\"down\":{\"uv\":[0,0,10,10]}},\"type\":\"cube\",\"uuid\":\"a0e67201-0de5-f611-feff-0dbad6b8e941\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-6,8,-4.5],\"to\":[6,39,5],\"autouv\":1,\"color\":1,\"visibility\":false,\"origin\":[0,8,-1.5],\"faces\":{\"north\":{\"uv\":[0,1,12,32]},\"east\":{\"uv\":[0,1,9.5,32]},\"south\":{\"uv\":[0,1,12,32]},\"west\":{\"uv\":[0,1,9.5,32]},\"up\":{\"uv\":[0,0,12,9.5]},\"down\":{\"uv\":[0,0,12,9.5]}},\"type\":\"cube\",\"uuid\":\"9afe6ef9-752d-2a94-3213-08adb08aa0df\"},{\"name\":\"glow_cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-14,23,8.4],\"to\":[14,51,8.4],\"autouv\":0,\"color\":8,\"rotation\":[-22.5,0,0],\"origin\":[0,36,9.4],\"faces\":{\"north\":{\"uv\":[1,1,32,32],\"texture\":1},\"east\":{\"uv\":[2,3,2,21],\"texture\":null},\"south\":{\"uv\":[1,1,32,32],\"texture\":1},\"west\":{\"uv\":[0,3,0,21],\"texture\":null},\"up\":{\"uv\":[2,0,20,0],\"texture\":null},\"down\":{\"uv\":[2,2,20,2],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"4fa795c4-3f80-a25f-5dbf-b99637163aca\"}],\"groups\":[{\"uuid\":\"10ee45d0-a3e0-f40b-990e-a0dac19721d1\",\"export\":true,\"locked\":false,\"origin\":[0,27,0.4],\"rotation\":[0,0,0],\"color\":0,\"name\":\"hi_head\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"c0820792-9e91-8bcb-812c-a6d07670799a\",\"export\":true,\"locked\":false,\"origin\":[-2.18628,31.1902,3.13333],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_hair_1\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"a7c9dbe8-f0b2-caf9-e2ae-fa40543a508a\",\"export\":true,\"locked\":false,\"origin\":[2.12317,31.1853,3.09375],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_hair_1\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"8ee82b29-c9f1-ae08-9c3b-dcd5f8787db5\",\"export\":true,\"locked\":false,\"origin\":[-2.2625,28.975,3.1125],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_hair_2\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"5a05f380-61c5-f164-885f-d3b2cc27acbe\",\"export\":true,\"locked\":false,\"origin\":[-2.225,25.56667,3.1],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_hair_3\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"aecdc16a-4f38-07a6-d0ed-48834f8a5836\",\"export\":true,\"locked\":false,\"origin\":[2.0625,29.6,3.1375],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_hair_2\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"6492768f-2a1a-0139-2a5a-af6c200210f8\",\"export\":true,\"locked\":false,\"origin\":[2.1,26.99167,3.1],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_hair_3\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"469494ea-42f7-b718-90ba-46d9298a3b38\",\"export\":true,\"locked\":false,\"origin\":[0.00072,22.89038,0.61914],\"rotation\":[0,0,0],\"color\":0,\"name\":\"body\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"0266d8da-f639-7c87-40f6-834b24e671e2\",\"export\":true,\"locked\":false,\"origin\":[3.925,24.075,0.25],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"a44819f0-9e13-12f5-b270-88d133e31f46\",\"export\":true,\"locked\":false,\"origin\":[3.925,21.97145,0.25],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"98b261b3-0a69-4a62-2229-0d1aee17c78e\",\"export\":true,\"locked\":false,\"origin\":[0,24.11363,-1.76293],\"rotation\":[0,0,0],\"color\":0,\"name\":\"chest\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"b6fc560a-2b3f-cb7b-2280-dfdae36d9d53\",\"export\":true,\"locked\":false,\"origin\":[4.05799,18.46909,0.2725],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_hand\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"5cdc1be0-a703-9397-813d-b0c2fe914a14\",\"export\":true,\"locked\":false,\"origin\":[-0.02111,19.10476,0.38027],\"rotation\":[0,0,0],\"color\":0,\"name\":\"under_body\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"20832476-f83d-a3bf-a371-dfb6ddc221f1\",\"export\":true,\"locked\":false,\"origin\":[1.65052,13.0185,0.25211],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"14f70d2a-4f4d-e8e6-fa68-cb95be5d9ff7\",\"export\":true,\"locked\":false,\"origin\":[1.65105,10.6869,0.256],\"rotation\":[-30,0,0],\"color\":0,\"name\":\"right_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"2e3cc482-b167-33b8-1714-5c7b341d65b4\",\"export\":true,\"locked\":false,\"origin\":[-1.65052,13.01847,0.2521],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"7113ca1c-2ae3-a77e-c3ab-63794d1ba17f\",\"export\":true,\"locked\":false,\"origin\":[-1.65105,10.68693,0.25598],\"rotation\":[-30,0,0],\"color\":0,\"name\":\"left_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"6f3eac45-0aee-fcf4-e283-e1fb93e876d6\",\"export\":true,\"locked\":false,\"origin\":[-3.925,24.075,0.25],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"7c168fd7-cdf9-c904-82b2-464caba505cc\",\"export\":true,\"locked\":false,\"origin\":[-3.925,21.97145,0.25],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"842ad7bd-7015-fa76-ebca-314ed49a789f\",\"export\":true,\"locked\":false,\"origin\":[-4.05799,18.46909,0.2725],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_hand\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"2404264b-66f1-5dc1-5fe5-db0c33526a27\",\"export\":true,\"locked\":false,\"origin\":[0,24.6,3.175],\"rotation\":[0,0,0],\"color\":0,\"name\":\"middle_cape_1\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"1a9595e0-ba95-9397-76c5-68d018b0975a\",\"export\":true,\"locked\":false,\"origin\":[0,21.60036,3.175],\"rotation\":[0,0,0],\"color\":0,\"name\":\"middle_cape_2\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"f719d2bb-6c28-0ab6-a585-41f54792160c\",\"export\":true,\"locked\":false,\"origin\":[0,18.60036,3.175],\"rotation\":[0,0,0],\"color\":0,\"name\":\"middle_cape_3\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"b2fd5761-a84d-7020-38c7-2efcd6c39298\",\"export\":true,\"locked\":false,\"origin\":[0,15.60036,3.175],\"rotation\":[0,0,0],\"color\":0,\"name\":\"middle_cape_4\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"9b4610f1-5d4d-9c62-d7db-b098f5a0a8e8\",\"export\":true,\"locked\":false,\"origin\":[0.1625,34.2375,-6.4],\"rotation\":[0,0,0],\"color\":0,\"name\":\"eye\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"62421ffb-7685-a28e-c939-59b0d8123776\",\"export\":true,\"locked\":false,\"origin\":[-5.2322,13.52419,0.26667],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_cape_1\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"167d2bb7-4626-6746-c72b-ba3c8dab3e2f\",\"export\":true,\"locked\":false,\"origin\":[-5.2322,11.52419,0.26667],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_cape_2\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"57123914-f464-2f02-3eb3-e1ac924f8ef2\",\"export\":true,\"locked\":false,\"origin\":[-5.2322,9.52419,0.26667],\"rotation\":[0,0,0],\"color\":0,\"name\":\"left_cape_3\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"06102678-1ae6-d632-30b0-6964f314e6bf\",\"export\":true,\"locked\":false,\"origin\":[5.2322,13.52419,0.2667],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_cape_1\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"977a05f5-98e7-7f45-0d7b-f345af018dd3\",\"export\":true,\"locked\":false,\"origin\":[5.2322,11.52419,0.26667],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_cape_2\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"18d36cc2-46b6-f929-3624-07aef443f80d\",\"export\":true,\"locked\":false,\"origin\":[5.2322,9.52419,0.26667],\"rotation\":[0,0,0],\"color\":0,\"name\":\"right_cape_3\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"0d53fe40-29a8-f10f-e40f-59194c70c775\",\"export\":true,\"locked\":false,\"origin\":[0,0,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"shadow\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":false,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"d8e887a2-a16d-3dfc-b2e0-ba8aca6bb84f\",\"export\":true,\"locked\":false,\"origin\":[0,4,-1.5],\"rotation\":[0,0,0],\"color\":0,\"name\":\"hitbox\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false},{\"uuid\":\"aa6e0a4a-d036-a286-5c6f-ada2c220032b\",\"export\":true,\"locked\":false,\"origin\":[0,36.5412,8.09344],\"rotation\":[0,0,0],\"color\":0,\"name\":\"circle\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true,\"primary_selected\":false}],\"outliner\":[{\"uuid\":\"469494ea-42f7-b718-90ba-46d9298a3b38\",\"isOpen\":true,\"children\":[{\"uuid\":\"10ee45d0-a3e0-f40b-990e-a0dac19721d1\",\"isOpen\":true,\"children\":[\"fdcdff03-57c1-e071-5769-588fbc314386\",\"5c446ca3-fa6a-9098-3658-6b2188e41ef4\",\"c73b2cca-267a-3b8a-82dc-1eeafcd6927d\",\"cd5d87f1-1f75-5842-de2a-3eeaf3af1f59\",\"ec21fc72-01ba-0601-895f-94d76063b07b\",\"29d56430-7916-7008-9cc8-b6565185a324\",\"2c80f29a-68d9-2841-a9b3-7de033e342f5\",\"85e31557-bf10-290c-931b-a1855a815d1c\",\"374c990f-cca9-7642-54f6-8fcc2ae47386\",{\"uuid\":\"9b4610f1-5d4d-9c62-d7db-b098f5a0a8e8\",\"isOpen\":true,\"children\":[\"2c82fef6-2f1c-e5d9-a1b0-10313da9a8cd\"]},\"bacad5c3-b6bc-3fc9-529e-b6f6ff181e82\",\"f322ebee-1063-7c39-2350-8d74edf49a71\",\"17633f4b-13a7-8951-8eec-bf2f1952ec70\",\"42c42a1c-3468-5c32-6bfe-36250a72b9af\",\"21b22b7a-bce2-d3cd-bafe-6bb8ca56111e\",\"350e3a30-4658-77a6-4df2-1d7b331a098b\",\"ecc83fce-7df5-b985-53a8-a3303f95832f\",\"3ba71253-feb2-8e7b-4548-01cd3e1df6ab\",\"41e7b572-b3cb-756c-9a95-64b8f5f33769\",\"83e3beb0-d1dd-6e57-57ce-dee9c9bdd8b9\",\"942cc6a2-71a8-88e6-b9dc-70b575a485e7\",\"2db4c218-3307-c625-7799-b63ef622617a\",\"c801537a-5f55-042c-3435-e557b199c416\",\"5573dea6-d06d-26ef-659c-5e9dfee7c967\",\"17f99990-ab5b-6c49-5c4e-3498f582fd93\",\"b3fd65a2-c4d8-8976-2bd9-eecda71c86b9\",\"9f7d1c1a-c9fd-ebd0-5737-915b7bb6942c\",\"2d82ea57-745f-beb3-0c88-6c509f7a74ed\",\"9a8f1ae5-db04-1afe-d3e5-624faaad48e9\",\"2ebc9aa9-73df-e4ed-0a7f-f22cc51c6feb\",\"84d754cf-915e-8061-2884-d5a7dc9f7bd1\",\"8ec78ad0-d84a-0628-5443-fbd1c509e136\",\"4584d514-f657-2cb1-f174-9ddae5cd00e3\",\"448668fe-7911-1061-80f2-3d5c40d07984\",\"da9a971e-d1c8-574f-1c0c-e63d98c1e9f6\",\"3f8a8ca3-5437-215f-de7e-08e69187015c\",\"0335203d-2335-ce64-0a28-16715076876f\",\"a20d7470-bd5d-94a6-8bed-c93cf0d2d161\",\"84e0afe0-c3b1-e5ee-63bf-491983d93639\",\"4458cb4c-42b1-5a2f-c0fa-8730c2a5e29b\",\"058b2d35-b9d7-23da-a53a-98f0b8533465\",\"a32e0821-ed45-ccd3-91c6-ac768ec3310c\",\"676e5395-2ca6-1959-efe3-e41270439957\",\"876f030b-55de-7dd6-e835-00488a4996f9\",\"512ee88e-a107-34c4-b782-b3cf94747612\",\"2e41d6ce-8800-9b74-6f26-94e295cac2bf\",\"27defe3c-bdd3-18bb-85d3-6405e21b1e03\",\"3d38f265-16fb-b26d-0851-3b04a28b6d64\",\"b3cf2f69-86ff-ed07-8934-90d0425f413e\",\"f0e1b661-bd04-bf34-5724-e4ce9acc5e75\",\"8c0b6782-b9e2-cf2d-31b4-c96d97905fd0\",\"571974f4-bc0d-5b57-c405-e5653335ee06\",\"c4478cd8-3191-c3b4-69cf-3377cc78af98\",\"8ba2c460-3e0d-40be-a9fc-cd7ae4a4574c\",\"667ec079-4b84-0569-1687-845a993a638f\",\"e81b7843-20f5-c76e-a1a1-e0e6e87d6767\",\"5729025f-74e3-e5dc-3d49-8d8ec85d1766\",\"3072f681-6e4b-aef4-27b4-6990650a7f1b\",\"cc3612bb-4f96-dc1e-46ad-d21b86d4df71\",\"d47bc743-9279-3d45-6344-f7035899232d\",\"6b77988c-a45d-1c22-dc83-86073939178e\",\"7f7169fb-d626-e57e-2176-8ade273cc9f4\",\"2d2daeae-d4e6-d6a9-d0d7-ac5a204040fe\",\"1812dc1c-20ad-522f-4394-729108bec75e\",\"9a276e96-6c77-ddd3-368c-f181c5dfb929\",\"d4d3e984-46f1-f6da-5c1f-cae44bfd11b3\",\"de0f931c-847a-9947-b487-f6a601951d69\",\"72a8e5fb-7b1b-994b-fc27-349d355d138b\",\"0f36a065-bbfb-34aa-9cd2-94c66b683a68\",\"fabe1f10-892d-dcfe-78dc-3bb27af4eb24\",\"831b1360-4fcf-59d3-82d5-df3af6f87c87\",\"107ef96f-b460-6a0c-a9e2-31d74b77b79a\",\"55a38668-5660-2226-d6e6-6e3e82332eaf\",\"ece00d0a-b988-98b1-0ce7-c89cef99cdba\",\"9b4e3438-f7a3-6771-95bc-51b94b1899e8\",\"185b2cad-6cc7-a818-34f9-59c70db1d853\",\"ac61223e-e43c-ced3-6869-82c8e685056c\",{\"uuid\":\"c0820792-9e91-8bcb-812c-a6d07670799a\",\"isOpen\":true,\"children\":[\"ae538734-efc4-2f34-17c4-995c75624993\",\"c0d88dd0-a196-b17c-e123-954163d428e7\",{\"uuid\":\"8ee82b29-c9f1-ae08-9c3b-dcd5f8787db5\",\"isOpen\":true,\"children\":[\"152d16d5-b078-564f-133e-d531ffcc2818\",{\"uuid\":\"5a05f380-61c5-f164-885f-d3b2cc27acbe\",\"isOpen\":true,\"children\":[\"25b70ee3-36dc-db96-bcdb-b06c141eff67\",\"15d2938a-7b52-b9b8-5e46-b1591e2f5234\",\"2a453afd-be32-1082-9486-3cc8b6d0f61f\"]}]}]},{\"uuid\":\"a7c9dbe8-f0b2-caf9-e2ae-fa40543a508a\",\"isOpen\":true,\"children\":[\"eab62980-956f-9ac3-e675-d862905ecc98\",{\"uuid\":\"aecdc16a-4f38-07a6-d0ed-48834f8a5836\",\"isOpen\":true,\"children\":[\"58c102f9-86e6-7ce8-e0ca-7b8d31dbf167\",\"9eaf4c53-3dbf-fcda-fd62-0a7b2682ac03\",{\"uuid\":\"6492768f-2a1a-0139-2a5a-af6c200210f8\",\"isOpen\":true,\"children\":[\"6a04a94f-b682-b4ec-6f46-89134ba4bb1a\",\"0511aed9-00e4-dadf-a777-d5572b593baf\",\"a1cb5820-0374-7914-881a-c780d8340eb4\"]}]}]}]},\"eac3ad48-ae72-b3bc-8b95-018333dc3051\",\"255c4dc9-0053-0fed-0d04-3030dc27ee40\",\"15fe5bf8-7f4f-e9db-baa8-6822a6077ce7\",\"6997413b-4ced-3c9b-d695-66afa5722357\",\"6d49082a-c952-04f5-6a33-5cf53b88d970\",\"87ccc294-b6b9-e5b8-72dc-6cbcefb36497\",\"4225da25-6240-50c1-7d61-035b7cd5d4ef\",\"531af403-a2fa-98db-bb4e-16441ceab62e\",\"f81abf97-3003-da0c-7fa2-018be5cd9b8d\",{\"uuid\":\"5cdc1be0-a703-9397-813d-b0c2fe914a14\",\"isOpen\":true,\"children\":[\"75f4c75b-74ad-f2a8-ff6b-e18c7f4fdedf\",\"d4f3afd2-536b-fd5d-03b6-b453a765ab11\",\"57155ef8-9d6a-186e-0e88-41fe79b2d637\",\"ca75edbd-a5d8-27c4-cc93-71bbbb4a9711\",\"9eacb6d9-6979-c722-5a5a-7f3f755d162d\",\"467613ba-eae9-d8c5-c500-764d19b63bdb\",\"ecea20f4-a55d-fe20-ac80-243b297f225d\",\"34dfbab7-01ac-2496-8629-e92b0a014b99\",\"ee8190eb-8d0c-759e-fb79-bcfac04aaa59\",\"59a35e30-c471-c0d5-ff54-b604450c2cce\",\"a66a23f5-34f4-0e99-bfd4-9943ea997a8f\",\"74f23901-ff08-52ac-dc12-6f1ee74b946a\",{\"uuid\":\"06102678-1ae6-d632-30b0-6964f314e6bf\",\"isOpen\":true,\"children\":[\"6afb3580-9d2a-a800-44e0-7ad32af91987\",\"e885a9ed-6555-231f-5a14-7d4f6dfc8839\",\"5e4c7faf-af00-d46f-ef74-f7616ec8b21d\",{\"uuid\":\"977a05f5-98e7-7f45-0d7b-f345af018dd3\",\"isOpen\":true,\"children\":[\"179fd506-fced-8fc5-f228-b39dfd836eab\",\"dcc13d2a-e8b4-9cf2-fb6a-1222d2c42980\",\"9ff9fa0a-03b9-b05d-2344-8fc7bef11e46\",{\"uuid\":\"18d36cc2-46b6-f929-3624-07aef443f80d\",\"isOpen\":true,\"children\":[\"60ac24f4-f0d5-2709-8498-d5929693e212\",\"120acc01-548e-637c-0f2f-6fc565133c9a\",\"3698deba-f32c-bf34-b848-ba738866e0fc\"]}]}]},{\"uuid\":\"62421ffb-7685-a28e-c939-59b0d8123776\",\"isOpen\":true,\"children\":[\"9aaec269-21fa-fc94-f584-04cf13144345\",\"5500625f-17c2-64b1-406a-697250abf2ef\",\"31163dd6-e25e-d315-3db0-b755cb62a140\",{\"uuid\":\"167d2bb7-4626-6746-c72b-ba3c8dab3e2f\",\"isOpen\":true,\"children\":[\"1e8fb086-03d6-eabd-c193-db916605a3c7\",\"a9e565b5-91ac-43ec-b6e8-fb95376fe40f\",\"e7ef1199-ed3e-93ee-adad-e9230cad7d6c\",{\"uuid\":\"57123914-f464-2f02-3eb3-e1ac924f8ef2\",\"isOpen\":true,\"children\":[\"b2b3fdbe-0de5-84ae-df96-288f90b79d94\",\"91fa364d-41e6-5c82-2bfa-1cc1570fd514\",\"f6378b2a-8e1f-a751-beda-9e1f6feda543\"]}]}]},\"4b3e6c3f-287d-2368-a998-c33d81b96ee4\",\"c6a80580-5139-f0a6-9f30-9ade3c37c5dc\",{\"uuid\":\"2e3cc482-b167-33b8-1714-5c7b341d65b4\",\"isOpen\":true,\"children\":[\"8c485616-0002-04d6-6cf3-9d32de2c3d01\",{\"uuid\":\"7113ca1c-2ae3-a77e-c3ab-63794d1ba17f\",\"isOpen\":true,\"children\":[\"7b67db12-b5ce-3ac3-bdda-5016a85904b8\",\"91d99b1a-9f51-fadc-5f4a-8ec6ea9fb9ae\",\"2280ee81-b151-2526-efae-1722409b0821\",\"ea1adedd-fb2e-60ce-3ccd-145bac9176cb\",\"706e638e-8e86-fce7-c6e6-e2789cf10cc5\",\"bdc513a2-6aeb-d4c6-093c-9eebed04c782\"]}]},{\"uuid\":\"20832476-f83d-a3bf-a371-dfb6ddc221f1\",\"isOpen\":true,\"children\":[\"29e89ffb-c9f6-6591-85c4-4dc2d8d97531\",{\"uuid\":\"14f70d2a-4f4d-e8e6-fa68-cb95be5d9ff7\",\"isOpen\":true,\"children\":[\"448df43a-1136-018d-c5ee-785cea750b64\",\"50f40905-4d6d-7098-57ec-25bb1db92c9a\",\"4db14d3d-0f93-84c0-5625-5298d979eb41\",\"3d9603af-d62c-a46e-fbfb-abf81268a2d9\",\"32e01dc5-5dae-1c4c-b1be-35eb3a079515\",\"f7aa46f3-7631-0bc0-28bb-f612ea592aee\"]}]}]},\"b4336081-325e-dbfb-9cad-39c227e78c67\",\"1416e039-1c93-1f55-854c-2ca9f6651c99\",\"8fe284b0-23bf-a450-600b-44047d4f2b61\",\"064202fa-1ea8-2e3d-08f6-15645a4d4f5c\",{\"uuid\":\"2404264b-66f1-5dc1-5fe5-db0c33526a27\",\"isOpen\":true,\"children\":[\"c7fc4bf2-2bdb-6f57-e1d2-1b55fbadc572\",{\"uuid\":\"1a9595e0-ba95-9397-76c5-68d018b0975a\",\"isOpen\":true,\"children\":[\"d084c28f-189e-764b-538f-cf0537a5c344\",\"41d62fcf-9109-d04a-1897-b6d0024546f1\",\"5f6f0613-a341-4bc1-caa8-71c6a4d9f006\",{\"uuid\":\"f719d2bb-6c28-0ab6-a585-41f54792160c\",\"isOpen\":true,\"children\":[\"96cda888-fe8e-9317-9335-171c7a631c52\",\"b5702594-4dba-0c01-6343-02d2faac24e1\",\"013aa42b-83c2-5f5f-327d-8a74970cdb49\",{\"uuid\":\"b2fd5761-a84d-7020-38c7-2efcd6c39298\",\"isOpen\":true,\"children\":[\"8fbc89dc-96ae-1dfb-471a-f121b77bdba1\",\"d1867ca7-e2c9-2a22-d67d-6a3117fef579\",\"f3b7b5ac-f8dd-ee5a-2837-8b886bc89be3\"]}]}]}]},\"7ca2ade1-2f88-677d-8126-849c195f075c\",{\"uuid\":\"98b261b3-0a69-4a62-2229-0d1aee17c78e\",\"isOpen\":true,\"children\":[\"2a40ace8-91f0-0a80-b85c-f1bfea213a68\",\"6e9d44d7-1f0b-5134-4382-e916bf84baba\"]},\"033aa5bf-e96a-e214-6edd-35746a2e92b5\",\"b93cff5c-2a56-1aab-a8c7-ed4d7397794a\",\"aee73142-be32-3036-4fe2-ddeccf72a5b1\",\"d9e7e5c3-da02-8477-fd18-15eb3ec1609c\",\"ac4a4b83-622d-18a3-f3a4-a2a64a04a81a\",\"add2bd23-961a-d6ee-0953-9458433b0e7e\",\"29745a85-fe96-2a85-74b5-c4c68d141744\",\"5b094f76-a7f5-f24e-e320-c58b2ac2b435\",\"26d54259-68ec-56a5-7d1b-b9e1022233fb\",\"f4ec532a-b1be-af15-da29-2dbbe5c35892\",\"55a299fb-f460-6263-e740-75923a2d9d86\",\"8bfbd4a0-a8fa-aa56-046f-d97347ba5903\",\"e9d83ef5-2045-ab67-4a98-bed4ba14d518\",{\"uuid\":\"6f3eac45-0aee-fcf4-e283-e1fb93e876d6\",\"isOpen\":true,\"children\":[\"c2834cb3-01dd-0214-bc6c-5f5265af0476\",\"a3e39edd-509f-a6e6-2351-ff79b7a61f6c\",\"5dec9d98-097d-f3e9-9827-c2dcd146712f\",{\"uuid\":\"7c168fd7-cdf9-c904-82b2-464caba505cc\",\"isOpen\":true,\"children\":[\"20c07f3a-08c5-782d-37e8-d229fcfa893a\",\"12f8aa93-1559-25fb-8b0c-6458b326b202\",\"a65ddbfa-a090-fa94-36f9-a83e90783a60\",\"11bb47a0-0738-66fb-7887-f3499e766f41\",\"faa9c325-4ca4-5292-9be5-eb74a175eadd\",{\"uuid\":\"842ad7bd-7015-fa76-ebca-314ed49a789f\",\"isOpen\":true,\"children\":[\"7779802d-844c-1e58-67ba-9d26d4d7aacb\",\"e0c86f4e-ff80-ead8-9b92-5246c36f8b34\",\"9e677dad-9f30-868d-f8a6-b4f1ca772402\",\"ee55ecef-75f5-6169-4093-ca093fe26167\",\"b462094a-f846-3cb1-75a7-4432dde1c9a3\",\"5a8b5a47-d25a-9f4f-c151-22ceffc086f7\",\"2873cfd9-8e30-f048-b028-8a08b6605032\",\"150732da-710a-804b-7ad6-0937f1647292\",\"fb92591b-ca3a-8a99-8628-b52142ad0610\",\"dd1b6bb9-1371-7468-c811-690929986466\"]}]}]},{\"uuid\":\"0266d8da-f639-7c87-40f6-834b24e671e2\",\"isOpen\":true,\"children\":[\"49960836-36d8-fde1-6010-119a680e1792\",\"be833fd0-a635-7db7-63ec-8b3555252f2c\",\"70921fe4-f2c5-95e6-5dab-9ba9df357552\",{\"uuid\":\"a44819f0-9e13-12f5-b270-88d133e31f46\",\"isOpen\":true,\"children\":[\"b098efed-8cd2-8935-e65b-ec57de1527a6\",\"0056ac3d-d6be-eb04-edd0-15cdce2e96a9\",\"cf7539d4-1cb6-0d62-d9d7-3a5537399b0e\",{\"uuid\":\"b6fc560a-2b3f-cb7b-2280-dfdae36d9d53\",\"isOpen\":true,\"children\":[\"71dd0d2e-1f35-9c20-f0e4-e7243ef62c59\",\"0ea452bb-ba2b-016e-f030-ec3360d55b63\",\"56e24b25-c478-848b-1b04-d7ce1e6f032c\",\"b36424db-8870-a556-61f4-ec7c1f63a964\",\"62b9158c-cca3-ecbd-e46c-3407f8d0306e\",\"f2c157b6-a525-ba83-2b2b-bcfa39f73d78\",\"ba2d481e-f120-fe09-5b95-35e6946f3ef5\",\"08b8a4dd-9817-f43b-7486-512f7efd2396\",\"5b39d357-c3a2-39d1-1c69-88c062c4ba3a\",\"376ba5a3-f0b4-f973-ed18-3feb313a350f\"]}]}]},{\"uuid\":\"d8e887a2-a16d-3dfc-b2e0-ba8aca6bb84f\",\"isOpen\":true,\"children\":[\"9afe6ef9-752d-2a94-3213-08adb08aa0df\"]},{\"uuid\":\"aa6e0a4a-d036-a286-5c6f-ada2c220032b\",\"isOpen\":true,\"children\":[\"4fa795c4-3f80-a25f-5dbf-b99637163aca\"]}]},{\"uuid\":\"0d53fe40-29a8-f10f-e40f-59194c70c775\",\"isOpen\":true,\"children\":[\"a0e67201-0de5-f611-feff-0dbad6b8e941\"]}],\"textures\":[{\"name\":\"blue_wizard.png\",\"path\":\"\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"1\",\"group\":\"\",\"width\":128,\"height\":128,\"uv_width\":128,\"uv_height\":128,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"9ee971d3-c578-3c74-5c3e-3f10b011a254\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAQAElEQVR4AexdB2BUxdb+Zkt6QhJ6NUgVUKoKAoI8paooFhTrExUQGwKiIpanok9EVESFh4og2HgKKAh28NEhiEDoJISSkGST3exutu/858zmLpuQIPoDJuwu+905c+bM3MmZMzNnZu5edDjJZ9gXO2RFmL2tSD75S47815pj8qlVuTLzSE4ZrP10vFzH+KQ0JJp5w2d/LwdPW6xw41tfywc++kneOHtlEOnbMyRjy46dMp2wYdc+uWLXQbkyY688cuSI7DzguiDKV3vlzgNy/YHDctW8RxS6XXeLZAyevkwyNPltu/fKbbv2lNY3VxbYHYr+fdETst99j0hNLlzCkxpAZUoodvngl35YHF7EG8QJYkUWO8wmO3xOPzwlPtgKnSgwWWGyOYKyHp8fOWZbMK4RBoMRvuRaMCYmQ+f3oV6jRjjq1SE1NVUTqTD0S2o7Uf7PKVu3vXv3RjsMsYDOAJvNjqKiQmRnZsKat7/CMsOBWV5jp/Q3+0jK7WeFS+h1gmJlvy6PF26fDyUuN+xON0zFVtjsTkhupBBRP5cREu/Uro24qHUL0aVhbXFhk/qiS5tWYtavW/D977uxZfPmEEmg7yuz5MBpcxWYlkJACFFGRieNWPzQQDG2SypWrV0nR9w42vnEkHvw7C/ZGPvlj3ji618VLurYQ2Vc8Z83VVimkHM88pcMQA+BIrsXDpcfTv+JOrv+wVni2odniqtGThf9H3hbDH1ijrj18Tli8ZjBJ+CP9DuiaRImXdER9Zs2Q51arVG7TkvUanQefC4XvE6Hgt/jQbzPDWfOQXS8oIlCTHwCEgwN8I9bHpDl7+Fx2IMsPxkqT2HfzamFHjfdeYJsUPAcJf6SAVD7o9gjUUBTgbNcL/6zGlx4by+hIVTHh6yOXhy/qHVL0bRhfQW32wGnxwxXiR3RSTVgiImFnhCVkAi/1wt/jdqcRcFpt6HYl4USZyFaO9fjAttmykv5S5w0LdlgWvWtQtHq75R8atzJpxgldA5edDw0VobirAxUBG7kGnEGRBsNKHYL1Lb8BK2RzqfGOh16apwYu7J8OSWuY9T4xXBR42q931c6CngcNugs+cEsaxd9KjT4dQZ4/W7EGGNh8EXh5faHMWt0d8wY0Q2bv13EdUdhSSHik1OC+cOF+EsjwNjONUX3JomoHR+NeL0Pn/oG/L/1Zd00VTKoILavMsjb/KHMWzdbLn7yKrz24nN487UpePHmq/HKNT0x885rFRo3OQ8t83+BI08Ppymaijn+fR7DMEHchrTXF6Dth1+g48CxovPV40XX6yaITs27gMEjgN1cdDxTmFC6v/p3xgg/DbsecvK8KHb6/moxp5TPTXO2w2yCs7gIeloh6KUPBodF5bVYAiFHZJSA12eDx2UBz+uMrRm75Mjax/BcGy8++Ect8W6vlDJOS/q+TWCE2wjA+mL8ZQPwqblfwk8OAXvgXNip4Pedu+WWzMPyQPYRuf/QUbl2/xG5JWO31PIWuTw4VuKCyeHGMVpOOt0eeNx26ONiab6PQjvLCrSyrkJz+zq0kJvRoU1rNYQXFZmR1/QGFHmNsDj1KFz7EfNF+zatFc5v1KBMw2v369O+Lxgcj4wArIVTRLFHwOYTiDNIJEaduh0ZyW8wGo3w0xrf6ZOIkx5aluuDd42i/QWQV+/0+qCLMsLh9aOo/nU43ORmHK1/LQQtOwXdT0QbFK1l9Bmj4NIboePBiJraT1Vi32bM1xly6Px0+fDSPfLQzzPlgW9fkkNm/SInLU5XRvfT1u/Em0s/1ooJu1B3ebeuojJMH9YDFYGHVlDPjzfqEaPXQUeNglP8XNC8mWjXqK5ontZEtE1rKNo3TxPtWzanJgsUEB8TLerWSBANE2PFI9/sxeil+/Dadi/e2mjCtC12xF8wTMS3u10kdrhTJLS9XdBHZdR7XDA47cg2ZyPbcgiFhYcU/1B+ES0ZHWRTHoCMwm9IVHy/x6lCvlzYqI5gH6DEenw6YX44gFTy1/7MuCjA5ZNqH8DNve4UirHvXSzNO7+S5owvpfn3hbLo9y+keceX0k78xC5jBeMUiikjsvb3DND2MTrRJhKjTCJFPHYLGAU0ReQcy0LWoX3EBTLzzFi9YZNcsyldskEX0ipAJYTZ5S8bwPC2KeK9q+qL1/vUFw+0L+tYVaZDDw3nQmeAdHvhK7ZD2tyQtBHD8rRLWIOg1v4cZyy4qa0oj/dfuE1+Ovmf8jMC01FCjeQsrvC2tR/etgWgGKUXH00rxTYbiui+X97fWyx4YKDofkkXcVmXToKXsKnhug9Qqp+zEiRfcL2o0eoakdLhZlGzx10i9bLbRMpFN4n4FoNpJBcWIcQJa//yFeM9fxPt45vsJWCam9/v52tA0k093m2zgMGcN4Z0BKNlWiNcec90cdOj7wvmM7jnkwMoGYXhOgJcPeULWRkeWvA/ROd8j6ij3ynElNLGw8txbMv841pnbZ4l3PfMAjF68ufigZc+E0xHx8aiZs1Uteyb+vgQ2bf4KzBWPH2H8m0SafUA+sTpgWveWygHTf9E1Xvsoi0qJCcQDBIJy+8fTgF6nR4Gg0FBp9fT4Y8ORvK4abhGnrlYHjZbZWaRVe4ptMkNu/bL9O075fpdB+SafYflhp375KaMPXL+lLtPQPtb75WtBt2ooB05T16VqRoFf+ZTYQ5/sISDXj3q1a4JK40YzNQZjRwg11KiQur9ahk4btE4oRjn+KX8n6dzO0vgoe1UDe4Qmnlu8q49bjfcLie8tN/u9Xnh9rjBy7jY2BjEUQ+0CQP8ZCjcFnpSsEEvEO+xQ+h05e8XjGsHg1Ijgil/jmjXvKmaw3keH/vqlyKARcHGrAUv7F6J/bmFZQq2FRaoeOLdo3kECMorZhhddLyn7lFGwIZQAm8IzfzGl94jGl36T9G463DR8OK7RKNLiKZ4gy53CSF0cNK5fgypr5ZRwOD3klPnBVkLfOTc6WmppSNeRfoUlIf5QpQSFBFC4OaPN8qhIbh53kZ5bN0smbtqpjy2/n22MZI89a/pWA6yD+w/IcNFafXx6FdbT+CHG6PyLnoKmsihLWCbxwcvGYGZ1oKd6fy+A+28dW7bWvCSTMNt4+eI8qio4/uJ6aNtX28IfLS2txUdgcOaBycd+PR9eaYMxSlUE15DtBJrmNQAneo1UbSfDNPnciian1JSRBhe/l8GkGIAYkmRifAhjvBn9OeymhGTlIykhudBW+pNvLypMMTGozziUupD0MaDz1Nc0S14VNBwQjob4SWtmwkPGZXJVIjth/KUzME8C74Ze33w+QTFDMOL7rmrO4DxzC29Vcg045N+Ap8M0uNkn1px0aJxSqJolJokGqQkHR/LT5YpJI06fEgsQHqpl3upsTR4KF6/60iRdsUEcf5Vk8R3T44og0CuE6/dSp8J1FI8JTa8MKgdXhvSWa0Y5j8wiJLUJgIbD9FAXGINFYbTRechx47hIyePduXBNOPTqEF4t6Q3Xlzxu7x5zho5/Msdcmb6UVYWAxV9pq7cJd9cmyVnbj4i39pwWI5bvlcO+2i1egiUh1kNI+f8qMqgKf+EYuZ0TMecizZjdtt9+KDDLnxE8ROEyjLY8BTW7DsiV61bL/kMoKwI8O6dlyM6OlodELHDKKVfZB45ysagREevtmUqIswuOg/tkHGD+6CDj5w6Dx3CMC8hxoBitx/bj1oghEB8tBE16ADmZPrZkElD7LESCFoReOg0xkeOoI/8g/J5copsilXRCPCRGIZpeT0hhRXP7G2Dt9zDlKx2ufyx12QoND6HdrsVutoNmawUmSFPMLMhMJjHGUrC8Swg2boZ8ab1iM9ejqRjv6D/Fb1E/z5XCBtt1xZ7dTAaDHBDD5ML4AdB+748E4SgI5ZrKpSsQAZ3a+6KPtqZizMKOMhB9PmPr8kR8ulw6wS0u3Ykul17L576Yb98fMU+eednW+SDneuLZ665RKR2GyPeHHa5mHhZfS4ymNNLU0Ionv9hr3z91yz53Pd75VXtW4sezRqpDaC1pU8EaRm5oRkWuxNFm+bBum4uNn75iuw1cYpkPsudy1MA/30VQeemhvZTI3mosbzU+zUhm8uHJCNovS/BZz01iHbTXr6WroVFbol42g/Q4m46IGJDgfTD4XJDUjev3bQl4hs1LwMvzfMuuw3smOUVFCK/oAAOm1UrptIwrc9NCMXBvCLsPmpCdr650jyhCV4anYwQ8BkIfIpJ9QtNDzdaxw0khKCdPl2Zv12SURRYbHCTjxAjvbA5XSgqcZeR4YibhvgSSmOa4eJHwtW04oOTjEoIoZ7k8ViLwHAXF6rwv/f1Foz5d3UXs2+5WHx4W1fxxfDLubfzQFIeXHSFKHZ6YKJeXex0Y/OBQ3L1vkNyA/kCFQoTk29w4eDxouPgx9UjYe6MdLDDSEkIyykgmuZ2vV5HPRVkBHrWg4KLGtZFjc8Gwgw3jQ7FTi+TZRAt/PBwc5VyWd7l8cNNIwEQSLCa8uG2mBQ8ZABMl4qf1iA12oDk2CiUSB6zKi66c/MmbAMq8cCv78gF43phwYxpKp6+fHEwTTHC4KLbVH8oZvgG4V15NWb5B+HpH/dL3pPfb3JCHx2HqBq1cEmLhmjXoAb2FzpRq3kPhWbteoOXZK0b1hUtGtZT27Gf332Z+PTWDuLpXk1Fnxp2TOgQj6e61sazV7Uog6T6jTHu2z1ywnf75YNLMuSwBZvU7h/7EQTlmXO4/0gO6CMIlX6n9DsfGlgoDn6cF2dk8g+xeVM6MrZvUyuDtXf1TgvLn4aV0LyreiX1TFuRCUZPCVzkTfNQrcHoc2DbgSPQ4hzm5wU2VP5QyxUI8NxvNltgo/N5R4kdHgL7BOVFdeUZFOdNo9wXx4AxspEVh7IPVggSPeH7wpIN8sn/rg8MS5R605jZIvXAOqx9vJNM37fppIZG4ufkV5efbwJKLGAjcNEw7SMv20nLIY4HQQ3kNJuUTJBHBmMtfZRbC0M11Kn0CZ2Kws9prp9968VixpD24n2a/7+4t5d4bWCr0OyKjqLevHpPlvw244B8dMmOzDv+uyNz1OIdwfV6R+NqaGhlXokWpuVoUbwML/6ytcxKRVu17DlaiAM5+diy9DW5dfnrcsf3b8hihw8MOhUMGoa6eZhcdE8O7ITZ9/XDO4TFYwaLf13fTfz75p4idNieO+dd7PphXhCZ67/G0Z2rzqiK/OSdp6am0lZxE1qCekFbFORSCOionzo9DtgKbQj9+GQJQHsXMioKtVv0zGrVtkcWT1GhmDeyv/jswatFx0HjRPv+j4noKCOa3XA/+k3fKjKyt2HFf96k0kNLPfdpnSRH7Rh59wY69q3szxXl1FI+ruW7b8lu+cwvh+TEn7PlhB8OylHf7JW3Ldwh71u8K/gzc022fBgfHw8NebTY8MYmYsPatXj3p3X4cftelHhAB7uA3Qs4zU54pQeJpc8Rcthg4POi/pVPVGZ/twAAEABJREFUiwZXPC2SjBJRFc0fdNPNu/YFe3rzXqNF896jaUcwRzao2RDnog9Af/JJvzo3efv8XL8QZVuZ9wa0nNQZNZLMhUwmqMIgWxEuWvbl25wocXnhoWWknzOSbGL08dWFEvyDi5+8eKvbg7ptO+G+rhdh/D8uRtayWWmFy2el5S75MC25dgqSa6Yg9EO3EnuXPaSO/RIMALf/sArebyBLn1HoPOD4uwa0jaDQ8sKF1nHTpAjvCX+vh9byGjPUNgTon9BSyoZzb2gj3r26hZjW/3yRnZ0NPot3WQrKCoXErnlvodQw/NPvoKHb+Y1El/qponXdZNGxdoJo06COcJMf4ib/xO06PvRb1rwvTT/NlJb0BWzDssXA6S4uvnZCdFpSrCGNHcby0Lmd4M83D16KpY/0xNsXZisnMDk+TH8cWic5SWhgxdz/1Y7MR8jhmncoKmvqLpmVkpKCRa+9gN1LFwrGpue74X9jL8DGWVMrMQMupSxOWbBstjIxJ+0aMhwOM20wOWAtsIJ/IKLTSzgLA496axnWZ2Ri656DGDxtsSwP25aZWDXvEbm3MBePWftj+sXzEH1nGP8wRFOaFgoiomhYYKcrmYZSilb65bm3oP4wMJiuTHDi5U3w2lXnqXN/lsm3WGUB4euRNwrGtJv64b2hfcE0g2XKY23p3j6HzmInfDRqJXX5p0jpNVLUvfIZrnYwi1c91eQAO7Xl4aDpSYOWwWYtDtvHwniq1PSgQtrEg4vm7TynYKda8Sq7SCmTK0sL5fv8PsTHRAVZeoMRhf6QW9OcH0MeeVDgDwie/xmViWmNHpo+amWRZCxq8SIY2/ccReeNk3DXqmFwLBiBein1Zah8uNC6gZM/lgNe+lgOnDxfDnhxrszd/Vtay0Y10ga1SUmbenXbpn+gleAY8Y9/vS0HvjEvCEveTmgvfjDQ0kyoZy8CauUHR1NijxtEnfgYMroynTgg+AdX66ap6iflHGqi176xRF77xtcK07YUylfW58rnftov6xtKEC3deOfy5Oh7imdj7KtfqQdIk2L1YPAqICyXgbTXAkHjvYG2fQEBr8uJzYfdyDYHHEMrefYeOt5FBR8hRBkPz0PztNdRAobfF8jP2QSdKeQ6mQrAyw4mrT4CMagfmbIXp8UrCvvfPV72/+c49SYv3gdgVCTnslngspkVxnRMFU9cWk8816eZeKZ7Q/FG77qCVjf3Wu3KV1TZa8frweBIWL4iJrlVRyS3aI+EJi2R0qoTWrbvgii9HnpaH8z5LUeuOOLHc+uKwEuq6esPyfkYqtbfrDCGNkL8+MyD4vuJo0T/3gNwdd+r8eOkB4Nd2kcGUFPQQp4zEPin317iEam+kvYgomiaUBG68L0YRAa/fpuAy20Grwac/KoYAvsdGjTBx+qtgwaNZ1r5vizcNEdVVa/XvxNDh0a/ZeySjObPbhRFQz5C49GTNPFzIjzVP0JXXrDQZ0CNKAG/14P9Fomi450Fh0sEfs93l8kSbOVS7j7Kk20u+xBIzaQEMEhEpm/PkLbcw8g5dJAPfdTDJNyoBTZ7ME5yJ3yt3myoVYDdCl6yMUKFeBpghPI02i2L4HZbVJTvf9n1T4gObVorMNMTFYf4xs2z4sPxFTHl18kcj9V5EaOXyKd98hL2CllLBBvRBY6yjUvsMl8pJYrdJ5fhDKH7DIUmE0weib22ssbFchqSatcFIzk58Fg38/PS50gG09pI0O/eGUID8xlLf92Cb37cwCTMMII/32zfL9fvz1Yvkxw74QlmwR7ur4hpdHEPyXj/8TsxvnsadW4/HZQ4lXL4oiNvvU68gclK4fa4kRp9wsByojwZisY0RkUhnp0RfdmyeWuWMfDWp9XwbXQlQe+NVfsATjoP8NI0oJVxqmFCjSQlmmTNhyvvqKJ1MmAUKhJml5O2VOtkgabJx731WrTRKnwetWfODdP/zgmqYUJ1lpZkRLQ47gByGsmi3/AxGHDnhODv+C/t1EGMmvQaRj01DeOnzsC9I0biyYdG48Z7R3GWMvAlWFXcE11MZuKB5gM4rXlw2QuVf8I+Q3lwplVr18mWVz6EeyfNJ4MGLmlcV3Rq3kU+eseDYIAc3zdefBLh+gkaQPn50+Z0y9EXN8SQFol49rKaSKxZF4UiEdMHtlCKZIXJaCdGzF2i0PflwC92JvVuKp6/omlQhuX4vX4up42cODtHg9A7E2DQkVUFOQGC78UIxAJXR2JjMOx1aiLh2lv4xU7i/KueFY17PnbCOwR4GmMEcp78yr3fbDafXOgcTg0awODhK9DSHReEpbgY2bn51D/o8IeOVvgYlpbMp6yKPu37qt/dcwYXLQ/5IROHs8yqEX463/OCjnFZKAReOkjiqGfDTjB4fe6lc4BQcPqpQHv9TagsO5AaVi+eq4yVLyXWgKMYKnuu00EDKP+HNqxTSzRrWE+0bVhHtCb4abDno1iW4wbRwG/rDAWnlwdv32oITZs3Mg2f3tMaX9zfgV/YKDZ/u0jBoCtbLR6dVgxPRCiYxwgtL5TOPJIjGQeP5qqQ6d633SsZhwsOQgPnuarnZWLA4i1pTIcbgppmpyoUFSmiIufe63QgFFo+rSwtXlHod5ggdRLCGKxGGbFgGdF11N6DQ1cTBWY7Zn2+Gtv35TKP5YMNzI3M+PfPu+TcPQ71qNjBg1mY8NNhzNhSSP6CTYEzheL6Wb/I1ITUUFa1pf9sxYOa1xwrh6tEOVnbj+bLjByTTM8tklzozD61BIPp1dt2yjWZR+WmnXskv63zleuuCL61c+eRfLn3aJ7Uygt1zDivhkeX7JDv6m/AqINdMGJvF2hyN36wWpqydoPBZaj6mAPLQz85oJxfJ3jAZqpibD9kwrYDR4KJHocdJnMx1pYeKO05slv8/Pt67MjaoWS+ur+3KLQVKjrcLkED4IciGVv2bxYcemk552NFM8ppJUZIxDit0NF2r6BtZG9CspLwCj149DbQcvH1ST0w48UrFL+ii1564XK51ehRUTrzOP87L/WBwaDjKKyWgLNmhF7FK7sUmwrU84va/M8N/OHt3ZTVrJg9WjK2fzURl17QFbwi4M2hsB8Byiuzw3kNxYX1UkWnurQWLJeoNOnzocRuR8e2rWlpVYeXd+CGj/J7scvqwsGcgMM3vksSHu9SI3gUzEXtoNFlRI/zcU+Xeui2a6o6leOTOfbcF97TXTAeviDQyEUWO3TGAF0jMQVxcQ3w4L8/F91umcLVUOAnehiFZjOO0T5/8cZlsK/9GVfe9JgavfieGrRNIg7Z0Bmd2rVRI0B6OP4uQFNMZWHu/2bJvE0fnqBIlq+RUsm8qQs0GGdKoMaz6owsHkSJx0vTjAs5NgfxWIpR8e6h3eGE22MiOcDn9cNRpH4roOLlL/x2Ua+zBG67A26ayrwuByy//Ue+9ONeefuCzXLrxM4K2gqFw9AyOvUfzBUJZZ3zdGBsPcmf6dX7ablWVoB7DOPCls1UDyRFgqFJvbNsCxbG3IyXc3vCrzdSY7gw5n+FmQMXHsi8+7uCzIvPqy8uql9L9GzWiI5k+ViWsUiMX1ucOXRJduaon02ZU9bnYTLlnyuvg8EYpYrW0yGOISFBnd3z+T3dUy01mWa4bFa4aSmXUCcFsXViMfGFR7Df0B27j+TDTmkI+by/7CMV4zK++naFVJEwvPyhAXhop02QCfz21aty+5f/llsWvlypsngYZvA7+jS0ql9b/KNlE1EnRofmtKtYIy7QmBXpmt2NenE6xNGqQMvvtlswx301Xv91v5xR3BtTzX0qyqp4fbp3EwwVoUuUoAt9vbE1EJtSm6iy35+2ficYzI34AKyFCtD0yqdE3S7DBQ/QPmohr65UqxXIaqz/3NwK/J6+/w45bl9PdklOe+vKRmlv9kji9TYb0QnLt1GN7WlvkszUHilpv7xwL9ZNeZDLwbrdh5B+IBcZWUdxxFSMU/00qVkD/ExjRfJFRUX8ilmuB64f0E/5ABXJneu84y1Ef2kmbZ5QUOG30/WPo/31j4tal96l/gOmUCHuRQyNl+rdq5HB8N1nh2Le87fji8n3YP4Ld+CTl+7C/XMWqW1kbTuZQ8rAFiZcLpewWCyKdppNsBYWqB+XuiyFyC3KEYxXP3sH0z5/B6t2/IY129Mpa+A78/knwQjEjl93HXOCwZxxtz0KBtP89nAeAeKq8Sti+O/4KyhjAKEFDJ2/WTJCeX+WdvujwEbFGPLoLFz+yHtodu04tLhjCm6d+JF6gOTjUbeCof3PHyzLCL3X4jGDxdh+bfH0dZ3Vg55aWj4dT5t8Ak5JCHnCSNJJI4OnI4Ymr4VsrBqYx+8ODtt9gI179ssN2zPUUMjK0OBzOcGY/cJtkqHx/0yoUxNHIAc3iE0G7M3wFzZdYmks4DICpQWuLVMS1atrnl2agYnLj486/9lmxnu/mQNCdOXlJWO2pR9mlvREy4atpAZKDk4FYXkWYPIAvM/PighFVGIyGJ6QR7dC0yuieShlFBpaqGQDHQuPXbobOTk52HfoKJK2fYQ6OctRuOEDrPpojJy6qSBz1i53Vp3kJPXzcu6tjDf36rImbyoK/giUC7v4gubikvMbkxlwLAB+SWW8IbDkZM5lbXrKy5r1kiazFe7AD0Ak8RkUAHZPDnx2P3gnUAOfJ/CKZl7MRoTlFNC/bTPR9cI2ZRTL2vJ7yTKIGPX8Z3yWTtSpf1/a5MOwOavloI8D27FOlwtuOjPg9wV5eQfQUkI7gLQXUPaxgZAbCPA/Zgz/eI3815INsseNdwQbst99j8iBt06SI8Y+gZEE67IlyF32HmKMsYgyRqHPD/9E16W3c3ZBF3EFHQIxdAYDBox9ipesavnIS8DELmNZJix/GEq6oXNevhJ6XT9C3vHIeKKAvPQ50mO3UiMdP6p976mhKo29Z95iVZHSizZne0psYNgtZjjJWeNkN/GapqWhaaMGkC2GIK3v42LQmHdEnxHTxQ1pSHu6a62mLMdlMJi+s1ZBGq0aFN9UkIfMY2YYoqI4KQhfQrHiMd8bZwE/as7z+oc/zg3KcHkvr8ySTjqOZrAs7SYFTwIP06kgC/M+ABtVWE4BDy/do365G6NLgTE6hvWhIP0+eOkQhYZ0MBKoZ6mECi5F+jjF5UMXxsejBoiPuu9RPC5DEeUu17y3kFw11fnKpQCOkuOGdzu+wYf39RXG6NigHD8jkLlsOaKIx3xGdHyCSjebLZj0rRczNzaE2WLBntwidQgUm1mo5DfvP6KmgFW70rEyIx22jPlqZOHj7bDcCjb4Paqn+5Os1KOilRJz8y1gz/jCJrWzmMG92qgzMFkhavgdit+2dUssf3JomVYtH1eCpRc+OyglKw3cvkCSLmSuD3AAQTzmM7TdwqKQBzt5tKKlJLQPy786oKWK+jxemPKPIaHNbYL3ARQzDC+6rJx81dN1ND8yMnPz5EX9HlGNeCzQruBePfTZD156Lg0AAAVQSURBVBUvJSUFPLSu2BD4X7cGv71cPvblb+DwEJ3Vh+rwjduvVVHuiYoodzm84iV8Pm24GoEmrinEh4fjlMF1v6SLeGtLoXIC/XS4xNm4h3LYb8r7qscyXbPv/WBwGqNT8y5y3F0PcZIC7wqyIasIXaI694RDH0UUcMcVN+HhG0Yqmka4YJmKUY0u/9+q6swzJoPBwyqD5oFgmcXU+7JeHYe896cEeRoRTSMH01527ujQxUdw0YaN9mvc239tDu6BvZ6aIjm8e8bHYHS77hb1KPZTByYjJuNXdCr+Hf/KuBuZ81+AxXfcoz/sDIw4M1Y7MXBY4KlgzquPioGjaSoP4+qEcWbT5SjYukA1IJ/sMTYvvl/92FMb3q2bpsoln9zF1UWbejUFx9lfYDDzlRbb1IOuTIcbdE/MnwE+GGFljJ07HaDTNFYC9/Lxl6Sm/Xfl1+oBTOal034BL9MYvbteqt6ssXTc9YLfus3gDRuW45DBPRCQYCeQQ05bW/pQRrdX0wX/LJt/mcPgtDcvS2i69tPxknFTwuG0jdsC+xO++MBTwSwzppPyDfH9r2sY8taV56NW+2GC07jO/GDJD/ntwI4dD+/9X/5MsqfP9FvXdFcPnmjpnIfTnZYjOLJ7O0fDDrq0/OXw7FmIPT9Pl8f+vRrcuB8M+0D+PP5bdGxSX3DIWmHeb5PXMQmmGa9lJWaNXm3LDEWjS/6hhnElSJeVkx8XXCaH1s8/U0swYuOaS4dI3orlpRgP3eykMX/ytP2Y+PJWTLj9Ubz4w17lwNHwzkmK5uf32Ig8C8xg+oF90Vg2apkaAXa+uEWNCtFLfGBwpoezE4PpI597BabvZqm9f5732fOXB9Zg/FIHGrZqx+JhB92QZxfhoQX7FF4/PB/8A8nP4reCwdrgkHkfeH4Bg8/M3y5cjHsW3CPWvnBbGmNGd+q5LxynWb4i1L6iO4yXXKCG26/Xfyl41OE48/Nq+BQ/M9qiZJi//6u3VH2oHqJ8eZMLPlXyT2fOxMB3BwpuTKZJFm96V0CjWY7B+e3kIDKYZjkyLBEK5oUbdNtX/RhUAtP/+2JuMM7KYAUxTwMvlRicxiGjPK3Jlg+5LA2ch6HF+d5Ma6FGcxksx2F5sIx2/8ro8nm0OJcZARDYnI9oImw1EDGAsG36wB8eMYCAHqrd9XRVOGIAp0uT1bSciAFU04Y7XdWOGMDp0mQ1LSdiANW04U5XtSMGcLo0WU3LiRhANW2401XtiAGcLk1W03IiBlDNGu50VzdiAKdbo9WsvIgBVLMGO93VjRjA6dZoNSsvYgDVrMFOd3UjBnC6NVrNyosYQDVrsNNd3YgBnG6NVrPyIgZQTRrsTFUzYgBnSrPVpNyIAVSThjpT1YwYwJnSbDUpN2IA1aShzlQ1IwZwpjRbTcqNGEA1aagzVc2IAZwpzVaTciMGUMUb6kxXL2IAZ1rDVbz8iAFU8QY609WLGMCZ1nAVLz9iAFW8gc509SIGcKY1XMXLjxhAFW+gM129iAGcaQ1X8fIjBlBFG+hsVStiAGdL01X0PhEDqKINc7aqFTGAs6XpKnqfiAFU0YY5W9WKGMDZ0nQVvU/EAKpow5ytakUM4GxpuoreJ2IAVaxhznZ1IgZwtjVexe4XMYAq1iBnuzoRAzjbGq9i94sYQBVrkLNdnYgBnG2NV7H7RQygijXI2a5OxADOtsar2P0iBlBFGuTvqkbEAP4uzVeR+0YMoIo0xN9VjYgB/F2aryL3jRhAFWmIv6saEQP4uzRfRe4bMYAq0hB/VzUiBvB3ab6K3DdiAH9zQ/zdt/8/AAAA//8FxbxzAAAABklEQVQDAELM3R7RUxLNAAAAAElFTkSuQmCC\"},{\"name\":\"blue_wizard_circle.png\",\"path\":\"\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"2\",\"group\":\"\",\"width\":32,\"height\":32,\"uv_width\":32,\"uv_height\":32,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"081651c7-604c-d7b8-f059-ab2f3d57f3c3\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAE+0lEQVR4AcRXS4hcVRA9dwYhYiQKIXa7G9CFMRFdOLiRCKLS6kCQsQPuDAQU7UGcEFwM5AV6NflApiMGl3GhToKM+GlQBIMbGVDHD3EjcdmdEMhoIgZC+uac6luvX2ZePtNZpHn16lbdU6fq3lfv0yO4w7/bLmD+/xhvZw3DF9CNY57c9Nn40DCFDF8AMK6E9btDkEYPT5pe42n4Airh0/oyDmr11IdQCR+vMbfB11ZAjCM4E59DJ36Abvxl5l5M/3YFoH5Xtvk1L5zR81Qc01x5XLcArUxC4g8hkk58EV38PnMPvmHCN2bW43Hw11zfvwSyzc954Rj3ssWdwVHxSAhfdVy3AEfOb8AuEi6R/CvKZiWU/HAZkIhYWiK/RDgW9AXjfrV4JyvR5QXEOFpfxjHh0xZv1Vjk0tvO92+9k5fQlu3a/Y5jIVsUL4zxkVfjopQXEMIVVLCTQacEFuFjoxpROrHHhJHSoLWYEizKpkT2QY9+CK84jcnzp/ggXjkKUl6AAF28wBVs1lDbXNC67aayBfwhn0uyp2iHFXg16SMgH+dWHeUFRHY7sF9orSK/1+WIOJySybpGzM95dypO8cmetaZMhqvyAs7iWa0+D+a2cxtRP8/kn2MBg994GrpGxnnhhPfLIR7yPQryJnyuygvo4RVHeGPRboic2o5sO7bM34earrW0bJvgKeHUIyjEg0/LnJcwOwYFdOMY790d5g14SroQPJUVrnnG5FxRSw3IlZ6Qli2/4iRZH6+eGBSReLkzk8w1JlxeAO/X05RPOHmA97A9ZARgZ7epN2bb8UySPVxxiz40L6CBanjVNB3yE7OHYli6NqZ4DgHj7cT9xB1nrtNyWgHetWoaTk5rQtdNmnaNspcrdKnJz6QnmPyIxtJm0yCuRnHsXsbW3loHFPh2Kw+hUF4roOjglh7S5MzF/sOGdpuyjwlctCNgkknu1tvCSptNg7g2xbH7GNt+/xJXP+CzFxihUF4rwIwLeLj+D17jaqabF7GE9Nu2DlrxuWwB3yeZrS9z6znPpC0mP26atvzEzFIMS9e5FM8hd0G81bCbuB115pMzLwCbwl/wV2rEj5o8eX//RcPxXMbGo7YjY4NxlY10B0xKy5bfADxlffwceyDmPIkX1TBv+YgbFEAjP0bwmY9TsJ5+q4rgStq6A6QzFuUxWUpOOzDZIEeBl3N2DCbNTKdN+I4rOuV9QJLAZpK0EnkCYjENXEPzxLYoSq7CIR7xgbwJn6vyAkLoIeA9oRQs0TjJNTuRfKaUnIM5ih2Kk5ghPvGaMTiVF9B/F0zYtU0fHOpYSQrNixBGvmJy4STy6/ZLmIlbexcoub5iNvBDhAy6V6lsG6V5OUbY2YGih9G4+fiBKpuibbdF+co9ng+eXSDvyiIMnEj6ittk4L4FNthHzX9xWKaTpsYEE9Z0j0tr3v2O43U/onjNSYyX/Bq7rC7AZ6gZfAwVvI4Hwzske4lijakET98FSJRUWiK/RDg+SyZQDQ3FGw/5yo7SAnT9JAzeCf+KqYavaW9t/ofnmeAoEyyJUAmlZZuf88KhEr6U3+L1dcX/D8ZpzsGptIB82pO7Q9v3QPiWK3uTCZ5gwgNqMOqDss2veeE8Rnolj3xJblxAApWqTpzkvW4vFuppPpLrGOI3fAEBPymfdzlG8bPstcrwBVTC335NTetdUpL9Zq6rAAAA///1H1lOAAAABklEQVQDAOnDcf7i3dppAAAAAElFTkSuQmCC\"}],\"animations\":[{\"uuid\":\"544eb61c-2138-d7f8-b67b-a49ae5e1b313\",\"name\":\"idle\",\"loop\":\"loop\",\"override\":false,\"length\":3,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"10ee45d0-a3e0-f40b-990e-a0dac19721d1\":{\"name\":\"hi_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"c0820792-9e91-8bcb-812c-a6d07670799a\":{\"name\":\"left_hair_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"a7c9dbe8-f0b2-caf9-e2ae-fa40543a508a\":{\"name\":\"right_hair_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"8ee82b29-c9f1-ae08-9c3b-dcd5f8787db5\":{\"name\":\"left_hair_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"5a05f380-61c5-f164-885f-d3b2cc27acbe\":{\"name\":\"left_hair_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"aecdc16a-4f38-07a6-d0ed-48834f8a5836\":{\"name\":\"right_hair_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"47b7bce3-22c2-07fe-4b8a-06c75634a25e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6492768f-2a1a-0139-2a5a-af6c200210f8\":{\"name\":\"right_hair_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"469494ea-42f7-b718-90ba-46d9298a3b38\":{\"name\":\"body\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"math.sin(q.anim_time * 360 / 3)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"34a05409-ee8d-3e9f-f50b-8b55c57fec2c\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"math.cos(q.anim_time * 360 / 3) * 3\",\"z\":\"0\"}],\"uuid\":\"46e3668b-2c2b-c8b6-1d43-c71e6721980f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"0266d8da-f639-7c87-40f6-834b24e671e2\":{\"name\":\"right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"7eebee87-dcb3-0b51-d498-1713cfa99aae\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.477660105\",\"y\":\"1.1540833125\",\"z\":\"7.2178270917\"}],\"uuid\":\"57299846-a34c-0742-7559-a702c3f050d6\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.4106286282\",\"y\":\"2.306436698\",\"z\":\"9.4374532452\"}],\"uuid\":\"908b3b7d-339f-4d99-1ab9-663ccad1f5e6\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.477660105\",\"y\":\"1.1540833125\",\"z\":\"7.2178270917\"}],\"uuid\":\"68d54e19-c145-dc76-f64f-1462f039e2aa\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"1f19ac3b-ee96-d885-7fd7-0296243ee7ac\",\"time\":3,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a44819f0-9e13-12f5-b270-88d133e31f46\":{\"name\":\"right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-32.5\"}],\"uuid\":\"42132fd5-19d9-b65e-4d7f-7c92bf3f4a33\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-35\"}],\"uuid\":\"dee6c331-8b3d-5c9a-316c-bd1347743b9a\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-37.5\"}],\"uuid\":\"94e557ba-6111-5014-9cdf-7af9bb92f61b\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-35\"}],\"uuid\":\"6ff51896-550f-9292-421e-3b24889b5d00\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-32.5\"}],\"uuid\":\"25c6f1f3-e6bb-53e4-e302-38e47cebbf95\",\"time\":3,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"98b261b3-0a69-4a62-2229-0d1aee17c78e\":{\"name\":\"chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"math.sin(q.anim_time * 360 / 3) * 5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c112a855-cdbf-d5ed-66b3-5484fbcb17a1\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"b6fc560a-2b3f-cb7b-2280-dfdae36d9d53\":{\"name\":\"right_hand\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"5cdc1be0-a703-9397-813d-b0c2fe914a14\":{\"name\":\"under_body\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2 * math.sin(q.anim_time * 360 / 3)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e953d30a-0a38-99c3-2c25-907a1411d11b\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"20832476-f83d-a3bf-a371-dfb6ddc221f1\":{\"name\":\"right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-math.sin(q.anim_time * 180 / 3) * 30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"daaabeb3-26f4-e58c-89a7-84003868aa82\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"14f70d2a-4f4d-e8e6-fa68-cb95be5d9ff7\":{\"name\":\"right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"2e3cc482-b167-33b8-1714-5c7b341d65b4\":{\"name\":\"left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-math.sin(q.anim_time * 180 / 3) * 5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4834ce42-16d6-b7c2-0d2d-e8bb7cbb41f4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0.5\",\"z\":\"0\"}],\"uuid\":\"c5eeabec-6a71-526f-0ee1-6de37fa7e4f7\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7113ca1c-2ae3-a77e-c3ab-63794d1ba17f\":{\"name\":\"left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"6f3eac45-0aee-fcf4-e283-e1fb93e876d6\":{\"name\":\"left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4549832862\",\"y\":\"0\",\"z\":\"-14.7309816032\"}],\"uuid\":\"5dd3f31e-13ae-2442-f497-e1609dfe68b0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4479674937\",\"y\":\"-0.3242667753\",\"z\":\"-17.2098758475\"}],\"uuid\":\"82d37488-d6bc-4996-08a3-7423a30c6904\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4269321258\",\"y\":\"-0.647926641\",\"z\":\"-19.6889287011\"}],\"uuid\":\"0a0c9559-71b5-5d9c-4b63-433a8120ea5c\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4479674937\",\"y\":\"-0.3242667753\",\"z\":\"-17.2098758475\"}],\"uuid\":\"2f5d4523-dc0d-b2e4-35e2-86ab1c4ae846\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4549832862\",\"y\":\"0\",\"z\":\"-14.7309816032\"}],\"uuid\":\"359528d7-459b-1ca4-589b-c1db6c895a81\",\"time\":3,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7c168fd7-cdf9-c904-82b2-464caba505cc\":{\"name\":\"left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"32.5\"}],\"uuid\":\"96b97287-9155-d23e-878e-e5e7006d6db5\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"37.5\"}],\"uuid\":\"49d4221f-7853-b238-18c7-5f7b1de37c9d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"42.5\"}],\"uuid\":\"006a5cf4-48cf-6abc-1360-1871f70fc03a\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"37.5\"}],\"uuid\":\"515964d2-4bf0-cb3d-6a63-23cefc265ad3\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"32.5\"}],\"uuid\":\"83a3dadd-ceff-4caa-6900-785cacf03576\",\"time\":3,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"842ad7bd-7015-fa76-ebca-314ed49a789f\":{\"name\":\"left_hand\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"d88421b2-d101-85eb-db68-b5c6e4dfd4b4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"2404264b-66f1-5dc1-5fe5-db0c33526a27\":{\"name\":\"middle_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-math.sin(q.anim_time * 360 / 3) * 20 - 20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fd867778-ff7c-b045-d87a-db2b6ab78c90\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9595e0-ba95-9397-76c5-68d018b0975a\":{\"name\":\"middle_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-math.sin(q.anim_time * 360 / 3) * 20 - 20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9993f148-16e8-e729-7f55-fa31876a5a86\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"f719d2bb-6c28-0ab6-a585-41f54792160c\":{\"name\":\"middle_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-math.sin(q.anim_time * 360 / 3) * 20 - 20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"002d8cd7-15d4-a2e6-7f24-bc9cd6940c60\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2fd5761-a84d-7020-38c7-2efcd6c39298\":{\"name\":\"middle_cape_4\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-math.sin(q.anim_time * 360 / 3) * 20 - 20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ca5c5ff7-879f-a8d1-707e-9dab331e36ce\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9b4610f1-5d4d-9c62-d7db-b098f5a0a8e8\":{\"name\":\"eye\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0e9fa4e5-5b9d-e6be-951d-4bf3d72035d7\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"ff2953e8-2e48-4911-59fe-1c28121b9186\",\"time\":0.25,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fe28f4ef-e08c-75fe-4fd4-62e7fcd8795a\",\"time\":0.5,\"color\":-1,\"interpolation\":\"linear\"}]},\"62421ffb-7685-a28e-c939-59b0d8123776\":{\"name\":\"left_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-math.sin(q.anim_time * 360 / 3) * 30 - 30\"}],\"uuid\":\"0b8b70a0-f19a-9921-feee-a8c334e1a75f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"167d2bb7-4626-6746-c72b-ba3c8dab3e2f\":{\"name\":\"left_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-math.sin(q.anim_time * 360 / 3) * 30 - 30\"}],\"uuid\":\"5f8e1906-344a-24c3-a15b-306f5ac34fd8\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"57123914-f464-2f02-3eb3-e1ac924f8ef2\":{\"name\":\"left_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-math.sin(q.anim_time * 360 / 3) * 30 - 30\"}],\"uuid\":\"88df0856-42d2-8850-67ac-46e0dd92ba0e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"06102678-1ae6-d632-30b0-6964f314e6bf\":{\"name\":\"right_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"math.sin(q.anim_time * 360 / 3) * 30 + 30\"}],\"uuid\":\"aa01fc61-85cc-9124-2306-071af11bd9ee\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"977a05f5-98e7-7f45-0d7b-f345af018dd3\":{\"name\":\"right_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"math.sin(q.anim_time * 360 / 3) * 30 + 30\"}],\"uuid\":\"869d48de-7430-ebc2-8037-cd89a936a5bf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"18d36cc2-46b6-f929-3624-07aef443f80d\":{\"name\":\"right_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"math.sin(q.anim_time * 360 / 3) * 30 + 30\"}],\"uuid\":\"b1eeb626-97ff-6c85-56aa-8189d2ab5c44\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"185b2cad-6cc7-a818-34f9-59c70db1d853\":{\"name\":\"left_hair_ik\",\"type\":\"null_object\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"-5\",\"y\":\"8\",\"z\":\"6\"}],\"uuid\":\"542933d9-6504-5d5f-cb51-00959fd860c2\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"-5\",\"y\":\"10\",\"z\":\"6\"}],\"uuid\":\"379c4ef7-5e54-792b-4b7e-fcb6103b578d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"-3\",\"y\":\"1\",\"z\":\"2\"}],\"uuid\":\"b9b27118-1bd3-682d-5022-0bcd8ab5a9b4\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"-1\",\"y\":\"-2\",\"z\":\"1\"}],\"uuid\":\"2dbeeda9-aea8-f8f0-3449-b06960b17478\",\"time\":2.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"-5\",\"y\":\"8\",\"z\":\"6\"}],\"uuid\":\"26cd5f0f-cb71-0fa0-3bb6-ae77559c611d\",\"time\":3,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"ac61223e-e43c-ced3-6869-82c8e685056c\":{\"name\":\"right_hair_ik\",\"type\":\"null_object\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"1\",\"y\":\"1\",\"z\":\"5\"}],\"uuid\":\"9de7d69f-76cd-27d0-1c86-77128759988b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"3\",\"y\":\"2\",\"z\":\"3\"}],\"uuid\":\"9e690c75-0313-2524-872c-cea00543b8b3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"1\",\"y\":\"1\",\"z\":\"5\"}],\"uuid\":\"ffc799a8-5277-90d3-91a7-d2beac14db51\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"1\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"428276f0-6248-802c-8358-fa847b89ab42\",\"time\":2.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"1\",\"y\":\"1\",\"z\":\"5\"}],\"uuid\":\"277df8ed-0145-f839-0357-2491ef8cf7b5\",\"time\":3,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"0d53fe40-29a8-f10f-e40f-59194c70c775\":{\"name\":\"shadow\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"d8e887a2-a16d-3dfc-b2e0-ba8aca6bb84f\":{\"name\":\"hitbox\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"aa6e0a4a-d036-a286-5c6f-ada2c220032b\":{\"name\":\"circle\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"math.cos(q.anim_time * 360 / 3) * 1\",\"z\":\"0\"}],\"uuid\":\"4dec9ec2-3b21-2000-feff-e9c53de07d24\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0.875 + 0.125 * math.sin(q.anim_time * 360 / 3)\",\"y\":\"0.875 + 0.125 * math.sin(q.anim_time * 360 / 3)\",\"z\":\"0.875 + 0.125 * math.sin(q.anim_time * 360 / 3)\"}],\"uuid\":\"c1d68df8-c996-6634-e09c-76480b65e836\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"}]}}},{\"uuid\":\"e88eba94-3ea0-aba8-4245-5d5bed60f1c8\",\"name\":\"walk\",\"loop\":\"loop\",\"override\":false,\"length\":2,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"10ee45d0-a3e0-f40b-990e-a0dac19721d1\":{\"name\":\"hi_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15 - 5 * math.sin(q.anim_time * 180 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7c0324ce-6bd5-dd91-3612-591a1b214ae5\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"c0820792-9e91-8bcb-812c-a6d07670799a\":{\"name\":\"left_hair_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"a7c9dbe8-f0b2-caf9-e2ae-fa40543a508a\":{\"name\":\"right_hair_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"8ee82b29-c9f1-ae08-9c3b-dcd5f8787db5\":{\"name\":\"left_hair_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"5a05f380-61c5-f164-885f-d3b2cc27acbe\":{\"name\":\"left_hair_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"aecdc16a-4f38-07a6-d0ed-48834f8a5836\":{\"name\":\"right_hair_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"6492768f-2a1a-0139-2a5a-af6c200210f8\":{\"name\":\"right_hair_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"469494ea-42f7-b718-90ba-46d9298a3b38\":{\"name\":\"body\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15 - 5 * math.sin(q.anim_time * 180 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9ac12e1c-0e37-a0c9-473a-b227c47c78be\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1 * math.sin(q.anim_time * 360 / 2)\",\"z\":\"0\"}],\"uuid\":\"ed0fdf5c-780d-67ed-ea0d-291c8d6b1123\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"0266d8da-f639-7c87-40f6-834b24e671e2\":{\"name\":\"right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10 * math.sin(q.anim_time * 360 / 2)\",\"y\":\"0\",\"z\":\"15\"}],\"uuid\":\"51adc5fd-7a5b-3f97-c3c2-a983769ac904\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"a44819f0-9e13-12f5-b270-88d133e31f46\":{\"name\":\"right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"98b261b3-0a69-4a62-2229-0d1aee17c78e\":{\"name\":\"chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"3 * math.sin(q.anim_time * 360 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"97c430b4-43d2-7ffd-6e1d-d4772a776170\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"b6fc560a-2b3f-cb7b-2280-dfdae36d9d53\":{\"name\":\"right_hand\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"5cdc1be0-a703-9397-813d-b0c2fe914a14\":{\"name\":\"under_body\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"- 2.5 * math.sin(q.anim_time * 180 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"af272626-f3d3-41bc-4512-9d594e18bdff\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"20832476-f83d-a3bf-a371-dfb6ddc221f1\":{\"name\":\"right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30 * math.sin(q.anim_time * 180 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"deaaadec-5af8-9fe9-7e2b-74362b59a72b\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"14f70d2a-4f4d-e8e6-fa68-cb95be5d9ff7\":{\"name\":\"right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"2e3cc482-b167-33b8-1714-5c7b341d65b4\":{\"name\":\"left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10 * math.sin(q.anim_time * 180 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"43c7d13c-5d5a-fd15-897a-efdb2721bcb0\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"7113ca1c-2ae3-a77e-c3ab-63794d1ba17f\":{\"name\":\"left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"6f3eac45-0aee-fcf4-e283-e1fb93e876d6\":{\"name\":\"left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10 * math.sin(q.anim_time * 360 / 2)\",\"y\":\"0\",\"z\":\"-15\"}],\"uuid\":\"a5b267c1-1db0-75c9-a2e2-6d6a9a4f0b7f\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"7c168fd7-cdf9-c904-82b2-464caba505cc\":{\"name\":\"left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"842ad7bd-7015-fa76-ebca-314ed49a789f\":{\"name\":\"left_hand\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"2404264b-66f1-5dc1-5fe5-db0c33526a27\":{\"name\":\"middle_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10 - 10 * math.sin(q.anim_time * 360 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"94fcf1c0-9bb4-7f78-c9c6-bf6d0dda91f1\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"1a9595e0-ba95-9397-76c5-68d018b0975a\":{\"name\":\"middle_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10 - 10 * math.sin(q.anim_time * 360 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"75d4eda2-619b-293d-4c18-2d143db300ee\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"f719d2bb-6c28-0ab6-a585-41f54792160c\":{\"name\":\"middle_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10 - 10 * math.sin(q.anim_time * 360 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"780943fe-961e-bc48-6047-2497a48a4fe9\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"b2fd5761-a84d-7020-38c7-2efcd6c39298\":{\"name\":\"middle_cape_4\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10 - 10 * math.sin(q.anim_time * 360 / 2)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"eb78dade-923f-0da4-34f1-28ff5054f823\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9b4610f1-5d4d-9c62-d7db-b098f5a0a8e8\":{\"name\":\"eye\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b655a4a9-5def-c25f-2e66-7717e20ef344\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"2717040f-2a45-c08d-5a50-62cd85fea8be\",\"time\":0.25,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0a1a24a5-8a0f-f8b8-0e3f-c9633e130c67\",\"time\":0.5,\"color\":-1,\"interpolation\":\"linear\"}]},\"62421ffb-7685-a28e-c939-59b0d8123776\":{\"name\":\"left_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"5 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"4b431b84-fe75-d364-d695-8027690aa95e\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"167d2bb7-4626-6746-c72b-ba3c8dab3e2f\":{\"name\":\"left_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"5 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"7ca65143-6b80-f5db-aec6-b5a468f90a36\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"57123914-f464-2f02-3eb3-e1ac924f8ef2\":{\"name\":\"left_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"5 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"b1dfdaa8-4bb8-c1a2-c775-278d5f27d440\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"06102678-1ae6-d632-30b0-6964f314e6bf\":{\"name\":\"right_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-5 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"414a2d73-47b1-628c-8d42-55278090393e\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"977a05f5-98e7-7f45-0d7b-f345af018dd3\":{\"name\":\"right_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-5 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"79642c40-f278-f686-8f4c-32e8680c1cd1\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"18d36cc2-46b6-f929-3624-07aef443f80d\":{\"name\":\"right_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-5 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"b450bfee-2948-10c1-7a1f-b0ef0c5164b6\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"0d53fe40-29a8-f10f-e40f-59194c70c775\":{\"name\":\"shadow\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"185b2cad-6cc7-a818-34f9-59c70db1d853\":{\"name\":\"left_hair_ik\",\"type\":\"null_object\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"5\",\"z\":\"8 + 3 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"18e2972b-b171-d1a6-3678-d6f458aa2b4c\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"ac61223e-e43c-ced3-6869-82c8e685056c\":{\"name\":\"right_hair_ik\",\"type\":\"null_object\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"3\",\"z\":\"5 + 3 * math.sin(q.anim_time * 360 / 2)\"}],\"uuid\":\"80187124-3b99-ac77-f79b-5b2fbb23f4c5\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"d8e887a2-a16d-3dfc-b2e0-ba8aca6bb84f\":{\"name\":\"hitbox\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"aa6e0a4a-d036-a286-5c6f-ada2c220032b\":{\"name\":\"circle\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"math.cos(q.anim_time * 360 / 2) * 1\",\"z\":\"0\"}],\"uuid\":\"62fd1ca1-47f1-3644-d09d-34e935ee21bd\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0.875 + 0.125 * math.sin(q.anim_time * 360 / 2) \",\"y\":\"0.875 + 0.125 * math.sin(q.anim_time * 360 / 2) \",\"z\":\"0.875 + 0.125 * math.sin(q.anim_time * 360 / 2) \"}],\"uuid\":\"f696e54d-a5f8-8309-261e-06286f89a356\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"}]}}},{\"uuid\":\"bd0a18b7-d804-edb7-7d30-590e1f252148\",\"name\":\"death\",\"loop\":\"once\",\"override\":false,\"length\":1.25,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"10ee45d0-a3e0-f40b-990e-a0dac19721d1\":{\"name\":\"hi_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a16f4d44-6756-5a48-4c23-5262260725ef\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f83ad7a7-55fd-79b7-f1ac-339cbdf6060c\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"695c990b-d178-58e8-379e-40e86db015b1\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b7ab6b9c-2fdf-efd1-35e3-d01d47d83aa7\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"52.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ffc95691-fca5-2e1f-c4cd-bc6f1cd2aea9\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a7ffee60-152c-2236-a83c-bbdafee5bb78\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a727b42c-3777-e465-5ff3-30cd4a3bca04\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c0820792-9e91-8bcb-812c-a6d07670799a\":{\"name\":\"left_hair_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"a7c9dbe8-f0b2-caf9-e2ae-fa40543a508a\":{\"name\":\"right_hair_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"8ee82b29-c9f1-ae08-9c3b-dcd5f8787db5\":{\"name\":\"left_hair_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"5a05f380-61c5-f164-885f-d3b2cc27acbe\":{\"name\":\"left_hair_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"aecdc16a-4f38-07a6-d0ed-48834f8a5836\":{\"name\":\"right_hair_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"6492768f-2a1a-0139-2a5a-af6c200210f8\":{\"name\":\"right_hair_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"469494ea-42f7-b718-90ba-46d9298a3b38\":{\"name\":\"body\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.1754392272\",\"y\":\"14.9415876115\",\"z\":\"1.3378029844\"}],\"uuid\":\"3db9ca7d-a189-2dee-10ee-87384d5fa038\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1d8754eb-447e-2377-79d7-6b302a1fefa7\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9764bf92-4968-3692-7d8e-7d4c8ceb3d46\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-65\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"405f6707-0c30-df63-06df-432413b007ec\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-90\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ae93e6f5-d158-e85c-4e43-66b2a36a8a68\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-9\",\"z\":\"0\"}],\"uuid\":\"3062ce3e-061c-b4f8-759d-e4d9854cf4e9\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b7ab2662-8f70-6498-1321-916b85e33d14\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-8.25\",\"z\":\"0\"}],\"uuid\":\"0d0e3e73-33d8-0ce9-3a20-175a7a93862d\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-15\",\"z\":\"-15\"}],\"uuid\":\"1b5ee9ff-17de-454b-85f2-cb0570050b90\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-19\",\"z\":\"-18\"}],\"uuid\":\"6ea49149-0200-7d3a-600a-24402efb557b\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-21\",\"z\":\"-18\"}],\"uuid\":\"db23f3c5-a08e-7bf7-683e-cf126d1232b6\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"0266d8da-f639-7c87-40f6-834b24e671e2\":{\"name\":\"right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4abae333-f2ae-9e7f-2ce9-b578b2ff278b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"64.2307378684\",\"y\":\"-13.566260371\",\"z\":\"6.4606648089\"}],\"uuid\":\"86d6faeb-6de5-ed09-799d-20633666516d\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"114.1486747733\",\"y\":\"-20.7048110546\",\"z\":\"-9.0071669588\"}],\"uuid\":\"4bc9b965-ae34-7011-dd25-b5d34eb2b0d2\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"136.7808211063\",\"y\":\"-13.9954453589\",\"z\":\"-14.4327550432\"}],\"uuid\":\"bd929d72-4a46-4250-2938-e870ce86437b\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"151.9244457898\",\"y\":\"-11.0310955788\",\"z\":\"-19.7338984647\"}],\"uuid\":\"22ddf6da-2a28-1c1a-29bf-bae2b4748338\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"82.0831222389\",\"y\":\"-20.4611761434\",\"z\":\"6.4190509652\"}],\"uuid\":\"44b182f2-3ccd-7794-fcf7-3f93dcd9d92c\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a44819f0-9e13-12f5-b270-88d133e31f46\":{\"name\":\"right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b215c7ca-397b-ad1c-e4d2-7604f2acd935\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c427d401-c525-3687-1f8e-ed18c9306141\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1d0c9879-06a1-9dbe-c324-ae7481efae95\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"98b261b3-0a69-4a62-2229-0d1aee17c78e\":{\"name\":\"chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"b6fc560a-2b3f-cb7b-2280-dfdae36d9d53\":{\"name\":\"right_hand\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"5cdc1be0-a703-9397-813d-b0c2fe914a14\":{\"name\":\"under_body\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"77a167f4-b81e-d422-37ac-b834f0f3b6d2\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a92329ea-c77f-d8c7-ed0b-e01d23b40210\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"806eb3e5-3807-f9a4-98a4-2f043200b554\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"72fa22e2-294c-6fec-7355-1c49ed019a72\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"20832476-f83d-a3bf-a371-dfb6ddc221f1\":{\"name\":\"right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"05ff9b91-ec75-d719-db63-3293c0b4b599\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a958082f-6e42-e767-a165-a83c1afc65cf\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c8f33edc-34a6-75ce-0ef5-f877961e16a2\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0922a78c-999f-4564-c405-3f9d420e1eee\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"14f70d2a-4f4d-e8e6-fa68-cb95be5d9ff7\":{\"name\":\"right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"628f7969-9587-c8e5-0d31-62e5c5863564\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"55\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"22c3ebe0-6d73-0b53-7181-91f24c94d51d\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"33.75\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f6beca42-d9a3-8df1-00de-db5253cc33ff\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"503e1745-76d8-cbd7-c233-c19d42d2abb1\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"2e3cc482-b167-33b8-1714-5c7b341d65b4\":{\"name\":\"left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e4f11bce-2710-3eb4-e355-555ee508b0e8\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d8988bec-15d2-f639-8898-cec491aa4545\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e45704cb-a1a1-b8a4-2c06-e8d54252c215\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"97f5e8cb-75da-c0fc-77c4-646785658f24\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7113ca1c-2ae3-a77e-c3ab-63794d1ba17f\":{\"name\":\"left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"13c4106c-330a-a553-e151-1d486f2c12d9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e608ccd2-cdce-c6be-a069-e7124ef76611\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1d644f29-8475-7b84-e57d-f0b3fac1814a\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6f3eac45-0aee-fcf4-e283-e1fb93e876d6\":{\"name\":\"left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"804fa243-e81b-4d15-e396-73477487588e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a3031388-d83b-d005-b810-023106bdf976\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"138.849121742\",\"y\":\"11.7214510344\",\"z\":\"13.0867025974\"}],\"uuid\":\"aae1ccdb-040e-1a64-4654-fcc0176820ea\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"145.927602266\",\"y\":\"8.5372576322\",\"z\":\"12.3796053964\"}],\"uuid\":\"b213405f-1a8b-11be-9ac8-cf4243be8c9f\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"156.6930376522\",\"y\":\"9.3072682329\",\"z\":\"20.576385285\"}],\"uuid\":\"291bebf0-5608-26de-5f15-5060aaaf68a0\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"76.8171724368\",\"y\":\"17.0659528795\",\"z\":\"-3.9323555472\"}],\"uuid\":\"2b1fd463-cc83-a524-c358-5094a714e5e9\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7c168fd7-cdf9-c904-82b2-464caba505cc\":{\"name\":\"left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"42.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8503d8e7-3d18-c261-d32b-5441c8cdd820\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a8fe171c-1644-dc02-1426-1417e1ee6b76\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d5170b4c-a695-0814-961b-996bed022005\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"842ad7bd-7015-fa76-ebca-314ed49a789f\":{\"name\":\"left_hand\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"2404264b-66f1-5dc1-5fe5-db0c33526a27\":{\"name\":\"middle_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2d8a8767-3c51-0746-4c41-8b01d495ab62\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-35\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"91ba3a67-624e-4c4a-fd7f-7872ec688bbe\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d93201b7-b457-450a-aef9-44f6ea7c6775\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"695acdab-d134-2b12-4062-1b5c0fd60b18\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2ae406dd-633c-cc46-6e99-89e47500a45a\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9595e0-ba95-9397-76c5-68d018b0975a\":{\"name\":\"middle_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fc68f8b7-5a03-b3dc-16b4-d9f2a56bb98b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3d852dde-dd3a-56a6-3bb9-5ca4d8fbe77e\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6c427534-c258-99d1-095a-f4b415860f83\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a0d3135b-3746-7353-99e1-7cd08d16f1d0\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"f719d2bb-6c28-0ab6-a585-41f54792160c\":{\"name\":\"middle_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9829f20c-f4f2-33c0-97ed-61f91d6da71d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9638942e-15bd-a3d0-8054-d420dfd4a061\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bdcc3fdd-3f05-b3ae-a1b3-951c3accfd04\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b86307a6-9e36-1c49-d7b3-a5c07b55f61a\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2fd5761-a84d-7020-38c7-2efcd6c39298\":{\"name\":\"middle_cape_4\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ed564ce8-2580-adc3-df1a-66f3dd90ef50\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f1f9962f-75b9-3d94-8aaf-ac302dda24ca\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4f1d202a-13c6-a371-60ee-d81501131b0f\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8e18f727-3b17-f8a6-133f-07e5ba157926\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9b4610f1-5d4d-9c62-d7db-b098f5a0a8e8\":{\"name\":\"eye\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"18313221-3fa4-a587-cc55-890a3eff17ac\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2bda4425-6bf7-2d59-6847-7ab4d2e51612\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"3897b300-98c5-809f-88c0-f74a6688feee\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"62421ffb-7685-a28e-c939-59b0d8123776\":{\"name\":\"left_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7379dd2a-f6f7-5e8f-0102-b7f1b31f9e92\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-50\"}],\"uuid\":\"71b45432-232c-99c1-328d-e34ef8949cb5\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-30\"}],\"uuid\":\"60e20a55-d34a-d34f-99e1-b2c26278de4a\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-17.5\"}],\"uuid\":\"6b0ff250-0887-ad33-48cc-6eaec769b82f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"167d2bb7-4626-6746-c72b-ba3c8dab3e2f\":{\"name\":\"left_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"dae60c74-bb07-e254-9c30-770a54621a78\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-42.5\"}],\"uuid\":\"4eabc75f-4981-874f-9ce4-476a91235f6d\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"6884ac67-dd1f-1724-5b89-fe0fb6a8fde7\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"2.5\"}],\"uuid\":\"1714a02c-64ac-4daf-5e28-6c9854fffc28\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"57123914-f464-2f02-3eb3-e1ac924f8ef2\":{\"name\":\"left_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f6101bb7-3bdb-a222-ba5e-3d32f02a8070\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-27.5\"}],\"uuid\":\"8082874f-2842-f8e3-0feb-88bb7050a7fe\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-20\"}],\"uuid\":\"8874cc5b-1e99-5217-547b-724ea9dbbce1\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-7.5\"}],\"uuid\":\"d524e0c7-ff1b-3322-ee1b-ccd272d52756\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"06102678-1ae6-d632-30b0-6964f314e6bf\":{\"name\":\"right_cape_1\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"077830f5-80a0-9f13-0a4a-036fa6f1e73f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"42.5\"}],\"uuid\":\"373c5e53-95f9-f7b6-8ba5-9ca3aefd5e82\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"27.5\"}],\"uuid\":\"787c4895-9b5e-3397-16cc-70bb01bb2d82\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"10\"}],\"uuid\":\"9759a6e5-b8de-8d59-3973-bddc97ba3ad8\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"977a05f5-98e7-7f45-0d7b-f345af018dd3\":{\"name\":\"right_cape_2\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"62671d38-f946-cedd-ea91-d7d2beb67149\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"72.5\"}],\"uuid\":\"0c3ad74d-3852-a53a-5313-394b42a291d4\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"52.5\"}],\"uuid\":\"81ced27f-4fa9-edd4-c1c7-0260f7289ee9\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"22.5\"}],\"uuid\":\"4f4d433f-8685-692b-2e07-9b0c1d8f63a3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"18d36cc2-46b6-f929-3624-07aef443f80d\":{\"name\":\"right_cape_3\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3cefc86c-cfb9-f1b2-9546-608ca9783f11\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"40\"}],\"uuid\":\"5a4095ba-df21-9670-d087-e0c396308e76\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"25\"}],\"uuid\":\"30d13ac6-c5a7-d523-d57e-caca63d2d277\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-5\"}],\"uuid\":\"a16ff8f7-573d-fd01-b08c-b34a496709a5\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"0d53fe40-29a8-f10f-e40f-59194c70c775\":{\"name\":\"shadow\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"d8e887a2-a16d-3dfc-b2e0-ba8aca6bb84f\":{\"name\":\"hitbox\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false},\"aa6e0a4a-d036-a286-5c6f-ada2c220032b\":{\"name\":\"circle\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"1\",\"z\":\"1\"}],\"uuid\":\"2e5fa573-aa72-0a92-7dd2-0563204132db\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5729eb61-1e9a-ec6d-8b7f-d400d4910bbf\",\"time\":0.25,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"185b2cad-6cc7-a818-34f9-59c70db1d853\":{\"name\":\"left_hair_ik\",\"type\":\"null_object\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"128f1ec0-f81f-d7fe-3a59-edc6c522fb10\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"-3\",\"y\":\"10\",\"z\":\"9\"}],\"uuid\":\"82fb9f83-7715-3c34-7d10-64159bf9d223\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"-3\",\"y\":\"14\",\"z\":\"4\"}],\"uuid\":\"f89754fe-dab1-5766-6d1f-614f9d2eec8c\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"-4\",\"y\":\"12\",\"z\":\"-7\"}],\"uuid\":\"5cbbcc40-75af-2ac2-868c-b116419a9eff\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"ac61223e-e43c-ced3-6869-82c8e685056c\":{\"name\":\"right_hair_ik\",\"type\":\"null_object\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"92a45fc3-4f29-8496-bb52-7cc3e3ad1c18\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"1\",\"y\":\"3\",\"z\":\"4\"}],\"uuid\":\"c0cef96a-9e56-5e34-dbeb-f67c3f2f23dc\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"1\",\"y\":\"9\",\"z\":\"4\"}],\"uuid\":\"88be0498-d67d-f58f-14c5-8e89cc829669\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"4\",\"y\":\"3\",\"z\":\"-6\"}],\"uuid\":\"7f369f69-8a9c-6a3d-b72d-d9b1948e857c\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}}]}"
  },
  {
    "path": "core/src/main/resources/config.yml",
    "content": "# BetterModel Configuration\n#\n# This file contains all the configuration options for the BetterModel plugin.\n# For detailed information on each option, please refer to the official documentation.\n\n# Debugging options for development and troubleshooting.\n# Enable these only if you are diagnosing issues.\ndebug:\n  # Toggles debug messages for hitbox creation and interaction.\n  hitbox: false\n  # Toggles detailed stack traces for exceptions handled by the plugin.\n  exception: false\n  # Toggles debug messages related to resource pack generation.\n  pack: false\n  # Toggles debug messages for model tracker lifecycle and updates.\n  tracker: false\n\n# Visual indicator settings.\nindicator:\n  # Shows a progress bar during resource pack generation.\n  progress_bar: true\n\n# Core feature modules.\n# Disable modules you don't need to save resources.\nmodule:\n  # Enables general entity models.\n  model: true\n  # Enables player-specific animations and models (e.g., custom limbs).\n  player-animation: true\n\n# Resource pack generation settings.\npack:\n  # Generates models compatible with modern Minecraft versions (>=1.21.4).\n  generate-modern-model: true\n  # Generates models compatible with legacy Minecraft versions (<=1.21.3).\n  generate-legacy-model: true\n  # Obfuscates model and texture names in the resource pack to prevent easy extraction.\n  use-obfuscation: true\n\n# Toggles metrics collection via bStats (https://bstats.org/plugin/bukkit/BetterModel/24237).\n# Disabling this helps us less to improve the plugin.\nmetrics: true\n\n# Enables sight-trace culling: models are only rendered if there is a clear line of sight.\nsight-trace: true\n\n# Merges the generated resource pack with packs from other plugins.\nmerge-with-external-resources: true\n\n# The base item used for custom model data.\n# It's recommended to use an item that is not commonly used for other purposes.\nitem: leather_horse_armor\n\n# The namespace used for custom model items.\nitem-namespace: bm_models\n\n# Maximum distance (in blocks) for sending animation packets. -1 for default server view distance.\nmax-sight: -1\n# Minimum distance (in blocks) before animation packets are sent.\nmin-sight: 5\n\n# The namespace used for resource pack assets (e.g., assets/bettermodel/...).\nnamespace: \"bettermodel\"\n\n# The format of the generated resource pack.\n# 'zip': A standard .zip file.\n# 'folder': A raw folder structure (useful for merging or debugging).\npack-type: zip\n\n# The location where the resource pack file or folder will be generated, relative to the server root.\nbuild-folder-location: BetterModel/build\n\n# If enabled, models attached to invisible mobs will also be invisible.\nfollow-mob-invisibility: true\n\n# If enabled, utilizes Purpur's AFK API to pause animations for AFK players.\nuse-purpur-afk: true\n\n# Checks for new plugin versions on startup and notifies admins.\nversion-check: true\n\n# The default movement controller for mountable models.\n# 'walk': Standard ground-based movement.\n# 'fly': Flying movement.\ndefault-mount-controller: walk\n\n# The time in ticks between inserted keyframes for smooth interpolation (lerp).\n# Lower values result in smoother but potentially more resource-intensive animations.\nlerp-frame-time: 3\n\n# Prevents players from swapping hotbar slots if they have a custom player model or animation active.\ncancel-player-model-inventory: false\n\n# The delay in ticks before a player's original entity is hidden after a model is applied.\n# This can help prevent visual glitches.\nplayer-hide-delay: 3\n\n# The number of packets to bundle together before sending.\n# Higher values can reduce network overhead but may increase perceived latency. 0 to disable.\npacket-bundling-size: 16\n\n# Enables strict loading mode. If true, the plugin will fail to load models with unsupported features.\n# If false, it will attempt to load them by ignoring unsupported parts, which may cause visual issues.\nenable-strict-loading: false\n"
  },
  {
    "path": "core/src/main/resources/demon_knight.bbmodel",
    "content": "{\"meta\":{\"format_version\":\"4.10\",\"model_format\":\"free\",\"box_uv\":false},\"name\":\"demon_knight\",\"model_identifier\":\"\",\"visible_box\":[1,1,0],\"variable_placeholders\":\"\",\"variable_placeholder_buttons\":[],\"timeline_setups\":[],\"unhandled_root_fields\":{},\"resolution\":{\"width\":128,\"height\":128},\"elements\":[{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.7625,44.2,-2.55],\"to\":[2.7625,45.05,0.85],\"autouv\":0,\"color\":9,\"origin\":[0,39.1,0],\"faces\":{\"north\":{\"uv\":[103,34,109,35],\"texture\":0},\"east\":{\"uv\":[91,31,94,32],\"texture\":0},\"south\":{\"uv\":[103,63,109,64],\"texture\":0},\"west\":{\"uv\":[6,93,9,94],\"texture\":0},\"up\":{\"uv\":[83,21,77,18],\"texture\":0},\"down\":{\"uv\":[83,21,77,24],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"aeb4b18d-0dfa-fbba-3213-36a2b8301fbe\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.125,39.95,-2.55],\"to\":[4.25,43.35,3.4],\"autouv\":0,\"color\":9,\"rotation\":[0,25,0],\"origin\":[3.4,41.225,1.7],\"faces\":{\"north\":{\"uv\":[19,70,21,73],\"texture\":0},\"east\":{\"uv\":[76,59,82,62],\"texture\":0},\"south\":{\"uv\":[80,56,82,59],\"texture\":0},\"west\":{\"uv\":[70,76,76,79],\"texture\":0},\"up\":{\"uv\":[26,95,24,89],\"texture\":0},\"down\":{\"uv\":[28,89,26,95],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fe3c2995-33c1-f5d9-bd1b-d6bef0b142c1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.4,37.825,-0.6375],\"to\":[2.55,39.1,5.3125],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[0,38.25,2.7625],\"faces\":{\"north\":{\"uv\":[103,15,109,16],\"texture\":0},\"east\":{\"uv\":[103,16,109,17],\"texture\":0},\"south\":{\"uv\":[18,103,24,104],\"texture\":0},\"west\":{\"uv\":[24,103,30,104],\"texture\":0},\"up\":{\"uv\":[57,14,51,8],\"texture\":0},\"down\":{\"uv\":[47,53,41,59],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b75f3760-7df4-f694-3187-05e86def041a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.85,39.1,-0.85],\"to\":[0.85,43.35,0.85],\"autouv\":0,\"color\":9,\"origin\":[0,39.1,0],\"faces\":{\"north\":{\"uv\":[39,60,41,64],\"texture\":0},\"east\":{\"uv\":[88,99,90,103],\"texture\":0},\"south\":{\"uv\":[12,100,14,104],\"texture\":0},\"west\":{\"uv\":[47,100,49,104],\"texture\":0},\"up\":{\"uv\":[83,49,81,47],\"texture\":0},\"down\":{\"uv\":[86,72,84,74],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a7aba758-5ffe-e499-3d63-2584906072f5\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.55,43.35,-0.85],\"to\":[2.55,44.2,0.85],\"autouv\":0,\"color\":9,\"origin\":[0,39.1,0],\"faces\":{\"north\":{\"uv\":[33,24,38,25],\"texture\":0},\"east\":{\"uv\":[31,51,33,52],\"texture\":0},\"south\":{\"uv\":[109,23,114,24],\"texture\":0},\"west\":{\"uv\":[41,52,43,53],\"texture\":0},\"up\":{\"uv\":[43,35,38,33],\"texture\":0},\"down\":{\"uv\":[82,74,77,76],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"820916df-84dd-05b4-f88d-bbe80c9c07bf\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.55,43.35,-1.7],\"to\":[4.25,45.05,3.4],\"autouv\":0,\"color\":9,\"rotation\":[0,25,0],\"origin\":[3.4,41.225,1.7],\"faces\":{\"north\":{\"uv\":[68,111,70,113],\"texture\":0},\"east\":{\"uv\":[25,95,30,97],\"texture\":0},\"south\":{\"uv\":[111,69,113,71],\"texture\":0},\"west\":{\"uv\":[95,27,100,29],\"texture\":0},\"up\":{\"uv\":[36,100,34,95],\"texture\":0},\"down\":{\"uv\":[38,95,36,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc68f85b-829a-93b5-feba-70599a0a5929\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.25,39.95,-2.55],\"to\":[-2.125,43.35,3.4],\"autouv\":0,\"color\":9,\"rotation\":[0,-25,0],\"origin\":[-3.4,41.225,1.7],\"faces\":{\"north\":{\"uv\":[44,103,46,106],\"texture\":0},\"east\":{\"uv\":[76,76,82,79],\"texture\":0},\"south\":{\"uv\":[54,103,56,106],\"texture\":0},\"west\":{\"uv\":[0,77,6,80],\"texture\":0},\"up\":{\"uv\":[30,95,28,89],\"texture\":0},\"down\":{\"uv\":[37,89,35,95],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"cdca1aaf-a948-f853-f008-51da7819ece6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.25,43.35,-1.7],\"to\":[-2.55,45.05,3.4],\"autouv\":0,\"color\":9,\"rotation\":[0,-25,0],\"origin\":[-3.4,41.225,1.7],\"faces\":{\"north\":{\"uv\":[111,65,113,67],\"texture\":0},\"east\":{\"uv\":[94,57,99,59],\"texture\":0},\"south\":{\"uv\":[111,67,113,69],\"texture\":0},\"west\":{\"uv\":[93,94,98,96],\"texture\":0},\"up\":{\"uv\":[97,5,95,0],\"texture\":0},\"down\":{\"uv\":[25,95,23,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f68c9e09-063b-d45d-67ba-5784f7f12563\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.4,45.05,-0.6375],\"to\":[2.55,45.9,5.3125],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[0,45.05,2.7625],\"faces\":{\"north\":{\"uv\":[103,26,109,27],\"texture\":0},\"east\":{\"uv\":[103,27,109,28],\"texture\":0},\"south\":{\"uv\":[103,28,109,29],\"texture\":0},\"west\":{\"uv\":[103,29,109,30],\"texture\":0},\"up\":{\"uv\":[61,20,55,14],\"texture\":0},\"down\":{\"uv\":[61,20,55,26],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d2d8ae28-2190-b3e0-8982-4c98ab732c72\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-1.7,39.1,5.1],\"to\":[1.7,45.05,6.8],\"autouv\":0,\"color\":9,\"origin\":[0,39.1,5.95],\"faces\":{\"north\":{\"uv\":[16,76,19,82],\"texture\":0},\"east\":{\"uv\":[82,87,84,93],\"texture\":0},\"south\":{\"uv\":[56,76,59,82],\"texture\":0},\"west\":{\"uv\":[22,88,24,94],\"texture\":0},\"up\":{\"uv\":[21,58,18,56],\"texture\":0},\"down\":{\"uv\":[80,52,77,54],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"58cc8b17-a41a-147f-cca7-e3435f2929d4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.4,39.1,2.55],\"to\":[3.4,45.05,5.1],\"autouv\":0,\"color\":9,\"origin\":[0,39.1,5.95],\"faces\":{\"north\":{\"uv\":[48,38,55,44],\"texture\":0},\"east\":{\"uv\":[59,76,62,82],\"texture\":0},\"south\":{\"uv\":[48,44,55,50],\"texture\":0},\"west\":{\"uv\":[62,76,65,82],\"texture\":0},\"up\":{\"uv\":[79,12,72,9],\"texture\":0},\"down\":{\"uv\":[79,12,72,15],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"07318316-9d72-9ddb-064d-bbf4bf2ae85b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4.25,40.8,-0.85],\"to\":[5.1,43.35,2.55],\"autouv\":0,\"color\":9,\"rotation\":[0,25,0],\"origin\":[3.4,42.075,1.7],\"faces\":{\"north\":{\"uv\":[47,53,48,56],\"texture\":0},\"east\":{\"uv\":[12,73,15,76],\"texture\":0},\"south\":{\"uv\":[52,73,53,76],\"texture\":0},\"west\":{\"uv\":[76,95,79,98],\"texture\":0},\"up\":{\"uv\":[40,78,39,75],\"texture\":0},\"down\":{\"uv\":[66,76,65,79],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2a05fb72-2e28-bf6e-8f2a-9450b726fefd\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.99625,43.9875,0.27625],\"to\":[5.3125,46.5375,2.82625],\"autouv\":0,\"color\":9,\"rotation\":[-10.11783,22.9824,-24.56202],\"origin\":[2.7625,44.4125,1.7],\"faces\":{\"north\":{\"uv\":[85,9,87,12],\"texture\":0},\"east\":{\"uv\":[49,96,52,99],\"texture\":0},\"south\":{\"uv\":[82,96,84,99],\"texture\":0},\"west\":{\"uv\":[52,96,55,99],\"texture\":0},\"up\":{\"uv\":[12,106,10,103],\"texture\":0},\"down\":{\"uv\":[35,103,33,106],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6e105804-4673-4479-eaed-0423f5918b41\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.91,45.2625,0.44625],\"to\":[5.95,46.9625,2.65625],\"autouv\":0,\"color\":9,\"rotation\":[1.16524,24.97457,2.75806],\"origin\":[2.7625,44.4125,1.7],\"faces\":{\"north\":{\"uv\":[3,98,5,100],\"texture\":0},\"east\":{\"uv\":[17,98,19,100],\"texture\":0},\"south\":{\"uv\":[35,110,37,112],\"texture\":0},\"west\":{\"uv\":[47,110,49,112],\"texture\":0},\"up\":{\"uv\":[63,112,61,110],\"texture\":0},\"down\":{\"uv\":[7,111,5,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2f5d276f-2374-36e5-0d78-d927dd2f992b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.17509,46.61728,-0.02179],\"to\":[5.10884,47.91353,1.67821],\"autouv\":0,\"color\":9,\"rotation\":[9.06159,23.39896,21.88023],\"origin\":[4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[58,106,60,107],\"texture\":0},\"east\":{\"uv\":[88,106,90,107],\"texture\":0},\"south\":{\"uv\":[116,86,118,87],\"texture\":0},\"west\":{\"uv\":[89,116,91,117],\"texture\":0},\"up\":{\"uv\":[14,113,12,111],\"texture\":0},\"down\":{\"uv\":[113,26,111,28],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"db0e95b6-4b6c-acca-df2c-3ac9b66cb68e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.17509,47.67978,0.23321],\"to\":[4.89634,48.93353,1.42321],\"autouv\":0,\"color\":9,\"rotation\":[17.48614,18.15489,45.31509],\"origin\":[4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[91,116,93,117],\"texture\":0},\"east\":{\"uv\":[89,76,90,77],\"texture\":0},\"south\":{\"uv\":[116,93,118,94],\"texture\":0},\"west\":{\"uv\":[91,30,92,31],\"texture\":0},\"up\":{\"uv\":[118,95,116,94],\"texture\":0},\"down\":{\"uv\":[97,116,95,117],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c1a05c0c-3ab3-9553-a6c4-6e0d418cd875\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.06884,48.31728,0.40321],\"to\":[4.68384,49.88978,1.25321],\"autouv\":0,\"color\":9,\"rotation\":[9.48702,23.2378,22.95495],\"origin\":[4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[28,111,30,113],\"texture\":0},\"east\":{\"uv\":[31,73,32,75],\"texture\":0},\"south\":{\"uv\":[111,31,113,33],\"texture\":0},\"west\":{\"uv\":[73,44,74,46],\"texture\":0},\"up\":{\"uv\":[118,96,116,95],\"texture\":0},\"down\":{\"uv\":[118,96,116,97],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d11eb44e-4f69-fbf5-844a-7c4cecd2193f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.64384,49.25228,0.48821],\"to\":[4.00384,50.35728,1.16821],\"autouv\":0,\"color\":9,\"rotation\":[-0.69935,24.99084,-1.65498],\"origin\":[4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[23,94,24,95],\"texture\":0},\"east\":{\"uv\":[34,94,35,95],\"texture\":0},\"south\":{\"uv\":[79,95,80,96],\"texture\":0},\"west\":{\"uv\":[5,96,6,97],\"texture\":0},\"up\":{\"uv\":[101,7,100,6],\"texture\":0},\"down\":{\"uv\":[101,45,100,46],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ca91afbe-2f07-a0dd-e8fe-da988c1e6e68\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.84122,50.17004,0.96671],\"to\":[4.09497,51.23254,1.47671],\"autouv\":0,\"color\":9,\"rotation\":[0.46627,24.99593,1.10335],\"origin\":[3.43622,50.91379,1.22171],\"faces\":{\"north\":{\"uv\":[75,100,76,101],\"texture\":0},\"east\":{\"uv\":[100,75,101,76],\"texture\":0},\"south\":{\"uv\":[101,21,102,22],\"texture\":0},\"west\":{\"uv\":[101,47,102,48],\"texture\":0},\"up\":{\"uv\":[55,103,54,102],\"texture\":0},\"down\":{\"uv\":[79,103,78,104],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"26039549-7222-b70e-e174-6a9ef3e572c4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.01122,51.23254,1.05171],\"to\":[3.92497,51.95504,1.39171],\"autouv\":0,\"color\":9,\"rotation\":[0.46627,24.99593,1.10335],\"origin\":[3.43622,50.91379,1.22171],\"faces\":{\"north\":{\"uv\":[105,20,106,21],\"texture\":0},\"east\":{\"uv\":[105,46,106,47],\"texture\":0},\"south\":{\"uv\":[40,106,41,107],\"texture\":0},\"west\":{\"uv\":[91,107,92,108],\"texture\":0},\"up\":{\"uv\":[106,108,105,107],\"texture\":0},\"down\":{\"uv\":[6,108,5,109],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8291fbf4-6863-c022-2659-40bc06c7fe75\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.23758,51.91237,1.02009],\"to\":[3.81133,52.61362,1.36009],\"autouv\":0,\"color\":9,\"rotation\":[-1.86306,24.93493,-4.41196],\"origin\":[3.49789,52.6508,1.19009],\"faces\":{\"north\":{\"uv\":[87,108,88,109],\"texture\":0},\"east\":{\"uv\":[108,93,109,94],\"texture\":0},\"south\":{\"uv\":[101,108,102,109],\"texture\":0},\"west\":{\"uv\":[109,8,110,9],\"texture\":0},\"up\":{\"uv\":[18,110,17,109],\"texture\":0},\"down\":{\"uv\":[50,110,49,111],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b954a18e-ac4f-0766-3526-c851081a0c17\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.35682,52.61238,1.02009],\"to\":[3.65432,53.46238,1.36009],\"autouv\":0,\"color\":9,\"rotation\":[-3.0217,24.82838,-7.16529],\"origin\":[3.49789,52.6508,1.19009],\"faces\":{\"north\":{\"uv\":[34,111,35,112],\"texture\":0},\"east\":{\"uv\":[44,111,45,112],\"texture\":0},\"south\":{\"uv\":[53,111,54,112],\"texture\":0},\"west\":{\"uv\":[63,111,64,112],\"texture\":0},\"up\":{\"uv\":[56,113,55,112],\"texture\":0},\"down\":{\"uv\":[68,112,67,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1348e076-4cc5-4f8c-f4e2-a261ff98ab5b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.3125,43.9875,0.27625],\"to\":[-2.99625,46.5375,2.82625],\"autouv\":0,\"color\":9,\"rotation\":[-10.11783,-22.9824,24.56202],\"origin\":[-2.7625,44.4125,1.7],\"faces\":{\"north\":{\"uv\":[35,103,37,106],\"texture\":0},\"east\":{\"uv\":[96,59,99,62],\"texture\":0},\"south\":{\"uv\":[37,103,39,106],\"texture\":0},\"west\":{\"uv\":[96,62,99,65],\"texture\":0},\"up\":{\"uv\":[41,106,39,103],\"texture\":0},\"down\":{\"uv\":[105,42,103,45],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5365895f-d7bd-c3f7-e1a3-73eac3124234\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.89634,47.67978,0.23321],\"to\":[-3.17509,48.93353,1.42321],\"autouv\":0,\"color\":9,\"rotation\":[17.48614,-18.15489,-45.31509],\"origin\":[-4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[116,104,118,105],\"texture\":0},\"east\":{\"uv\":[118,23,119,24],\"texture\":0},\"south\":{\"uv\":[116,105,118,106],\"texture\":0},\"west\":{\"uv\":[28,118,29,119],\"texture\":0},\"up\":{\"uv\":[108,117,106,116],\"texture\":0},\"down\":{\"uv\":[118,106,116,107],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2ca2424c-e508-d492-3fe5-e9222fc5921f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.10884,46.61728,-0.02179],\"to\":[-3.17509,47.91353,1.67821],\"autouv\":0,\"color\":9,\"rotation\":[9.06159,-23.39896,-21.88023],\"origin\":[-4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[116,100,118,101],\"texture\":0},\"east\":{\"uv\":[116,101,118,102],\"texture\":0},\"south\":{\"uv\":[116,102,118,103],\"texture\":0},\"west\":{\"uv\":[104,116,106,117],\"texture\":0},\"up\":{\"uv\":[58,113,56,111],\"texture\":0},\"down\":{\"uv\":[113,57,111,59],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"46051a90-1e40-4677-7aa0-1c2f182902fc\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.95,45.2625,0.44625],\"to\":[-3.91,46.9625,2.65625],\"autouv\":0,\"color\":9,\"rotation\":[1.16524,-24.97457,-2.75806],\"origin\":[-2.7625,44.4125,1.7],\"faces\":{\"north\":{\"uv\":[42,111,44,113],\"texture\":0},\"east\":{\"uv\":[111,43,113,45],\"texture\":0},\"south\":{\"uv\":[49,111,51,113],\"texture\":0},\"west\":{\"uv\":[51,111,53,113],\"texture\":0},\"up\":{\"uv\":[113,55,111,53],\"texture\":0},\"down\":{\"uv\":[113,55,111,57],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f36f47e6-b78d-00d0-582d-beb80e4b41c4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.68384,48.31728,0.40321],\"to\":[-3.06884,49.88978,1.25321],\"autouv\":0,\"color\":9,\"rotation\":[9.48702,-23.2378,-22.95495],\"origin\":[-4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[32,111,34,113],\"texture\":0},\"east\":{\"uv\":[76,62,77,64],\"texture\":0},\"south\":{\"uv\":[111,35,113,37],\"texture\":0},\"west\":{\"uv\":[47,84,48,86],\"texture\":0},\"up\":{\"uv\":[118,98,116,97],\"texture\":0},\"down\":{\"uv\":[118,98,116,99],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f8fa9e70-c280-fbed-5a29-37340fa87ce8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.09497,50.17004,0.96671],\"to\":[-2.84122,51.23254,1.47671],\"autouv\":0,\"color\":9,\"rotation\":[0.46627,-24.99593,-1.10335],\"origin\":[-3.43622,50.91379,1.22171],\"faces\":{\"north\":{\"uv\":[118,11,119,12],\"texture\":0},\"east\":{\"uv\":[14,118,15,119],\"texture\":0},\"south\":{\"uv\":[118,17,119,18],\"texture\":0},\"west\":{\"uv\":[118,18,119,19],\"texture\":0},\"up\":{\"uv\":[21,119,20,118],\"texture\":0},\"down\":{\"uv\":[22,118,21,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"cf4d790b-d4b4-7a65-6206-b6c09a260faa\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.00384,49.25228,0.48821],\"to\":[-2.64384,50.35728,1.16821],\"autouv\":0,\"color\":9,\"rotation\":[-0.69935,-24.99084,1.65498],\"origin\":[-4.28009,48.31728,0.82821],\"faces\":{\"north\":{\"uv\":[7,118,8,119],\"texture\":0},\"east\":{\"uv\":[8,118,9,119],\"texture\":0},\"south\":{\"uv\":[118,8,119,9],\"texture\":0},\"west\":{\"uv\":[9,118,10,119],\"texture\":0},\"up\":{\"uv\":[119,10,118,9],\"texture\":0},\"down\":{\"uv\":[119,10,118,11],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"01a2d1ed-ed02-b637-51a2-5cf940965937\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.81133,51.91237,1.02009],\"to\":[-3.23758,52.61362,1.36009],\"autouv\":0,\"color\":9,\"rotation\":[-1.86306,-24.93493,4.41196],\"origin\":[-3.49789,52.6508,1.19009],\"faces\":{\"north\":{\"uv\":[117,115,118,116],\"texture\":0},\"east\":{\"uv\":[117,116,118,117],\"texture\":0},\"south\":{\"uv\":[117,117,118,118],\"texture\":0},\"west\":{\"uv\":[118,3,119,4],\"texture\":0},\"up\":{\"uv\":[119,5,118,4],\"texture\":0},\"down\":{\"uv\":[119,5,118,6],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9f6c769f-5d82-d995-6b33-028a7b5730d4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.92497,51.23254,1.05171],\"to\":[-3.01122,51.95504,1.39171],\"autouv\":0,\"color\":9,\"rotation\":[0.46627,-24.99593,-1.10335],\"origin\":[-3.43622,50.91379,1.22171],\"faces\":{\"north\":{\"uv\":[117,109,118,110],\"texture\":0},\"east\":{\"uv\":[117,110,118,111],\"texture\":0},\"south\":{\"uv\":[111,117,112,118],\"texture\":0},\"west\":{\"uv\":[117,111,118,112],\"texture\":0},\"up\":{\"uv\":[113,118,112,117],\"texture\":0},\"down\":{\"uv\":[118,112,117,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"74341beb-c746-c557-18e6-3bdcc8579260\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.65432,52.61238,1.02009],\"to\":[-3.35682,53.46238,1.36009],\"autouv\":0,\"color\":9,\"rotation\":[-3.0217,-24.82838,7.16529],\"origin\":[-3.49789,52.6508,1.19009],\"faces\":{\"north\":{\"uv\":[73,113,74,114],\"texture\":0},\"east\":{\"uv\":[100,117,101,118],\"texture\":0},\"south\":{\"uv\":[106,117,107,118],\"texture\":0},\"west\":{\"uv\":[107,117,108,118],\"texture\":0},\"up\":{\"uv\":[109,118,108,117],\"texture\":0},\"down\":{\"uv\":[118,108,117,109],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"331420c7-b85d-798a-9191-a0d65b746238\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.58007,39.9925,-1.2242],\"to\":[4.43007,41.225,2.1758],\"autouv\":0,\"color\":9,\"rotation\":[-18.2489,17.38772,-47.81374],\"origin\":[4.00507,40.8,0.3908],\"faces\":{\"north\":{\"uv\":[77,43,78,44],\"texture\":0},\"east\":{\"uv\":[23,58,26,59],\"texture\":0},\"south\":{\"uv\":[64,82,65,83],\"texture\":0},\"west\":{\"uv\":[28,63,31,64],\"texture\":0},\"up\":{\"uv\":[10,118,9,115],\"texture\":0},\"down\":{\"uv\":[15,115,14,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b15ccc49-db06-5d54-8e86-936d7d405485\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.43007,39.9925,-1.2242],\"to\":[-3.58007,41.225,2.1758],\"autouv\":0,\"color\":9,\"rotation\":[-18.2489,-17.38772,47.81374],\"origin\":[-4.00507,40.8,0.3908],\"faces\":{\"north\":{\"uv\":[83,51,84,52],\"texture\":0},\"east\":{\"uv\":[74,49,77,50],\"texture\":0},\"south\":{\"uv\":[55,85,56,86],\"texture\":0},\"west\":{\"uv\":[82,78,85,79],\"texture\":0},\"up\":{\"uv\":[116,59,115,56],\"texture\":0},\"down\":{\"uv\":[59,115,58,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ed613350-2d17-57fa-a4d2-b97cb9cdd58a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.1,40.8,-0.85],\"to\":[-4.25,43.35,2.55],\"autouv\":0,\"color\":9,\"rotation\":[0,-25,0],\"origin\":[-3.4,42.075,1.7],\"faces\":{\"north\":{\"uv\":[75,114,76,117],\"texture\":0},\"east\":{\"uv\":[90,95,93,98],\"texture\":0},\"south\":{\"uv\":[86,114,87,117],\"texture\":0},\"west\":{\"uv\":[96,48,99,51],\"texture\":0},\"up\":{\"uv\":[101,117,100,114],\"texture\":0},\"down\":{\"uv\":[109,114,108,117],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"41431638-6882-7e35-c079-f79f0a85c310\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.85,45.05,0.425],\"to\":[0.85,46.75,3.825],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-45],\"origin\":[0,45.9,2.125],\"faces\":{\"north\":{\"uv\":[96,65,98,67],\"texture\":0},\"east\":{\"uv\":[16,82,19,84],\"texture\":0},\"south\":{\"uv\":[96,70,98,72],\"texture\":0},\"west\":{\"uv\":[30,92,33,94],\"texture\":0},\"up\":{\"uv\":[18,65,16,62],\"texture\":0},\"down\":{\"uv\":[23,65,21,68],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1242df13-8c4c-5466-4de7-c98d18085b08\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.36,39.1,4.73875],\"to\":[3.74,45.05,5.95],\"autouv\":0,\"color\":9,\"rotation\":[0,45,0],\"origin\":[2.55,42.075,5.95],\"faces\":{\"north\":{\"uv\":[88,77,90,83],\"texture\":0},\"east\":{\"uv\":[41,101,42,107],\"texture\":0},\"south\":{\"uv\":[84,88,86,94],\"texture\":0},\"west\":{\"uv\":[42,101,43,107],\"texture\":0},\"up\":{\"uv\":[80,4,78,3],\"texture\":0},\"down\":{\"uv\":[85,42,83,43],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ece57724-7e60-507a-ab93-1f99a2f0eb03\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.74,39.1,4.73875],\"to\":[-1.36,45.05,5.95],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[-2.55,42.075,5.95],\"faces\":{\"north\":{\"uv\":[86,88,88,94],\"texture\":0},\"east\":{\"uv\":[43,101,44,107],\"texture\":0},\"south\":{\"uv\":[88,88,90,94],\"texture\":0},\"west\":{\"uv\":[53,102,54,108],\"texture\":0},\"up\":{\"uv\":[12,85,10,84],\"texture\":0},\"down\":{\"uv\":[88,77,86,78],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"623ac13d-7644-fa69-45e2-fdefbd5ef451\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.125,39.1,-1.7],\"to\":[4.25,39.95,3.4],\"autouv\":0,\"color\":9,\"rotation\":[0,25,0],\"origin\":[3.4,41.225,1.7],\"faces\":{\"north\":{\"uv\":[92,54,94,55],\"texture\":0},\"east\":{\"uv\":[109,90,114,91],\"texture\":0},\"south\":{\"uv\":[80,92,82,93],\"texture\":0},\"west\":{\"uv\":[109,99,114,100],\"texture\":0},\"up\":{\"uv\":[88,99,86,94],\"texture\":0},\"down\":{\"uv\":[90,94,88,99],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"997d2204-820b-a00f-3097-9963e1100c6a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.25,39.1,-1.7],\"to\":[-2.125,39.95,3.4],\"autouv\":0,\"color\":9,\"rotation\":[0,-25,0],\"origin\":[-3.4,41.225,1.7],\"faces\":{\"north\":{\"uv\":[88,46,90,47],\"texture\":0},\"east\":{\"uv\":[109,34,114,35],\"texture\":0},\"south\":{\"uv\":[92,21,94,22],\"texture\":0},\"west\":{\"uv\":[109,64,114,65],\"texture\":0},\"up\":{\"uv\":[45,98,43,93],\"texture\":0},\"down\":{\"uv\":[86,94,84,99],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"15067340-dab6-b719-c5db-ccddd8c13f0d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.73839,39.44,-2.05932],\"to\":[-0.61339,40.05625,-0.84807],\"autouv\":0,\"color\":9,\"rotation\":[45,-25,0],\"origin\":[-1.59089,39.7375,-1.46432],\"faces\":{\"north\":{\"uv\":[50,93,52,94],\"texture\":0},\"east\":{\"uv\":[26,50,27,51],\"texture\":0},\"south\":{\"uv\":[94,24,96,25],\"texture\":0},\"west\":{\"uv\":[61,55,62,56],\"texture\":0},\"up\":{\"uv\":[97,6,95,5],\"texture\":0},\"down\":{\"uv\":[98,51,96,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0e146668-2a18-534a-dd8b-05002a4f7f5c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.61339,39.44,-2.05932],\"to\":[2.73839,40.05625,-0.84807],\"autouv\":0,\"color\":9,\"rotation\":[45,25,0],\"origin\":[1.59089,39.7375,-1.46432],\"faces\":{\"north\":{\"uv\":[41,97,43,98],\"texture\":0},\"east\":{\"uv\":[15,69,16,70],\"texture\":0},\"south\":{\"uv\":[98,71,100,72],\"texture\":0},\"west\":{\"uv\":[71,54,72,55],\"texture\":0},\"up\":{\"uv\":[46,107,44,106],\"texture\":0},\"down\":{\"uv\":[108,50,106,51],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1a7991e4-745f-7efc-444b-299209fd9f79\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4.39293,41.48,-1.66843],\"to\":[5.39168,43.07375,1.73157],\"autouv\":0,\"color\":9,\"rotation\":[-14.06573,20.88127,-35.10451],\"origin\":[4.96668,42.925,0.03157],\"faces\":{\"north\":{\"uv\":[67,20,68,22],\"texture\":0},\"east\":{\"uv\":[14,101,17,103],\"texture\":0},\"south\":{\"uv\":[67,36,68,38],\"texture\":0},\"west\":{\"uv\":[30,103,33,105],\"texture\":0},\"up\":{\"uv\":[16,93,15,90],\"texture\":0},\"down\":{\"uv\":[50,91,49,94],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"da8540f3-6a51-c071-0867-38b58f566207\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.39168,41.48,-1.66843],\"to\":[-4.39293,43.07375,1.73157],\"autouv\":0,\"color\":9,\"rotation\":[-14.06573,-20.88127,35.10451],\"origin\":[-4.96668,42.925,0.03157],\"faces\":{\"north\":{\"uv\":[72,18,73,20],\"texture\":0},\"east\":{\"uv\":[103,30,106,32],\"texture\":0},\"south\":{\"uv\":[72,34,73,36],\"texture\":0},\"west\":{\"uv\":[103,32,106,34],\"texture\":0},\"up\":{\"uv\":[23,100,22,97],\"texture\":0},\"down\":{\"uv\":[58,113,57,116],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f0b034ea-a228-d9e8-8bc5-94e6d27857a7\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.4,44.2,-2.3375],\"to\":[-1.7,45.05,-0.6375],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[0,44.2,1.0625],\"faces\":{\"north\":{\"uv\":[26,68,28,69],\"texture\":0},\"east\":{\"uv\":[70,9,72,10],\"texture\":0},\"south\":{\"uv\":[6,77,8,78],\"texture\":0},\"west\":{\"uv\":[77,24,79,25],\"texture\":0},\"up\":{\"uv\":[67,12,65,10],\"texture\":0},\"down\":{\"uv\":[72,74,70,76],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"60360fc3-03e2-1007-9b85-358674639cf6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-1.9125,36.55,0.85],\"to\":[1.0625,37.825,3.825],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[0,36.975,2.7625],\"faces\":{\"north\":{\"uv\":[19,93,22,94],\"texture\":0},\"east\":{\"uv\":[90,101,93,102],\"texture\":0},\"south\":{\"uv\":[114,99,117,100],\"texture\":0},\"west\":{\"uv\":[115,5,118,6],\"texture\":0},\"up\":{\"uv\":[82,99,79,96],\"texture\":0},\"down\":{\"uv\":[99,85,96,88],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"89820f44-93e4-0e7c-484b-8fa0e83e56ec\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3,35,-0.25],\"to\":[3,36.5,5.25],\"autouv\":0,\"color\":1,\"origin\":[0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[89,4,95,6],\"texture\":0},\"east\":{\"uv\":[37,89,43,91],\"texture\":0},\"south\":{\"uv\":[89,72,95,74],\"texture\":0},\"west\":{\"uv\":[89,74,95,76],\"texture\":0},\"up\":{\"uv\":[61,44,55,38],\"texture\":0},\"down\":{\"uv\":[61,44,55,50],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"56c157e4-61fb-c7ec-de55-bc79f81ffd66\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4.32507,33,-2.88425],\"to\":[7.32507,37.5,1.61575],\"autouv\":0,\"color\":1,\"rotation\":[5,-15,10],\"origin\":[3.32507,35.25,-0.13425],\"faces\":{\"north\":{\"uv\":[87,32,90,37],\"texture\":0},\"east\":{\"uv\":[33,64,38,69],\"texture\":0},\"south\":{\"uv\":[87,37,90,42],\"texture\":0},\"west\":{\"uv\":[38,64,43,69],\"texture\":0},\"up\":{\"uv\":[90,52,87,47],\"texture\":0},\"down\":{\"uv\":[90,62,87,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"976142e5-835f-0783-2b66-047893b31e8d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4.32507,33,3.38425],\"to\":[7.32507,37.5,7.88425],\"autouv\":0,\"color\":1,\"rotation\":[-5,15,10],\"origin\":[3.32507,35.25,5.13425],\"faces\":{\"north\":{\"uv\":[73,87,76,92],\"texture\":0},\"east\":{\"uv\":[62,65,67,70],\"texture\":0},\"south\":{\"uv\":[76,87,79,92],\"texture\":0},\"west\":{\"uv\":[66,50,71,55],\"texture\":0},\"up\":{\"uv\":[82,92,79,87],\"texture\":0},\"down\":{\"uv\":[90,83,87,88],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"30860483-4b6d-b211-99f0-8b5163830762\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1,28,-1.25],\"to\":[4,32,6.25],\"autouv\":0,\"color\":1,\"origin\":[0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[40,69,43,73],\"texture\":0},\"east\":{\"uv\":[55,26,63,30],\"texture\":0},\"south\":{\"uv\":[43,89,46,93],\"texture\":0},\"west\":{\"uv\":[57,8,65,12],\"texture\":0},\"up\":{\"uv\":[56,76,53,68],\"texture\":0},\"down\":{\"uv\":[59,68,56,76],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9a75352d-e331-7910-3b95-f3aa4d6fda3e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.82507,32.75,3.38425],\"to\":[4.82507,37.25,7.88425],\"autouv\":0,\"color\":1,\"rotation\":[-1.55797,15.71814,22.94361],\"origin\":[3.32507,35.25,5.13425],\"faces\":{\"north\":{\"uv\":[0,88,3,93],\"texture\":0},\"east\":{\"uv\":[67,0,72,5],\"texture\":0},\"south\":{\"uv\":[6,88,9,93],\"texture\":0},\"west\":{\"uv\":[67,10,72,15],\"texture\":0},\"up\":{\"uv\":[19,93,16,88],\"texture\":0},\"down\":{\"uv\":[22,88,19,93],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fefb8a0c-4dc9-eb37-14ea-19ed11636ce9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.82507,32.75,-2.88425],\"to\":[4.82507,37.25,1.61575],\"autouv\":0,\"color\":1,\"rotation\":[1.55797,-15.71814,22.94361],\"origin\":[3.32507,35.25,-0.13425],\"faces\":{\"north\":{\"uv\":[64,87,67,92],\"texture\":0},\"east\":{\"uv\":[65,5,70,10],\"texture\":0},\"south\":{\"uv\":[67,87,70,92],\"texture\":0},\"west\":{\"uv\":[16,65,21,70],\"texture\":0},\"up\":{\"uv\":[90,72,87,67],\"texture\":0},\"down\":{\"uv\":[73,87,70,92],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c5de4175-68a1-cce8-dda1-6f7519bc1b2c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.75,35.025,-2.875],\"to\":[2.75,36.275,-0.125],\"autouv\":0,\"color\":1,\"rotation\":[-12.5,0,0],\"origin\":[0,36,-1.25],\"faces\":{\"north\":{\"uv\":[64,103,70,104],\"texture\":0},\"east\":{\"uv\":[115,28,118,29],\"texture\":0},\"south\":{\"uv\":[103,64,109,65],\"texture\":0},\"west\":{\"uv\":[115,33,118,34],\"texture\":0},\"up\":{\"uv\":[83,37,77,34],\"texture\":0},\"down\":{\"uv\":[83,37,77,40],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"02fddf8c-7397-7a63-4a7c-c467b91d5448\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.75,35.025,5.125],\"to\":[2.75,36.275,7.875],\"autouv\":0,\"color\":1,\"rotation\":[12.5,0,0],\"origin\":[0,36,6.25],\"faces\":{\"north\":{\"uv\":[18,104,24,105],\"texture\":0},\"east\":{\"uv\":[115,59,118,60],\"texture\":0},\"south\":{\"uv\":[24,104,30,105],\"texture\":0},\"west\":{\"uv\":[115,65,118,66],\"texture\":0},\"up\":{\"uv\":[83,43,77,40],\"texture\":0},\"down\":{\"uv\":[83,49,77,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b4c05bb6-f96e-2f4d-93ec-7e5dd4a9252d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[7.41742,35.25,-0.15232],\"to\":[12.91742,37.45,5.94768],\"autouv\":0,\"color\":6,\"origin\":[8.51742,34.15,2.34768],\"faces\":{\"north\":{\"uv\":[92,19,98,21],\"texture\":0},\"east\":{\"uv\":[92,52,98,54],\"texture\":0},\"south\":{\"uv\":[64,92,70,94],\"texture\":0},\"west\":{\"uv\":[70,92,76,94],\"texture\":0},\"up\":{\"uv\":[61,56,55,50],\"texture\":0},\"down\":{\"uv\":[6,56,0,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9681954d-8e83-f105-06c4-e9fa51ed312b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.43951,31.95,3.81133],\"to\":[15.63951,34.425,6.61133],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,0],\"origin\":[13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[93,113,95,115],\"texture\":0},\"east\":{\"uv\":[108,43,111,45],\"texture\":0},\"south\":{\"uv\":[95,113,97,115],\"texture\":0},\"west\":{\"uv\":[47,108,50,110],\"texture\":0},\"up\":{\"uv\":[52,111,50,108],\"texture\":0},\"down\":{\"uv\":[110,50,108,53],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"953becdc-ed25-d2ba-a418-ea805624ad64\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[12.91742,31.95,1.24768],\"to\":[15.39242,34.425,4.54768],\"autouv\":0,\"color\":6,\"origin\":[13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[113,87,115,89],\"texture\":0},\"east\":{\"uv\":[35,108,38,110],\"texture\":0},\"south\":{\"uv\":[91,113,93,115],\"texture\":0},\"west\":{\"uv\":[108,35,111,37],\"texture\":0},\"up\":{\"uv\":[110,40,108,37],\"texture\":0},\"down\":{\"uv\":[110,40,108,43],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d97eb0a1-681c-6e0f-ae41-2dff2b52b9e4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.43951,31.95,-0.83633],\"to\":[15.63951,34.425,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,0],\"origin\":[13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[28,113,30,115],\"texture\":0},\"east\":{\"uv\":[102,107,105,109],\"texture\":0},\"south\":{\"uv\":[32,113,34,115],\"texture\":0},\"west\":{\"uv\":[2,108,5,110],\"texture\":0},\"up\":{\"uv\":[56,109,54,106],\"texture\":0},\"down\":{\"uv\":[20,107,18,110],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3a2af0ee-d802-79a2-cb5a-99891015755e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[15.39242,32.775,2.45768],\"to\":[16.49242,33.6,3.33768],\"autouv\":0,\"color\":6,\"origin\":[13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[35,118,36,119],\"texture\":0},\"east\":{\"uv\":[118,35,119,36],\"texture\":0},\"south\":{\"uv\":[36,118,37,119],\"texture\":0},\"west\":{\"uv\":[118,36,119,37],\"texture\":0},\"up\":{\"uv\":[39,119,38,118],\"texture\":0},\"down\":{\"uv\":[40,118,39,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7acdfb4e-2563-277e-8412-a2a4d2fab162\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[12.88951,34.0675,-0.28633],\"to\":[15.08951,36.5425,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,12.5],\"origin\":[13.34367,35.855,2.8875],\"faces\":{\"north\":{\"uv\":[69,113,71,115],\"texture\":0},\"east\":{\"uv\":[113,69,115,71],\"texture\":0},\"south\":{\"uv\":[71,113,73,115],\"texture\":0},\"west\":{\"uv\":[76,113,78,115],\"texture\":0},\"up\":{\"uv\":[80,115,78,113],\"texture\":0},\"down\":{\"uv\":[115,78,113,80],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f193499c-22db-5035-ce51-f61078f27294\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[12.36742,34.0675,1.24768],\"to\":[14.84242,36.5425,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,12.5],\"origin\":[13.34367,35.855,2.8875],\"faces\":{\"north\":{\"uv\":[67,113,69,115],\"texture\":0},\"east\":{\"uv\":[108,6,111,8],\"texture\":0},\"south\":{\"uv\":[113,67,115,69],\"texture\":0},\"west\":{\"uv\":[20,108,23,110],\"texture\":0},\"up\":{\"uv\":[63,110,61,107],\"texture\":0},\"down\":{\"uv\":[78,107,76,110],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8d01d4a6-df49-be4c-8e8a-7b488fc41a79\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[12.88951,34.0675,3.81133],\"to\":[15.08951,36.5425,6.06133],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,12.5],\"origin\":[13.34367,35.855,2.8875],\"faces\":{\"north\":{\"uv\":[42,113,44,115],\"texture\":0},\"east\":{\"uv\":[113,53,115,55],\"texture\":0},\"south\":{\"uv\":[55,113,57,115],\"texture\":0},\"west\":{\"uv\":[113,55,115,57],\"texture\":0},\"up\":{\"uv\":[115,59,113,57],\"texture\":0},\"down\":{\"uv\":[115,65,113,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"29a5e95c-df63-f7b3-7495-1a02c37f40b8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.95451,36.02,0.51367],\"to\":[14.15451,38.495,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,25],\"origin\":[12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[84,113,86,115],\"texture\":0},\"east\":{\"uv\":[117,35,118,37],\"texture\":0},\"south\":{\"uv\":[87,113,89,115],\"texture\":0},\"west\":{\"uv\":[37,117,38,119],\"texture\":0},\"up\":{\"uv\":[119,38,117,37],\"texture\":0},\"down\":{\"uv\":[40,117,38,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"34e10c22-6551-673a-cd19-e2d348ba1e23\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.43242,36.02,1.24768],\"to\":[13.90742,38.495,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,25],\"origin\":[12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[82,113,84,115],\"texture\":0},\"east\":{\"uv\":[23,108,26,110],\"texture\":0},\"south\":{\"uv\":[113,82,115,84],\"texture\":0},\"west\":{\"uv\":[108,24,111,26],\"texture\":0},\"up\":{\"uv\":[86,110,84,107],\"texture\":0},\"down\":{\"uv\":[35,108,33,111],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6952340a-c4cc-6f9f-7cae-86bf966078b4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.95451,36.02,3.81133],\"to\":[14.15451,38.495,5.26133],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,25],\"origin\":[12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[80,113,82,115],\"texture\":0},\"east\":{\"uv\":[117,31,118,33],\"texture\":0},\"south\":{\"uv\":[113,80,115,82],\"texture\":0},\"west\":{\"uv\":[32,117,33,119],\"texture\":0},\"up\":{\"uv\":[35,118,33,117],\"texture\":0},\"down\":{\"uv\":[37,117,35,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"137bc3e4-445b-981d-20fd-f1039c3c2cbf\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.90742,36.845,2.45768],\"to\":[15.00742,37.67,3.33768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,25],\"origin\":[12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[118,31,119,32],\"texture\":0},\"east\":{\"uv\":[118,32,119,33],\"texture\":0},\"south\":{\"uv\":[33,118,34,119],\"texture\":0},\"west\":{\"uv\":[118,33,119,34],\"texture\":0},\"up\":{\"uv\":[35,119,34,118],\"texture\":0},\"down\":{\"uv\":[119,34,118,35],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"84f643b0-2711-5778-7f8c-81ccbc28a2b1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.2088,37.2,0.6688],\"to\":[12.6088,38.575,5.0688],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,7.5],\"origin\":[10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[113,27,117,28],\"texture\":0},\"east\":{\"uv\":[113,31,117,32],\"texture\":0},\"south\":{\"uv\":[113,32,117,33],\"texture\":0},\"west\":{\"uv\":[113,35,117,36],\"texture\":0},\"up\":{\"uv\":[87,22,83,18],\"texture\":0},\"down\":{\"uv\":[87,22,83,26],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0721e534-324b-d2ee-e49b-171b151cd7b6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[7.69242,33.05,-0.70232],\"to\":[12.91742,35.25,6.49768],\"autouv\":0,\"color\":6,\"origin\":[8.51742,31.95,2.34768],\"faces\":{\"north\":{\"uv\":[96,7,101,9],\"texture\":0},\"east\":{\"uv\":[88,0,95,2],\"texture\":0},\"south\":{\"uv\":[14,96,19,98],\"texture\":0},\"west\":{\"uv\":[88,2,95,4],\"texture\":0},\"up\":{\"uv\":[52,63,47,56],\"texture\":0},\"down\":{\"uv\":[57,56,52,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4dc33995-1196-37e1-bf4c-f3fe418da321\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[7.96742,30.85,-1.25232],\"to\":[12.91742,33.05,7.04768],\"autouv\":0,\"color\":6,\"origin\":[8.51742,29.75,2.34768],\"faces\":{\"north\":{\"uv\":[96,17,101,19],\"texture\":0},\"east\":{\"uv\":[79,16,87,18],\"texture\":0},\"south\":{\"uv\":[96,24,101,26],\"texture\":0},\"west\":{\"uv\":[79,32,87,34],\"texture\":0},\"up\":{\"uv\":[26,58,21,50],\"texture\":0},\"down\":{\"uv\":[31,51,26,59],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"bdf0da6b-28a5-f3e1-7130-ad8588783047\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.96451,29.75,3.81133],\"to\":[15.36451,32.225,7.16133],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,-15],\"origin\":[13.61867,31.5375,2.8875],\"faces\":{\"north\":{\"uv\":[101,81,105,83],\"texture\":0},\"east\":{\"uv\":[108,69,111,71],\"texture\":0},\"south\":{\"uv\":[101,83,105,85],\"texture\":0},\"west\":{\"uv\":[78,108,81,110],\"texture\":0},\"up\":{\"uv\":[97,12,93,9],\"texture\":0},\"down\":{\"uv\":[97,12,93,15],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"47499439-5a44-785c-6e79-f143d2ce5073\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[12.64242,29.75,1.24768],\"to\":[15.11742,32.225,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-15],\"origin\":[13.61867,31.5375,2.8875],\"faces\":{\"north\":{\"uv\":[97,113,99,115],\"texture\":0},\"east\":{\"uv\":[108,65,111,67],\"texture\":0},\"south\":{\"uv\":[113,100,115,102],\"texture\":0},\"west\":{\"uv\":[108,67,111,69],\"texture\":0},\"up\":{\"uv\":[54,111,52,108],\"texture\":0},\"down\":{\"uv\":[110,75,108,78],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fa971384-8820-d365-fcb5-a99f37f4f75f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.96451,29.75,-1.38633],\"to\":[15.36451,32.225,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,-15],\"origin\":[13.61867,31.5375,2.8875],\"faces\":{\"north\":{\"uv\":[101,77,105,79],\"texture\":0},\"east\":{\"uv\":[108,53,111,55],\"texture\":0},\"south\":{\"uv\":[101,79,105,81],\"texture\":0},\"west\":{\"uv\":[108,55,111,57],\"texture\":0},\"up\":{\"uv\":[80,95,76,92],\"texture\":0},\"down\":{\"uv\":[4,93,0,96],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"560d51d1-acc3-bfae-e3cd-a81b839151ae\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.0338,38.575,1.4938],\"to\":[11.7838,38.875,4.2438],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,7.5],\"origin\":[10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[116,13,119,14],\"texture\":0},\"east\":{\"uv\":[116,14,119,15],\"texture\":0},\"south\":{\"uv\":[116,15,119,16],\"texture\":0},\"west\":{\"uv\":[116,16,119,17],\"texture\":0},\"up\":{\"uv\":[23,103,20,100],\"texture\":0},\"down\":{\"uv\":[26,100,23,103],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b2a87c88-5035-521e-27bc-4aeac5271565\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.81742,29.45,0.97268],\"to\":[14.21742,30.825,5.37268],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,0],\"origin\":[12.01742,31.1,2.07268],\"faces\":{\"north\":{\"uv\":[113,36,117,37],\"texture\":0},\"east\":{\"uv\":[113,43,117,44],\"texture\":0},\"south\":{\"uv\":[113,44,117,45],\"texture\":0},\"west\":{\"uv\":[49,113,53,114],\"texture\":0},\"up\":{\"uv\":[87,30,83,26],\"texture\":0},\"down\":{\"uv\":[87,34,83,38],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dfc17fef-2439-97eb-2f60-f03d53a9b1c0\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.31742,21.95,1.72268],\"to\":[13.21742,22.95,4.62268],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,0],\"origin\":[11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[19,116,22,117],\"texture\":0},\"east\":{\"uv\":[116,20,119,21],\"texture\":0},\"south\":{\"uv\":[116,21,119,22],\"texture\":0},\"west\":{\"uv\":[116,22,119,23],\"texture\":0},\"up\":{\"uv\":[29,103,26,100],\"texture\":0},\"down\":{\"uv\":[103,26,100,29],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"67a5e263-dd1e-b00e-9b95-a310c307f747\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.81742,24.95,1.22268],\"to\":[13.71742,29.825,5.12268],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,0],\"origin\":[11.76742,28.1,2.07268],\"faces\":{\"north\":{\"uv\":[74,44,78,49],\"texture\":0},\"east\":{\"uv\":[66,74,70,79],\"texture\":0},\"south\":{\"uv\":[31,75,35,80],\"texture\":0},\"west\":{\"uv\":[35,75,39,80],\"texture\":0},\"up\":{\"uv\":[68,87,64,83],\"texture\":0},\"down\":{\"uv\":[72,83,68,87],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"639ce11a-9c6a-6c42-3379-700283fa19d8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4,31,-0.25],\"to\":[8,35.5,5.25],\"autouv\":0,\"color\":1,\"origin\":[0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[27,73,31,78],\"texture\":0},\"east\":{\"uv\":[61,12,67,17],\"texture\":0},\"south\":{\"uv\":[73,34,77,39],\"texture\":0},\"west\":{\"uv\":[61,17,67,22],\"texture\":0},\"up\":{\"uv\":[19,76,15,70],\"texture\":0},\"down\":{\"uv\":[66,70,62,76],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d6db19a7-5456-61a3-c464-720c60d0ad72\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4,28,0],\"to\":[7,31,5],\"autouv\":0,\"color\":1,\"origin\":[0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[41,98,44,101],\"texture\":0},\"east\":{\"uv\":[84,4,89,7],\"texture\":0},\"south\":{\"uv\":[98,51,101,54],\"texture\":0},\"west\":{\"uv\":[30,84,35,87],\"texture\":0},\"up\":{\"uv\":[38,89,35,84],\"texture\":0},\"down\":{\"uv\":[41,84,38,89],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c1a5837c-09d2-f191-8927-3147276487e0\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4,24,-0.75],\"to\":[4,28,5.75],\"autouv\":0,\"color\":1,\"origin\":[0.5,31.5,3.25],\"faces\":{\"north\":{\"uv\":[59,34,67,38],\"texture\":0},\"east\":{\"uv\":[61,22,68,26],\"texture\":0},\"south\":{\"uv\":[31,60,39,64],\"texture\":0},\"west\":{\"uv\":[61,38,68,42],\"texture\":0},\"up\":{\"uv\":[35,42,27,35],\"texture\":0},\"down\":{\"uv\":[43,35,35,42],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a6fd2059-bc63-6b1e-1adb-b110a04138fe\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.1,24.675,0.5],\"to\":[5.925,29.35,4.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-40],\"origin\":[4.6,27.2,2.5],\"faces\":{\"north\":{\"uv\":[24,84,27,89],\"texture\":0},\"east\":{\"uv\":[72,54,76,59],\"texture\":0},\"south\":{\"uv\":[27,84,30,89],\"texture\":0},\"west\":{\"uv\":[72,59,76,64],\"texture\":0},\"up\":{\"uv\":[15,94,12,90],\"texture\":0},\"down\":{\"uv\":[93,13,90,17],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e3e2d9f0-1441-b200-dd73-7e161d346cb0\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1,28.35,-1.75],\"to\":[4.5,32.2,-0.75],\"autouv\":0,\"color\":1,\"rotation\":[-15,0,0],\"origin\":[2.5,30.2,-1.25],\"faces\":{\"north\":{\"uv\":[48,78,52,82],\"texture\":0},\"east\":{\"uv\":[100,110,101,114],\"texture\":0},\"south\":{\"uv\":[79,12,83,16],\"texture\":0},\"west\":{\"uv\":[7,111,8,115],\"texture\":0},\"up\":{\"uv\":[90,60,86,59],\"texture\":0},\"down\":{\"uv\":[91,55,87,56],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"bcdd576f-aa6b-f54b-c58b-1143876ab139\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1,28.35,5.725],\"to\":[4.5,32.2,6.725],\"autouv\":0,\"color\":1,\"rotation\":[15,0,0],\"origin\":[2.5,30.2,6.225],\"faces\":{\"north\":{\"uv\":[79,24,83,28],\"texture\":0},\"east\":{\"uv\":[9,111,10,115],\"texture\":0},\"south\":{\"uv\":[79,28,83,32],\"texture\":0},\"west\":{\"uv\":[14,111,15,115],\"texture\":0},\"up\":{\"uv\":[100,27,96,26],\"texture\":0},\"down\":{\"uv\":[115,28,111,29],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5990e492-34e4-0cf6-bf08-baf0fb599311\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-1,28,-0.75],\"to\":[1,32,5.75],\"autouv\":0,\"color\":1,\"origin\":[-4.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[73,100,75,104],\"texture\":0},\"east\":{\"uv\":[61,42,68,46],\"texture\":0},\"south\":{\"uv\":[99,100,101,104],\"texture\":0},\"west\":{\"uv\":[61,46,68,50],\"texture\":0},\"up\":{\"uv\":[52,93,50,86],\"texture\":0},\"down\":{\"uv\":[35,87,33,94],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"550e740c-3b6c-a212-ebfd-d12fbacfee1c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.5,32,-2.25],\"to\":[5.5,35.5,7.25],\"autouv\":0,\"color\":1,\"origin\":[0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[44,14,55,18],\"texture\":0},\"east\":{\"uv\":[51,0,61,4],\"texture\":0},\"south\":{\"uv\":[44,18,55,22],\"texture\":0},\"west\":{\"uv\":[51,4,61,8],\"texture\":0},\"up\":{\"uv\":[38,35,27,25],\"texture\":0},\"down\":{\"uv\":[44,0,33,10],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"26ce26ab-08fe-cbe9-470c-982100b16eeb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-7,28,0],\"to\":[-4,31,5],\"autouv\":0,\"color\":1,\"origin\":[-0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[98,54,101,57],\"texture\":0},\"east\":{\"uv\":[86,56,91,59],\"texture\":0},\"south\":{\"uv\":[98,65,101,68],\"texture\":0},\"west\":{\"uv\":[87,19,92,22],\"texture\":0},\"up\":{\"uv\":[90,27,87,22],\"texture\":0},\"down\":{\"uv\":[33,87,30,92],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"80406cd6-c894-2499-2966-49eeeb4d05b9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.925,24.675,0.5],\"to\":[-3.1,29.35,4.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,40],\"origin\":[-4.6,27.2,2.5],\"faces\":{\"north\":{\"uv\":[87,9,90,14],\"texture\":0},\"east\":{\"uv\":[19,73,23,78],\"texture\":0},\"south\":{\"uv\":[87,14,90,19],\"texture\":0},\"west\":{\"uv\":[23,73,27,78],\"texture\":0},\"up\":{\"uv\":[55,94,52,90],\"texture\":0},\"down\":{\"uv\":[40,91,37,95],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"20981f6a-ae7b-25df-06bc-490e3d7ff703\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-8,31,-0.25],\"to\":[-4,35.5,5.25],\"autouv\":0,\"color\":1,\"origin\":[-0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[8,73,12,78],\"texture\":0},\"east\":{\"uv\":[41,59,47,64],\"texture\":0},\"south\":{\"uv\":[73,18,77,23],\"texture\":0},\"west\":{\"uv\":[61,0,67,5],\"texture\":0},\"up\":{\"uv\":[36,75,32,69],\"texture\":0},\"down\":{\"uv\":[40,69,36,75],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b413c645-64a0-6eeb-9cfc-b24522d8f63a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.82507,32.75,-2.88425],\"to\":[-1.82507,37.25,1.61575],\"autouv\":0,\"color\":1,\"rotation\":[1.55797,15.71814,-22.94361],\"origin\":[-3.32507,35.25,-0.13425],\"faces\":{\"north\":{\"uv\":[55,86,58,91],\"texture\":0},\"east\":{\"uv\":[57,63,62,68],\"texture\":0},\"south\":{\"uv\":[58,86,61,91],\"texture\":0},\"west\":{\"uv\":[28,64,33,69],\"texture\":0},\"up\":{\"uv\":[64,91,61,86],\"texture\":0},\"down\":{\"uv\":[89,72,86,77],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c668b80a-6004-f47e-6897-f60fe8fae744\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-7.32507,33,-2.88425],\"to\":[-4.32507,37.5,1.61575],\"autouv\":0,\"color\":1,\"rotation\":[5,15,-10],\"origin\":[-3.32507,35.25,-0.13425],\"faces\":{\"north\":{\"uv\":[85,42,88,47],\"texture\":0},\"east\":{\"uv\":[47,63,52,68],\"texture\":0},\"south\":{\"uv\":[52,85,55,90],\"texture\":0},\"west\":{\"uv\":[52,63,57,68],\"texture\":0},\"up\":{\"uv\":[88,83,85,78],\"texture\":0},\"down\":{\"uv\":[50,86,47,91],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1652a13e-4cf6-7d97-cf8f-1de6a42a62f9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-7.32507,33,3.38425],\"to\":[-4.32507,37.5,7.88425],\"autouv\":0,\"color\":1,\"rotation\":[-5,-15,-10],\"origin\":[-3.32507,35.25,5.13425],\"faces\":{\"north\":{\"uv\":[84,67,87,72],\"texture\":0},\"east\":{\"uv\":[62,60,67,65],\"texture\":0},\"south\":{\"uv\":[84,83,87,88],\"texture\":0},\"west\":{\"uv\":[23,63,28,68],\"texture\":0},\"up\":{\"uv\":[13,90,10,85],\"texture\":0},\"down\":{\"uv\":[16,85,13,90],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"bb38f1ab-01f0-5aff-e1a3-8322fb354dda\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.82507,32.75,3.38425],\"to\":[-1.82507,37.25,7.88425],\"autouv\":0,\"color\":1,\"rotation\":[-1.55797,-15.71814,-22.94361],\"origin\":[-3.32507,35.25,5.13425],\"faces\":{\"north\":{\"uv\":[41,84,44,89],\"texture\":0},\"east\":{\"uv\":[61,50,66,55],\"texture\":0},\"south\":{\"uv\":[44,84,47,89],\"texture\":0},\"west\":{\"uv\":[62,55,67,60],\"texture\":0},\"up\":{\"uv\":[87,56,84,51],\"texture\":0},\"down\":{\"uv\":[87,62,84,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ca345538-c5b6-cc10-73dd-3f9acc846f95\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4,28,-1.25],\"to\":[-1,32,6.25],\"autouv\":0,\"color\":1,\"origin\":[-0.5,35.5,3.25],\"faces\":{\"north\":{\"uv\":[9,90,12,94],\"texture\":0},\"east\":{\"uv\":[23,59,31,63],\"texture\":0},\"south\":{\"uv\":[90,9,93,13],\"texture\":0},\"west\":{\"uv\":[59,30,67,34],\"texture\":0},\"up\":{\"uv\":[62,76,59,68],\"texture\":0},\"down\":{\"uv\":[3,69,0,77],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"004b5756-3745-4694-ac32-533f6ec57441\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.5,28.35,5.725],\"to\":[-1,32.2,6.725],\"autouv\":0,\"color\":1,\"rotation\":[15,0,0],\"origin\":[-2.5,30.2,6.225],\"faces\":{\"north\":{\"uv\":[63,26,67,30],\"texture\":0},\"east\":{\"uv\":[37,110,38,114],\"texture\":0},\"south\":{\"uv\":[43,64,47,68],\"texture\":0},\"west\":{\"uv\":[86,110,87,114],\"texture\":0},\"up\":{\"uv\":[72,26,68,25],\"texture\":0},\"down\":{\"uv\":[76,4,72,5],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"17175f5b-35b1-3d5b-3d47-33e3b89cfcf7\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.3,21.95,0.75],\"to\":[3.55,24.45,4.25],\"autouv\":0,\"color\":1,\"rotation\":[0,0,10],\"origin\":[2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[44,30,48,33],\"texture\":0},\"east\":{\"uv\":[61,5,65,8],\"texture\":0},\"south\":{\"uv\":[90,22,94,25],\"texture\":0},\"west\":{\"uv\":[90,32,94,35],\"texture\":0},\"up\":{\"uv\":[69,83,65,79],\"texture\":0},\"down\":{\"uv\":[73,79,69,83],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"04c0418d-5912-cd30-2b8b-0ad684dbc87f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.21451,28,3.81133],\"to\":[14.61451,30.475,6.66133],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,-7.5],\"origin\":[12.86867,29.7875,2.8875],\"faces\":{\"north\":{\"uv\":[101,100,105,102],\"texture\":0},\"east\":{\"uv\":[108,78,111,80],\"texture\":0},\"south\":{\"uv\":[102,21,106,23],\"texture\":0},\"west\":{\"uv\":[108,80,111,82],\"texture\":0},\"up\":{\"uv\":[19,96,15,93],\"texture\":0},\"down\":{\"uv\":[84,93,80,96],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"16fc3b7f-37f9-d76e-bafd-e1397a513ffd\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.89242,28,1.24768],\"to\":[14.36742,30.475,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-7.5],\"origin\":[12.86867,29.7875,2.8875],\"faces\":{\"north\":{\"uv\":[101,113,103,115],\"texture\":0},\"east\":{\"uv\":[81,108,84,110],\"texture\":0},\"south\":{\"uv\":[113,102,115,104],\"texture\":0},\"west\":{\"uv\":[108,82,111,84],\"texture\":0},\"up\":{\"uv\":[93,111,91,108],\"texture\":0},\"down\":{\"uv\":[95,108,93,111],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"419267b6-458e-9b4f-83d2-43b0e6c69f8d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.21451,28,-0.88633],\"to\":[14.61451,30.475,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,-7.5],\"origin\":[12.86867,29.7875,2.8875],\"faces\":{\"north\":{\"uv\":[102,47,106,49],\"texture\":0},\"east\":{\"uv\":[95,108,98,110],\"texture\":0},\"south\":{\"uv\":[49,102,53,104],\"texture\":0},\"west\":{\"uv\":[98,108,101,110],\"texture\":0},\"up\":{\"uv\":[97,94,93,91],\"texture\":0},\"down\":{\"uv\":[10,94,6,97],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b58b8a38-dee1-2ef6-f951-1f08a07bdbf4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.71451,26.25,3.81133],\"to\":[14.11451,28.725,5.66133],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,0],\"origin\":[12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[102,49,106,51],\"texture\":0},\"east\":{\"uv\":[109,113,111,115],\"texture\":0},\"south\":{\"uv\":[102,57,106,59],\"texture\":0},\"west\":{\"uv\":[113,111,115,113],\"texture\":0},\"up\":{\"uv\":[106,61,102,59],\"texture\":0},\"down\":{\"uv\":[106,61,102,63],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ccf114a8-f053-924b-d8ca-24b0a70368da\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.86742,27.075,2.45768],\"to\":[14.96742,27.9,3.33768],\"autouv\":0,\"color\":6,\"origin\":[12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[43,118,44,119],\"texture\":0},\"east\":{\"uv\":[118,43,119,44],\"texture\":0},\"south\":{\"uv\":[44,118,45,119],\"texture\":0},\"west\":{\"uv\":[118,44,119,45],\"texture\":0},\"up\":{\"uv\":[119,46,118,45],\"texture\":0},\"down\":{\"uv\":[47,118,46,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fdc93b97-72a5-e783-9db3-5a032ac3608a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.39242,26.25,1.24768],\"to\":[13.86742,28.725,4.54768],\"autouv\":0,\"color\":6,\"origin\":[12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[113,113,115,115],\"texture\":0},\"east\":{\"uv\":[108,100,111,102],\"texture\":0},\"south\":{\"uv\":[0,114,2,116],\"texture\":0},\"west\":{\"uv\":[108,102,111,104],\"texture\":0},\"up\":{\"uv\":[107,111,105,108],\"texture\":0},\"down\":{\"uv\":[109,108,107,111],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3251f438-779f-574a-a206-d180ea007ccb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.71451,26.25,0.11367],\"to\":[14.11451,28.725,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,0],\"origin\":[12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[79,102,83,104],\"texture\":0},\"east\":{\"uv\":[114,0,116,2],\"texture\":0},\"south\":{\"uv\":[83,102,87,104],\"texture\":0},\"west\":{\"uv\":[2,114,4,116],\"texture\":0},\"up\":{\"uv\":[106,87,102,85],\"texture\":0},\"down\":{\"uv\":[106,87,102,89],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fa153ca0-6723-e9f0-8cfb-7a3617eedafe\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.50662,21.95,-0.99545],\"to\":[3.50662,24.2,2.25455],\"autouv\":0,\"color\":1,\"rotation\":[0,-22.5,10],\"origin\":[2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[106,20,109,22],\"texture\":0},\"east\":{\"uv\":[106,30,109,32],\"texture\":0},\"south\":{\"uv\":[106,32,109,34],\"texture\":0},\"west\":{\"uv\":[33,106,36,108],\"texture\":0},\"up\":{\"uv\":[31,100,28,97],\"texture\":0},\"down\":{\"uv\":[34,97,31,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"722552e7-6fd6-e7ea-9dab-4d9bc3971265\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.50662,21.95,2.74545],\"to\":[3.50662,24.2,5.99545],\"autouv\":0,\"color\":1,\"rotation\":[0,22.5,10],\"origin\":[2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[106,96,109,98],\"texture\":0},\"east\":{\"uv\":[106,98,109,100],\"texture\":0},\"south\":{\"uv\":[99,106,102,108],\"texture\":0},\"west\":{\"uv\":[106,104,109,106],\"texture\":0},\"up\":{\"uv\":[17,101,14,98],\"texture\":0},\"down\":{\"uv\":[101,19,98,22],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6d219fa0-914b-4c91-2184-7e3e52b423bd\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.55,21.95,-0.5],\"to\":[4.8,24.2,5.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,10],\"origin\":[2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[45,93,46,95],\"texture\":0},\"east\":{\"uv\":[90,17,96,19],\"texture\":0},\"south\":{\"uv\":[14,94,15,96],\"texture\":0},\"west\":{\"uv\":[90,25,96,27],\"texture\":0},\"up\":{\"uv\":[88,108,87,102],\"texture\":0},\"down\":{\"uv\":[99,102,98,108],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fc34e1fb-b43d-b65a-94a7-9137ff36bdb2\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4.6,19.875,-0.5],\"to\":[5.85,22.125,5.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,10],\"origin\":[3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[116,111,117,113],\"texture\":0},\"east\":{\"uv\":[90,63,96,65],\"texture\":0},\"south\":{\"uv\":[114,116,115,118],\"texture\":0},\"west\":{\"uv\":[90,65,96,67],\"texture\":0},\"up\":{\"uv\":[10,111,9,105],\"texture\":0},\"down\":{\"uv\":[33,105,32,111],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7069c39d-7c58-42b2-26d5-f9812eb9eece\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.55662,19.875,-0.99545],\"to\":[4.55662,22.125,2.25455],\"autouv\":0,\"color\":1,\"rotation\":[0,-22.5,10],\"origin\":[3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[105,81,108,83],\"texture\":0},\"east\":{\"uv\":[105,83,108,85],\"texture\":0},\"south\":{\"uv\":[105,100,108,102],\"texture\":0},\"west\":{\"uv\":[105,102,108,104],\"texture\":0},\"up\":{\"uv\":[22,100,19,97],\"texture\":0},\"down\":{\"uv\":[28,97,25,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3a929634-fa05-3210-ff3e-5605ad95f727\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.55662,19.875,2.74545],\"to\":[4.55662,22.125,5.99545],\"autouv\":0,\"color\":1,\"rotation\":[0,22.5,10],\"origin\":[3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[75,105,78,107],\"texture\":0},\"east\":{\"uv\":[105,75,108,77],\"texture\":0},\"south\":{\"uv\":[105,77,108,79],\"texture\":0},\"west\":{\"uv\":[105,79,108,81],\"texture\":0},\"up\":{\"uv\":[14,100,11,97],\"texture\":0},\"down\":{\"uv\":[100,12,97,15],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ed0088d8-cbda-85d1-1d53-989379b69dbe\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.25,19.875,0.75],\"to\":[4.6,22.125,4.25],\"autouv\":0,\"color\":1,\"rotation\":[0,0,10],\"origin\":[3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[95,29,100,31],\"texture\":0},\"east\":{\"uv\":[57,12,61,14],\"texture\":0},\"south\":{\"uv\":[38,95,43,97],\"texture\":0},\"west\":{\"uv\":[73,23,77,25],\"texture\":0},\"up\":{\"uv\":[77,68,72,64],\"texture\":0},\"down\":{\"uv\":[77,68,72,72],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4fd10ae8-f304-3b56-048e-f387a9b4c279\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.50662,21.95,-0.99545],\"to\":[-0.50662,24.2,2.25455],\"autouv\":0,\"color\":1,\"rotation\":[0,22.5,-10],\"origin\":[-2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[106,89,109,91],\"texture\":0},\"east\":{\"uv\":[92,106,95,108],\"texture\":0},\"south\":{\"uv\":[106,94,109,96],\"texture\":0},\"west\":{\"uv\":[95,106,98,108],\"texture\":0},\"up\":{\"uv\":[100,94,97,91],\"texture\":0},\"down\":{\"uv\":[3,98,0,101],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f78e3b4c-9ec3-6e3f-34b0-f82d64ce42f3\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.55662,19.875,-0.99545],\"to\":[-1.55662,22.125,2.25455],\"autouv\":0,\"color\":1,\"rotation\":[0,22.5,-10],\"origin\":[-3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[78,106,81,108],\"texture\":0},\"east\":{\"uv\":[81,106,84,108],\"texture\":0},\"south\":{\"uv\":[106,85,109,87],\"texture\":0},\"west\":{\"uv\":[106,87,109,89],\"texture\":0},\"up\":{\"uv\":[73,100,70,97],\"texture\":0},\"down\":{\"uv\":[76,97,73,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"108f8152-a871-a04e-3694-ee8429454f4d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.85,19.875,-0.5],\"to\":[-4.6,22.125,5.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-10],\"origin\":[-3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[0,117,1,119],\"texture\":0},\"east\":{\"uv\":[90,87,96,89],\"texture\":0},\"south\":{\"uv\":[1,117,2,119],\"texture\":0},\"west\":{\"uv\":[90,89,96,91],\"texture\":0},\"up\":{\"uv\":[12,112,11,106],\"texture\":0},\"down\":{\"uv\":[40,106,39,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9f7af667-56fc-1051-3344-31768975a667\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.8,21.95,-0.5],\"to\":[-3.55,24.2,5.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-10],\"origin\":[-2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[115,116,116,118],\"texture\":0},\"east\":{\"uv\":[90,70,96,72],\"texture\":0},\"south\":{\"uv\":[116,116,117,118],\"texture\":0},\"west\":{\"uv\":[90,85,96,87],\"texture\":0},\"up\":{\"uv\":[64,111,63,105],\"texture\":0},\"down\":{\"uv\":[11,106,10,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"efd0ecbf-1a4e-dc52-e0be-fe4c9b79e954\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.55662,19.875,2.74545],\"to\":[-1.55662,22.125,5.99545],\"autouv\":0,\"color\":1,\"rotation\":[0,-22.5,-10],\"origin\":[-3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[50,106,53,108],\"texture\":0},\"east\":{\"uv\":[106,57,109,59],\"texture\":0},\"south\":{\"uv\":[106,59,109,61],\"texture\":0},\"west\":{\"uv\":[106,61,109,63],\"texture\":0},\"up\":{\"uv\":[67,100,64,97],\"texture\":0},\"down\":{\"uv\":[70,97,67,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a9a3fdae-afce-dfd5-2b6d-f2eab998494f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.50662,21.95,2.74545],\"to\":[-0.50662,24.2,5.99545],\"autouv\":0,\"color\":1,\"rotation\":[0,-22.5,-10],\"origin\":[-2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[36,106,39,108],\"texture\":0},\"east\":{\"uv\":[106,46,109,48],\"texture\":0},\"south\":{\"uv\":[47,106,50,108],\"texture\":0},\"west\":{\"uv\":[106,48,109,50],\"texture\":0},\"up\":{\"uv\":[41,100,38,97],\"texture\":0},\"down\":{\"uv\":[58,97,55,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"db3d2176-8b10-9f62-b5ba-2582cd79869e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.6,19.875,0.75],\"to\":[0.25,22.125,4.25],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-10],\"origin\":[-3.975,21,2.5],\"faces\":{\"north\":{\"uv\":[95,42,100,44],\"texture\":0},\"east\":{\"uv\":[100,73,104,75],\"texture\":0},\"south\":{\"uv\":[95,44,100,46],\"texture\":0},\"west\":{\"uv\":[100,91,104,93],\"texture\":0},\"up\":{\"uv\":[77,76,72,72],\"texture\":0},\"down\":{\"uv\":[8,73,3,77],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"233e6b71-b925-8a60-e09d-c2c1d3dcc613\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.55,21.95,0.75],\"to\":[0.3,24.45,4.25],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-10],\"origin\":[-2.925,23.075,2.5],\"faces\":{\"north\":{\"uv\":[90,67,94,70],\"texture\":0},\"east\":{\"uv\":[90,76,94,79],\"texture\":0},\"south\":{\"uv\":[90,79,94,82],\"texture\":0},\"west\":{\"uv\":[90,82,94,85],\"texture\":0},\"up\":{\"uv\":[77,83,73,79],\"texture\":0},\"down\":{\"uv\":[81,79,77,83],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2e873e0e-30e9-2e8c-0ec5-76ae8cbbfc1c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.35,17.725,0.75],\"to\":[5.425,19.975,4.25],\"autouv\":0,\"color\":1,\"rotation\":[0,0,10],\"origin\":[4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[90,39,96,41],\"texture\":0},\"east\":{\"uv\":[98,22,102,24],\"texture\":0},\"south\":{\"uv\":[90,46,96,48],\"texture\":0},\"west\":{\"uv\":[99,15,103,17],\"texture\":0},\"up\":{\"uv\":[74,50,68,46],\"texture\":0},\"down\":{\"uv\":[9,69,3,73],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f4bd8628-fad1-fde9-fbb6-68b207058fad\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.38162,17.725,2.74545],\"to\":[5.38162,19.975,5.99545],\"autouv\":0,\"color\":1,\"rotation\":[0,22.5,10],\"origin\":[4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[105,37,108,39],\"texture\":0},\"east\":{\"uv\":[105,39,108,41],\"texture\":0},\"south\":{\"uv\":[105,42,108,44],\"texture\":0},\"west\":{\"uv\":[105,44,108,46],\"texture\":0},\"up\":{\"uv\":[99,99,96,96],\"texture\":0},\"down\":{\"uv\":[100,0,97,3],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4f38d578-03f6-6f4a-c0cf-9ade07a86ed9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.38162,17.725,-0.99545],\"to\":[5.38162,19.975,2.25455],\"autouv\":0,\"color\":1,\"rotation\":[0,-22.5,10],\"origin\":[4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[70,103,73,105],\"texture\":0},\"east\":{\"uv\":[75,103,78,105],\"texture\":0},\"south\":{\"uv\":[47,104,50,106],\"texture\":0},\"west\":{\"uv\":[50,104,53,106],\"texture\":0},\"up\":{\"uv\":[99,91,96,88],\"texture\":0},\"down\":{\"uv\":[96,96,93,99],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"00ac818e-4fca-67c4-cb16-1bf156ea1ec8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[5.425,17.725,-0.5],\"to\":[6.675,19.975,5.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,10],\"origin\":[4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[44,98,45,100],\"texture\":0},\"east\":{\"uv\":[90,35,96,37],\"texture\":0},\"south\":{\"uv\":[98,94,99,96],\"texture\":0},\"west\":{\"uv\":[90,37,96,39],\"texture\":0},\"up\":{\"uv\":[47,109,46,103],\"texture\":0},\"down\":{\"uv\":[57,103,56,109],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f9e2b3b4-83be-9820-5fd2-6cc5671c0d06\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.425,17.725,0.75],\"to\":[0.35,19.975,4.25],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-10],\"origin\":[-4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[90,59,96,61],\"texture\":0},\"east\":{\"uv\":[99,63,103,65],\"texture\":0},\"south\":{\"uv\":[90,61,96,63],\"texture\":0},\"west\":{\"uv\":[100,71,104,73],\"texture\":0},\"up\":{\"uv\":[15,73,9,69],\"texture\":0},\"down\":{\"uv\":[32,69,26,73],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"91c6c002-aa48-c40c-eb94-c2d2c6ce9387\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.38162,17.725,-0.99545],\"to\":[-2.38162,19.975,2.25455],\"autouv\":0,\"color\":1,\"rotation\":[0,22.5,-10],\"origin\":[-4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[105,65,108,67],\"texture\":0},\"east\":{\"uv\":[105,67,108,69],\"texture\":0},\"south\":{\"uv\":[105,69,108,71],\"texture\":0},\"west\":{\"uv\":[70,105,73,107],\"texture\":0},\"up\":{\"uv\":[11,100,8,97],\"texture\":0},\"down\":{\"uv\":[100,9,97,12],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a8cf07da-f774-cb70-728f-fddd7dd86939\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-6.675,17.725,-0.5],\"to\":[-5.425,19.975,5.5],\"autouv\":0,\"color\":1,\"rotation\":[0,0,-10],\"origin\":[-4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[2,101,3,103],\"texture\":0},\"east\":{\"uv\":[90,48,96,50],\"texture\":0},\"south\":{\"uv\":[97,116,98,118],\"texture\":0},\"west\":{\"uv\":[90,50,96,52],\"texture\":0},\"up\":{\"uv\":[58,109,57,103],\"texture\":0},\"down\":{\"uv\":[87,104,86,110],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"174a8673-5e4f-54fb-8845-de9053585a39\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.38162,17.725,2.74545],\"to\":[-2.38162,19.975,5.99545],\"autouv\":0,\"color\":1,\"rotation\":[0,-22.5,-10],\"origin\":[-4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[105,51,108,53],\"texture\":0},\"east\":{\"uv\":[105,53,108,55],\"texture\":0},\"south\":{\"uv\":[105,55,108,57],\"texture\":0},\"west\":{\"uv\":[60,105,63,107],\"texture\":0},\"up\":{\"uv\":[100,6,97,3],\"texture\":0},\"down\":{\"uv\":[8,97,5,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c7ff5c49-b255-2025-50e2-7dbc631d35b1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.84137,17.08997,-1.41551],\"to\":[3.34137,19.33997,0.83449],\"autouv\":0,\"color\":1,\"rotation\":[2.59945,-7.70999,9.31682],\"origin\":[1.0023,20.45817,-0.72757],\"faces\":{\"north\":{\"uv\":[105,24,108,26],\"texture\":0},\"east\":{\"uv\":[112,48,114,50],\"texture\":0},\"south\":{\"uv\":[26,105,29,107],\"texture\":0},\"west\":{\"uv\":[53,112,55,114],\"texture\":0},\"up\":{\"uv\":[32,107,29,105],\"texture\":0},\"down\":{\"uv\":[108,35,105,37],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"43e12532-2dd1-ac2b-45a8-200ffb0c32eb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.43567,19.35056,-1.24746],\"to\":[2.93567,21.60056,1.00254],\"autouv\":0,\"color\":1,\"rotation\":[2.59945,-7.70999,9.31682],\"origin\":[1.0023,20.45817,-0.72757],\"faces\":{\"north\":{\"uv\":[112,29,114,31],\"texture\":0},\"east\":{\"uv\":[34,112,36,114],\"texture\":0},\"south\":{\"uv\":[39,112,41,114],\"texture\":0},\"west\":{\"uv\":[44,112,46,114],\"texture\":0},\"up\":{\"uv\":[48,114,46,112],\"texture\":0},\"down\":{\"uv\":[114,46,112,48],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b6c79338-054b-389c-c5fb-b4847f74bdf7\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.31323,21.57637,-0.98964],\"to\":[2.31323,24.07637,1.26036],\"autouv\":0,\"color\":1,\"rotation\":[2.59945,-7.70999,9.31682],\"origin\":[1.0023,20.45817,-0.72757],\"faces\":{\"north\":{\"uv\":[88,103,90,106],\"texture\":0},\"east\":{\"uv\":[12,104,14,107],\"texture\":0},\"south\":{\"uv\":[64,104,66,107],\"texture\":0},\"west\":{\"uv\":[66,104,68,107],\"texture\":0},\"up\":{\"uv\":[78,113,76,111],\"texture\":0},\"down\":{\"uv\":[80,111,78,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"92fea33d-fa66-6a60-cefd-7ddb861f07de\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.18677,21.57637,3.78964],\"to\":[1.81323,24.07637,6.03964],\"autouv\":0,\"color\":1,\"rotation\":[-2.59945,7.70999,9.31682],\"origin\":[1.0023,20.45817,0.72757],\"faces\":{\"north\":{\"uv\":[105,17,107,20],\"texture\":0},\"east\":{\"uv\":[20,105,22,108],\"texture\":0},\"south\":{\"uv\":[22,105,24,108],\"texture\":0},\"west\":{\"uv\":[24,105,26,108],\"texture\":0},\"up\":{\"uv\":[114,22,112,20],\"texture\":0},\"down\":{\"uv\":[28,112,26,114],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"17e9cc5d-478c-6abb-ce1d-6510a8f229e5\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.06433,19.35056,4.04746],\"to\":[2.43567,21.60056,6.29746],\"autouv\":0,\"color\":1,\"rotation\":[-2.59945,7.70999,9.31682],\"origin\":[1.0023,20.45817,0.72757],\"faces\":{\"north\":{\"uv\":[2,112,4,114],\"texture\":0},\"east\":{\"uv\":[10,112,12,114],\"texture\":0},\"south\":{\"uv\":[112,12,114,14],\"texture\":0},\"west\":{\"uv\":[112,14,114,16],\"texture\":0},\"up\":{\"uv\":[17,114,15,112],\"texture\":0},\"down\":{\"uv\":[21,112,19,114],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3904e256-e178-2e6c-d063-3e84a6ce82b6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.34137,17.08997,4.21551],\"to\":[2.84137,19.33997,6.46551],\"autouv\":0,\"color\":1,\"rotation\":[-2.59945,7.70999,9.31682],\"origin\":[1.0023,20.45817,0.72757],\"faces\":{\"north\":{\"uv\":[104,71,107,73],\"texture\":0},\"east\":{\"uv\":[111,78,113,80],\"texture\":0},\"south\":{\"uv\":[104,73,107,75],\"texture\":0},\"west\":{\"uv\":[80,111,82,113],\"texture\":0},\"up\":{\"uv\":[81,106,78,104],\"texture\":0},\"down\":{\"uv\":[84,104,81,106],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d6f505c5-44ae-c646-2aff-fbf44961b32d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.84137,17.08997,4.21551],\"to\":[-0.34137,19.33997,6.46551],\"autouv\":0,\"color\":1,\"rotation\":[-2.59945,-7.70999,-9.31682],\"origin\":[-1.0023,20.45817,0.72757],\"faces\":{\"north\":{\"uv\":[6,105,9,107],\"texture\":0},\"east\":{\"uv\":[0,112,2,114],\"texture\":0},\"south\":{\"uv\":[105,6,108,8],\"texture\":0},\"west\":{\"uv\":[112,0,114,2],\"texture\":0},\"up\":{\"uv\":[17,107,14,105],\"texture\":0},\"down\":{\"uv\":[20,105,17,107],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"82e0d2c1-d8ff-f573-2089-cb24e3f5066a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.43567,19.35056,4.04746],\"to\":[0.06433,21.60056,6.29746],\"autouv\":0,\"color\":1,\"rotation\":[-2.59945,-7.70999,-9.31682],\"origin\":[-1.0023,20.45817,0.72757],\"faces\":{\"north\":{\"uv\":[111,102,113,104],\"texture\":0},\"east\":{\"uv\":[105,111,107,113],\"texture\":0},\"south\":{\"uv\":[107,111,109,113],\"texture\":0},\"west\":{\"uv\":[111,108,113,110],\"texture\":0},\"up\":{\"uv\":[111,113,109,111],\"texture\":0},\"down\":{\"uv\":[113,110,111,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ee2d6ac7-e504-ec74-8603-62e046365557\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-1.81323,21.57637,3.78964],\"to\":[0.18677,24.07637,6.03964],\"autouv\":0,\"color\":1,\"rotation\":[-2.59945,-7.70999,-9.31682],\"origin\":[-1.0023,20.45817,0.72757],\"faces\":{\"north\":{\"uv\":[102,104,104,107],\"texture\":0},\"east\":{\"uv\":[104,104,106,107],\"texture\":0},\"south\":{\"uv\":[2,105,4,108],\"texture\":0},\"west\":{\"uv\":[4,105,6,108],\"texture\":0},\"up\":{\"uv\":[113,102,111,100],\"texture\":0},\"down\":{\"uv\":[103,111,101,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"58509bf9-3d6a-868c-d637-ff183a6cdc0b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.34137,17.08997,-1.41551],\"to\":[-0.84137,19.33997,0.83449],\"autouv\":0,\"color\":1,\"rotation\":[2.59945,7.70999,-9.31682],\"origin\":[-1.0023,20.45817,-0.72757],\"faces\":{\"north\":{\"uv\":[104,91,107,93],\"texture\":0},\"east\":{\"uv\":[95,111,97,113],\"texture\":0},\"south\":{\"uv\":[92,104,95,106],\"texture\":0},\"west\":{\"uv\":[97,111,99,113],\"texture\":0},\"up\":{\"uv\":[98,106,95,104],\"texture\":0},\"down\":{\"uv\":[102,104,99,106],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"caaf69a3-d39a-aa71-72dc-fd4da017e228\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.93567,19.35056,-1.24746],\"to\":[-0.43567,21.60056,1.00254],\"autouv\":0,\"color\":1,\"rotation\":[2.59945,7.70999,-9.31682],\"origin\":[-1.0023,20.45817,-0.72757],\"faces\":{\"north\":{\"uv\":[111,82,113,84],\"texture\":0},\"east\":{\"uv\":[84,111,86,113],\"texture\":0},\"south\":{\"uv\":[87,111,89,113],\"texture\":0},\"west\":{\"uv\":[111,87,113,89],\"texture\":0},\"up\":{\"uv\":[93,113,91,111],\"texture\":0},\"down\":{\"uv\":[95,111,93,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"08acc518-8e41-a360-e3fb-99702198e77e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.31323,21.57637,-0.98964],\"to\":[-0.31323,24.07637,1.26036],\"autouv\":0,\"color\":1,\"rotation\":[2.59945,7.70999,-9.31682],\"origin\":[-1.0023,20.45817,-0.72757],\"faces\":{\"north\":{\"uv\":[68,104,70,107],\"texture\":0},\"east\":{\"uv\":[73,104,75,107],\"texture\":0},\"south\":{\"uv\":[84,104,86,107],\"texture\":0},\"west\":{\"uv\":[90,104,92,107],\"texture\":0},\"up\":{\"uv\":[113,82,111,80],\"texture\":0},\"down\":{\"uv\":[84,111,82,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"61a1fc5e-ca0c-2440-50ad-c9ed748263f8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[0.9,15.35,-0.45],\"to\":[6.3,16.7,4.95],\"autouv\":0,\"color\":0,\"rotation\":[0,-45,0],\"origin\":[3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[110,5,115,6],\"texture\":0},\"east\":{\"uv\":[110,37,115,38],\"texture\":0},\"south\":{\"uv\":[110,38,115,39],\"texture\":0},\"west\":{\"uv\":[110,39,115,40],\"texture\":0},\"up\":{\"uv\":[72,20,67,15],\"texture\":0},\"down\":{\"uv\":[72,26,67,31],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fa05b37a-8d36-473a-80b6-04517fb820f1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.575,8.6,0.225],\"to\":[5.625,12.65,4.275],\"autouv\":0,\"color\":0,\"rotation\":[0,-45,0],\"origin\":[3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[80,52,84,56],\"texture\":0},\"east\":{\"uv\":[80,62,84,66],\"texture\":0},\"south\":{\"uv\":[80,66,84,70],\"texture\":0},\"west\":{\"uv\":[80,70,84,74],\"texture\":0},\"up\":{\"uv\":[16,85,12,81],\"texture\":0},\"down\":{\"uv\":[85,43,81,47],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1f0c2fcb-f77e-bb94-21d8-46a5aa7b0dd5\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.125,7.25,-0.225],\"to\":[6.075,8.6,4.725],\"autouv\":0,\"color\":0,\"rotation\":[0,-45,0],\"origin\":[3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[110,51,115,52],\"texture\":0},\"east\":{\"uv\":[110,52,115,53],\"texture\":0},\"south\":{\"uv\":[110,71,115,72],\"texture\":0},\"west\":{\"uv\":[110,72,115,73],\"texture\":0},\"up\":{\"uv\":[72,65,67,60],\"texture\":0},\"down\":{\"uv\":[72,65,67,70],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d9a8de97-fd96-8be7-1d32-234b444005dc\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.7838,38.575,1.9938],\"to\":[11.2838,39.375,3.4938],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,-2.5],\"origin\":[10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[117,26,119,27],\"texture\":0},\"east\":{\"uv\":[117,27,119,28],\"texture\":0},\"south\":{\"uv\":[28,117,30,118],\"texture\":0},\"west\":{\"uv\":[30,117,32,118],\"texture\":0},\"up\":{\"uv\":[14,115,12,113],\"texture\":0},\"down\":{\"uv\":[19,113,17,115],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"18eb35ac-628a-8ef8-3c6e-8dbac690df22\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.31742,18.95,1.27268],\"to\":[12.46742,19.95,4.47268],\"autouv\":0,\"color\":6,\"origin\":[11.76742,23.35,2.07268],\"faces\":{\"north\":{\"uv\":[118,46,119,47],\"texture\":0},\"east\":{\"uv\":[48,116,51,117],\"texture\":0},\"south\":{\"uv\":[47,118,48,119],\"texture\":0},\"west\":{\"uv\":[116,48,119,49],\"texture\":0},\"up\":{\"uv\":[4,119,3,116],\"texture\":0},\"down\":{\"uv\":[5,116,4,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"757cfe40-f697-b3d4-3ab5-931a0f300a16\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.56742,18.65,3.77268],\"to\":[12.21742,19.45,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[48,118,49,119],\"texture\":0},\"east\":{\"uv\":[49,118,50,119],\"texture\":0},\"south\":{\"uv\":[118,49,119,50],\"texture\":0},\"west\":{\"uv\":[50,118,51,119],\"texture\":0},\"up\":{\"uv\":[52,119,51,118],\"texture\":0},\"down\":{\"uv\":[119,51,118,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"70d08c0b-6a6f-3aec-4064-a6b8dc9beb84\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.56742,18.55,2.17268],\"to\":[12.21742,19.45,2.87268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[118,79,119,80],\"texture\":0},\"east\":{\"uv\":[81,118,82,119],\"texture\":0},\"south\":{\"uv\":[82,118,83,119],\"texture\":0},\"west\":{\"uv\":[118,82,119,83],\"texture\":0},\"up\":{\"uv\":[119,84,118,83],\"texture\":0},\"down\":{\"uv\":[119,84,118,85],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a9292015-e3c2-6dc9-675c-6234fe1a6667\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.56742,18.65,1.37268],\"to\":[12.21742,19.45,2.07268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[103,118,104,119],\"texture\":0},\"east\":{\"uv\":[118,103,119,104],\"texture\":0},\"south\":{\"uv\":[104,118,105,119],\"texture\":0},\"west\":{\"uv\":[118,104,119,105],\"texture\":0},\"up\":{\"uv\":[106,119,105,118],\"texture\":0},\"down\":{\"uv\":[119,105,118,106],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c5f19499-a1b1-8a9b-1503-6f6e12280777\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.56742,18.55,2.97268],\"to\":[12.21742,19.45,3.67268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[100,118,101,119],\"texture\":0},\"east\":{\"uv\":[118,100,119,101],\"texture\":0},\"south\":{\"uv\":[101,118,102,119],\"texture\":0},\"west\":{\"uv\":[118,101,119,102],\"texture\":0},\"up\":{\"uv\":[103,119,102,118],\"texture\":0},\"down\":{\"uv\":[119,102,118,103],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"69aa58aa-ca4d-d8ff-032f-fab509531f5a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.57992,18.7125,3.77268],\"to\":[12.22992,19.8125,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-135],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[118,75,119,76],\"texture\":0},\"east\":{\"uv\":[76,118,77,119],\"texture\":0},\"south\":{\"uv\":[77,118,78,119],\"texture\":0},\"west\":{\"uv\":[78,118,79,119],\"texture\":0},\"up\":{\"uv\":[119,79,118,78],\"texture\":0},\"down\":{\"uv\":[80,118,79,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e369a8bd-344b-ef80-43d3-765cbfcf672c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.57992,18.5125,2.97268],\"to\":[12.22992,19.7125,3.67268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-135],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[68,118,69,119],\"texture\":0},\"east\":{\"uv\":[69,118,70,119],\"texture\":0},\"south\":{\"uv\":[118,69,119,70],\"texture\":0},\"west\":{\"uv\":[71,118,72,119],\"texture\":0},\"up\":{\"uv\":[73,119,72,118],\"texture\":0},\"down\":{\"uv\":[119,74,118,75],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b4965020-4279-25d4-0eb4-1f7cccba52fe\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.57992,18.5125,2.17268],\"to\":[12.22992,19.7125,2.87268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-135],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[62,118,63,119],\"texture\":0},\"east\":{\"uv\":[63,118,64,119],\"texture\":0},\"south\":{\"uv\":[64,118,65,119],\"texture\":0},\"west\":{\"uv\":[118,64,119,65],\"texture\":0},\"up\":{\"uv\":[119,66,118,65],\"texture\":0},\"down\":{\"uv\":[68,118,67,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1a73524e-26f4-7fb3-97e2-8624cd92cede\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.57992,18.7125,1.37268],\"to\":[12.22992,19.8125,2.07268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-135],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[118,52,119,53],\"texture\":0},\"east\":{\"uv\":[118,53,119,54],\"texture\":0},\"south\":{\"uv\":[118,54,119,55],\"texture\":0},\"west\":{\"uv\":[58,118,59,119],\"texture\":0},\"up\":{\"uv\":[119,60,118,59],\"texture\":0},\"down\":{\"uv\":[62,118,61,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fb17833e-914e-9268-4340-f5603d6a03e4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.36742,18.05,2.17268],\"to\":[11.01742,18.95,2.87268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[118,96,119,97],\"texture\":0},\"east\":{\"uv\":[97,118,98,119],\"texture\":0},\"south\":{\"uv\":[118,97,119,98],\"texture\":0},\"west\":{\"uv\":[98,118,99,119],\"texture\":0},\"up\":{\"uv\":[119,99,118,98],\"texture\":0},\"down\":{\"uv\":[100,118,99,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b9e03e5e-eb23-1c48-294a-0003ec580bed\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.36742,18.05,2.97268],\"to\":[11.01742,18.95,3.67268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[118,93,119,94],\"texture\":0},\"east\":{\"uv\":[94,118,95,119],\"texture\":0},\"south\":{\"uv\":[118,94,119,95],\"texture\":0},\"west\":{\"uv\":[95,118,96,119],\"texture\":0},\"up\":{\"uv\":[119,96,118,95],\"texture\":0},\"down\":{\"uv\":[97,118,96,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"564eb56f-b9b3-74a8-a7bf-fec1f711bca9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.36742,18.15,1.37268],\"to\":[11.01742,18.95,2.07268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[118,90,119,91],\"texture\":0},\"east\":{\"uv\":[91,118,92,119],\"texture\":0},\"south\":{\"uv\":[118,91,119,92],\"texture\":0},\"west\":{\"uv\":[92,118,93,119],\"texture\":0},\"up\":{\"uv\":[119,93,118,92],\"texture\":0},\"down\":{\"uv\":[94,118,93,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a6fa060e-d211-974f-c460-2142b9213f67\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.36742,18.15,3.77268],\"to\":[11.01742,18.95,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[118,86,119,87],\"texture\":0},\"east\":{\"uv\":[118,87,119,88],\"texture\":0},\"south\":{\"uv\":[118,88,119,89],\"texture\":0},\"west\":{\"uv\":[89,118,90,119],\"texture\":0},\"up\":{\"uv\":[119,90,118,89],\"texture\":0},\"down\":{\"uv\":[91,118,90,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"bdd6d7a9-0e80-b7c7-aab3-b5f075817d54\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.51742,19.95,1.27268],\"to\":[11.66742,21.95,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[11.89242,20.95,2.87268],\"faces\":{\"north\":{\"uv\":[40,117,41,119],\"texture\":0},\"east\":{\"uv\":[5,109,8,111],\"texture\":0},\"south\":{\"uv\":[42,117,43,119],\"texture\":0},\"west\":{\"uv\":[12,109,15,111],\"texture\":0},\"up\":{\"uv\":[11,119,10,116],\"texture\":0},\"down\":{\"uv\":[12,116,11,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f8c3aa34-a09e-a0a1-10df-c672519263e3\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.81742,20.95,2.22268],\"to\":[12.71742,21.95,4.12268],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,0],\"origin\":[11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[117,38,119,39],\"texture\":0},\"east\":{\"uv\":[117,39,119,40],\"texture\":0},\"south\":{\"uv\":[117,40,119,41],\"texture\":0},\"west\":{\"uv\":[117,41,119,42],\"texture\":0},\"up\":{\"uv\":[12,116,10,114],\"texture\":0},\"down\":{\"uv\":[116,12,114,14],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"73685916-cd25-a75a-fa4e-c1371475dee6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.31742,23.95,1.72268],\"to\":[13.21742,24.95,4.62268],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,0],\"origin\":[11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[116,24,119,25],\"texture\":0},\"east\":{\"uv\":[116,25,119,26],\"texture\":0},\"south\":{\"uv\":[34,116,37,117],\"texture\":0},\"west\":{\"uv\":[116,47,119,48],\"texture\":0},\"up\":{\"uv\":[32,103,29,100],\"texture\":0},\"down\":{\"uv\":[103,29,100,32],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0da7fcdb-b43d-8b27-971b-6df95829641c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.81742,22.95,1.22268],\"to\":[13.71742,23.95,5.12268],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,0],\"origin\":[11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[105,113,109,114],\"texture\":0},\"east\":{\"uv\":[113,108,117,109],\"texture\":0},\"south\":{\"uv\":[113,109,117,110],\"texture\":0},\"west\":{\"uv\":[113,110,117,111],\"texture\":0},\"up\":{\"uv\":[87,42,83,38],\"texture\":0},\"down\":{\"uv\":[87,47,83,51],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"52345605-2265-faad-f6ad-eca1be3dec6c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.7838,39.175,2.1188],\"to\":[11.1588,39.975,3.4938],\"autouv\":0,\"color\":6,\"rotation\":[0,-45,-12.5],\"origin\":[10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[118,28,119,29],\"texture\":0},\"east\":{\"uv\":[29,118,30,119],\"texture\":0},\"south\":{\"uv\":[118,29,119,30],\"texture\":0},\"west\":{\"uv\":[30,118,31,119],\"texture\":0},\"up\":{\"uv\":[119,31,118,30],\"texture\":0},\"down\":{\"uv\":[32,118,31,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"22bbe239-0026-6040-7756-3fa9d0c984b8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.91742,35.25,-0.15232],\"to\":[-7.41742,37.45,5.94768],\"autouv\":0,\"color\":6,\"origin\":[-8.51742,34.15,2.34768],\"faces\":{\"north\":{\"uv\":[93,15,99,17],\"texture\":0},\"east\":{\"uv\":[94,31,100,33],\"texture\":0},\"south\":{\"uv\":[94,33,100,35],\"texture\":0},\"west\":{\"uv\":[49,94,55,96],\"texture\":0},\"up\":{\"uv\":[12,62,6,56],\"texture\":0},\"down\":{\"uv\":[18,56,12,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d603d704-1925-f44c-266d-72cc47243394\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.91742,33.05,-0.70232],\"to\":[-7.69242,35.25,6.49768],\"autouv\":0,\"color\":6,\"origin\":[-8.51742,31.95,2.34768],\"faces\":{\"north\":{\"uv\":[96,35,101,37],\"texture\":0},\"east\":{\"uv\":[88,42,95,44],\"texture\":0},\"south\":{\"uv\":[96,37,101,39],\"texture\":0},\"west\":{\"uv\":[88,44,95,46],\"texture\":0},\"up\":{\"uv\":[62,63,57,56],\"texture\":0},\"down\":{\"uv\":[23,58,18,65],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"508cda1a-e3ba-807d-6286-d5d1cc9fb6e4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.91742,30.85,-1.25232],\"to\":[-7.96742,33.05,7.04768],\"autouv\":0,\"color\":6,\"origin\":[-8.51742,29.75,2.34768],\"faces\":{\"north\":{\"uv\":[96,39,101,41],\"texture\":0},\"east\":{\"uv\":[82,60,90,62],\"texture\":0},\"south\":{\"uv\":[96,46,101,48],\"texture\":0},\"west\":{\"uv\":[83,30,91,32],\"texture\":0},\"up\":{\"uv\":[36,60,31,52],\"texture\":0},\"down\":{\"uv\":[41,52,36,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"54c37abf-dbde-9f65-0956-990938e33023\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.6088,37.2,0.6688],\"to\":[-8.2088,38.575,5.0688],\"autouv\":0,\"color\":6,\"rotation\":[0,45,-7.5],\"origin\":[-10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[114,23,118,24],\"texture\":0},\"east\":{\"uv\":[114,29,118,30],\"texture\":0},\"south\":{\"uv\":[114,30,118,31],\"texture\":0},\"west\":{\"uv\":[114,34,118,35],\"texture\":0},\"up\":{\"uv\":[76,87,72,83],\"texture\":0},\"down\":{\"uv\":[80,83,76,87],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"722703c7-5013-9ab7-215d-48e7c06ff787\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.21742,29.45,0.97268],\"to\":[-9.81742,30.825,5.37268],\"autouv\":0,\"color\":6,\"rotation\":[0,45,0],\"origin\":[-12.01742,31.1,2.07268],\"faces\":{\"north\":{\"uv\":[37,114,41,115],\"texture\":0},\"east\":{\"uv\":[44,114,48,115],\"texture\":0},\"south\":{\"uv\":[114,45,118,46],\"texture\":0},\"west\":{\"uv\":[114,46,118,47],\"texture\":0},\"up\":{\"uv\":[84,87,80,83],\"texture\":0},\"down\":{\"uv\":[4,84,0,88],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ef1ded35-14f3-8696-879a-601eae597e63\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.7838,38.575,1.4938],\"to\":[-9.0338,38.875,4.2438],\"autouv\":0,\"color\":6,\"rotation\":[0,45,-7.5],\"origin\":[-10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[59,116,62,117],\"texture\":0},\"east\":{\"uv\":[116,60,119,61],\"texture\":0},\"south\":{\"uv\":[116,61,119,62],\"texture\":0},\"west\":{\"uv\":[62,116,65,117],\"texture\":0},\"up\":{\"uv\":[35,103,32,100],\"texture\":0},\"down\":{\"uv\":[103,32,100,35],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ef81dede-8278-e312-7aad-6a074c2d8d48\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.2838,38.575,1.9938],\"to\":[-9.7838,39.375,3.4938],\"autouv\":0,\"color\":6,\"rotation\":[0,45,2.5],\"origin\":[-10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[117,55,119,56],\"texture\":0},\"east\":{\"uv\":[61,117,63,118],\"texture\":0},\"south\":{\"uv\":[63,117,65,118],\"texture\":0},\"west\":{\"uv\":[68,117,70,118],\"texture\":0},\"up\":{\"uv\":[28,116,26,114],\"texture\":0},\"down\":{\"uv\":[36,114,34,116],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"de921633-70f2-da2a-1f66-363321a64837\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.1588,39.175,2.1188],\"to\":[-9.7838,39.975,3.4938],\"autouv\":0,\"color\":6,\"rotation\":[0,45,12.5],\"origin\":[-10.5338,38.80625,2.8063],\"faces\":{\"north\":{\"uv\":[119,93,120,94],\"texture\":0},\"east\":{\"uv\":[94,119,95,120],\"texture\":0},\"south\":{\"uv\":[119,94,120,95],\"texture\":0},\"west\":{\"uv\":[95,119,96,120],\"texture\":0},\"up\":{\"uv\":[120,96,119,95],\"texture\":0},\"down\":{\"uv\":[97,119,96,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"845173dc-0d83-f68f-e654-bf3ca32bf352\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.63951,31.95,-0.83633],\"to\":[-13.43951,34.425,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,0],\"origin\":[-13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[114,47,116,49],\"texture\":0},\"east\":{\"uv\":[109,15,112,17],\"texture\":0},\"south\":{\"uv\":[49,114,51,116],\"texture\":0},\"west\":{\"uv\":[109,20,112,22],\"texture\":0},\"up\":{\"uv\":[28,112,26,109],\"texture\":0},\"down\":{\"uv\":[111,26,109,29],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e006de73-92a6-ca4a-8d6d-4690f7579d0c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.08951,34.0675,3.81133],\"to\":[-12.88951,36.5425,6.06133],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,-12.5],\"origin\":[-13.34367,35.855,2.8875],\"faces\":{\"north\":{\"uv\":[51,114,53,116],\"texture\":0},\"east\":{\"uv\":[53,114,55,116],\"texture\":0},\"south\":{\"uv\":[59,114,61,116],\"texture\":0},\"west\":{\"uv\":[114,60,116,62],\"texture\":0},\"up\":{\"uv\":[63,116,61,114],\"texture\":0},\"down\":{\"uv\":[116,62,114,64],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fdb96169-284e-3bbd-53d1-03b4d5fb6897\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.84242,34.0675,1.24768],\"to\":[-12.36742,36.5425,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-12.5],\"origin\":[-13.34367,35.855,2.8875],\"faces\":{\"north\":{\"uv\":[63,114,65,116],\"texture\":0},\"east\":{\"uv\":[28,109,31,111],\"texture\":0},\"south\":{\"uv\":[65,114,67,116],\"texture\":0},\"west\":{\"uv\":[109,29,112,31],\"texture\":0},\"up\":{\"uv\":[111,34,109,31],\"texture\":0},\"down\":{\"uv\":[42,109,40,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7fd3fcca-9ba4-2adc-2762-35fde297b11c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.08951,34.0675,-0.28633],\"to\":[-12.88951,36.5425,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,-12.5],\"origin\":[-13.34367,35.855,2.8875],\"faces\":{\"north\":{\"uv\":[73,114,75,116],\"texture\":0},\"east\":{\"uv\":[114,97,116,99],\"texture\":0},\"south\":{\"uv\":[104,114,106,116],\"texture\":0},\"west\":{\"uv\":[106,114,108,116],\"texture\":0},\"up\":{\"uv\":[113,116,111,114],\"texture\":0},\"down\":{\"uv\":[117,3,115,5],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3a0e61a6-2d75-adaa-7062-4259da88a4e0\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.00742,36.845,2.45768],\"to\":[-13.90742,37.67,3.33768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-25],\"origin\":[-12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[119,96,120,97],\"texture\":0},\"east\":{\"uv\":[97,119,98,120],\"texture\":0},\"south\":{\"uv\":[119,97,120,98],\"texture\":0},\"west\":{\"uv\":[98,119,99,120],\"texture\":0},\"up\":{\"uv\":[120,99,119,98],\"texture\":0},\"down\":{\"uv\":[100,119,99,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"91149954-6324-2c6c-09e2-731c212abb72\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.15451,36.02,3.81133],\"to\":[-11.95451,38.495,5.26133],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,-25],\"origin\":[-12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[5,115,7,117],\"texture\":0},\"east\":{\"uv\":[70,117,71,119],\"texture\":0},\"south\":{\"uv\":[7,115,9,117],\"texture\":0},\"west\":{\"uv\":[117,74,118,76],\"texture\":0},\"up\":{\"uv\":[119,77,117,76],\"texture\":0},\"down\":{\"uv\":[119,77,117,78],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b8894893-a530-25de-6c35-cbd6b147abe3\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-13.90742,36.02,1.24768],\"to\":[-11.43242,38.495,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-25],\"origin\":[-12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[115,8,117,10],\"texture\":0},\"east\":{\"uv\":[42,109,45,111],\"texture\":0},\"south\":{\"uv\":[12,115,14,117],\"texture\":0},\"west\":{\"uv\":[109,46,112,48],\"texture\":0},\"up\":{\"uv\":[47,112,45,109],\"texture\":0},\"down\":{\"uv\":[56,109,54,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"58b6c4b7-3e97-b3c7-1227-a0ed6ed79a99\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.15451,36.02,0.51367],\"to\":[-11.95451,38.495,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,-25],\"origin\":[-12.40867,37.8075,2.8875],\"faces\":{\"north\":{\"uv\":[17,115,19,117],\"texture\":0},\"east\":{\"uv\":[75,117,76,119],\"texture\":0},\"south\":{\"uv\":[22,115,24,117],\"texture\":0},\"west\":{\"uv\":[117,78,118,80],\"texture\":0},\"up\":{\"uv\":[83,118,81,117],\"texture\":0},\"down\":{\"uv\":[91,117,89,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"df480e42-eb3d-2448-bd27-bfe5d6b68cc1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.39242,31.95,1.24768],\"to\":[-12.91742,34.425,4.54768],\"autouv\":0,\"color\":6,\"origin\":[-13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[24,115,26,117],\"texture\":0},\"east\":{\"uv\":[109,48,112,50],\"texture\":0},\"south\":{\"uv\":[28,115,30,117],\"texture\":0},\"west\":{\"uv\":[56,109,59,111],\"texture\":0},\"up\":{\"uv\":[111,60,109,57],\"texture\":0},\"down\":{\"uv\":[61,109,59,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dad8a33f-b4fd-6f3e-6c94-b04e0b7fb511\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-16.49242,32.775,2.45768],\"to\":[-15.39242,33.6,3.33768],\"autouv\":0,\"color\":6,\"origin\":[-13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[119,99,120,100],\"texture\":0},\"east\":{\"uv\":[100,119,101,120],\"texture\":0},\"south\":{\"uv\":[119,100,120,101],\"texture\":0},\"west\":{\"uv\":[101,119,102,120],\"texture\":0},\"up\":{\"uv\":[120,102,119,101],\"texture\":0},\"down\":{\"uv\":[103,119,102,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7187327e-ad38-ae9f-8f99-2ad9529ac9a3\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.63951,31.95,3.81133],\"to\":[-13.43951,34.425,6.61133],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,0],\"origin\":[-13.89367,33.7375,2.8875],\"faces\":{\"north\":{\"uv\":[30,115,32,117],\"texture\":0},\"east\":{\"uv\":[109,60,112,62],\"texture\":0},\"south\":{\"uv\":[32,115,34,117],\"texture\":0},\"west\":{\"uv\":[109,62,112,64],\"texture\":0},\"up\":{\"uv\":[66,112,64,109],\"texture\":0},\"down\":{\"uv\":[68,109,66,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f877ff54-6c50-4e2a-11c0-e426a27ef36c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.36451,29.75,-1.38633],\"to\":[-10.96451,32.225,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,15],\"origin\":[-13.61867,31.5375,2.8875],\"faces\":{\"north\":{\"uv\":[102,89,106,91],\"texture\":0},\"east\":{\"uv\":[68,109,71,111],\"texture\":0},\"south\":{\"uv\":[90,102,94,104],\"texture\":0},\"west\":{\"uv\":[71,109,74,111],\"texture\":0},\"up\":{\"uv\":[98,57,94,54],\"texture\":0},\"down\":{\"uv\":[68,94,64,97],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"59c92544-ec84-5abb-6d63-a1d70a6d7b06\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.11742,29.75,1.24768],\"to\":[-12.64242,32.225,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,15],\"origin\":[-13.61867,31.5375,2.8875],\"faces\":{\"north\":{\"uv\":[37,115,39,117],\"texture\":0},\"east\":{\"uv\":[109,85,112,87],\"texture\":0},\"south\":{\"uv\":[115,37,117,39],\"texture\":0},\"west\":{\"uv\":[87,109,90,111],\"texture\":0},\"up\":{\"uv\":[76,112,74,109],\"texture\":0},\"down\":{\"uv\":[111,87,109,90],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9ec2de2a-8f71-a7e2-674e-8d9e602defde\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.36451,29.75,3.81133],\"to\":[-10.96451,32.225,7.16133],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,15],\"origin\":[-13.61867,31.5375,2.8875],\"faces\":{\"north\":{\"uv\":[94,102,98,104],\"texture\":0},\"east\":{\"uv\":[109,93,112,95],\"texture\":0},\"south\":{\"uv\":[102,94,106,96],\"texture\":0},\"west\":{\"uv\":[109,95,112,97],\"texture\":0},\"up\":{\"uv\":[98,70,94,67],\"texture\":0},\"down\":{\"uv\":[72,94,68,97],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ec32ecb8-e42d-396e-9431-867be23ad41f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.61451,28,3.81133],\"to\":[-10.21451,30.475,6.66133],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,7.5],\"origin\":[-12.86867,29.7875,2.8875],\"faces\":{\"north\":{\"uv\":[102,96,106,98],\"texture\":0},\"east\":{\"uv\":[109,97,112,99],\"texture\":0},\"south\":{\"uv\":[102,98,106,100],\"texture\":0},\"west\":{\"uv\":[101,109,104,111],\"texture\":0},\"up\":{\"uv\":[76,97,72,94],\"texture\":0},\"down\":{\"uv\":[98,76,94,79],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e25b8ff0-483c-0dbb-7459-b417b0e56469\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.36742,28,1.24768],\"to\":[-11.89242,30.475,4.54768],\"autouv\":0,\"color\":6,\"rotation\":[0,0,7.5],\"origin\":[-12.86867,29.7875,2.8875],\"faces\":{\"north\":{\"uv\":[39,115,41,117],\"texture\":0},\"east\":{\"uv\":[109,104,112,106],\"texture\":0},\"south\":{\"uv\":[115,39,117,41],\"texture\":0},\"west\":{\"uv\":[109,106,112,108],\"texture\":0},\"up\":{\"uv\":[111,111,109,108],\"texture\":0},\"down\":{\"uv\":[112,0,110,3],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"45704531-8633-32b8-8a22-59a1514f7b9b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.61451,28,-0.88633],\"to\":[-10.21451,30.475,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,7.5],\"origin\":[-12.86867,29.7875,2.8875],\"faces\":{\"north\":{\"uv\":[101,102,105,104],\"texture\":0},\"east\":{\"uv\":[2,110,5,112],\"texture\":0},\"south\":{\"uv\":[103,0,107,2],\"texture\":0},\"west\":{\"uv\":[110,3,113,5],\"texture\":0},\"up\":{\"uv\":[98,82,94,79],\"texture\":0},\"down\":{\"uv\":[98,82,94,85],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"50a5b6d1-8c29-200c-2a12-ba217ea71245\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-13.21742,21.95,1.72268],\"to\":[-10.31742,22.95,4.62268],\"autouv\":0,\"color\":6,\"rotation\":[0,45,0],\"origin\":[-11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[116,62,119,63],\"texture\":0},\"east\":{\"uv\":[116,63,119,64],\"texture\":0},\"south\":{\"uv\":[116,66,119,67],\"texture\":0},\"west\":{\"uv\":[116,67,119,68],\"texture\":0},\"up\":{\"uv\":[38,103,35,100],\"texture\":0},\"down\":{\"uv\":[41,100,38,103],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fdddcb40-2248-3a33-3221-dfca397c82fc\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-13.71742,22.95,1.22268],\"to\":[-9.81742,23.95,5.12268],\"autouv\":0,\"color\":6,\"rotation\":[0,45,0],\"origin\":[-11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[114,49,118,50],\"texture\":0},\"east\":{\"uv\":[114,64,118,65],\"texture\":0},\"south\":{\"uv\":[114,84,118,85],\"texture\":0},\"west\":{\"uv\":[114,90,118,91],\"texture\":0},\"up\":{\"uv\":[88,4,84,0],\"texture\":0},\"down\":{\"uv\":[10,84,6,88],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c44306cd-d399-8e82-5bd3-e47320ea1792\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-13.21742,23.95,1.72268],\"to\":[-10.31742,24.95,4.62268],\"autouv\":0,\"color\":6,\"rotation\":[0,45,0],\"origin\":[-11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[68,116,71,117],\"texture\":0},\"east\":{\"uv\":[116,68,119,69],\"texture\":0},\"south\":{\"uv\":[116,70,119,71],\"texture\":0},\"west\":{\"uv\":[116,71,119,72],\"texture\":0},\"up\":{\"uv\":[103,45,100,42],\"texture\":0},\"down\":{\"uv\":[47,100,44,103],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"718c9191-dce2-cf36-c73b-b759257f64f4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-13.71742,24.95,1.22268],\"to\":[-9.81742,29.825,5.12268],\"autouv\":0,\"color\":6,\"rotation\":[0,45,0],\"origin\":[-11.76742,28.1,2.07268],\"faces\":{\"north\":{\"uv\":[76,4,80,9],\"texture\":0},\"east\":{\"uv\":[12,76,16,81],\"texture\":0},\"south\":{\"uv\":[52,76,56,81],\"texture\":0},\"west\":{\"uv\":[76,54,80,59],\"texture\":0},\"up\":{\"uv\":[20,88,16,84],\"texture\":0},\"down\":{\"uv\":[24,84,20,88],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2d700834-8646-abd2-38c7-074d7eff5448\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.11451,26.25,3.81133],\"to\":[-9.71451,28.725,5.66133],\"autouv\":0,\"color\":6,\"rotation\":[0,22.5,0],\"origin\":[-12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[2,103,6,105],\"texture\":0},\"east\":{\"uv\":[115,41,117,43],\"texture\":0},\"south\":{\"uv\":[103,2,107,4],\"texture\":0},\"west\":{\"uv\":[42,115,44,117],\"texture\":0},\"up\":{\"uv\":[107,6,103,4],\"texture\":0},\"down\":{\"uv\":[10,103,6,105],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9c60e29d-b8ef-986c-fb03-327be81416a6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.96742,27.075,2.45768],\"to\":[-13.86742,27.9,3.33768],\"autouv\":0,\"color\":6,\"origin\":[-12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[119,102,120,103],\"texture\":0},\"east\":{\"uv\":[103,119,104,120],\"texture\":0},\"south\":{\"uv\":[119,103,120,104],\"texture\":0},\"west\":{\"uv\":[104,119,105,120],\"texture\":0},\"up\":{\"uv\":[120,105,119,104],\"texture\":0},\"down\":{\"uv\":[106,119,105,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"62206ed9-99dd-90ed-2d52-cd55447fb334\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-13.86742,26.25,1.24768],\"to\":[-11.39242,28.725,4.54768],\"autouv\":0,\"color\":6,\"origin\":[-12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[44,115,46,117],\"texture\":0},\"east\":{\"uv\":[110,8,113,10],\"texture\":0},\"south\":{\"uv\":[46,115,48,117],\"texture\":0},\"west\":{\"uv\":[110,10,113,12],\"texture\":0},\"up\":{\"uv\":[112,15,110,12],\"texture\":0},\"down\":{\"uv\":[19,110,17,113],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f004b9df-bebc-3520-d00f-ae5b0be298c0\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-14.11451,26.25,0.11367],\"to\":[-9.71451,28.725,1.96367],\"autouv\":0,\"color\":6,\"rotation\":[0,-22.5,0],\"origin\":[-12.36867,28.0375,2.8875],\"faces\":{\"north\":{\"uv\":[103,9,107,11],\"texture\":0},\"east\":{\"uv\":[115,50,117,52],\"texture\":0},\"south\":{\"uv\":[103,11,107,13],\"texture\":0},\"west\":{\"uv\":[115,52,117,54],\"texture\":0},\"up\":{\"uv\":[107,15,103,13],\"texture\":0},\"down\":{\"uv\":[18,103,14,105],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"60f5e47b-26aa-497f-5a67-2da702d98cb6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.71742,20.95,2.22268],\"to\":[-10.81742,21.95,4.12268],\"autouv\":0,\"color\":6,\"rotation\":[0,45,0],\"origin\":[-11.76742,25.35,2.07268],\"faces\":{\"north\":{\"uv\":[91,117,93,118],\"texture\":0},\"east\":{\"uv\":[95,117,97,118],\"texture\":0},\"south\":{\"uv\":[117,99,119,100],\"texture\":0},\"west\":{\"uv\":[104,117,106,118],\"texture\":0},\"up\":{\"uv\":[117,56,115,54],\"texture\":0},\"down\":{\"uv\":[57,115,55,117],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a4707f71-4a31-7c53-b88e-7483a893a6e3\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.46742,18.95,1.27268],\"to\":[-11.31742,19.95,4.47268],\"autouv\":0,\"color\":6,\"origin\":[-11.76742,23.35,2.07268],\"faces\":{\"north\":{\"uv\":[119,105,120,106],\"texture\":0},\"east\":{\"uv\":[116,72,119,73],\"texture\":0},\"south\":{\"uv\":[106,119,107,120],\"texture\":0},\"west\":{\"uv\":[116,73,119,74],\"texture\":0},\"up\":{\"uv\":[16,119,15,116],\"texture\":0},\"down\":{\"uv\":[17,116,16,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"47b56504-f1d3-9686-415e-a4f0709dfafd\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.66742,19.95,1.27268],\"to\":[-10.51742,21.95,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,-45],\"origin\":[-11.89242,20.95,2.87268],\"faces\":{\"north\":{\"uv\":[83,117,84,119],\"texture\":0},\"east\":{\"uv\":[110,17,113,19],\"texture\":0},\"south\":{\"uv\":[86,117,87,119],\"texture\":0},\"west\":{\"uv\":[19,110,22,112],\"texture\":0},\"up\":{\"uv\":[27,119,26,116],\"texture\":0},\"down\":{\"uv\":[28,116,27,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5ceffcd8-205b-97a8-bfeb-be8bbcb0cfca\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.21742,18.65,3.77268],\"to\":[-11.56742,19.45,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[119,106,120,107],\"texture\":0},\"east\":{\"uv\":[107,119,108,120],\"texture\":0},\"south\":{\"uv\":[119,107,120,108],\"texture\":0},\"west\":{\"uv\":[108,119,109,120],\"texture\":0},\"up\":{\"uv\":[120,109,119,108],\"texture\":0},\"down\":{\"uv\":[110,119,109,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"63ad2ccc-25ba-c115-dbf5-20ab848089b9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.22992,18.7125,1.37268],\"to\":[-11.57992,19.8125,2.07268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,135],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[119,109,120,110],\"texture\":0},\"east\":{\"uv\":[110,119,111,120],\"texture\":0},\"south\":{\"uv\":[119,110,120,111],\"texture\":0},\"west\":{\"uv\":[111,119,112,120],\"texture\":0},\"up\":{\"uv\":[120,112,119,111],\"texture\":0},\"down\":{\"uv\":[113,119,112,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8acf7fca-4399-fc24-c932-72bf5ca8da68\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.22992,18.5125,2.17268],\"to\":[-11.57992,19.7125,2.87268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,135],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[119,112,120,113],\"texture\":0},\"east\":{\"uv\":[113,119,114,120],\"texture\":0},\"south\":{\"uv\":[119,113,120,114],\"texture\":0},\"west\":{\"uv\":[114,119,115,120],\"texture\":0},\"up\":{\"uv\":[120,115,119,114],\"texture\":0},\"down\":{\"uv\":[116,119,115,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8d0f979c-d198-2689-ad2d-5351f49c68a7\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.22992,18.5125,2.97268],\"to\":[-11.57992,19.7125,3.67268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,135],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[119,115,120,116],\"texture\":0},\"east\":{\"uv\":[116,119,117,120],\"texture\":0},\"south\":{\"uv\":[119,116,120,117],\"texture\":0},\"west\":{\"uv\":[117,119,118,120],\"texture\":0},\"up\":{\"uv\":[120,118,119,117],\"texture\":0},\"down\":{\"uv\":[119,119,118,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1779dea4-7ee2-5b9f-985c-8616cbc88867\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.22992,18.7125,3.77268],\"to\":[-11.57992,19.8125,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,135],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[119,118,120,119],\"texture\":0},\"east\":{\"uv\":[119,119,120,120],\"texture\":0},\"south\":{\"uv\":[0,120,1,121],\"texture\":0},\"west\":{\"uv\":[120,0,121,1],\"texture\":0},\"up\":{\"uv\":[2,121,1,120],\"texture\":0},\"down\":{\"uv\":[121,1,120,2],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"939e2423-ada1-5d47-2c16-e0f6e92b53dd\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.21742,18.55,2.17268],\"to\":[-11.56742,19.45,2.87268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[2,120,3,121],\"texture\":0},\"east\":{\"uv\":[120,2,121,3],\"texture\":0},\"south\":{\"uv\":[3,120,4,121],\"texture\":0},\"west\":{\"uv\":[120,3,121,4],\"texture\":0},\"up\":{\"uv\":[5,121,4,120],\"texture\":0},\"down\":{\"uv\":[121,4,120,5],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4ca72344-81c5-e89d-19f4-dda145e7d3ae\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.01742,18.15,3.77268],\"to\":[-10.36742,18.95,4.47268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[5,120,6,121],\"texture\":0},\"east\":{\"uv\":[120,5,121,6],\"texture\":0},\"south\":{\"uv\":[6,120,7,121],\"texture\":0},\"west\":{\"uv\":[120,6,121,7],\"texture\":0},\"up\":{\"uv\":[8,121,7,120],\"texture\":0},\"down\":{\"uv\":[121,7,120,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f0e925fc-4f13-150a-775c-1c5d29164e3c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.01742,18.15,1.37268],\"to\":[-10.36742,18.95,2.07268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[8,120,9,121],\"texture\":0},\"east\":{\"uv\":[120,8,121,9],\"texture\":0},\"south\":{\"uv\":[9,120,10,121],\"texture\":0},\"west\":{\"uv\":[120,9,121,10],\"texture\":0},\"up\":{\"uv\":[11,121,10,120],\"texture\":0},\"down\":{\"uv\":[121,10,120,11],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0bbbbb03-7bcd-305b-ca5e-f058b7ff5bb4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.01742,18.05,2.97268],\"to\":[-10.36742,18.95,3.67268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[11,120,12,121],\"texture\":0},\"east\":{\"uv\":[120,11,121,12],\"texture\":0},\"south\":{\"uv\":[12,120,13,121],\"texture\":0},\"west\":{\"uv\":[120,12,121,13],\"texture\":0},\"up\":{\"uv\":[14,121,13,120],\"texture\":0},\"down\":{\"uv\":[121,13,120,14],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fdad1047-07ba-c7f6-deb1-005943d3b357\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.01742,18.05,2.17268],\"to\":[-10.36742,18.95,2.87268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[14,120,15,121],\"texture\":0},\"east\":{\"uv\":[120,14,121,15],\"texture\":0},\"south\":{\"uv\":[15,120,16,121],\"texture\":0},\"west\":{\"uv\":[120,15,121,16],\"texture\":0},\"up\":{\"uv\":[17,121,16,120],\"texture\":0},\"down\":{\"uv\":[121,16,120,17],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"63e86aa5-0394-218d-e0f8-9ed00cbd7b9d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.21742,18.55,2.97268],\"to\":[-11.56742,19.45,3.67268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[17,120,18,121],\"texture\":0},\"east\":{\"uv\":[120,17,121,18],\"texture\":0},\"south\":{\"uv\":[18,120,19,121],\"texture\":0},\"west\":{\"uv\":[120,18,121,19],\"texture\":0},\"up\":{\"uv\":[20,121,19,120],\"texture\":0},\"down\":{\"uv\":[121,19,120,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc122ad0-1ea8-4da9-420f-0e2874ed1f0d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-12.21742,18.65,1.37268],\"to\":[-11.56742,19.45,2.07268],\"autouv\":0,\"color\":6,\"rotation\":[0,0,45],\"origin\":[-11.29242,18.9375,3.32268],\"faces\":{\"north\":{\"uv\":[20,120,21,121],\"texture\":0},\"east\":{\"uv\":[120,20,121,21],\"texture\":0},\"south\":{\"uv\":[21,120,22,121],\"texture\":0},\"west\":{\"uv\":[120,21,121,22],\"texture\":0},\"up\":{\"uv\":[23,121,22,120],\"texture\":0},\"down\":{\"uv\":[121,22,120,23],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3e61372b-b54f-9344-36e1-5186b613c788\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.075,1.5,0.725],\"to\":[5.125,7.25,3.775],\"autouv\":0,\"color\":0,\"rotation\":[0,-45,0],\"origin\":[3.6,9.725,2.25],\"faces\":{\"north\":{\"uv\":[77,62,80,68],\"texture\":0},\"east\":{\"uv\":[77,68,80,74],\"texture\":0},\"south\":{\"uv\":[6,78,9,84],\"texture\":0},\"west\":{\"uv\":[9,78,12,84],\"texture\":0},\"up\":{\"uv\":[55,102,52,99],\"texture\":0},\"down\":{\"uv\":[102,57,99,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0646db02-a200-9b1f-229c-08c15715b008\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.825,0,-2.025],\"to\":[4.325,1.5,0.5],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[3.6,11.725,-1.275],\"faces\":{\"north\":{\"uv\":[107,13,110,15],\"texture\":0},\"east\":{\"uv\":[15,107,18,109],\"texture\":0},\"south\":{\"uv\":[107,17,110,19],\"texture\":0},\"west\":{\"uv\":[26,107,29,109],\"texture\":0},\"up\":{\"uv\":[102,63,99,60],\"texture\":0},\"down\":{\"uv\":[82,99,79,102],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f3622b46-ec20-0528-a9a7-46b0dd0cbc5b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.325,1.5,-0.275],\"to\":[4.875,2.275,2.275],\"autouv\":0,\"color\":0,\"origin\":[3.6,13.725,0],\"faces\":{\"north\":{\"uv\":[115,88,118,89],\"texture\":0},\"east\":{\"uv\":[115,89,118,90],\"texture\":0},\"south\":{\"uv\":[90,115,93,116],\"texture\":0},\"west\":{\"uv\":[115,91,118,92],\"texture\":0},\"up\":{\"uv\":[85,102,82,99],\"texture\":0},\"down\":{\"uv\":[88,99,85,102],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"95ac3da4-49bd-6f19-ca05-4972ba65cb16\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4.6,0.775,-0.275],\"to\":[5.9,2.4,3.275],\"autouv\":0,\"color\":0,\"rotation\":[0,0,-22.5],\"origin\":[5.1,1.975,1.5],\"faces\":{\"north\":{\"uv\":[117,8,118,10],\"texture\":0},\"east\":{\"uv\":[101,35,105,37],\"texture\":0},\"south\":{\"uv\":[117,10,118,12],\"texture\":0},\"west\":{\"uv\":[101,37,105,39],\"texture\":0},\"up\":{\"uv\":[90,115,89,111],\"texture\":0},\"down\":{\"uv\":[100,111,99,115],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4a69a736-4e03-2708-8e12-d859066dde6b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.825,0,-0.525],\"to\":[5.375,1.5,4.525],\"autouv\":0,\"color\":0,\"origin\":[3.6,11.725,2.25],\"faces\":{\"north\":{\"uv\":[101,6,105,8],\"texture\":0},\"east\":{\"uv\":[55,95,60,97],\"texture\":0},\"south\":{\"uv\":[101,17,105,19],\"texture\":0},\"west\":{\"uv\":[95,72,100,74],\"texture\":0},\"up\":{\"uv\":[77,44,73,39],\"texture\":0},\"down\":{\"uv\":[44,73,40,78],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a4f58cf6-bc79-dd90-4c51-6ecad2650c26\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.825,1.925,2.975],\"to\":[5.375,3.375,5.175],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[3.6,2.5,5.5],\"faces\":{\"north\":{\"uv\":[112,95,116,96],\"texture\":0},\"east\":{\"uv\":[116,113,118,114],\"texture\":0},\"south\":{\"uv\":[112,96,116,97],\"texture\":0},\"west\":{\"uv\":[7,117,9,118],\"texture\":0},\"up\":{\"uv\":[105,21,101,19],\"texture\":0},\"down\":{\"uv\":[105,24,101,26],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"67aa1f25-afaf-291e-ac2e-8431eb1b480a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.3,0.775,-0.275],\"to\":[2.6,2.4,3.275],\"autouv\":0,\"color\":0,\"rotation\":[0,0,22.5],\"origin\":[2.1,1.975,1.5],\"faces\":{\"north\":{\"uv\":[12,117,13,119],\"texture\":0},\"east\":{\"uv\":[101,39,105,41],\"texture\":0},\"south\":{\"uv\":[13,117,14,119],\"texture\":0},\"west\":{\"uv\":[101,45,105,47],\"texture\":0},\"up\":{\"uv\":[104,115,103,111],\"texture\":0},\"down\":{\"uv\":[5,112,4,116],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b70e23c8-c0f5-91d4-399f-6351986144cd\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.25,0,1.05],\"to\":[4.175,0.5,4.725],\"autouv\":0,\"color\":0,\"rotation\":[0,-22.5,0],\"origin\":[2.525,0.75,3.575],\"faces\":{\"north\":{\"uv\":[83,115,86,116],\"texture\":0},\"east\":{\"uv\":[112,85,116,86],\"texture\":0},\"south\":{\"uv\":[115,83,118,84],\"texture\":0},\"west\":{\"uv\":[112,86,116,87],\"texture\":0},\"up\":{\"uv\":[43,95,40,91],\"texture\":0},\"down\":{\"uv\":[49,91,46,95],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"513276dc-9049-753a-13bf-2a5cf4f197ab\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.05,0,1.05],\"to\":[5.975,0.5,4.725],\"autouv\":0,\"color\":0,\"rotation\":[0,22.5,0],\"origin\":[4.7,0.75,3.575],\"faces\":{\"north\":{\"uv\":[87,115,90,116],\"texture\":0},\"east\":{\"uv\":[112,93,116,94],\"texture\":0},\"south\":{\"uv\":[115,87,118,88],\"texture\":0},\"west\":{\"uv\":[112,94,116,95],\"texture\":0},\"up\":{\"uv\":[58,95,55,91],\"texture\":0},\"down\":{\"uv\":[94,55,91,59],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"54e9970f-49f9-29a9-04d1-2c8e7099bf55\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.125,5.125,1.775],\"to\":[6.075,7.9,2.725],\"autouv\":0,\"color\":0,\"rotation\":[0,0,-22.5],\"origin\":[4.6,6.925,2.25],\"faces\":{\"north\":{\"uv\":[98,68,101,71],\"texture\":0},\"east\":{\"uv\":[115,66,116,69],\"texture\":0},\"south\":{\"uv\":[76,98,79,101],\"texture\":0},\"west\":{\"uv\":[67,115,68,118],\"texture\":0},\"up\":{\"uv\":[71,116,68,115],\"texture\":0},\"down\":{\"uv\":[118,69,115,70],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"bbfd3b01-4e82-5915-0284-5ec05be23f5a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.125,5.125,1.775],\"to\":[4.075,7.9,2.725],\"autouv\":0,\"color\":0,\"rotation\":[0,0,22.5],\"origin\":[2.6,6.925,2.25],\"faces\":{\"north\":{\"uv\":[99,48,102,51],\"texture\":0},\"east\":{\"uv\":[79,115,80,118],\"texture\":0},\"south\":{\"uv\":[49,99,52,102],\"texture\":0},\"west\":{\"uv\":[115,79,116,82],\"texture\":0},\"up\":{\"uv\":[83,116,80,115],\"texture\":0},\"down\":{\"uv\":[118,82,115,83],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0775f99c-e0db-1022-ceb4-58e90e3c33f4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.125,4.75,-0.15],\"to\":[4.075,7.525,2.8],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[4.6,6.55,0.325],\"faces\":{\"north\":{\"uv\":[115,70,116,73],\"texture\":0},\"east\":{\"uv\":[98,76,101,79],\"texture\":0},\"south\":{\"uv\":[71,115,72,118],\"texture\":0},\"west\":{\"uv\":[98,79,101,82],\"texture\":0},\"up\":{\"uv\":[73,118,72,115],\"texture\":0},\"down\":{\"uv\":[116,73,115,76],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7e816966-7fdf-ee4e-f702-6982983edc0a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.125,4.75,1.7],\"to\":[4.075,7.525,4.65],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[4.6,6.55,4.175],\"faces\":{\"north\":{\"uv\":[76,115,77,118],\"texture\":0},\"east\":{\"uv\":[98,82,101,85],\"texture\":0},\"south\":{\"uv\":[115,76,116,79],\"texture\":0},\"west\":{\"uv\":[90,98,93,101],\"texture\":0},\"up\":{\"uv\":[78,118,77,115],\"texture\":0},\"down\":{\"uv\":[79,115,78,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e85ce7b0-711b-5558-279c-95322808712b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.4,12.65,0.05],\"to\":[5.8,14,4.45],\"autouv\":0,\"color\":0,\"rotation\":[0,-45,0],\"origin\":[3.6,12.875,2.25],\"faces\":{\"north\":{\"uv\":[111,89,115,90],\"texture\":0},\"east\":{\"uv\":[112,2,116,3],\"texture\":0},\"south\":{\"uv\":[112,16,116,17],\"texture\":0},\"west\":{\"uv\":[112,22,116,23],\"texture\":0},\"up\":{\"uv\":[35,84,31,80],\"texture\":0},\"down\":{\"uv\":[39,80,35,84],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a6d01e53-e044-783f-db4d-4fafd64dcc16\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[1.15,14,-0.2],\"to\":[6.05,15.35,4.7],\"autouv\":0,\"color\":0,\"rotation\":[0,-45,0],\"origin\":[3.6,14.225,2.25],\"faces\":{\"north\":{\"uv\":[110,40,115,41],\"texture\":0},\"east\":{\"uv\":[110,41,115,42],\"texture\":0},\"south\":{\"uv\":[110,42,115,43],\"texture\":0},\"west\":{\"uv\":[110,50,115,51],\"texture\":0},\"up\":{\"uv\":[72,36,67,31],\"texture\":0},\"down\":{\"uv\":[72,55,67,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5c46d5ab-7804-c02b-767c-a261e30d4ed5\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.325,7.9,0.125],\"to\":[4.875,9.65,2.35],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[3.6,7.625,0.325],\"faces\":{\"north\":{\"uv\":[106,106,109,108],\"texture\":0},\"east\":{\"uv\":[61,112,63,114],\"texture\":0},\"south\":{\"uv\":[107,0,110,2],\"texture\":0},\"west\":{\"uv\":[112,62,114,64],\"texture\":0},\"up\":{\"uv\":[110,4,107,2],\"texture\":0},\"down\":{\"uv\":[110,4,107,6],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0edb8eca-2de0-0bed-b9a2-1cb5665cb3e0\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[2.325,7.9,2.15],\"to\":[4.875,9.65,4.375],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[3.6,7.625,4.175],\"faces\":{\"north\":{\"uv\":[6,107,9,109],\"texture\":0},\"east\":{\"uv\":[63,112,65,114],\"texture\":0},\"south\":{\"uv\":[107,9,110,11],\"texture\":0},\"west\":{\"uv\":[65,112,67,114],\"texture\":0},\"up\":{\"uv\":[110,13,107,11],\"texture\":0},\"down\":{\"uv\":[15,107,12,109],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c7656138-efa9-f632-69ce-d6c344f64e6d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[4.9,16.6,1.3],\"to\":[6.3,18.7,3.2],\"autouv\":0,\"color\":0,\"rotation\":[0,0,-22.5],\"origin\":[3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[2,117,3,119],\"texture\":0},\"east\":{\"uv\":[59,112,61,114],\"texture\":0},\"south\":{\"uv\":[117,3,118,5],\"texture\":0},\"west\":{\"uv\":[112,60,114,62],\"texture\":0},\"up\":{\"uv\":[6,119,5,117],\"texture\":0},\"down\":{\"uv\":[7,117,6,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2a01c2d2-7cd6-7b7b-2271-a69360c97bbb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[6.825,13.85,-0.5],\"to\":[6.825,18.1,5.5],\"autouv\":0,\"color\":1,\"origin\":[4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[0,0,0,4],\"texture\":0},\"east\":{\"uv\":[70,5,76,9],\"texture\":0},\"south\":{\"uv\":[0,0,0,4],\"texture\":0},\"west\":{\"uv\":[70,9,76,5],\"texture\":0},\"up\":{\"uv\":[0,6,0,0],\"texture\":0},\"down\":{\"uv\":[0,0,0,6],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6e6bfdbd-594d-cad5-b4b0-7b3fe365077e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-6.825,13.85,-0.5],\"to\":[-6.825,18.1,5.5],\"autouv\":0,\"color\":1,\"origin\":[-4.8,18.85,2.5],\"faces\":{\"north\":{\"uv\":[0,0,0,4],\"texture\":0},\"east\":{\"uv\":[72,4,78,0],\"texture\":0},\"south\":{\"uv\":[0,0,0,4],\"texture\":0},\"west\":{\"uv\":[72,0,78,4],\"texture\":0},\"up\":{\"uv\":[0,6,0,0],\"texture\":0},\"down\":{\"uv\":[0,0,0,6],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"cca29179-e0a1-fc3e-5ddd-31cbb7c1ec41\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.5,16.75,-0.75],\"to\":[5.5,18,5.75],\"autouv\":0,\"color\":1,\"origin\":[0.5,31.5,3.25],\"faces\":{\"north\":{\"uv\":[89,6,100,7],\"texture\":0},\"east\":{\"uv\":[101,41,108,42],\"texture\":0},\"south\":{\"uv\":[90,41,101,42],\"texture\":0},\"west\":{\"uv\":[102,23,109,24],\"texture\":0},\"up\":{\"uv\":[44,17,33,10],\"texture\":0},\"down\":{\"uv\":[44,17,33,24],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"948e9537-9de2-64e9-517b-27eeca22ebff\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.5,13.75,-0.75],\"to\":[5.5,18,-0.75],\"autouv\":0,\"color\":1,\"origin\":[0.5,31.5,3.25],\"faces\":{\"north\":{\"uv\":[44,22,55,26],\"texture\":0},\"east\":{\"uv\":[0,0,0,4],\"texture\":0},\"south\":{\"uv\":[55,22,44,26],\"texture\":0},\"west\":{\"uv\":[0,0,0,4],\"texture\":0},\"up\":{\"uv\":[11,0,0,0],\"texture\":0},\"down\":{\"uv\":[11,0,0,0],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"919f6ad0-5645-985f-2e89-4f499936da91\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.5,13.58882,5.75793],\"to\":[5.5,17.83882,5.75793],\"autouv\":0,\"color\":1,\"origin\":[0.5,31.33882,9.75793],\"faces\":{\"north\":{\"uv\":[59,34,48,38],\"texture\":0},\"east\":{\"uv\":[0,0,0,4],\"texture\":0},\"south\":{\"uv\":[48,34,59,38],\"texture\":0},\"west\":{\"uv\":[0,0,0,4],\"texture\":0},\"up\":{\"uv\":[11,0,0,0],\"texture\":0},\"down\":{\"uv\":[11,0,0,0],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9069559d-0e29-81b5-612a-496ee632e27a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-6.3,15.35,-0.45],\"to\":[-0.9,16.7,4.95],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[-3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[110,73,115,74],\"texture\":0},\"east\":{\"uv\":[110,74,115,75],\"texture\":0},\"south\":{\"uv\":[110,75,115,76],\"texture\":0},\"west\":{\"uv\":[76,110,81,111],\"texture\":0},\"up\":{\"uv\":[73,25,68,20],\"texture\":0},\"down\":{\"uv\":[26,68,21,73],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1c3c5f85-a621-e9b2-0336-471cbcc6e18b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-6.3,16.6,1.3],\"to\":[-4.9,18.7,3.2],\"autouv\":0,\"color\":0,\"rotation\":[0,0,22.5],\"origin\":[-3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[17,117,18,119],\"texture\":0},\"east\":{\"uv\":[74,112,76,114],\"texture\":0},\"south\":{\"uv\":[117,17,118,19],\"texture\":0},\"west\":{\"uv\":[112,97,114,99],\"texture\":0},\"up\":{\"uv\":[19,119,18,117],\"texture\":0},\"down\":{\"uv\":[20,117,19,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8a7a2efd-5ca7-a58c-d13c-a0c9d491963a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.8,12.65,0.05],\"to\":[-1.4,14,4.45],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[-3.6,12.875,2.25],\"faces\":{\"north\":{\"uv\":[112,104,116,105],\"texture\":0},\"east\":{\"uv\":[112,105,116,106],\"texture\":0},\"south\":{\"uv\":[112,106,116,107],\"texture\":0},\"west\":{\"uv\":[112,107,116,108],\"texture\":0},\"up\":{\"uv\":[56,85,52,81],\"texture\":0},\"down\":{\"uv\":[85,79,81,83],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"20579d70-7fd9-9640-758b-9cb91bf8c8c2\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-6.05,14,-0.2],\"to\":[-1.15,15.35,4.7],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[-3.6,14.225,2.25],\"faces\":{\"north\":{\"uv\":[110,76,115,77],\"texture\":0},\"east\":{\"uv\":[110,77,115,78],\"texture\":0},\"south\":{\"uv\":[81,110,86,111],\"texture\":0},\"west\":{\"uv\":[110,91,115,92],\"texture\":0},\"up\":{\"uv\":[73,41,68,36],\"texture\":0},\"down\":{\"uv\":[73,41,68,46],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b81b6460-4ec1-dccd-76bd-c6aee50e3af8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.625,8.6,0.225],\"to\":[-1.575,12.65,4.275],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[-3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[48,82,52,86],\"texture\":0},\"east\":{\"uv\":[56,82,60,86],\"texture\":0},\"south\":{\"uv\":[82,56,86,60],\"texture\":0},\"west\":{\"uv\":[60,82,64,86],\"texture\":0},\"up\":{\"uv\":[86,78,82,74],\"texture\":0},\"down\":{\"uv\":[87,12,83,16],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6a259483-478e-e732-e39f-95f07ae3adbf\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.875,7.9,0.125],\"to\":[-2.325,9.65,2.35],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[-3.6,7.625,0.325],\"faces\":{\"north\":{\"uv\":[29,107,32,109],\"texture\":0},\"east\":{\"uv\":[111,112,113,114],\"texture\":0},\"south\":{\"uv\":[40,107,43,109],\"texture\":0},\"west\":{\"uv\":[113,3,115,5],\"texture\":0},\"up\":{\"uv\":[46,109,43,107],\"texture\":0},\"down\":{\"uv\":[61,107,58,109],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0b9618b4-fc3f-7310-9d4d-e38f2fe541fe\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.875,7.9,2.15],\"to\":[-2.325,9.65,4.375],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[-3.6,7.625,4.175],\"faces\":{\"north\":{\"uv\":[64,107,67,109],\"texture\":0},\"east\":{\"uv\":[5,113,7,115],\"texture\":0},\"south\":{\"uv\":[67,107,70,109],\"texture\":0},\"west\":{\"uv\":[113,8,115,10],\"texture\":0},\"up\":{\"uv\":[73,109,70,107],\"texture\":0},\"down\":{\"uv\":[110,71,107,73],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"aaff0afc-ccb6-ee65-cc65-691c3ab20d47\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-6.075,7.25,-0.225],\"to\":[-1.125,8.6,4.725],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[-3.6,15.575,2.25],\"faces\":{\"north\":{\"uv\":[110,92,115,93],\"texture\":0},\"east\":{\"uv\":[95,110,100,111],\"texture\":0},\"south\":{\"uv\":[111,6,116,7],\"texture\":0},\"west\":{\"uv\":[111,7,116,8],\"texture\":0},\"up\":{\"uv\":[48,73,43,68],\"texture\":0},\"down\":{\"uv\":[53,68,48,73],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"30868f8a-27c4-db95-7b51-d3227b252417\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-6.075,5.125,1.775],\"to\":[-3.125,7.9,2.725],\"autouv\":0,\"color\":0,\"rotation\":[0,0,22.5],\"origin\":[-4.6,6.925,2.25],\"faces\":{\"north\":{\"uv\":[99,85,102,88],\"texture\":0},\"east\":{\"uv\":[93,115,94,118],\"texture\":0},\"south\":{\"uv\":[99,88,102,91],\"texture\":0},\"west\":{\"uv\":[94,115,95,118],\"texture\":0},\"up\":{\"uv\":[118,93,115,92],\"texture\":0},\"down\":{\"uv\":[98,115,95,116],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f32e0837-cf2b-2fd2-2d0a-a4f0b02f63f5\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.075,4.75,-0.15],\"to\":[-3.125,7.525,2.8],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[-4.6,6.55,0.325],\"faces\":{\"north\":{\"uv\":[98,115,99,118],\"texture\":0},\"east\":{\"uv\":[93,99,96,102],\"texture\":0},\"south\":{\"uv\":[99,115,100,118],\"texture\":0},\"west\":{\"uv\":[99,94,102,97],\"texture\":0},\"up\":{\"uv\":[116,103,115,100],\"texture\":0},\"down\":{\"uv\":[102,115,101,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b40b0cc3-9809-2839-b031-5124f77b3721\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.075,4.75,1.7],\"to\":[-3.125,7.525,4.65],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[-4.6,6.55,4.175],\"faces\":{\"north\":{\"uv\":[102,115,103,118],\"texture\":0},\"east\":{\"uv\":[96,99,99,102],\"texture\":0},\"south\":{\"uv\":[103,115,104,118],\"texture\":0},\"west\":{\"uv\":[99,97,102,100],\"texture\":0},\"up\":{\"uv\":[110,118,109,115],\"texture\":0},\"down\":{\"uv\":[111,115,110,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5f5bd476-ae04-dfcc-0637-40fddcbb54ba\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.075,5.125,1.775],\"to\":[-1.125,7.9,2.725],\"autouv\":0,\"color\":0,\"rotation\":[0,0,-22.5],\"origin\":[-2.6,6.925,2.25],\"faces\":{\"north\":{\"uv\":[100,0,103,3],\"texture\":0},\"east\":{\"uv\":[115,111,116,114],\"texture\":0},\"south\":{\"uv\":[3,100,6,103],\"texture\":0},\"west\":{\"uv\":[113,115,114,118],\"texture\":0},\"up\":{\"uv\":[118,104,115,103],\"texture\":0},\"down\":{\"uv\":[117,115,114,116],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"30d51b39-09ac-67d2-92c7-c7f791abdaa4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.125,1.5,0.725],\"to\":[-2.075,7.25,3.775],\"autouv\":0,\"color\":0,\"rotation\":[0,45,0],\"origin\":[-3.6,9.725,2.25],\"faces\":{\"north\":{\"uv\":[19,78,22,84],\"texture\":0},\"east\":{\"uv\":[22,78,25,84],\"texture\":0},\"south\":{\"uv\":[25,78,28,84],\"texture\":0},\"west\":{\"uv\":[28,78,31,84],\"texture\":0},\"up\":{\"uv\":[103,6,100,3],\"texture\":0},\"down\":{\"uv\":[9,100,6,103],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4b2bd505-e7b7-881c-fe09-1452c0fb24fa\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.325,0,-2.025],\"to\":[-1.825,1.5,0.5],\"autouv\":0,\"color\":0,\"rotation\":[0,-45,0],\"origin\":[-3.6,11.725,-1.275],\"faces\":{\"north\":{\"uv\":[73,107,76,109],\"texture\":0},\"east\":{\"uv\":[107,73,110,75],\"texture\":0},\"south\":{\"uv\":[88,107,91,109],\"texture\":0},\"west\":{\"uv\":[107,91,110,93],\"texture\":0},\"up\":{\"uv\":[12,103,9,100],\"texture\":0},\"down\":{\"uv\":[103,9,100,12],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"19245183-7ac5-9d1b-aa96-e962912f2d8d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.375,0,-0.525],\"to\":[-1.825,1.5,4.525],\"autouv\":0,\"color\":0,\"origin\":[-3.6,11.725,2.25],\"faces\":{\"north\":{\"uv\":[101,51,105,53],\"texture\":0},\"east\":{\"uv\":[95,74,100,76],\"texture\":0},\"south\":{\"uv\":[101,53,105,55],\"texture\":0},\"west\":{\"uv\":[0,96,5,98],\"texture\":0},\"up\":{\"uv\":[48,78,44,73],\"texture\":0},\"down\":{\"uv\":[52,73,48,78],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4a289af4-8561-0f5e-93c9-d96eb96dc343\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.175,0,1.05],\"to\":[-1.25,0.5,4.725],\"autouv\":0,\"color\":0,\"rotation\":[0,22.5,0],\"origin\":[-2.525,0.75,3.575],\"faces\":{\"north\":{\"uv\":[115,114,118,115],\"texture\":0},\"east\":{\"uv\":[113,10,117,11],\"texture\":0},\"south\":{\"uv\":[0,116,3,117],\"texture\":0},\"west\":{\"uv\":[113,11,117,12],\"texture\":0},\"up\":{\"uv\":[61,95,58,91],\"texture\":0},\"down\":{\"uv\":[64,91,61,95],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8c07b076-c295-d254-3ff8-54610dafefb3\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.975,0,1.05],\"to\":[-3.05,0.5,4.725],\"autouv\":0,\"color\":0,\"rotation\":[0,-22.5,0],\"origin\":[-4.7,0.75,3.575],\"faces\":{\"north\":{\"uv\":[116,0,119,1],\"texture\":0},\"east\":{\"uv\":[113,17,117,18],\"texture\":0},\"south\":{\"uv\":[116,1,119,2],\"texture\":0},\"west\":{\"uv\":[113,18,117,19],\"texture\":0},\"up\":{\"uv\":[93,95,90,91],\"texture\":0},\"down\":{\"uv\":[95,27,92,31],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3214c9af-3f64-9d60-ba4b-5af50d31e81d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.375,1.925,2.975],\"to\":[-1.825,3.375,5.175],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[-3.6,2.5,5.5],\"faces\":{\"north\":{\"uv\":[113,19,117,20],\"texture\":0},\"east\":{\"uv\":[117,19,119,20],\"texture\":0},\"south\":{\"uv\":[113,26,117,27],\"texture\":0},\"west\":{\"uv\":[20,117,22,118],\"texture\":0},\"up\":{\"uv\":[105,57,101,55],\"texture\":0},\"down\":{\"uv\":[105,65,101,67],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b61b47e5-aef0-c7b6-d356-aba62813221d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.875,1.5,-0.275],\"to\":[-2.325,2.275,2.275],\"autouv\":0,\"color\":0,\"origin\":[-3.6,13.725,0],\"faces\":{\"north\":{\"uv\":[116,2,119,3],\"texture\":0},\"east\":{\"uv\":[116,6,119,7],\"texture\":0},\"south\":{\"uv\":[116,7,119,8],\"texture\":0},\"west\":{\"uv\":[116,12,119,13],\"texture\":0},\"up\":{\"uv\":[103,15,100,12],\"texture\":0},\"down\":{\"uv\":[20,100,17,103],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b00f9f74-7073-84e7-5351-080096405a56\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.9,0.775,-0.275],\"to\":[-4.6,2.4,3.275],\"autouv\":0,\"color\":0,\"rotation\":[0,0,22.5],\"origin\":[-5.1,1.975,1.5],\"faces\":{\"north\":{\"uv\":[22,117,23,119],\"texture\":0},\"east\":{\"uv\":[101,67,105,69],\"texture\":0},\"south\":{\"uv\":[23,117,24,119],\"texture\":0},\"west\":{\"uv\":[101,69,105,71],\"texture\":0},\"up\":{\"uv\":[22,116,21,112],\"texture\":0},\"down\":{\"uv\":[37,112,36,116],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9f80b25e-feab-7894-4534-3b6a5d570273\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-2.6,0.775,-0.275],\"to\":[-1.3,2.4,3.275],\"autouv\":0,\"color\":0,\"rotation\":[0,0,-22.5],\"origin\":[-2.1,1.975,1.5],\"faces\":{\"north\":{\"uv\":[24,117,25,119],\"texture\":0},\"east\":{\"uv\":[75,101,79,103],\"texture\":0},\"south\":{\"uv\":[25,117,26,119],\"texture\":0},\"west\":{\"uv\":[101,75,105,77],\"texture\":0},\"up\":{\"uv\":[42,116,41,112],\"texture\":0},\"down\":{\"uv\":[49,112,48,116],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ab201e6d-7749-1af6-e5c5-cc3e1f2dd006\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4.5,28.35,-1.75],\"to\":[-1,32.2,-0.75],\"autouv\":0,\"color\":1,\"rotation\":[-15,0,0],\"origin\":[-2.5,30.2,-1.25],\"faces\":{\"north\":{\"uv\":[0,80,4,84],\"texture\":0},\"east\":{\"uv\":[30,111,31,115],\"texture\":0},\"south\":{\"uv\":[80,3,84,7],\"texture\":0},\"west\":{\"uv\":[58,111,59,115],\"texture\":0},\"up\":{\"uv\":[115,34,111,33],\"texture\":0},\"down\":{\"uv\":[115,59,111,60],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c4d4556c-7a64-9699-2301-d8aa058e4096\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-0.75,32.5,-2.75],\"to\":[0.75,34,-2.25],\"autouv\":0,\"color\":3,\"rotation\":[0,0,45],\"origin\":[0,33.25,-2.75],\"faces\":{\"north\":{\"uv\":[70,111,72,113],\"texture\":0},\"east\":{\"uv\":[9,88,10,90],\"texture\":0},\"south\":{\"uv\":[72,111,74,113],\"texture\":0},\"west\":{\"uv\":[46,89,47,91],\"texture\":0},\"up\":{\"uv\":[118,108,116,107],\"texture\":0},\"down\":{\"uv\":[113,116,111,117],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d5105f83-0c6e-5ab9-a5c1-d78f73dfffff\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.5,7.5,7.45],\"to\":[5.5,32,7.45],\"autouv\":0,\"color\":3,\"rotation\":[0,0,-22.5],\"origin\":[0,31.25,7.45],\"faces\":{\"north\":{\"uv\":[11,25,0,50],\"texture\":0},\"east\":{\"uv\":[0,0,0,25],\"texture\":0},\"south\":{\"uv\":[0,25,11,50],\"texture\":0},\"west\":{\"uv\":[0,0,0,25],\"texture\":0},\"up\":{\"uv\":[11,0,0,0],\"texture\":0},\"down\":{\"uv\":[11,0,0,0],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f2a2c076-3830-827c-a89d-9f32cb8332e2\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-5.5,7.5,7.45],\"to\":[5.5,32,7.45],\"autouv\":0,\"color\":3,\"rotation\":[0,0,22.5],\"origin\":[0,31.25,7.45],\"faces\":{\"north\":{\"uv\":[22,0,11,25],\"texture\":0},\"east\":{\"uv\":[0,0,0,25],\"texture\":0},\"south\":{\"uv\":[11,0,22,25],\"texture\":0},\"west\":{\"uv\":[0,0,0,25],\"texture\":0},\"up\":{\"uv\":[11,0,0,0],\"texture\":0},\"down\":{\"uv\":[11,0,0,0],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e151b038-dd4d-7183-9426-8978e675de2c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-4,7.25,7.5],\"to\":[4,32,7.5],\"autouv\":0,\"color\":3,\"origin\":[0,31.25,7.5],\"faces\":{\"north\":{\"uv\":[27,25,19,50],\"texture\":0},\"east\":{\"uv\":[0,0,0,25],\"texture\":0},\"south\":{\"uv\":[19,25,27,50],\"texture\":0},\"west\":{\"uv\":[0,0,0,25],\"texture\":0},\"up\":{\"uv\":[8,0,0,0],\"texture\":0},\"down\":{\"uv\":[8,0,0,0],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"21560076-f4db-b654-055a-793913997dea\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-8,0,-3],\"to\":[8,54,8],\"autouv\":1,\"color\":3,\"visibility\":false,\"origin\":[0,0,0],\"faces\":{\"north\":{\"uv\":[0,0,16,16]},\"east\":{\"uv\":[0,0,11,16]},\"south\":{\"uv\":[0,0,16,16]},\"west\":{\"uv\":[0,0,11,16]},\"up\":{\"uv\":[0,0,16,11]},\"down\":{\"uv\":[0,0,16,11]}},\"type\":\"cube\",\"uuid\":\"06fba819-151e-afac-85b3-405b6bb18348\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.29242,18.9375,-1.67732],\"to\":[11.04242,19.6875,6.32268],\"autouv\":0,\"color\":4,\"rotation\":[0,0,45],\"origin\":[11.29242,19.9375,3.32268],\"faces\":{\"north\":{\"uv\":[106,118,107,119],\"texture\":0},\"east\":{\"uv\":[100,93,108,94],\"texture\":0},\"south\":{\"uv\":[118,106,119,107],\"texture\":0},\"west\":{\"uv\":[101,8,109,9],\"texture\":0},\"up\":{\"uv\":[1,109,0,101],\"texture\":0},\"down\":{\"uv\":[2,101,1,109],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c087230a-862d-9d6d-e4cb-c9d00e17a4d1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.66742,18.4125,6.32268],\"to\":[11.91742,19.6625,7.57268],\"autouv\":0,\"color\":4,\"origin\":[11.91742,19.6625,4.07268],\"faces\":{\"north\":{\"uv\":[112,118,113,119],\"texture\":0},\"east\":{\"uv\":[118,112,119,113],\"texture\":0},\"south\":{\"uv\":[113,118,114,119],\"texture\":0},\"west\":{\"uv\":[118,113,119,114],\"texture\":0},\"up\":{\"uv\":[115,119,114,118],\"texture\":0},\"down\":{\"uv\":[119,114,118,115],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8977ffc9-8598-74f8-bf1d-68b5cd2583eb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.66742,18.6375,8.27268],\"to\":[11.91742,19.3125,8.94768],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[11.29242,19.0375,8.67268],\"faces\":{\"north\":{\"uv\":[119,86,120,87],\"texture\":0},\"east\":{\"uv\":[87,119,88,120],\"texture\":0},\"south\":{\"uv\":[119,87,120,88],\"texture\":0},\"west\":{\"uv\":[88,119,89,120],\"texture\":0},\"up\":{\"uv\":[120,89,119,88],\"texture\":0},\"down\":{\"uv\":[90,119,89,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f3f5b091-43dd-29d8-afc7-d5b805eeb9cb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.91742,18.6625,6.57268],\"to\":[12.16742,19.4125,7.32268],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[11.29242,19.0375,6.94768],\"faces\":{\"north\":{\"uv\":[119,2,120,3],\"texture\":0},\"east\":{\"uv\":[3,119,4,120],\"texture\":0},\"south\":{\"uv\":[119,3,120,4],\"texture\":0},\"west\":{\"uv\":[4,119,5,120],\"texture\":0},\"up\":{\"uv\":[120,5,119,4],\"texture\":0},\"down\":{\"uv\":[6,119,5,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a74a46b7-034f-fa99-4db3-6c56e6f71589\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.41742,18.6625,6.57268],\"to\":[10.66742,19.4125,7.32268],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[9.79242,19.0375,6.94768],\"faces\":{\"north\":{\"uv\":[119,83,120,84],\"texture\":0},\"east\":{\"uv\":[84,119,85,120],\"texture\":0},\"south\":{\"uv\":[119,84,120,85],\"texture\":0},\"west\":{\"uv\":[85,119,86,120],\"texture\":0},\"up\":{\"uv\":[120,86,119,85],\"texture\":0},\"down\":{\"uv\":[87,119,86,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"95560117-7d00-3533-3923-c0492750ea03\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.91742,19.6625,6.57268],\"to\":[11.66742,19.9125,7.32268],\"autouv\":0,\"color\":4,\"rotation\":[0,45,0],\"origin\":[11.29242,19.6625,6.94768],\"faces\":{\"north\":{\"uv\":[115,118,116,119],\"texture\":0},\"east\":{\"uv\":[118,115,119,116],\"texture\":0},\"south\":{\"uv\":[116,118,117,119],\"texture\":0},\"west\":{\"uv\":[118,116,119,117],\"texture\":0},\"up\":{\"uv\":[118,119,117,118],\"texture\":0},\"down\":{\"uv\":[119,117,118,118],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4ff64c77-291b-3c79-e625-bc7ebe3a8bc5\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.91742,18.1625,6.57268],\"to\":[11.66742,18.4125,7.32268],\"autouv\":0,\"color\":4,\"rotation\":[0,45,0],\"origin\":[11.29242,18.1625,6.94768],\"faces\":{\"north\":{\"uv\":[118,118,119,119],\"texture\":0},\"east\":{\"uv\":[0,119,1,120],\"texture\":0},\"south\":{\"uv\":[119,0,120,1],\"texture\":0},\"west\":{\"uv\":[1,119,2,120],\"texture\":0},\"up\":{\"uv\":[120,2,119,1],\"texture\":0},\"down\":{\"uv\":[3,119,2,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3ba8c767-b1af-b6b4-a90c-2853d99a5911\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.39242,19.0375,7.57268],\"to\":[10.94242,19.5875,8.57268],\"autouv\":0,\"color\":4,\"rotation\":[0,0,45],\"origin\":[11.29242,19.9375,5.57268],\"faces\":{\"north\":{\"uv\":[109,118,110,119],\"texture\":0},\"east\":{\"uv\":[118,109,119,110],\"texture\":0},\"south\":{\"uv\":[110,118,111,119],\"texture\":0},\"west\":{\"uv\":[118,110,119,111],\"texture\":0},\"up\":{\"uv\":[112,119,111,118],\"texture\":0},\"down\":{\"uv\":[119,111,118,112],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"8b5ad391-f368-2fec-1d47-55410fd47190\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.91742,18.6625,-2.67732],\"to\":[12.16742,19.4125,-1.92732],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[11.29242,19.0375,-2.30232],\"faces\":{\"north\":{\"uv\":[119,80,120,81],\"texture\":0},\"east\":{\"uv\":[81,119,82,120],\"texture\":0},\"south\":{\"uv\":[119,81,120,82],\"texture\":0},\"west\":{\"uv\":[82,119,83,120],\"texture\":0},\"up\":{\"uv\":[120,83,119,82],\"texture\":0},\"down\":{\"uv\":[84,119,83,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9d609d71-99c7-9a40-eeac-222423bcb4e8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.01742,19.6625,-2.57732],\"to\":[11.56742,20.9125,-2.02732],\"autouv\":0,\"color\":4,\"rotation\":[0,45,0],\"origin\":[11.29242,19.6625,-2.30232],\"faces\":{\"north\":{\"uv\":[119,74,120,75],\"texture\":0},\"east\":{\"uv\":[75,119,76,120],\"texture\":0},\"south\":{\"uv\":[119,75,120,76],\"texture\":0},\"west\":{\"uv\":[76,119,77,120],\"texture\":0},\"up\":{\"uv\":[120,77,119,76],\"texture\":0},\"down\":{\"uv\":[78,119,77,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1c221324-8e5a-d711-e550-a1f5e3038d8a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.66742,18.4125,-2.92732],\"to\":[11.91742,19.6625,-1.67732],\"autouv\":0,\"color\":4,\"origin\":[11.91742,19.6625,-5.17732],\"faces\":{\"north\":{\"uv\":[119,8,120,9],\"texture\":0},\"east\":{\"uv\":[9,119,10,120],\"texture\":0},\"south\":{\"uv\":[119,9,120,10],\"texture\":0},\"west\":{\"uv\":[10,119,11,120],\"texture\":0},\"up\":{\"uv\":[120,11,119,10],\"texture\":0},\"down\":{\"uv\":[12,119,11,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ac2d484e-6396-ee68-ed13-3a4b61d4b29b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.41742,18.6625,-2.67732],\"to\":[10.66742,19.4125,-1.92732],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[9.79242,19.0375,-2.30232],\"faces\":{\"north\":{\"uv\":[119,5,120,6],\"texture\":0},\"east\":{\"uv\":[6,119,7,120],\"texture\":0},\"south\":{\"uv\":[119,6,120,7],\"texture\":0},\"west\":{\"uv\":[7,119,8,120],\"texture\":0},\"up\":{\"uv\":[120,8,119,7],\"texture\":0},\"down\":{\"uv\":[9,119,8,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"89c33bc3-1e54-c13e-afef-9a0ca6ef63e3\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.29242,18.9375,-18.92732],\"to\":[11.04242,19.6875,-2.92732],\"autouv\":0,\"color\":4,\"rotation\":[0,0,45],\"origin\":[11.29242,19.9375,-5.92732],\"faces\":{\"north\":{\"uv\":[107,118,108,119],\"texture\":0},\"east\":{\"uv\":[80,7,96,8],\"texture\":0},\"south\":{\"uv\":[118,107,119,108],\"texture\":0},\"west\":{\"uv\":[80,8,96,9],\"texture\":0},\"up\":{\"uv\":[5,96,4,80],\"texture\":0},\"down\":{\"uv\":[6,80,5,96],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"80fdca4d-b28b-c0f6-507c-625123d48c14\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.16742,17.6625,-23.55232],\"to\":[12.41742,20.4125,-19.05232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,19.6625,-22.55232],\"faces\":{\"north\":{\"uv\":[0,109,2,112],\"texture\":0},\"east\":{\"uv\":[87,27,92,30],\"texture\":0},\"south\":{\"uv\":[15,109,17,112],\"texture\":0},\"west\":{\"uv\":[87,52,92,55],\"texture\":0},\"up\":{\"uv\":[47,100,45,95],\"texture\":0},\"down\":{\"uv\":[49,95,47,100],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a2f6ed5c-0f4c-374a-5435-973f5eb5f187\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.66742,18.4125,-16.67732],\"to\":[11.91742,19.6625,-15.92732],\"autouv\":0,\"color\":4,\"rotation\":[22.5,0,0],\"origin\":[11.29242,19.0375,-16.30232],\"faces\":{\"north\":{\"uv\":[119,65,120,66],\"texture\":0},\"east\":{\"uv\":[66,119,67,120],\"texture\":0},\"south\":{\"uv\":[119,66,120,67],\"texture\":0},\"west\":{\"uv\":[67,119,68,120],\"texture\":0},\"up\":{\"uv\":[120,68,119,67],\"texture\":0},\"down\":{\"uv\":[69,119,68,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ddfd78e6-b1d2-bd51-ec01-c77415cba75d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.66742,18.4125,-17.92732],\"to\":[11.91742,19.6625,-17.17732],\"autouv\":0,\"color\":4,\"rotation\":[22.5,0,0],\"origin\":[11.29242,19.0375,-17.55232],\"faces\":{\"north\":{\"uv\":[119,68,120,69],\"texture\":0},\"east\":{\"uv\":[69,119,70,120],\"texture\":0},\"south\":{\"uv\":[119,69,120,70],\"texture\":0},\"west\":{\"uv\":[70,119,71,120],\"texture\":0},\"up\":{\"uv\":[120,71,119,70],\"texture\":0},\"down\":{\"uv\":[72,119,71,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2faff165-0bac-727f-f020-671fa6b6a2e7\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.66742,18.4125,-15.17732],\"to\":[11.91742,19.6625,-14.42732],\"autouv\":0,\"color\":4,\"rotation\":[22.5,0,0],\"origin\":[11.29242,19.0375,-14.80232],\"faces\":{\"north\":{\"uv\":[119,71,120,72],\"texture\":0},\"east\":{\"uv\":[72,119,73,120],\"texture\":0},\"south\":{\"uv\":[119,72,120,73],\"texture\":0},\"west\":{\"uv\":[73,119,74,120],\"texture\":0},\"up\":{\"uv\":[120,74,119,73],\"texture\":0},\"down\":{\"uv\":[75,119,74,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2831f117-3c8b-9a36-df4d-9ecad1568fed\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.41742,18.3875,-19.42732],\"to\":[12.16742,19.3125,-18.50232],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[11.29242,19.0375,-18.77732],\"faces\":{\"north\":{\"uv\":[46,117,48,118],\"texture\":0},\"east\":{\"uv\":[119,92,120,93],\"texture\":0},\"south\":{\"uv\":[48,117,50,118],\"texture\":0},\"west\":{\"uv\":[93,119,94,120],\"texture\":0},\"up\":{\"uv\":[52,118,50,117],\"texture\":0},\"down\":{\"uv\":[119,50,117,51],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d19b1c81-1041-090e-b271-57d212ce1fcf\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.16742,20.4125,-24.55232],\"to\":[13.41742,23.1625,-18.05232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,22.4125,-22.55232],\"faces\":{\"north\":{\"uv\":[10,94,14,97],\"texture\":0},\"east\":{\"uv\":[72,15,79,18],\"texture\":0},\"south\":{\"uv\":[19,94,23,97],\"texture\":0},\"west\":{\"uv\":[72,25,79,28],\"texture\":0},\"up\":{\"uv\":[4,69,0,62],\"texture\":0},\"down\":{\"uv\":[8,62,4,69],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e0147d4e-cb1c-6164-a545-637ffef444b6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.16742,14.9125,-24.55232],\"to\":[13.41742,17.6625,-18.05232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,16.9125,-22.55232],\"faces\":{\"north\":{\"uv\":[94,21,98,24],\"texture\":0},\"east\":{\"uv\":[72,28,79,31],\"texture\":0},\"south\":{\"uv\":[30,94,34,97],\"texture\":0},\"west\":{\"uv\":[72,31,79,34],\"texture\":0},\"up\":{\"uv\":[12,69,8,62],\"texture\":0},\"down\":{\"uv\":[16,62,12,69],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"50527203-69db-5358-9c03-7ce2948218c8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.66742,23.1625,-24.05232],\"to\":[12.91742,23.6625,-18.55232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,22.4125,-22.55232],\"faces\":{\"north\":{\"uv\":[51,116,54,117],\"texture\":0},\"east\":{\"uv\":[106,22,112,23],\"texture\":0},\"south\":{\"uv\":[116,56,119,57],\"texture\":0},\"west\":{\"uv\":[107,19,113,20],\"texture\":0},\"up\":{\"uv\":[42,84,39,78],\"texture\":0},\"down\":{\"uv\":[45,78,42,84],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"c5d62892-6810-ea9d-d775-bb4365179fc1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.66742,14.4125,-24.05232],\"to\":[12.91742,14.9125,-18.55232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,13.6625,-22.55232],\"faces\":{\"north\":{\"uv\":[116,57,119,58],\"texture\":0},\"east\":{\"uv\":[108,45,114,46],\"texture\":0},\"south\":{\"uv\":[116,58,119,59],\"texture\":0},\"west\":{\"uv\":[108,84,114,85],\"texture\":0},\"up\":{\"uv\":[81,49,78,43],\"texture\":0},\"down\":{\"uv\":[48,78,45,84],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ba7003e9-c068-e689-cd00-b2d418bfc31b\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.49242,19.1375,-25.42732],\"to\":[10.84242,19.4875,-23.42732],\"autouv\":0,\"color\":4,\"rotation\":[0,0,45],\"origin\":[11.29242,19.9375,-26.42732],\"faces\":{\"north\":{\"uv\":[108,118,109,119],\"texture\":0},\"east\":{\"uv\":[117,42,119,43],\"texture\":0},\"south\":{\"uv\":[118,108,119,109],\"texture\":0},\"west\":{\"uv\":[43,117,45,118],\"texture\":0},\"up\":{\"uv\":[118,45,117,43],\"texture\":0},\"down\":{\"uv\":[46,117,45,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"af406e05-afd9-6e24-703e-55b85b6a73ac\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.91742,18.8875,-25.70232],\"to\":[11.66742,19.3125,-25.27732],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[11.29242,19.0375,-25.55232],\"faces\":{\"north\":{\"uv\":[119,89,120,90],\"texture\":0},\"east\":{\"uv\":[90,119,91,120],\"texture\":0},\"south\":{\"uv\":[119,90,120,91],\"texture\":0},\"west\":{\"uv\":[91,119,92,120],\"texture\":0},\"up\":{\"uv\":[120,92,119,91],\"texture\":0},\"down\":{\"uv\":[93,119,92,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0fa4c566-f097-47cf-20bc-c8e93923dbeb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[12.41742,17.8875,-22.17732],\"to\":[12.91742,19.8125,-20.25232],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[13.04242,19.0375,-21.02732],\"faces\":{\"north\":{\"uv\":[117,51,118,53],\"texture\":0},\"east\":{\"uv\":[114,14,116,16],\"texture\":0},\"south\":{\"uv\":[52,117,53,119],\"texture\":0},\"west\":{\"uv\":[15,114,17,116],\"texture\":0},\"up\":{\"uv\":[54,119,53,117],\"texture\":0},\"down\":{\"uv\":[118,53,117,55],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"3c008bd4-bf5c-033f-d1dd-55987d3bc51c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[9.66742,17.8875,-22.17732],\"to\":[10.16742,19.8125,-20.25232],\"autouv\":0,\"color\":4,\"rotation\":[45,0,0],\"origin\":[10.29242,19.0375,-21.02732],\"faces\":{\"north\":{\"uv\":[55,117,56,119],\"texture\":0},\"east\":{\"uv\":[19,114,21,116],\"texture\":0},\"south\":{\"uv\":[56,117,57,119],\"texture\":0},\"west\":{\"uv\":[114,20,116,22],\"texture\":0},\"up\":{\"uv\":[60,119,59,117],\"texture\":0},\"down\":{\"uv\":[61,117,60,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"84dc7bab-eb3e-3c92-766f-674f5fe8147d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,21.4125,-19.80232],\"to\":[13.66742,22.1625,-19.05232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,22.4125,-22.55232],\"faces\":{\"north\":{\"uv\":[119,17,120,18],\"texture\":0},\"east\":{\"uv\":[18,119,19,120],\"texture\":0},\"south\":{\"uv\":[119,18,120,19],\"texture\":0},\"west\":{\"uv\":[19,119,20,120],\"texture\":0},\"up\":{\"uv\":[120,20,119,19],\"texture\":0},\"down\":{\"uv\":[21,119,20,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fc170399-36d0-a3cb-d49e-befa5de166a6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,21.4125,-23.55232],\"to\":[13.66742,22.1625,-22.80232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,22.4125,-26.30232],\"faces\":{\"north\":{\"uv\":[119,20,120,21],\"texture\":0},\"east\":{\"uv\":[21,119,22,120],\"texture\":0},\"south\":{\"uv\":[119,21,120,22],\"texture\":0},\"west\":{\"uv\":[22,119,23,120],\"texture\":0},\"up\":{\"uv\":[120,23,119,22],\"texture\":0},\"down\":{\"uv\":[24,119,23,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f3848d14-f6d0-c14a-9ee7-197983dbb509\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,21.4125,-22.30232],\"to\":[13.66742,22.1625,-21.55232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,22.4125,-25.05232],\"faces\":{\"north\":{\"uv\":[119,23,120,24],\"texture\":0},\"east\":{\"uv\":[24,119,25,120],\"texture\":0},\"south\":{\"uv\":[119,24,120,25],\"texture\":0},\"west\":{\"uv\":[25,119,26,120],\"texture\":0},\"up\":{\"uv\":[120,26,119,25],\"texture\":0},\"down\":{\"uv\":[27,119,26,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f1a6a1a1-1fbf-b2f6-69cc-0b02165f442f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,21.4125,-21.05232],\"to\":[13.66742,22.1625,-20.30232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,22.4125,-23.80232],\"faces\":{\"north\":{\"uv\":[119,62,120,63],\"texture\":0},\"east\":{\"uv\":[63,119,64,120],\"texture\":0},\"south\":{\"uv\":[119,63,120,64],\"texture\":0},\"west\":{\"uv\":[64,119,65,120],\"texture\":0},\"up\":{\"uv\":[120,65,119,64],\"texture\":0},\"down\":{\"uv\":[66,119,65,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4d32635b-7169-63ea-8edd-2058b2f5844c\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,15.9125,-22.30232],\"to\":[13.66742,16.6625,-21.55232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,16.9125,-25.05232],\"faces\":{\"north\":{\"uv\":[119,35,120,36],\"texture\":0},\"east\":{\"uv\":[36,119,37,120],\"texture\":0},\"south\":{\"uv\":[119,36,120,37],\"texture\":0},\"west\":{\"uv\":[37,119,38,120],\"texture\":0},\"up\":{\"uv\":[120,38,119,37],\"texture\":0},\"down\":{\"uv\":[39,119,38,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"84ee9ba3-52b6-09b8-d529-4a2cbb570f77\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,15.9125,-23.55232],\"to\":[13.66742,16.6625,-22.80232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,16.9125,-26.30232],\"faces\":{\"north\":{\"uv\":[119,32,120,33],\"texture\":0},\"east\":{\"uv\":[33,119,34,120],\"texture\":0},\"south\":{\"uv\":[119,33,120,34],\"texture\":0},\"west\":{\"uv\":[34,119,35,120],\"texture\":0},\"up\":{\"uv\":[120,35,119,34],\"texture\":0},\"down\":{\"uv\":[36,119,35,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"46417fff-471d-3791-5732-884a2289abf4\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,15.9125,-21.05232],\"to\":[13.66742,16.6625,-20.30232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,16.9125,-23.80232],\"faces\":{\"north\":{\"uv\":[119,29,120,30],\"texture\":0},\"east\":{\"uv\":[30,119,31,120],\"texture\":0},\"south\":{\"uv\":[119,30,120,31],\"texture\":0},\"west\":{\"uv\":[31,119,32,120],\"texture\":0},\"up\":{\"uv\":[120,32,119,31],\"texture\":0},\"down\":{\"uv\":[33,119,32,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"90eb0ce1-ff48-4351-bf47-d88694dcb183\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[13.41742,15.9125,-19.80232],\"to\":[13.66742,16.6625,-19.05232],\"autouv\":0,\"color\":4,\"origin\":[11.91742,16.9125,-22.55232],\"faces\":{\"north\":{\"uv\":[119,26,120,27],\"texture\":0},\"east\":{\"uv\":[27,119,28,120],\"texture\":0},\"south\":{\"uv\":[119,27,120,28],\"texture\":0},\"west\":{\"uv\":[28,119,29,120],\"texture\":0},\"up\":{\"uv\":[120,29,119,28],\"texture\":0},\"down\":{\"uv\":[30,119,29,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"60aa03cc-070d-71a6-406f-6d50e0d1361a\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,15.9125,-22.30232],\"to\":[9.16742,16.6625,-21.55232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,16.9125,-25.05232],\"faces\":{\"north\":{\"uv\":[119,59,120,60],\"texture\":0},\"east\":{\"uv\":[60,119,61,120],\"texture\":0},\"south\":{\"uv\":[119,60,120,61],\"texture\":0},\"west\":{\"uv\":[61,119,62,120],\"texture\":0},\"up\":{\"uv\":[120,62,119,61],\"texture\":0},\"down\":{\"uv\":[63,119,62,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"881889d5-8955-b00a-ef17-f4cad22b51d9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,15.9125,-23.55232],\"to\":[9.16742,16.6625,-22.80232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,16.9125,-26.30232],\"faces\":{\"north\":{\"uv\":[119,56,120,57],\"texture\":0},\"east\":{\"uv\":[57,119,58,120],\"texture\":0},\"south\":{\"uv\":[119,57,120,58],\"texture\":0},\"west\":{\"uv\":[58,119,59,120],\"texture\":0},\"up\":{\"uv\":[120,59,119,58],\"texture\":0},\"down\":{\"uv\":[60,119,59,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7ddf7986-5648-d1ef-1cf5-592925b33ae6\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,15.9125,-21.05232],\"to\":[9.16742,16.6625,-20.30232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,16.9125,-23.80232],\"faces\":{\"north\":{\"uv\":[119,53,120,54],\"texture\":0},\"east\":{\"uv\":[54,119,55,120],\"texture\":0},\"south\":{\"uv\":[119,54,120,55],\"texture\":0},\"west\":{\"uv\":[55,119,56,120],\"texture\":0},\"up\":{\"uv\":[120,56,119,55],\"texture\":0},\"down\":{\"uv\":[57,119,56,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d878a78b-eeb5-f72a-ed3b-7ec05126ba3e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,15.9125,-19.80232],\"to\":[9.16742,16.6625,-19.05232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,16.9125,-22.55232],\"faces\":{\"north\":{\"uv\":[119,50,120,51],\"texture\":0},\"east\":{\"uv\":[51,119,52,120],\"texture\":0},\"south\":{\"uv\":[119,51,120,52],\"texture\":0},\"west\":{\"uv\":[52,119,53,120],\"texture\":0},\"up\":{\"uv\":[120,53,119,52],\"texture\":0},\"down\":{\"uv\":[54,119,53,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"4731abb8-728a-e9c5-a444-84b2ceb44bde\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,21.4125,-19.80232],\"to\":[9.16742,22.1625,-19.05232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,22.4125,-22.55232],\"faces\":{\"north\":{\"uv\":[119,47,120,48],\"texture\":0},\"east\":{\"uv\":[48,119,49,120],\"texture\":0},\"south\":{\"uv\":[119,48,120,49],\"texture\":0},\"west\":{\"uv\":[49,119,50,120],\"texture\":0},\"up\":{\"uv\":[120,50,119,49],\"texture\":0},\"down\":{\"uv\":[51,119,50,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"017686be-6b99-fc6e-430f-51e630c857fb\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,21.4125,-21.05232],\"to\":[9.16742,22.1625,-20.30232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,22.4125,-23.80232],\"faces\":{\"north\":{\"uv\":[119,44,120,45],\"texture\":0},\"east\":{\"uv\":[45,119,46,120],\"texture\":0},\"south\":{\"uv\":[119,45,120,46],\"texture\":0},\"west\":{\"uv\":[46,119,47,120],\"texture\":0},\"up\":{\"uv\":[120,47,119,46],\"texture\":0},\"down\":{\"uv\":[48,119,47,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"39bfba34-edbb-7c44-d1b8-c277995bf5d8\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,21.4125,-22.30232],\"to\":[9.16742,22.1625,-21.55232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,22.4125,-25.05232],\"faces\":{\"north\":{\"uv\":[119,41,120,42],\"texture\":0},\"east\":{\"uv\":[42,119,43,120],\"texture\":0},\"south\":{\"uv\":[119,42,120,43],\"texture\":0},\"west\":{\"uv\":[43,119,44,120],\"texture\":0},\"up\":{\"uv\":[120,44,119,43],\"texture\":0},\"down\":{\"uv\":[45,119,44,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"42dbda59-442e-cce4-8243-bae66ee979b9\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[8.91742,21.4125,-23.55232],\"to\":[9.16742,22.1625,-22.80232],\"autouv\":0,\"color\":4,\"origin\":[7.41742,22.4125,-26.30232],\"faces\":{\"north\":{\"uv\":[119,38,120,39],\"texture\":0},\"east\":{\"uv\":[39,119,40,120],\"texture\":0},\"south\":{\"uv\":[119,39,120,40],\"texture\":0},\"west\":{\"uv\":[40,119,41,120],\"texture\":0},\"up\":{\"uv\":[120,41,119,40],\"texture\":0},\"down\":{\"uv\":[42,119,41,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b7510d87-7ad4-243c-b939-ece13db6e1db\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.76742,20.9125,-2.82732],\"to\":[11.81742,21.4125,-1.77732],\"autouv\":0,\"color\":4,\"origin\":[11.91742,19.6625,-5.17732],\"faces\":{\"north\":{\"uv\":[119,11,120,12],\"texture\":0},\"east\":{\"uv\":[12,119,13,120],\"texture\":0},\"south\":{\"uv\":[119,12,120,13],\"texture\":0},\"west\":{\"uv\":[13,119,14,120],\"texture\":0},\"up\":{\"uv\":[120,14,119,13],\"texture\":0},\"down\":{\"uv\":[15,119,14,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"304765d9-7128-9347-1e49-087cdc5da139\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.01742,17.1625,-2.57732],\"to\":[11.56742,18.4125,-2.02732],\"autouv\":0,\"color\":4,\"rotation\":[0,45,0],\"origin\":[11.29242,17.1625,-2.30232],\"faces\":{\"north\":{\"uv\":[119,77,120,78],\"texture\":0},\"east\":{\"uv\":[78,119,79,120],\"texture\":0},\"south\":{\"uv\":[119,78,120,79],\"texture\":0},\"west\":{\"uv\":[79,119,80,120],\"texture\":0},\"up\":{\"uv\":[120,80,119,79],\"texture\":0},\"down\":{\"uv\":[81,119,80,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"98bf52c3-2eb1-5d25-58bd-72b0e6a5fe1e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[10.76742,16.6625,-2.82732],\"to\":[11.81742,17.1625,-1.77732],\"autouv\":0,\"color\":4,\"origin\":[11.91742,15.4125,-5.17732],\"faces\":{\"north\":{\"uv\":[119,14,120,15],\"texture\":0},\"east\":{\"uv\":[15,119,16,120],\"texture\":0},\"south\":{\"uv\":[119,15,120,16],\"texture\":0},\"west\":{\"uv\":[16,119,17,120],\"texture\":0},\"up\":{\"uv\":[120,17,119,16],\"texture\":0},\"down\":{\"uv\":[18,119,17,120],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"fb35b537-e58f-2fa0-f887-d6d019c2932d\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.54242,18.6875,0.32268],\"to\":[-10.79242,19.4375,5.32268],\"autouv\":0,\"color\":9,\"origin\":[-10.79242,18.4375,3.32268],\"faces\":{\"north\":{\"uv\":[23,120,24,121],\"texture\":0},\"east\":{\"uv\":[111,24,116,25],\"texture\":0},\"south\":{\"uv\":[120,23,121,24],\"texture\":0},\"west\":{\"uv\":[111,25,116,26],\"texture\":0},\"up\":{\"uv\":[4,93,3,88],\"texture\":0},\"down\":{\"uv\":[105,109,104,114],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0e653ee1-fb8a-4e45-7455-91bd2048c061\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-13.21742,18.6875,-1.67732],\"to\":[-12.46742,19.4375,1.32268],\"autouv\":0,\"color\":9,\"rotation\":[0,45,0],\"origin\":[-12.84242,19.0625,-1.17732],\"faces\":{\"north\":{\"uv\":[24,120,25,121],\"texture\":0},\"east\":{\"uv\":[116,80,119,81],\"texture\":0},\"south\":{\"uv\":[120,24,121,25],\"texture\":0},\"west\":{\"uv\":[81,116,84,117],\"texture\":0},\"up\":{\"uv\":[85,119,84,116],\"texture\":0},\"down\":{\"uv\":[86,116,85,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"76cf3cc6-7ee4-f915-d4c4-4282c96d6969\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-11.78258,18.6875,4.89768],\"to\":[-11.03258,19.4375,7.89768],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[-11.40758,19.0625,5.39768],\"faces\":{\"north\":{\"uv\":[25,120,26,121],\"texture\":0},\"east\":{\"uv\":[116,81,119,82],\"texture\":0},\"south\":{\"uv\":[120,25,121,26],\"texture\":0},\"west\":{\"uv\":[116,85,119,86],\"texture\":0},\"up\":{\"uv\":[88,119,87,116],\"texture\":0},\"down\":{\"uv\":[89,116,88,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"97f742d7-a697-80c9-1d9f-397363f4952e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.24242,12.6875,-4.67732],\"to\":[-14.74242,22.4375,0.32268],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[-15.04242,20.0625,-0.17732],\"faces\":{\"north\":{\"uv\":[60,95,61,105],\"texture\":0},\"east\":{\"uv\":[33,42,38,52],\"texture\":0},\"south\":{\"uv\":[61,95,62,105],\"texture\":0},\"west\":{\"uv\":[38,42,43,52],\"texture\":0},\"up\":{\"uv\":[23,115,22,110],\"texture\":0},\"down\":{\"uv\":[24,110,23,115],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"29321ff2-c3b6-8f41-2e32-b6f9c6af8d06\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.54242,14.6875,0.02268],\"to\":[-15.04242,23.4375,5.62268],\"autouv\":0,\"color\":9,\"origin\":[-14.74242,18.4375,3.32268],\"faces\":{\"north\":{\"uv\":[58,97,59,106],\"texture\":0},\"east\":{\"uv\":[38,24,44,33],\"texture\":0},\"south\":{\"uv\":[59,97,60,106],\"texture\":0},\"west\":{\"uv\":[27,42,33,51],\"texture\":0},\"up\":{\"uv\":[39,114,38,108],\"texture\":0},\"down\":{\"uv\":[9,109,8,115],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"73d20e87-3c4e-65b7-ab04-82c6632ac983\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.24242,12.6875,5.32732],\"to\":[-14.74242,22.4375,10.32732],\"autouv\":0,\"color\":9,\"rotation\":[0,45,0],\"origin\":[-15.04242,20.0625,5.82732],\"faces\":{\"north\":{\"uv\":[62,95,63,105],\"texture\":0},\"east\":{\"uv\":[43,33,48,43],\"texture\":0},\"south\":{\"uv\":[63,95,64,105],\"texture\":0},\"west\":{\"uv\":[43,43,48,53],\"texture\":0},\"up\":{\"uv\":[25,115,24,110],\"texture\":0},\"down\":{\"uv\":[26,110,25,115],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"916a6cdb-a6e7-7646-77fb-cd5299df3d75\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.56742,23.3375,0.02268],\"to\":[-15.06742,26.3375,5.62268],\"autouv\":0,\"color\":9,\"rotation\":[0,0,-22.5],\"origin\":[-15.31742,23.3375,2.82268],\"faces\":{\"north\":{\"uv\":[116,77,117,80],\"texture\":0},\"east\":{\"uv\":[78,0,84,3],\"texture\":0},\"south\":{\"uv\":[80,116,81,119],\"texture\":0},\"west\":{\"uv\":[79,9,85,12],\"texture\":0},\"up\":{\"uv\":[32,115,31,109],\"texture\":0},\"down\":{\"uv\":[91,109,90,115],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"210cd302-529f-f9ad-2284-5372cce7b1ac\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.24242,9.9375,-4.67732],\"to\":[-14.74242,12.6875,-1.67732],\"autouv\":0,\"color\":9,\"rotation\":[0,-45,0],\"origin\":[-15.04242,8.3125,-0.17732],\"faces\":{\"north\":{\"uv\":[41,116,42,119],\"texture\":0},\"east\":{\"uv\":[55,100,58,103],\"texture\":0},\"south\":{\"uv\":[54,116,55,119],\"texture\":0},\"west\":{\"uv\":[64,100,67,103],\"texture\":0},\"up\":{\"uv\":[58,119,57,116],\"texture\":0},\"down\":{\"uv\":[66,116,65,119],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2f10273e-7625-2f00-37c7-7c953f6059ad\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-15.24242,9.9375,7.32732],\"to\":[-14.74242,12.6875,10.32732],\"autouv\":0,\"color\":9,\"rotation\":[0,45,0],\"origin\":[-15.04242,8.3125,5.82732],\"faces\":{\"north\":{\"uv\":[66,116,67,119],\"texture\":0},\"east\":{\"uv\":[67,100,70,103],\"texture\":0},\"south\":{\"uv\":[73,116,74,119],\"texture\":0},\"west\":{\"uv\":[70,100,73,103],\"texture\":0},\"up\":{\"uv\":[75,119,74,116],\"texture\":0},\"down\":{\"uv\":[117,74,116,77],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0973a951-8178-58a9-9352-f3fd079a3124\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-3.5,18,-0.75],\"to\":[3.5,24,5.75],\"autouv\":0,\"color\":1,\"origin\":[0.5,31.5,3.25],\"faces\":{\"north\":{\"uv\":[0,50,7,56],\"texture\":0},\"east\":{\"uv\":[7,50,14,56],\"texture\":0},\"south\":{\"uv\":[14,50,21,56],\"texture\":0},\"west\":{\"uv\":[48,50,55,56],\"texture\":0},\"up\":{\"uv\":[51,7,44,0],\"texture\":0},\"down\":{\"uv\":[51,7,44,14],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"40ca8419-4070-3278-78e1-6d76eb6624a1\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.29242,11.21393,-38.03278],\"to\":[11.29242,27.21393,-14.03278],\"autouv\":0,\"color\":9,\"origin\":[11.29242,19.08893,-38.03278],\"faces\":{\"north\":{\"uv\":[0,0,2,2],\"texture\":null},\"east\":{\"uv\":[0,0,32,32],\"rotation\":90,\"texture\":2},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":2},\"up\":{\"uv\":[0,0,2,2],\"texture\":null},\"down\":{\"uv\":[0,0,2,2],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"751aa955-ee70-e57a-5860-8db70824e547\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[3.29242,18.96393,-38.28278],\"to\":[19.29242,18.96393,-14.28278],\"autouv\":0,\"color\":9,\"origin\":[11.29242,19.08893,-38.28278],\"faces\":{\"north\":{\"uv\":[0,0,0,2],\"texture\":null},\"east\":{\"uv\":[0,0,0,0],\"rotation\":90,\"texture\":null},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":null},\"up\":{\"uv\":[0,0,32,32],\"texture\":2},\"down\":{\"uv\":[32,0,0,32],\"rotation\":180,\"texture\":2}},\"type\":\"cube\",\"uuid\":\"12dc434f-721c-d2c1-c9c3-379413c4712f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[5.29242,18.96393,-34.28278],\"to\":[17.29242,18.96393,-18.28278],\"autouv\":0,\"color\":9,\"rotation\":[-22.5,0,0],\"origin\":[11.29242,19.08893,-34.28278],\"faces\":{\"north\":{\"uv\":[0,0,0,2],\"texture\":null},\"east\":{\"uv\":[0,0,0,0],\"rotation\":90,\"texture\":null},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":null},\"up\":{\"uv\":[0,0,32,32],\"texture\":2},\"down\":{\"uv\":[32,0,0,32],\"rotation\":180,\"texture\":2}},\"type\":\"cube\",\"uuid\":\"3d1af7a5-df34-b320-f067-da1b3d2f8cd0\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[5.29242,18.96393,-34.28278],\"to\":[17.29242,18.96393,-18.28278],\"autouv\":0,\"color\":9,\"rotation\":[22.5,0,0],\"origin\":[11.29242,19.08893,-34.28278],\"faces\":{\"north\":{\"uv\":[0,0,0,2],\"texture\":null},\"east\":{\"uv\":[0,0,0,0],\"rotation\":90,\"texture\":null},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":null},\"up\":{\"uv\":[0,0,32,32],\"texture\":2},\"down\":{\"uv\":[32,0,0,32],\"rotation\":180,\"texture\":2}},\"type\":\"cube\",\"uuid\":\"5f3c900f-a480-cb33-b171-ecc6111d8927\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.29242,13.21393,-34.78278],\"to\":[11.29242,25.21393,-18.78278],\"autouv\":0,\"color\":9,\"rotation\":[0,22.5,0],\"origin\":[11.29242,19.08893,-34.78278],\"faces\":{\"north\":{\"uv\":[0,0,2,2],\"texture\":null},\"east\":{\"uv\":[0,0,32,32],\"rotation\":90,\"texture\":2},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":2},\"up\":{\"uv\":[0,0,2,2],\"texture\":null},\"down\":{\"uv\":[0,0,2,2],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"ca06a670-d403-c657-79e8-0824cc3fdc41\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.29242,13.21393,-34.28278],\"to\":[11.29242,25.21393,-18.28278],\"autouv\":0,\"color\":9,\"rotation\":[0,-22.5,0],\"origin\":[11.29242,19.08893,-34.28278],\"faces\":{\"north\":{\"uv\":[0,0,2,2],\"texture\":null},\"east\":{\"uv\":[0,0,32,32],\"rotation\":90,\"texture\":2},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":2},\"up\":{\"uv\":[0,0,2,2],\"texture\":null},\"down\":{\"uv\":[0,0,2,2],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"9c0d5db7-e69b-cf72-399f-66cf71bbddba\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.29242,13.21393,-38.03278],\"to\":[11.29242,25.21393,-2.03278],\"autouv\":0,\"color\":9,\"origin\":[11.29242,19.08893,-38.03278],\"faces\":{\"north\":{\"uv\":[0,0,2,2],\"texture\":null},\"east\":{\"uv\":[0,0,32,32],\"rotation\":90,\"texture\":2},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":2},\"up\":{\"uv\":[0,0,2,2],\"texture\":null},\"down\":{\"uv\":[0,0,2,2],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"a53e20df-9f48-7aa4-7c3b-335394525a55\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[5.29242,18.96393,-38.28278],\"to\":[17.29242,18.96393,-2.28278],\"autouv\":0,\"color\":9,\"origin\":[11.29242,19.08893,-38.28278],\"faces\":{\"north\":{\"uv\":[0,0,0,2],\"texture\":null},\"east\":{\"uv\":[0,0,0,0],\"rotation\":90,\"texture\":null},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,32,32,0],\"rotation\":90,\"texture\":null},\"up\":{\"uv\":[0,0,32,32],\"texture\":2},\"down\":{\"uv\":[32,0,0,32],\"rotation\":180,\"texture\":2}},\"type\":\"cube\",\"uuid\":\"195c9da8-bf7e-278b-5861-541e642e9e06\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[11.04242,19.03893,-25.05778],\"to\":[11.54242,35.03893,-9.05778],\"autouv\":0,\"color\":0,\"origin\":[11.29242,19.03893,-21.05778],\"faces\":{\"north\":{\"uv\":[0,0,0,0],\"texture\":null},\"east\":{\"uv\":[32,0,0,32],\"texture\":1},\"south\":{\"uv\":[0,0,0,0],\"texture\":null},\"west\":{\"uv\":[0,0,32,32],\"texture\":1},\"up\":{\"uv\":[0,0,0,2],\"texture\":null},\"down\":{\"uv\":[0,0,0,0],\"texture\":null}},\"type\":\"cube\",\"uuid\":\"5e478285-5bb1-597c-ab98-f0ba7c72ca3f\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-16.54242,10.9375,-4.17732],\"to\":[-9.54242,24.9375,9.82268],\"autouv\":1,\"color\":2,\"visibility\":false,\"origin\":[-15.54242,18.9375,2.82268],\"faces\":{\"north\":{\"uv\":[0,0,7,14]},\"east\":{\"uv\":[0,0,14,14]},\"south\":{\"uv\":[0,0,7,14]},\"west\":{\"uv\":[0,0,14,14]},\"up\":{\"uv\":[0,0,7,14]},\"down\":{\"uv\":[0,0,7,14]}},\"type\":\"cube\",\"uuid\":\"a97b80b2-addb-5b18-4657-99a47dd5af59\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-1,55.54164,1.11016],\"to\":[1,57.54164,3.11016],\"autouv\":1,\"color\":5,\"visibility\":false,\"origin\":[0,55.54164,2.11016],\"faces\":{\"north\":{\"uv\":[0,0,2,2]},\"east\":{\"uv\":[0,0,2,2]},\"south\":{\"uv\":[0,0,2,2]},\"west\":{\"uv\":[0,0,2,2]},\"up\":{\"uv\":[0,0,2,2]},\"down\":{\"uv\":[0,0,2,2]}},\"type\":\"cube\",\"uuid\":\"2d6272a2-dad4-f12b-0a50-c5cdbd2ec49e\"},{\"name\":\"cube\",\"box_uv\":false,\"rescale\":false,\"locked\":false,\"light_emission\":0,\"render_order\":\"default\",\"allow_mirror_modeling\":true,\"from\":[-21,0,-18.75],\"to\":[21,0,23.25],\"autouv\":1,\"color\":6,\"visibility\":false,\"origin\":[0,0,2.25],\"faces\":{\"north\":{\"uv\":[0,0,42,0]},\"east\":{\"uv\":[0,0,42,0]},\"south\":{\"uv\":[0,0,42,0]},\"west\":{\"uv\":[0,0,42,0]},\"up\":{\"uv\":[0,0,42,42]},\"down\":{\"uv\":[0,0,42,42]}},\"type\":\"cube\",\"uuid\":\"00c5e440-634b-654f-a9d9-95f08060aea3\"}],\"outliner\":[{\"name\":\"tag_name\",\"origin\":[0,56.54164,2.11016],\"color\":0,\"uuid\":\"669dcb38-e598-cb35-ec4c-55b67efeecce\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"2d6272a2-dad4-f12b-0a50-c5cdbd2ec49e\"]},{\"name\":\"body\",\"origin\":[0,0,2.25],\"color\":0,\"uuid\":\"a633f85e-6f17-f4be-fc15-e8561725fc8d\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[{\"name\":\"upper_body\",\"origin\":[0,24.00119,2.34697],\"color\":0,\"uuid\":\"788aa374-e94c-c383-6565-af12949da09e\",\"export\":true,\"mirror_uv\":false,\"isOpen\":false,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"a6fd2059-bc63-6b1e-1adb-b110a04138fe\",\"56c157e4-61fb-c7ec-de55-bc79f81ffd66\",\"26ce26ab-08fe-cbe9-470c-982100b16eeb\",\"d5105f83-0c6e-5ab9-a5c1-d78f73dfffff\",\"02fddf8c-7397-7a63-4a7c-c467b91d5448\",\"b4c05bb6-f96e-2f4d-93ec-7e5dd4a9252d\",\"9a75352d-e331-7910-3b95-f3aa4d6fda3e\",\"17175f5b-35b1-3d5b-3d47-33e3b89cfcf7\",\"004b5756-3745-4694-ac32-533f6ec57441\",\"bcdd576f-aa6b-f54b-c58b-1143876ab139\",\"5990e492-34e4-0cf6-bf08-baf0fb599311\",\"e3e2d9f0-1441-b200-dd73-7e161d346cb0\",\"fefb8a0c-4dc9-eb37-14ea-19ed11636ce9\",\"30860483-4b6d-b211-99f0-8b5163830762\",\"c5de4175-68a1-cce8-dda1-6f7519bc1b2c\",\"976142e5-835f-0783-2b66-047893b31e8d\",\"d6db19a7-5456-61a3-c464-720c60d0ad72\",\"80406cd6-c894-2499-2966-49eeeb4d05b9\",\"20981f6a-ae7b-25df-06bc-490e3d7ff703\",\"c4d4556c-7a64-9699-2301-d8aa058e4096\",\"b413c645-64a0-6eeb-9cfc-b24522d8f63a\",\"c668b80a-6004-f47e-6897-f60fe8fae744\",\"1652a13e-4cf6-7d97-cf8f-1de6a42a62f9\",\"bb38f1ab-01f0-5aff-e1a3-8322fb354dda\",\"ca345538-c5b6-cc10-73dd-3f9acc846f95\",\"c1a5837c-09d2-f191-8927-3147276487e0\",\"550e740c-3b6c-a212-ebfd-d12fbacfee1c\",\"233e6b71-b925-8a60-e09d-c2c1d3dcc613\",\"2e873e0e-30e9-2e8c-0ec5-76ae8cbbfc1c\",\"91c6c002-aa48-c40c-eb94-c2d2c6ce9387\",\"f4bd8628-fad1-fde9-fbb6-68b207058fad\",\"4fd10ae8-f304-3b56-048e-f387a9b4c279\",\"04c0418d-5912-cd30-2b8b-0ad684dbc87f\",{\"name\":\"h_head\",\"origin\":[0,36.54164,1.11016],\"color\":0,\"uuid\":\"e3e41a2b-7718-8509-a501-c13a87118853\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"820916df-84dd-05b4-f88d-bbe80c9c07bf\",\"b75f3760-7df4-f694-3187-05e86def041a\",\"d2d8ae28-2190-b3e0-8982-4c98ab732c72\",\"60360fc3-03e2-1007-9b85-358674639cf6\",\"a7aba758-5ffe-e499-3d63-2584906072f5\",\"58cc8b17-a41a-147f-cca7-e3435f2929d4\",\"ece57724-7e60-507a-ab93-1f99a2f0eb03\",\"623ac13d-7644-fa69-45e2-fdefbd5ef451\",\"07318316-9d72-9ddb-064d-bbf4bf2ae85b\",\"1242df13-8c4c-5466-4de7-c98d18085b08\",\"fe3c2995-33c1-f5d9-bd1b-d6bef0b142c1\",\"15067340-dab6-b719-c5db-ccddd8c13f0d\",\"997d2204-820b-a00f-3097-9963e1100c6a\",\"0e146668-2a18-534a-dd8b-05002a4f7f5c\",\"1a7991e4-745f-7efc-444b-299209fd9f79\",\"2a05fb72-2e28-bf6e-8f2a-9450b726fefd\",\"da8540f3-6a51-c071-0867-38b58f566207\",\"f0b034ea-a228-d9e8-8bc5-94e6d27857a7\",\"41431638-6882-7e35-c079-f79f0a85c310\",\"b15ccc49-db06-5d54-8e86-936d7d405485\",\"ed613350-2d17-57fa-a4d2-b97cb9cdd58a\",\"2f5d276f-2374-36e5-0d78-d927dd2f992b\",\"db0e95b6-4b6c-acca-df2c-3ac9b66cb68e\",\"c1a05c0c-3ab3-9553-a6c4-6e0d418cd875\",\"d11eb44e-4f69-fbf5-844a-7c4cecd2193f\",\"ca91afbe-2f07-a0dd-e8fe-da988c1e6e68\",\"26039549-7222-b70e-e174-6a9ef3e572c4\",\"8291fbf4-6863-c022-2659-40bc06c7fe75\",\"b954a18e-ac4f-0766-3526-c851081a0c17\",\"1348e076-4cc5-4f8c-f4e2-a261ff98ab5b\",\"6e105804-4673-4479-eaed-0423f5918b41\",\"331420c7-b85d-798a-9191-a0d65b746238\",\"74341beb-c746-c557-18e6-3bdcc8579260\",\"9f6c769f-5d82-d995-6b33-028a7b5730d4\",\"01a2d1ed-ed02-b637-51a2-5cf940965937\",\"cf4d790b-d4b4-7a65-6206-b6c09a260faa\",\"f8fa9e70-c280-fbed-5a29-37340fa87ce8\",\"f36f47e6-b78d-00d0-582d-beb80e4b41c4\",\"46051a90-1e40-4677-7aa0-1c2f182902fc\",\"2ca2424c-e508-d492-3fe5-e9222fc5921f\",\"5365895f-d7bd-c3f7-e1a3-73eac3124234\",\"f68c9e09-063b-d45d-67ba-5784f7f12563\",\"cdca1aaf-a948-f853-f008-51da7819ece6\",\"dc68f85b-829a-93b5-feba-70599a0a5929\",\"aeb4b18d-0dfa-fbba-3213-36a2b8301fbe\",\"89820f44-93e4-0e7c-484b-8fa0e83e56ec\"]},{\"name\":\"cloak\",\"origin\":[0,32.04192,7.5],\"rotation\":[-10,0,0],\"color\":0,\"uuid\":\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"21560076-f4db-b654-055a-793913997dea\",\"e151b038-dd4d-7183-9426-8978e675de2c\",\"f2a2c076-3830-827c-a89d-9f32cb8332e2\"]},{\"name\":\"right_arm\",\"origin\":[8.08957,34.33184,2.87003],\"color\":0,\"uuid\":\"8909e1fb-858b-2f76-7079-1923d78bfb4b\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"9681954d-8e83-f105-06c4-e9fa51ed312b\",\"4dc33995-1196-37e1-bf4c-f3fe418da321\",\"bdf0da6b-28a5-f3e1-7130-ad8588783047\",\"0721e534-324b-d2ee-e49b-171b151cd7b6\",\"dfc17fef-2439-97eb-2f60-f03d53a9b1c0\",\"b2a87c88-5035-521e-27bc-4aeac5271565\",\"18eb35ac-628a-8ef8-3c6e-8dbac690df22\",\"22bbe239-0026-6040-7756-3fa9d0c984b8\",\"3a2af0ee-d802-79a2-cb5a-99891015755e\",\"29a5e95c-df63-f7b3-7495-1a02c37f40b8\",\"8d01d4a6-df49-be4c-8e8a-7b488fc41a79\",\"f193499c-22db-5035-ce51-f61078f27294\",\"84f643b0-2711-5778-7f8c-81ccbc28a2b1\",\"137bc3e4-445b-981d-20fd-f1039c3c2cbf\",\"6952340a-c4cc-6f9f-7cae-86bf966078b4\",\"34e10c22-6551-673a-cd19-e2d348ba1e23\",\"d97eb0a1-681c-6e0f-ae41-2dff2b52b9e4\",\"7acdfb4e-2563-277e-8412-a2a4d2fab162\",\"953becdc-ed25-d2ba-a418-ea805624ad64\",\"560d51d1-acc3-bfae-e3cd-a81b839151ae\",\"fa971384-8820-d365-fcb5-a99f37f4f75f\",\"47499439-5a44-785c-6e79-f143d2ce5073\",\"16fc3b7f-37f9-d76e-bafd-e1397a513ffd\",\"419267b6-458e-9b4f-83d2-43b0e6c69f8d\",\"b58b8a38-dee1-2ef6-f951-1f08a07bdbf4\",{\"name\":\"right_sub_arm\",\"origin\":[11.88585,29.64991,2.88267],\"color\":0,\"uuid\":\"a512dfcd-47cd-1831-ffcc-611e43a564d9\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"67a5e263-dd1e-b00e-9b95-a310c307f747\",\"52345605-2265-faad-f6ad-eca1be3dec6c\",\"0da7fcdb-b43d-8b27-971b-6df95829641c\",\"639ce11a-9c6a-6c42-3379-700283fa19d8\",\"ccf114a8-f053-924b-d8ca-24b0a70368da\",\"fdc93b97-72a5-e783-9db3-5a032ac3608a\",\"3251f438-779f-574a-a206-d180ea007ccb\",\"fa153ca0-6723-e9f0-8cfb-7a3617eedafe\",\"73685916-cd25-a75a-fa4e-c1371475dee6\",{\"name\":\"right_hand\",\"origin\":[11.19883,21.58754,2.91553],\"color\":0,\"uuid\":\"dcf45c2b-cc07-3246-e68f-1319556212c0\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"757cfe40-f697-b3d4-3ab5-931a0f300a16\",\"f8c3aa34-a09e-a0a1-10df-c672519263e3\",\"70d08c0b-6a6f-3aec-4064-a6b8dc9beb84\",\"fb17833e-914e-9268-4340-f5603d6a03e4\",\"1a73524e-26f4-7fb3-97e2-8624cd92cede\",\"b4965020-4279-25d4-0eb4-1f7cccba52fe\",\"e369a8bd-344b-ef80-43d3-765cbfcf672c\",\"a9292015-e3c2-6dc9-675c-6234fe1a6667\",\"bdd6d7a9-0e80-b7c7-aab3-b5f075817d54\",\"a6fa060e-d211-974f-c460-2142b9213f67\",\"564eb56f-b9b3-74a8-a7bf-fec1f711bca9\",\"b9e03e5e-eb23-1c48-294a-0003ec580bed\",\"69aa58aa-ca4d-d8ff-032f-fab509531f5a\",\"c5f19499-a1b1-8a9b-1503-6f6e12280777\",{\"name\":\"hammer\",\"origin\":[11.29242,19.03893,2.94222],\"color\":0,\"uuid\":\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"c087230a-862d-9d6d-e4cb-c9d00e17a4d1\",\"80fdca4d-b28b-c0f6-507c-625123d48c14\",\"af406e05-afd9-6e24-703e-55b85b6a73ac\",\"8b5ad391-f368-2fec-1d47-55410fd47190\",\"8977ffc9-8598-74f8-bf1d-68b5cd2583eb\",\"4ff64c77-291b-3c79-e625-bc7ebe3a8bc5\",\"3ba8c767-b1af-b6b4-a90c-2853d99a5911\",\"a74a46b7-034f-fa99-4db3-6c56e6f71589\",\"89c33bc3-1e54-c13e-afef-9a0ca6ef63e3\",\"ac2d484e-6396-ee68-ed13-3a4b61d4b29b\",\"304765d9-7128-9347-1e49-087cdc5da139\",\"fb35b537-e58f-2fa0-f887-d6d019c2932d\",\"a2f6ed5c-0f4c-374a-5435-973f5eb5f187\",\"e0147d4e-cb1c-6164-a545-637ffef444b6\",\"fc170399-36d0-a3cb-d49e-befa5de166a6\",\"f3848d14-f6d0-c14a-9ee7-197983dbb509\",\"f1a6a1a1-1fbf-b2f6-69cc-0b02165f442f\",\"60aa03cc-070d-71a6-406f-6d50e0d1361a\",\"90eb0ce1-ff48-4351-bf47-d88694dcb183\",\"46417fff-471d-3791-5732-884a2289abf4\",\"84ee9ba3-52b6-09b8-d529-4a2cbb570f77\",\"b7510d87-7ad4-243c-b939-ece13db6e1db\",\"42dbda59-442e-cce4-8243-bae66ee979b9\",\"39bfba34-edbb-7c44-d1b8-c277995bf5d8\",\"017686be-6b99-fc6e-430f-51e630c857fb\",\"4731abb8-728a-e9c5-a444-84b2ceb44bde\",\"d878a78b-eeb5-f72a-ed3b-7ec05126ba3e\",\"7ddf7986-5648-d1ef-1cf5-592925b33ae6\",\"881889d5-8955-b00a-ef17-f4cad22b51d9\",\"4d32635b-7169-63ea-8edd-2058b2f5844c\",\"c5d62892-6810-ea9d-d775-bb4365179fc1\",\"ba7003e9-c068-e689-cd00-b2d418bfc31b\",\"50527203-69db-5358-9c03-7ce2948218c8\",\"ddfd78e6-b1d2-bd51-ec01-c77415cba75d\",\"2faff165-0bac-727f-f020-671fa6b6a2e7\",\"2831f117-3c8b-9a36-df4d-9ecad1568fed\",\"1c221324-8e5a-d711-e550-a1f5e3038d8a\",\"98bf52c3-2eb1-5d25-58bd-72b0e6a5fe1e\",\"9d609d71-99c7-9a40-eeac-222423bcb4e8\",\"95560117-7d00-3533-3923-c0492750ea03\",\"f3f5b091-43dd-29d8-afc7-d5b805eeb9cb\",\"0fa4c566-f097-47cf-20bc-c8e93923dbeb\",\"d19b1c81-1041-090e-b271-57d212ce1fcf\",\"3c008bd4-bf5c-033f-d1dd-55987d3bc51c\",\"84dc7bab-eb3e-3c92-766f-674f5fe8147d\",{\"name\":\"vfx_pierce\",\"origin\":[11.29242,19.09131,-38.08726],\"color\":0,\"uuid\":\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"751aa955-ee70-e57a-5860-8db70824e547\",\"a53e20df-9f48-7aa4-7c3b-335394525a55\",\"12dc434f-721c-d2c1-c9c3-379413c4712f\",\"195c9da8-bf7e-278b-5861-541e642e9e06\",\"ca06a670-d403-c657-79e8-0824cc3fdc41\",\"9c0d5db7-e69b-cf72-399f-66cf71bbddba\",\"5f3c900f-a480-cb33-b171-ecc6111d8927\",\"3d1af7a5-df34-b320-f067-da1b3d2f8cd0\"]},{\"name\":\"vfx_slash\",\"origin\":[11.29242,19.03893,-17.05778],\"color\":0,\"uuid\":\"6b1142e6-7581-b73e-4a67-e134ad4585c6\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"5e478285-5bb1-597c-ab98-f0ba7c72ca3f\"]},{\"name\":\"hammer_tip\",\"origin\":[11.29242,19.03893,-21.30778],\"color\":0,\"uuid\":\"f2778196-fbf7-7f1d-e39b-d4f62da49e44\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[]}]}]}]}]},{\"name\":\"left_arm\",\"origin\":[-7.83957,34.33184,2.87003],\"color\":0,\"uuid\":\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"d603d704-1925-f44c-266d-72cc47243394\",\"508cda1a-e3ba-807d-6286-d5d1cc9fb6e4\",\"54c37abf-dbde-9f65-0956-990938e33023\",\"722703c7-5013-9ab7-215d-48e7c06ff787\",\"ef1ded35-14f3-8696-879a-601eae597e63\",\"ef81dede-8278-e312-7aad-6a074c2d8d48\",\"de921633-70f2-da2a-1f66-363321a64837\",\"845173dc-0d83-f68f-e654-bf3ca32bf352\",\"e006de73-92a6-ca4a-8d6d-4690f7579d0c\",\"fdb96169-284e-3bbd-53d1-03b4d5fb6897\",\"7fd3fcca-9ba4-2adc-2762-35fde297b11c\",\"3a0e61a6-2d75-adaa-7062-4259da88a4e0\",\"91149954-6324-2c6c-09e2-731c212abb72\",\"b8894893-a530-25de-6c35-cbd6b147abe3\",\"58b6c4b7-3e97-b3c7-1227-a0ed6ed79a99\",\"df480e42-eb3d-2448-bd27-bfe5d6b68cc1\",\"dad8a33f-b4fd-6f3e-6c94-b04e0b7fb511\",\"7187327e-ad38-ae9f-8f99-2ad9529ac9a3\",\"f877ff54-6c50-4e2a-11c0-e426a27ef36c\",\"59c92544-ec84-5abb-6d63-a1d70a6d7b06\",\"9ec2de2a-8f71-a7e2-674e-8d9e602defde\",\"ec32ecb8-e42d-396e-9431-867be23ad41f\",\"e25b8ff0-483c-0dbb-7459-b417b0e56469\",\"45704531-8633-32b8-8a22-59a1514f7b9b\",\"50a5b6d1-8c29-200c-2a12-ba217ea71245\",{\"name\":\"left_sub_arm\",\"origin\":[-11.88585,29.64991,2.88267],\"color\":0,\"uuid\":\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"fdddcb40-2248-3a33-3221-dfca397c82fc\",\"c44306cd-d399-8e82-5bd3-e47320ea1792\",\"718c9191-dce2-cf36-c73b-b759257f64f4\",\"2d700834-8646-abd2-38c7-074d7eff5448\",\"9c60e29d-b8ef-986c-fb03-327be81416a6\",\"62206ed9-99dd-90ed-2d52-cd55447fb334\",\"f004b9df-bebc-3520-d00f-ae5b0be298c0\",\"60f5e47b-26aa-497f-5a67-2da702d98cb6\",\"a4707f71-4a31-7c53-b88e-7483a893a6e3\",{\"name\":\"left_hand\",\"origin\":[-11.19883,21.58754,2.91553],\"color\":0,\"uuid\":\"08fa9aea-2f3a-ad98-9647-65d992767b55\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"47b56504-f1d3-9686-415e-a4f0709dfafd\",\"5ceffcd8-205b-97a8-bfeb-be8bbcb0cfca\",\"63ad2ccc-25ba-c115-dbf5-20ab848089b9\",\"8acf7fca-4399-fc24-c932-72bf5ca8da68\",\"8d0f979c-d198-2689-ad2d-5351f49c68a7\",\"1779dea4-7ee2-5b9f-985c-8616cbc88867\",\"939e2423-ada1-5d47-2c16-e0f6e92b53dd\",\"4ca72344-81c5-e89d-19f4-dda145e7d3ae\",\"f0e925fc-4f13-150a-775c-1c5d29164e3c\",\"0bbbbb03-7bcd-305b-ca5e-f058b7ff5bb4\",\"fdad1047-07ba-c7f6-deb1-005943d3b357\",\"63e86aa5-0394-218d-e0f8-9ed00cbd7b9d\",\"dc122ad0-1ea8-4da9-420f-0e2874ed1f0d\",\"3e61372b-b54f-9344-36e1-5186b613c788\",{\"name\":\"shield\",\"origin\":[-11.29242,18.9375,3.32268],\"color\":0,\"uuid\":\"621973ec-d68a-5564-596e-d9cd80a4a0f6\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"0e653ee1-fb8a-4e45-7455-91bd2048c061\",\"29321ff2-c3b6-8f41-2e32-b6f9c6af8d06\",\"2f10273e-7625-2f00-37c7-7c953f6059ad\",\"916a6cdb-a6e7-7646-77fb-cd5299df3d75\",\"0973a951-8178-58a9-9352-f3fd079a3124\",\"73d20e87-3c4e-65b7-ab04-82c6632ac983\",\"210cd302-529f-f9ad-2284-5372cce7b1ac\",\"76cf3cc6-7ee4-f915-d4c4-4282c96d6969\",\"97f742d7-a697-80c9-1d9f-397363f4952e\",{\"name\":\"shield_tip\",\"origin\":[-15.54242,18.9375,2.82268],\"color\":0,\"uuid\":\"ced16314-0730-1aa9-0196-c7a0c2f8177d\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":false,\"autouv\":0,\"selected\":false,\"children\":[\"a97b80b2-addb-5b18-4657-99a47dd5af59\"]}]}]}]}]}]},{\"name\":\"under_body\",\"origin\":[0,24.04289,2.51243],\"color\":0,\"uuid\":\"820eda84-2205-a32c-17c7-d80c17b133ed\",\"export\":true,\"mirror_uv\":false,\"isOpen\":false,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"fc34e1fb-b43d-b65a-94a7-9137ff36bdb2\",\"f9e2b3b4-83be-9820-5fd2-6cc5671c0d06\",\"174a8673-5e4f-54fb-8845-de9053585a39\",\"7069c39d-7c58-42b2-26d5-f9812eb9eece\",\"efd0ecbf-1a4e-dc52-e0be-fe4c9b79e954\",\"9f7af667-56fc-1051-3344-31768975a667\",\"722552e7-6fd6-e7ea-9dab-4d9bc3971265\",\"40ca8419-4070-3278-78e1-6d76eb6624a1\",\"b6c79338-054b-389c-c5fb-b4847f74bdf7\",\"43e12532-2dd1-ac2b-45a8-200ffb0c32eb\",\"00ac818e-4fca-67c4-cb16-1bf156ea1ec8\",\"3a929634-fa05-3210-ff3e-5605ad95f727\",\"92fea33d-fa66-6a60-cefd-7ddb861f07de\",\"08acc518-8e41-a360-e3fb-99702198e77e\",\"61a1fc5e-ca0c-2440-50ad-c9ed748263f8\",\"caaf69a3-d39a-aa71-72dc-fd4da017e228\",\"f78e3b4c-9ec3-6e3f-34b0-f82d64ce42f3\",\"108f8152-a871-a04e-3694-ee8429454f4d\",\"a8cf07da-f774-cb70-728f-fddd7dd86939\",\"4f38d578-03f6-6f4a-c0cf-9ade07a86ed9\",\"d6f505c5-44ae-c646-2aff-fbf44961b32d\",\"3904e256-e178-2e6c-d063-3e84a6ce82b6\",\"17e9cc5d-478c-6abb-ce1d-6510a8f229e5\",\"6d219fa0-914b-4c91-2184-7e3e52b423bd\",\"ed0088d8-cbda-85d1-1d53-989379b69dbe\",\"ee2d6ac7-e504-ec74-8603-62e046365557\",\"82e0d2c1-d8ff-f573-2089-cb24e3f5066a\",\"58509bf9-3d6a-868c-d637-ff183a6cdc0b\",\"db3d2176-8b10-9f62-b5ba-2582cd79869e\",\"a9a3fdae-afce-dfd5-2b6d-f2eab998494f\",\"c7ff5c49-b255-2025-50e2-7dbc631d35b1\",\"948e9537-9de2-64e9-517b-27eeca22ebff\",{\"name\":\"front_armor\",\"origin\":[0,18.025,-0.75],\"rotation\":[40,0,0],\"color\":0,\"uuid\":\"81c98f4b-5ade-166e-be9a-8cbee4eddc3a\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"919f6ad0-5645-985f-2e89-4f499936da91\"]},{\"name\":\"back_armor\",\"origin\":[0,17.81382,5.75793],\"rotation\":[-20,0,0],\"color\":0,\"uuid\":\"6e67f127-d75b-e721-1019-14fc3600d26f\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"9069559d-0e29-81b5-612a-496ee632e27a\"]},{\"name\":\"right_armor\",\"origin\":[6.825,18.075,2.5],\"rotation\":[0,0,40],\"color\":0,\"uuid\":\"5371e06c-b54b-154b-b1c7-cfce2a9b20d4\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"6e6bfdbd-594d-cad5-b4b0-7b3fe365077e\"]},{\"name\":\"left_armor\",\"origin\":[-6.825,18.075,2.5],\"rotation\":[0,0,-37.5],\"color\":0,\"uuid\":\"3a05cd31-c803-0a4c-716f-337c916a2ee5\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"cca29179-e0a1-fc3e-5ddd-31cbb7c1ec41\"]},{\"name\":\"right_leg\",\"origin\":[3.67015,16.62216,2.25],\"rotation\":[0,0,5],\"color\":0,\"uuid\":\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"fa05b37a-8d36-473a-80b6-04517fb820f1\",\"2a01c2d2-7cd6-7b7b-2271-a69360c97bbb\",\"a6d01e53-e044-783f-db4d-4fafd64dcc16\",\"5c46d5ab-7804-c02b-767c-a261e30d4ed5\",\"1f0c2fcb-f77e-bb94-21d8-46a5aa7b0dd5\",\"0edb8eca-2de0-0bed-b9a2-1cb5665cb3e0\",\"c7656138-efa9-f632-69ce-d6c344f64e6d\",\"d9a8de97-fd96-8be7-1d32-234b444005dc\",\"bbfd3b01-4e82-5915-0284-5ec05be23f5a\",\"7e816966-7fdf-ee4e-f702-6982983edc0a\",\"e85ce7b0-711b-5558-279c-95322808712b\",\"0775f99c-e0db-1022-ceb4-58e90e3c33f4\",{\"name\":\"right_sub_leg\",\"origin\":[3.6018,7.24722,1.98421],\"rotation\":[0,0,-5],\"color\":0,\"uuid\":\"76bfe555-a273-5b15-5684-5779b6fddf7f\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"0646db02-a200-9b1f-229c-08c15715b008\",{\"name\":\"right_feet\",\"origin\":[3.60202,2.2325,1.95098],\"color\":0,\"uuid\":\"436ebcaf-7570-8c3d-368e-80184e593a90\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"4a69a736-4e03-2708-8e12-d859066dde6b\",\"67aa1f25-afaf-291e-ac2e-8431eb1b480a\",\"a4f58cf6-bc79-dd90-4c51-6ecad2650c26\",\"513276dc-9049-753a-13bf-2a5cf4f197ab\",\"54e9970f-49f9-29a9-04d1-2c8e7099bf55\",\"b70e23c8-c0f5-91d4-399f-6351986144cd\",\"f3622b46-ec20-0528-a9a7-46b0dd0cbc5b\",\"95ac3da4-49bd-6f19-ca05-4972ba65cb16\"]}]}]},{\"name\":\"left_leg\",\"origin\":[-3.67015,16.62216,2.25],\"rotation\":[0,0,-5],\"color\":0,\"uuid\":\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"1c3c5f85-a621-e9b2-0336-471cbcc6e18b\",\"8a7a2efd-5ca7-a58c-d13c-a0c9d491963a\",\"20579d70-7fd9-9640-758b-9cb91bf8c8c2\",\"b81b6460-4ec1-dccd-76bd-c6aee50e3af8\",\"6a259483-478e-e732-e39f-95f07ae3adbf\",\"0b9618b4-fc3f-7310-9d4d-e38f2fe541fe\",\"aaff0afc-ccb6-ee65-cc65-691c3ab20d47\",\"30868f8a-27c4-db95-7b51-d3227b252417\",\"f32e0837-cf2b-2fd2-2d0a-a4f0b02f63f5\",\"b40b0cc3-9809-2839-b031-5124f77b3721\",\"5f5bd476-ae04-dfcc-0637-40fddcbb54ba\",\"30d51b39-09ac-67d2-92c7-c7f791abdaa4\",{\"name\":\"left_sub_leg\",\"origin\":[-3.6018,7.24722,1.98421],\"rotation\":[0,0,5],\"color\":0,\"uuid\":\"d21a5df4-acaf-a697-c2f5-1e490af18700\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"4b2bd505-e7b7-881c-fe09-1452c0fb24fa\",{\"name\":\"left_feet\",\"origin\":[-3.60202,2.23749,1.95098],\"color\":0,\"uuid\":\"c942401f-8d13-b8e4-8a4a-173d0e84ed99\",\"export\":true,\"mirror_uv\":false,\"isOpen\":true,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":false,\"children\":[\"19245183-7ac5-9d1b-aa96-e962912f2d8d\",\"4a289af4-8561-0f5e-93c9-d96eb96dc343\",\"8c07b076-c295-d254-3ff8-54610dafefb3\",\"3214c9af-3f64-9d60-ba4b-5af50d31e81d\",\"b61b47e5-aef0-c7b6-d356-aba62813221d\",\"b00f9f74-7073-84e7-5351-080096405a56\",\"9f80b25e-feab-7894-4534-3b6a5d570273\",\"ab201e6d-7749-1af6-e5c5-cc3e1f2dd006\"]}]}]}]},{\"name\":\"shadow\",\"origin\":[0,0,2.25],\"color\":0,\"uuid\":\"13f14e6e-cb30-77bd-8dad-1a4a126c67b7\",\"export\":true,\"mirror_uv\":false,\"isOpen\":false,\"locked\":false,\"visibility\":true,\"autouv\":0,\"selected\":true,\"children\":[\"00c5e440-634b-654f-a9d9-95f08060aea3\"]}]},{\"name\":\"hitbox\",\"origin\":[0,27,2.5],\"color\":0,\"uuid\":\"eda9ab8a-65b4-2f01-0572-e038295f98fa\",\"export\":true,\"mirror_uv\":false,\"isOpen\":false,\"locked\":false,\"visibility\":false,\"autouv\":0,\"selected\":false,\"children\":[\"06fba819-151e-afac-85b3-405b6bb18348\"]}],\"textures\":[{\"path\":\"\",\"name\":\"knight.png\",\"folder\":\"block\",\"namespace\":\"\",\"id\":\"0\",\"group\":\"\",\"width\":128,\"height\":128,\"uv_width\":128,\"uv_height\":128,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"9ecc68d2-cfec-2635-1845-a32d430a1c11\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAIABJREFUeF7tfQecJFW1/lepc+6Z6clx8y67S1iQoEQXUUAeoPKe4lMExIT5qQ+V8EyIOYEBRHwIRhRRkOSKBAGBZdnA5pmdnZync6i6f8+tqunqmu6eHliQ9/95denp7qrbt+797jnnnijgBbR71y9l5W5LaAxJVeVfXbhtv1Br1+fG6sv2V+7+34yMVez3Ay2NNfXz7YHhsn3cuWbFvPsfTCbBL6b/MP5/3hqd+nPSJ6IoQNP0dz/YP1Ey7He1xCpOw7Z0DqPZLJhq3Ew/w4pDuH92tuKzdjud/MJ92Sy/5u2dzUwSgB/vH5y7J3vSMnbO5lHcPT1dsZ+aF8l8ikqLT99zAGgan6u3bd1Xc9+vZAD8OZmE1wF0HNEGgWnY+eQA8hrQ6WHwiIDiFiHKAjJJDUxl+PLusZoBsD2Tx0gmUwKAeKEACPrUPZFI1AyAN3e1MKcI3Nk7PPf7M6oqEFBMkJRDYs2LVDMA/j+jAPfE4/ApAmSJQZEETGcYcqqAVV6gySEALhGCDGgpDZk8w5W7SgGwtr6+IgXwqIV5FGAgl5u7vtPtLLn337z+uffXjo7yv98Y8qN7WR3yozOYSBTw+9lZOEUBfakC+o/vxsrH+uaoxKEBwFFLGYhiFanWXL9JjSHx/xkF+N3MLERjm9BrQdO5gYgcchrDzQMTJZvIJM3mpFQDgFdTMZHNoEd2zM3hd0dG5vrb4POWsKSLw5G56/4OFY1hN2SXC96Baf75xwYG+L3fb2tjG5vq0exJ4L3bpnDjWGW2uSgKcO/xBu8vAMjPB8H/jzLAh3bvL9k4q3yekvd3TUyVzOEpsRgLyOC7cDrPkGfAg5ZFtd58UWsj01ge3oJcFgB2MFlJ+TtamljU74LicsAxPMPvv2ZYl21eOgCcbAAgC4D+kRwkGmNnQEL9vykD2Ceanujs5gaTFetCIOGdAc/PxEsAcM9kqYB1XH09iygC/DIwW2CYzAOPVdmB1Nn7YrG5nW6lANUA8MP2NuYPKvBG3VBlGZkDk3jHnoG5sWU0TSAh8NzNo/jDoRIC733tUgba/bT4GQCS8Y9AUAASWYZk4f/eKaAcAM5qqYNLEhBbWgdP0I29Tx5AKi9g28wsSNqmpjJg03SppL4uHGZBWUC7W8BQlmE8x9BRjvlW+KzF68afxkpPEqs8LrR4RExmGS48aTVYVoPqcaPv8R1wKiL8SxswtWMYU1mGKweLJxwCwCGVAe49xeD/JKcQEEhGUYwjUh5IJhgSuf97p4ByANjYFIVbBhSHBDWnIqsCWQ14fmoWDU4JecYwmWNISCokSeCEUBYESKLAASKh+Hc1PnuqP4rOY7vACkAmmcHYrhF8Z3d/CTyWuJzwygL//TObGyArDqiijOHBEfgcgEMS4JAAOkG+d+9QyTHw0FKAEw0A0OIT2yIAEBWgF5cDM8NZJFMqFxBfjB6gdYmAWIeA3h0ME4NFOcghArcPVRZoyi0kjW19KFB1D26enp33/SmxyJzwRxNLo6DXbdM6AAqMYSKnIUIMv0rTGOOCI12vMoa8pnHKkdcY7/Mt9TGEPA64HBJyuTzSmTxuHtElfP16hgIj4ZPkCQZVY3h87Vp0PfH03EJ/paWFT9KD6SSaXCJiqoIpRUI2pmJkb/YQsoBjDBmAFt0FuLoiyI7NgOVVOMJ+TPbNIpnUIKjA25574XqA+lYBigtQ88BInw4A2lV1DgHXHxhd8GxsX48XAgBT4Lq0rY7JYGACsC+pL55XEhB1Crgg5jNAog+J/kuL9u/P6ry4IxiEIgp8Ad30MEY7yuctGeJzs8m59/szxFv1ttzroV+GYEghp7qLAqgp8dN1n23UFWCPZTJwSwJ8GhD0ujkANm2dPnTHwHs3LGU6rQPgFqA0eJCfSvIndzYEMbFvGsm4yk8IF26tXRNYdQst4ssXSgFaXfruOiMw/8z+p9kxTnrHshqShvKPJjmoCHh/i59L+3zhRUDSgBz18/RBPurDGqKIuRWM5zRkE8VFrhUAUaWUupzhK+oBtkgCAjKDQ1Pxy8HJklm6NBrFdaOjwkVHxtihpQB0DKSnlQHBJUFwi9CyOrJ9q1ox8vd+JOIahDzwti21U4BFrHHVS18oALq9gFMAjnCFOVk2tL789bbxceQ0IEVb39JIJ/ClJSF4JRFZBUgrgIdkozzDSU8e4FeuaYxiedCDfakCMhY2UysA7Bo8Ot6ZQ/gF0+AVVCiFPH49VKqL+HhDA2sL+tC6XsSN940fQhZw6lLGeb4KOFuCyE3Mghm6ayXiw9TBuE4BcsCFz70yKMBooYCEqtak77hpeY/OmC1Xf3xvX0XQTRQKVftdFY2wdp8DSZWBJYukXTFUvWbHfcn03G9MkCrYaKTKtf64FQDv7u/n332sJcoInPcQJbbYBujv37xpCfvYnf2HjgXc9/rlzNUaQaZvAkqDH7nx2bnJkjwOzIxnkSQKkF2cLeClogC0+NRqBcC3lnQWAWAA4epenZyXawsBoCMQYG5Zgqpp2D09M7eYJ0dCJeSkEgCicikL2NjoxI54DjvjBQzndfBd0hBhJGP8xZAj/quzdW6oDWuFQ0sBNr3tSKYmMlBCXmTHaPerECQRgijCUe/HZO8s4mNZfkRczClgMQC4oqudT54ightpQMcgJ0BrnUkBmZyusKH21GyCC49OUURQATKaLrnfa9Pemb9/Xbfet7V96cDgCwbAEXV1TIIu/T89MfmiAdDuFRFWRGyL57ErleP9neDz6SeAE3ogeHy4cVvRGHTIAXDvaUuZ4JD4YmeHp/kRUJQlCIoENZVDcpYhkTBOAYuwBr4QALgkoC7AIAcAj18g6QuJKWB0SkDGoKL7sylAE+CTJdQ7gcm8hpFsAb8ZKS6G9bfLyRBfObsNis8NQRLgagihENfJdWp4Cuf+ck9VFnBccxOrk4FcvoB7LMfXW1cvYU6PA+7GEERRRK5QwPT+Sfzn9l0l/bU5HGXN2/05ffGtALhtQzsaZAE/PlBkNQSAQ8oCuCKIBEBZBlMLXA0seZ3cnFkgAEwDiYTKD83/uf2lkQGu2djMBncwtK4U4HYD3oiA1AxDNCYgMcGQSANDexk0UtykCiioIhyiBDKV+hQVg5k8bhqoHQAfOCk2JxII3O7PIEkiNFXDnm3Fo105ED8lygiJKtLZHDYNjc8t2vUrOpnLIcPldkBSHEjmCpiZTuNDu/YuGgCfaq5noYgbPp8De/fPYJlF5/GSUAACAFeM0D8RkEMeqPEMCmkNqbiGVFrXmrxjW+2ngMM9npocOWiSe0JevriCCDjdQCQmcLktlwGSMwx5YwPQ+EiyD8siwHRtlSiqmC4UcIvNgmcuXjkKcM4RRQucfZFzpQq7eRi4P69B0FTk8nl8aUUjgl31YC4XJI0hM5VGIZXGpl0j6F7TBMXlRGE2jeRUEl/cvLekL7swSF9ax1rutJB1KXh3i3xozcF/PG0JI4UIF/yNJXPEgshNpVCI55CKM6TpzATgXTt6a5K86drFAOD1sbq5yXHK5IwhQ5BkfhohTZpK/gjGyWRSTSFAu5UBU3lgVlWRUjXcNVZqwasGgMu7otiUMC1e+pXdCnH2+e20gBfOJj8KM1nOEj9wYAiMNH+qik/VB6EoEgqCAociQ9M0FHI5PJbKoD7kJsUxRE2DouXx7b1DiwKA9eRAN365uRkmAK5z+/Dpu4taQ/uoa14kuvH3py5lmqDvcBI9mUC7j4FpgJYVkE0zpEnvSdLpIljACwEACXchl4hgTyOcLgecfgd2PdaLdDY/dzTdnk6QvwZECHBLwFBG42f6+yoIgeUowEVtUTydI74n8H5pQXscKqbzpM4FSD1NfVO/FxzZg9Q+XSnjX9+ML9z7HKKKwIVPup5A8466EEJtIbgifmhqAdmJOGYHZ/Gj/qLwdklDCIFmP5hLwdiuSVzcpx/5nrx4TVlKueFHW0vWkfQAdP3nVoUOrT/Ab07sYRpRAMMhhHYWV1VqArQsQyZH3jL6Seqyl4gCHB0J8gmmJ/Y6RHgcEgqQoAgM0xkVWbJGGhTg+weLhpFyPNr+2TdXNLNv2nz66JoNrS3wKyIKBQ2zuQKWyWmu29+f0hBWSMjUbf/vOG41krvGebeBw5txzT2bEZAEpDWiQPq8XBAOwOdVIMkSZGgQ83k+7l8MFi2AbwuHuCmaaxg1IBbwQm51g4Se6T0T6M8VUN8YgOwQcfVft/Lfe/70tfwotOKB7XOPtePYjkPLAn5+vK4o4fyfVl8QQMYORnNeAPJ5hizTXQTe/RJRgC5/qR6djqCiIPAJU1VtbvfTLFRzIC0HiGt6mtnNB0tNsRwAHW1o9MhIZfMYSeaw1TDW2Pv49ooeuLvCyA7MQsup+PzAMFpdAibyulmYWjyV4q8XHK2zMlPv9MjmokHqvMZ6DCMHX3MEuak0RhI5vMohQHYp0CQFu2czcDocEAQBP9q9TwfAxsMwEU/i+Mf099QIAN8IhfGJ3z5ekdIvigXcekQ3X3dzh3FLiE4GuHaQnjFneLi+5/m+mvteDAs4MRbVKYCxQwiM9DexBFEE8mSMNAjlN/brpNPeTgx5iHthMK1yQ40sAC1uiQNpf7KoiTPvW9HUBI8iIV8ocBDsnZiAqWQyr2mQ5aoaN/sYbjq7q4ScX3Rn6anpfR3NLBz2cZlqJp3Du08tb9G8775RRJc1IL5vDLNZDf87VgQSAeCQuoT972HdnLjO6coNgZC/J5MlY8i8xBTghIYInBLgkgFPwAnJIUMqFOBwK9wzZnZgGrM5AiLw7d6DZQHwxnof52LPx/PcukfgWeqT4RIFbJ6Zf7SzS9k+SSrLi2vVOHLq9KYlzNsSRWpoklNTu07h7HC44snozqn5Quwf1q0quf5je/uw+agWODeV6hbsQKx5l9KNP11dilp7Z5wCGA7yi6EAS10udkwoiGN8XuxMZ/DH8Ql8eEk997oVQhJHnDatIplneHRGg5MWXwKcHhlaXuNgSKsCRIcEMZPDZBbIFoAfHCj6yFvHelLIQx7cGM6qHAA0CVGHCKckYCBt+vsX77AD4NxYiNU5RPygv7w+oRzVqfTZVUtbWaglgkDEC5bJoLdvGpmxouWQ7tujZtHgEjGU1nDnZFGlfOYpJ88DyfZHHi1hAQsC4KdrunknDkGATxQwZ6igWTHgsfHp3TUBpVrMAP3GZTt0K1kt7YM99WhyCZCiJIEzsGkNibSGv05rnOSTKthcPBqcLOkCEz0MsQFqN/aXB8Aylx5UQeTfbNQHcbTdGT3Qwmy0Ux1OB9d3kLyh+N18x87sGVxQE1jLc17a1sgC0QCEApDNZpFM5xAh8mVp2/JZeCQBiQLDPRabwqEBgLGraeF9ksiBwBeedCdkixCBjY++QABYj8/a4gBw7eoG7m8vBGWdSecZ1FkVb37iQE1gpPlbKODELiR+wuKcac5/+3onmjQRKUH3hndB4M4ho9DwgT/VPpZawEDXWI+ibR532duaHeSHB9xmREnd3NXGgiEZ+ZzGHXIOr9flpJXe1MIsgCgAzSgHgEkB6ANy9zJdvjRg46aFQXDv4Ya/gDlsAhGBwBASL9taOwW47rgmIkv6tqRG/SQ0nPeX2hVMhwIA+W4RToMU0kjcJPcAmGEMX32s+jHzrOWlfJzu296rnwKolYvYsQOgzS2i3S1iV0LDmKFkswPAzv9bHHqcQW0AMCmAKMCnkN7coAAEMtPpkwEb76sBAOQyRlSDnpS2C80YjYX+zgGXbV4EAE5t0akQPwwb/3Iaznvw5QWAc6kEt6C7dZEBlky7dNwgpdDBUQeW9NRDlARs2dyPn9v0Dqf1BBlNp1MSuX6E/vUNFo01tQCgHAkwAfC4zZfR2t/YcRtYwDFTAwU4rJsfiRRSaDgEKKTaIm2fBDii5ILEkJtO8Pcb764Ogvtft5JppBCgRiFumq4p5LuXWMCjiwDA8U066Vd0Sx/9YxkN5z9eO9k9FBTgmA1eRFRBx7BIwqYGhywhDQ13bCsg6nGikM9jIpWdB4DTl4SZIDDDL5Bxy+TO/uoUwLrgJ4ZL/QbM796zYSW8rn/ofD0yfv/gNg7O85obS7BytNeL2gCwpot7+ShOAbHVMTi8CpKGLpqcncn0S35/StiHE295sir/ffDN65izMYDknmFOBRwRPzdwaHkdFJf9uXYAfHlVA3c5owhMNqvpelcNOP/Z8mf7WnjsQoBYOhflUuzt2Nf44W3SDUJarsBd4ESHjMxEHD98NMEtgyQUko6gx+bDZx/TA9OzyBpzUYkF1PIc3zlsFSPNZ15VsW96grusvz5WGoW83i+jTs7USAHI/90poOmIFhQmZrjtOz+jH0UkpwNaugBHJIATb60OgHvPsMkAptHIOFFc9sAiALCiwdDuGOK6IbG/lAAopzn871e3MJIBiBsRV9Sj4hhxNDy8PaVroRjjGtH1FYQ2c1F3pjLcbbvDI2KCHAnLtO9VOLlYL71hmX5y+9X4FP/4w21NJT151zRh9f7ZGinAYUUKUL+8DtrULLxLYkjsGuS7mPz9NYJYhuG1D+ysSgHmAcCwGTgaAtx9bDEU4IvLG3j4YZYx7pVL3ICE1P/Y8tJRgHIAII1dvSrApQlIigwBTUBKZBiVGO58uro/wEVhP1fXUjtry/aSuXtvW3NZRY8VADet6mR1hzVhYv8U3vnE83P3LwSAvMeBo0WGqJKujQKQ4OZwCvD7JMgKILoVKEE3siMznOyKggwtrmLjw9W1SvMAQPOTI6cRBzePXvZY7RSgnID05ZUtTKGEDBanyg9u1yNia2kLsYByACA9QKAhxC2KGXJESOUgel1IjU3jlseKAl253397NIDwYY2ALOLkm+8vGedNK5cwr1eBryUIJio8CGJszwTeub0oZ/1gRSfzBRRkMgwXbSl6H5kAuGxXdZ8LCg2rQRFkUACXgFCDBzJUaIUCXC1hDgCKQ5MdLrAccOq9z1WnAK8z4gaM2XCEAlAnM2BpDVqmsKhTQDkAfH1lE/NLIpymroL8/uL6kCg0inMaQTfRkpKIJHB6Ja0gvW7LZNDiceBgTkNdI4Pk0DDbL8KlKBjMqHhkYL7i6MNtzWw/eb5Y2iluhauPKWcAiSmJAkXuCLh8d6k+/6drVzKfV+YhY2c9uqVk7r68pIMF3BKcXjcymghBzWN6NoNP7i6eci5qjfI4Q7I6kgtch1vEbAG4/kDRu8gclv0oWLMQeMuabiYqJP0TBRCgkLMCuXsRzeVWP1KviVDcbpx657PVAXDqUiZH3dByeb7gXHQuAM5IANn+WWx8fOGjZLWd/M1VLSwqiTxmjxv6BWDToAqPQkctYMURTUhlNWRSeQhTcXijHowfmMFEFkjkgGeyBQTId0AS0N2oYTxdwPi4AJfTiYlsAZsMu7t1DCYAiJTTgYTONad5ZXhlBs+SBp7SJb5/HDMZ4H07S4+o31m5lLWuaYTHK2HTw3vR1hWGy+2ENxbGWN8EkEkjM5pGtC3CPVslQcP0yCx29o6izivB0xhE795J7MwVWc0DM0VV8GkRXc9glwFCx3ei69mB2ljALau7GHlMUY6CQNDB9euFdJZPrm9lM+I7h7iQ4wj58NpfbK4OgOOWMsEvguvMzCsLgLcrhuTzo9j41+osZCEy/s1VzSyiiPAEyBok8qPhn/bnOCAoSJK2PP3PIQsQVQ3TJLGpDKTeJ/Xw46ksom4n6t0iAgEVO4dzmMwIqHcrcIsMyzURXy8jhJ3b3s7IeTOiAC6BYQWZn0lNIuvHw2ye4vcEfNBGAb6ypJORv0K9B7jnwAx8ThlupwLZ5eQBHZOpHDo8CiSvG4ogQiDn0HQeW0dn4JF1V7cUObJa8gaVA8D9k6XGod8cu56d+9hmHh6+IAu4mTSBlOvGIcAbkODviCLdN8bVnXLYw4WY/FSKn+fPur+6F+y9pAgylUemSzspcQzVQC3axIUoQJ1bhKtBhiCL0KYKuLsvz8k+kXsi1CZ3oIXhFkpLJpMnkkks9SmYVYEppmIiTvK8gHqXDMLUOsVdFgBndXQwRZZ5OBhRx9EJPSGDvd1tW4ijLBk+/p5Ilt08X1veyZq7IpBlEWOjKTgyaTw5WJqD4AYiU0b7vOG63sIYN4qF1zZh6JkhEJF4Qk1yr6f/bGzgV5/TgIWFwB+v6iSpilMAj0eEK+iEmsrq5/8ZUncYs8iANz5YHQAPvfUYlhmeLImsMdkAvW58+MWygGZW75fhjOnoYmMqzn+sst+BSxTLStrHB918jAdTKo8JpBZS6Hgm4Y7R+YmZTm9rY2GPA2FFwmgyg5QtSYS5ODuTRSUPfRax6AUqAeCqng7mouhgpwvJTA5iPoe+iYUB0C4wOENuOJwS0pqI2YFZ3JVKIKMy7E3rruvkD7AgBbhptU4BdAAI8DQFkR2dhac1hPRoAs46LzLDs9AKDOc+tAAFMEPH+OoYKmAznxCpkx96sQBoYfURGc4wOeEBbDyP85+qfCysBIDTox7uNLIzkedaNNpebW4JjW4Jtw8ZUqVlewdt9v/jggF4A+QjqLvHpZM6iF4IAD7a0cw6l9ZxD59EQcBs/ziGxxYGQLPG4A8rcIU9yELC+O5J/DWfxHhWw67UIgBw40qdAkgOwOUWEVkZQ2LfKNwtEWQGp7lHq6vOj8xQHOc/Wuq3bieBFDjiaPRzCqKS1GWkkPGtaEZi+yA2PvDiAPCtNc2sPuLgR1aWIBKu4d1byzt90NgqAeBwn4tTNooSMr2HiAJQ1O8jM6l5pNoOgKu62vVHpystNObb/aVRRLVQgIua6lks4oHHpUCWHZidimNqshgrSD9jZQH/buRUXCpIqG8PIuiWuKZ2bCqLLbkcJnOLBMCPyBgkCPz873IL8LeFkBmLQ3I4kItnQLpsb3sEiQOTePPDCwCAUsiYxiDOj0Uw8o13SDyHQC0GpWoywLdWt7A6n6SbrDMaJgsMl74AALQouhbOzh9oPU/0+eYN4dfTehYus5kAaFjXzANEJraPcHVwNQA0K8VMYNWecaHvvA6d/a2UJMSafXA5JcxMZjA5ncUtk7pm0Gw1sYAfEgUgAUoROAXwNniRT2YhShIKs1ke/OHpjCB9YBJvqQEArvYIssMzYAUVStALNZnlegVqgakCgrKIST+QlYFoEtAyDPfOZObcDM3BH+aWudMHKWDMnbYtnUeEYgEEgfM6WsCLXwAAKIFSpYm+KBqdJzf8dmaag44ETbJLXdHRVvb2z/XqkSI/WbWEh3xpTMCPKgiMdN1vD1amXtYf+NKqHtbQE0UGAv76hO70uZrC3SJOuH0OjA7EEc8DPzUAsLyOLEXAb5fGFpYBfrCqi1sDKdulyyXA4XfwCZdcTmRGE9wyKEjkaMnw1kera57uJgpg2v/JZ56MQYn0HADC0wWEG90YdahIFFQ0Q0Z+KIutUJHPkHcxQ1rT/x3hdYCUIBIdh+hEkgcmyevX2LmUo4/GdNYzla2DlVjAYgHwUGoWbR6Zg/FguoAj/UH8rErK2ocuPInJThnjzwzjppFS6mFd2FoB8KGeLhbwyJhNpNHqdqNjbRMm947j88/u4d0dsMQK0vszWoLsxs7O2vQAN6zo4CxAIvOvW4Tk0HcYWb7IfMmVYBRdRflo/1YdAL87aQkDCd7cxYTBXRfmpmSVUwCGFRQ4XOfAqEdDOqOiWZPh/ocgt9Wj6Ra/rIZJlWGyoOEor4KoIiIdlCD2eOHangA52JOBXYu5MHEgwV3QqwFgIXJa7vtyFOAA0+CwqaDt+QGtfd1x5HKWE53IawL6pkrJsvW6K/bVZtpe6XbPUaW3tDbB7VI4xfzeLj2H4SmRUMmjjEhZ3D0wU5se4PqVXdxpgVyqHQ4BpJbmBi7S5Rge33RKoBFc+Hh1APzquB49gYThxOHvaUCidxyM++ACHQ4g45MQ9wKFjIqQQ4FnNI8+SQOboWhOVgSAz4GIQ8Rkg0g+25B6U2iaAgoOAfmwAxP9KW6bf7kAwDQVoiDqmfEE4A+2/IDWFXjsM29kkwMp9P99ABOJREUcVgLAB2N1LOwUkcgz/u8vyaKT6C1v7Snb33fv0hNLme267o7arIHXEwXgDyXA4RAhyYYDB9/EFjGXARf+vXrE761H9ugUgHsVGZSAXglRGkNdTEBBEcDcElhGhcfrgDaZw8x4XvfQVMGlWJMChJwiRhslUHC/NJFDbBaYCogQ3RJy+9M8N++hBkCl1Toh4GMSaet4CjjgAVt+QOt9txzWyeKqzOWUj+6sLjj/V1cL6zmshUcwzw5MYnYmhT0TCfgppIxsTwWGtzTH8PPhYg7i/zxlHWfZThfD5T//G//pzanS08tnmpvZp5f5FpYBrl/VZVJ57g4m8vBvPfKXLz+tH5kDKPXbM9UpAI8bIKMMURDz1ZThNKA5JiBPRzinBEllcHkU5McymKbUckaQydS0ivG0iqN8DtAuGI4JEMNO+A9mORWKh0TdCaMvi3zq0FOASgA4KeRjfllEvKCnbXt4Zr6+wB5b+I2lXXPdnb21aM61/sYlrY0s6HHA6VCQy+mBJ8sdLuzMZ9GyrBGhqBfbn+jDJHllGe2s7nYeki+oWVy7W7ewvqlRjzS6cWBk7rqaTgElFMBIcsghzvWqFoWOunDmr7m4AUNu4H2YhjQVWBsUkfdKXA6QBtJwkZVOIL/3AgTSxRYYpkdVjKULXAYIukSM14mQPDKaBgoY9RMLEUGpKOXhHAqzLx8ATo/4eSzAwYzKd+WTZVS75QCgBFwQFBGv+0v5CF0yNpkr9txMcZFXR3yIkipeA0biWXxnX3U/iCs6Wnk/t1koRW0AWKUHe9Dp0jzq8B1cPH0ZWRIJAAtQgDWWyCG6n2QH88ClAUf79DeUUk10Sfwfm8zh/mQOsaDEr+cUIKNTAGIBibAIV50Lwo4kWEBEvN2JTCIPaazdrcOZAAAgAElEQVQAdUp92ViAddeeYVjh7NTCrgn85rIuuJUCxKAHp/y51Bxs3vuxdh0AZK62AqBasYhyVIoyi1PsQKtb4gquFsmF760OL8wC2rylKcmtnfcnyxsw6Jpq970xPD+O7Y9jk7iVFCeMcXmDrM38jE8JDhM5xHRbKybzpacAeCUo7V7Mbp2hQwmEo4KYGUpCmFRRmK0OgJtXLGFBDwMp97h3rkL5CwREbMmXaAyVSHS5ya4VAN9a1gl31AlJBJ5PZRCMhZFnGvd0SpOX8Og04pMpPhfTZK62kHk7AKIyCWfAxmiYD+lVbhmRoIyCx43+vllu9Ppf48Rh+lLUZA18oQDoMpIT0aisWhV6f2aomNDQnMBn4qXhTvT5IzN6ouWbD2tllFY9pelW3ik6BvocqJN1qbtARzAISBF4fCLiDnDyn0stDADrAnqcDLmCADfIZF2aD/C8bbWbqisBIKzIuKA+CpF0J+SUQuFqApWUAR5NJhDwOHncYjydRSZXgMg0/MLw6zu3p5XnA57Mqni4v8jHafxW5xhTFUyawPo6Nzw+BVODccxkGX5iKIJ2nNCD924Zq40CdFehAPuqUIAVdJ/hEGl/Pa0MBSgHgMNtod70sNWA8uiGTkb1eZjIeJwg7aJqpwCiAOV2MFGhuWYg4aIFpHVrP+ZupM9Wut3wywKa3RKGMyre29MBl1+E4pNx3c5iqLZ5/9kon3LmN3mV5wxIpbPoHS2tOlIOAMe4ZfI0g+x1ouB2YvTADA56Ze6v8PWeKM55/AB+u75hYRawxlzIMjO1tUrNmnVVgPPqQwyAoQzlp5/f3tgQLWu/N68sBwCXwjBKHiI2EnD5ntoDTqwAWO5283SxBAKql/Bfq7sgZbOQBBVXj43wmAC3ogeGUKLof1fKl5D5UTzNfS8KhQJ2j+tJJszWbET60PuPnHM0lFwOu7cN46f79JzEXz58OQLLY/jBQ89DYSq+3hXCJZuHagPAUV4vMz1XqTOKeDHfP1kFAEfTfTaDivn+6JcJAOfGovhKhQhgzlpsFMDtIF96AUPpYl0ec5I/XkYrR1K93+NBSBbQ6BQwmGUImuZD48YRI1Wu+eyf7WyFq94NF3K45sCgvkuJHWh6Iuk/7dELTHzI5hU8oMjwu5yYzmt4YF9vRQC8vqMZLoeImXgWdw7oaWU+s6qHn70veUb3PDarhdVEAY4zeHm5HfZoFQBUu++IGmWAWllAJQrwjtYoPru3fASwCYCQl2HakGXNfBbX7C+f3uukMpbAx1UVMaeAFpeEvUkVARsAxkhlbmlXdrZymUiRNLz3+cppZs9u0AM4zdYriQi7FAxnNZynafCQi157EFO9MxjxCPCG3OjvncEX+suXvLP2tXnDes7jaooNLLfw5uRRyDX5hFqf+R3Pl3cKeWdLlPQ7CCoikmTqs7UXIwNUAkC5mnwkbdvbCo9uHbM2q8LE/LwcAB7L57CUdBcaw56UimaS6IyINbKZNLtEDFNupLyGkENAssB4GPfD8eqKIjsAHsnkIEsi8irDJS4XpxqmV/PTuQzPc0j6su8PV66XQHUPm15VnPu1e3ILywCVAEDZLBVZPzZZZaZKAHh9XZjxXPWSAL843/b9fxUAD2YzCMkidx2jGkDLFZEvDj0r8f6RvMo/D4sSGl0iTwY1ldPw6AIAKDfvVmHPPGmY9QJSlpCy19bpR8FP79eNSWacAMkKBAClPoj82AxeFAAqSdCVAHBho56azNpiVFTCqJ71zOz8nfliWcDLQQHusxRwoGc73C3xZA3xgm6afj5JjqVAvSxzVbpeGQQoJz9VSmdvzpkVAPaScR46VxptIQCY1/3TAZC0ko4ykG8uE0xZ7Rho78L0kLV+fqhZwE0Tpbn4TV98+k3aflEH5SAUsGWm9KTyVBX5yTpe64nCmn3cDpbPHNUNt1vC4J4ppMhBA8BNg5X1BWeEQqwmIbASC/jV+h5GErMh5M5dthgKsBAAJkQRzR4FE3kN6VwBjQrDrCbAqVDePwGteTpaabhxqHwihhcDgHKmWLP0inVOzFp85mevCgbmKB0BICBTwSbyqZzvSmbtp5LzhxUAxEKIhfanVbiMRTb7uKgpwpViqQLwP2XyH/7+Da9iU0MJpCy1B97ZJL1wGYBYgCIV8+2YAzmUABiXFTR4nUgXKKFyFpQQxaWIiHoc3C7x9/5JZDQNmyvspn8GAA6zVfM0taDdIZ0vV2q1AGBD2AEKfNmRyHPVsLXtt+Uusv/Oj49awzLkf2nRmdQEAAp+tHb28MH5Klvr9ye0ennEsOxxIjsZx32PpeAWBYxZwpfM6xeiALtUxiNlYjIpQPIYIp8/lwMRMhNrAh7s03PmVkq/9s8AALGARCEPMg2TJpLiHUiFS4WkZvIMr4uWeueYc1FOz0DfWSnAxgZd+t88nefxhpUA8OMzjmVsYgZT5LMZ12Urs4iUlXXUZA18IQCwDuzRxzOIKQq2WLxWzO/fVDdf7Wk1unQGAoxs+3UUYqVpGM0WoEgSZFnmx67hWT3p4WLy75UrAV9uV5Yz/tTCAggAWa2ADreE0azGPXYoXSx5L1FUzuk29yzzt2MdQTi8Tri8bqSnUkil8zwt3CeeKaqLF6pAYvZ17fqVTKSklZk8qIAHNZNVLRoAd1ywjFnr1y9IAdp8c5lCFZ8Lf7xvEh5RxKil6nU1ANhr8dK1VscJ895yC/SW1ibWVueFy6tgT+8Ubh+YrxT51SoqbWc3T81/f76tOAP9bi0AoOvWeNwsIItQmVE/gJJriQKyKsObGsrr+jVyd5IVOFwOkD0jmchA1Aq47kBRkKsVAOVOE1RhLOwQeb7DoYyKA+l8bZFB937kWJbo140PnqYI7n2yehL8M17djfh+fdCOoBfSk2lu8cqruhs0NXPxyu3GcgD48pL5xVXPL2Odu6C9lYXcpGNgmE7ncPuB+bkBqPDTwssPRI3YACt1uGOo1AhD3/3Ydgqgz7pcLkYh3ySoukkxZGkXVABAJRbwntZmVidLeFoD6hXyvFLRYEtXG436UNca4urf7jUxTCdUjM+kcf2Dz/JfLhdKT6eAph4nbnqqWI28HCUUrCxAVGQ8tL984KN5M8kA1sa2k58cA3e1NxTi5u6qFQCfthQ6MvsuZ507q6WJyWQYoYqauRzustTJNe/7Zk/nnI3C1M9bX0mUJjfFJgrUsF3wh5H5iaLLAaDDqSeapKWnlGyULZyOgiQDnLFIGYAAQH3tlB1wQuOy0HpbosiC3w2XIiORycHnlpHVRKRzefxpeIxTXpNFfrGjkf1wuOiFfNKa0OIAQANZkAXYADCzZb7F9Z2GabVWAHzImuPGsNKVs86dWB9lkqQXa6Bgk7+Mz0/VWq7wkxWwzgDdCwTz83P0PDQ+34e/GgCo3zaH7r0cUgQuA0wZxbM5S+ksBpDYWZrdGPSkqEBTVQ7so4x0u+a477fIV3vS6TkC95GuOkbC55MTecRcIlpECbdTJTejLQoA7sYw0sNTNQFAciqc/KdHp3Fws54IwmpdHaTCPbY2Khe4oWNnvDSCli57T4slw5XRUTmSWY5H9+fn5+mxLtpyb7Eczc5k0XO2HKXYPF0alFmJBWzw+Zip9VPIP5ECVwwZIG24wNO9X9ywDNkxirCe73FkB8BWiz/gEov5l/qpBIAPd9Xxso3bpgucCkUg4s/xol9hTQCoNEHl+IX9MxJGPnV8DNOl1kuMJ+fb768dKc+L7AKNjypBWVqA6LXRTqEiQbb2QgFQjlJsNzKjWX+iHAU4NuDniSpISUUVaqwtYwHAtStbUchrKAgOvOGZbSXhaHYA3D9ZZL2n+txo9UmIrW3BM4/14+sWA9CVK7tZ0OOE1BjE9i16ZNA5J67D7J4xvOVvRb9DmteXBQDWhz8+6uO7oaEw3xr4SgPAMdEwa3AI3JBDBpwt07qdvpZ2uM/LqCBVAQK8osidQUgnwP38LAC47vgVPOaSiRJOurN80YZPdrSyGVXDnyeKvPsot4vvaEUSMJvT8BNLiZsPL2lnHoeMZE5DPBPnGdROqNcp6Mz0JL6/WBngxVIA64StDXr0ZEbafGvgKw0Aa0MhRmAlUzeR6MUAYKnbzUzHmZgicwGQ0vhmean3YkqS/+lpmTuQvu7Z8j6HphB4/cHKfg3lQEnskBJjhV0CVx7NZhkGsyp+Z0kf+7JTAL+DAiiBjZ75evFXIgCsE7sYAJinALqfrIDkQWUyqr8bamsri6l0BLTnHeC7uMY6x9/obOKq+ozsgD/ixnj/NHan8/iLJRTtkADgnFY94MBsXRSpYLTfjZYem+zn0dc1RvkuI2MPOTN0eUuPkIlUEvtsaVXsMkDCSHdi/qa9RJq9vq7dT7AShSMKcCgAUGfTJ5hWwEMFgEuaw9zfn8rX/2y0yKaokHSgJwpn2IPk4AxSg7OggtLXbVjJ1FwBvpgfT08MLHwMXIgFEABI4nWQ7rvA0G4kgyZvlTts52Y7AF4bCzNSk/amGZIFDcuDQT3y2EitmkgmXjEAaKeIT0vbnkjxukCLUUNb77cC4HpLuJb1GjuYy1GA8+pCjLLmk8Pp7y3VQm5b2sbIYyuT130OSe6++EC/sPO4HjbY0oZ33fkYDhkFaPHIaHBL2JdmiKQzXNDzK4C9wpYdAMfWhViPR0JvWsVYjuFV9WE4ZQkZXn5NRSFZeuzSBepipMF7mmKwaw5fKgrQXKrQo9g/PpqXGwD/092B7VPFszyNwZoixqw8fnHEj+CKGD9lZA5OIh5XcYkBgGcyHvxkXz9q0gQuRAHOa2tm7X4HUiIlMdIwM1ZK9qvFyS8L+LjOnOrb07/XNNXB73bCwQronc0hHY/PeSGT6xNFt1j1CR9qbVo0AFb5PCU7uc5Fvw/4ZeAARZ4YECNHDvpH+vzv948LJ4V0O7/pOHqAciUuotnBb6UAN41OYIctlvBkWyp4yai2Rj9p1wOUA8DZATefK19jAOnROGSBYXdcxXuW+LF5WncYeUsZe4f9kYSLWqKMjkJU+NDazOSDZ7Y0sZBLQU6UucOjOlOa8OCeKnHy9qijtXV1CFFyO6ZiMp1HhgssxR2vWiRoGsvH2loWDYBlXpuuQKBF1msMES+lBSaVLf1Nef/Ir48AcIzfx8h9LegQePDnvtR81/FqeLADwDTzVjLw2AGQtPj8HWdzq+/2OBFaGkN2aAY/26+byE90uuB3CDxXAZ0GJtIM+7J5fHJ5CMuNZF4XHRljC9oC/i0WYrSwBAJrExnjpJ7StVAj40deY/ARw7G0TZY4+aP9Ptbg1DOMULqyOqfIeT+FVNNEU8p/uj3m1MOs4/SBpekeZEUa8J0LuiBJAlSV4ShLedS/X7yGKY1h5IancO1do2htDkDSNMwU0thj82egc7LZ2jwSn7CRjMaPbgFFwCRlG52YFszgSkrnTnqB3cnFAeDYUID3R3mKTw0ULYKVYg7tALB6Pp9gA8ASn5tXGSej2+54CnWU2bQhgCseKg04fWc0yggAezQfvvN8X20sYJ3fy9WJdo0+xeXRjqDJoN1DigmKOtUTgOiVQOjV6v26we/jGjLaZQQoc6EJYPT5bEHjsfVEepP/SJ5gB4B9h/3ord1wt0SRGZzE2huKD/vMpYcxV0cM6d4RXPN7yqvrgEOSeL7fA5PTJeObsejVyYmDxkz5hUjkIzDQeJ4q48G7COqPc4LBkum7tLOR51Mg9lJr0OlpgaKrmT0w9ES/f67/pZEgwl4nNFHCV7eW6ha+2BFj57b4sB0B3LX7IG4cq+xCbj6fcEFbC1u+OgYysWx9dgC7MnFuJROzIiSu39a4tytRgxmVwcvz8RabNU5+LUUL8YwSuoKFfOXIsYMaUQCabKIiNPH08RZbVgv7RH7h0hVwNtchuW8Ia79djK/f+rFXMeeyFuT3DeGDN+2C0+mAw+FAJpfD6NRMyfjGLcaZuZJ4ttXdZTGwLGbhzWvt4/6v01ci3TsFOsGe+Vz1GgtmH7UCoL+M38UXe3Rz+iOTk9i36rC5R/i1NrCwT+CbuzpYvUcBy+cwnMiiLxvnu2Q6qeoKDiNUjIolkZTely2tq2edMFNDNocu4/5K7/dkMiVnL/tEfumdPRDrQshNxbHmq8VqJVvfv57JLfVQB8bxntv7uAcRpWWjMf55eLSkT7LdL7So+23jWOh6+/f2cX/4yHaoigPJwSTOeq58ZhB7H9UAcMtZRzOv14nh50bw1T3zo42u7NKtjpvGpjC2bu3iAPD61mZGZ3xSHqQLecyqlBwSGI7Pr6FLPVcDgFVDVssk2vuyT+RVb2mH0+tCNpXB+u8XaxU8dcka5u9uRGL/CC6/rZezJ6JSs3kNm2ZLI3Je7fcz4s0kw1DcHXnNkOl2IK1x3X27x1U15Vstz2Ef9yXtjZz8U6vEAuxxFEOWiCY7C/jysg4qbsBT43x0a5GivCcW4eDuzWuc3d48VDSPkzGoJp/AkxrqeHAo+eSRPVqV9YUfKFND9+UGwKff3AZHfQjZ0WlssAiBT168hrna6pE9OIYP3t7HhU0yyAxmNDxoS950QsDP6p0iZ0HDGY3LM/Q3gYVYkj2Cx26d5BXBO8PIDMzybKdveLa09IsdIL9ft9pwFmHYbjHN2tXBdgDsTBTN5PZTwDcq1BF6f2OUy827syp//vsni/qDDxy3Ep+RXkBo2KsbKGOIMA8AZzT6kC4ANw2UBkrYJ8A+gdGAX7eXQ0OSn8dFfhp4dGx+1QtrX2YCCvosahFRryb3MUnPZff1fX2YyeXnZBTa0U/YXMhN2z3tRzLW2Js9gmceAFYuKfpAht3YuOmpqlbDO45Yy9wRNxzI4Kl9xSPzQgB40pKB3HoKoBHfOFg0pRP4y1Glj/yyl6eHIWCT2vgwt6e2BBH2zk5q9PPMoX2Tpceg7yzvhMrrAAI7U0W02h/MPoErGqM8eIIVNPRnCujyyphRBdzVV936VQkAXz/nCAiahNm+OL62Yx+mbO7oRbMMsMzrwU5LoES5ibNH8NjHf8Nxa5A3kjeLThlnPFE+14/Z961HrmN+B4OoZrFjvOgY0xr1wR1wwh3x8CosiZEkhEIO5/2tKCNY5YB92VJFVDm/v3LPY60nXJMQaO/k1CY/IyFwH5XgtrSbjuiCEPVhti+B5y0GnIUA0N5YjyVeCbPpLHeZWhl0YUwTcceu6gkZKgHgk8ta4HSTxM/Qa8mrb47DWqSBXLL0YF4BV+4rFqzirlpGdhM7jy7HAqgqubM5wCX7hVjAd9esZO1HNMFdyOCZvx2cm0EPBD5u2emAmsshk8rx4hMf6Cs6tlLOBfOGcSO/svm+EgDsFkXTOEaZS35TS4YQOwBOb9bPtLsnSoM5b1jbBZEMQgltUQAIRUJocIgYoRTyTOBKjDQTMWyxW+8vE/lDAOCl4ihfsEVh9KXXrULa4cLEtmGMxYsgLQeAK7raEFpRD9kl48N3PDH3qFd3t0IOe3kO5Nx4iidx0jSGK/cVF+wtlvj9n1usnld2d0CgNDWkO1FVKKLGQXb6Zv1M/tVl3YzLGZKG2XRRC1XJJGyd/xWWlLA5m1+gFQAmSM3PLmqOMNpcj1rYCAGhJiHQumMajz4a6s6n+ZjeW9fKeR8dA19zbwrPvLOep7lIDKdKALDCS7p3uk4oyxq6gkFGPvN5lXLzM8iUlJiETosvXyUAkGDT7ZFwcLbo93dxSx2lDOYLoFp4ejkAXN7ZBp9fAVXZvHqLnleX2peWNQE+DyjjoNgQ5Ampp3eM4uq9esoVapUA8NWzjoSzzoexRw9AyOXhjjihBJ14zZ36MfXKpfWMqnzdYshK58cijGL7b7VI6NZFf5+lYvlOi+m7GguwA+CsaJhRVtI4y8ArizxC2ac5a6MAlQBwaWMHIutiUCIuLP3gZjxztov7/pMcYJUB1sX8fAelZwvYYeG35oKY0bSmbcF8v9fCu+0AqBRG3WxJ9HDbEz7cfrpu9KBWDgCXdjej++hOZP5Rweyq3xYpAHnq+OpdoIFPzKgQNJVn6bx2z8IA+Nxhbcg73EgmC/Blc/DUuaCmc3jt33ZwALyvPcooDd63+nQh98LmMCOv4SNWL4HbJcNJaJCBRDwLli3gsaeKv/kkef9yRZqA4klAV40fH/HC1xlFfDaPTz66lT/zWUE/Vh/dhpntg0jkNFx9sKj5e0MoxGpiAZUA8PaWbnjFLFcQXbevGDDx8FSxbNl9G1Ywwe+Glsgimy7PGl5uAFh31w9P6WJCRoIjLeBbu4vVPK7uauU1/4iAKKeuRHz/GKZ2j+B7vXrOnWoU4H/OWocCkzGycwINeZVrHb1LIjjlt4/yBX9rU4TJIsNPBvRKXm9tDrPpnIZjI3Vwuhxg2Szy+TyyOfIoBMYt7O0BIxSO7rPbA44MBuF0yEilsvjagSKrMsfb7lZ42Rs6AZDKvkFWXhwFOKeuFY1r6uFMxvG5v+jep9SsAPjzCatYIRCAms4jP5nC85ZcgC+GAlgX0SrkHBYoupoNWhQntQpIZr9WL6LPr++E6hXAAhl88/4i0CuxgIvaGniqWordsJptTWHylEiARw3dO6FvlMP9XiqDgL50Uaayu319v719Tvh794HSFPLn14VY2CFgMK1BceRR55CwL1XACUoQPo+E+mY/L9AxlWH41MFiqNyLpgCn+ergBIMiqPippehBCQU4cR1LU1VGo5U7HlaiAK/y+7DS48aOdBqf662cB/elBsDVS1rhCbngjfh41LOazWOydwIPT5WPkra7u95pCIgmCJe53VwHuNOwL3S6XPz9pEXmWQwAzgwHmcvISNKbz/AiGvUOGa/1BkDVY+rWt2Hk6X7ui/m+fcU8CgSATIThgX1Fim3dWObfgulhYn7gMmoTNAky15yRt6vV190KAHuH1godZlWOSgBY4XEjKEmY/EcWrmoesVYAWCfOKie8GApwaVsjZFmCw0FFmyRQKUgis3vLxDbQ89oB8KxFg0ff9xo73bQvmLaIWgFw5dgwN5nz0rRisT4BfUbH2d8tXYmuJ4qGMfsxsNmtJ8TqlhXcvGop6h+tXvG9IgAytuJZl5/eAGfED1ESkRqewrm/nJ8tjOIMaRcpXhcyE7M8d8Buo4SZXQg8fIVr7ixOQk+iD2WBUAsAPmsYQ+yA/ODuouRv/c7KAjrsDiS2Tv5sc3ixB3TYAbDFUP+ajiCmYwhJ5mY7vynEdSK0uWihKQv5rUMTglURtCZY6kD7jf7yWVLsz7zSSGBxyAHwxmOCfPGpUaWsD90zv2DjN16nF58wm3mNNa8OkTPKu3f4Gl/R/0oU8NjmLBqcDnx9f+VCkOVIGH1WKaFVOQA8cUxnSTeXPT8/INR6gR0A1u/Ip9+qFKPvKgHA6hl0XizCyCGGhFACQMQh4LahSeFoS87GtYFSAPzIog6+9phVLBD0YDqTx6ceKq3nbM61U2O4YU0L2h4ujUiyz6FQzjf9P5piJWd9Lsys0F2tTPNw/bgLTZ1hXnHSE3Si77lBDAUSJZlGv/aYjlorAEhJQnzsza+OwN8Z4+XWEgdG8auHE4i5nC8IANYsG9YHtO468/NDCQAbVSkBv50CWAFwqsWBZIWrmMOQTgHm/D5fxUfhIz1tzKGQi56GDZoGp98BWS3AARVnGVXUSAaoiQWUA8BRloyZ1gHGLGnWC04J4aALvEhQvoCZTB5SuqiwOSLim3OGWGLRcH28vpgr95bJybk57A76UecoUgCrZHztiJ6PoBKvrwSANZY4wx8cptfTtbef9xWl89ss4/n3SNGtyzpOa3TuoQaAVRFUrV6AVf75RIP+XBQTYN1sRAFqAsDxFrJjepsstUxcJQDESKNntM8P6+fniy2L++oVdTj5AZ081QKAo+r8cEkOXLtXZwFWADzmFHhJFUrA+Ivd84tWvJwA6LQEglgXyT4GOwU43FP0VubWNqNZ5/dJS1TPCU116FoZg1Nm2LZlCN/cW6wxWA0AZr81U4CXCgBH13lhxsPVAoBjG8hs7MCXygDgTpFhWcDBBafbd80vXHVJU5jROZnMvVYVKsXum+1QUYCIBfjjlqOdNV6hXHiXlb8HbFlFzDEetPR3WnM9Aj43CtkcppIZ/HABIdB6Ams2QPr4kWsXPgVYAWBNd2JFd4tT4WdZ8ucbyRfm7VCr8uKG5T1zvPCyGnLwV7reivK6YAAhB2XGYPhLmawgpwR9jOIOSKiyWtH+a0k917ZNqRr+LTI/tJwmnsLWqD4h3f9QPIOwJPLqoOu9CpfSx/MqHm/WEFEAZbsD3zuou2VTsxpsFgKA1dBjBUDRKR44Ihamiri4ubdyRdHL26OM8ghSxvtdhuq926DY909PC+a8nRwI4EtLOxcGQBPlUBeAoVSuRAq1OlYcGXAyMjKQRuuRKT1DxY1Hr2SBZj8KY3H8+yO6HpzaSwGAhWzhlaxoH+wheUOvRFoJAAM5lae5I4HxiUSe/002PCpdSz6QKU3DPaECXAKQfS6HqEvk5eCoQsef4sXIpoUAYN2hnU6nXRTh71/TFOXh3j+pAoAPtIfZttkCT1U7YcQSlAMA9VcTBWjzKLzyR38yJ1gn0iqFrvc72RKvgvGcik2TOgC+t7yTeTwKL77y9meKOoF/BgDKzea3lnazjJzlFkhqlQCwyyK4biOXJyMF7Gq3POeH9F2y0jGG6KgKp6THNhJ1mLQkr6D77EEyJMfYVbtlV9748JRYAx2KsNbvROe6drBCAbs292OrxdXrkTKm89dHQjweg3wCF00BunxOTgH2x0u9fbtceiIks80a7tWmcGO32FE27cW0flu9W7q3kjBnPUK9vTnKKKagEQq+WyHrCPX1etKhKwKGUxpI0Lq8p3wq1/vHVZAHT6AdyE4B8WlK+S5A6AzDJUoY6JvCXZO6ruCpUd3AYzZ7Qmd7KTkCwJCaA6XR/9SBhfP8rwuH+e+U0AwAAA2cSURBVCSeVufXQ+gkDf2TaWyZKPr6lQPA68JkLxBx20jRKfRDq7rYFaG6hVnAEr++0HteJAAqkenvLOtifkXjfoFJlUKxKCBExPvLCHO1AOB8imRSGbolVwkArIImHdVW+zyM4gCiolwVAGYY1QnGaeg1fh9XzuScEs9iOp1V8fcx3bfvYcvus26ALutZfkbXvdNZ/83BIEa0HNyygI/3ztfk2dXZVv3AA0Y/5rjaLWzjZ5bUde+tq2ORkBN1YRcOHJjF3Ya3VjZfqI0FrAjqfvNvDegpTj9TpiLFNS0t7JvGWbwcBSg3Ade0NnJydr+R/KjS5FmBUwsAyLpGPPoEj68qAEwdfLfTWQIAc8HpWa0k2pzo08M+nvr1I/uLC2Z+90IA0JvPcUB9rozweigA8KFYHYs6RbhkcLnk1zO6v+Zxbg/oNPA/g9V9L4XVIRc/lr4tEObhXp/qm0+qCACfHRgQzFf6gYV2wFc6mxiFYP1xUs9aVQ0A/xGNzvEPK7rpvt+tXsHeuG1+cAV50rzu8GbujjW2Yxzv2F9qUTTzCD84McMBcPe0ngLOCrhyADg16OOu45+2bIRyALjhyJUsUOfB1FgSP9tVtM+bJNqkAHYlVrVn/enSThZoi0CSZJx53xOckpi/fU4wOMd6RmIBnk9ZdQiY3j6IrqUR5MbjGJnJ4xcGu6DnfFd9PVsoPExYG9bNf2e7g9wZ5fNDtQGAg8CtsE6PXighLOq669tGdK+UzzY38p26aXY+AL7W2sKpA8UQfuTggFBtUqw8l7JimO+35HJY45H57vqHww8ut2UNtQPAKi+YFTaETAFWIe0rLS28f14RZK7mLfDtqREcGXRyj+ardulywFdX9TC3U0YilcfvBoqVuy9vb+ZD/MHACIgFWPsnMFuLQNvB/u3ONiYrMrdKOrsjvJh3//YRLJOdeGqimMNwqs7PI5tT2QLkqTiaeqJIH5zERIbhk0/qquX6tv7FAWCLcbyzTrj5dzkKQN81OWQe/08kc7lbrxZqAoD+vvvcVzNPzAdtOo2Tb9s0J0B9paORJXKAVwE+bhGOvvPqdSwUdMPbFMS//fBP8/zv7QCwLqq54GYtILOaiEkBagEAJV4iQAWcAjxqMdPZfw8NYnXAiRVeCT/on18L6AunHcGETA4TyRw2GO4RBIAfLe8pMd2a82lGBq8K+dG5JIoDu0fxh6GiUeqBdavx80yaF5ZMZXK4areu/LL7Apr9kTAaVkS0uEX0pVQ8OJ3g11916jp2laGNLbeu9Jlw5vIwjw7+g4Fs+tCqhj2pVQHpy00WcLOlpt3qTl29+a1gF/67T88xbAXALauXM5dLgcOpYKvxgGadG7qW0plYefJnejqZizIf/SMU/Yod5YtT0X0/X7WM71RrAgQ7AExSf/M5r2E+FwOlP7hz0/N8jCcG9cqmRAHGDI32NsNsbR3/Z5Z1sqaOCKbjefzomV38nnLC7kfXLGEUnTyVyOBkQ1gjAJy5YRkCTiCtiHj/b4tp4kwArKyLoM4nY2o6hT8MFr2R3nbsCjSG3di/bRg7Wrtw14N/rgqAo31e1uCi6CgRB9MqPtbaikv37cURTi+xvnkbyQoGYWNPiJGGr2+waBS59rT1iPdNQvE6cbSUrgoAiv7deUAPgPhErJjxk4Bjbb8c0g1F1QDQ7nDwhT0qHMRvDFZSDrkmAD6+R89QeSCXEyoB4JqVSxl5BecLBewwEkE+MqUnZbyisbEqAD7c08aolh+dOu7Yr/N5Mr443RIibX4obgWj28bQn8sj7BQQO7wF+/9+EIks8BxT0RYKQGIa0tk8ru8ravdMAJzocqGlJ4zMRBJKPs9Z4j2zBTT4PfC5nMhmc9jfVhkA6z16FtQA3/0SZvIaz8twRUcbB4BXEysa0Mx5Fc5cplOAHQeK0T4myi89vIF91O3Hp/bO8gUhVmBSgPWhAPKhAle/7ujT771q7RJouQKy02mc1FIeAJsM3/WAIuOLPQG873ndInjf6uXoeFx3SS8HABMcfcccgV9M6PeYALCDhK4R/vq3kl1D4zX1byYA6LrPH9SdRU0K8LgRr/D4ngYMDBaw/hj9NGDXe5j69oThv++z6fcbvO45EN/U2cYu6u2fU9IELPYE6nuzJUz+3Fg9GzVUvBeGw/j90iWYfuJJPsYNASdXQN09pctVZj+zRrrSS5pC+FTviECyRs0UwESRdRDmhJ7fXM8+3xmYA4B1ImhCN1uCO6wDek80ipPaSp2n3rVFN+m6HDow7AAwPYboO5oEKwX4cWcbu9IokNRuGHgGDcMJgdUEhzluszr4M0euRc8Tz/CPabynO3QBKW4NujDcsOlzqrrhkyR2mM+Lb//Ri5ERFW84tzTc3ATCYgDw29esYec8tHXRALikIYxfdhYBsNrr4Or4Bw1K9t66Oq6h/N6ELj+cEfLjuyO6Ozqlhxnam12YBZQDgCkNP1rI8V1qUgCOVqMq5TUHBuYAYFIMs68PNNbjhMbSdLEmAE6t12vrPJfK8L6tMoC5gHYA3L6sjV2wSz/mmcciKwDeYStZt9k4eVzW2shTn1y2e59AfZoAGCiTYJr6HpRyGEoXU76/3xC+2KtfxUyKYo7RPElctks3T1/cEmF073/E6vglv56cnqMAdxyzjP3b47sEUtlSYaibBnWNHbEtouH7Mwlu6fzj5DQfp09UMZbR8LaoH7e1L0HDzqc5paVMKwMZDUvrwshpApb+Q0Ck9lB2lt9/nMeLOoO6TLQJ2LR1emEWsNztYo1OkVeqokaLaWbl3srUqgCoxKdvX9fDDi/1aEI6rwteNw/rR6YDuXxFAJiTbL6aJJTe2xf7ZltCCOu9X+vp5DzyI3t7+cRuDBhl7Q2x6M7xSV7skd7SBNJxlqyCJxo5eszKYjcs7eb97Mxk5opV2wFwdjTMKDr5PKNgxM508dqbu9oY6SleGwpxD9/fG7l/r+jQk3BuSyb54t43PS1Qiv2fjY7w1DxvCvhxcZ8O/FsPX8kaju7Aa79/j/DuZS1sLMuw0alTtFtHh3n5mp3pYsKNmikAAYCSC5D0aALAnMRfrl7GvC6GC5/dD1MD+C4jDo3Qal73C0Mqf7ORluyW7jb29n36wM8g1DtFfLC1nV/+zYMHeNr4u437TeHNWsaN7qFkTWTjp9j/VytePKUwLow9OTKOmEsvK0elUXZYHtoOHPuxyQSEx/Bt/PXoJDwUuczAk0XQAtj7oPfmfYP5XNVi1dZ7P9zWzL5eIa6frruus5F9vLeoczHfEwDMGINr2mPsswf00PAb1q1ikizjkqeK0cl2EJq/v//oI1izJ4FzNo8uzAKsLmGXx2IItYfx0b/pjoQ/WbGEogPx0T193IX7yjXd+FXvMDeH0mR9plmvdrEkoB8Hr7YUZTbZwmmhEKOEDDuSKXyysRG/i89yuz7ZrukecxfcNlw8BvW43aCdQppEMnJc3RPGF0YzoJwV2w0AkHsqof7tS1pRyKpw+pw4+Ygi2wlsT+OMp3TJ3RyL1VJpXayTjTrOxI7KuZubx9VrepqZvVg1ffdRlw5ua1u6eyd2L13OP9r+yKP81WkUyqScgR9rqmdfGSqGcv13Sz37wsAYpwAmAD7RWs9+OaYbgqz3mr9z16/1aq1nnlcqp5gAWPlY38IswOyMlCwHVRXRnjq4e6e4j9lVLTEWdACf65/gAPh0Tzv2TMaR1zRcNzo6B4BWg+/QPR9v0AeliJQIgjJy6K+/npnBlau6sE4uLeS8/snNQrlzvTmuH3R0sBNbZGw6mMdv1WJO/bsHdKPLWct1C5rZftVUD8gyelNZdFICPZq8TXrkbvak5WwSIkaSpfZ4t6Lb9QkApoxD4zL7tOsrrL9H35mJGZmUx//W617HZ+3Zi98v6eF/lzvHU0o3M7EzV9vWRdmN4xOCVd64uC7KHjTczPceffjcz5ryCEn79KFdVU4AoNgBqhu0oB7ADoBIdwSevhkOADOVOV3zp7EJXL22B3fvGUKLoqAt6MNYUtcdWAFAdvjLd+8T/iMcZnQdCVz0SgC4am0PvrijDw+tXcXvM4MWxo7bULKI9Ln52aaohLUTY3jaGcBpRlXyt/ftwi0dy/Ct3gGQMBhqCuCOnQfx7jPW4YLpBBpRQG9ORafHhXhBRd2m5/nO3nHiUmi5HM7fPMqdPsz2jVU6CSCKsdzwrqGJo3voPX1vCqs0qXSt6cB595Gt+GFSwNTgDE5b0YxbFd2Z9F3RPG6c0E887Gn9JEJRUBS3X+3vxjLFrFpWN+KaTcUcSea4Y26FXX9mB+7apwP4en/RXkDva2IB2ZN0rZrZaLfQZ+Yrff6eeHHnndltCFLWmwC8YUzkO83s71D8fSj6oGFW6sf6nfUZbY827y1l3yQhi76gv835osAYCpixz6m9g/OHxnBTdAn/2NwEg8evYwpz8PcDJ6xlDs3J/x46fj2TmcLBfuvEBM7uaUZ8LIE/z85ysJpArPT3Qt5UnEzQQMxzLf1NnZtIXWgy6HsTtYEGH2ZHdSVFXWcE1z5ZW468Wn7jX9e8NDNQVU/80vzkv3p9Jc3AvwDwSlqNf8JY/gWAf8Kkv5J+8l8AeCWtxj9hLP8CwD9h0l9JP/kvALySVuOfMJZ/AeCfMOmvpJ/8f0ni4co0jdltAAAAAElFTkSuQmCC\"},{\"path\":\"\",\"name\":\"knight_slash.png\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"1\",\"group\":\"\",\"width\":32,\"height\":32,\"uv_width\":32,\"uv_height\":32,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"8bb06706-12e7-2f99-b8f7-ab39d69162b0\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA0dJREFUWEfVlr1rFFEUxX/TmCJETGUM4kcwahXbJIJutNDKWJpOm2gbK8Fis2BjY1oNFvoHKKYTBF0Fo2UgjSgmUREjIoIhSAKy7nk7d7wzmTUTsxgcGOa93TfvnnvOufdNxBZf0RbHpzCAEpSqUG014P8HQKszt/0KM6AXxmFcMrRSij8CkO4KbAEFQHcr2WgKIGs6C/zPAGSzVOAylCOKV04Rpgp5wIJrw1YA8OyuCyAJXoKomp/9ZnpELgDb0PQulyjLiZb9ZgJmZWnKwK0Oboy2MxZeONwohTz6vSn/xqC5AALtXZRDYGD6HVOP5pnJC+D9MQRDG+0RawAkG3YBpxuZ31/m9uwXesfjvmA0FvHHepWQy0ANaghAmoEd9ZModEJ54Mkx3vKa93nybKRhJQCc8aplOJ4AmAM+AytpDzzfz4PBvQzzCqJFIm9MJdCsXG2dPdcwELJvA0aABVAAFqECT00CGbS/m76+AU7ysAEgK0vRfpECkNI/pt8BqNiBVOuiZvJ4BpSVdcsipao1KQBXYeZaG0fYCZwAHjfor6yIgMahNNLBmdFOxugB9gFz7Ime8cHAqWc0a1ieJauoNdR58yH9vwHtMLnMxKclvvfv5typAxwKm8X6S7ZKUAnWA5A91JoDUHbygG6N3wA/gW0xO8DkPSYuLnHZADSjP2tQgU0xkLTcNsqJ+YyvLBD7vZ59ZZFKaNMvYLqbqcEfDHPwtyRFTtQ0A677hU8RY0A7aRw+T+Kx5FkFloFOCJ5oXBeiKnd8cDGge1cH20N7dyDzAShry1xP/y1sv1sEk0jzuGdEK0QmizWlstiVuXvg5VeuD8xyRa8EAIYw6f8+ew9EwbLSaG5rYgCT25hQpmbckLlVTny2HJ3nbAJAg9CA9LWnbD0DFtTTb1nb07Oi0tWlMq43qeATL+1CcrZ8FDuBgdCAHEUp+rMm1Nx84MGZTAZe8xJM32VqcJVhep2HbjaApQEI5SXHgFHrGbHA9jSpPEgvSZYhrdd+sVRqcFFSglkA2cytCixTC25gLHM/11jr9e75eGwg4jMmAEjRn83KMspu7N2f5xuTyp4qTI3NS9qvLkUol6T9+j99IW9m7IHaPo7FX4d/VkKdCxtXAAAAAElFTkSuQmCC\"},{\"path\":\"\",\"name\":\"kinght_pierce.png\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"2\",\"group\":\"\",\"width\":32,\"height\":32,\"uv_width\":32,\"uv_height\":32,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"f723476e-ee1c-003a-f7e4-4c487e274851\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAx9JREFUWEe9lj9v01AUxX8WLKgbEgxMGdj5ACmSBz6IWUBiAYkM/CmyLbVqJSQqBiqBGPJBGDIk34KhG8qExNANjM+Lr3tj4tgOap9s2U7sd88599z7XsR/jAyyFD5G8HPXaaJdP9R3n+H0yZwX7DPK4bEADZ1vJwAKpLOYU/AduA/5Pvm1AkjhjDlLvgIxnCQcvYaDa1MgnZOyX4aT6KNV2ChhsKKDP1CgT/D+2ZQJ5xVfXafh/kYEf4aosBOAYkpRB/fRRisVzCN9gPQGUBsP7gLLf/w+W3khyq4aQEZRM7MUyAO61zWDvDz6VsQgBYLzjX0zuFBdOYCMNLC34CaFKVCB+JJx+gN+9VGhtwKF5T65ZKqcB9Y6Kw+E66x/GnoBqHp+Gown9joV3O5NfvPBCD5kHL+EN12V0AtAqPuMSWDnZbd79QAp45/VmMKxfXS+oM8LHV5uKaGAXgmvgu5XjelmBL+3QdgKQNLfgb2a/QgWU9SBoyKmUO71rDEWQPOB+aIsyS4VOhWwQL7EFPARpOMMFtUCHEBJKZ+Kc8hn23vCVgDm/EXFMJqtciplAoAYFs4X3yBPY9JaCb1cVsQ2FVoB1M6XtJXjo+mlqeZQSIFQciVLA6bfgxoJhdIzTkDfta0PrQDk/AcwCbmthlcgbEiS1aK0EYAZt1JB7dlAelO2AvDOFwNj5idRGvQs6X3XsxR5cw4CEHKfsBQ7sbYJJW2zpATMAMQQz8qk1CtnpZB8MobbOTxvtueNCmhSQ2+yNwPXQWIKe8cD0PvBkDZa2vMagMp4Z569D2wBfBrShNTM2fxfKTJDSs2jGYcH8K7VAwFAQuql95L5AL4cN6XGp61WtGzPzb3CmgKh58dMNLny1px4E4C2JbdZdmpomrNp2DUAhlSBvbm60tAGwnvC/NAkVnc27XYWsBzDvRye2qRN1kNUsHd9c9Ii5TvjWmsV0y7ZdwFgCh7D4UN46+MEAAXcWsDFJvZVI/M7gbWK7LMF96B9mjVR7YETOHpVobMITbbNXmCV0LX38/PULR72IrjoXI43BR36W5OIJ/sXYhpiMBkEWqYAAAAASUVORK5CYII=\"}],\"animations\":[{\"uuid\":\"0568e895-3c91-5d89-99db-a8cd152c6bc6\",\"name\":\"idle\",\"loop\":\"loop\",\"override\":false,\"length\":2,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9aaeddf8-969e-96ec-499b-e177f3ca9bdd\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\\n\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b41191dd-a827-1d70-2481-57ecaab86d85\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c1a96e4f-e4d5-8b01-bc63-24a30810abda\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"95977c77-ae25-228d-e7ef-c26e45121bdd\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"dea0f803-3228-b1d7-1079-45f536051fbb\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5fae9431-7c30-479b-a141-8afd7b912528\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"2\"}],\"uuid\":\"de7b1cb6-e4af-26a0-6f0f-25934a6777e7\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"3\"}],\"uuid\":\"ddc3fa9b-478a-cae4-d2b7-52aa90d05217\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"2b591754-f4e4-0b7a-86ae-4641bf978234\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2e609dad-b848-67a0-7c7a-814c1291eec5\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2c4a5034-e8aa-1479-eab7-bfdf5f817950\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"afc9efd5-7991-5479-4685-6974030808fd\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"3\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"524c421a-15af-6a4d-acf9-cd8468e01d74\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a8694bb3-e773-87e7-d336-851a4ec119c7\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cdc3fb8f-aa14-4576-8035-d5a854b8a72d\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"22e9dcff-6d62-025e-d201-7b315f096fd0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.5\",\"z\":\"0\"}],\"uuid\":\"fc74a17f-7364-b098-e1db-da334555f52f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.25\",\"z\":\"0\"}],\"uuid\":\"14d06730-eb9b-1523-a2b4-39ff6e7a92bb\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ec87811f-65d5-17ce-810e-a189a5ac1d9c\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"26eb55a4-bfde-c9d0-95c9-a69453b52429\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"de9d485e-dab5-8181-4935-9f26a595ed51\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"5285314c-5162-09c4-5484-49628cf0a3ca\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"3f6f8f72-c792-940e-e5a6-61b045bdf30a\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-44.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"9a4eddc4-31bc-941e-6095-94b12644a5fa\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"72ad90bd-c715-fd60-9527-9d16fc5b64e8\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"a134ca79-b6e8-cf51-79c6-c252707c393b\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"ec847310-4f84-e7d7-d766-fb53ab5849aa\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f3ce685d-3702-b666-1d52-f2c3dbb7a967\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"346de168-6db2-96ee-3256-63587cb1baf3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"2\"}],\"uuid\":\"59467291-3096-8f20-998b-e30b3563af4a\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"3\"}],\"uuid\":\"e54a1cff-07e9-d81e-1cb6-e664311fa8bd\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"c9a0049c-04d5-ace2-8062-8e6f6c2f884e\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fa70244b-00fa-e279-2f52-16909df98bd6\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5371e06c-b54b-154b-b1c7-cfce2a9b20d4\":{\"name\":\"right_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cc9bae98-3244-1324-0c0e-d3dd2ecf8ad0\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"6edd2d20-911f-34af-f68b-c35efd9f0e78\",\"time\":0.5,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"2.5\"}],\"uuid\":\"0ea0f311-8824-837e-7ff8-c3c3730f2853\",\"time\":1,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"861a5003-238d-31cf-70bc-950490161a4f\",\"time\":2,\"color\":-1,\"interpolation\":\"linear\"}]},\"3a05cd31-c803-0a4c-716f-337c916a2ee5\":{\"name\":\"left_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"892e5c98-765f-aa7a-ea28-f13f7ae43c9a\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-5\"}],\"uuid\":\"53be47a5-8837-d908-1ed9-98a68646afe5\",\"time\":0.5,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-2.5\"}],\"uuid\":\"efe765f2-d9e1-0a5b-c650-ee3c1df06d51\",\"time\":1,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"258ac48f-c894-68c1-5296-4db0bcbca6c9\",\"time\":2,\"color\":-1,\"interpolation\":\"linear\"}]}}},{\"uuid\":\"510cb280-3696-1b43-8e58-1de159ee2e73\",\"name\":\"death\",\"loop\":\"once\",\"override\":false,\"length\":2.5,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"8020a4d2-86c4-c7df-3226-15f2fa672aa8\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b0ba3966-f18f-dd38-f025-671f517f8e04\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"340cf0b5-d2bc-5ecd-53e1-cf305e74b2e5\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"4.9952651976\",\"y\":\"0.2178207497\",\"z\":\"-2.4904987465\"}],\"uuid\":\"4caff30f-3d14-14b5-9d9c-2798952df1f2\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"89.9762987955\",\"y\":\"0.2165792068\",\"z\":\"-7.490534542\"}],\"uuid\":\"ae3284d0-537d-1369-f4fa-72bb074ff2a4\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"92.176738467\",\"y\":\"-0.1017084386\",\"z\":\"0.0217524291\"}],\"uuid\":\"ca2fec36-6322-efd3-a594-9b84a342965c\",\"time\":1.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7fcd76c2-f8a4-c148-ddf8-af17ae796f6b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"b0762dcc-23a4-098d-8170-e6d66d7bd03c\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-7\",\"z\":\"0\"}],\"uuid\":\"95ec6482-0272-9859-3396-1951277b89a1\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"6\",\"z\":\"0\"}],\"uuid\":\"7e13a2d2-7312-c1c3-43fd-38c54af47820\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-7\",\"z\":\"0\"}],\"uuid\":\"21b95278-b341-2bc6-8029-c207da55f910\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"4364873b-23f9-3f6e-c786-663c4bbdc991\",\"time\":1.54167,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"eae7ff44-ab85-27d8-b667-736f16e09902\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"12f39d56-99dc-9d96-2620-1da6e5f5f028\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bc207ecb-14cf-fff2-a7fd-96b0a5317891\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6efe5b7e-e780-eabd-e076-4c8b0c4e0ce4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cc0147ff-e3ba-d298-3733-76be4918fc46\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"79b0ab3c-3ef4-c14a-b2fd-7b89d05c07c0\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5f0025c2-7396-1cff-c649-5b15c91e684d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d1930a1d-f6c4-3fa9-ec4d-f30be9285883\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"913410bc-781c-8159-47d8-759d83b6cb13\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5f879aee-67da-ff07-c674-c491a3b5ea65\",\"time\":1.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d6218785-1b3f-6809-dfc6-3f547e03fc81\",\"time\":2.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"12497260-82b3-fe10-467f-701a208d25a4\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"675d5f30-711d-6b7d-bdf8-f2a67ec27bf0\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bee90a8e-63cb-4223-950d-89611312e340\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b5f74ae8-343e-e32f-8b1b-7dc876f8307c\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"97883f34-e784-3e6f-33f0-e2a7b3db80f7\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"eab89d5c-1e5a-b97b-d624-a0af5526be9b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-110.7533267908\",\"y\":\"33.5046869781\",\"z\":\"-4.7014465817\"}],\"uuid\":\"7b73720f-f242-3a95-0830-4f6443f781f1\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-134.3128187158\",\"y\":\"8.8034834933\",\"z\":\"8.9092819994\"}],\"uuid\":\"e0b04727-7bf8-a821-8234-57ed39e723db\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-166.8128187158\",\"y\":\"8.8034834933\",\"z\":\"8.9092819994\"}],\"uuid\":\"c338f603-760a-e403-2703-9276aedb6d07\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"d357b2b5-23e9-c300-f8ec-f87d248d8ff3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4e2212d7-bd4d-f37b-871c-2ae0bda3205d\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-60\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a89eb7f9-c62f-2fb2-5e80-41c7ca811c8c\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b0b8c9e5-2cbe-a327-c5e5-a2edd78a4745\",\"time\":1.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.4368798415\",\"y\":\"-80.0381493561\",\"z\":\"0.4368798422\"}],\"uuid\":\"a3f30f24-5c48-ae1b-2832-6cfb89770745\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.4393978432\",\"y\":\"-80.0371962438\",\"z\":\"0.4366846869\"}],\"uuid\":\"0b4a8cd7-e8da-0d56-0644-e7ed84d89212\",\"time\":2.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"bdd1cd1d-d74a-ece9-874c-bf429b5abffd\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"39.7004210349\",\"y\":\"26.1121805722\",\"z\":\"20.0727326499\"}],\"uuid\":\"d1c6f13c-0dad-e5ed-48f0-3356a0e78718\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"65\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b8847624-dabc-912b-6fb4-23e6c9af3bdf\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"77.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1e5daf38-a347-0353-dbd4-35c0a36d513e\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"20dfaa41-1955-d6db-a4aa-4dd3601dedb0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"17b70c8d-01c1-3e8f-d2fe-bfbce7dd52b2\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a8c57466-0c8f-0b1a-94af-81dec6cd81ee\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5f00bf5f-83d7-4833-524b-dd637117dd7b\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"51299b95-d594-0125-9e3f-bc3689241e20\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4b3b863b-74b2-639a-a7ab-e95f80b55e83\",\"time\":1.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"116e320d-52d5-9564-aca1-1da1cf1bfd9f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5e0b9dec-1093-a954-b9f1-20d5edcb1415\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.3593155127\",\"y\":\"2.2494361891\",\"z\":\"7.1565621013\"}],\"uuid\":\"7034adff-e4cd-3746-93ca-c9998cdf21e4\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-39.6005110996\",\"y\":\"5.44649138\",\"z\":\"11.0100837016\"}],\"uuid\":\"0457095f-c9e7-c678-b46c-65e00255d3a5\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-19.6005110996\",\"y\":\"5.44649138\",\"z\":\"11.0100837016\"}],\"uuid\":\"e1bac75a-8490-94ec-9989-13bc46e04c18\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"24eb8803-b446-6b07-679e-d0c8d9422232\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"90\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d8b914e9-3451-0090-c33a-9c041982516d\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"122.4556059038\",\"y\":\"-0.4677121805\",\"z\":\"-0.0471610168\"}],\"uuid\":\"f5b6a89f-c418-c043-341d-0e6cb1684b74\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.4556059038\",\"y\":\"-0.4677121805\",\"z\":\"-0.0471610168\"}],\"uuid\":\"e81fcb8a-4e06-d660-fab1-32971d5892a7\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"19.9556059038\",\"y\":\"-0.4677121805\",\"z\":\"-0.0471610168\"}],\"uuid\":\"24a9e4a4-c548-1a5e-8930-1c8acaccdd07\",\"time\":1.54167,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"436ebcaf-7570-8c3d-368e-80184e593a90\":{\"name\":\"right_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e3170697-624f-2399-e57b-f36ec554de0a\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"72.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cb338767-44ba-b0f2-2ebd-067d2c98b7d8\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fa3c79c2-0028-b8e1-8c70-3982af355be0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-14.877386585\",\"y\":\"-1.9359724559\",\"z\":\"-7.2472078555\"}],\"uuid\":\"c7229567-fb75-7276-bc3a-433c37989c76\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.1355863577\",\"y\":\"-4.6057413366\",\"z\":\"-11.4820643647\"}],\"uuid\":\"19014854-9d05-1139-c36e-8941717defbb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-9.6355863577\",\"y\":\"-4.6057413366\",\"z\":\"-11.4820643647\"}],\"uuid\":\"d747c85e-ce00-50c3-a6fb-c286e720b3be\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9b962340-0489-07f8-3f0d-4d8713e2cde8\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"102.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"99e9f35d-b476-7db6-c4a1-f588b808b79c\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"120\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7c34c4d8-bce2-781c-486a-50e07fa81024\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f3d0a1ab-dc80-7ba5-4d0c-21d3de246bf0\",\"time\":1.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a8a570db-7e6a-e661-a79f-fe6e6630f4ff\",\"time\":1.54167,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c942401f-8d13-b8e4-8a4a-173d0e84ed99\":{\"name\":\"left_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a356cdb2-c0f0-acc4-50b6-3548e534bfb0\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e0d54c45-2db2-7ace-b8f3-434018d1f4c7\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f22cd20f-7fd0-cba2-ee86-2ffd436a291d\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"67db9f71-991f-0caa-c480-9f841d70d07e\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"b31c3388-4100-ca50-0493-e30ac3af7eaa\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2f6cd6b8-e7f0-212e-4daa-da63a04ae071\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"5371e06c-b54b-154b-b1c7-cfce2a9b20d4\":{\"name\":\"right_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"771d32bd-dd3a-7bf0-1410-15cbd01564cf\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"3a05cd31-c803-0a4c-716f-337c916a2ee5\":{\"name\":\"left_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f71eac59-fff1-04a7-3e5e-0ac8a9322789\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]}}},{\"uuid\":\"4642b4ed-f48c-2563-1140-dbf58b985fe1\",\"name\":\"walk\",\"loop\":\"loop\",\"override\":false,\"length\":1.5,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9aaeddf8-969e-96ec-499b-e177f3ca9bdd\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"60822f88-85f7-a6f6-6815-eab0eec41c32\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d4d369bd-34ea-2687-bbd9-26ed2bb20341\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"78bda604-b1c7-3d1b-a7c1-ff5295a3dcf1\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c3377665-d618-dcea-7e29-c69bbb2bdda9\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"dea0f803-3228-b1d7-1079-45f536051fbb\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5fae9431-7c30-479b-a141-8afd7b912528\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2c4a5034-e8aa-1479-eab7-bfdf5f817950\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"22e9dcff-6d62-025e-d201-7b315f096fd0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"26eb55a4-bfde-c9d0-95c9-a69453b52429\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"de9d485e-dab5-8181-4935-9f26a595ed51\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"d0705793-56ba-753f-ac10-5cf57b1f0324\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"2.5\",\"z\":\"0\"}],\"uuid\":\"52638576-eb9c-f6dd-7f5b-04e47403bfb6\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"352f9338-ebb0-9683-4313-5ecb0cff41f5\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"d3fa48e6-eda4-b933-94dc-3cba7deac0bc\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"e9a500bf-a132-1092-3e54-c0d70a47ceeb\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.75\",\"z\":\"0\"}],\"uuid\":\"9e0f4761-8e81-54da-4fa4-28f80d4b51d0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.5\",\"z\":\"0\"}],\"uuid\":\"6d493461-ad31-a3ca-86d1-b594348d976e\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.75\",\"z\":\"0\"}],\"uuid\":\"ea98ce1e-29e2-b66f-2c11-9e1751577cda\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.5\",\"z\":\"0\"}],\"uuid\":\"ed34e37c-9f69-37ce-ca7b-a99b3e33d4ae\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.75\",\"z\":\"0\"}],\"uuid\":\"f5ef19fe-073a-838d-ae21-ce153ed2a529\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"5285314c-5162-09c4-5484-49628cf0a3ca\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"ec847310-4f84-e7d7-d766-fb53ab5849aa\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"91d34a2e-312c-0a63-74f0-0142d5294391\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"346de168-6db2-96ee-3256-63587cb1baf3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"81c98f4b-5ade-166e-be9a-8cbee4eddc3a\":{\"name\":\"front_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9ab72de0-8948-bb5f-8b41-9a1161fb4091\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fde09f44-b0bd-a56b-a445-cb4ce43e6e23\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9525c60a-f019-24a7-87e6-ee6d749afb8d\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"70afea31-76be-75d9-46d3-43d028bcfe7c\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8db2f7fe-8f10-d022-039e-4663c30b40ba\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6e67f127-d75b-e721-1019-14fc3600d26f\":{\"name\":\"back_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"51b1e6a8-28f0-b252-d8e9-aea000a03c02\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3534eed3-8978-e92a-6d2f-d663fff7a203\",\"time\":0.375,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6abad1ca-822f-6280-faf9-c49623ecdbbe\",\"time\":0.75,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d5222dfa-c52a-aaa9-1811-0fed2e95a0a3\",\"time\":1.125,\"color\":-1,\"interpolation\":\"linear\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9304ec04-03f8-78e1-f29c-6c69f42cf0ad\",\"time\":1.5,\"color\":-1,\"interpolation\":\"linear\"}]},\"5371e06c-b54b-154b-b1c7-cfce2a9b20d4\":{\"name\":\"right_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cc9bae98-3244-1324-0c0e-d3dd2ecf8ad0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"3a05cd31-c803-0a4c-716f-337c916a2ee5\":{\"name\":\"left_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"892e5c98-765f-aa7a-ea28-f13f7ae43c9a\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4073217e-05e6-bd60-58fb-c3e0139d6379\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8bd0c01e-36dc-2b14-1c7d-e7497c86e92a\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ded4e1d9-1a5f-01e6-a033-7599a98283ad\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6c935ae0-2d69-12b6-d39d-4ef8c12f3c90\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4d42860c-b1ae-52b4-b73a-beef85c1cc94\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f2c87eee-443b-0e50-bf0a-9ee7303828a4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"69c9d864-76ab-4ca3-308f-8baeb422cc96\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f81b8860-566d-743c-7b9e-30c1a690c13b\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"eb139803-e6c6-232b-79ec-d0f06a32d027\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1fa6d108-f49b-da21-81b0-5c4bd018a52c\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"436ebcaf-7570-8c3d-368e-80184e593a90\":{\"name\":\"right_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"741619fb-c331-624f-091c-0b623f5de04b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5b213e97-2bb8-7c37-630c-a1e7aaa0d842\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3c925edd-4b6c-df59-9de2-fd46e659893a\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7abf5873-1ea1-11e1-e36b-25d9fa312338\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"182dc758-48d0-137d-7a68-4d7b3a155dac\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d912926a-c050-b5dd-6282-ee299bb55ea6\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ac422e86-edce-fea2-29a6-653f655af0b1\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ada3c732-4aa4-4549-03f0-896ff112aa58\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"132b7f44-474a-ffc4-561d-593b28910874\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"531d539b-24aa-89e2-a9c1-a05de88dd2f7\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"edfcb841-4b36-29bb-05c3-9032b24325cb\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"43d87a42-cb2a-5375-2e88-4511b4743b7e\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a7d96d8c-3faf-278f-b298-87943ef779eb\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d18acf2a-399b-460f-a7e8-1cdc41bbe0fe\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c942401f-8d13-b8e4-8a4a-173d0e84ed99\":{\"name\":\"left_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"439749d0-1563-1ad8-5da9-97e8cdcd345d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"547ed0ff-908b-c20f-d4a0-f29393bd92eb\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"23227e6a-0f2e-b88f-e678-00bae5850529\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"4106bf4d-64cb-7fcb-e313-93f3c0f91322\",\"name\":\"run\",\"loop\":\"loop\",\"override\":false,\"length\":0.91667,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"45\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9aaeddf8-969e-96ec-499b-e177f3ca9bdd\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9ec65425-bb1c-2d1a-62da-16ebd057b61d\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d9ba50bf-b192-2983-d4d1-1472501b8877\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"45\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"87889298-b035-0863-6bdf-7de30281300a\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3e8c50d0-6ea8-dc6e-0077-a1e2e505002f\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5fb98c66-a182-097c-c2f8-8f4dbda173f2\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"45\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c540bb50-0723-d9a5-8c45-b3225ce8600d\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"dea0f803-3228-b1d7-1079-45f536051fbb\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5fae9431-7c30-479b-a141-8afd7b912528\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"2c4a5034-e8aa-1479-eab7-bfdf5f817950\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"2.5\",\"z\":\"0\"}],\"uuid\":\"45fea26f-99a1-5ffd-2231-a669a559728b\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"bd7462be-9a4e-0725-9a96-9ccac3e05e8b\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"687e1408-c6fd-021b-2ae0-f2ea5dedb295\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"2.5\",\"z\":\"0\"}],\"uuid\":\"63288b3d-abd4-6c2f-3c5c-0fa7e9b08dcd\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"873eeba7-b3da-087b-5f42-37d37ab23d71\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"7787f115-9bfc-1011-3c5b-e251e73f6065\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"22e9dcff-6d62-025e-d201-7b315f096fd0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"26eb55a4-bfde-c9d0-95c9-a69453b52429\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"de9d485e-dab5-8181-4935-9f26a595ed51\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"5285314c-5162-09c4-5484-49628cf0a3ca\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"ec847310-4f84-e7d7-d766-fb53ab5849aa\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f3ce685d-3702-b666-1d52-f2c3dbb7a967\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"346de168-6db2-96ee-3256-63587cb1baf3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5371e06c-b54b-154b-b1c7-cfce2a9b20d4\":{\"name\":\"right_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cc9bae98-3244-1324-0c0e-d3dd2ecf8ad0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"3a05cd31-c803-0a4c-716f-337c916a2ee5\":{\"name\":\"left_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"892e5c98-765f-aa7a-ea28-f13f7ae43c9a\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d709fc5a-8350-da8c-53b3-a40c25f3b016\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"35ecf87a-bedc-7a14-a40b-e6b4ba66e969\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"9116ff90-5872-ab22-e384-e0ceaf3436a5\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"2e5cd626-b44b-5969-6fc4-43ac37037752\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"1f7bfd87-80e9-ed24-1012-03461aa4698d\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"1db6e464-dce2-3fd9-0e57-6bfc17d7b86c\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"f2f4fc25-983e-d1da-dc88-f7a38e4636a8\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"c81a8133-a45a-784e-1087-0837e69535f7\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"768cc4b1-7942-604e-1d3e-03d80679b22f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"076ea76f-053f-1b63-d26d-4afe34124582\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"2.5\",\"z\":\"0\"}],\"uuid\":\"98cbc9e9-ac63-d22b-5a8a-08180c794768\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"d55c9330-6818-e5be-ea2a-d06ab4ebb670\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"2.5\",\"z\":\"0\"}],\"uuid\":\"25107aa3-6be0-53a1-3a1d-05bfe1c09bf2\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"2babf29d-3a10-5f11-8636-bb42e5e97bf4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"47b6bdc0-7d28-3758-dfee-8b3a98979461\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"81c98f4b-5ade-166e-be9a-8cbee4eddc3a\":{\"name\":\"front_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1f5d185f-1021-6c5b-9a6f-3922e7635e31\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"753af197-18c9-3e96-28c1-d5e25d26fcb6\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d824f74a-8325-1c7e-4f67-50dcb987ead2\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7417cc1d-0a41-7e51-f928-32277c8435eb\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6c2f94c8-d72e-1b32-a8ea-6ccc7a405e3d\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"50e37f7e-5aae-4ca6-c510-3d032630e525\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f8433ded-8c06-f647-6afd-2467559ed44e\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"922a3145-26b4-70ca-a930-e8ba98f73a5f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"20da063d-9109-9c89-706b-7a1cec5ead3e\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0f82d10a-efd2-c76d-f44e-ab9716d03163\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"43af1291-643d-e435-e345-18aba110484e\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3a738583-a65b-e679-6513-a9d958a7cd73\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"de720af0-262a-d1f2-8542-8c02548e9713\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2f5e4d2a-8fc4-662f-bfa2-a9178ea0a040\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"746977ac-4ef3-5fce-33bc-5c7935ab1f78\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6e25dfbe-df32-e0a4-c058-4d12d8fbb111\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"52d82805-3f0e-c083-2507-0ed9c09227e5\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f279a7fc-ea92-6386-68a4-48601531e998\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b23177b6-3233-0d50-d425-87055cbb8d69\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"39783afa-11d6-239e-9691-de4a55f1b627\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9a63dd9a-4d60-8b4a-c9e1-c6422f2ac540\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"436ebcaf-7570-8c3d-368e-80184e593a90\":{\"name\":\"right_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5ed78388-81f4-b52d-ed0a-c07bb3f41a97\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ff65fd54-ffb2-822b-3d46-64111fb00f31\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1bd77986-ca36-d1cc-d7b3-9d76da8ec2af\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5d94846d-bf39-0ee8-1e6f-fccafecb58a5\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1efeb39c-fcd1-c586-9b8c-e353e002c6ab\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"29fbec2b-d6a9-4378-e3f0-b1323dbd9f99\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e5813c2c-417a-e9f0-f884-f24ba7e935df\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f1afb47e-c3b7-eb28-58b3-e186f5f50326\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0a77b47d-db3b-0396-bcf1-4d28c5f678de\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9293d127-897d-ccd1-79af-68c3466c051f\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6209ac0b-10f9-16e8-fb88-8ff12c4da021\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4b7973c2-dece-e433-d49b-0d91b25e4b9d\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c05bf0ae-689a-84d3-e888-cf771fc004f4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"915bde67-8104-9f0d-64d6-0e8f7b8884e3\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4737d868-1811-5746-dc7f-5a021d215b17\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1b037746-788a-fac5-53df-8a483be5e6de\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5506fdcb-071f-9f05-8461-1126d926e475\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"30247f7a-363c-971c-abc3-d28b3d8fdb81\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"68c69f55-6ef0-a7e2-b760-8734009d5fae\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9bd6c318-0662-cf37-37f3-b45fe1e71ad5\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d3b21f76-0334-afe3-6e31-39c04633f835\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c942401f-8d13-b8e4-8a4a-173d0e84ed99\":{\"name\":\"left_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"525671c2-57b1-b81e-fddb-3c3a9d9ee81e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"087222c9-19d7-52ec-d96a-176c26680920\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7a0e9bc7-6ecf-99c1-445c-d0e8e0601afd\",\"time\":0.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"39bfc63d-774c-5316-0b96-9a85d7bd1583\",\"time\":0.45833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8f13e430-9bb3-9880-7127-1757bf63c8af\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1bf32ab8-3394-953c-b746-7ef81e416e73\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f0051431-d00b-5bd2-1084-d63d4b7da6b2\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"e9ddab9f-74b7-2fb1-5c04-ba511e9d0b8c\",\"name\":\"guard\",\"loop\":\"loop\",\"override\":false,\"length\":1.25,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3d26cb97-e5ce-3f17-43cd-643a71443391\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"971eea94-f544-0922-d057-eed67a3cccb6\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f40ff047-9154-08db-c669-bf9c7c0e5b2a\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ac566804-2035-411e-1aca-87c763b96092\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f0451f27-00ac-18e3-232b-5f042461c53c\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0ea521b7-7dd8-8902-6a82-8b80533e9667\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"dea0f803-3228-b1d7-1079-45f536051fbb\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"10\"}],\"uuid\":\"5fae9431-7c30-479b-a141-8afd7b912528\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"13.7453807011\",\"y\":\"24.3683005175\",\"z\":\"5.7632011063\"}],\"uuid\":\"2c4a5034-e8aa-1479-eab7-bfdf5f817950\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"22e9dcff-6d62-025e-d201-7b315f096fd0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"26eb55a4-bfde-c9d0-95c9-a69453b52429\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"de9d485e-dab5-8181-4935-9f26a595ed51\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"7.5\",\"z\":\"17.5\"}],\"uuid\":\"5285314c-5162-09c4-5484-49628cf0a3ca\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"ec847310-4f84-e7d7-d766-fb53ab5849aa\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f3ce685d-3702-b666-1d52-f2c3dbb7a967\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-54.7711766863\",\"y\":\"-17.5581677785\",\"z\":\"-0.5641567311\"}],\"uuid\":\"346de168-6db2-96ee-3256-63587cb1baf3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-54.7711766863\",\"y\":\"-17.5581677785\",\"z\":\"-0.5641567311\"}],\"uuid\":\"07abdd8d-91c4-b952-c78c-c12513a72de4\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-55.2586332172\",\"y\":\"-22.4395396341\",\"z\":\"0.5841097705\"}],\"uuid\":\"ea1776a7-c481-6e51-6b62-16ab802a4cc4\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-55.5495253525\",\"y\":\"-24.8764419773\",\"z\":\"1.1913415952\"}],\"uuid\":\"a4898274-e46d-031e-d5f0-f590cc959f6a\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-55\",\"y\":\"-20\",\"z\":\"0\"}],\"uuid\":\"c95c2520-73b6-66f2-436f-b14f914b43d3\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-54.7711766863\",\"y\":\"-17.5581677785\",\"z\":\"-0.5641567311\"}],\"uuid\":\"9e65bf4d-5fdc-8811-775b-dde8a3c5163e\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"f34d68fa-4fe8-de16-e32b-f84d314a87f9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"139d2723-f811-92f3-87b0-2ab0d94d4ca8\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"9167b23f-c09f-fd28-7c3a-bd26fb0fa90d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"7.5\",\"z\":\"0\"}],\"uuid\":\"43bb8d4d-e46b-dd86-1585-e5d55b7550ae\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"de8df2d8-013f-9ba7-ba6c-08578f9a094d\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"e470b839-3cdf-1b9d-8543-532586599311\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.75\",\"z\":\"0\"}],\"uuid\":\"d3e16565-42b1-d549-f8a7-1ca80ef64adc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-25\",\"z\":\"0\"}],\"uuid\":\"ff8900c4-b6d7-2cd5-686b-c0d1fa7e89f8\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-25\",\"z\":\"0\"}],\"uuid\":\"a810309d-a926-376f-44b0-effeb8288be2\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-30\",\"z\":\"0\"}],\"uuid\":\"ee5927f1-0b2b-ac7d-22e6-05a5faa6f524\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-32.5\",\"z\":\"0\"}],\"uuid\":\"a6a894f3-af64-7649-7d63-62b822a9c412\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-27.5\",\"z\":\"0\"}],\"uuid\":\"1c3c9cac-143e-d53c-2b14-de3b6be57c05\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"-25\",\"z\":\"0\"}],\"uuid\":\"df0e4a33-bfc5-588f-be03-2d15c236086d\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"08fa9aea-2f3a-ad98-9647-65d992767b55\":{\"name\":\"left_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-85.4374286751\",\"y\":\"-36.1324307237\",\"z\":\"79.7006674603\"}],\"uuid\":\"2ce710fb-f885-ee5d-0f82-0cef47337413\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"621973ec-d68a-5564-596e-d9cd80a4a0f6\":{\"name\":\"shield\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"65\"}],\"uuid\":\"a4f9efde-a87e-d679-6054-dc24c6e785fa\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"7.5\"}],\"uuid\":\"58be1892-c208-24fa-9f69-39859b610a0f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"55f2562e-6c6c-f724-fd73-b86e5b1ac42f\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"-5\"}],\"uuid\":\"e6b6a8c2-8be9-8341-cba5-505fd1a2efcb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"-7.5\"}],\"uuid\":\"115bd3f7-234b-a5e7-9806-bcae53842ea3\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"5fe6181f-cbd5-848b-98d1-e8152f8532a4\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"7.5\"}],\"uuid\":\"8a463b24-6c8d-a44c-f55e-377e12a64a3e\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"35b4882a-31ab-a98d-4865-17b7572a0f8a\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"-7.5\"}],\"uuid\":\"e52942b8-0916-f087-090d-0485d99289d5\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"-5\"}],\"uuid\":\"fa3c206b-d3a2-a21f-fc76-e7317209c85a\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"c391f3a4-8bb4-6c91-1fe8-ad129c430095\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"7.5\"}],\"uuid\":\"4db2de23-12cd-dc86-e7dc-fe2d5c63feda\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"-5\"}],\"uuid\":\"6040e07c-fea3-f464-5a06-3c3a42d431a0\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"-7.5\"}],\"uuid\":\"2a083383-9eca-c82b-a401-b9830a957626\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"617859b4-9b4f-d736-4db1-8e36b5fd2eeb\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"4a4d08eb-0c10-51e8-6dde-e173838f1957\",\"name\":\"hammer_attack_1\",\"loop\":\"once\",\"override\":false,\"length\":1.25,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1b1b7dec-ba79-d599-a3db-11a766e66cf9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.6696299537\",\"y\":\"-19.9853884562\",\"z\":\"-2.5880725782\"}],\"uuid\":\"786a0300-3f6a-d89d-015f-f695a9753917\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.7136409398\",\"y\":\"-32.4846643084\",\"z\":\"-2.716354566\"}],\"uuid\":\"4fbdef4c-a87a-a437-015c-206e2342f954\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"17.5\",\"z\":\"0\"}],\"uuid\":\"0ba86cb3-9d82-615f-6fc4-7cc3ceda9f6b\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"73d2cef0-0cb3-414e-34fa-762c7750e570\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.75\",\"z\":\"0\"}],\"uuid\":\"220a4b15-cecf-9c21-39c2-05f78068c80a\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7d930b96-bad1-0866-7acf-9ecc87f16b48\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"c78982e4-af8f-37fe-0318-7d89d4d3f859\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"9.8086313282\",\"y\":\"-2.4255811716\",\"z\":\"-0.8807534581\"}],\"uuid\":\"e3832921-14fd-900d-6e48-f5f9bacdd243\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.2462486798\",\"y\":\"9.8553006608\",\"z\":\"1.6477784446\"}],\"uuid\":\"27fbf510-1854-9d2a-a80e-7caeb72cabe6\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.6733365898\",\"y\":\"9.6173935822\",\"z\":\"4.1728100336\"}],\"uuid\":\"5b631dfe-f46f-3d38-49ab-7c138ab3d9e8\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ddfe426e-bf46-2d44-4ce1-08a981a67d78\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5f9f87f1-907e-d611-63fc-d565e9595f46\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9eaa2782-2fa4-4eda-cbfb-ee5f6b6205f7\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1f44ac96-e70e-7b77-fe61-6784672e2d61\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5.0767330166\",\"y\":\"9.9615580981\",\"z\":\"-0.8804470158\"}],\"uuid\":\"2f3e7959-a76f-7401-b03e-df0a27a289d3\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5.5138520612\",\"y\":\"-24.8983739694\",\"z\":\"2.3272993503\"}],\"uuid\":\"7b657bb8-ca8f-b8da-a4cf-80b605cfc7a7\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5.7686322235\",\"y\":\"-29.8742012582\",\"z\":\"2.8806590864\"}],\"uuid\":\"4b9148a2-f734-64f3-769a-a36daabd7748\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"36f74993-13cc-8807-2fcb-fda1ca9b127d\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9b495d0c-129d-009f-6a15-e30f7baba72d\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e78c6bc7-9646-cf70-b054-f0dde92d2ddb\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e47353aa-4064-349e-ee79-94840880db87\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fd3ea7d0-8537-40ea-b93b-c72a9d897c1b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-89.841262567\",\"y\":\"-7.0453261831\",\"z\":\"-2.5781617804\"}],\"uuid\":\"6eb13775-5af9-65f7-dc8a-659325059ba9\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-87.341262567\",\"y\":\"-7.0453261831\",\"z\":\"-2.5781617804\"}],\"uuid\":\"42066c40-7d16-9890-706c-fd9137e7a5da\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.6570134652\",\"y\":\"63.4220934291\",\"z\":\"46.1580740926\"}],\"uuid\":\"86e08d4a-e039-98f2-8814-7488a93aef9b\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.1937755234\",\"y\":\"64.2455098641\",\"z\":\"45.5178564722\"}],\"uuid\":\"d74af142-54ce-90c8-cbc6-bce2c3396947\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"e8f00e41-be0a-1bbf-1680-3e2ebde0bbf6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"60\",\"z\":\"-20\"}],\"uuid\":\"0e010224-4c62-9495-1839-491b3a502a8c\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"fc434329-f147-42a3-d3c3-331d7b5cbff8\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-32.5\",\"z\":\"-20\"}],\"uuid\":\"79057b83-c498-3ecf-7fcf-0f5502c17670\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-65\",\"z\":\"0\"}],\"uuid\":\"c7205370-0742-bdf2-2f03-5dc04cb0fed1\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-52.5\",\"z\":\"0\"}],\"uuid\":\"01d3604c-f9f6-0565-e903-0f4e2a7538d9\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"-72.5\",\"z\":\"0\"}],\"uuid\":\"fb38379e-eb9d-6143-4146-e3b2d007b113\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"62.5\",\"y\":\"-72.5\",\"z\":\"0\"}],\"uuid\":\"7a1ad48f-8618-38f8-f5d7-cb55c4ca9e7d\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"30cc3c4d-3f8b-4a38-64d1-d8593fe6d38d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-17.5\"}],\"uuid\":\"dc638d5e-24cb-7a35-f001-f99aa8d0f0b9\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"8.9188017707\",\"y\":\"-4.4192464358\",\"z\":\"20.2873425764\"}],\"uuid\":\"9295b6f2-d756-a453-b47b-44ae122382ed\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"16.6017897738\",\"y\":\"-11.3133541722\",\"z\":\"33.3441109825\"}],\"uuid\":\"7ba320e6-56ee-83b0-0519-06b14938d274\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0fa74dd9-0fa8-9f7d-923d-cf13e136c071\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e9b8500e-748c-6723-520f-89f17d13ce3e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"35c4d8ab-7dad-96bf-339a-de8d6e95c5ad\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"62cffffd-8f61-de42-9113-dd6e828a972e\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-11.74\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f6289bec-1d33-3b34-cf82-679bb34a7acc\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bda2abfd-d6b9-f857-b331-0ede0ac430cf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a741be0a-5165-4ce2-6662-6671dd53d9bc\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"85016fbc-43e6-d741-c0ab-d50471214e38\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-38.4268434977\",\"y\":\"25.4967141976\",\"z\":\"28.4834661178\"}],\"uuid\":\"05ec89f7-c93e-d091-6558-51a7b425bf61\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40.3985022075\",\"y\":\"22.0779417413\",\"z\":\"24.7639715819\"}],\"uuid\":\"be37b7b2-c34c-a12c-fdd4-0b80b67b9cd4\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0b6665ea-0009-3118-3e74-d022c4cd4683\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"621973ec-d68a-5564-596e-d9cd80a4a0f6\":{\"name\":\"shield\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"cf2ad0bb-4211-41df-af74-f79e757fef2b\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"622019b8-f799-e3b7-8e3c-185f7614fe20\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f09c202e-fb53-4a38-63cc-1e5c978cd5bf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5462389525\",\"y\":\"4.8811889482\",\"z\":\"-1.08482395\"}],\"uuid\":\"d09f6d36-356e-efe3-d4d7-5a2ddba1e23d\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-19.4009377236\",\"y\":\"18.3210986848\",\"z\":\"-0.6039670971\"}],\"uuid\":\"8bf8de06-c288-e7e7-9263-ab55e5e10bb9\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"16.7268205668\",\"y\":\"-5.6526197535\",\"z\":\"13.8547690382\"}],\"uuid\":\"9b9cce11-631d-656e-a4b8-6fa3b2fb8f7e\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"24.2268205668\",\"y\":\"-5.6526197535\",\"z\":\"13.8547690382\"}],\"uuid\":\"81588c09-34c5-e0f0-4a38-688ff32acf07\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4034aadd-ac15-4222-7441-375f4f0fc834\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"41e491fc-fff7-a044-c42d-e5e0c0f7df2a\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ba86adb5-6c3a-5688-2fad-cfcd19578e1d\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.0175412737\",\"y\":\"2.349144295\",\"z\":\"0.8555298152\"}],\"uuid\":\"7d77986f-0a6e-979b-25b7-d8e1cb229d50\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"61311066-9e6b-41ea-ae18-df8c531c8efb\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"07416e53-9f01-d6df-d09a-5b9cd1447fed\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.9409310696\",\"y\":\"5.7357552465\",\"z\":\"-11.1250111054\"}],\"uuid\":\"e18e5d62-99ea-f277-1b69-cda940d99b38\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-19.3445693673\",\"y\":\"-25.265343289\",\"z\":\"3.2804683392\"}],\"uuid\":\"bc6f5eaa-28c1-5e43-c178-95a1741507f9\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-26.8445693673\",\"y\":\"-25.265343289\",\"z\":\"3.2804683392\"}],\"uuid\":\"4e24d6b8-b0de-73d3-8759-947bd05753e9\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d4aa0a4b-d1b1-986b-c5e8-e9940bdd73bd\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"78958494-e126-049c-3d91-e36a104b36f3\",\"time\":0.70833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"19.842303908\",\"y\":\"2.5586821925\",\"z\":\"-7.0523929293\"}],\"uuid\":\"8b5037df-3a80-55df-ad10-da2159966be6\",\"time\":0.875,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.342303908\",\"y\":\"2.5586821925\",\"z\":\"-7.0523929293\"}],\"uuid\":\"171c02bb-b3ad-aac7-b9ff-9fbecf1de0f8\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"2.5\",\"z\":\"1\"}],\"uuid\":\"72ac3b66-e62b-f26d-5f69-3311be6b17b1\",\"time\":0.875,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"b2a515a2-421b-228b-0674-610e4526b0f3\",\"time\":1.25,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"e50f560b-5445-2865-134b-afa36dd5329b\",\"time\":0.70833,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"bfe4aa0a-998a-f8da-4357-46864057679b\",\"time\":1.04167,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0f95b084-4bac-29b7-391f-0f02394ab19b\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"22552919-6050-7754-acd1-d0cd20c47e0e\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"5371e06c-b54b-154b-b1c7-cfce2a9b20d4\":{\"name\":\"right_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a9ca584d-8130-8424-fbbe-6d61c4d1450a\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"3a05cd31-c803-0a4c-716f-337c916a2ee5\":{\"name\":\"left_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e82803e7-4fbb-f7dd-1b65-5093476920f3\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]}}},{\"uuid\":\"7acd538f-4e3c-22ad-ec93-0ed59151a482\",\"name\":\"hammer_attack_2\",\"loop\":\"once\",\"override\":false,\"length\":1.25,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"73d2cef0-0cb3-414e-34fa-762c7750e570\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"69801aaa-6d29-af98-94a9-9d9fab4b3ee7\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.1091175935\",\"y\":\"-2.4976190449\",\"z\":\"2.5023786868\"}],\"uuid\":\"3db2f3f5-32ab-7653-3f36-509bd04c35c5\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"a7662d40-adb4-6560-57ed-097fc3c8718e\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1092217464\",\"y\":\"-4.9976190427\",\"z\":\"-0.0023854959\"}],\"uuid\":\"5419dcdb-10a7-dd4b-8e56-150cc349e623\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f5ea03a5-2acc-3086-6661-5539130fafe3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"8d3c759b-46f9-939c-902d-b11e18c85640\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.5\",\"z\":\"0\"}],\"uuid\":\"df30836b-251f-82da-81d7-b8e67c48534e\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"8d8181fe-2f28-965b-6a40-cbb1fbf9872e\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.6733365898\",\"y\":\"9.6173935822\",\"z\":\"4.1728100336\"}],\"uuid\":\"5b631dfe-f46f-3d38-49ab-7c138ab3d9e8\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.9373275637\",\"y\":\"19.5663908517\",\"z\":\"5.2167003589\"}],\"uuid\":\"d7c163b0-5fed-a9c8-322f-f1358e30fa7c\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"11.1507296471\",\"y\":\"24.5386408206\",\"z\":\"5.7845420461\"}],\"uuid\":\"41d820f2-fe7e-76ec-4ea5-83eda9bac168\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.7710390435\",\"y\":\"-19.7205168855\",\"z\":\"-2.5682502902\"}],\"uuid\":\"584f2e37-9bf5-3386-3d2e-bd1bc6086ccc\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"13.2710390435\",\"y\":\"-19.7205168855\",\"z\":\"-2.5682502902\"}],\"uuid\":\"a51a1766-1f3d-8988-50b4-7fc728b93c7f\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5.7686322235\",\"y\":\"-29.8742012582\",\"z\":\"2.8806590864\"}],\"uuid\":\"4b9148a2-f734-64f3-769a-a36daabd7748\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.3787161594\",\"y\":\"-47.2626005229\",\"z\":\"5.433274539\"}],\"uuid\":\"73d92e2d-f637-27d9-df51-5588d75375fa\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.7102375661\",\"y\":\"-52.1340621627\",\"z\":\"7.1775562076\"}],\"uuid\":\"ab6964e1-4250-21fe-1b8c-63ecd9d1bbbe\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-9.0158790017\",\"y\":\"21.8194246308\",\"z\":\"-7.0989933162\"}],\"uuid\":\"3730f41f-cff2-2a4b-2579-dfb6ffede68d\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-14.0158790017\",\"y\":\"21.8194246308\",\"z\":\"-7.0989933162\"}],\"uuid\":\"2489eec9-8732-9f85-3239-7515cef5033f\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e47353aa-4064-349e-ee79-94840880db87\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8e7165d1-5889-d88f-085b-ada0a74c47b7\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6a930b54-9396-600b-7072-2c19d5c1901c\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a736f3fd-eb5f-5bd1-6e0c-7de3e5ce4a76\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2916e978-c2fd-1ff4-2932-d9dffd2154e6\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.1937755234\",\"y\":\"64.2455098641\",\"z\":\"45.5178564722\"}],\"uuid\":\"d74af142-54ce-90c8-cbc6-bce2c3396947\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"80.1423824502\",\"y\":\"73.0563319589\",\"z\":\"100.4673277231\"}],\"uuid\":\"1afbd003-15e6-205f-be19-9864c2035905\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"55.9593666764\",\"y\":\"63.5254267789\",\"z\":\"74.0015760198\"}],\"uuid\":\"3696b7fa-ae6a-04fb-b173-61821aa06556\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-60\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bfb71df5-ee13-b7e5-3137-48e09eacb8ec\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-72.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"34ee0766-a837-e045-b206-23d8a3b9d6fe\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-32.5\"}],\"uuid\":\"911076a9-a54c-9b69-38b3-87590a8884b6\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3ae9bfc0-89d3-26c7-2d4a-48021345c7ad\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"-65\"}],\"uuid\":\"e9808c47-8709-b384-ddb5-267d720b3fa4\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"65\",\"z\":\"0\"}],\"uuid\":\"19fe472a-9e8d-a41e-4f14-e6393a8f1d6c\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-42.1862611809\",\"y\":\"55.2245633302\",\"z\":\"-47.8137388188\"}],\"uuid\":\"8d0fc26a-9901-62f8-40bc-c9f77ce0998d\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"62.5\",\"y\":\"-72.5\",\"z\":\"0\"}],\"uuid\":\"7a1ad48f-8618-38f8-f5d7-cb55c4ca9e7d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"132.4496748596\",\"y\":\"-70.3622198506\",\"z\":\"-167.1044344205\"}],\"uuid\":\"e6457e70-4684-6244-ade5-83974bb722e4\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"183.6473435932\",\"y\":\"-62.882775119\",\"z\":\"-165.6886667993\"}],\"uuid\":\"fe5ecf53-6ee1-1cd4-9d11-f04fbf5bdba5\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"69.0215502875\",\"y\":\"-61.7777128461\",\"z\":\"-50.5143631535\"}],\"uuid\":\"f8ae30c1-da2e-c946-afc7-534cf4cbe392\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.5215502875\",\"y\":\"-61.7777128461\",\"z\":\"-50.5143631535\"}],\"uuid\":\"794f1018-dec9-57b8-c197-77904accdac0\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"16.6017897738\",\"y\":\"-11.3133541722\",\"z\":\"33.3441109825\"}],\"uuid\":\"7ba320e6-56ee-83b0-0519-06b14938d274\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.0622974315\",\"y\":\"-4.2453013066\",\"z\":\"11.7678224621\"}],\"uuid\":\"dc7e6d1f-c7a3-11cd-8acb-384dd50d3cda\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1.8700552083\",\"y\":\"5.078545816\",\"z\":\"-14.1327227497\"}],\"uuid\":\"d5aee4bc-23dd-df08-52fe-95d7c5a19fbe\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"12.3064507049\",\"y\":\"4.897691423\",\"z\":\"-9.1143093073\"}],\"uuid\":\"168f3874-1de5-c2e3-95d5-59cbfc583d20\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"62cffffd-8f61-de42-9113-dd6e828a972e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-14.5108186991\",\"y\":\"-3.8409657163\",\"z\":\"-14.5108186991\"}],\"uuid\":\"993a0a10-1f13-7f73-e4e6-213fb22e252a\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-31.1497318285\",\"y\":\"-6.2796719244\",\"z\":\"-24.2476877954\"}],\"uuid\":\"cf6ece02-367a-e5c6-b54d-1c91304eed70\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-38.4268434977\",\"y\":\"25.4967141976\",\"z\":\"28.4834661178\"}],\"uuid\":\"05ec89f7-c93e-d091-6558-51a7b425bf61\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.686261181\",\"y\":\"17.3877183348\",\"z\":\"18.2489023831\"}],\"uuid\":\"581599ed-9106-f316-2859-a1ebb4c46754\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1.7430527801\",\"y\":\"17.9074425817\",\"z\":\"39.2405275584\"}],\"uuid\":\"f071053f-0601-1b5f-e367-d7fd0818b505\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"621973ec-d68a-5564-596e-d9cd80a4a0f6\":{\"name\":\"shield\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"5\"}],\"uuid\":\"cf2ad0bb-4211-41df-af74-f79e757fef2b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"24.2268205668\",\"y\":\"-5.6526197535\",\"z\":\"13.8547690382\"}],\"uuid\":\"81588c09-34c5-e0f0-4a38-688ff32acf07\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"24.23\",\"y\":\"-5.65\",\"z\":\"13.85\"}],\"uuid\":\"779e4f0e-5633-73da-ed00-d06be443b094\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.7824927505\",\"y\":\"-0.7771014437\",\"z\":\"-0.8689597174\"}],\"uuid\":\"0e11b7bf-d2c1-e003-0bf3-f186d2f5502a\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.7824927505\",\"y\":\"-0.7771014437\",\"z\":\"-0.8689597174\"}],\"uuid\":\"714e6d29-aa96-1be4-39be-e352922679e3\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.0175412737\",\"y\":\"2.349144295\",\"z\":\"0.8555298152\"}],\"uuid\":\"7d77986f-0a6e-979b-25b7-d8e1cb229d50\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.02\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"94873de5-bae7-2ae4-bf73-c02569337dcf\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.02\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"ffc19709-b900-6a98-a2de-45b060df351e\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.52\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"c8224408-7093-6a60-7cb0-705ee5d1e29c\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"436ebcaf-7570-8c3d-368e-80184e593a90\":{\"name\":\"right_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4a9bfe70-2191-670c-84be-64863e24150e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"20b23c03-80f9-5165-1a38-119818e01545\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7ddbfeab-7e8c-525c-8981-e0a400bf1a34\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-26.8445693673\",\"y\":\"-25.265343289\",\"z\":\"3.2804683392\"}],\"uuid\":\"4e24d6b8-b0de-73d3-8759-947bd05753e9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.8445693673\",\"y\":\"-25.265343289\",\"z\":\"3.2804683392\"}],\"uuid\":\"ea344538-76f9-883c-dae4-5f884e250bba\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-39.3445693673\",\"y\":\"-25.265343289\",\"z\":\"3.2804683392\"}],\"uuid\":\"28c1fc67-fb29-812d-982c-2ff9fa2e3726\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.2773272072\",\"y\":\"4.0215500522\",\"z\":\"-6.3358608564\"}],\"uuid\":\"475b96f5-e83d-f59c-486d-259b42ea531e\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"19.7773272072\",\"y\":\"4.0215500522\",\"z\":\"-6.3358608564\"}],\"uuid\":\"8254f5d6-49be-c3aa-7086-d531d03888dd\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.342303908\",\"y\":\"2.5586821925\",\"z\":\"-7.0523929293\"}],\"uuid\":\"171c02bb-b3ad-aac7-b9ff-9fbecf1de0f8\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.342303908\",\"y\":\"2.5586821925\",\"z\":\"-7.0523929293\"}],\"uuid\":\"538156c8-36da-6c4e-ab53-8fc2cd859422\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.34\",\"y\":\"2.56\",\"z\":\"-7.05\"}],\"uuid\":\"fe5e9a50-6505-eefc-2a3f-e1cf8cd4edf1\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"19.84\",\"y\":\"2.56\",\"z\":\"-7.05\"}],\"uuid\":\"6adf892a-a4ae-e27c-79a2-4b7f48e0e009\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"9.7824020671\",\"y\":\"1.7555897191\",\"z\":\"-7.3111717268\"}],\"uuid\":\"7ff94733-d327-a447-2899-a10026bdbba4\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c942401f-8d13-b8e4-8a4a-173d0e84ed99\":{\"name\":\"left_feet\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cff4e154-7491-0984-60a5-eac79e4b1bab\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"326b623b-2b43-6c49-d977-4095567cecf0\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"be24df1f-f7e3-c63b-fec5-20ae98caeef9\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7092b32c-17a5-8b3d-6d83-3ba0b6bbb5f6\",\"time\":0.83333,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"b2a515a2-421b-228b-0674-610e4526b0f3\",\"time\":0,\"color\":-1,\"uniform\":false,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"829f03e6-f064-6f0a-a12f-098251a8ee6e\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"db30d203-8b09-6d1c-5cf7-67f604f949d6\",\"time\":0.66667,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6aead5b7-6b9a-3567-5b7d-ea9757392256\",\"time\":1,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"2\",\"y\":\"2\",\"z\":\"2\"}],\"uuid\":\"0c42d890-1644-e968-a462-29d0f7f7d316\",\"time\":0.83333,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"}]}}},{\"uuid\":\"68929995-de66-3381-35d2-c6647ca6e67d\",\"name\":\"hammer_attack_3\",\"loop\":\"once\",\"override\":false,\"length\":2.25,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1092217464\",\"y\":\"-4.9976190427\",\"z\":\"-0.0023854959\"}],\"uuid\":\"5419dcdb-10a7-dd4b-8e56-150cc349e623\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.11\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"d0f1225e-ba33-daa3-0967-632cefc0ea9b\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.6474230458\",\"y\":\"-4.9238497548\",\"z\":\"-0.870384675\"}],\"uuid\":\"1f4f41a8-43b6-328e-6bb7-d2029414fa15\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.65\",\"y\":\"-4.92\",\"z\":\"-0.87\"}],\"uuid\":\"e3c1ae52-6f22-b758-b05f-a1d0139b6803\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"97241568-f93f-1e8c-ecc9-c8790855e6b9\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.3907782536\",\"y\":\"-4.9976190427\",\"z\":\"-0.0023854959\"}],\"uuid\":\"88714e3a-5fe9-7e4f-6583-3c9833558bbe\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.27\",\"y\":\"-5\",\"z\":\"0.06\"}],\"uuid\":\"0f4dddc2-dfb8-403b-9946-5b7f8e4e528e\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10.15\",\"y\":\"-5.01\",\"z\":\"0.09\"}],\"uuid\":\"6da355c3-1780-f742-e6f2-6d6049c2eb93\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4c8660c3-4d74-64fd-1d0f-f49dd9ec198d\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.5\",\"z\":\"0\"}],\"uuid\":\"df30836b-251f-82da-81d7-b8e67c48534e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.5\",\"z\":\"0\"}],\"uuid\":\"ef5008f2-5d53-a29f-cfb1-6773b9c23368\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2.5\",\"z\":\"0\"}],\"uuid\":\"cf1a90c9-f65d-609c-cd90-2a8a4bebc073\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3.5\",\"z\":\"0\"}],\"uuid\":\"d2f276cc-9f5d-b832-38ad-ba0bc1a99a1f\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3.5\",\"z\":\"0\"}],\"uuid\":\"c61ffef9-abab-b6ea-1f84-4ad8158d4976\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-0.5\",\"z\":\"0\"}],\"uuid\":\"dcb15320-11dc-ff08-49d4-ed8495527e6d\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"13.2710390435\",\"y\":\"-19.7205168855\",\"z\":\"-2.5682502902\"}],\"uuid\":\"a51a1766-1f3d-8988-50b4-7fc728b93c7f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.7710390435\",\"y\":\"-19.7205168855\",\"z\":\"-2.5682502902\"}],\"uuid\":\"11052c0f-2480-f2f3-8eb9-498d8c51a9d7\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5.9934708057\",\"y\":\"-4.2747370108\",\"z\":\"2.7150195278\"}],\"uuid\":\"11c940c3-1427-2168-a941-9f5a29690e04\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10.9644846511\",\"y\":\"-19.7205168855\",\"z\":\"-2.5682502902\"}],\"uuid\":\"c3809dc2-dc2b-8271-10ba-5b4f39cbbd4c\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-11.8746557255\",\"y\":\"9.6866902376\",\"z\":\"4.5488344014\"}],\"uuid\":\"f0f40104-422a-824e-69ec-d362da3e04ad\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.2254119804\",\"y\":\"-4.4280254989\",\"z\":\"7.9986462362\"}],\"uuid\":\"19efc726-2c51-fb26-e988-9708cbee9e2a\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"33.2926333821\",\"y\":\"-11.8214595291\",\"z\":\"-3.6332869614\"}],\"uuid\":\"b860f581-ee54-ac34-059f-6cac99cdbeff\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"33.29\",\"y\":\"-14.32\",\"z\":\"-3.63\"}],\"uuid\":\"40eb45e7-4d96-2308-16d4-adcf99c1863b\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"43.29\",\"y\":\"-14.32\",\"z\":\"-3.63\"}],\"uuid\":\"9eef37bf-699e-24fc-42ba-a69852b8042f\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.9060405348\",\"y\":\"0.9816687976\",\"z\":\"-0.0251436091\"}],\"uuid\":\"8ea26349-b439-1823-3fa9-0455b21d3401\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-14.0158790017\",\"y\":\"21.8194246308\",\"z\":\"-7.0989933162\"}],\"uuid\":\"2489eec9-8732-9f85-3239-7515cef5033f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-3.0281240726\",\"y\":\"22.4040808767\",\"z\":\"-4.4753047279\"}],\"uuid\":\"c0adc41e-db2d-78a5-b9b9-43d9ed9b4838\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"19.4718759274\",\"y\":\"22.4040808767\",\"z\":\"-4.4753047279\"}],\"uuid\":\"42d31442-70b9-a55c-99e4-e7f4a2db8db6\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"11.9718759274\",\"y\":\"22.4040808767\",\"z\":\"-4.4753047279\"}],\"uuid\":\"0f4902fb-f80d-3d9e-e652-cdc907f56013\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1.9718759274\",\"y\":\"22.4040808767\",\"z\":\"-4.4753047279\"}],\"uuid\":\"2077bbd7-27ff-f1cc-db12-f2495a9f4319\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-8.03\",\"y\":\"22.4\",\"z\":\"-4.48\"}],\"uuid\":\"e8dc0cb3-5776-b082-c493-df41b1a9adab\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-3.03\",\"y\":\"22.4\",\"z\":\"-4.48\"}],\"uuid\":\"8acf989a-892d-757e-df9c-3420adc48d7a\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.03\",\"y\":\"22.4\",\"z\":\"-4.48\"}],\"uuid\":\"b4bd0a4a-bc68-433e-76a6-24761c62df78\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-3.03\",\"y\":\"7.4\",\"z\":\"0.52\"}],\"uuid\":\"d83ee307-1871-0082-1f39-d161aee2687b\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2916e978-c2fd-1ff4-2932-d9dffd2154e6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ae26a00d-d136-03b1-41fe-59dfee4dce75\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9370ace8-772e-e156-5e99-f6c3b61217db\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7987262d-ced0-a5f9-d03b-3b34ad78349e\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fbab7eb6-22bf-07fa-8cbd-48498916e252\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ffa33c7f-1a8a-6f0a-4d95-04c032d39e13\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"07ddc4c6-648a-4e75-20ea-5994b7ddf947\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"90\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6d2dd852-7c8e-6f70-fbf1-57f421cedd66\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bbcebcdf-c37b-06c3-50ec-af598fec6431\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e611f0ce-7104-2158-d034-c65a45490c99\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b9eff2f4-042f-5cdd-e302-16bd68c3f8f8\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-72.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"34ee0766-a837-e045-b206-23d8a3b9d6fe\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-127.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0b9a6a49-f395-94f1-8312-a532f8b9b6f3\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-127.9244467882\",\"y\":\"-7.9185000792\",\"z\":\"6.1267309018\"}],\"uuid\":\"27ebc863-901a-b6ab-5065-5c8bc4ea5d1e\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-137.9244467882\",\"y\":\"-7.9185000792\",\"z\":\"6.1267309018\"}],\"uuid\":\"f797a277-ccfd-db57-d9b2-01e5fa9d9c08\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-178.0233407839\",\"y\":\"3.3225807566\",\"z\":\"16.9537500901\"}],\"uuid\":\"b4a1b140-65f2-c832-e8b2-bbb1fa048581\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-128.1204456239\",\"y\":\"-9.2593718846\",\"z\":\"2.5980158114\"}],\"uuid\":\"701050d2-358e-358e-e193-6f374beb5952\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-52.2962837048\",\"y\":\"1.7610109902\",\"z\":\"-29.3488032387\"}],\"uuid\":\"ca9fedd5-30f9-2b06-6f41-c2148eb9f502\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-52.2962837048\",\"y\":\"1.7610109902\",\"z\":\"-29.3488032387\"}],\"uuid\":\"216d527b-699e-90fb-1998-4d7aaa8eb5c3\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"f7a3336b-809e-0966-af6d-7d9156208a4e\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-42.1862611809\",\"y\":\"55.2245633302\",\"z\":\"-47.8137388188\"}],\"uuid\":\"8d0fc26a-9901-62f8-40bc-c9f77ce0998d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-44.7185386925\",\"y\":\"53.5046204069\",\"z\":\"-50.9288531115\"}],\"uuid\":\"25a5fe4a-6165-24eb-15b8-c3473940ad5b\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50.8894896827\",\"y\":\"47.9365935671\",\"z\":\"-58.8864388912\"}],\"uuid\":\"e48f7ba2-fc70-464f-a01f-b9701fd480d6\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15.6376942806\",\"y\":\"63.9686690589\",\"z\":\"-17.3026653259\"}],\"uuid\":\"fa1e5c2f-41a4-adef-6e4e-bf9f58f128a4\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.2832219061\",\"y\":\"39.8578808548\",\"z\":\"-45.6531679612\"}],\"uuid\":\"3d99cde7-c9e8-afa3-bad1-5738a704aba1\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-29.9566499018\",\"y\":\"59.9476088103\",\"z\":\"-30.410358294\"}],\"uuid\":\"c09f05f4-a465-a87d-fec0-fde8e43e67b1\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-41.2009622532\",\"y\":\"49.2853322068\",\"z\":\"-40.6851409241\"}],\"uuid\":\"ff8f58bf-4c5a-7284-a864-80b38665cbe5\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"32e6287e-adf4-e930-1c15-9ae9be48a01e\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.5215502875\",\"y\":\"-61.7777128461\",\"z\":\"-50.5143631535\"}],\"uuid\":\"794f1018-dec9-57b8-c197-77904accdac0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"86.9513138835\",\"y\":\"-73.8651018995\",\"z\":\"-75.0888002117\"}],\"uuid\":\"da77cdcb-7ad1-5c0b-a29a-998b0664fb05\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.2353527438\",\"y\":\"-51.1150674833\",\"z\":\"-9.2501158675\"}],\"uuid\":\"44f36723-c0f8-ff49-a41b-6de828531f36\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"68.7353527438\",\"y\":\"-51.1150674833\",\"z\":\"-9.2501158675\"}],\"uuid\":\"49bd31d3-fafa-ddf2-5986-112e32336c03\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"95.1074979228\",\"y\":\"-54.0308419195\",\"z\":\"-42.1153172543\"}],\"uuid\":\"ddac85ce-dbe0-297a-9897-31b05308c743\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"80.4922695548\",\"y\":\"-55.944438858\",\"z\":\"-54.7759816792\"}],\"uuid\":\"8516fe30-1a65-1ade-3093-26eb5d9cfd23\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"58.8705045819\",\"y\":\"-22.8693080051\",\"z\":\"-38.4385602338\"}],\"uuid\":\"4c2198de-9c28-73f4-7468-ddfbc75cbec6\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.8701229845\",\"y\":\"-19.5470956865\",\"z\":\"-31.4164080838\"}],\"uuid\":\"455b252f-766d-2a54-0166-8e3ed51a2687\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"56.8554393194\",\"y\":\"-14.7598210701\",\"z\":\"-23.6078658164\"}],\"uuid\":\"e89a29ef-88a7-45e8-a4aa-c0c2416fba4c\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.8701229845\",\"y\":\"-19.5470956865\",\"z\":\"-31.4164080838\"}],\"uuid\":\"b23209e2-b71c-4aec-7418-94be4949d493\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"125f6596-2eb7-55af-69eb-8677927fe476\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"65\"}],\"uuid\":\"6750bd21-6ca9-0af1-1990-691231ec7d5e\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7ba40dd8-629c-ba37-7163-1d70e0fd4649\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"57.5\"}],\"uuid\":\"02008661-ad77-9147-214e-d2edce0f9e65\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"75\"}],\"uuid\":\"4a85b6ff-c083-4ee4-20f3-64284b8ff8c7\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"75\"}],\"uuid\":\"7de32ba5-4c82-5d29-4d8f-b2e7c88fc615\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3c3b5d3f-20cc-696a-f796-069f871b7185\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8cc1ae4b-a887-d93e-a134-48eb5015515c\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3a5a200e-5b23-de6e-f6fc-1503688a765c\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8ef8ba69-28fb-de6e-2705-59239bd37edb\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"62.0092548231\",\"y\":\"-0.4316912615\",\"z\":\"-4.7675517875\"}],\"uuid\":\"bfcf3c88-9ae6-cf4d-4815-cc1253d64802\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0128b596-c461-0be0-3e87-ae99c43616b7\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"71.7180308119\",\"y\":\"-0.4920358582\",\"z\":\"-6.9015946924\"}],\"uuid\":\"9f1fec70-bfe8-521c-82fc-95c8bf0100c4\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"83.5952619136\",\"y\":\"-0.2620600682\",\"z\":\"-10.8773772113\"}],\"uuid\":\"1fb1e754-10db-852f-dd4e-847cfa05f47e\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.5331663206\",\"y\":\"19.3369807941\",\"z\":\"6.474997315\"}],\"uuid\":\"0f898036-bd46-54c8-2922-1bf7e71b61bd\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ce0a2261-dc9a-b477-2b1d-8c924a13c61c\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"77766224-4c56-ead7-8336-ff23a97eb3d9\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8941e3e9-30df-496b-1a44-9aa82008a633\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f1037af0-08b6-96ab-b8a9-9f604591d5e2\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-59.419604523\",\"y\":\"4.4112625392\",\"z\":\"1.8609367826\"}],\"uuid\":\"e75f6bf7-92b2-ff34-6b1e-d895fea1ffc7\",\"time\":1.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"515c5559-b19e-1a6c-d194-29a0226d431f\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.1345380161\",\"y\":\"0.0343648853\",\"z\":\"0.9349334418\"}],\"uuid\":\"bf173ded-0269-b11a-89a0-579adb2ed085\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-35.561839418\",\"y\":\"-2.6973273112\",\"z\":\"-0.2733372638\"}],\"uuid\":\"5c803596-624f-2feb-5737-43fb585c8364\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-35.561839418\",\"y\":\"-2.6973273112\",\"z\":\"-0.2733372638\"}],\"uuid\":\"edfe0c9e-41db-a123-d6aa-bc3fbf3b6c9c\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"75992dfc-ca12-468b-4ad8-22a335c6c870\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.7824927505\",\"y\":\"-0.7771014437\",\"z\":\"-0.8689597174\"}],\"uuid\":\"714e6d29-aa96-1be4-39be-e352922679e3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5.2824927505\",\"y\":\"-0.7771014437\",\"z\":\"-0.8689597174\"}],\"uuid\":\"027f3663-0314-bb9e-f17f-4909219a363b\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"9.7175072495\",\"y\":\"-0.7771014437\",\"z\":\"-0.8689597174\"}],\"uuid\":\"8c4217d6-5580-f9f2-1db1-14c195fe823d\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2175072495\",\"y\":\"-0.7771014437\",\"z\":\"-0.8689597174\"}],\"uuid\":\"ce372cd1-74cf-c009-45b7-ccfd6f7500ae\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.22\",\"y\":\"-0.78\",\"z\":\"-0.87\"}],\"uuid\":\"06fe5ad8-49b5-c0c3-1481-c840cf8d0c97\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.0854773535\",\"y\":\"-2.0710910703\",\"z\":\"-4.3046531819\"}],\"uuid\":\"fe4b2b06-210e-6818-9182-6a8199343c06\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.9257249471\",\"y\":\"-6.3276235401\",\"z\":\"-5.592924586\"}],\"uuid\":\"36366c3a-ac04-5c04-e1b2-0666166ddd82\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.9257249471\",\"y\":\"-6.3276235401\",\"z\":\"-5.592924586\"}],\"uuid\":\"ffc459e4-6cc4-6c5e-a7aa-011aa2bfb47a\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-19.57\",\"y\":\"-6.33\",\"z\":\"-5.59\"}],\"uuid\":\"0942847c-6696-72b8-0627-1047c824d420\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c2783087-740b-9efa-26a8-a97cebef2d46\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.52\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"c8224408-7093-6a60-7cb0-705ee5d1e29c\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.52\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"52df1888-d866-d09c-1bdd-97264544ffef\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"50.02\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"b7d624cd-9b83-5246-29fe-f8c589aea371\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.02\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"f7cf5d1f-76a7-2c2b-cb64-ee48f34f975e\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.02\",\"y\":\"2.35\",\"z\":\"0.86\"}],\"uuid\":\"87bace8c-7d34-8ab1-fab8-b8c16aaf77e0\",\"time\":1,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.4306519248\",\"y\":\"2.512107265\",\"z\":\"-0.4054368633\"}],\"uuid\":\"92aece4b-e73e-5faf-344f-0100549a4270\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"46.9944148989\",\"y\":\"3.877620433\",\"z\":\"-3.708398832\"}],\"uuid\":\"1e047db9-0f4d-4fcd-4285-878e216fe2ac\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"71.9944148989\",\"y\":\"3.877620433\",\"z\":\"-3.708398832\"}],\"uuid\":\"89c102f5-3f1c-f870-7620-8fd3190806be\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"29.49\",\"y\":\"3.88\",\"z\":\"-3.71\"}],\"uuid\":\"3eb6b73d-3fc8-486d-f466-8614d2b892b3\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"dc2edf12-1919-0c47-f85b-ccd8f5180958\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"19.7773272072\",\"y\":\"4.0215500522\",\"z\":\"-6.3358608564\"}],\"uuid\":\"8254f5d6-49be-c3aa-7086-d531d03888dd\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.6544374225\",\"y\":\"3.9742628642\",\"z\":\"6.1946635266\"}],\"uuid\":\"0cc840b0-5edb-34d7-050d-460146350550\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-21.8455625775\",\"y\":\"3.9742628642\",\"z\":\"6.1946635266\"}],\"uuid\":\"d761792a-955d-9859-62a0-18780da551e4\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-9.3455625775\",\"y\":\"3.9742628642\",\"z\":\"6.1946635266\"}],\"uuid\":\"56a0a4f9-80c4-7617-13ba-48bb31647ea8\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-44.3045028717\",\"y\":\"4.8460261594\",\"z\":\"6.5670128269\"}],\"uuid\":\"7270293e-9aae-244f-2f12-c6bbf41813d7\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-56.8001045578\",\"y\":\"5.1896248202\",\"z\":\"6.563506006\"}],\"uuid\":\"ab789810-81de-abf7-0628-4afac167871c\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-61.8001045578\",\"y\":\"5.1896248202\",\"z\":\"6.563506006\"}],\"uuid\":\"65f926cb-8824-30c2-0531-f99a34cc9007\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-31.8\",\"y\":\"5.19\",\"z\":\"6.56\"}],\"uuid\":\"bdb13ba5-e132-92ff-8c5c-91f628a8b80d\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e6bd6ecd-3205-7c82-d745-afaf885a3e01\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"9.7824020671\",\"y\":\"1.7555897191\",\"z\":\"-7.3111717268\"}],\"uuid\":\"7ff94733-d327-a447-2899-a10026bdbba4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.2824020671\",\"y\":\"1.7555897191\",\"z\":\"-7.3111717268\"}],\"uuid\":\"dba230b0-7807-c71f-8414-e2f10e993710\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.2824020671\",\"y\":\"1.7555897191\",\"z\":\"-7.3111717268\"}],\"uuid\":\"06bc60ab-4def-54b8-6b4c-6560b2fc6268\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"9.7824020671\",\"y\":\"1.7555897191\",\"z\":\"-7.3111717268\"}],\"uuid\":\"1c7d220d-2ccf-b1a3-929e-260ad7b9a3d3\",\"time\":0.79167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"37.2885661048\",\"y\":\"2.291634426\",\"z\":\"-6.9105191417\"}],\"uuid\":\"59a67d47-dd2b-98f8-ec90-9f814a68cff8\",\"time\":1.20833,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"42.2910410363\",\"y\":\"2.3655123276\",\"z\":\"-6.8126584981\"}],\"uuid\":\"752161ac-c921-9c53-681c-a6d41b4bf3eb\",\"time\":1.29167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"54.7910410363\",\"y\":\"2.3655123276\",\"z\":\"-6.8126584981\"}],\"uuid\":\"8e6ddb58-25f0-a57d-8271-35e014a24eb0\",\"time\":1.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"29.79\",\"y\":\"2.37\",\"z\":\"-6.81\"}],\"uuid\":\"ee809423-dae6-ba11-c322-457825be6017\",\"time\":1.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f76eb944-e985-a29c-2c7f-4e1975efa04b\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"766720b6-ef18-a906-fe7c-91cec9d74c1e\",\"time\":0,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"631db0ee-affe-ab1f-d8d2-bd817de1bcae\",\"time\":1.125,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"1\",\"z\":\"1\"}],\"uuid\":\"518284dd-d754-d4c9-c282-8a06fbc547c1\",\"time\":1.20833,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"2\",\"z\":\"1\"}],\"uuid\":\"2ec267ed-686c-0c6c-f5c2-dca2b30c2694\",\"time\":1.29167,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"1\",\"y\":\"0\",\"z\":\"1\"}],\"uuid\":\"83c5925e-251f-d60a-1ab4-f8bd8ff3b4c5\",\"time\":1.5,\"color\":-1,\"uniform\":false,\"interpolation\":\"linear\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e1751804-1587-d53e-510c-119440896dd5\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"}]}}},{\"uuid\":\"278631b6-ea97-aabe-b020-7abbb28d5986\",\"name\":\"shield_attack_1\",\"loop\":\"once\",\"override\":false,\"length\":1.25,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9aaeddf8-969e-96ec-499b-e177f3ca9bdd\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f9e80793-6624-05b4-105c-257503d29e7e\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cb8f4bf7-c06a-b918-ee5c-2e4b7170c95f\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1ed31989-274f-9c7e-b970-15726691e047\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"dea0f803-3228-b1d7-1079-45f536051fbb\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-85\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"489bee7f-cb6e-d8d8-d87e-4e53de84239d\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-85.7312453482\",\"y\":\"2.509721274\",\"z\":\"-46.1135416426\"}],\"uuid\":\"507cd94b-6004-036e-4485-01cc973ab316\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-85.7312453482\",\"y\":\"2.509721274\",\"z\":\"-46.1135416426\"}],\"uuid\":\"f8855233-7dda-2590-7b61-ab8f71530006\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5fae9431-7c30-479b-a141-8afd7b912528\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7ccc4a6c-0c15-665b-3118-81eb0d6d7bfc\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"9c6ef684-aad2-7864-73e0-9ae4d53e5878\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"50\"}],\"uuid\":\"db56b344-e93a-8f36-d40a-6e6489c2a2e8\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2c4a5034-e8aa-1479-eab7-bfdf5f817950\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"-10\",\"z\":\"0\"}],\"uuid\":\"da6aa0f0-ef10-59f8-b704-a2f0f83289cf\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.8674260512\",\"y\":\"32.7690834759\",\"z\":\"14.528800838\"}],\"uuid\":\"348df97a-6aaf-ae86-cc2f-deba55b8c3d5\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"51adbc86-d17e-650f-5bd3-5a1d8628b7a0\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"22e9dcff-6d62-025e-d201-7b315f096fd0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"26eb55a4-bfde-c9d0-95c9-a69453b52429\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"de9d485e-dab5-8181-4935-9f26a595ed51\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"5285314c-5162-09c4-5484-49628cf0a3ca\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-22.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"05e82c1a-5979-7d52-5be2-369ab8643cda\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.5\",\"y\":\"27.5\",\"z\":\"17.5\"}],\"uuid\":\"fc917305-0287-b8b8-5649-587928e07692\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.5\",\"y\":\"27.5\",\"z\":\"17.5\"}],\"uuid\":\"7ccd1019-3235-dee8-b3ae-2c71892d7f92\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"ec847310-4f84-e7d7-d766-fb53ab5849aa\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"492109e8-e3d2-65d6-5b0c-681b79d2debe\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-43.1891941881\",\"y\":\"-41.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"3cbfe7b4-1976-b18f-bfee-61edf32073c1\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-43.1891941881\",\"y\":\"-41.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"82699b8e-1eec-f4f1-3e25-9c8b1093a340\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f3ce685d-3702-b666-1d52-f2c3dbb7a967\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"346de168-6db2-96ee-3256-63587cb1baf3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a9fed4ad-3e38-46da-fe48-276bf92818ae\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-110.3703136516\",\"y\":\"-40.5010889301\",\"z\":\"20.6863595327\"}],\"uuid\":\"c63a6b87-e980-b2cf-2243-2069b5ba2e10\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-70\",\"y\":\"2.5\",\"z\":\"47.5\"}],\"uuid\":\"4785fe2c-5b6f-1e75-123b-eec62343bff6\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5371e06c-b54b-154b-b1c7-cfce2a9b20d4\":{\"name\":\"right_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cc9bae98-3244-1324-0c0e-d3dd2ecf8ad0\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"3a05cd31-c803-0a4c-716f-337c916a2ee5\":{\"name\":\"left_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"892e5c98-765f-aa7a-ea28-f13f7ae43c9a\",\"time\":0,\"color\":-1,\"interpolation\":\"linear\"}]},\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5b120357-d3e7-3f99-af0e-5a40659e76e5\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"-20\",\"z\":\"0\"}],\"uuid\":\"09cd0ad4-ab0e-9580-2da6-78dbb3407c58\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"12.5\",\"z\":\"0\"}],\"uuid\":\"91700c73-6d2b-d3ae-1b90-210a6e6ff0db\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"12.5\",\"z\":\"0\"}],\"uuid\":\"35aab2f7-88f1-f91c-6a34-e484bf4fabdc\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f904d334-10b2-a2a1-b90e-7f22c8409e28\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"29815afa-d487-9580-ce8f-3de867ea32a9\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3\",\"z\":\"0\"}],\"uuid\":\"9c9ce216-9bd0-89fb-df3b-62167ab42e10\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3\",\"z\":\"0\"}],\"uuid\":\"932ea191-fbc2-d364-73e9-d05bdc3315b5\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cb886698-fb4c-9055-5729-e52f5c72c77f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"95f80753-e55c-3b7c-bf53-b7769cb84a8f\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"-10\",\"z\":\"0\"}],\"uuid\":\"ae9bb722-adfc-5f39-e19f-713b29b399cd\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"-10\",\"z\":\"0\"}],\"uuid\":\"de1e6170-350f-a3e0-dfe4-0e164fde910f\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"08fa9aea-2f3a-ad98-9647-65d992767b55\":{\"name\":\"left_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"accba002-32fb-d416-bd9a-332895956d14\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cdd63cc4-92fe-bccd-2a10-112b927e9181\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-72.48030955\",\"y\":\"-50.8350819403\",\"z\":\"88.5344358877\"}],\"uuid\":\"df2306ab-e8e3-c85a-e8f9-86f0b3b0c345\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"22.5\"}],\"uuid\":\"c865a1ea-1c13-8071-86cc-2f614ee83406\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"621973ec-d68a-5564-596e-d9cd80a4a0f6\":{\"name\":\"shield\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ac932628-12e7-ab77-0349-9f001b096922\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6fadd96e-eaf5-033d-7436-9edadbf463c7\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.7450989839\",\"y\":\"3.6869834395\",\"z\":\"59.7113435627\"}],\"uuid\":\"a4cad7a8-8204-780e-5891-7038456a8949\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.7450989839\",\"y\":\"3.6869834395\",\"z\":\"59.7113435627\"}],\"uuid\":\"902bc1d6-7047-cda6-fe52-1345084e33dc\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"81c98f4b-5ade-166e-be9a-8cbee4eddc3a\":{\"name\":\"front_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7949e715-0052-8077-3d65-a2d9e8615439\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bf889c9b-01ef-50a9-076e-351c81b6f3b4\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0b74d257-b19a-7279-ef7c-08f7a2a0814b\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4ffce021-dcca-9b55-bf39-5918b08eb91c\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"05b3e52d-6142-bd4c-f19b-18c831466b1c\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"829d0c77-bf21-ccf2-0f06-c4f1310e55c6\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d0f7b7d0-2fdb-08b5-eb30-05f7d9d58a87\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e4736043-be0c-cae6-3ef2-6160a8bccb1f\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"eb502aed-a9ef-5add-ff01-ddaeff23efdc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0caf1631-e726-c826-1302-5468c9542adf\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"38cab1f7-c219-4596-3c62-f1bb8d7c9300\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e05604bb-d2c8-c8f1-44b4-2a1c7981f668\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0698b021-dae2-4205-7f11-32c9c689293f\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5b44b36f-9b2b-2656-e9c8-822e37140667\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7e282a11-9070-f49b-2f94-fea84a93b2fd\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"947c81dc-763a-9e6f-02a7-c201f88b866c\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7a656745-dcf1-4a7a-c5cc-d29a586ffd49\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d80f3713-3f15-8a74-2112-93cce2f0b62a\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d77e07e4-f3c2-cdf8-9b7b-9eb570ffdb20\",\"time\":0.54167,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"815d4e79-bae7-6ba8-aa59-2c9d202f371b\",\"time\":1.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"64329f10-02a0-0046-020a-75f5d0e2528f\",\"name\":\"shield_attack_2\",\"loop\":\"once\",\"override\":false,\"length\":2.25,\"snapping\":24,\"selected\":false,\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"c5eedd4c-1c07-3567-ca9a-1d47e13b8a95\":{\"name\":\"cloak\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4cce6b4b-729e-b202-76c7-d545c951ca43\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f35dd944-fc43-3bbd-5e5b-d3573cf07d4d\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"156e98f5-d57a-9abe-f501-36ec6d176cae\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a17a67fd-7b6d-6962-b8a1-40b85e433c3c\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"45\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b3f0e60d-db3a-5e36-00e6-05551de70020\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"07297c90-9e98-a6c4-66bc-c1c968bfca92\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"53f038db-6138-27bd-02d3-c6e5d077a1d2\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a512dfcd-47cd-1831-ffcc-611e43a564d9\":{\"name\":\"right_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-85.7312453482\",\"y\":\"2.509721274\",\"z\":\"-46.1135416426\"}],\"uuid\":\"1f9392bf-48cf-786b-8920-a075e6e8dfb1\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-85.7312453482\",\"y\":\"2.509721274\",\"z\":\"-46.1135416426\"}],\"uuid\":\"46b5461c-c66b-5e44-4e9a-5331406de8b4\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-103.23\",\"y\":\"2.51\",\"z\":\"-46.11\"}],\"uuid\":\"36a41a70-c9ff-8be7-0328-e37a73e2781e\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-75\",\"y\":\"0\",\"z\":\"-25\"}],\"uuid\":\"97ce081e-6aef-6ada-bd91-69504d07cebe\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-103.23\",\"y\":\"2.51\",\"z\":\"-46.11\"}],\"uuid\":\"70de3c33-8368-4f19-ffdf-9d1b14830429\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c1a7024c-c2d5-f677-558b-549f2d02cdbd\":{\"name\":\"left_sub_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"50\"}],\"uuid\":\"1f4d34c3-543f-0de1-84d3-259b8aba497e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8e0a9552-ca9d-4960-201d-8601e1fbd4f2\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"04610b3e-9c47-9c4e-bbb6-78da8de2852b\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-52.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"30a13d3e-45ee-a557-e294-cdd2a10676d9\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.4309112331\",\"y\":\"0.4673957558\",\"z\":\"1.2447027674\"}],\"uuid\":\"4fc4ae7f-c6d7-db82-41b0-4ea2632488ee\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"80fd2cd7-a062-a706-6209-57a3e3c7919a\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30.1670030659\",\"y\":\"0.7231543205\",\"z\":\"8.5484839094\"}],\"uuid\":\"293c3c83-af35-182e-f766-63070661d01a\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"788aa374-e94c-c383-6565-af12949da09e\":{\"name\":\"upper_body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"8feaadc2-d2df-a26f-5e72-12f1ffb746c8\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"9d957068-47d4-145c-bd64-1df921c011cf\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"a6ace2a0-5804-d2a4-3ec4-205a5cb832c4\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"5\",\"z\":\"0\"}],\"uuid\":\"95b62c9a-7f52-875d-8d49-91e96e0b21af\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"50.8227165896\",\"y\":\"13.3763646802\",\"z\":\"26.800620538\"}],\"uuid\":\"3b9898ed-9d5d-dce2-95ce-50a75ac48b1d\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"70cfec07-220c-0ff5-f558-161b8eba5bfe\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"35.8227165896\",\"y\":\"-14.1236353198\",\"z\":\"26.800620538\"}],\"uuid\":\"632564dc-2b4e-84fd-65f2-e40685d62e21\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"162c587c-e220-1d1f-273f-19ef6077bc40\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"6b1142e6-7581-b73e-4a67-e134ad4585c6\":{\"name\":\"vfx_slash\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"06f5ff61-3784-a42b-599d-f35578fd6e3e\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1fd8b4f5-f36a-533d-4613-8ff29fcf0828\",\"time\":2.25,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"f16e4292-9e8d-235c-1fc2-b1d02dd31366\":{\"name\":\"vfx_pierce\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d1a5e2a3-48ec-ed24-3e06-8f0df92631a7\",\"time\":0,\"color\":-1,\"uniform\":true,\"interpolation\":\"linear\"},{\"channel\":\"scale\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"34775f4a-1325-0779-3cb2-50ae577fa369\",\"time\":2.25,\"color\":-1,\"uniform\":true,\"interpolation\":\"catmullrom\"}]},\"8909e1fb-858b-2f76-7079-1923d78bfb4b\":{\"name\":\"right_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.5\",\"y\":\"27.5\",\"z\":\"17.5\"}],\"uuid\":\"91e49ccc-fe06-a45d-a0ee-fb8ec13e85f6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.5\",\"y\":\"27.5\",\"z\":\"17.5\"}],\"uuid\":\"f5cae43a-8ca1-2b3c-242b-2a85d0b6c113\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-55\",\"y\":\"27.5\",\"z\":\"17.5\"}],\"uuid\":\"4bbec5ff-9cf9-5177-f5e1-4e6963640cce\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.5\",\"y\":\"27.5\",\"z\":\"17.5\"}],\"uuid\":\"f73645da-31ab-1db3-9599-6ac3148ceb9b\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"17.5\"}],\"uuid\":\"1520d75d-b1c8-7a2b-828c-09a2795d4fa7\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"27.5\",\"z\":\"17.5\"}],\"uuid\":\"551592cb-4c7a-711b-eca3-3290ab32a0b7\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"dcf45c2b-cc07-3246-e68f-1319556212c0\":{\"name\":\"right_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-43.1891941881\",\"y\":\"-41.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"c53dcb8b-2f62-f929-3e01-3e16839e2e52\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-43.1891941881\",\"y\":\"-41.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"20f7d2e8-e9bd-bb80-9318-f52cd56cfdee\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.6891941881\",\"y\":\"-26.0646509681\",\"z\":\"24.2317823278\"}],\"uuid\":\"7453bd06-3947-8102-7d49-23bec9e0b74c\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"8f6061d3-ef98-da1e-5f4a-33b4ff2874f1\":{\"name\":\"hammer\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a47ba037-7059-cae3-820a-1a06208232df\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3af14301-1080-4f14-0934-4d145a50929d\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"28aa58e8-cc75-0593-0775-3690cbb1dd9c\":{\"name\":\"left_arm\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-70\",\"y\":\"2.5\",\"z\":\"47.5\"}],\"uuid\":\"35514051-5fa8-457d-17e6-7471bd968df5\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ca727ce7-b9d5-2e9a-e84b-a4cccfa81ebe\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-130\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"27970128-e0e9-fd73-4cef-5efccc959217\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-140\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1585e58c-3e94-1805-fd04-4e738dc4d1ad\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-45.540998577\",\"y\":\"-25.9440256268\",\"z\":\"-12.5519268326\"}],\"uuid\":\"3dd0f933-1d0d-ea30-a29c-8a7a5caa1cc0\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a6dafaa3-3e35-1f7b-1703-719c20bbe4cf\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.2017292565\",\"y\":\"-4.7672682729\",\"z\":\"0.5611677721\"}],\"uuid\":\"736c24c9-4186-7f03-fd86-7554bac562bc\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a633f85e-6f17-f4be-fc15-e8561725fc8d\":{\"name\":\"body\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"12.5\",\"z\":\"0\"}],\"uuid\":\"d97a9efc-fb72-2d03-5e40-a5a4a6ac042b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5\",\"y\":\"12.5\",\"z\":\"0\"}],\"uuid\":\"d855d5f7-b44a-8364-cdbe-f0e98f45f8fa\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"12.5\",\"z\":\"0\"}],\"uuid\":\"5d1a5505-55cb-9430-b0ea-cc754a94abc7\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"12.5\",\"z\":\"0\"}],\"uuid\":\"44a0d3b4-27d7-28b5-ff15-35e8fa66019d\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"12.5\",\"z\":\"0\"}],\"uuid\":\"8b754d68-6de3-3dc4-d563-9a5791b8b01f\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.1019253534\",\"y\":\"11.9127617839\",\"z\":\"-3.8139723926\"}],\"uuid\":\"a61e1172-b5e5-c32f-a779-6168e17b91ff\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4b093467-69f1-22e2-9ba5-7c9259ca9a2e\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3\",\"z\":\"0\"}],\"uuid\":\"c06ee386-a6e8-7c1c-dba0-a167a7572f08\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"a8e58795-463c-7890-7bd9-357eb23abecc\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"22\",\"z\":\"0\"}],\"uuid\":\"62ecf1f6-b6de-c3fd-0a86-ceecaad925bb\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-5\",\"z\":\"0\"}],\"uuid\":\"c2176fd8-7476-c979-7a48-ff077d41519b\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2\",\"z\":\"0\"}],\"uuid\":\"6c4d0e7d-04fa-7fd2-34eb-f4efee907e78\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5bfd368e-2c5d-b8ac-9617-f257151323d9\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"16.81\",\"z\":\"0\"}],\"uuid\":\"4aeb4c03-096b-6699-bb00-89a5785c1007\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e3e41a2b-7718-8509-a501-c13a87118853\":{\"name\":\"h_head\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"-10\",\"z\":\"0\"}],\"uuid\":\"25f29464-5d40-3025-d491-39dc29307ac4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"-10\",\"z\":\"0\"}],\"uuid\":\"db24203d-e173-098d-25d2-727613f59747\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"-10\",\"z\":\"0\"}],\"uuid\":\"635ded11-f5ab-0a5b-4cd2-f84ed1184d66\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-53.7147145384\",\"y\":\"-23.9403918278\",\"z\":\"14.1313484524\"}],\"uuid\":\"503a5cf9-b4f6-f2d9-d8cf-2bc7ec54a6d3\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.4554275644\",\"y\":\"0.4206975165\",\"z\":\"-15.2568188401\"}],\"uuid\":\"b7c9f7d8-db2d-5c73-2aee-c7a4e91646bf\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4d7e3a42-035b-42a0-456c-cedb90aa7009\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"08fa9aea-2f3a-ad98-9647-65d992767b55\":{\"name\":\"left_hand\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"22.5\"}],\"uuid\":\"4108e05a-0873-5acb-45b3-bf95519d3fc5\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-90\",\"z\":\"22.5\"}],\"uuid\":\"17006c9c-f43b-3186-a6db-206a97798532\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"-90\",\"z\":\"22.5\"}],\"uuid\":\"f5459780-9df5-c20c-cf4e-2c6fd9ace467\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"-85\",\"z\":\"22.5\"}],\"uuid\":\"05edd0a2-c26c-6784-8a80-c9741a28feae\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"-10\",\"z\":\"22.5\"}],\"uuid\":\"a2005296-d1a2-7bb4-b283-9ab16ff5269c\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0836c3d7-8701-9ea6-ae35-1edcce3ab7fc\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"621973ec-d68a-5564-596e-d9cd80a4a0f6\":{\"name\":\"shield\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.7450989839\",\"y\":\"3.6869834395\",\"z\":\"59.7113435627\"}],\"uuid\":\"4c0947c3-3917-0434-f6e0-506674a43e1e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.75\",\"y\":\"1.19\",\"z\":\"59.71\"}],\"uuid\":\"9eb60096-33f1-9883-6c5e-77742120f633\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.75\",\"y\":\"1.19\",\"z\":\"87.21\"}],\"uuid\":\"d6fc1a8a-097d-4ac8-0c56-7c42748d7560\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-28.69787198\",\"y\":\"-2.8660043568\",\"z\":\"32.0274704597\"}],\"uuid\":\"551437e9-07e7-0bbb-1d3b-9bdeb35b9fe2\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"76ae89ce-e4d9-9b1f-5c30-881ca954df9c\",\"time\":2.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"81c98f4b-5ade-166e-be9a-8cbee4eddc3a\":{\"name\":\"front_armor\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5fa55e7b-8762-8b5c-0322-14775d2d7bcc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9bbcfe86-35fe-5dd7-1fba-8d89aadd07f5\":{\"name\":\"right_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b11bd6c5-c01f-76e2-35f5-321ca26785de\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"355d15a3-0dcc-aa89-881d-21ce53f7b23c\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2cf44481-f502-ae24-046a-3da12b0fe27c\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4dce3c70-3b31-25a4-7bcc-8a0396600c9e\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-87.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"537f4db4-3b7b-0500-cb45-3690c7ceedf9\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9eda75a4-7db9-6c1e-9df2-a271be12d6af\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-41.1975167366\",\"y\":\"-4.1841018364\",\"z\":\"8.43806997\"}],\"uuid\":\"c7f90c84-0378-733c-8194-6bc8e160f51d\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"76bfe555-a273-5b15-5684-5779b6fddf7f\":{\"name\":\"right_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6d1d768c-34d4-a634-feb5-f277b4d1aeaf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a9b7c7e7-786c-31eb-e5a4-249e548ee6b4\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"42.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f2dbde6d-76ad-afbe-2f9f-52c6967f75e1\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"64.8506594855\",\"y\":\"-2.6331213387\",\"z\":\"-0.1715498031\"}],\"uuid\":\"0940ba3d-d217-5bff-5aa9-9756b63c3b6c\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"-2.6331213387\",\"z\":\"-0.1715498031\"}],\"uuid\":\"2659eeda-a2f1-e944-8e0b-7465bbbcb232\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25.1356479959\",\"y\":\"2.233482251\",\"z\":\"0.0756133347\"}],\"uuid\":\"49048782-5b44-a81a-837f-f0eceef600a6\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b2409710-4f5e-2feb-9aee-fcdb2d9320fa\":{\"name\":\"left_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"35995a1b-8c59-f78b-0227-772acead331d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6481733e-1df7-1392-5119-c690e32b9af0\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"be544dbd-d083-9ef4-148e-61a262ac1300\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6671222b-4d44-8002-42ef-05f90bd4ec53\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"75862e9a-da7a-772b-d39d-4632851fb942\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"13.4980418595\",\"y\":\"-4.90689418\",\"z\":\"-4.1197178199\"}],\"uuid\":\"68b897b6-1a51-529e-843b-8fe42cda8a73\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"d21a5df4-acaf-a697-c2f5-1e490af18700\":{\"name\":\"left_sub_leg\",\"type\":\"bone\",\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ecca4479-9be1-ef6b-1da5-0b70e4ab3c8e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"018c12a3-a241-8b52-3190-72547640c08a\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"649abd67-89c8-f7d3-d874-d78f7ceac15b\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8fca8ba5-63d6-4225-6ff3-f106676983a3\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e9d28f97-5ae6-1c53-e64d-8623aaf8d356\",\"time\":0.91667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"99d32996-b1ca-1954-4b35-2fc604bbcc74\",\"time\":2,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"34.8218133931\",\"y\":\"1.9560482195\",\"z\":\"1.0226853405\"}],\"uuid\":\"ebfa545c-a035-e8b2-5273-2cc2898849e0\",\"time\":1.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}}]}"
  },
  {
    "path": "core/src/main/resources/steve.bbmodel",
    "content": "{\"meta\":{\"format_version\":\"5.0\",\"model_format\":\"free\",\"box_uv\":false},\"name\":\"steve\",\"model_identifier\":\"\",\"visible_box\":[1,1,0],\"variable_placeholders\":\"\",\"variable_placeholder_buttons\":[],\"timeline_setups\":[],\"unhandled_root_fields\":{},\"resolution\":{\"width\":64,\"height\":64},\"elements\":[{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,11.25,-1.875],\"to\":[3.75,15,1.875],\"autouv\":0,\"color\":1,\"origin\":[0,11.25,0],\"uv_offset\":[16,24],\"faces\":{\"north\":{\"uv\":[20,28,28,32],\"texture\":0},\"east\":{\"uv\":[16,28,20,32],\"texture\":0},\"south\":{\"uv\":[32,28,40,32],\"texture\":0},\"west\":{\"uv\":[28,28,32,32],\"texture\":0},\"up\":{\"uv\":[28,28,20,24],\"texture\":0},\"down\":{\"uv\":[36,16,28,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ea42f7f7-a6f1-4479-c43f-48211bab5ed2\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,15,-1.875],\"to\":[3.75,18.75,1.875],\"autouv\":0,\"color\":2,\"origin\":[0,15,0],\"uv_offset\":[16,20],\"faces\":{\"north\":{\"uv\":[20,24,28,28],\"texture\":0},\"east\":{\"uv\":[16,24,20,28],\"texture\":0},\"south\":{\"uv\":[32,24,40,28],\"texture\":0},\"west\":{\"uv\":[28,24,32,28],\"texture\":0},\"up\":{\"uv\":[28,24,20,20],\"texture\":0},\"down\":{\"uv\":[28,28,20,32],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5ea74bdb-ba28-b8e3-103b-9be6ff2262da\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,18.75,-1.875],\"to\":[3.75,22.5,1.875],\"autouv\":0,\"color\":3,\"origin\":[0,18.75,0],\"uv_offset\":[16,16],\"faces\":{\"north\":{\"uv\":[20,20,28,24],\"texture\":0},\"east\":{\"uv\":[16,20,20,24],\"texture\":0},\"south\":{\"uv\":[32,20,40,24],\"texture\":0},\"west\":{\"uv\":[28,20,32,24],\"texture\":0},\"up\":{\"uv\":[28,20,20,16],\"texture\":0},\"down\":{\"uv\":[28,24,20,28],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc1510db-a719-17b4-e253-c992a92c5d25\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,18.76563,-1.85937],\"to\":[3.73438,22.48438,1.85938],\"autouv\":0,\"color\":3,\"inflate\":0.25,\"origin\":[0,18.75,0],\"uv_offset\":[16,32],\"faces\":{\"north\":{\"uv\":[20,36,28,40],\"texture\":0},\"east\":{\"uv\":[16,36,20,40],\"texture\":0},\"south\":{\"uv\":[32,36,40,40],\"texture\":0},\"west\":{\"uv\":[28,36,32,40],\"texture\":0},\"up\":{\"uv\":[28,36,20,32],\"texture\":0},\"down\":{\"uv\":[8,0,0,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d51a8665-a2bc-af6e-1230-5acba07248e7\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,15.01563,-1.85937],\"to\":[3.73438,18.73438,1.85938],\"autouv\":0,\"color\":2,\"inflate\":0.25,\"origin\":[0,15,0],\"uv_offset\":[16,36],\"faces\":{\"north\":{\"uv\":[20,40,28,44],\"texture\":0},\"east\":{\"uv\":[16,40,20,44],\"texture\":0},\"south\":{\"uv\":[32,40,40,44],\"texture\":0},\"west\":{\"uv\":[28,40,32,44],\"texture\":0},\"up\":{\"uv\":[8,4,0,0],\"texture\":0},\"down\":{\"uv\":[8,0,0,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f5b9f499-b26b-f912-1a54-151d702e13ed\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,11.26563,-1.85937],\"to\":[3.73438,14.98438,1.85938],\"autouv\":0,\"color\":1,\"inflate\":0.25,\"origin\":[0,11.25,0],\"uv_offset\":[16,40],\"faces\":{\"north\":{\"uv\":[20,44,28,48],\"texture\":0},\"east\":{\"uv\":[16,44,20,48],\"texture\":0},\"south\":{\"uv\":[32,44,40,48],\"texture\":0},\"west\":{\"uv\":[28,44,32,48],\"texture\":0},\"up\":{\"uv\":[8,4,0,0],\"texture\":0},\"down\":{\"uv\":[36,32,28,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"357ebf82-23ba-edb1-081f-dca75d94b83c\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.75,16.875,-1.875],\"to\":[7.5,22.5,1.875],\"autouv\":0,\"color\":4,\"origin\":[5.15625,21.5625,0],\"uv_offset\":[40,16],\"faces\":{\"north\":{\"uv\":[44,20.1,48,26.1],\"texture\":0},\"east\":{\"uv\":[40,20,44,26],\"texture\":0},\"south\":{\"uv\":[52,20,56,26],\"texture\":0},\"west\":{\"uv\":[48,20,52,26],\"texture\":0},\"up\":{\"uv\":[48,20.1,44,16.1],\"texture\":0},\"down\":{\"uv\":[48,26.1,44,30.1],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"53d40d2e-0941-29f9-00ed-1b19c941dcd8\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.76563,16.89063,-1.85937],\"to\":[7.48438,22.48438,1.85938],\"autouv\":0,\"color\":4,\"inflate\":0.25,\"origin\":[5.15625,21.5625,0],\"uv_offset\":[40,32],\"faces\":{\"north\":{\"uv\":[44,36,48,42],\"texture\":0},\"east\":{\"uv\":[40,36,44,42],\"texture\":0},\"south\":{\"uv\":[52,36,56,42],\"texture\":0},\"west\":{\"uv\":[48,36,52,42],\"texture\":0},\"up\":{\"uv\":[48,36,44,32],\"texture\":0},\"down\":{\"uv\":[56,32,52,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b17452ef-afbe-e010-f2c9-e0ba945faa1f\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.5,16.875,-1.875],\"to\":[-3.75,22.5,1.875],\"autouv\":0,\"color\":4,\"origin\":[-5.15625,21.5625,0],\"uv_offset\":[32,48],\"faces\":{\"north\":{\"uv\":[36,52,40,58],\"texture\":0},\"east\":{\"uv\":[32,52,36,58],\"texture\":0},\"south\":{\"uv\":[44,52,48,58],\"texture\":0},\"west\":{\"uv\":[40,52,44,58],\"texture\":0},\"up\":{\"uv\":[40,52,36,48],\"texture\":0},\"down\":{\"uv\":[40,58,36,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"addb9f66-a2a4-46f7-b6af-f5a23c00fe70\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.48437,16.89063,-1.85937],\"to\":[-3.76562,22.48438,1.85938],\"autouv\":0,\"color\":4,\"inflate\":0.25,\"origin\":[-5.15625,21.5625,0],\"uv_offset\":[48,48],\"faces\":{\"north\":{\"uv\":[52,52,56,58],\"texture\":0},\"east\":{\"uv\":[48,52,52,58],\"texture\":0},\"south\":{\"uv\":[60,52,64,58],\"texture\":0},\"west\":{\"uv\":[56,52,60,58],\"texture\":0},\"up\":{\"uv\":[56,52,52,48],\"texture\":0},\"down\":{\"uv\":[64,48,60,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5e7dc31c-c64a-ff15-90d9-52a6f77cb14b\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.75,11.25,-1.875],\"to\":[7.5,16.875,1.875],\"autouv\":0,\"color\":8,\"origin\":[5.15625,15.9375,0],\"uv_offset\":[40,22],\"faces\":{\"north\":{\"uv\":[44,26,48,32],\"texture\":0},\"east\":{\"uv\":[40,26,44,32],\"texture\":0},\"south\":{\"uv\":[52,26,56,32],\"texture\":0},\"west\":{\"uv\":[48,26,52,32],\"texture\":0},\"up\":{\"uv\":[48,26,44,22],\"texture\":0},\"down\":{\"uv\":[52,16,48,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e02c395d-e1bc-1375-a8ed-729e19544ce9\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.76563,11.26563,-1.85937],\"to\":[7.48438,16.85938,1.85938],\"autouv\":0,\"color\":8,\"inflate\":0.25,\"origin\":[5.15625,15.9375,0],\"uv_offset\":[40,38],\"faces\":{\"north\":{\"uv\":[44,42,48,48],\"texture\":0},\"east\":{\"uv\":[40,42,44,48],\"texture\":0},\"south\":{\"uv\":[52,42,56,48],\"texture\":0},\"west\":{\"uv\":[48,42,52,48],\"texture\":0},\"up\":{\"uv\":[44,36,40,32],\"texture\":0},\"down\":{\"uv\":[52,32,48,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a509c2d7-53ef-2b11-1331-f716b9c210d6\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.5,11.25,-1.875],\"to\":[-3.75,16.875,1.875],\"autouv\":0,\"color\":8,\"origin\":[-5.15625,15.9375,0],\"uv_offset\":[32,54],\"faces\":{\"north\":{\"uv\":[36,58,40,64],\"texture\":0},\"east\":{\"uv\":[32,58,36,64],\"texture\":0},\"south\":{\"uv\":[44,58,48,64],\"texture\":0},\"west\":{\"uv\":[40,58,44,64],\"texture\":0},\"up\":{\"uv\":[40,58,36,54],\"texture\":0},\"down\":{\"uv\":[44,48,40,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1433ee62-21ac-d72c-0fd0-37000e5eb221\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.48437,11.26563,-1.85937],\"to\":[-3.76562,16.85938,1.85938],\"autouv\":0,\"color\":8,\"inflate\":0.25,\"origin\":[-5.15625,15.9375,0],\"uv_offset\":[48,54],\"faces\":{\"north\":{\"uv\":[52,58,56,64],\"texture\":0},\"east\":{\"uv\":[48,58,52,64],\"texture\":0},\"south\":{\"uv\":[60,58,64,64],\"texture\":0},\"west\":{\"uv\":[56,58,60,64],\"texture\":0},\"up\":{\"uv\":[52,52,48,48],\"texture\":0},\"down\":{\"uv\":[60,48,56,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"24bacfd6-cde8-81a0-f620-998cf5393d48\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0,5.625,-1.875],\"to\":[3.75,11.25,1.875],\"autouv\":0,\"color\":7,\"origin\":[1.40625,10.3125,0],\"uv_offset\":[0,16],\"faces\":{\"north\":{\"uv\":[4,20,8,26],\"texture\":0},\"east\":{\"uv\":[0,20,4,26],\"texture\":0},\"south\":{\"uv\":[12,20,16,26],\"texture\":0},\"west\":{\"uv\":[8,20,12,26],\"texture\":0},\"up\":{\"uv\":[8,20,4,16],\"texture\":0},\"down\":{\"uv\":[8,26,4,30],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b17675fc-b79b-0ad9-8f81-aa2dd1fc8c97\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.01563,5.64063,-1.85937],\"to\":[3.73438,11.23438,1.85938],\"autouv\":0,\"color\":7,\"inflate\":0.25,\"origin\":[1.40625,10.3125,0],\"uv_offset\":[0,32],\"faces\":{\"north\":{\"uv\":[4,36,8,42],\"texture\":0},\"east\":{\"uv\":[0,36,4,42],\"texture\":0},\"south\":{\"uv\":[12,36,16,42],\"texture\":0},\"west\":{\"uv\":[8,36,12,42],\"texture\":0},\"up\":{\"uv\":[8,36,4,32],\"texture\":0},\"down\":{\"uv\":[16,32,12,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2e42e483-1557-d21e-aee0-94af7bedfd40\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0,0,-1.875],\"to\":[3.75,5.625,1.875],\"autouv\":0,\"color\":6,\"origin\":[1.40625,4.6875,0],\"uv_offset\":[0,22],\"faces\":{\"north\":{\"uv\":[4,26,8,32],\"texture\":0},\"east\":{\"uv\":[0,26,4,32],\"texture\":0},\"south\":{\"uv\":[12,26,16,32],\"texture\":0},\"west\":{\"uv\":[8,26,12,32],\"texture\":0},\"up\":{\"uv\":[8,26,4,22],\"texture\":0},\"down\":{\"uv\":[12,16,8,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9455d16e-4bbf-4b63-881d-7daf943e782b\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.01563,0.01563,-1.85937],\"to\":[3.73438,5.60938,1.85938],\"autouv\":0,\"color\":6,\"inflate\":0.25,\"origin\":[1.40625,4.6875,0],\"uv_offset\":[0,38],\"faces\":{\"north\":{\"uv\":[4,42,8,48],\"texture\":0},\"east\":{\"uv\":[0,42,4,48],\"texture\":0},\"south\":{\"uv\":[12,42,16,48],\"texture\":0},\"west\":{\"uv\":[8,42,12,48],\"texture\":0},\"up\":{\"uv\":[4,36,0,32],\"texture\":0},\"down\":{\"uv\":[12,32,8,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"16aee685-0ead-a542-5b3c-a62467ca45e3\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,5.625,-1.875],\"to\":[0,11.25,1.875],\"autouv\":0,\"color\":7,\"origin\":[-1.40625,10.3125,0],\"uv_offset\":[16,48],\"faces\":{\"north\":{\"uv\":[20,52,24,58],\"texture\":0},\"east\":{\"uv\":[16,52,20,58],\"texture\":0},\"south\":{\"uv\":[28,52,32,58],\"texture\":0},\"west\":{\"uv\":[24,52,28,58],\"texture\":0},\"up\":{\"uv\":[24,52,20,48],\"texture\":0},\"down\":{\"uv\":[24,58,20,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0e370edc-7b05-dccf-dd2a-a92cfe9f3e22\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,5.64063,-1.85937],\"to\":[-0.01562,11.23438,1.85938],\"autouv\":0,\"color\":7,\"inflate\":0.25,\"origin\":[-1.40625,10.3125,0],\"uv_offset\":[0,48],\"faces\":{\"north\":{\"uv\":[4,52,8,58],\"texture\":0},\"east\":{\"uv\":[0,52,4,58],\"texture\":0},\"south\":{\"uv\":[12,52,16,58],\"texture\":0},\"west\":{\"uv\":[8,52,12,58],\"texture\":0},\"up\":{\"uv\":[8,52,4,48],\"texture\":0},\"down\":{\"uv\":[16,48,12,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc189735-0a58-619b-07ef-feec37d65e7d\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,0,-1.875],\"to\":[0,5.625,1.875],\"autouv\":0,\"color\":6,\"origin\":[-1.40625,4.6875,0],\"uv_offset\":[16,54],\"faces\":{\"north\":{\"uv\":[20,58,24,64],\"texture\":0},\"east\":{\"uv\":[16,58,20,64],\"texture\":0},\"south\":{\"uv\":[28,58,32,64],\"texture\":0},\"west\":{\"uv\":[24,58,28,64],\"texture\":0},\"up\":{\"uv\":[24,58,20,54],\"texture\":0},\"down\":{\"uv\":[28,48,24,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6bcf768b-beab-4c39-6d96-91266036a4e1\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,0.01563,-1.85938],\"to\":[-0.01562,5.60938,1.85937],\"autouv\":0,\"color\":6,\"inflate\":0.25,\"origin\":[-1.40625,4.6875,-0.00001],\"uv_offset\":[0,54],\"faces\":{\"north\":{\"uv\":[4,58,8,64],\"texture\":0},\"east\":{\"uv\":[0,58,4,64],\"texture\":0},\"south\":{\"uv\":[12,58,16,64],\"texture\":0},\"west\":{\"uv\":[8,58,12,64],\"texture\":0},\"up\":{\"uv\":[4,52,0,48],\"texture\":0},\"down\":{\"uv\":[12,48,8,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"274282a7-bc7b-02f8-4dce-84226e9dd9c1\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,22.5,-3.75],\"to\":[3.75,30,3.75],\"autouv\":0,\"color\":4,\"origin\":[0,22.5,-1.875],\"faces\":{\"north\":{\"uv\":[8,8,16,16],\"texture\":0},\"east\":{\"uv\":[0,8,8,16],\"texture\":0},\"south\":{\"uv\":[24,8,32,16],\"texture\":0},\"west\":{\"uv\":[16,8,24,16],\"texture\":0},\"up\":{\"uv\":[16,8,8,0],\"texture\":0},\"down\":{\"uv\":[24,0,16,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7f60fbaf-510d-2e5f-b7d2-9111e08443cd\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,22.51563,-3.73437],\"to\":[3.73438,29.98438,3.73438],\"autouv\":0,\"color\":4,\"inflate\":0.5,\"origin\":[0,22.5,-1.875],\"uv_offset\":[32,0],\"faces\":{\"north\":{\"uv\":[40,8,48,16],\"texture\":0},\"east\":{\"uv\":[32,8,40,16],\"texture\":0},\"south\":{\"uv\":[56,8,64,16],\"texture\":0},\"west\":{\"uv\":[48,8,56,16],\"texture\":0},\"up\":{\"uv\":[48,8,40,0],\"texture\":0},\"down\":{\"uv\":[56,0,48,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e0f94313-bf88-492d-0c68-bd6b466a3b68\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1,35.8,-1],\"to\":[1,37.8,1],\"autouv\":1,\"color\":3,\"visibility\":false,\"origin\":[0,35.8,0],\"faces\":{\"north\":{\"uv\":[0,0,2,2]},\"east\":{\"uv\":[0,0,2,2]},\"south\":{\"uv\":[0,0,2,2]},\"west\":{\"uv\":[0,0,2,2]},\"up\":{\"uv\":[0,0,2,2]},\"down\":{\"uv\":[0,0,2,2]}},\"type\":\"cube\",\"uuid\":\"34afd0ba-f1fd-3d07-3b19-cdbe87b7bc1e\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-8,0,-8],\"to\":[8,0,8],\"autouv\":1,\"color\":9,\"visibility\":false,\"origin\":[0,0,0],\"faces\":{\"north\":{\"uv\":[0,0,16,0]},\"east\":{\"uv\":[0,0,16,0]},\"south\":{\"uv\":[0,0,16,0]},\"west\":{\"uv\":[0,0,16,0]},\"up\":{\"uv\":[0,0,16,16]},\"down\":{\"uv\":[0,0,16,16]}},\"type\":\"cube\",\"uuid\":\"ee35aceb-8b83-2bcd-dd62-f18b680f1ece\"}],\"groups\":[{\"uuid\":\"778fa89c-759a-8884-89d9-238c555d2dc1\",\"export\":true,\"locked\":false,\"origin\":[0,17,0],\"rotation\":[0,0,0],\"color\":9,\"name\":\"player_root\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"678a7cf6-aa1f-be09-a547-bc4aa322ef40\",\"export\":true,\"locked\":false,\"origin\":[0,17,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"shadow\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\",\"export\":true,\"locked\":false,\"origin\":[0,11.25,0],\"rotation\":[0,0,0],\"color\":1,\"name\":\"phip_hip\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"e297aef6-7dfd-f100-2e7c-ab113699b922\",\"export\":true,\"locked\":false,\"origin\":[0,15,0],\"rotation\":[0,0,0],\"color\":2,\"name\":\"pw_waist\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"a0c01522-9040-7533-fa11-f6a45d3d96ac\",\"export\":true,\"locked\":false,\"origin\":[0,18.75,0],\"rotation\":[0,0,0],\"color\":3,\"name\":\"pc_chest\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"34097e46-c233-c03c-d8b9-aee154c9946f\",\"export\":true,\"locked\":false,\"origin\":[0,22.5,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"h_ph_head\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\",\"export\":true,\"locked\":false,\"origin\":[5,22,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"pra_right_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"cf1618da-24d8-aab8-eebc-128815c02d35\",\"export\":true,\"locked\":false,\"origin\":[5,16.5,0],\"rotation\":[0,0,0],\"color\":8,\"name\":\"prfa_right_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"fcaf8da0-0146-2587-b578-3e1af888deaa\",\"export\":true,\"locked\":false,\"origin\":[5.625,10.875,0],\"rotation\":[-90,0,0],\"color\":0,\"name\":\"pri_right_item\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"b3135254-0351-3462-2479-e6a3286c89ff\",\"export\":true,\"locked\":false,\"origin\":[-5,22,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"pla_left_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\",\"export\":true,\"locked\":false,\"origin\":[-5,16.5,0],\"rotation\":[0,0,0],\"color\":8,\"name\":\"plfa_left_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"0e94ff40-15b6-0b24-d0a0-447f000d761b\",\"export\":true,\"locked\":false,\"origin\":[-5.625,10.875,0],\"rotation\":[-90,0,0],\"color\":0,\"name\":\"pli_left_item\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\",\"export\":true,\"locked\":false,\"origin\":[1.875,11.25,0],\"rotation\":[0,0,0],\"color\":7,\"name\":\"prl_right_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"c6d9e946-1d10-482d-14b1-0766027adba8\",\"export\":true,\"locked\":false,\"origin\":[1.875,5.625,0],\"rotation\":[0,0,0],\"color\":6,\"name\":\"prfl_right_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\",\"export\":true,\"locked\":false,\"origin\":[-1.875,11.25,0],\"rotation\":[0,0,0],\"color\":7,\"name\":\"pll_left_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\",\"export\":true,\"locked\":false,\"origin\":[-1.875,5.625,0],\"rotation\":[0,0,0],\"color\":6,\"name\":\"plfl_left_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"310c12c3-81f7-ed2f-172e-4d4c44aaa31f\",\"export\":true,\"locked\":false,\"origin\":[0,36.8,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"tag_name\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"9824ae07-4a7e-34a7-e7db-3dbb3327c38b\",\"export\":true,\"locked\":false,\"origin\":[0,22.75,2],\"rotation\":[0,0,0],\"color\":0,\"name\":\"cape_cape\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true}],\"outliner\":[{\"uuid\":\"778fa89c-759a-8884-89d9-238c555d2dc1\",\"isOpen\":true,\"children\":[{\"uuid\":\"678a7cf6-aa1f-be09-a547-bc4aa322ef40\",\"isOpen\":true,\"children\":[\"ee35aceb-8b83-2bcd-dd62-f18b680f1ece\"]},{\"uuid\":\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\",\"isOpen\":true,\"children\":[\"ea42f7f7-a6f1-4479-c43f-48211bab5ed2\",\"357ebf82-23ba-edb1-081f-dca75d94b83c\",{\"uuid\":\"e297aef6-7dfd-f100-2e7c-ab113699b922\",\"isOpen\":true,\"children\":[\"5ea74bdb-ba28-b8e3-103b-9be6ff2262da\",\"f5b9f499-b26b-f912-1a54-151d702e13ed\",{\"uuid\":\"a0c01522-9040-7533-fa11-f6a45d3d96ac\",\"isOpen\":true,\"children\":[{\"uuid\":\"9824ae07-4a7e-34a7-e7db-3dbb3327c38b\",\"isOpen\":true,\"children\":[]},\"dc1510db-a719-17b4-e253-c992a92c5d25\",\"d51a8665-a2bc-af6e-1230-5acba07248e7\",{\"uuid\":\"34097e46-c233-c03c-d8b9-aee154c9946f\",\"isOpen\":true,\"children\":[\"7f60fbaf-510d-2e5f-b7d2-9111e08443cd\",\"e0f94313-bf88-492d-0c68-bd6b466a3b68\"]},{\"uuid\":\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\",\"isOpen\":true,\"children\":[\"53d40d2e-0941-29f9-00ed-1b19c941dcd8\",\"b17452ef-afbe-e010-f2c9-e0ba945faa1f\",{\"uuid\":\"cf1618da-24d8-aab8-eebc-128815c02d35\",\"isOpen\":true,\"children\":[\"e02c395d-e1bc-1375-a8ed-729e19544ce9\",\"a509c2d7-53ef-2b11-1331-f716b9c210d6\",{\"uuid\":\"fcaf8da0-0146-2587-b578-3e1af888deaa\",\"isOpen\":true,\"children\":[]}]}]},{\"uuid\":\"b3135254-0351-3462-2479-e6a3286c89ff\",\"isOpen\":true,\"children\":[\"addb9f66-a2a4-46f7-b6af-f5a23c00fe70\",\"5e7dc31c-c64a-ff15-90d9-52a6f77cb14b\",{\"uuid\":\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\",\"isOpen\":true,\"children\":[\"1433ee62-21ac-d72c-0fd0-37000e5eb221\",\"24bacfd6-cde8-81a0-f620-998cf5393d48\",{\"uuid\":\"0e94ff40-15b6-0b24-d0a0-447f000d761b\",\"isOpen\":true,\"children\":[]}]}]}]}]}]},{\"uuid\":\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\",\"isOpen\":true,\"children\":[\"b17675fc-b79b-0ad9-8f81-aa2dd1fc8c97\",\"2e42e483-1557-d21e-aee0-94af7bedfd40\",{\"uuid\":\"c6d9e946-1d10-482d-14b1-0766027adba8\",\"isOpen\":true,\"children\":[\"9455d16e-4bbf-4b63-881d-7daf943e782b\",\"16aee685-0ead-a542-5b3c-a62467ca45e3\"]}]},{\"uuid\":\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\",\"isOpen\":true,\"children\":[\"0e370edc-7b05-dccf-dd2a-a92cfe9f3e22\",\"dc189735-0a58-619b-07ef-feec37d65e7d\",{\"uuid\":\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\",\"isOpen\":true,\"children\":[\"6bcf768b-beab-4c39-6d96-91266036a4e1\",\"274282a7-bc7b-02f8-4dce-84226e9dd9c1\"]}]},{\"uuid\":\"310c12c3-81f7-ed2f-172e-4d4c44aaa31f\",\"isOpen\":true,\"children\":[\"34afd0ba-f1fd-3d07-3b19-cdbe87b7bc1e\"]}]}],\"textures\":[{\"name\":\"-steve_template.png\",\"path\":\"\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"0\",\"group\":\"\",\"width\":64,\"height\":64,\"uv_width\":64,\"uv_height\":64,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"44166cb6-b1bf-f227-4398-96c94f36d7f7\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAApBJREFUeF7tWztLA0EQvoCgEGsTiPhAwcbOJr12WmlhYWVlr1b+gFRqoZVVKkutLLUXwc5GUHxgIBFLBa1OJrmDzbi3O+u6SHJf2rndmftudl7fphBZfmc3T7HpkfreoXGHk/puwabDJA+t32ocGVCdHtXaeHn3EhEAlVJZK2+0mtFfALA0O661k2wj/Vk6Vta3Y5t+AGBzT3gAjgBiAIJgX6fB+eaeMc+XbzaMcfLo6sAoH1h9M8oXixWjPLT+AgFQLY3o83zrNSIDJspFrfyx+RERAMXJMa384+E5IgAGKzNa+VfjNiIALspb2nRMtpH+44VhrXzt/D0m/cM7O1r5e60Wk/6hqX2t/PN+MwYAEg/YnOt4wGmj8yGXE6/dv86JB+QeAMSAfg+CpjwUOg39exrkL8/7b97v29rL0P27r338fX/kR7X74/2+pL8P3T362gcAGAJaD1Cf6fsjwHsBHvR4rc9rex7EXIOma6/gal9WGZx+5K5S+JLV/rzW57V9WsunvQRfz8+bbT/+vG1/2360HgBkNELwgAQBHAG1G0QMQBDsngBJoiylQWSBZKSGNOg4Q5TODLM8TOKhqAMkdYBagbmWmj1fCmMewBCgfjvl4zn/LuHb1fU6cH35fF/7RPOA3AOAeYCCQC7mASk3x7k4zr1xro24NZXbc+XyJNydaX+JfaI6AAAk7Cw8gNHREhfDEVD4fcQAxwsNCIKCGxzIAoYjJolRSIOSdjj3dQDmAQoCvvx7r63XssO+7XD6/wLf+wW/Xe9iPwAIPRLzvV8Qen2bGwwZBH3nCaHXtwHwTYM+c3sTsySZ+/tWigAAHoAjgBiAIIgs4DkURRrMuCAhzeO+APp4MOqAvJfC38z1ql4UO+HHAAAAAElFTkSuQmCC\"}],\"animations\":[{\"uuid\":\"91f45b91-0388-39e0-52fd-e98e668d0d06\",\"name\":\"roll\",\"loop\":\"once\",\"override\":false,\"length\":0.66667,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10.3452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"4e015e1c-020c-c0cc-cf03-d0f365b91b4f\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"96a1cbef-dc1c-8cb3-893d-1844d13a8898\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-55.3452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"cffaa113-7472-9a2d-879c-3b37b7918233\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.8452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"1859ff86-2a12-36c8-57b7-cf230e86e3e3\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-173.4065947693\",\"y\":\"12.6083678689\",\"z\":\"-8.1925230243\"}],\"uuid\":\"6c944778-0f30-a404-20be-7d985529dcc3\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-254.4306397576\",\"y\":\"-5.2125464926\",\"z\":\"-2.586642225\"}],\"uuid\":\"6fea6dfb-ac36-8bb7-7b6c-f1b31c3fb09c\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-299.316821318\",\"y\":\"-7.7099874859\",\"z\":\"-2.5994534331\"}],\"uuid\":\"19e7bcfa-34ef-ca1f-9316-34616fa523cb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-334.7684189709\",\"y\":\"2.2798591799\",\"z\":\"-2.5779800118\"}],\"uuid\":\"143b95c6-51d7-3d73-dab9-4afc76a18bee\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-359.7452658674\",\"y\":\"0.977484586\",\"z\":\"-0.7993651888\"}],\"uuid\":\"ca8b487a-5316-821c-6a0d-2b17dc09e2ce\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"739f6fd0-eeff-515d-bc2c-05459d38fb35\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"610dffa2-b064-c32e-49b0-95e662c9fbe0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f8040d86-2633-7d51-02f2-d276ac128c20\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-15\",\"z\":\"-5\"}],\"uuid\":\"2613c6a2-076c-8a2d-7022-314ada456d6a\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-16\",\"z\":\"3\"}],\"uuid\":\"65475ddb-0901-3649-c168-8f332b92d1c4\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-4\",\"z\":\"5\"}],\"uuid\":\"d26510d5-a53d-5570-114b-1a49178fc6f7\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"4\"}],\"uuid\":\"03b08547-82b6-c8ce-eb7e-8147925b4d3c\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3d14037c-74a9-abc2-b13d-3600ae6c5dd0\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3\",\"z\":\"0.31\"}],\"uuid\":\"7a5c383e-b443-3773-3fc7-b41a95b66aa3\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f218f334-8ebe-66d8-1769-41de9f9f7ad0\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"75314a09-9386-3e4d-3f96-a8e59911fe5e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e579328d-a99d-9a7f-0a05-746e42159080\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5169123047\",\"y\":\"4.8873117739\",\"z\":\"2.6276738824\"}],\"uuid\":\"57494920-9783-7099-8971-f8828309b07f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.2554184495\",\"y\":\"1.9324031631\",\"z\":\"0.6242872643\"}],\"uuid\":\"14faa28e-7b7c-4fca-04bd-fb15427d1d14\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.321532076\",\"y\":\"-0.1985844587\",\"z\":\"-0.7364643904\"}],\"uuid\":\"2960010b-2391-100b-9059-499f90d98595\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.7446837252\",\"y\":\"-2.2894808314\",\"z\":\"1.027565895\"}],\"uuid\":\"6436ac0d-b7c1-eb4c-741b-c6fec8760846\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6cb82785-ecee-7220-9b21-7f748ca4ff73\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"554f1255-7efc-bebf-d907-e6b38e212768\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"918337ed-db4f-55f0-7a21-d10d25500ae6\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"66b19558-a367-598b-50c7-142c4211a07d\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"af84a634-f0ee-f31b-42da-a9789b3af450\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9b812e3b-ff6a-5bcf-9b14-d88149ef2a29\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.9335647827\",\"y\":\"0.1910653446\",\"z\":\"0.5860712492\"}],\"uuid\":\"d26f71b7-3eb6-c334-8e41-f3048cc39bfd\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40.8337650092\",\"y\":\"1.5719388815\",\"z\":\"0.836263831\"}],\"uuid\":\"908fd1af-b92a-7c86-ec15-33aa208e85d9\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.5153158634\",\"y\":\"1.5694142029\",\"z\":\"1.1566234175\"}],\"uuid\":\"06e3b992-d5bc-6bd5-da5e-ec28155c8af4\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"af2c3045-852e-36da-7224-00ca89c0c360\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4e57e7e0-bff8-8f86-f5a9-14379ed8f0cc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7e8f0cb8-aacf-3a06-0990-319fcca439ca\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"951b375f-8b17-0bce-c1e5-b020c20b1378\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"96e0a9d7-f9a1-ca27-c4c1-49b9b9ae9739\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d691f682-5653-24b5-64cd-f593d802ae7a\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"64d27d7c-16f1-4e24-11de-1df5b650e5b7\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-44.7041455577\",\"y\":\"-0.5463397482\",\"z\":\"2.4016359177\"}],\"uuid\":\"cf7881c9-f9af-c342-d155-dda6ca40df0f\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-42.1987087714\",\"y\":\"0.9199484607\",\"z\":\"2.3855070851\"}],\"uuid\":\"1e94d2b6-12f7-f0b3-5513-e088c678cddc\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"4.7824447755\",\"y\":\"-15.0272212866\",\"z\":\"0.6748976469\"}],\"uuid\":\"d578b08c-e775-fb98-c9f8-42f4e180dc8f\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3a4776d8-2065-0aff-5901-b4d3d6ecf2f6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.9790874962\",\"y\":\"-5.8032396565\",\"z\":\"15.815264958\"}],\"uuid\":\"f9ce9b56-16a1-0ade-1b32-395dc6a6508f\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.1815443852\",\"y\":\"7.1155834398\",\"z\":\"18.1304160091\"}],\"uuid\":\"14d812e3-97c4-fb55-ae8c-7ec92768c5bd\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.9790874962\",\"y\":\"-5.8032396565\",\"z\":\"15.815264958\"}],\"uuid\":\"5afbcdab-4795-1ca8-59a9-285e3dfac932\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.9171104584\",\"y\":\"-4.5000466504\",\"z\":\"5.8600103802\"}],\"uuid\":\"6499750a-a4fb-5c57-4995-c4d47b630672\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"8.2765048988\",\"y\":\"-2.9212329552\",\"z\":\"-1.4866588636\"}],\"uuid\":\"7f018859-2372-9fd4-17e0-756d8fffc147\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"aba6447d-d798-c3c4-8579-72b987b8b41c\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.7013003421\",\"y\":\"1.5426491326\",\"z\":\"16.086881186\"}],\"uuid\":\"3896e9cb-f48c-75cc-888d-769cf83332f2\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"55\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8e1409c4-941b-084c-02e5-df03cc1a4690\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"92ea27f8-0e0a-b629-1027-b86cf738d7ec\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"79.7622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"fc368334-2af0-e8a5-5d0a-5cbdb752e343\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"117.2622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"7f4efd9b-c5ba-5977-b5fa-b1b9799bbac1\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"94.7622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"7db6c762-536f-e3b9-482d-8337cffe5746\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"95.0395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"bbc95573-feb7-ffe2-345c-af69f5f8f939\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"72.5395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"ab7c0856-f341-34ea-f57c-6904b0e30d92\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"9e4fcb79-0aa2-9377-3fc6-ab698d600e8a\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"63743b29-3a44-0bc8-f5a5-8903bb15d390\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25.6848204073\",\"y\":\"10.1778091184\",\"z\":\"-20.173933666\"}],\"uuid\":\"7a3b3bdf-dd57-badd-a6dd-7580d30e23e0\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4323899f-7d3f-557a-beda-d05603cd2f96\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.4403124196\",\"y\":\"16.3255534092\",\"z\":\"-34.2402741209\"}],\"uuid\":\"744316fc-f3d8-67fd-9a68-2b2c568b639e\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.2988707081\",\"y\":\"3.4553272208\",\"z\":\"-6.6606725408\"}],\"uuid\":\"6cbe013b-e0b9-702c-e627-f2c374f53718\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fd38995a-2f81-eb5e-6c06-404eceec04f6\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5f5fe94f-19c5-c019-7a47-ac924d74285c\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"339cdfba-9029-f75f-9d19-ac8567b07424\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"87d530dc-a3ca-5bc5-53fd-d0b58e67c115\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"95\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4056d3f0-9227-879d-0ccd-a715a6acefd8\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"60\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5cb622b9-5c77-b222-ba79-a62ddbf1e801\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.9979365876\",\"y\":\"19.3545961515\",\"z\":\"-11.7009195082\"}],\"uuid\":\"d08bec63-419b-45f1-d3ac-4bf88c534563\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.9979365876\",\"y\":\"19.3545961515\",\"z\":\"-11.7009195082\"}],\"uuid\":\"aeaa276f-1181-1922-960d-7fb6845fd9f3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"13.2306976448\",\"y\":\"4.1075261935\",\"z\":\"1.5640491387\"}],\"uuid\":\"2416d5e6-91bb-6991-a145-7778a9ab717b\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ce1a67cc-be9b-632e-7d0f-1ea657ce8f06\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3e549bb1-58c3-92b5-d13c-9c3c0772ea12\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c6b905c0-5196-e52b-59c7-8d3927a73397\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"82.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e4592899-dc47-7f8c-acd9-92811b5fdd77\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"82.1404769782\",\"y\":\"-17.3455144203\",\"z\":\"2.3566635967\"}],\"uuid\":\"07d4219f-d8d7-811e-fba2-06fb667c4f62\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"31.427095159\",\"y\":\"-13.5306627252\",\"z\":\"-4.344153106\"}],\"uuid\":\"7a496aba-7df1-b5b5-4dbe-7e6420e13fef\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"11.427095159\",\"y\":\"-13.5306627252\",\"z\":\"-4.344153106\"}],\"uuid\":\"446c86e4-1c3b-eff5-a180-a62769af7bb8\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"95fe2e07-173b-a066-a66d-0be58ae890a5\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"48.3424105805\",\"y\":\"1.1690538795\",\"z\":\"-1.2575696869\"}],\"uuid\":\"06246a98-556c-f22c-45a0-44083c73dbe9\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.8934503775\",\"y\":\"-3.8213109421\",\"z\":\"17.9129813754\"}],\"uuid\":\"aead35ef-607e-93dc-f018-85e424e6a137\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"447233aa-66b2-dbd7-e45d-483f46dadbae\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bd388449-ced9-61f6-e408-4f913fc0355d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.4009318272\",\"y\":\"-4.2154088774\",\"z\":\"2.6913572849\"}],\"uuid\":\"7b84f346-ab75-70f4-a8f4-67879637f9e5\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.4009318272\",\"y\":\"-4.2154088774\",\"z\":\"2.6913572849\"}],\"uuid\":\"724ba19e-1a1c-00e9-111a-d67ed5aa5c83\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"48.3698475508\",\"y\":\"-13.3767559929\",\"z\":\"0.8884687898\"}],\"uuid\":\"28e08960-9fa6-1789-1edf-5b1e443db7bd\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.7930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"3629a4e5-a2bf-6722-7a63-824e74693c95\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-23.2930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"fc5d0a27-b180-4a2f-5fb1-c25e9d8d0cbc\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.2930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"0471b600-cd96-fb88-b394-25b68b236191\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b3528148-886c-4981-aad9-a449912e4ff3\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c6d9e946-1d10-482d-14b1-0766027adba8\":{\"name\":\"prfl_right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4d65f4ff-4666-1d6e-e2a2-5754675f39d3\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"713091ea-3c5f-2107-8abf-ee97bc6176ee\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-35\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"020d854c-8f1d-eea6-0d44-f050772acad3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"747e19b0-5b0d-1bde-37c5-d61eae5ee649\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1a3a7fe0-9ac6-642d-1655-a02cbedc50c0\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-63.5104629601\",\"y\":\"-0.0562419487\",\"z\":\"-3.8034923955\"}],\"uuid\":\"cf494402-d95a-c9a9-0666-1105ac4ff1ba\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30.3667568215\",\"y\":\"-5.6104628256\",\"z\":\"0.822128921\"}],\"uuid\":\"338edd44-0be5-f9d0-495a-16b3c7a47468\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5627211181\",\"y\":\"5.0414022802\",\"z\":\"3.6575498102\"}],\"uuid\":\"601767a5-ac9a-bc96-4b0b-237160c36700\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50.8538632696\",\"y\":\"5.4159878628\",\"z\":\"2.605219904\"}],\"uuid\":\"b124849a-6e1c-463f-9163-98272fab4254\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.1096711992\",\"y\":\"-3.7317133585\",\"z\":\"-11.938445897\"}],\"uuid\":\"486ede20-d032-4d46-d9cd-95cf257c04d9\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1e3e41fd-1374-7b20-aada-7d42393cb399\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-93.8329224881\",\"y\":\"-12.0025641778\",\"z\":\"-18.1816740733\"}],\"uuid\":\"13a334d3-48cd-1136-e910-d86f10ecd6ff\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25.8053212577\",\"y\":\"7.0178718272\",\"z\":\"-29.2161110452\"}],\"uuid\":\"7e87c3b5-9d38-b35b-df3b-aa05fc47517d\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-39.0037108296\",\"y\":\"-5.9031625207\",\"z\":\"-19.1431448415\"}],\"uuid\":\"6601f44d-7b3e-1933-edfb-aaf37ed8f707\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-14.2395920994\",\"y\":\"3.5940186635\",\"z\":\"-7.5169369411\"}],\"uuid\":\"284cc08b-c152-0ab3-3689-6d441335ef56\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8085208f-1d05-3f74-9820-0173fe43fd6b\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.9519624501\",\"y\":\"-7.2690960426\",\"z\":\"-16.7194764292\"}],\"uuid\":\"487c4a04-c29a-1aa1-e26a-aa6dbafe7e0a\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"18.247962068\",\"y\":\"7.8313214321\",\"z\":\"-24.4844657083\"}],\"uuid\":\"73a7fea9-6029-f2f6-ba64-9449179096e1\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\":{\"name\":\"plfl_left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a0002446-b908-df56-d45b-79e09a54036d\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"71dde296-8a96-6c52-d883-110fe28f6083\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18.550117808\",\"y\":\"-0.4065298268\",\"z\":\"4.9663131482\"}],\"uuid\":\"a9e10a80-2787-3737-8868-9c28c2cbafdb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5298058614\",\"y\":\"-2.9158033357\",\"z\":\"6.6597637739\"}],\"uuid\":\"3ec121c2-1295-28ed-2ca7-22374f35c684\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c43f7153-1df2-38ec-1edd-b3694bed6dc8\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40.1236819605\",\"y\":\"-0.9306502051\",\"z\":\"1.542865843\"}],\"uuid\":\"fefea430-0ffa-21c0-738a-21ab44462a5e\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-33.1776348771\",\"y\":\"-2.2590643664\",\"z\":\"4.7558127555\"}],\"uuid\":\"3245ce43-4453-1563-6426-957785fa545f\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-53.9268912309\",\"y\":\"-7.3104700825\",\"z\":\"8.1053141589\"}],\"uuid\":\"8d32a863-19e6-66eb-dd63-bc4a4490440e\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.6010831098\",\"y\":\"0.530086938\",\"z\":\"2.60850085\"}],\"uuid\":\"1a5cc3fb-2736-9e73-929b-2b1f796b4824\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}}]}"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nkotlin = \"2.3.21\"\npaperweight = \"2.0.0-beta.21\"\nresourcefactory = \"1.3.1\"\n\nshadow = \"9.4.1\"\nminotaur = \"2.9.0\"\nhangarPublish = \"0.1.4\"\n\nfabric-api = \"0.146.1+26.1.2\"\nfabric-language-kotlin = \"1.13.11+kotlin.2.3.21\"\n\ncloud-bukkit = \"2.0.0-beta.15\"\ncloud-mod = \"2.0.0-beta.16\"\ncloud-core = \"2.0.0\"\n\nconfigurate = \"4.2.0\"\n\nneoform = \"26.1.2-1\"\n\n[libraries]\nkotlin = { module = \"org.jetbrains.kotlin:kotlin-stdlib\", version.ref = \"kotlin\" }\n\nsemver4j = \"org.semver4j:semver4j:6.0.0\"\ncaffeine = \"com.github.ben-manes.caffeine:caffeine:3.2.3\"\n\nadventure-api = \"net.kyori:adventure-api:4.26.1\"\nadventure-examination = \"net.kyori:examination-api:1.3.0\"\nadventure-option = \"net.kyori:option:1.1.0\"\nadventure-platform-bukkit = \"net.kyori:adventure-platform-bukkit:4.4.1\"\nadventure-platform-fabric = \"net.kyori:adventure-platform-fabric:6.9.0\"\n\nasm-tree = \"org.ow2.asm:asm-tree:9.9.1\"\n\nfabric-loader = \"net.fabricmc:fabric-loader:0.19.2\"\nfabric-language-kotlin = { module = \"net.fabricmc:fabric-language-kotlin\", version.ref = \"fabric-language-kotlin\" }\n\npolymer-resource-pack = \"eu.pb4:polymer-resource-pack:0.16.3+26.1.2\"\n\nconfigurate-core = { module = \"org.spongepowered:configurate-core\", version.ref = \"configurate\" }\nconfigurate-yaml = { module = \"org.spongepowered:configurate-yaml\", version.ref = \"configurate\" }\n\njoml = \"org.joml:joml:1.10.8\"\ngson = \"com.google.code.gson:gson:2.14.0\"\nfastutil = \"it.unimi.dsi:fastutil:8.5.18\"\nguava = \"com.google.guava:guava:33.6.0-jre\"\n\ncloud-paper = { module = \"org.incendo:cloud-paper\", version.ref = \"cloud-bukkit\" }\ncloud-fabric = { module = \"org.incendo:cloud-fabric\", version.ref = \"cloud-mod\" }\ncloud-core = { module = \"org.incendo:cloud-core\", version.ref = \"cloud-core\" }\ngeantyref = \"io.leangen.geantyref:geantyref:2.0.1\"\n\nlombok = \"org.projectlombok:lombok:1.18.46\"\nbStats = \"org.bstats:bstats-bukkit:3.2.1\"\ndynamicUV = \"io.github.toxicity188:dynamicuv:1.2.2\"\narmormodel = \"io.github.toxicity188:armormodel:1.0.2\"\njavamesh = \"io.github.toxicity188:java-mesh:0.0.1\"\nmolangCompiler = \"gg.moonflower:molang-compiler:3.1.1.19\"\nlibby = \"net.byteflux:libby-bukkit:1.3.1\"\n\nbuild-kotlin-jvm = { module = \"org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin\", version.ref = \"kotlin\" }\nbuild-shadow = { module = \"com.gradleup.shadow:com.gradleup.shadow.gradle.plugin\", version.ref = \"shadow\" }\nbuild-minotaur = { module = \"com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin\", version.ref = \"minotaur\" }\nbuild-hangarPublish = { module = \"io.papermc.hangar-publish-plugin:io.papermc.hangar-publish-plugin.gradle.plugin\", version.ref = \"hangarPublish\" }\nbuild-resourcefactory = { module = \"xyz.jpenilla:resource-factory\", version.ref = \"resourcefactory\" }\nbuild-paperweight = { module = \"io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin\", version.ref = \"paperweight\" }\n\n[bundles]\nminecraft = [\n    \"adventure-api\",\n    \"joml\",\n    \"fastutil\",\n    \"gson\",\n    \"guava\"\n]\n\nfabric = [\n    \"fabric-loader\",\n    \"fabric-language-kotlin\"\n]\n\nfabric-library = [\n    \"configurate-core\",\n    \"configurate-yaml\"\n]\n\nfabric-mod = [\n    \"adventure-platform-fabric\",\n    \"cloud-fabric\",\n    \"polymer-resource-pack\",\n]\n\nlibrary = [\n    \"semver4j\",\n    \"dynamicUV\",\n    \"javamesh\",\n    \"caffeine\"\n]\n\ncore = [\n    \"armormodel\",\n    \"molangCompiler\"\n]\n\nmanifestLibrary = [\n    \"kotlin\",\n    \"bStats\",\n    \"molangCompiler\",\n    \"cloud-paper\",\n    \"cloud-core\",\n    \"geantyref\",\n    \"adventure-api\",\n    \"adventure-examination\",\n    \"adventure-option\",\n    \"adventure-platform-bukkit\",\n    \"asm-tree\"\n]\n\nshadedLibrary = [\n    \"armormodel\",\n    \"libby\"\n]\n\n[plugins]\nresourcefactory-bukkit = { id = \"xyz.jpenilla.resource-factory-bukkit-convention\" }\nresourcefactory-paper = { id = \"xyz.jpenilla.resource-factory-paper-convention\" }\nresourcefactory-fabric = { id = \"xyz.jpenilla.resource-factory-fabric-convention\" }\n\nhangar = { id = \"io.papermc.hangar-publish-plugin\" }\n\nconvention-publish = { id = \"publish-conventions\" }\nconvention-plugin = { id = \"plugin-conventions\" }\nconvention-standard = { id = \"standard-conventions\" }\nconvention-bukkit = { id = \"bukkit-conventions\" }\nconvention-paperweight = { id = \"paperweight-conventions\" }\nconvention-modrinth = { id = \"modrinth-conventions\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "kotlin.code.style=official\nkotlin.daemon.jvmargs=-Xmx4g\n\norg.gradle.caching=true\norg.gradle.configuration-cache=true\norg.gradle.parallel=true\norg.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\nproject_version=3.0.2\nminecraft_version=26.1.2\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "nms/v1_21_R3/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    alias(libs.plugins.convention.paperweight)\n}\n\ndependencies {\n    paperweight.paperDevBundle(\"1.21.4-R0.1-SNAPSHOT\")\n}\n\ntasks {\n    compileJava {\n        options.release = 21\n    }\n    compileKotlin {\n        compilerOptions.jvmTarget = JvmTarget.JVM_21\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/java/kr/toxicity/model/bukkit/nms/v1_21_R3/AbstractHitBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\npublic abstract class AbstractHitBox extends ArmorStand implements HitBox {\n\n    AbstractHitBox(@NotNull Level level) {\n        super(EntityType.ARMOR_STAND, level);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final boolean equals(@Nullable Object other) {\n        return super.equals(other);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final int hashCode() {\n        return super.hashCode();\n    }\n\n    @Override\n    public void remove(@NotNull RemovalReason reason, @Nullable org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) {\n        super.remove(reason, eventCause);\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/BaseEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.persistence.PersistentDataHolder\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.stream.Stream\n\ninternal data class BaseEntityImpl(\n    private val delegate: CraftEntity\n) : BaseBukkitEntity, PersistentDataHolder by delegate {\n    override fun customName(): AdventureComponent? = handle().run {\n        if (this is ServerPlayer) (customName ?: name).asAdventure() else customName?.asAdventure()?.takeIf {\n            isCustomNameVisible\n        }\n    }\n\n    override fun entity(): org.bukkit.entity.Entity = delegate\n    override fun handle(): Entity = delegate.vanillaEntity\n    override fun uuid(): UUID = delegate.uniqueId\n    override fun id(): Int = handle().id\n    override fun dead(): Boolean = (handle() as? LivingEntity)?.isDeadOrDying == true || handle().removalReason != null || !handle().valid\n    override fun invisible(): Boolean = handle().isInvisible || (handle() as? LivingEntity)?.hasEffect(MobEffects.INVISIBILITY) == true\n    override fun glow(): Boolean = handle().isCurrentlyGlowing\n\n    override fun onWalk(): Boolean {\n        return handle().isWalking()\n    }\n\n    override fun scale(): Double {\n        val handle = handle()\n        return if (handle is LivingEntity) handle.scale.toDouble() else 1.0\n    }\n\n    override fun pitch(): Float = handle().xRot\n    override fun ground(): Boolean = handle().onGround()\n    override fun bodyYaw(): Float = handle().let { if (it is LivingEntity) it.yBodyRot else it.yRot }\n    override fun yaw(): Float = handle().yRot\n    override fun headYaw(): Float = handle().let { if (it is LivingEntity) it.yHeadRot else it.yRot }\n    override fun fly(): Boolean = handle().isFlying\n\n    override fun damageTick(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        val duration = handle.invulnerableDuration.toFloat()\n        if (duration <= 0F) return 0F\n        val knockBack = 1 - (handle.getAttribute(Attributes.KNOCKBACK_RESISTANCE)?.value?.toFloat() ?: 0F)\n        return handle.invulnerableTime.toFloat() / duration * knockBack\n    }\n\n    override fun walkSpeed(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        if (!handle.onGround) return 1F\n        val speed = handle.getEffect(MobEffects.MOVEMENT_SPEED)?.amplifier ?: 0\n        val slow = handle.getEffect(MobEffects.MOVEMENT_SLOWDOWN)?.amplifier ?: 0\n        return (1F + (speed - slow) * 0.2F)\n            .coerceAtLeast(0.2F)\n            .coerceAtMost(2F)\n    }\n\n    override fun passengerPosition(dest: Vector3f): Vector3f {\n        return handle().passengerPosition(dest)\n    }\n\n    override fun platform(): PlatformEntity = delegate.wrap()\n    override fun trackedBy(): Stream<PlatformPlayer> = delegate.trackedBy.stream().map { it.wrap() }\n    override fun location(): PlatformLocation = delegate.location.wrap()\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/BasePlayerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitPlayer\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport net.minecraft.util.Mth\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.bukkit.entity.Player\nimport java.util.stream.Stream\n\ninternal data class BasePlayerImpl(\n    private val delegate: CraftPlayer,\n    private val profile: () -> ModelProfile,\n    private val skinParts: () -> PlayerSkinParts\n) : BaseBukkitEntity by BaseEntityImpl(delegate), BaseBukkitPlayer, Profiled by ProfiledImpl(PlayerArmorImpl(delegate), profile, skinParts) {\n\n    override fun entity(): Player = delegate\n\n    override fun updateInventory() {\n        delegate.handle.containerMenu.sendAllDataToRemote()\n    }\n\n    override fun platform(): PlatformPlayer = delegate.wrap()\n\n    override fun trackedBy(): Stream<PlatformPlayer> = Stream.concat(\n        Stream.of(delegate),\n        delegate.trackedBy.stream()\n    ).map {\n        it.wrap()\n    }\n\n    override fun bodyYaw(): Float {\n        val handle = delegate.handle\n        var yaw = -45 * handle.xMovement()\n        if (handle.zMovement() < 0) yaw *= -1\n        return Mth.wrapDegrees(handle.yHeadRot + yaw)\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/BukkitWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.bukkit.platform.*\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter.adapt\nimport kr.toxicity.model.api.bukkit.platform.BukkitItemStack\nimport kr.toxicity.model.api.platform.*\nimport org.bukkit.Location\nimport org.bukkit.OfflinePlayer\nimport org.bukkit.World\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.LivingEntity\nimport org.bukkit.entity.Player\nimport org.bukkit.inventory.ItemStack\n\ninternal fun Entity.wrap() = adapt(this)\ninternal fun LivingEntity.wrap() = adapt(this)\ninternal fun OfflinePlayer.wrap() = adapt(this)\ninternal fun Player.wrap() = adapt(this)\ninternal fun Location.wrap() = adapt(this)\ninternal fun World.wrap() = adapt(this)\ninternal fun ItemStack.wrap() = adapt(this)\n\ninternal fun PlatformEntity.unwarp(): Entity = (this as BukkitEntity).source()\ninternal fun PlatformLivingEntity.unwarp(): LivingEntity = (this as BukkitLivingEntity).source()\ninternal fun PlatformOfflinePlayer.unwarp(): OfflinePlayer = (this as BukkitOfflinePlayer).source()\ninternal fun PlatformPlayer.unwarp(): Player = (this as BukkitPlayer).source()\ninternal fun PlatformLocation.unwarp(): Location = (this as BukkitLocation).source()\ninternal fun PlatformWorld.unwarp(): World = (this as BukkitWorld).source()\ninternal fun PlatformItemStack.unwarp(): ItemStack = (this as BukkitItemStack).source()\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/EntityData.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\nimport java.lang.reflect.Field\n\ninternal fun Field.toEntityDataAccessor() = run {\n    isAccessible = true\n    get(null) as EntityDataAccessor<*>\n}\n\ninternal fun Class<*>.accessors() = declaredFields.filter { f ->\n    EntityDataAccessor::class.java.isAssignableFrom(f.type)\n}.map {\n    it.toEntityDataAccessor()\n}\n\ninternal val DISPLAY_SET = Display::class.java.accessors()\ninternal val SHARED_FLAG = Entity::class.java.accessors().first().id\ninternal val ITEM_DISPLAY_ID = ItemDisplay::class.java.accessors().map {\n    it.id\n}\ninternal val ITEM_SERIALIZER = ItemDisplay::class.java.accessors().first()\ninternal val ITEM_ENTITY_DATA = buildList {\n    add(SHARED_FLAG)\n    addAll(ITEM_DISPLAY_ID)\n    add(Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID.id)\n    DISPLAY_SET.subList(7, DISPLAY_SET.size).mapTo(this) { it.id }\n}.toIntSet()\n\n@Suppress(\"UNCHECKED_CAST\")\nprivate val DISPLAY_INTERPOLATION_DELAY = (DISPLAY_SET.first() as EntityDataAccessor<Int>).run {\n    SynchedEntityData.DataValue(id, serializer, 0)\n}\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_INTERPOLATION_DURATION = DISPLAY_SET[1] as EntityDataAccessor<Int>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_TRANSLATION = DISPLAY_SET[3] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_SCALE = DISPLAY_SET[4] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_ROTATION = DISPLAY_SET[5] as EntityDataAccessor<Quaternionf>\n\n\ninternal class TransformationData {\n\n    private var _duration = 0\n    private val duration get() = SynchedEntityData.DataValue(DISPLAY_INTERPOLATION_DURATION.id, DISPLAY_INTERPOLATION_DURATION.serializer, _duration)\n    private val translation = Item(Vector3f(), DISPLAY_TRANSLATION, MathUtil::isSimilar, Vector3f::set)\n    private val scale = Item(Vector3f(), DISPLAY_SCALE, MathUtil::isSimilar, Vector3f::set)\n    private val rotation = Item(Quaternionf(), DISPLAY_ROTATION, MathUtil::isSimilar, Quaternionf::set)\n\n    fun packDirty(entityId: Int, dest: AnimationBundler) {\n        val i = translation.cleanIndex + scale.cleanIndex + rotation.cleanIndex\n        if (i == 0) return\n        (dest.mod as ModAnimationBundlerImpl).append(entityId) {\n            dest.standard += ClientboundSetEntityDataPacket(entityId, buildList(i + 2) {\n                add(DISPLAY_INTERPOLATION_DELAY)\n                translation.value?.let { appendPosition(it.value); add(it) }\n                rotation.value?.let { appendRotation(it.value); add(it) }\n                scale.value?.let { appendScale(it.value); add(it) }\n                appendDuration(_duration); add(duration)\n            })\n        }\n    }\n\n    fun transform(\n        duration: Int,\n        translation: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf\n    ) {\n        _duration = duration\n        this.translation.set(translation)\n        this.scale.set(scale)\n        this.rotation.set(rotation)\n    }\n\n    fun pack() = listOf(\n        DISPLAY_INTERPOLATION_DELAY,\n        duration,\n        translation.forceValue,\n        scale.forceValue,\n        rotation.forceValue\n    )\n\n    private class Item<T : Any>(\n        initialValue: T,\n        private val accessor: EntityDataAccessor<T>,\n        private val dirtyChecker: (T, T) -> Boolean,\n        private val setter: (T, T) -> Unit\n    ) {\n        private val _t: T = initialValue\n        private var _dirty = false\n\n        val dirty get() = _dirty\n        val cleanIndex get() = if (dirty) 1 else 0\n        val value get() = if (_dirty) {\n            _dirty = false\n            forceValue\n        } else null\n        val forceValue get() = SynchedEntityData.DataValue(accessor.id, accessor.serializer, _t)\n\n        fun set(other: T) {\n            if (dirtyChecker(_t, other)) return\n            _dirty = true\n            setter(_t, other)\n        }\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport io.netty.buffer.Unpooled\nimport io.papermc.paper.adventure.PaperAdventure\nimport io.papermc.paper.configuration.GlobalConfiguration\nimport it.unimi.dsi.fastutil.ints.IntSet\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.network.syncher.SynchedEntityData.DataItem\nimport net.minecraft.network.syncher.SynchedEntityData.DataValue\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.goal.RangedAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedBowAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal\nimport net.minecraft.world.entity.animal.FlyingAnimal\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.inventory.CraftItemStack\nimport org.bukkit.craftbukkit.util.CraftChatMessage\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(noinline paperGetter: (T) -> R): (T) -> R {\n    return if (BetterModelBukkit.IS_PAPER) paperGetter else createAdaptedFieldGetter()\n}\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(): (T) -> R {\n    return T::class.java.declaredFields.first {\n        R::class.java.isAssignableFrom(it.type)\n    }.apply {\n        isAccessible = true\n    }.let { getter ->\n        { t ->\n            getter[t] as R\n        }\n    }\n}\n\ninternal fun <H, T> dirtyChecked(hash: () -> H, function: (H) -> T, equalityChecker: (H, H) -> Boolean = { a, b -> a == b }): () -> T {\n    val lock = Any()\n    var h = hash()\n    var value = function(h)\n    return {\n        val newH = hash()\n        if (equalityChecker(h, newH)) value else synchronized(lock) {\n            h = newH\n            value = function(h)\n            value\n        }\n    }\n}\n\ninternal val CONFIG get() = BetterModel.config()\n\ninternal val EMPTY_ITEM = VanillaItemStack.EMPTY\ninternal fun BukkitItemStack.asVanilla() = CraftItemStack.asNMSCopy(this)\ninternal fun VanillaItemStack.asBukkit() = CraftItemStack.asCraftMirror(this)\n\ninternal val ONLINE_MODE by lazy(LazyThreadSafetyMode.NONE) {\n    if (BetterModelBukkit.IS_PAPER) GlobalConfiguration.get().proxies.isProxyOnlineMode else Bukkit.getOnlineMode()\n}\n\ninternal fun List<Int>.toIntSet(): IntSet = IntSet.of(*toIntArray())\n\ninternal fun Entity.passengerPosition(dest: Vector3f): Vector3f {\n    return attachments.get(EntityAttachment.PASSENGER, 0, yRot).let { v ->\n        dest.set(v.x.toFloat(), v.y.toFloat(), v.z.toFloat())\n    }\n}\n\nprivate val DATA_ITEMS = SynchedEntityData::class.java.declaredFields.first {\n    it.type.isArray\n}.apply {\n    isAccessible = true\n}\n\ninternal fun SynchedEntityData.pack(\n    clean: Boolean = false,\n    itemFilter: (DataItem<*>) -> Boolean = { true },\n    valueFilter: (DataValue<*>) -> Boolean = { true },\n    required: (List<Pair<DataItem<*>, DataValue<*>>>) -> Boolean = { it.isNotEmpty() }\n): List<DataValue<*>>? = (DATA_ITEMS[this] as Array<*>)\n    .mapNotNull map@ {\n        val item = (it as? DataItem<*>)?.takeIf(itemFilter) ?: return@map null\n        val value = item.value().takeIf(valueFilter) ?: return@map null\n        item to value\n    }\n    .takeIf(required)\n    ?.map {\n        if (clean) it.first.isDirty = false\n        it.second\n    }\n\ninternal fun Entity.isWalking(): Boolean {\n    return controllingPassenger?.isWalking() ?: when (this) {\n        is Mob -> navigation.isInProgress || goalSelector.availableGoals.any {\n            it.isRunning && when (it.goal) {\n                is RangedAttackGoal, is RangedCrossbowAttackGoal<*>, is RangedBowAttackGoal<*> -> true\n                else -> false\n            }\n        }\n        is ServerPlayer -> xMovement() != 0F || zMovement() != 0F\n        else -> false\n    }\n}\n\ninternal fun ServerPlayer.xMovement(): Float {\n    val leftMovement: Boolean = lastClientInput.left()\n    val rightMovement: Boolean = lastClientInput.right()\n    return if (leftMovement == rightMovement) 0F else if (leftMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.yMovement(): Float = if (isJump()) 1F else if (lastClientInput.shift) -1F else 0F\n\ninternal fun ServerPlayer.zMovement(): Float {\n    val forwardMovement: Boolean = lastClientInput.forward()\n    val backwardMovement: Boolean = lastClientInput.backward()\n    return if (forwardMovement == backwardMovement) 0F else if (forwardMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.isJump() = lastClientInput.jump()\n\ninternal val Entity.isFlying: Boolean\n    get() = when (this) {\n        is FlyingAnimal -> isFlying\n        is FlyingMob -> true\n        is Mob -> isNoAi\n        is Player -> abilities.flying\n        is LivingEntity -> isFallFlying\n        else -> false\n    }\n\ninternal val CraftEntity.vanillaEntity: Entity\n    get() = if (BetterModelBukkit.IS_PAPER) handleRaw else handle\n\ninternal inline fun <T> useByteBuf(block: (FriendlyByteBuf) -> T): T {\n    val buffer = FriendlyByteBuf(Unpooled.buffer())\n    return try {\n        block(buffer)\n    } finally {\n        buffer.release()\n    }\n}\n\ninternal fun EntityTrackerRegistry.entityFlag(uuid: UUID, byte: Byte): Byte {\n    var b = byte.toInt()\n    val hideOption = hideOption(uuid)\n    if (hideOption.fire()) b = b and 1.inv()\n    if (hideOption.visibility()) b = b or (1 shl 5)\n    if (hideOption.glowing()) b = b and (1 shl 6).inv()\n    return b.toByte()\n}\n\ninternal fun Vector3f.toVanilla() = Vec3(x.toDouble(), y.toDouble(), z.toDouble())\ninternal fun Vec3.toBukkit() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())\n\ninternal inline fun LivingEntity.toEquipmentPacket(mapper: (EquipmentSlot) -> ItemStack? = { if (hasItemInSlot(it)) getItemBySlot(it) else null }): ClientboundSetEquipmentPacket? {\n    val equip = EquipmentSlot.entries.mapNotNull {\n        mapper(it)?.let { item -> com.mojang.datafixers.util.Pair.of(it, item) }\n    }\n    return if (equip.isNotEmpty()) ClientboundSetEquipmentPacket(id, equip) else null\n}\ninternal fun LivingEntity.toEmptyEquipmentPacket() = toEquipmentPacket { ItemStack.EMPTY }\n\ninternal val Player.hotbarSlot get() = inventory.selected + 36\ninternal val PLAYER_EQUIPMENT_SLOT = IntSet.of(45, 5, 6, 7, 8)\ninternal fun ClientboundContainerSetSlotPacket.isEquipment(player: Player) = containerId == 0 && (PLAYER_EQUIPMENT_SLOT.contains(slot) || slot == player.hotbarSlot)\n\ninternal fun Entity.toFakeAddPacket() = ClientboundAddEntityPacket(\n    id,\n    uuid,\n    x,\n    y,\n    z,\n    xRot,\n    yRot,\n    EntityType.ITEM_DISPLAY,\n    0,\n    deltaMovement,\n    yHeadRot.toDouble()\n)\n\ninternal fun Player.toCustomisation() = entityData.get(Player.DATA_PLAYER_MODE_CUSTOMISATION).toInt()\n\ninternal fun VanillaComponent.asAdventure() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asAdventure(this)\n} else {\n    GsonComponentSerializer.gson().deserialize(CraftChatMessage.toJSON(this))\n}\n\ninternal fun AdventureComponent.asVanilla() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asVanilla(this)\n} else {\n    CraftChatMessage.fromJSON(GsonComponentSerializer.gson().serialize(this))\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/HitBoxImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport io.papermc.paper.event.entity.EntityKnockbackEvent\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.event.hitbox.*\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.api.nms.ModelInteractionHand\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.core.BlockPos\nimport net.minecraft.network.protocol.game.ServerboundInteractPacket\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionHand.MAIN_HAND\nimport net.minecraft.world.InteractionHand.OFF_HAND\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.damagesource.DamageSource\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.entity.projectile.Projectile\nimport net.minecraft.world.entity.projectile.ProjectileDeflection\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.phys.AABB\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.Color\nimport org.bukkit.Particle\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftArmorStand\nimport org.bukkit.craftbukkit.entity.CraftLivingEntity\nimport org.bukkit.event.entity.CreatureSpawnEvent\nimport org.bukkit.event.entity.EntityPotionEffectEvent\nimport org.bukkit.event.entity.EntityRemoveEvent\nimport org.bukkit.plugin.Plugin\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal class HitBoxImpl(\n    private val source: ModelBoundingBox,\n    private val bone: RenderedBone,\n    private var listener: HitBoxListener,\n    private val delegate: Entity,\n    private var mountController: MountController\n) : AbstractHitBox(delegate.level()) {\n    private val posCache = BoneMovement()\n    private var initialized = false\n    private var jumpDelay = 0\n    private var mounted = false\n    private var collision = ifLivingEntity { collides } == true\n    private var noGravity = if (delegate is Mob) delegate.isNoAi else delegate.isNoGravity\n    private var forceDismount = false\n    private var onFly = false\n\n    val craftEntity: HitBox by lazy {\n        object : CraftArmorStand(Bukkit.getServer() as CraftServer, this), HitBox by this {}\n    }\n    val dimensions: EntityDimensions get() = source.run {\n        EntityDimensions(\n            (x() + z()).toFloat() / 2,\n            y().toFloat(),\n            delegate.eyeHeight,\n            EntityAttachments.createDefault(0F, 0F),\n            false\n        ).scale(bone.hitBoxScale())\n    }\n    private val interaction by lazy {\n        HitBoxInteraction(this)\n    }\n\n    init {\n        moveTo(delegate.position())\n        isInvisible = true\n        persist = false\n        isSilent = true\n        initialized = true\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        level().addFreshEntity(interaction.apply {\n            moveTo(delegate.position())\n        }, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        interaction.startRiding(this)\n        listener.handle(HitBoxCreateEvent(this))\n    }\n\n    private fun initialSetup() {\n        if (mounted) {\n            mounted = false\n            if (delegate is Mob) delegate.isNoAi = noGravity\n            else delegate.isNoGravity = noGravity\n            ifLivingEntity { collides = collision }\n        }\n    }\n\n    override fun id(): Int = id\n    override fun uuid(): UUID = uuid\n    override fun source(): PlatformEntity = delegate.bukkitEntity.wrap()\n    override fun positionSource(): RenderedBone = bone\n    override fun forceDismount(): Boolean = forceDismount\n    override fun mountController(): MountController = mountController\n    override fun hasMountDriver(): Boolean = controllingPassenger != null\n    override fun mountController(controller: MountController) {\n        this.mountController = controller\n    }\n    override fun relativePosition(): Vector3f = delegate.position().run {\n        bone.hitBoxPosition(posCache).add(x.toFloat(), y.toFloat(), z.toFloat())\n    }\n    override fun listener(): HitBoxListener = listener\n    override fun listener(listener: HitBoxListener) {\n        this.listener = listener\n    }\n    override fun getItemBySlot(slot: EquipmentSlot): ItemStack = ItemStack.EMPTY\n    override fun setItemSlot(slot: EquipmentSlot, stack: ItemStack) {\n    }\n    override fun getMainArm(): HumanoidArm = HumanoidArm.RIGHT\n\n    override fun mount(entity: PlatformEntity) {\n        if (controllingPassenger != null) return\n        if (interaction.bukkitEntity.addPassenger(entity.unwarp())) {\n            if (mountController.canControl()) {\n                mounted = true\n                noGravity = delegate.isNoGravity\n                ifLivingEntity {\n                    collision = collides\n                    collides = false\n                }\n            }\n            listener.handle(HitBoxMountEvent(this, entity))\n        }\n    }\n\n    override fun dismount(entity: PlatformEntity) {\n        forceDismount = true\n        if (interaction.bukkitEntity.removePassenger(entity.unwarp())) listener.handle(HitBoxDismountEvent(this, entity))\n        forceDismount = false\n    }\n\n    override fun dismountAll() {\n        forceDismount = true\n        interaction.passengers.forEach {\n            it.stopRiding(true)\n            listener.handle(HitBoxDismountEvent(this, it.bukkitEntity.wrap()))\n        }\n        forceDismount = false\n    }\n\n    override fun setRemainingFireTicks(remainingFireTicks: Int) {\n        delegate.remainingFireTicks = remainingFireTicks\n    }\n\n    override fun getRemainingFireTicks(): Int {\n        return delegate.remainingFireTicks\n    }\n\n    override fun knockback(\n        d0: Double,\n        d1: Double,\n        d2: Double,\n        attacker: Entity?,\n        cause: EntityKnockbackEvent.Cause\n    ) {\n        if (attacker === delegate) return\n        ifLivingEntity { knockback(d0, d1, d2, attacker, cause) }\n    }\n\n    override fun push(pushingEntity: Entity) {\n        if (pushingEntity === delegate) return\n        delegate.push(pushingEntity)\n    }\n\n    override fun push(x: Double, y: Double, z: Double, pushingEntity: Entity?) {\n        if (pushingEntity === delegate) return\n        delegate.push(x, y, z, pushingEntity)\n    }\n\n    override fun isCollidable(ignoreClimbing: Boolean): Boolean {\n        return delegate.isCollidable(ignoreClimbing)\n    }\n\n    override fun canCollideWith(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWith(entity)\n    }\n\n    override fun canCollideWithBukkit(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWithBukkit(entity)\n    }\n\n    private fun checkCollide(entity: Entity): Boolean {\n        return entity !== delegate\n                && passengers.none { it === entity }\n                && delegate.passengers.none { it === entity }\n                && (entity !is HitBoxImpl || entity.delegate !== delegate)\n    }\n\n    override fun getActiveEffects(): Collection<MobEffectInstance> {\n        return ifLivingEntity { getActiveEffects() } ?: emptyList()\n    }\n\n    override fun getControllingPassenger(): LivingEntity? {\n        return if (mounted) interaction.firstPassenger as? LivingEntity ?: super.getControllingPassenger() else null\n    }\n\n    override fun onWalk(): Boolean {\n        return isWalking()\n    }\n\n    private fun mountControl(player: ServerPlayer) {\n        if (delegate !is LivingEntity) return\n        val travelVector = Vec3(delegate.xxa.toDouble(), delegate.yya.toDouble(), delegate.zza.toDouble())\n        if (!mountController.canFly() && delegate.isFallFlying) return\n\n        updateFlyStatus(player)\n        val riddenInput = rideInput(player, travelVector)\n        if (riddenInput.length() > 0.01) {\n            delegate.yRot = player.yRot\n            if (onFly) delegate.yHeadRot = player.yRot\n            delegate.move(MoverType.SELF, Vec3(riddenInput.x.toDouble(), riddenInput.y.toDouble(), riddenInput.z.toDouble()))\n        }\n        val dy = delegate.deltaMovement.y + delegate.gravity\n        if (!onFly && mountController.canJump() && (delegate.horizontalCollision || player.isJump()) && dy in 0.0..0.01 && jumpDelay == 0) {\n            jumpDelay = 10\n            delegate.jumpFromGround()\n        }\n    }\n\n    private fun movementSpeed() = ifLivingEntity {\n        getAttribute(Attributes.MOVEMENT_SPEED)?.value?.toFloat()?.let {\n            if (!onFly && !shouldDiscardFriction()) level()\n                .getBlockState(blockPosBelowThatAffectsMyMovement)\n                .block\n                .getFriction() * it else it\n        } ?: 0.0F\n    } ?: 0.0F\n\n    private fun updateFlyStatus(player: ServerPlayer) {\n        val fly = (player.isJump() && mountController.canFly()) || noGravity || onFly\n        if (delegate is Mob) delegate.isNoAi = fly\n        else delegate.isNoGravity = fly\n        onFly = fly && !delegate.onGround()\n        if (onFly) delegate.resetFallDistance()\n    }\n\n    private fun rideInput(player: ServerPlayer, travelVector: Vec3) = mountController.move(\n        if (onFly) MountController.MoveType.FLY else MountController.MoveType.DEFAULT,\n        player.bukkitEntity.wrap(),\n        (delegate.bukkitEntity as org.bukkit.entity.LivingEntity).wrap(),\n        Vector3f(\n            player.xMovement(),\n            player.yMovement(),\n            player.zMovement()\n        ),\n        Vector3f(\n            travelVector.x.toFloat(),\n            travelVector.y.toFloat(),\n            travelVector.z.toFloat()\n        )\n    ).mul(movementSpeed()).rotateY(-Math.toRadians(player.yRot.toDouble()).toFloat())\n\n    override fun tick() {\n        delegate.removalReason?.let {\n            if (!isRemoved) remove(it)\n            return\n        }\n        val controller = controllingPassenger\n        if (jumpDelay > 0) jumpDelay--\n        interaction.isInvisible = delegate.isInvisible\n        if (controller is ServerPlayer && !isDeadOrDying && mountController.canControl()) {\n            if (delegate is Mob) delegate.navigation.stop()\n            mountControl(controller)\n        } else initialSetup()\n        yRot = bone.rotation().y\n        yHeadRot = yRot\n        yBodyRot = yRot\n        val pos = relativePosition()\n        val minusHeight = source.minY * bone.hitBoxScale()\n        setPos(\n            pos.x.toDouble(),\n            pos.y.toDouble() + minusHeight,\n            pos.z.toDouble()\n        )\n        BlockPos.betweenClosedStream(boundingBox).forEach {\n            level().getBlockState(it).entityInside(level(), it, delegate)\n        }\n        if (isInLava) delegate.lavaHurt()\n        firstTick = false\n        listener.sync(craftEntity)\n    }\n\n    override fun remove(reason: RemovalReason, cause: EntityRemoveEvent.Cause?) {\n        initialSetup()\n        listener.handle(HitBoxRemoveEvent(craftEntity))\n        interaction.remove(reason)\n        super.remove(reason, cause)\n    }\n\n    override fun getBukkitLivingEntity(): CraftLivingEntity = bukkitEntity\n    override fun getBukkitEntity(): CraftLivingEntity = craftEntity as CraftLivingEntity\n    override fun getBukkitEntityRaw(): CraftLivingEntity = bukkitEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun isDeadOrDying(): Boolean {\n        return ifLivingEntity { isDeadOrDying } == true\n    }\n\n    override fun hide(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            hideEntity(plugin, bukkitEntity)\n            hideEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun show(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            showEntity(plugin, bukkitEntity)\n            showEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        val interact = HitBoxInteractAtEvent(\n            (player.bukkitEntity as org.bukkit.entity.Player).wrap(), craftEntity, when (hand) {\n                MAIN_HAND -> ModelInteractionHand.RIGHT\n                OFF_HAND -> ModelInteractionHand.LEFT\n            }, vec.toBukkit()\n        )\n        if (!listener.handle(interact)) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand, vec))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, cause: EntityPotionEffectEvent.Cause): Boolean {\n        return ifLivingEntity { addEffect(effectInstance, cause) } == true\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, entity: Entity?): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause,\n        fireEvent: Boolean\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause, fireEvent) } == true\n    }\n\n    override fun hurtServer(world: ServerLevel, source: DamageSource, amount: Float): Boolean {\n        if (source.entity === delegate || delegate.isInvulnerable) return false\n        if (source.entity === controllingPassenger && !mountController.canBeDamagedByRider()) return false\n        val ds = ModelDamageSourceImpl(source)\n        val event = HitBoxDamagedEvent(craftEntity, ds, amount)\n        if (!listener.handle(event)) return false\n        return ifLivingEntity { hurtServer(world, source, event.damage) } == true\n    }\n\n    override fun deflection(projectile: Projectile): ProjectileDeflection {\n        if (projectile.owner === delegate) return ProjectileDeflection.NONE\n        return ifLivingEntity { deflection(projectile) } ?: ProjectileDeflection.NONE\n    }\n\n    override fun getHealth(): Float {\n        return ifLivingEntity { health } ?: super.getHealth()\n    }\n\n    override fun makeBoundingBox(vec3: Vec3): AABB {\n        return if (!initialized) {\n            super.makeBoundingBox(vec3)\n        } else {\n            val scale = bone.hitBoxScale()\n            AABB(\n                vec3.x + source.minX * scale,\n                vec3.y,\n                vec3.z + source.minZ * scale,\n                vec3.x + source.maxX * scale,\n                vec3.y + source.y() * scale,\n                vec3.z + source.maxZ * scale\n            ).apply {\n                if (CONFIG.debug().has(DebugConfig.DebugOption.HITBOX)) {\n                    bukkitEntity.world.spawnParticle(Particle.DUST, minX, minY, minZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                    bukkitEntity.world.spawnParticle(Particle.DUST, maxX, maxY, maxZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                }\n            }\n        }\n    }\n    override fun getDefaultDimensions(pose: Pose): EntityDimensions = if (initialized) dimensions else super.getDefaultDimensions(pose)\n\n    override fun removeHitBox() {\n        source().task {\n            dismountAll()\n            remove(ifLivingEntity { removalReason } ?: RemovalReason.KILLED)\n        }\n    }\n\n    private inline fun <T> ifLivingEntity(block: LivingEntity.() -> T): T? {\n        return if (delegate.valid) (delegate as? LivingEntity)?.block() else null\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/HitBoxInteraction.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.nms.HitBox\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.Interaction\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftInteraction\n\ninternal class HitBoxInteraction(\n    val delegate: HitBoxImpl\n) : Interaction(EntityType.INTERACTION, delegate.level()) {\n\n    init {\n        persist = false\n    }\n\n    private val craftEntity: CraftInteraction by lazy {\n        object : CraftInteraction(Bukkit.getServer() as CraftServer, this), HitBox by delegate {}\n    }\n\n    override fun getBukkitEntity(): CraftEntity = craftEntity\n    override fun getBukkitEntityRaw(): CraftEntity = craftEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun tick() {\n        val dimension = delegate.dimensions\n        width = dimension.width\n        height = dimension.height\n        yRot = delegate.yRot\n        xRot = delegate.xRot\n        setSharedFlagOnFire(delegate.remainingFireTicks > 0)\n    }\n\n    override fun skipAttackInteraction(entity: Entity): Boolean {\n        return if (entity is Player) {\n            entity.attack(delegate)\n            true\n        } else false\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        delegate.interact(player, hand)\n        return InteractionResult.FAIL\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        delegate.interactAt(player, vec, hand)\n        return InteractionResult.FAIL\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/ModAnimationBundlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.nms.ModAnimationBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.RegistryFriendlyByteBuf\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket\nimport net.minecraft.server.MinecraftServer\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\ninternal class ModAnimationBundlerImpl(initialCapacity: Int) : ModAnimationBundler {\n\n    companion object {\n\n        const val KEY = \"modelengine:bulk_data\"\n\n        const val PACKET_TYPE_BULK_DATA = 0x00\n\n        const val FIELD_TRANSLATION = 1 shl 0\n        const val FIELD_LEFT_ROTATION = 1 shl 1\n        const val FIELD_SCALE = 1 shl 2\n        const val FIELD_TRANSFORM_DURATION = 1 shl 4\n\n        private val EMPTY_BUILD_TASK: (FriendlyByteBuf) -> Unit = {}\n    }\n\n    private val packet by lazy {\n        useByteBuf { buffer ->\n            ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC.decode(\n                RegistryFriendlyByteBuf(\n                    buffer,\n                    MinecraftServer.getServer().registryAccess()\n                ).apply {\n                    writeUtf(KEY)\n                    useByteBuf {\n                        it.writeByte(PACKET_TYPE_BULK_DATA)\n                        it.writeVarInt(builderList.size)\n                        builderList.forEach { builder -> builder(it) }\n                        writeBytes(it)\n                    }\n                }\n            )\n        }\n    }\n\n    private val builderList = ArrayList<(FriendlyByteBuf) -> Unit>(initialCapacity)\n\n    override fun send(player: PlatformPlayer) {\n        (player.unwarp() as CraftPlayer).handle.connection.send(packet)\n    }\n\n    fun append(id: Int, scope: Appender.() -> Unit) {\n        val build = Appender(id).apply(scope).build()\n        if (build !== EMPTY_BUILD_TASK) builderList += build\n    }\n\n    class Appender(\n        val entityId: Int,\n    ) {\n        private var mask = 0\n        private var buildTask = EMPTY_BUILD_TASK\n        private val isEmpty get() = buildTask === EMPTY_BUILD_TASK\n\n        fun appendPosition(vector: Vector3f) {\n            mask = mask or FIELD_TRANSLATION\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendScale(vector: Vector3f) {\n            mask = mask or FIELD_SCALE\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendRotation(quaternion: Quaternionf) {\n            mask = mask or FIELD_LEFT_ROTATION\n            task {\n                writeFloat(it, quaternion.x)\n                writeFloat(it, quaternion.y)\n                writeFloat(it, quaternion.z)\n                writeFloat(it, quaternion.w)\n            }\n        }\n\n        fun appendDuration(duration: Int) {\n            mask = mask or FIELD_TRANSFORM_DURATION\n            task {\n                writeVarInt(it, duration)\n            }\n        }\n\n        fun build(): (FriendlyByteBuf) -> Unit {\n            if (isEmpty) return EMPTY_BUILD_TASK\n            val m = mask\n            val t = buildTask\n            return {\n                writeVarInt(it,entityId)\n                writeByte(it, m)\n                t(it)\n            }\n        }\n\n        private fun task(task: (FriendlyByteBuf) -> Unit) {\n            if (isEmpty) {\n                buildTask = task\n                return\n            }\n            val last = buildTask\n            buildTask = {\n                last(it)\n                task(it)\n            }\n        }\n\n        private fun writeFloat(buf: FriendlyByteBuf, float: Float) {\n            buf.writeShort(MathUtil.floatToHalf(float).toInt())\n        }\n\n        private fun writeVarInt(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeVarInt(duration)\n        }\n\n        private fun writeByte(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeByte(duration)\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/ModelDamageSourceImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.event.ModelDamageSource\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport net.minecraft.world.damagesource.DamageSource\nimport org.bukkit.craftbukkit.util.CraftLocation\n\ninternal class ModelDamageSourceImpl(\n    private val source: DamageSource\n) : ModelDamageSource {\n    override fun getCausingEntity(): PlatformEntity? = source.entity?.bukkitEntity?.wrap()\n    override fun getDirectEntity(): PlatformEntity? = source.directEntity?.bukkitEntity?.wrap()\n    override fun getDamageLocation(): PlatformLocation? = source.sourcePositionRaw()?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun getSourceLocation(): PlatformLocation? = source.sourcePosition?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun isIndirect(): Boolean = !source.isDirect\n    override fun getFoodExhaustion(): Float = source.foodExhaustion\n    override fun scalesWithDifficulty(): Boolean = source.scalesWithDifficulty()\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/ModelDisplayImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.ModelDisplay\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformItemTransform\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.util.Brightness\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport org.joml.Quaternionf\nimport org.joml.Vector3d\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicBoolean\n\ninternal class ModelDisplayImpl(\n    private val pos: Vector3d,\n    val display: ItemDisplay,\n    val yOffset: Double\n) : ModelDisplay {\n\n    private val entityData = display.entityData\n    private val entityDataLock = SingleLock()\n    private val forceGlow = AtomicBoolean()\n    private val forceInvisibility = AtomicBoolean()\n\n    private val oldPos = Vector3d(pos)\n\n    override fun id(): Int = display.id\n    override fun uuid(): UUID = display.uuid\n    override fun rotate(rotation: ModelRotation, bundler: PacketBundler) {\n        display.xRot = rotation.x\n        display.yRot = rotation.y\n        bundler += ClientboundMoveEntityPacket.Rot(\n            display.id,\n            rotation.packedY(),\n            rotation.packedX(),\n            display.onGround\n        )\n    }\n\n    override fun invisible(invisible: Boolean) {\n        if (forceInvisibility.compareAndSet(!invisible, invisible)) {\n            entityDataLock.accessToLock {\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPotionEffect(entity: BaseEntity) {\n        val beforeInvisible = display.isInvisible\n        val afterInvisible = entity.invisible()\n        entityDataLock.accessToLock {\n            display.setGlowingTag(entity.glow() || forceGlow.get())\n            if (CONFIG.followMobInvisibility() && beforeInvisible != afterInvisible) {\n                display.isInvisible = afterInvisible\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPosition(location: PlatformLocation) {\n        oldPos.set(pos)\n        pos.set(location.x(), location.y(), location.z())\n    }\n\n\n    override fun spawn(showItem: Boolean, bundler: PacketBundler) {\n        bundler += addPacket\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    override fun teleport(location: PlatformLocation, bundler: PacketBundler) {\n        display.moveTo(\n            location.x(),\n            location.y(),\n            location.z(),\n            location.yaw(),\n            0F\n        )\n        bundler += ClientboundTeleportEntityPacket.teleport(display.id, PositionMoveRotation.of(display), emptySet(), display.onGround)\n    }\n\n    override fun sendPosition(adapter: BaseEntity, bundler: PacketBundler) {\n        val handle = adapter.handle() as Entity\n        if (oldPos.distanceSquared(pos) < 1e-8) return\n        bundler += ClientboundEntityPositionSyncPacket(\n            display.id,\n            PositionMoveRotation.of(handle),\n            handle.onGround()\n        )\n    }\n\n    override fun display(transform: PlatformItemTransform) {\n        entityDataLock.accessToLock {\n            display.itemTransform = ItemDisplayContext.BY_ID.apply(transform.ordinal)\n        }\n    }\n\n    override fun moveDuration(duration: Int) {\n        entityDataLock.accessToLock {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = duration\n        }\n    }\n\n    override fun item(itemStack: PlatformItemStack) {\n        entityDataLock.accessToLock {\n            display.itemStack = itemStack.unwarp().asVanilla()\n        }\n    }\n\n    override fun brightness(block: Int, sky: Int) {\n        entityDataLock.accessToLock {\n            display.brightnessOverride = if (block < 0 && sky < 0) null else Brightness(\n                block,\n                sky\n            )\n        }\n    }\n\n    override fun viewRange(range: Float) {\n        entityDataLock.accessToLock {\n            display.viewRange = range\n        }\n    }\n\n    override fun shadowRadius(radius: Float) {\n        entityDataLock.accessToLock {\n            display.shadowRadius = radius\n        }\n    }\n\n    override fun glow(glow: Boolean) {\n        if (!forceGlow.compareAndSet(!glow, glow)) return\n        entityDataLock.accessToLock {\n            display.setGlowingTag(display.isCurrentlyGlowing || glow)\n        }\n    }\n\n    override fun glowColor(glowColor: Int) {\n        entityDataLock.accessToLock {\n            display.glowColorOverride = glowColor\n        }\n    }\n\n    override fun billboard(billboard: PlatformBillboard) {\n        entityDataLock.accessToLock {\n            display.billboardConstraints = Display.BillboardConstraints.BY_ID.apply(billboard.ordinal)\n        }\n    }\n\n    override fun createTransformer(): DisplayTransformer = DisplayTransformerImpl(display)\n\n    override fun invisible(): Boolean = entityDataLock.accessToLock {\n        display.isInvisible || forceInvisibility.get() || display.itemStack.`is`(Items.AIR)\n    }\n\n    override fun sendDirtyEntityData(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                clean = true,\n                itemFilter = { it.isDirty },\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(!invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    override fun sendEntityData(showItem: Boolean, bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(showItem && !invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    private fun List<SynchedEntityData.DataValue<*>>.markVisible(showItem: Boolean) = map {\n        if (it.id == ITEM_SERIALIZER.id) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.ITEM_STACK,\n            if (showItem) display.itemStack else EMPTY_ITEM\n        ) else it\n    }\n\n    private val addPacket\n        get() = ClientboundAddEntityPacket(\n            display.id,\n            display.uuid,\n            pos.x,\n            pos.y + yOffset,\n            pos.z,\n            display.xRot,\n            display.yRot,\n            display.type,\n            0,\n            display.deltaMovement,\n            display.yHeadRot.toDouble()\n        )\n\n    private val removePacket = ClientboundRemoveEntitiesPacket(display.id)\n\n    private class DisplayTransformerImpl(\n        source: ItemDisplay\n    ) : DisplayTransformer {\n        private val id = source.id\n        private val entityData = TransformationData()\n        private val entityDataLock = SingleLock()\n\n        override fun transform(\n            duration: Int,\n            position: Vector3f,\n            scale: Vector3f,\n            rotation: Quaternionf,\n            bundler: AnimationBundler\n        ) {\n            entityDataLock.accessToLock {\n                entityData.transform(\n                    duration,\n                    position,\n                    scale,\n                    rotation\n                )\n                entityData.packDirty(id, bundler)\n            }\n        }\n\n        override fun sendTransformation(bundler: PacketBundler) {\n            entityDataLock.accessToLock {\n                entityData.pack()\n            }?.run {\n                bundler += ClientboundSetEntityDataPacket(id, this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/ModelGameProfile.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport com.mojang.authlib.GameProfile\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\n\ninternal data class ModelGameProfile(\n    private val gameProfile: GameProfile\n) : ModelProfile {\n\n    private val info = ModelProfileInfo(gameProfile.id, gameProfile.name)\n    private val skin by lazy {\n        gameProfile.properties[\"textures\"].firstOrNull()?.let {\n            BetterModel.platform().profileManager().skin(it.value)\n        } ?: ModelProfileSkin.EMPTY\n    }\n\n    override fun info(): ModelProfileInfo = info\n\n    override fun skin(): ModelProfileSkin = skin\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/ModelNametagImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport com.mojang.math.Transformation\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.BonePosition\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.nms.ModelNametag\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.EntityUtil\nimport net.kyori.adventure.text.Component\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\ninternal class ModelNametagImpl(\n    private val bone: RenderedBone\n) : ModelNametag {\n    private companion object {\n        private val emptyVector = Vector3f()\n        private val emptyTransformation = Transformation(\n            Vector3f(-1F / 40F, -0.2F - 1F / 40F, 0F),\n            null,\n            null,\n            null\n        )\n    }\n\n    private val viewedPlayer = ConcurrentHashMap.newKeySet<UUID>()\n    private val display = Display.TextDisplay(\n        EntityType.TEXT_DISPLAY,\n        MinecraftServer.getServer().overworld()\n    ).apply {\n        entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n        setTransformation(emptyTransformation)\n        billboardConstraints = Display.BillboardConstraints.CENTER\n    }\n    private val posCache = BoneMovement()\n    private var alwaysVisible = false\n    private var location = BetterModel.platform().adapter().zero()\n\n    override fun component(component: Component?) {\n        display.text = component?.asVanilla() ?: VanillaComponent.empty()\n    }\n\n    override fun teleport(location: PlatformLocation) {\n        this.location = location\n    }\n\n    override fun alwaysVisible(alwaysVisible: Boolean) {\n        this.alwaysVisible = alwaysVisible\n    }\n\n    override fun send(player: PlatformPlayer) {\n        if (display.text == VanillaComponent.empty()) return\n        val hb = bone.group.hitBoxPoint\n        val pos = bone.worldPosition(BonePosition(emptyVector, hb, player.uuid()), posCache)\n        display.moveTo(Vec3(\n            location.x() + pos.x,\n            location.y() + pos.y,\n            location.z() + pos.z\n        ))\n        val inPoint = alwaysVisible || EntityUtil.isCustomNameVisible(player.location(), location)\n        when {\n            inPoint && viewedPlayer.add(player.uuid()) -> bundlerOfNotNull(\n                addPacket,\n                display.entityData.pack()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            inPoint -> bundlerOfNotNull(\n                ClientboundEntityPositionSyncPacket(display.id, PositionMoveRotation.of(display), false),\n                display.entityData.packDirty()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            viewedPlayer.remove(player.uuid()) -> bundlerOf(removePacket)\n            else -> null\n        }?.send(player)\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    private val addPacket get() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        display.x,\n        display.y,\n        display.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket get() = ClientboundRemoveEntitiesPacket(display.id)\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/NMSImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup\nimport com.mojang.authlib.GameProfile\nimport io.netty.channel.ChannelDuplexHandler\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.channel.ChannelPromise\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.*\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.core.NonNullList\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.network.Connection\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.resources.ResourceLocation\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl\nimport net.minecraft.util.ARGB\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport net.minecraft.world.item.component.CustomModelData\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.level.entity.LevelEntityGetter\nimport net.minecraft.world.level.entity.LevelEntityGetterAdapter\nimport net.minecraft.world.level.entity.PersistentEntitySectionManager\nimport org.bukkit.craftbukkit.CraftWorld\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Vector3d\nimport java.util.*\nimport java.util.function.Consumer\nimport java.util.function.IntConsumer\n\nclass NMSImpl : NMS {\n\n    companion object {\n        private const val INJECT_NAME = \"bettermodel_channel_handler\"\n\n        //Spigot\n        private val getGameProfile: (Player) -> GameProfile = createAdaptedFieldGetter { it.gameProfile }\n        private val getConnection: (ServerCommonPacketListenerImpl) -> Connection = createAdaptedFieldGetter { it.connection }\n        private val spigotChunkAccess = ServerLevel::class.java.fields.firstOrNull {\n            it.type == PersistentEntitySectionManager::class.java\n        }?.apply {\n            isAccessible = true\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        private val ServerLevel.levelGetter\n            get(): LevelEntityGetter<Entity> {\n                return if (BetterModelBukkit.IS_PAPER) {\n                    `moonrise$getEntityLookup`()\n                } else {\n                    spigotChunkAccess?.get(this)?.let {\n                        (it as PersistentEntitySectionManager<*>).entityGetter as LevelEntityGetter<Entity>\n                    } ?: throw RuntimeException(\"LevelEntityGetter\")\n                }\n            }\n        private val getEntityById: (LevelEntityGetter<Entity>, Int) -> Entity? = if (BetterModelBukkit.IS_PAPER) { g, i ->\n            (g as EntityLookup)[i]\n        } else LevelEntityGetterAdapter::class.java.declaredFields.first {\n            net.minecraft.world.level.entity.EntityLookup::class.java.isAssignableFrom(it.type)\n        }.let {\n            it.isAccessible = true\n            { e, i ->\n                (it[e] as net.minecraft.world.level.entity.EntityLookup<*>).getEntity(i) as? Entity\n            }\n        }\n        private fun Int.toEntity(level: ServerLevel) = getEntityById(level.levelGetter, this)\n        //Spigot\n        private val hitBoxData by lazy {\n            ItemDisplay(EntityType.ITEM_DISPLAY, MinecraftServer.getServer().overworld()).run {\n                entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n                entityData.nonDefaultValues!!\n            }\n        }\n    }\n\n    override fun hide(channel: PlayerChannelHandler, registry: EntityTrackerRegistry) {\n        val target = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf()\n        target.entityData.pack(\n            valueFilter = { it.id == SHARED_FLAG }\n        )?.let {\n            list += ClientboundSetEntityDataPacket(target.id, it).toRegistryDataPacket(channel.uuid(), registry)\n        }\n        if (target is LivingEntity) {\n            val packet = if (registry.hideOption(channel.uuid()).equipment) target.toEmptyEquipmentPacket() else target.toEquipmentPacket()\n            packet?.let { list += it }\n        }\n        list.send(channel.player())\n    }\n\n    private fun ClientboundSetEntityDataPacket.toRegistryDataPacket(uuid: UUID, registry: EntityTrackerRegistry) = ClientboundSetEntityDataPacket(id, packedItems().map {\n        if (it.id == SHARED_FLAG) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.BYTE,\n            registry.entityFlag(uuid, it.value() as Byte)\n        ) else it\n    })\n\n    inner class PlayerChannelHandlerImpl(\n        private val player: CraftPlayer\n    ) : PlayerChannelHandler, ChannelDuplexHandler() {\n        private val connection = player.handle.connection\n        private val uuid = player.uniqueId\n        private val base = adapt(player.wrap())\n\n        init {\n            val pipeline = getConnection(connection).channel.pipeline()\n            pipeline.addBefore(pipeline.first { it.value is Connection }.key, INJECT_NAME, this)\n        }\n\n        override fun close() {\n            val channel = getConnection(connection).channel\n            channel.eventLoop().submit {\n                channel.pipeline().remove(INJECT_NAME)\n            }\n        }\n\n        override fun base(): BasePlayer = base\n        override fun isModEnabled(): Boolean = player.listeningPluginChannels.contains(\"modelengine:bulk_data\")\n\n        private val playerModel get() = connection.player.id.toRegistry()\n\n        private fun Int.toPlayerEntity() = toEntity(connection.player.serverLevel())\n        private fun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n        private inline fun Int.toRegistry(\n            ifHitBox: (Entity) -> Unit = {}\n        ) = (EntityTrackerRegistry.registry(this) ?: toPlayerEntity()?.let {\n            if (it is HitBox) ifHitBox(it)\n            it.toRegistry()\n        })?.takeIf {\n            it.isSpawned(player.uniqueId)\n        }\n\n        override fun sendEntityData(registry: EntityTrackerRegistry) {\n            val handle = registry.entity().handle() as? Entity ?: return\n            val list = bundlerOf(\n                ClientboundSetPassengersPacket(handle)\n            )\n            handle.entityData.pack(\n                valueFilter = { it.id == SHARED_FLAG }\n            )?.let {\n                list += ClientboundSetEntityDataPacket(handle.id, it)\n            }\n            if (handle is LivingEntity) handle.toEquipmentPacket()?.let {\n                list += it\n            }\n            list.send(player.wrap())\n        }\n\n        private fun <T : ClientGamePacketListener> Packet<in T>.handle(): Packet<in T>? {\n            when (this) {\n                is ClientboundBundlePacket -> return if (subPackets() is Keyed) this else ClientboundBundlePacket(subPackets().mapNotNull {\n                    it.handle()\n                })\n                is ClientboundAddEntityPacket -> {\n                    val entity = id.toPlayerEntity() ?: return this\n                    if (entity is HitBox) return entity.toFakeAddPacket()\n                    val wrap = entity.bukkitEntity.wrap()\n                    BetterModel.registry(wrap).ifPresent {\n                        wrap.taskLater(1) {\n                            it.spawn(player.wrap())\n                        }\n                    }\n                }\n                is ClientboundRemoveEntitiesPacket -> {\n                    entityIds\n                        .asSequence()\n                        .mapNotNull map@ {\n                            it.toRegistry {\n                                return@map null\n                            }\n                        }\n                        .forEach {\n                            it.remove()\n                        }\n                }\n                is ClientboundSetPassengersPacket -> {\n                    vehicle.toRegistry()?.let {\n                        return it.mountPacket(it.entity().handle() as? Entity ?: return this, array = passengers)\n                    }\n                }\n                is ClientboundUpdateAttributesPacket if entityId.toPlayerEntity() is HitBox -> return null\n                is ClientboundSetEntityDataPacket -> id.toRegistry {\n                    return ClientboundSetEntityDataPacket(id, hitBoxData)\n                }?.let { registry ->\n                    return toRegistryDataPacket(uuid, registry)\n                }\n                is ClientboundSetEquipmentPacket -> entity.toRegistry()?.let {\n                    if (it.hideOption(uuid).equipment()) (it.entity().handle() as? LivingEntity)?.toEmptyEquipmentPacket()?.let { packet ->\n                        return packet\n                    }\n                }\n                is ClientboundRespawnPacket -> playerModel?.let {\n                    bundlerOf(it.mountPacket(connection.player)).send(player.wrap())\n                }\n                is ClientboundContainerSetSlotPacket if isEquipment(connection.player) && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetSlotPacket(containerId, stateId, slot, EMPTY_ITEM)\n                }\n                is ClientboundContainerSetContentPacket if containerId == 0 && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetContentPacket(\n                        containerId,\n                        stateId,\n                        (items as NonNullList<VanillaItemStack>).apply {\n                            PLAYER_EQUIPMENT_SLOT.forEach(IntConsumer { set(it, EMPTY_ITEM) })\n                            set(connection.player.hotbarSlot, EMPTY_ITEM)\n                        },\n                        carriedItem\n                    )\n                }\n            }\n            return this\n        }\n\n        override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {\n            super.write(ctx, if (msg is Packet<*>) msg.handle() ?: return else msg, promise)\n        }\n\n        override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {\n            fun EntityTrackerRegistry.updatePlayerLimb() = BetterModel.platform().scheduler().asyncTaskLater(1) {\n                if (isClosed) return@asyncTaskLater\n                player.handle.containerMenu.sendAllDataToRemote()\n                trackers().forEach { tracker ->\n                    tracker.update(TrackerUpdateAction.itemMapping()) { bone ->\n                        !bone.itemMapper.fixed()\n                    }\n                }\n            }\n            when (msg) {\n                is ServerboundSetCarriedItemPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) {\n                            connection.send(ClientboundSetHeldSlotPacket(player.inventory.heldItemSlot))\n                            return\n                        }\n                        registry.updatePlayerLimb()\n                    }\n                }\n                is ServerboundPlayerActionPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) return\n                        registry.updatePlayerLimb()\n                    }\n                }\n            }\n            super.channelRead(ctx, msg)\n        }\n\n        private fun EntityTrackerRegistry.remove() {\n            remove(player.wrap())\n        }\n    }\n\n    override fun mount(registry: EntityTrackerRegistry, bundler: PacketBundler) {\n        val entity = registry.entity().handle()\n        if (entity is Entity) bundler += registry.mountPacket(entity)\n    }\n\n    private fun EntityTrackerRegistry.mountPacket(entity: Entity, array: IntArray = entity.passengers.filter {\n        EntityTrackerRegistry.registry(it.uuid) == null\n    }.map {\n        it.id\n    }.toIntArray()): ClientboundSetPassengersPacket {\n        return useByteBuf { buffer ->\n            buffer.writeVarInt(entity.id)\n            buffer.writeVarIntArray(displays()\n                .mapToInt {\n                    (it as ModelDisplayImpl).display.id\n                }.toArray() + array)\n            ClientboundSetPassengersPacket.STREAM_CODEC.decode(buffer)\n        }\n    }\n\n    override fun inject(player: PlatformPlayer): PlayerChannelHandlerImpl = PlayerChannelHandlerImpl(player.unwarp() as CraftPlayer)\n\n    override fun createBundler(initialCapacity: Int): PacketBundler = bundlerOf(initialCapacity)\n    override fun createParallelBundler(threshold: Int): PacketBundler = parallelBundlerOf(threshold)\n    override fun createModAnimationBuilder(initialCapacity: Int): ModAnimationBundler = ModAnimationBundlerImpl(initialCapacity)\n\n    override fun create(location: PlatformLocation, yOffset: Double, initialConsumer: Consumer<ModelDisplay>): ModelDisplay = ModelDisplayImpl(\n        Vector3d(location.x(), location.y(), location.z()),\n        ItemDisplay(EntityType.ITEM_DISPLAY, (location.world().unwarp() as CraftWorld).handle).apply {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n            billboardConstraints = Display.BillboardConstraints.FIXED\n            valid = true\n            yRot = location.yaw()\n            itemTransform = ItemDisplayContext.FIXED\n        },\n        yOffset\n    ).apply {\n        initialConsumer.accept(this)\n        display.entityData.packDirty()\n    }\n\n    override fun createNametag(bone: RenderedBone): ModelNametag = ModelNametagImpl(bone)\n\n    override fun tint(itemStack: PlatformItemStack, rgb: Int): PlatformItemStack {\n        return itemStack.unwarp().asVanilla().apply {\n            set(DataComponents.DYED_COLOR, DyedItemColor(rgb, false))\n            set(DataComponents.CUSTOM_MODEL_DATA, get(DataComponents.CUSTOM_MODEL_DATA)?.let {\n                CustomModelData(it.floats, it.flags, it.strings, it.colors\n                    .run {\n                        if (rgb == 0xFFFFFF) this else map { color ->\n                            ARGB.multiply(color, rgb) and 0xFFFFFF\n                        }\n                    }\n                    .ifEmpty { listOf(rgb) })\n            })\n        }.asBukkit().wrap()\n    }\n\n    override fun createHitBox(entity: BaseEntity, bone: RenderedBone, boundingBox: ModelBoundingBox, mountController: MountController, listener: HitBoxListener): HitBox? {\n        val handle = entity.handle() as? Entity ?: return null\n        return HitBoxImpl(\n            boundingBox.center(),\n            bone,\n            listener,\n            handle,\n            mountController\n        ).craftEntity\n    }\n    override fun version(): NMSVersion = NMSVersion.V1_21_R3\n\n    override fun adapt(entity: PlatformEntity): BaseBukkitEntity {\n        val craft = entity.unwarp() as CraftEntity\n        return BaseEntityImpl(craft)\n    }\n\n    override fun adapt(player: PlatformPlayer): BasePlayer {\n        val craft = player.unwarp() as CraftPlayer\n        return BasePlayerImpl(\n            craft,\n            dirtyChecked(\n                { getGameProfile(craft.handle) },\n                { ModelGameProfile(it) },\n                { a, b -> a == b && a.properties[\"texture\"] === b.properties[\"texture\"]}\n            ),\n            dirtyChecked({ craft.handle.toCustomisation() }, { PlayerSkinParts(it) })\n        )\n    }\n\n    override fun profile(player: PlatformPlayer): ModelProfile = ModelGameProfile(getGameProfile((player.unwarp() as CraftPlayer).handle))\n\n    override fun createSkinItem(model: String, floats: List<Float>, flags: List<Boolean>, strings: List<String>, colors: List<Int>): TransformedItemStack {\n        return VanillaItemStack(Items.PLAYER_HEAD).run {\n            set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(floats, flags, strings, colors))\n            set(DataComponents.ITEM_MODEL, ResourceLocation.parse(model))\n            TransformedItemStack.of(asBukkit().wrap())\n        }\n    }\n\n    override fun isProxyOnlineMode(): Boolean = ONLINE_MODE\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/PacketBundlers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.kyori.adventure.key.Key\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.network.PacketSendListener\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\nprivate val KEY = Key.key(\"bettermodel\")\n\ninternal fun bundlerOfNotNull(vararg packets: ClientPacket?) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.filterNotNull().toMutableList())\ninternal fun bundlerOf(vararg packets: ClientPacket) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.toMutableList())\ninternal fun bundlerOf(size: Int) = SimpleBundler(ArrayList(size))\ninternal fun parallelBundlerOf(threshold: Int) = ParallelBundler(threshold)\n\ninternal operator fun PacketBundler.plusAssign(other: ClientPacket) {\n    when (this) {\n        is SimpleBundler -> add(other)\n        is ParallelBundler -> add(other)\n        else -> throw RuntimeException(\"unsupported bundler.\")\n    }\n}\ninternal fun Packet<*>.assumeSize() = when (this) {\n    is ClientboundSetEntityDataPacket -> packedItems.size\n    is ClientboundSetEquipmentPacket -> slots.size\n    else -> 1\n}\n\ninternal interface PluginBundlePacketImpl : Iterable<ClientPacket>, Keyed {\n    val bundlePacket: ClientboundBundlePacket\n    fun size(): Int\n    fun isEmpty(): Boolean\n    fun add(other: ClientPacket)\n}\n\ninternal class SimpleBundler(\n    private val list: MutableList<ClientPacket>\n) : PacketBundler, PluginBundlePacketImpl {\n    override val bundlePacket = ClientboundBundlePacket(this)\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        connection.send(bundlePacket, PacketSendListener.thenRun(onSuccess))\n    }\n    override fun isEmpty(): Boolean = list.isEmpty()\n    override fun size(): Int = list.size\n    override fun key(): Key = KEY\n    override fun iterator(): MutableIterator<ClientPacket> = list.iterator()\n    override fun add(other: ClientPacket) {\n        list += other\n    }\n}\n\ninternal class ParallelBundler(\n    private val threshold: Int\n) : PacketBundler {\n    private val subBundlers = mutableListOf<PluginBundlePacketImpl>()\n    private var sizeAssume = 0\n    private val newBundler get() = bundlerOf().apply {\n        sizeAssume = 0\n        subBundlers += this\n    }\n    private var selectedBundler = newBundler\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        subBundlers.forEach {\n            connection.send(it.bundlePacket)\n        }\n    }\n    override fun isEmpty(): Boolean = selectedBundler.isEmpty()\n    override fun size(): Int = subBundlers.sumOf(PluginBundlePacketImpl::size)\n    fun add(other: ClientPacket) {\n        (if (sizeAssume > threshold) newBundler else selectedBundler)\n            .apply { selectedBundler = this }\n            .add(other)\n        sizeAssume += other.assumeSize()\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/PlayerArmorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.item.equipment.EquipmentAssets\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\ninternal data class PlayerArmorImpl(\n    private val player: CraftPlayer\n) : PlayerArmor {\n\n    override fun helmet(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.HEAD).toArmorItem()\n    }\n\n    override fun leggings(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.LEGS).toArmorItem()\n    }\n\n    override fun chestplate(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.CHEST).toArmorItem()\n    }\n\n    override fun boots(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.FEET).toArmorItem()\n    }\n\n    private fun VanillaItemStack.toArmorItem(): ArmorItem? = get(DataComponents.EQUIPPABLE)?.assetId?.map {\n        val trim = get(DataComponents.TRIM)\n        ArmorItem(\n            get(DataComponents.DYED_COLOR)?.rgb ?: if (it === EquipmentAssets.LEATHER) DyedItemColor.LEATHER_COLOR else 0xFFFFFF,\n            it.location().path,\n            trim?.pattern?.value()?.assetId?.path,\n            trim?.material?.value()?.assetName\n        )\n    }?.orElse(null)\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/ProfiledImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\n\n\ninternal class ProfiledImpl(\n    private val playerArmor: PlayerArmor,\n    private val modelProfile: () -> ModelProfile,\n    private val playerSkinParts: () -> PlayerSkinParts\n) : Profiled {\n\n    override fun profile(): ModelProfile = modelProfile()\n    override fun armors(): PlayerArmor = playerArmor\n    override fun skinParts(): PlayerSkinParts = playerSkinParts()\n}\n"
  },
  {
    "path": "nms/v1_21_R3/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R3/TypeAliases.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R3\n\nimport net.minecraft.network.chat.Component\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientGamePacketListener\nimport net.minecraft.world.item.ItemStack\n\ninternal typealias VanillaItemStack = ItemStack\ninternal typealias BukkitItemStack = org.bukkit.inventory.ItemStack\ninternal typealias ClientPacket = Packet<ClientGamePacketListener>\ninternal typealias VanillaComponent = Component\ninternal typealias AdventureComponent = net.kyori.adventure.text.Component\n"
  },
  {
    "path": "nms/v1_21_R4/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    alias(libs.plugins.convention.paperweight)\n}\n\ndependencies {\n    paperweight.paperDevBundle(\"1.21.5-R0.1-SNAPSHOT\")\n}\n\ntasks {\n    compileJava {\n        options.release = 21\n    }\n    compileKotlin {\n        compilerOptions.jvmTarget = JvmTarget.JVM_21\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/java/kr/toxicity/model/bukkit/nms/v1_21_R4/AbstractHitBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\npublic abstract class AbstractHitBox extends ArmorStand implements HitBox {\n\n    AbstractHitBox(@NotNull Level level) {\n        super(EntityType.ARMOR_STAND, level);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final boolean equals(@Nullable Object other) {\n        return super.equals(other);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final int hashCode() {\n        return super.hashCode();\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/BaseEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.persistence.PersistentDataHolder\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.stream.Stream\n\ninternal data class BaseEntityImpl(\n    private val delegate: CraftEntity\n) : BaseBukkitEntity, PersistentDataHolder by delegate {\n    override fun customName(): AdventureComponent? = handle().run {\n        if (this is ServerPlayer) (customName ?: name).asAdventure() else customName?.asAdventure()?.takeIf {\n            isCustomNameVisible\n        }\n    }\n\n    override fun entity(): org.bukkit.entity.Entity = delegate\n    override fun handle(): Entity = delegate.vanillaEntity\n    override fun uuid(): UUID = delegate.uniqueId\n    override fun id(): Int = handle().id\n    override fun dead(): Boolean = (handle() as? LivingEntity)?.isDeadOrDying == true || handle().removalReason != null || !handle().valid\n    override fun invisible(): Boolean = handle().isInvisible || (handle() as? LivingEntity)?.hasEffect(MobEffects.INVISIBILITY) == true\n    override fun glow(): Boolean = handle().isCurrentlyGlowing\n\n    override fun onWalk(): Boolean {\n        return handle().isWalking()\n    }\n\n    override fun scale(): Double {\n        val handle = handle()\n        return if (handle is LivingEntity) handle.scale.toDouble() else 1.0\n    }\n\n    override fun pitch(): Float = handle().xRot\n    override fun ground(): Boolean = handle().onGround()\n    override fun bodyYaw(): Float = handle().let { if (it is LivingEntity) it.yBodyRot else it.yRot }\n    override fun yaw(): Float = handle().yRot\n    override fun headYaw(): Float = handle().let { if (it is LivingEntity) it.yHeadRot else it.yRot }\n    override fun fly(): Boolean = handle().isFlying\n\n    override fun damageTick(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        val duration = handle.invulnerableDuration.toFloat()\n        if (duration <= 0F) return 0F\n        val knockBack = 1 - (handle.getAttribute(Attributes.KNOCKBACK_RESISTANCE)?.value?.toFloat() ?: 0F)\n        return handle.invulnerableTime.toFloat() / duration * knockBack\n    }\n\n    override fun walkSpeed(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        if (!handle.onGround) return 1F\n        val speed = handle.getEffect(MobEffects.SPEED)?.amplifier ?: 0\n        val slow = handle.getEffect(MobEffects.SLOWNESS)?.amplifier ?: 0\n        return (1F + (speed - slow) * 0.2F)\n            .coerceAtLeast(0.2F)\n            .coerceAtMost(2F)\n    }\n\n    override fun passengerPosition(dest: Vector3f): Vector3f {\n        return handle().passengerPosition(dest)\n    }\n\n    override fun platform(): PlatformEntity = delegate.wrap()\n    override fun trackedBy(): Stream<PlatformPlayer> = delegate.trackedBy.stream().map { it.wrap() }\n    override fun location(): PlatformLocation = delegate.location.wrap()\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/BasePlayerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitPlayer\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport net.minecraft.util.Mth\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.bukkit.entity.Player\nimport java.util.stream.Stream\n\ninternal data class BasePlayerImpl(\n    private val delegate: CraftPlayer,\n    private val profile: () -> ModelProfile,\n    private val skinParts: () -> PlayerSkinParts\n) : BaseBukkitEntity by BaseEntityImpl(delegate), BaseBukkitPlayer, Profiled by ProfiledImpl(PlayerArmorImpl(delegate), profile, skinParts) {\n\n    override fun entity(): Player = delegate\n\n    override fun updateInventory() {\n        delegate.handle.containerMenu.sendAllDataToRemote()\n    }\n\n    override fun platform(): PlatformPlayer = delegate.wrap()\n\n    override fun trackedBy(): Stream<PlatformPlayer> = Stream.concat(\n        Stream.of(delegate),\n        delegate.trackedBy.stream()\n    ).map {\n        it.wrap()\n    }\n\n    override fun bodyYaw(): Float {\n        val handle = delegate.handle\n        var yaw = -45 * handle.xMovement()\n        if (handle.zMovement() < 0) yaw *= -1\n        return Mth.wrapDegrees(handle.yHeadRot + yaw)\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/BukkitWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.bukkit.platform.*\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter.adapt\nimport kr.toxicity.model.api.bukkit.platform.BukkitItemStack\nimport kr.toxicity.model.api.platform.*\nimport org.bukkit.Location\nimport org.bukkit.OfflinePlayer\nimport org.bukkit.World\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.LivingEntity\nimport org.bukkit.entity.Player\nimport org.bukkit.inventory.ItemStack\n\ninternal fun Entity.wrap() = adapt(this)\ninternal fun LivingEntity.wrap() = adapt(this)\ninternal fun OfflinePlayer.wrap() = adapt(this)\ninternal fun Player.wrap() = adapt(this)\ninternal fun Location.wrap() = adapt(this)\ninternal fun World.wrap() = adapt(this)\ninternal fun ItemStack.wrap() = adapt(this)\n\ninternal fun PlatformEntity.unwarp(): Entity = (this as BukkitEntity).source()\ninternal fun PlatformLivingEntity.unwarp(): LivingEntity = (this as BukkitLivingEntity).source()\ninternal fun PlatformOfflinePlayer.unwarp(): OfflinePlayer = (this as BukkitOfflinePlayer).source()\ninternal fun PlatformPlayer.unwarp(): Player = (this as BukkitPlayer).source()\ninternal fun PlatformLocation.unwarp(): Location = (this as BukkitLocation).source()\ninternal fun PlatformWorld.unwarp(): World = (this as BukkitWorld).source()\ninternal fun PlatformItemStack.unwarp(): ItemStack = (this as BukkitItemStack).source()\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/EntityData.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\nimport java.lang.reflect.Field\n\ninternal fun Field.toEntityDataAccessor() = run {\n    isAccessible = true\n    get(null) as EntityDataAccessor<*>\n}\n\ninternal fun Class<*>.accessors() = declaredFields.filter { f ->\n    EntityDataAccessor::class.java.isAssignableFrom(f.type)\n}.map {\n    it.toEntityDataAccessor()\n}\n\ninternal val DISPLAY_SET = Display::class.java.accessors()\ninternal val SHARED_FLAG = Entity::class.java.accessors().first().id\ninternal val ITEM_DISPLAY_ID = ItemDisplay::class.java.accessors().map {\n    it.id\n}\ninternal val ITEM_SERIALIZER = ItemDisplay::class.java.accessors().first()\ninternal val ITEM_ENTITY_DATA = buildList {\n    add(SHARED_FLAG)\n    addAll(ITEM_DISPLAY_ID)\n    add(Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID.id)\n    DISPLAY_SET.subList(7, DISPLAY_SET.size).mapTo(this) { it.id }\n}.toIntSet()\n\n@Suppress(\"UNCHECKED_CAST\")\nprivate val DISPLAY_INTERPOLATION_DELAY = (DISPLAY_SET.first() as EntityDataAccessor<Int>).run {\n    SynchedEntityData.DataValue(id, serializer, 0)\n}\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_INTERPOLATION_DURATION = DISPLAY_SET[1] as EntityDataAccessor<Int>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_TRANSLATION = DISPLAY_SET[3] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_SCALE = DISPLAY_SET[4] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_ROTATION = DISPLAY_SET[5] as EntityDataAccessor<Quaternionf>\n\n\ninternal class TransformationData {\n\n    private var _duration = 0\n    private val duration get() = SynchedEntityData.DataValue(DISPLAY_INTERPOLATION_DURATION.id, DISPLAY_INTERPOLATION_DURATION.serializer, _duration)\n    private val translation = Item(Vector3f(), DISPLAY_TRANSLATION, MathUtil::isSimilar, Vector3f::set)\n    private val scale = Item(Vector3f(), DISPLAY_SCALE, MathUtil::isSimilar, Vector3f::set)\n    private val rotation = Item(Quaternionf(), DISPLAY_ROTATION, MathUtil::isSimilar, Quaternionf::set)\n\n    fun packDirty(entityId: Int, dest: AnimationBundler) {\n        val i = translation.cleanIndex + scale.cleanIndex + rotation.cleanIndex\n        if (i == 0) return\n        (dest.mod as ModAnimationBundlerImpl).append(entityId) {\n            dest.standard += ClientboundSetEntityDataPacket(entityId, buildList(i + 2) {\n                add(DISPLAY_INTERPOLATION_DELAY)\n                translation.value?.let { appendPosition(it.value); add(it) }\n                rotation.value?.let { appendRotation(it.value); add(it) }\n                scale.value?.let { appendScale(it.value); add(it) }\n                appendDuration(_duration); add(duration)\n            })\n        }\n    }\n\n    fun transform(\n        duration: Int,\n        translation: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf\n    ) {\n        _duration = duration\n        this.translation.set(translation)\n        this.scale.set(scale)\n        this.rotation.set(rotation)\n    }\n\n    fun pack() = listOf(\n        DISPLAY_INTERPOLATION_DELAY,\n        duration,\n        translation.forceValue,\n        scale.forceValue,\n        rotation.forceValue\n    )\n\n    private class Item<T : Any>(\n        initialValue: T,\n        private val accessor: EntityDataAccessor<T>,\n        private val dirtyChecker: (T, T) -> Boolean,\n        private val setter: (T, T) -> Unit\n    ) {\n        private val _t: T = initialValue\n        private var _dirty = false\n\n        val dirty get() = _dirty\n        val cleanIndex get() = if (dirty) 1 else 0\n        val value get() = if (_dirty) {\n            _dirty = false\n            forceValue\n        } else null\n        val forceValue get() = SynchedEntityData.DataValue(accessor.id, accessor.serializer, _t)\n\n        fun set(other: T) {\n            if (dirtyChecker(_t, other)) return\n            _dirty = true\n            setter(_t, other)\n        }\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport io.netty.buffer.Unpooled\nimport io.papermc.paper.adventure.PaperAdventure\nimport io.papermc.paper.configuration.GlobalConfiguration\nimport it.unimi.dsi.fastutil.ints.IntSet\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.network.syncher.SynchedEntityData.DataItem\nimport net.minecraft.network.syncher.SynchedEntityData.DataValue\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.goal.RangedAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedBowAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal\nimport net.minecraft.world.entity.animal.FlyingAnimal\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.inventory.CraftItemStack\nimport org.bukkit.craftbukkit.util.CraftChatMessage\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(noinline paperGetter: (T) -> R): (T) -> R {\n    return if (BetterModelBukkit.IS_PAPER) paperGetter else createAdaptedFieldGetter()\n}\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(): (T) -> R {\n    return T::class.java.declaredFields.first {\n        R::class.java.isAssignableFrom(it.type)\n    }.apply {\n        isAccessible = true\n    }.let { getter ->\n        { t ->\n            getter[t] as R\n        }\n    }\n}\n\ninternal fun <H, T> dirtyChecked(hash: () -> H, function: (H) -> T, equalityChecker: (H, H) -> Boolean = { a, b -> a == b }): () -> T {\n    val lock = Any()\n    var h = hash()\n    var value = function(h)\n    return {\n        val newH = hash()\n        if (equalityChecker(h, newH)) value else synchronized(lock) {\n            h = newH\n            value = function(h)\n            value\n        }\n    }\n}\n\ninternal val CONFIG get() = BetterModel.config()\ninternal val EMPTY_ITEM = VanillaItemStack.EMPTY\ninternal fun BukkitItemStack.asVanilla() = CraftItemStack.asNMSCopy(this)\ninternal fun VanillaItemStack.asBukkit() = CraftItemStack.asCraftMirror(this)\n\ninternal val ONLINE_MODE by lazy(LazyThreadSafetyMode.NONE) {\n    if (BetterModelBukkit.IS_PAPER) GlobalConfiguration.get().proxies.isProxyOnlineMode else Bukkit.getOnlineMode()\n}\n\ninternal fun List<Int>.toIntSet(): IntSet = IntSet.of(*toIntArray())\n\ninternal fun Entity.passengerPosition(dest: Vector3f): Vector3f {\n    return attachments.get(EntityAttachment.PASSENGER, 0, yRot).let { v ->\n        dest.set(v.x.toFloat(), v.y.toFloat(), v.z.toFloat())\n    }\n}\n\nprivate val DATA_ITEMS = SynchedEntityData::class.java.declaredFields.first {\n    it.type.isArray\n}.apply {\n    isAccessible = true\n}\n\ninternal fun SynchedEntityData.pack(\n    clean: Boolean = false,\n    itemFilter: (DataItem<*>) -> Boolean = { true },\n    valueFilter: (DataValue<*>) -> Boolean = { true },\n    required: (List<Pair<DataItem<*>, DataValue<*>>>) -> Boolean = { it.isNotEmpty() }\n): List<DataValue<*>>? = (DATA_ITEMS[this] as Array<*>)\n    .mapNotNull map@ {\n        val item = (it as? DataItem<*>)?.takeIf(itemFilter) ?: return@map null\n        val value = item.value().takeIf(valueFilter) ?: return@map null\n        item to value\n    }\n    .takeIf(required)\n    ?.map {\n        if (clean) it.first.isDirty = false\n        it.second\n    }\n\ninternal fun Entity.isWalking(): Boolean {\n    return controllingPassenger?.isWalking() ?: when (this) {\n        is Mob -> navigation.isInProgress || goalSelector.availableGoals.any {\n            it.isRunning && when (it.goal) {\n                is RangedAttackGoal, is RangedCrossbowAttackGoal<*>, is RangedBowAttackGoal<*> -> true\n                else -> false\n            }\n        }\n        is ServerPlayer -> xMovement() != 0F || zMovement() != 0F\n        else -> false\n    }\n}\n\ninternal fun ServerPlayer.xMovement(): Float {\n    val leftMovement: Boolean = lastClientInput.left()\n    val rightMovement: Boolean = lastClientInput.right()\n    return if (leftMovement == rightMovement) 0F else if (leftMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.yMovement(): Float = if (isJump()) 1F else if (lastClientInput.shift) -1F else 0F\n\ninternal fun ServerPlayer.zMovement(): Float {\n    val forwardMovement: Boolean = lastClientInput.forward()\n    val backwardMovement: Boolean = lastClientInput.backward()\n    return if (forwardMovement == backwardMovement) 0F else if (forwardMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.isJump() = lastClientInput.jump()\n\ninternal val Entity.isFlying: Boolean\n    get() = when (this) {\n        is FlyingAnimal -> isFlying\n        is FlyingMob -> true\n        is Mob -> isNoAi\n        is Player -> abilities.flying\n        is LivingEntity -> isFallFlying\n        else -> false\n    }\n\ninternal val CraftEntity.vanillaEntity: Entity\n    get() = if (BetterModelBukkit.IS_PAPER) handleRaw else handle\n\ninternal fun Entity.moveTo(vec: Vec3) = snapTo(vec)\ninternal fun Entity.moveTo(x: Double, y: Double, z: Double, yaw: Float, pitch: Float) = snapTo(x, y, z, yaw, pitch)\n\ninternal inline fun <T> useByteBuf(block: (FriendlyByteBuf) -> T): T {\n    val buffer = FriendlyByteBuf(Unpooled.buffer())\n    return try {\n        block(buffer)\n    } finally {\n        buffer.release()\n    }\n}\n\ninternal fun EntityTrackerRegistry.entityFlag(uuid: UUID, byte: Byte): Byte {\n    var b = byte.toInt()\n    val hideOption = hideOption(uuid)\n    if (hideOption.fire()) b = b and 1.inv()\n    if (hideOption.visibility()) b = b or (1 shl 5)\n    if (hideOption.glowing()) b = b and (1 shl 6).inv()\n    return b.toByte()\n}\n\ninternal fun Vector3f.toVanilla() = Vec3(x.toDouble(), y.toDouble(), z.toDouble())\ninternal fun Vec3.toBukkit() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())\n\ninternal inline fun LivingEntity.toEquipmentPacket(mapper: (EquipmentSlot) -> ItemStack? = { getItemBySlot(it).takeUnless { item -> item.isEmpty } }): ClientboundSetEquipmentPacket? {\n    val equip = EquipmentSlot.entries.mapNotNull {\n        mapper(it)?.let { item -> com.mojang.datafixers.util.Pair.of(it, item) }\n    }\n    return if (equip.isNotEmpty()) ClientboundSetEquipmentPacket(id, equip) else null\n}\ninternal fun LivingEntity.toEmptyEquipmentPacket() = toEquipmentPacket { ItemStack.EMPTY }\n\ninternal val Player.hotbarSlot get() = inventory.selectedSlot + 36\ninternal val PLAYER_EQUIPMENT_SLOT = IntSet.of(45, 5, 6, 7, 8)\ninternal fun ClientboundContainerSetSlotPacket.isEquipment(player: Player) = containerId == 0 && (PLAYER_EQUIPMENT_SLOT.contains(slot) || slot == player.hotbarSlot)\n\ninternal fun Entity.toFakeAddPacket() = ClientboundAddEntityPacket(\n    id,\n    uuid,\n    x,\n    y,\n    z,\n    xRot,\n    yRot,\n    EntityType.ITEM_DISPLAY,\n    0,\n    deltaMovement,\n    yHeadRot.toDouble()\n)\n\ninternal fun Player.toCustomisation() = entityData.get(Player.DATA_PLAYER_MODE_CUSTOMISATION).toInt()\n\ninternal fun VanillaComponent.asAdventure() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asAdventure(this)\n} else {\n    GsonComponentSerializer.gson().deserialize(CraftChatMessage.toJSON(this))\n}\n\ninternal fun AdventureComponent.asVanilla() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asVanilla(this)\n} else {\n    CraftChatMessage.fromJSON(GsonComponentSerializer.gson().serialize(this))\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/HitBoxImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport io.papermc.paper.event.entity.EntityKnockbackEvent\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.event.hitbox.*\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.api.nms.ModelInteractionHand\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.network.protocol.game.ServerboundInteractPacket\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionHand.MAIN_HAND\nimport net.minecraft.world.InteractionHand.OFF_HAND\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.damagesource.DamageSource\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.entity.projectile.Projectile\nimport net.minecraft.world.entity.projectile.ProjectileDeflection\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.level.BlockGetter\nimport net.minecraft.world.phys.AABB\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.Color\nimport org.bukkit.Particle\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftArmorStand\nimport org.bukkit.craftbukkit.entity.CraftLivingEntity\nimport org.bukkit.event.entity.CreatureSpawnEvent\nimport org.bukkit.event.entity.EntityPotionEffectEvent\nimport org.bukkit.event.entity.EntityRemoveEvent\nimport org.bukkit.plugin.Plugin\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal class HitBoxImpl(\n    private val source: ModelBoundingBox,\n    private val bone: RenderedBone,\n    private var listener: HitBoxListener,\n    private val delegate: Entity,\n    private var mountController: MountController\n) : AbstractHitBox(delegate.level()) {\n    private val posCache = BoneMovement()\n    private var initialized = false\n    private var jumpDelay = 0\n    private var mounted = false\n    private var collision = ifLivingEntity { collides } == true\n    private var noGravity = if (delegate is Mob) delegate.isNoAi else delegate.isNoGravity\n    private var forceDismount = false\n    private var onFly = false\n\n    val craftEntity: HitBox by lazy {\n        object : CraftArmorStand(Bukkit.getServer() as CraftServer, this), HitBox by this {}\n    }\n    val dimensions: EntityDimensions get() = source.run {\n        EntityDimensions(\n            (x() + z()).toFloat() / 2,\n            y().toFloat(),\n            delegate.eyeHeight,\n            EntityAttachments.createDefault(0F, 0F),\n            false\n        ).scale(bone.hitBoxScale())\n    }\n    private val interaction by lazy {\n        HitBoxInteraction(this)\n    }\n    private val applier = InsideBlockEffectApplier.StepBasedCollector()\n\n    init {\n        moveTo(delegate.position())\n        isInvisible = true\n        persist = false\n        isSilent = true\n        initialized = true\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        level().addFreshEntity(interaction.apply {\n            moveTo(delegate.position())\n        }, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        interaction.startRiding(this)\n        listener.handle(HitBoxCreateEvent(this))\n    }\n\n    private fun initialSetup() {\n        if (mounted) {\n            mounted = false\n            if (delegate is Mob) delegate.isNoAi = noGravity\n            else delegate.isNoGravity = noGravity\n            ifLivingEntity { collides = collision }\n        }\n    }\n\n    override fun id(): Int = id\n    override fun uuid(): UUID = uuid\n    override fun source(): PlatformEntity = delegate.bukkitEntity.wrap()\n    override fun positionSource(): RenderedBone = bone\n    override fun forceDismount(): Boolean = forceDismount\n    override fun mountController(): MountController = mountController\n    override fun hasMountDriver(): Boolean = controllingPassenger != null\n    override fun mountController(controller: MountController) {\n        this.mountController = controller\n    }\n    override fun relativePosition(): Vector3f = delegate.position().run {\n        bone.hitBoxPosition(posCache).add(x.toFloat(), y.toFloat(), z.toFloat())\n    }\n    override fun listener(): HitBoxListener = listener\n    override fun listener(listener: HitBoxListener) {\n        this.listener = listener\n    }\n    override fun getItemBySlot(slot: EquipmentSlot): ItemStack = ItemStack.EMPTY\n    override fun setItemSlot(slot: EquipmentSlot, stack: ItemStack) {\n    }\n    override fun getMainArm(): HumanoidArm = HumanoidArm.RIGHT\n\n    override fun mount(entity: PlatformEntity) {\n        if (controllingPassenger != null) return\n        if (interaction.bukkitEntity.addPassenger(entity.unwarp())) {\n            if (mountController.canControl()) {\n                mounted = true\n                noGravity = delegate.isNoGravity\n                ifLivingEntity {\n                    collision = collides\n                    collides = false\n                }\n            }\n            listener.handle(HitBoxMountEvent(this, entity))\n        }\n    }\n\n    override fun dismount(entity: PlatformEntity) {\n        forceDismount = true\n        if (interaction.bukkitEntity.removePassenger(entity.unwarp())) listener.handle(HitBoxDismountEvent(this, entity))\n        forceDismount = false\n    }\n\n    override fun dismountAll() {\n        forceDismount = true\n        interaction.passengers.forEach {\n            it.stopRiding(true)\n            listener.handle(HitBoxDismountEvent(this, it.bukkitEntity.wrap()))\n        }\n        forceDismount = false\n    }\n\n    override fun setRemainingFireTicks(remainingFireTicks: Int) {\n        delegate.remainingFireTicks = remainingFireTicks\n    }\n\n    override fun getRemainingFireTicks(): Int {\n        return delegate.remainingFireTicks\n    }\n\n    override fun knockback(\n        d0: Double,\n        d1: Double,\n        d2: Double,\n        attacker: Entity?,\n        cause: EntityKnockbackEvent.Cause\n    ) {\n        if (attacker === delegate) return\n        ifLivingEntity { knockback(d0, d1, d2, attacker, cause) }\n    }\n\n    override fun push(pushingEntity: Entity) {\n        if (pushingEntity === delegate) return\n        delegate.push(pushingEntity)\n    }\n\n    override fun push(x: Double, y: Double, z: Double, pushingEntity: Entity?) {\n        if (pushingEntity === delegate) return\n        delegate.push(x, y, z, pushingEntity)\n    }\n\n    override fun isCollidable(ignoreClimbing: Boolean): Boolean {\n        return delegate.isCollidable(ignoreClimbing)\n    }\n\n    override fun canCollideWith(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWith(entity)\n    }\n\n    override fun canCollideWithBukkit(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWithBukkit(entity)\n    }\n\n    private fun checkCollide(entity: Entity): Boolean {\n        return entity !== delegate\n                && passengers.none { it === entity }\n                && delegate.passengers.none { it === entity }\n                && (entity !is HitBoxImpl || entity.delegate !== delegate)\n    }\n\n    override fun getActiveEffects(): Collection<MobEffectInstance> {\n        return ifLivingEntity { getActiveEffects() } ?: emptyList()\n    }\n\n    override fun getControllingPassenger(): LivingEntity? {\n        return if (mounted) interaction.firstPassenger as? LivingEntity ?: super.getControllingPassenger() else null\n    }\n\n    override fun onWalk(): Boolean {\n        return isWalking()\n    }\n\n    private fun mountControl(player: ServerPlayer) {\n        if (delegate !is LivingEntity) return\n        val travelVector = Vec3(delegate.xxa.toDouble(), delegate.yya.toDouble(), delegate.zza.toDouble())\n        if (!mountController.canFly() && delegate.isFallFlying) return\n\n        updateFlyStatus(player)\n        val riddenInput = rideInput(player, travelVector)\n        if (riddenInput.length() > 0.01) {\n            delegate.yRot = player.yRot\n            if (onFly) delegate.yHeadRot = player.yRot\n            delegate.move(MoverType.SELF, Vec3(riddenInput.x.toDouble(), riddenInput.y.toDouble(), riddenInput.z.toDouble()))\n        }\n        val dy = delegate.deltaMovement.y + delegate.gravity\n        if (!onFly && mountController.canJump() && (delegate.horizontalCollision || player.isJump()) && dy in 0.0..0.01 && jumpDelay == 0) {\n            jumpDelay = 10\n            delegate.jumpFromGround()\n        }\n    }\n\n    private fun movementSpeed() = ifLivingEntity {\n        getAttribute(Attributes.MOVEMENT_SPEED)?.value?.toFloat()?.let {\n            if (!onFly && !shouldDiscardFriction()) level()\n                .getBlockState(blockPosBelowThatAffectsMyMovement)\n                .block\n                .getFriction() * it else it\n        } ?: 0.0F\n    } ?: 0.0F\n\n    private fun updateFlyStatus(player: ServerPlayer) {\n        val fly = (player.isJump() && mountController.canFly()) || noGravity || onFly\n        if (delegate is Mob) delegate.isNoAi = fly\n        else delegate.isNoGravity = fly\n        onFly = fly && !delegate.onGround()\n        if (onFly) delegate.resetFallDistance()\n    }\n\n    override fun tick() {\n        delegate.removalReason?.let {\n            if (!isRemoved) remove(it)\n            return\n        }\n        val controller = controllingPassenger\n        if (jumpDelay > 0) jumpDelay--\n        interaction.isInvisible = delegate.isInvisible\n        if (controller is ServerPlayer && !isDeadOrDying && mountController.canControl()) {\n            if (delegate is Mob) delegate.navigation.stop()\n            mountControl(controller)\n        } else initialSetup()\n        yRot = bone.rotation().y\n        yHeadRot = yRot\n        yBodyRot = yRot\n        val pos = relativePosition()\n        val minusHeight = source.minY * bone.hitBoxScale()\n        setPos(\n            pos.x.toDouble(),\n            pos.y.toDouble() + minusHeight,\n            pos.z.toDouble()\n        )\n        BlockGetter.forEachBlockIntersectedBetween(\n            oldPosition(),\n            position(),\n            boundingBox\n        ) { pos, step ->\n            if (BetterModelBukkit.IS_PAPER) applier.advanceStep(step, pos)\n            level().getBlockState(pos).entityInside(level(), pos, delegate, applier)\n        }\n        applier.applyAndClear(delegate)\n        if (isInLava) delegate.lavaHurt()\n        firstTick = false\n        listener.sync(craftEntity)\n    }\n\n    private fun rideInput(player: ServerPlayer, travelVector: Vec3) = mountController.move(\n        if (onFly) MountController.MoveType.FLY else MountController.MoveType.DEFAULT,\n        player.bukkitEntity.wrap(),\n        (delegate.bukkitEntity as org.bukkit.entity.LivingEntity).wrap(),\n        Vector3f(\n            player.xMovement(),\n            player.yMovement(),\n            player.zMovement()\n        ),\n        Vector3f(\n            travelVector.x.toFloat(),\n            travelVector.y.toFloat(),\n            travelVector.z.toFloat()\n        )\n    ).mul(movementSpeed()).rotateY(-Math.toRadians(player.yRot.toDouble()).toFloat())\n\n    override fun remove(reason: RemovalReason, cause: EntityRemoveEvent.Cause?) {\n        initialSetup()\n        listener.handle(HitBoxRemoveEvent(craftEntity))\n        interaction.remove(reason)\n        super.remove(reason, cause)\n    }\n\n    override fun getBukkitLivingEntity(): CraftLivingEntity = bukkitEntity\n    override fun getBukkitEntity(): CraftLivingEntity = craftEntity as CraftLivingEntity\n    override fun getBukkitEntityRaw(): CraftLivingEntity = bukkitEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun isDeadOrDying(): Boolean {\n        return ifLivingEntity { isDeadOrDying } == true\n    }\n\n    override fun hide(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            hideEntity(plugin, bukkitEntity)\n            hideEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun show(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            showEntity(plugin, bukkitEntity)\n            showEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        val interact = HitBoxInteractAtEvent(\n            (player.bukkitEntity as org.bukkit.entity.Player).wrap(), craftEntity, when (hand) {\n                MAIN_HAND -> ModelInteractionHand.RIGHT\n                OFF_HAND -> ModelInteractionHand.LEFT\n            }, vec.toBukkit()\n        )\n        if (!listener.handle(interact)) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand, vec))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, cause: EntityPotionEffectEvent.Cause): Boolean {\n        return ifLivingEntity { addEffect(effectInstance, cause) } == true\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, entity: Entity?): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause,\n        fireEvent: Boolean\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause, fireEvent) } == true\n    }\n\n    override fun hurtServer(world: ServerLevel, source: DamageSource, amount: Float): Boolean {\n        if (source.entity === delegate || delegate.isInvulnerable) return false\n        if (source.entity === controllingPassenger && !mountController.canBeDamagedByRider()) return false\n        val ds = ModelDamageSourceImpl(source)\n        val event = HitBoxDamagedEvent(craftEntity, ds, amount)\n        if (!listener.handle(event)) return false\n        return ifLivingEntity { hurtServer(world, source, event.damage) } == true\n    }\n\n    override fun deflection(projectile: Projectile): ProjectileDeflection {\n        if (projectile.owner === delegate) return ProjectileDeflection.NONE\n        return ifLivingEntity { deflection(projectile) } ?: ProjectileDeflection.NONE\n    }\n\n    override fun getHealth(): Float {\n        return ifLivingEntity { health } ?: super.getHealth()\n    }\n\n    override fun makeBoundingBox(vec3: Vec3): AABB {\n        return if (!initialized) {\n            super.makeBoundingBox(vec3)\n        } else {\n            val scale = bone.hitBoxScale()\n            AABB(\n                vec3.x + source.minX * scale,\n                vec3.y,\n                vec3.z + source.minZ * scale,\n                vec3.x + source.maxX * scale,\n                vec3.y + source.y() * scale,\n                vec3.z + source.maxZ * scale\n            ).apply {\n                if (CONFIG.debug().has(DebugConfig.DebugOption.HITBOX)) {\n                    bukkitEntity.world.spawnParticle(Particle.DUST, minX, minY, minZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                    bukkitEntity.world.spawnParticle(Particle.DUST, maxX, maxY, maxZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                }\n            }\n        }\n    }\n    override fun getDefaultDimensions(pose: Pose): EntityDimensions = if (initialized) dimensions else super.getDefaultDimensions(pose)\n\n    override fun removeHitBox() {\n        source().task {\n            dismountAll()\n            remove(ifLivingEntity { removalReason } ?: RemovalReason.KILLED)\n        }\n    }\n\n    private inline fun <T> ifLivingEntity(block: LivingEntity.() -> T): T? {\n        return if (delegate.valid) (delegate as? LivingEntity)?.block() else null\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/HitBoxInteraction.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.nms.HitBox\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.Interaction\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftInteraction\n\ninternal class HitBoxInteraction(\n    val delegate: HitBoxImpl\n) : Interaction(EntityType.INTERACTION, delegate.level()) {\n\n    init {\n        persist = false\n    }\n\n    private val craftEntity: CraftInteraction by lazy {\n        object : CraftInteraction(Bukkit.getServer() as CraftServer, this), HitBox by delegate {}\n    }\n\n    override fun getBukkitEntity(): CraftEntity = craftEntity\n    override fun getBukkitEntityRaw(): CraftEntity = craftEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun tick() {\n        val dimension = delegate.dimensions\n        width = dimension.width\n        height = dimension.height\n        yRot = delegate.yRot\n        xRot = delegate.xRot\n        setSharedFlagOnFire(delegate.remainingFireTicks > 0)\n    }\n\n    override fun skipAttackInteraction(entity: Entity): Boolean {\n        return if (entity is Player) {\n            entity.attack(delegate)\n            true\n        } else false\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        delegate.interact(player, hand)\n        return InteractionResult.FAIL\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        delegate.interactAt(player, vec, hand)\n        return InteractionResult.FAIL\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/ModAnimationBundlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.nms.ModAnimationBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.RegistryFriendlyByteBuf\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket\nimport net.minecraft.server.MinecraftServer\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\ninternal class ModAnimationBundlerImpl(initialCapacity: Int) : ModAnimationBundler {\n\n    companion object {\n\n        const val KEY = \"modelengine:bulk_data\"\n\n        const val PACKET_TYPE_BULK_DATA = 0x00\n\n        const val FIELD_TRANSLATION = 1 shl 0\n        const val FIELD_LEFT_ROTATION = 1 shl 1\n        const val FIELD_SCALE = 1 shl 2\n        const val FIELD_TRANSFORM_DURATION = 1 shl 4\n\n        private val EMPTY_BUILD_TASK: (FriendlyByteBuf) -> Unit = {}\n    }\n\n    private val packet by lazy {\n        useByteBuf { buffer ->\n            ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC.decode(\n                RegistryFriendlyByteBuf(\n                    buffer,\n                    MinecraftServer.getServer().registryAccess()\n                ).apply {\n                    writeUtf(KEY)\n                    useByteBuf {\n                        it.writeByte(PACKET_TYPE_BULK_DATA)\n                        it.writeVarInt(builderList.size)\n                        builderList.forEach { builder -> builder(it) }\n                        writeBytes(it)\n                    }\n                }\n            )\n        }\n    }\n\n    private val builderList = ArrayList<(FriendlyByteBuf) -> Unit>(initialCapacity)\n\n    override fun send(player: PlatformPlayer) {\n        (player.unwarp() as CraftPlayer).handle.connection.send(packet)\n    }\n\n    fun append(id: Int, scope: Appender.() -> Unit) {\n        val build = Appender(id).apply(scope).build()\n        if (build !== EMPTY_BUILD_TASK) builderList += build\n    }\n\n    class Appender(\n        val entityId: Int,\n    ) {\n        private var mask = 0\n        private var buildTask = EMPTY_BUILD_TASK\n        private val isEmpty get() = buildTask === EMPTY_BUILD_TASK\n\n        fun appendPosition(vector: Vector3f) {\n            mask = mask or FIELD_TRANSLATION\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendScale(vector: Vector3f) {\n            mask = mask or FIELD_SCALE\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendRotation(quaternion: Quaternionf) {\n            mask = mask or FIELD_LEFT_ROTATION\n            task {\n                writeFloat(it, quaternion.x)\n                writeFloat(it, quaternion.y)\n                writeFloat(it, quaternion.z)\n                writeFloat(it, quaternion.w)\n            }\n        }\n\n        fun appendDuration(duration: Int) {\n            mask = mask or FIELD_TRANSFORM_DURATION\n            task {\n                writeVarInt(it, duration)\n            }\n        }\n\n        fun build(): (FriendlyByteBuf) -> Unit {\n            if (isEmpty) return EMPTY_BUILD_TASK\n            val m = mask\n            val t = buildTask\n            return {\n                writeVarInt(it,entityId)\n                writeByte(it, m)\n                t(it)\n            }\n        }\n\n        private fun task(task: (FriendlyByteBuf) -> Unit) {\n            if (isEmpty) {\n                buildTask = task\n                return\n            }\n            val last = buildTask\n            buildTask = {\n                last(it)\n                task(it)\n            }\n        }\n\n        private fun writeFloat(buf: FriendlyByteBuf, float: Float) {\n            buf.writeShort(MathUtil.floatToHalf(float).toInt())\n        }\n\n        private fun writeVarInt(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeVarInt(duration)\n        }\n\n        private fun writeByte(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeByte(duration)\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/ModelDamageSourceImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.event.ModelDamageSource\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport net.minecraft.world.damagesource.DamageSource\nimport org.bukkit.craftbukkit.util.CraftLocation\n\ninternal class ModelDamageSourceImpl(\n    private val source: DamageSource\n) : ModelDamageSource {\n    override fun getCausingEntity(): PlatformEntity? = source.entity?.bukkitEntity?.wrap()\n    override fun getDirectEntity(): PlatformEntity? = source.directEntity?.bukkitEntity?.wrap()\n    override fun getDamageLocation(): PlatformLocation? = source.sourcePositionRaw()?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun getSourceLocation(): PlatformLocation? = source.sourcePosition?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun isIndirect(): Boolean = !source.isDirect\n    override fun getFoodExhaustion(): Float = source.foodExhaustion\n    override fun scalesWithDifficulty(): Boolean = source.scalesWithDifficulty()\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/ModelDisplayImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.ModelDisplay\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformItemTransform\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.util.Brightness\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport org.joml.Quaternionf\nimport org.joml.Vector3d\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicBoolean\n\ninternal class ModelDisplayImpl(\n    private val pos: Vector3d,\n    val display: ItemDisplay,\n    val yOffset: Double\n) : ModelDisplay {\n\n    private val entityData = display.entityData\n    private val entityDataLock = SingleLock()\n    private val forceGlow = AtomicBoolean()\n    private val forceInvisibility = AtomicBoolean()\n\n    private val oldPos = Vector3d(pos)\n\n    override fun id(): Int = display.id\n    override fun uuid(): UUID = display.uuid\n    override fun rotate(rotation: ModelRotation, bundler: PacketBundler) {\n        display.xRot = rotation.x\n        display.yRot = rotation.y\n        bundler += ClientboundMoveEntityPacket.Rot(\n            display.id,\n            rotation.packedY(),\n            rotation.packedX(),\n            display.onGround\n        )\n    }\n\n    override fun invisible(invisible: Boolean) {\n        if (forceInvisibility.compareAndSet(!invisible, invisible)) {\n            entityDataLock.accessToLock {\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPotionEffect(entity: BaseEntity) {\n        val beforeInvisible = display.isInvisible\n        val afterInvisible = entity.invisible()\n        entityDataLock.accessToLock {\n            display.setGlowingTag(entity.glow() || forceGlow.get())\n            if (CONFIG.followMobInvisibility() && beforeInvisible != afterInvisible) {\n                display.isInvisible = afterInvisible\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPosition(location: PlatformLocation) {\n        oldPos.set(pos)\n        pos.set(location.x(), location.y(), location.z())\n    }\n\n\n    override fun spawn(showItem: Boolean, bundler: PacketBundler) {\n        bundler += addPacket\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    override fun teleport(location: PlatformLocation, bundler: PacketBundler) {\n        display.moveTo(\n            location.x(),\n            location.y(),\n            location.z(),\n            location.yaw(),\n            0F\n        )\n        bundler += ClientboundTeleportEntityPacket.teleport(display.id, PositionMoveRotation.of(display), emptySet(), display.onGround)\n    }\n\n    override fun sendPosition(adapter: BaseEntity, bundler: PacketBundler) {\n        val handle = adapter.handle() as Entity\n        if (oldPos.distanceSquared(pos) < 1e-8) return\n        bundler += ClientboundEntityPositionSyncPacket(\n            display.id,\n            PositionMoveRotation.of(handle),\n            handle.onGround()\n        )\n    }\n\n    override fun display(transform: PlatformItemTransform) {\n        entityDataLock.accessToLock {\n            display.itemTransform = ItemDisplayContext.BY_ID.apply(transform.ordinal)\n        }\n    }\n\n    override fun moveDuration(duration: Int) {\n        entityDataLock.accessToLock {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = duration\n        }\n    }\n\n    override fun item(itemStack: PlatformItemStack) {\n        entityDataLock.accessToLock {\n            display.itemStack = itemStack.unwarp().asVanilla()\n        }\n    }\n\n    override fun brightness(block: Int, sky: Int) {\n        entityDataLock.accessToLock {\n            display.brightnessOverride = if (block < 0 && sky < 0) null else Brightness(\n                block,\n                sky\n            )\n        }\n    }\n\n    override fun viewRange(range: Float) {\n        entityDataLock.accessToLock {\n            display.viewRange = range\n        }\n    }\n\n    override fun shadowRadius(radius: Float) {\n        entityDataLock.accessToLock {\n            display.shadowRadius = radius\n        }\n    }\n\n    override fun glow(glow: Boolean) {\n        if (!forceGlow.compareAndSet(!glow, glow)) return\n        entityDataLock.accessToLock {\n            display.setGlowingTag(display.isCurrentlyGlowing || glow)\n        }\n    }\n\n    override fun glowColor(glowColor: Int) {\n        entityDataLock.accessToLock {\n            display.glowColorOverride = glowColor\n        }\n    }\n\n    override fun billboard(billboard: PlatformBillboard) {\n        entityDataLock.accessToLock {\n            display.billboardConstraints = Display.BillboardConstraints.BY_ID.apply(billboard.ordinal)\n        }\n    }\n\n    override fun createTransformer(): DisplayTransformer = DisplayTransformerImpl(display)\n\n    override fun invisible(): Boolean = entityDataLock.accessToLock {\n        display.isInvisible || forceInvisibility.get() || display.itemStack.`is`(Items.AIR)\n    }\n\n    override fun sendDirtyEntityData(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                clean = true,\n                itemFilter = { it.isDirty },\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(!invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    override fun sendEntityData(showItem: Boolean, bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(showItem && !invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    private fun List<SynchedEntityData.DataValue<*>>.markVisible(showItem: Boolean) = map {\n        if (it.id == ITEM_SERIALIZER.id) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.ITEM_STACK,\n            if (showItem) display.itemStack else EMPTY_ITEM\n        ) else it\n    }\n\n    private val addPacket\n        get() = ClientboundAddEntityPacket(\n            display.id,\n            display.uuid,\n            pos.x,\n            pos.y + yOffset,\n            pos.z,\n            display.xRot,\n            display.yRot,\n            display.type,\n            0,\n            display.deltaMovement,\n            display.yHeadRot.toDouble()\n        )\n\n    private val removePacket = ClientboundRemoveEntitiesPacket(display.id)\n\n    private class DisplayTransformerImpl(\n        source: ItemDisplay\n    ) : DisplayTransformer {\n        private val id = source.id\n        private val entityData = TransformationData()\n        private val entityDataLock = SingleLock()\n\n        override fun transform(\n            duration: Int,\n            position: Vector3f,\n            scale: Vector3f,\n            rotation: Quaternionf,\n            bundler: AnimationBundler\n        ) {\n            entityDataLock.accessToLock {\n                entityData.transform(\n                    duration,\n                    position,\n                    scale,\n                    rotation\n                )\n                entityData.packDirty(id, bundler)\n            }\n        }\n\n        override fun sendTransformation(bundler: PacketBundler) {\n            entityDataLock.accessToLock {\n                entityData.pack()\n            }?.run {\n                bundler += ClientboundSetEntityDataPacket(id, this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/ModelGameProfile.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport com.mojang.authlib.GameProfile\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\n\ninternal data class ModelGameProfile(\n    private val gameProfile: GameProfile\n) : ModelProfile {\n\n    private val info = ModelProfileInfo(gameProfile.id, gameProfile.name)\n    private val skin by lazy {\n        gameProfile.properties[\"textures\"].firstOrNull()?.let {\n            BetterModel.platform().profileManager().skin(it.value)\n        } ?: ModelProfileSkin.EMPTY\n    }\n\n    override fun info(): ModelProfileInfo = info\n\n    override fun skin(): ModelProfileSkin = skin\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/ModelNametagImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport com.mojang.math.Transformation\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.BonePosition\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.nms.ModelNametag\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.EntityUtil\nimport net.kyori.adventure.text.Component\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\ninternal class ModelNametagImpl(\n    private val bone: RenderedBone\n) : ModelNametag {\n    private companion object {\n        private val emptyVector = Vector3f()\n        private val emptyTransformation = Transformation(\n            Vector3f(-1F / 40F, -0.2F - 1F / 40F, 0F),\n            null,\n            null,\n            null\n        )\n    }\n\n    private val viewedPlayer = ConcurrentHashMap.newKeySet<UUID>()\n    private val display = Display.TextDisplay(\n        EntityType.TEXT_DISPLAY,\n        MinecraftServer.getServer().overworld()\n    ).apply {\n        entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n        setTransformation(emptyTransformation)\n        billboardConstraints = Display.BillboardConstraints.CENTER\n    }\n    private val posCache = BoneMovement()\n    private var alwaysVisible = false\n    private var location = BetterModel.platform().adapter().zero()\n\n    override fun component(component: Component?) {\n        display.text = component?.asVanilla() ?: VanillaComponent.empty()\n    }\n\n    override fun teleport(location: PlatformLocation) {\n        this.location = location\n    }\n\n    override fun alwaysVisible(alwaysVisible: Boolean) {\n        this.alwaysVisible = alwaysVisible\n    }\n\n    override fun send(player: PlatformPlayer) {\n        if (display.text == VanillaComponent.empty()) return\n        val hb = bone.group.hitBoxPoint\n        val pos = bone.worldPosition(BonePosition(emptyVector, hb, player.uuid()), posCache)\n        display.moveTo(Vec3(\n            location.x() + pos.x,\n            location.y() + pos.y,\n            location.z() + pos.z\n        ))\n        val inPoint = alwaysVisible || EntityUtil.isCustomNameVisible(player.location(), location)\n        when {\n            inPoint && viewedPlayer.add(player.uuid()) -> bundlerOfNotNull(\n                addPacket,\n                display.entityData.pack()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            inPoint -> bundlerOfNotNull(\n                ClientboundEntityPositionSyncPacket(display.id, PositionMoveRotation.of(display), false),\n                display.entityData.packDirty()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            viewedPlayer.remove(player.uuid()) -> bundlerOf(removePacket)\n            else -> null\n        }?.send(player)\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    private val addPacket get() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        display.x,\n        display.y,\n        display.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket get() = ClientboundRemoveEntitiesPacket(display.id)\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/NMSImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup\nimport com.mojang.authlib.GameProfile\nimport io.netty.channel.ChannelDuplexHandler\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.channel.ChannelPromise\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.*\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.network.Connection\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.resources.ResourceLocation\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl\nimport net.minecraft.util.ARGB\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport net.minecraft.world.item.component.CustomModelData\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.level.entity.LevelEntityGetter\nimport net.minecraft.world.level.entity.LevelEntityGetterAdapter\nimport net.minecraft.world.level.entity.PersistentEntitySectionManager\nimport org.bukkit.craftbukkit.CraftWorld\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Vector3d\nimport java.util.*\nimport java.util.function.Consumer\nimport java.util.function.IntConsumer\n\nclass NMSImpl : NMS {\n\n    companion object {\n        private const val INJECT_NAME = \"bettermodel_channel_handler\"\n\n        //Spigot\n        private val getGameProfile: (Player) -> GameProfile = createAdaptedFieldGetter { it.gameProfile }\n        private val getConnection: (ServerCommonPacketListenerImpl) -> Connection = createAdaptedFieldGetter { it.connection }\n        private val spigotChunkAccess = ServerLevel::class.java.fields.firstOrNull {\n            it.type == PersistentEntitySectionManager::class.java\n        }?.apply {\n            isAccessible = true\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        private val ServerLevel.levelGetter\n            get(): LevelEntityGetter<Entity> {\n                return if (BetterModelBukkit.IS_PAPER) {\n                    `moonrise$getEntityLookup`()\n                } else {\n                    spigotChunkAccess?.get(this)?.let {\n                        (it as PersistentEntitySectionManager<*>).entityGetter as LevelEntityGetter<Entity>\n                    } ?: throw RuntimeException(\"LevelEntityGetter\")\n                }\n            }\n        private val getEntityById: (LevelEntityGetter<Entity>, Int) -> Entity? = if (BetterModelBukkit.IS_PAPER) { g, i ->\n            (g as EntityLookup)[i]\n        } else LevelEntityGetterAdapter::class.java.declaredFields.first {\n            net.minecraft.world.level.entity.EntityLookup::class.java.isAssignableFrom(it.type)\n        }.let {\n            it.isAccessible = true\n            { e, i ->\n                (it[e] as net.minecraft.world.level.entity.EntityLookup<*>).getEntity(i) as? Entity\n            }\n        }\n        private fun Int.toEntity(level: ServerLevel) = getEntityById(level.levelGetter, this)\n        //Spigot\n        private val hitBoxData by lazy {\n            ItemDisplay(EntityType.ITEM_DISPLAY, MinecraftServer.getServer().overworld()).run {\n                entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n                entityData.nonDefaultValues!!\n            }\n        }\n    }\n\n    override fun hide(channel: PlayerChannelHandler, registry: EntityTrackerRegistry) {\n        val target = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf()\n        target.entityData.pack(\n            valueFilter = { it.id == SHARED_FLAG }\n        )?.let {\n            list += ClientboundSetEntityDataPacket(target.id, it).toRegistryDataPacket(channel.uuid(), registry)\n        }\n        if (target is LivingEntity) {\n            val packet = if (registry.hideOption(channel.uuid()).equipment) target.toEmptyEquipmentPacket() else target.toEquipmentPacket()\n            packet?.let { list += it }\n        }\n        list.send(channel.player())\n    }\n\n    private fun ClientboundSetEntityDataPacket.toRegistryDataPacket(uuid: UUID, registry: EntityTrackerRegistry) = ClientboundSetEntityDataPacket(id, packedItems().map {\n        if (it.id == SHARED_FLAG) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.BYTE,\n            registry.entityFlag(uuid, it.value() as Byte)\n        ) else it\n    })\n\n    inner class PlayerChannelHandlerImpl(\n        private val player: CraftPlayer\n    ) : PlayerChannelHandler, ChannelDuplexHandler() {\n        private val connection = player.handle.connection\n        private val uuid = player.uniqueId\n        private val base = adapt(player.wrap())\n\n        init {\n            val pipeline = getConnection(connection).channel.pipeline()\n            pipeline.addBefore(pipeline.first { it.value is Connection }.key, INJECT_NAME, this)\n        }\n\n        override fun close() {\n            val channel = getConnection(connection).channel\n            channel.eventLoop().submit {\n                channel.pipeline().remove(INJECT_NAME)\n            }\n        }\n\n        override fun base(): BasePlayer = base\n        override fun isModEnabled(): Boolean = player.listeningPluginChannels.contains(\"modelengine:bulk_data\")\n\n        private val playerModel get() = connection.player.id.toRegistry()\n\n        private fun Int.toPlayerEntity() = toEntity(connection.player.serverLevel())\n        private fun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n        private inline fun Int.toRegistry(\n            ifHitBox: (Entity) -> Unit = {}\n        ) = (EntityTrackerRegistry.registry(this) ?: toPlayerEntity()?.let {\n            if (it is HitBox) ifHitBox(it)\n            it.toRegistry()\n        })?.takeIf {\n            it.isSpawned(player.uniqueId)\n        }\n\n        override fun sendEntityData(registry: EntityTrackerRegistry) {\n            val handle = registry.entity().handle() as? Entity ?: return\n            val list = bundlerOf(\n                ClientboundSetPassengersPacket(handle)\n            )\n            handle.entityData.pack(\n                valueFilter = { it.id == SHARED_FLAG }\n            )?.let {\n                list += ClientboundSetEntityDataPacket(handle.id, it)\n            }\n            if (handle is LivingEntity) handle.toEquipmentPacket()?.let {\n                list += it\n            }\n            list.send(player.wrap())\n        }\n\n        private fun <T : ClientGamePacketListener> Packet<in T>.handle(): Packet<in T>? {\n            when (this) {\n                is ClientboundBundlePacket -> return if (subPackets() is Keyed) this else ClientboundBundlePacket(subPackets().mapNotNull {\n                    it.handle()\n                })\n                is ClientboundAddEntityPacket -> {\n                    val entity = id.toPlayerEntity() ?: return this\n                    if (entity is HitBox) return entity.toFakeAddPacket()\n                    val wrap = entity.bukkitEntity.wrap()\n                    BetterModel.registry(wrap).ifPresent {\n                        wrap.taskLater(1) {\n                            it.spawn(player.wrap())\n                        }\n                    }\n                }\n                is ClientboundRemoveEntitiesPacket -> {\n                    entityIds\n                        .asSequence()\n                        .mapNotNull map@ {\n                            it.toRegistry {\n                                return@map null\n                            }\n                        }\n                        .forEach {\n                            it.remove()\n                        }\n                }\n                is ClientboundSetPassengersPacket -> {\n                    vehicle.toRegistry()?.let {\n                        return it.mountPacket(it.entity().handle() as? Entity ?: return this, array = passengers)\n                    }\n                }\n                is ClientboundUpdateAttributesPacket if entityId.toPlayerEntity() is HitBox -> return null\n                is ClientboundSetEntityDataPacket -> id.toRegistry {\n                    return ClientboundSetEntityDataPacket(id, hitBoxData)\n                }?.let { registry ->\n                    return toRegistryDataPacket(uuid, registry)\n                }\n                is ClientboundSetEquipmentPacket -> entity.toRegistry {\n                    return null\n                }?.let {\n                    if (it.hideOption(uuid).equipment()) (it.entity().handle() as? LivingEntity)?.toEmptyEquipmentPacket()?.let { packet ->\n                        return packet\n                    }\n                }\n                is ClientboundRespawnPacket -> playerModel?.let {\n                    bundlerOf(it.mountPacket(connection.player)).send(player.wrap())\n                }\n                is ClientboundContainerSetSlotPacket if isEquipment(connection.player) && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetSlotPacket(containerId, stateId, slot, EMPTY_ITEM)\n                }\n                is ClientboundContainerSetContentPacket if containerId == 0 && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetContentPacket(\n                        containerId,\n                        stateId,\n                        items.apply {\n                            PLAYER_EQUIPMENT_SLOT.forEach(IntConsumer { set(it, EMPTY_ITEM) })\n                            set(connection.player.hotbarSlot, EMPTY_ITEM)\n                        },\n                        carriedItem\n                    )\n                }\n            }\n            return this\n        }\n\n        override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {\n            super.write(ctx, if (msg is Packet<*>) msg.handle() ?: return else msg, promise)\n        }\n\n        override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {\n            fun EntityTrackerRegistry.updatePlayerLimb() = BetterModel.platform().scheduler().asyncTaskLater(1) {\n                if (isClosed) return@asyncTaskLater\n                player.handle.containerMenu.sendAllDataToRemote()\n                trackers().forEach { tracker ->\n                    tracker.update(TrackerUpdateAction.itemMapping()) { bone ->\n                        !bone.itemMapper.fixed()\n                    }\n                }\n            }\n            when (msg) {\n                is ServerboundSetCarriedItemPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) {\n                            connection.send(ClientboundSetHeldSlotPacket(player.inventory.heldItemSlot))\n                            return\n                        }\n                        registry.updatePlayerLimb()\n                    }\n                }\n                is ServerboundPlayerActionPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) return\n                        registry.updatePlayerLimb()\n                    }\n                }\n            }\n            super.channelRead(ctx, msg)\n        }\n\n        private fun EntityTrackerRegistry.remove() {\n            remove(player.wrap())\n        }\n    }\n\n    override fun mount(registry: EntityTrackerRegistry, bundler: PacketBundler) {\n        val entity = registry.entity().handle()\n        if (entity is Entity) bundler += registry.mountPacket(entity)\n    }\n\n    private fun EntityTrackerRegistry.mountPacket(entity: Entity, array: IntArray = entity.passengers.filter {\n        EntityTrackerRegistry.registry(it.uuid) == null\n    }.map {\n        it.id\n    }.toIntArray()): ClientboundSetPassengersPacket {\n        return useByteBuf { buffer ->\n            buffer.writeVarInt(entity.id)\n            buffer.writeVarIntArray(displays()\n                .mapToInt {\n                    (it as ModelDisplayImpl).display.id\n                }.toArray() + array)\n            ClientboundSetPassengersPacket.STREAM_CODEC.decode(buffer)\n        }\n    }\n\n    override fun inject(player: PlatformPlayer): PlayerChannelHandlerImpl = PlayerChannelHandlerImpl(player.unwarp() as CraftPlayer)\n\n    override fun createBundler(initialCapacity: Int): PacketBundler = bundlerOf(initialCapacity)\n    override fun createParallelBundler(threshold: Int): PacketBundler = parallelBundlerOf(threshold)\n    override fun createModAnimationBuilder(initialCapacity: Int): ModAnimationBundler = ModAnimationBundlerImpl(initialCapacity)\n\n    override fun create(location: PlatformLocation, yOffset: Double, initialConsumer: Consumer<ModelDisplay>): ModelDisplay = ModelDisplayImpl(\n        Vector3d(location.x(), location.y(), location.z()),\n        ItemDisplay(EntityType.ITEM_DISPLAY, (location.world().unwarp() as CraftWorld).handle).apply {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n            billboardConstraints = Display.BillboardConstraints.FIXED\n            valid = true\n            yRot = location.yaw()\n            itemTransform = ItemDisplayContext.FIXED\n        },\n        yOffset\n    ).apply {\n        initialConsumer.accept(this)\n        display.entityData.packDirty()\n    }\n\n    override fun createNametag(bone: RenderedBone): ModelNametag = ModelNametagImpl(bone)\n\n    override fun tint(itemStack: PlatformItemStack, rgb: Int): PlatformItemStack {\n        return itemStack.unwarp().asVanilla().apply {\n            set(DataComponents.DYED_COLOR, DyedItemColor(rgb))\n            set(DataComponents.CUSTOM_MODEL_DATA, get(DataComponents.CUSTOM_MODEL_DATA)?.let {\n                CustomModelData(it.floats, it.flags, it.strings, it.colors\n                    .run {\n                        if (rgb == 0xFFFFFF) this else map { color ->\n                            ARGB.multiply(color, rgb) and 0xFFFFFF\n                        }\n                    }\n                    .ifEmpty { listOf(rgb) })\n            })\n        }.asBukkit().wrap()\n    }\n\n    override fun createHitBox(entity: BaseEntity, bone: RenderedBone, boundingBox: ModelBoundingBox, mountController: MountController, listener: HitBoxListener): HitBox? {\n        val handle = entity.handle() as? Entity ?: return null\n        return HitBoxImpl(\n            boundingBox.center(),\n            bone,\n            listener,\n            handle,\n            mountController\n        ).craftEntity\n    }\n    override fun version(): NMSVersion = NMSVersion.V1_21_R4\n\n    override fun adapt(entity: PlatformEntity): BaseBukkitEntity {\n        val craft = entity.unwarp() as CraftEntity\n        return BaseEntityImpl(craft)\n    }\n\n    override fun adapt(player: PlatformPlayer): BasePlayer {\n        val craft = player.unwarp() as CraftPlayer\n        return BasePlayerImpl(\n            craft,\n            dirtyChecked(\n                { getGameProfile(craft.handle) },\n                { ModelGameProfile(it) },\n                { a, b -> a == b && a.properties[\"texture\"] === b.properties[\"texture\"]}\n            ),\n            dirtyChecked({ craft.handle.toCustomisation() }, { PlayerSkinParts(it) })\n        )\n    }\n\n    override fun profile(player: PlatformPlayer): ModelProfile = ModelGameProfile(getGameProfile((player.unwarp() as CraftPlayer).handle))\n\n    override fun createSkinItem(model: String, floats: List<Float>, flags: List<Boolean>, strings: List<String>, colors: List<Int>): TransformedItemStack {\n        return VanillaItemStack(Items.PLAYER_HEAD).run {\n            set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(floats, flags, strings, colors))\n            set(DataComponents.ITEM_MODEL, ResourceLocation.parse(model))\n            TransformedItemStack.of(asBukkit().wrap())\n        }\n    }\n\n    override fun isProxyOnlineMode(): Boolean = ONLINE_MODE\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/PacketBundlers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.kyori.adventure.key.Key\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.network.PacketSendListener\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\nprivate val KEY = Key.key(\"bettermodel\")\n\ninternal fun bundlerOfNotNull(vararg packets: ClientPacket?) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.filterNotNull().toMutableList())\ninternal fun bundlerOf(vararg packets: ClientPacket) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.toMutableList())\ninternal fun bundlerOf(size: Int) = SimpleBundler(ArrayList(size))\ninternal fun parallelBundlerOf(threshold: Int) = ParallelBundler(threshold)\n\ninternal operator fun PacketBundler.plusAssign(other: ClientPacket) {\n    when (this) {\n        is SimpleBundler -> add(other)\n        is ParallelBundler -> add(other)\n        else -> throw RuntimeException(\"unsupported bundler.\")\n    }\n}\ninternal fun Packet<*>.assumeSize() = when (this) {\n    is ClientboundSetEntityDataPacket -> packedItems.size\n    is ClientboundSetEquipmentPacket -> slots.size\n    else -> 1\n}\n\ninternal interface PluginBundlePacketImpl : Iterable<ClientPacket>, Keyed {\n    val bundlePacket: ClientboundBundlePacket\n    fun size(): Int\n    fun isEmpty(): Boolean\n    fun add(other: ClientPacket)\n}\n\ninternal class SimpleBundler(\n    private val list: MutableList<ClientPacket>\n) : PacketBundler, PluginBundlePacketImpl {\n    override val bundlePacket = ClientboundBundlePacket(this)\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        connection.send(bundlePacket, PacketSendListener.thenRun(onSuccess))\n    }\n    override fun isEmpty(): Boolean = list.isEmpty()\n    override fun size(): Int = list.size\n    override fun key(): Key = KEY\n    override fun iterator(): MutableIterator<ClientPacket> = list.iterator()\n    override fun add(other: ClientPacket) {\n        list += other\n    }\n}\n\ninternal class ParallelBundler(\n    private val threshold: Int\n) : PacketBundler {\n    private val subBundlers = mutableListOf<PluginBundlePacketImpl>()\n    private var sizeAssume = 0\n    private val newBundler get() = bundlerOf().apply {\n        sizeAssume = 0\n        subBundlers += this\n    }\n    private var selectedBundler = newBundler\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        subBundlers.forEach {\n            connection.send(it.bundlePacket)\n        }\n    }\n    override fun isEmpty(): Boolean = selectedBundler.isEmpty()\n    override fun size(): Int = subBundlers.sumOf(PluginBundlePacketImpl::size)\n    fun add(other: ClientPacket) {\n        (if (sizeAssume > threshold) newBundler else selectedBundler)\n            .apply { selectedBundler = this }\n            .add(other)\n        sizeAssume += other.assumeSize()\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/PlayerArmorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.item.equipment.EquipmentAssets\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\ninternal data class PlayerArmorImpl(\n    private val player: CraftPlayer\n) : PlayerArmor {\n\n    override fun helmet(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.HEAD).toArmorItem()\n    }\n\n    override fun leggings(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.LEGS).toArmorItem()\n    }\n\n    override fun chestplate(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.CHEST).toArmorItem()\n    }\n\n    override fun boots(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.FEET).toArmorItem()\n    }\n\n    private fun VanillaItemStack.toArmorItem(): ArmorItem? = get(DataComponents.EQUIPPABLE)?.assetId?.map {\n        val trim = get(DataComponents.TRIM)\n        ArmorItem(\n            get(DataComponents.DYED_COLOR)?.rgb ?: if (it === EquipmentAssets.LEATHER) DyedItemColor.LEATHER_COLOR else 0xFFFFFF,\n            it.location().path,\n            trim?.pattern?.value()?.assetId?.path,\n            trim?.material?.value()?.assets?.base?.suffix\n        )\n    }?.orElse(null)\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/ProfiledImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\n\n\ninternal class ProfiledImpl(\n    private val playerArmor: PlayerArmor,\n    private val modelProfile: () -> ModelProfile,\n    private val playerSkinParts: () -> PlayerSkinParts\n) : Profiled {\n\n    override fun profile(): ModelProfile = modelProfile()\n    override fun armors(): PlayerArmor = playerArmor\n    override fun skinParts(): PlayerSkinParts = playerSkinParts()\n}\n"
  },
  {
    "path": "nms/v1_21_R4/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R4/TypeAliases.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R4\n\nimport net.minecraft.network.chat.Component\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientGamePacketListener\nimport net.minecraft.world.item.ItemStack\n\ninternal typealias VanillaItemStack = ItemStack\ninternal typealias BukkitItemStack = org.bukkit.inventory.ItemStack\ninternal typealias ClientPacket = Packet<ClientGamePacketListener>\ninternal typealias VanillaComponent = Component\ninternal typealias AdventureComponent = net.kyori.adventure.text.Component\n"
  },
  {
    "path": "nms/v1_21_R5/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    alias(libs.plugins.convention.paperweight)\n}\n\ndependencies {\n    paperweight.paperDevBundle(\"1.21.8-R0.1-SNAPSHOT\")\n}\n\ntasks {\n    compileJava {\n        options.release = 21\n    }\n    compileKotlin {\n        compilerOptions.jvmTarget = JvmTarget.JVM_21\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/java/kr/toxicity/model/bukkit/nms/v1_21_R5/AbstractHitBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\npublic abstract class AbstractHitBox extends ArmorStand implements HitBox {\n\n    AbstractHitBox(@NotNull Level level) {\n        super(EntityType.ARMOR_STAND, level);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final boolean equals(@Nullable Object other) {\n        return super.equals(other);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final int hashCode() {\n        return super.hashCode();\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/BaseEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.persistence.PersistentDataHolder\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.stream.Stream\n\ninternal data class BaseEntityImpl(\n    private val delegate: CraftEntity\n) : BaseBukkitEntity, PersistentDataHolder by delegate {\n    override fun customName(): AdventureComponent? = handle().run {\n        if (this is ServerPlayer) (customName ?: name).asAdventure() else customName?.asAdventure()?.takeIf {\n            isCustomNameVisible\n        }\n    }\n\n    override fun entity(): org.bukkit.entity.Entity = delegate\n    override fun handle(): Entity = delegate.vanillaEntity\n    override fun uuid(): UUID = delegate.uniqueId\n    override fun id(): Int = handle().id\n    override fun dead(): Boolean = (handle() as? LivingEntity)?.isDeadOrDying == true || handle().removalReason != null || !handle().valid\n    override fun invisible(): Boolean = handle().isInvisible || (handle() as? LivingEntity)?.hasEffect(MobEffects.INVISIBILITY) == true\n    override fun glow(): Boolean = handle().isCurrentlyGlowing\n\n    override fun onWalk(): Boolean {\n        return handle().isWalking()\n    }\n\n    override fun scale(): Double {\n        val handle = handle()\n        return if (handle is LivingEntity) handle.scale.toDouble() else 1.0\n    }\n\n    override fun pitch(): Float = handle().xRot\n    override fun ground(): Boolean = handle().onGround()\n    override fun bodyYaw(): Float = handle().let { if (it is LivingEntity) it.yBodyRot else it.yRot }\n    override fun yaw(): Float = handle().yRot\n    override fun headYaw(): Float = handle().let { if (it is LivingEntity) it.yHeadRot else it.yRot }\n    override fun fly(): Boolean = handle().isFlying\n\n    override fun damageTick(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        val duration = handle.invulnerableDuration.toFloat()\n        if (duration <= 0F) return 0F\n        val knockBack = 1 - (handle.getAttribute(Attributes.KNOCKBACK_RESISTANCE)?.value?.toFloat() ?: 0F)\n        return handle.invulnerableTime.toFloat() / duration * knockBack\n    }\n\n    override fun walkSpeed(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        if (!handle.onGround) return 1F\n        val speed = handle.getEffect(MobEffects.SPEED)?.amplifier ?: 0\n        val slow = handle.getEffect(MobEffects.SLOWNESS)?.amplifier ?: 0\n        return (1F + (speed - slow) * 0.2F)\n            .coerceAtLeast(0.2F)\n            .coerceAtMost(2F)\n    }\n\n    override fun passengerPosition(dest: Vector3f): Vector3f {\n        return handle().passengerPosition(dest)\n    }\n\n    override fun platform(): PlatformEntity = delegate.wrap()\n    override fun trackedBy(): Stream<PlatformPlayer> = delegate.trackedBy.stream().map { it.wrap() }\n    override fun location(): PlatformLocation = delegate.location.wrap()\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/BasePlayerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitPlayer\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport net.minecraft.util.Mth\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.bukkit.entity.Player\nimport java.util.stream.Stream\n\ninternal data class BasePlayerImpl(\n    private val delegate: CraftPlayer,\n    private val profile: () -> ModelProfile,\n    private val skinParts: () -> PlayerSkinParts\n) : BaseBukkitEntity by BaseEntityImpl(delegate), BaseBukkitPlayer, Profiled by ProfiledImpl(PlayerArmorImpl(delegate), profile, skinParts) {\n\n    override fun entity(): Player = delegate\n\n    override fun updateInventory() {\n        delegate.handle.containerMenu.sendAllDataToRemote()\n    }\n\n    override fun platform(): PlatformPlayer = delegate.wrap()\n\n    override fun trackedBy(): Stream<PlatformPlayer> = Stream.concat(\n        Stream.of(delegate),\n        delegate.trackedBy.stream()\n    ).map {\n        it.wrap()\n    }\n\n    override fun bodyYaw(): Float {\n        val handle = delegate.handle\n        var yaw = -45 * handle.xMovement()\n        if (handle.zMovement() < 0) yaw *= -1\n        return Mth.wrapDegrees(handle.yHeadRot + yaw)\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/BukkitWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.bukkit.platform.*\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter.adapt\nimport kr.toxicity.model.api.bukkit.platform.BukkitItemStack\nimport kr.toxicity.model.api.platform.*\nimport org.bukkit.Location\nimport org.bukkit.OfflinePlayer\nimport org.bukkit.World\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.LivingEntity\nimport org.bukkit.entity.Player\nimport org.bukkit.inventory.ItemStack\n\ninternal fun Entity.wrap() = adapt(this)\ninternal fun LivingEntity.wrap() = adapt(this)\ninternal fun OfflinePlayer.wrap() = adapt(this)\ninternal fun Player.wrap() = adapt(this)\ninternal fun Location.wrap() = adapt(this)\ninternal fun World.wrap() = adapt(this)\ninternal fun ItemStack.wrap() = adapt(this)\n\ninternal fun PlatformEntity.unwarp(): Entity = (this as BukkitEntity).source()\ninternal fun PlatformLivingEntity.unwarp(): LivingEntity = (this as BukkitLivingEntity).source()\ninternal fun PlatformOfflinePlayer.unwarp(): OfflinePlayer = (this as BukkitOfflinePlayer).source()\ninternal fun PlatformPlayer.unwarp(): Player = (this as BukkitPlayer).source()\ninternal fun PlatformLocation.unwarp(): Location = (this as BukkitLocation).source()\ninternal fun PlatformWorld.unwarp(): World = (this as BukkitWorld).source()\ninternal fun PlatformItemStack.unwarp(): ItemStack = (this as BukkitItemStack).source()\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/EntityData.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\nimport java.lang.reflect.Field\n\ninternal fun Field.toEntityDataAccessor() = run {\n    isAccessible = true\n    get(null) as EntityDataAccessor<*>\n}\n\ninternal fun Class<*>.accessors() = declaredFields.filter { f ->\n    EntityDataAccessor::class.java.isAssignableFrom(f.type)\n}.map {\n    it.toEntityDataAccessor()\n}\n\ninternal val DISPLAY_SET = Display::class.java.accessors()\ninternal val SHARED_FLAG = Entity::class.java.accessors().first().id\ninternal val ITEM_DISPLAY_ID = ItemDisplay::class.java.accessors().map {\n    it.id\n}\ninternal val ITEM_SERIALIZER = ItemDisplay::class.java.accessors().first()\ninternal val ITEM_ENTITY_DATA = buildList {\n    add(SHARED_FLAG)\n    addAll(ITEM_DISPLAY_ID)\n    add(Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID.id)\n    DISPLAY_SET.subList(7, DISPLAY_SET.size).mapTo(this) { it.id }\n}.toIntSet()\n\n@Suppress(\"UNCHECKED_CAST\")\nprivate val DISPLAY_INTERPOLATION_DELAY = (DISPLAY_SET.first() as EntityDataAccessor<Int>).run {\n    SynchedEntityData.DataValue(id, serializer, 0)\n}\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_INTERPOLATION_DURATION = DISPLAY_SET[1] as EntityDataAccessor<Int>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_TRANSLATION = DISPLAY_SET[3] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_SCALE = DISPLAY_SET[4] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_ROTATION = DISPLAY_SET[5] as EntityDataAccessor<Quaternionf>\n\n\ninternal class TransformationData {\n\n    private var _duration = 0\n    private val duration get() = SynchedEntityData.DataValue(DISPLAY_INTERPOLATION_DURATION.id, DISPLAY_INTERPOLATION_DURATION.serializer, _duration)\n    private val translation = Item(Vector3f(), DISPLAY_TRANSLATION, MathUtil::isSimilar, Vector3f::set)\n    private val scale = Item(Vector3f(), DISPLAY_SCALE, MathUtil::isSimilar, Vector3f::set)\n    private val rotation = Item(Quaternionf(), DISPLAY_ROTATION, MathUtil::isSimilar, Quaternionf::set)\n\n    fun packDirty(entityId: Int, dest: AnimationBundler) {\n        val i = translation.cleanIndex + scale.cleanIndex + rotation.cleanIndex\n        if (i == 0) return\n        (dest.mod as ModAnimationBundlerImpl).append(entityId) {\n            dest.standard += ClientboundSetEntityDataPacket(entityId, buildList(i + 2) {\n                add(DISPLAY_INTERPOLATION_DELAY)\n                translation.value?.let { appendPosition(it.value); add(it) }\n                rotation.value?.let { appendRotation(it.value); add(it) }\n                scale.value?.let { appendScale(it.value); add(it) }\n                appendDuration(_duration); add(duration)\n            })\n        }\n    }\n\n    fun transform(\n        duration: Int,\n        translation: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf\n    ) {\n        _duration = duration\n        this.translation.set(translation)\n        this.scale.set(scale)\n        this.rotation.set(rotation)\n    }\n\n    fun pack() = listOf(\n        DISPLAY_INTERPOLATION_DELAY,\n        duration,\n        translation.forceValue,\n        scale.forceValue,\n        rotation.forceValue\n    )\n\n    private class Item<T : Any>(\n        initialValue: T,\n        private val accessor: EntityDataAccessor<T>,\n        private val dirtyChecker: (T, T) -> Boolean,\n        private val setter: (T, T) -> Unit\n    ) {\n        private val _t: T = initialValue\n        private var _dirty = false\n\n        val dirty get() = _dirty\n        val cleanIndex get() = if (dirty) 1 else 0\n        val value get() = if (_dirty) {\n            _dirty = false\n            forceValue\n        } else null\n        val forceValue get() = SynchedEntityData.DataValue(accessor.id, accessor.serializer, _t)\n\n        fun set(other: T) {\n            if (dirtyChecker(_t, other)) return\n            _dirty = true\n            setter(_t, other)\n        }\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport io.netty.buffer.Unpooled\nimport io.papermc.paper.adventure.PaperAdventure\nimport io.papermc.paper.configuration.GlobalConfiguration\nimport it.unimi.dsi.fastutil.ints.IntSet\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.network.syncher.SynchedEntityData.DataItem\nimport net.minecraft.network.syncher.SynchedEntityData.DataValue\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.goal.RangedAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedBowAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal\nimport net.minecraft.world.entity.animal.FlyingAnimal\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.inventory.CraftItemStack\nimport org.bukkit.craftbukkit.util.CraftChatMessage\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(noinline paperGetter: (T) -> R): (T) -> R {\n    return if (BetterModelBukkit.IS_PAPER) paperGetter else createAdaptedFieldGetter()\n}\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(): (T) -> R {\n    return T::class.java.declaredFields.first {\n        R::class.java.isAssignableFrom(it.type)\n    }.apply {\n        isAccessible = true\n    }.let { getter ->\n        { t ->\n            getter[t] as R\n        }\n    }\n}\n\ninternal fun <H, T> dirtyChecked(hash: () -> H, function: (H) -> T, equalityChecker: (H, H) -> Boolean = { a, b -> a == b }): () -> T {\n    val lock = Any()\n    var h = hash()\n    var value = function(h)\n    return {\n        val newH = hash()\n        if (equalityChecker(h, newH)) value else synchronized(lock) {\n            h = newH\n            value = function(h)\n            value\n        }\n    }\n}\n\ninternal val CONFIG get() = BetterModel.config()\ninternal val EMPTY_ITEM = VanillaItemStack.EMPTY\ninternal fun BukkitItemStack.asVanilla() = CraftItemStack.asNMSCopy(this)\ninternal fun VanillaItemStack.asBukkit() = CraftItemStack.asCraftMirror(this)\n\ninternal val ONLINE_MODE by lazy(LazyThreadSafetyMode.NONE) {\n    if (BetterModelBukkit.IS_PAPER) GlobalConfiguration.get().proxies.isProxyOnlineMode else Bukkit.getOnlineMode()\n}\n\ninternal fun List<Int>.toIntSet(): IntSet = IntSet.of(*toIntArray())\n\ninternal fun Entity.passengerPosition(dest: Vector3f): Vector3f {\n    return attachments.get(EntityAttachment.PASSENGER, 0, yRot).let { v ->\n        dest.set(v.x.toFloat(), v.y.toFloat(), v.z.toFloat())\n    }\n}\n\nprivate val DATA_ITEMS = SynchedEntityData::class.java.declaredFields.first {\n    it.type.isArray\n}.apply {\n    isAccessible = true\n}\n\ninternal fun SynchedEntityData.pack(\n    clean: Boolean = false,\n    itemFilter: (DataItem<*>) -> Boolean = { true },\n    valueFilter: (DataValue<*>) -> Boolean = { true },\n    required: (List<Pair<DataItem<*>, DataValue<*>>>) -> Boolean = { it.isNotEmpty() }\n): List<DataValue<*>>? = (DATA_ITEMS[this] as Array<*>)\n    .mapNotNull map@ {\n        val item = (it as? DataItem<*>)?.takeIf(itemFilter) ?: return@map null\n        val value = item.value().takeIf(valueFilter) ?: return@map null\n        item to value\n    }\n    .takeIf(required)\n    ?.map {\n        if (clean) it.first.isDirty = false\n        it.second\n    }\n\ninternal fun Entity.isWalking(): Boolean {\n    return controllingPassenger?.isWalking() ?: when (this) {\n        is Mob -> navigation.isInProgress || goalSelector.availableGoals.any {\n            it.isRunning && when (it.goal) {\n                is RangedAttackGoal, is RangedCrossbowAttackGoal<*>, is RangedBowAttackGoal<*> -> true\n                else -> false\n            }\n        }\n        is ServerPlayer -> xMovement() != 0F || zMovement() != 0F\n        else -> false\n    }\n}\n\ninternal fun ServerPlayer.xMovement(): Float {\n    val leftMovement: Boolean = lastClientInput.left()\n    val rightMovement: Boolean = lastClientInput.right()\n    return if (leftMovement == rightMovement) 0F else if (leftMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.yMovement(): Float = if (isJump()) 1F else if (lastClientInput.shift) -1F else 0F\n\ninternal fun ServerPlayer.zMovement(): Float {\n    val forwardMovement: Boolean = lastClientInput.forward()\n    val backwardMovement: Boolean = lastClientInput.backward()\n    return if (forwardMovement == backwardMovement) 0F else if (forwardMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.isJump() = lastClientInput.jump()\n\ninternal val Entity.isFlying: Boolean\n    get() = when (this) {\n        is FlyingAnimal -> isFlying\n        is Mob -> isNoAi\n        is Player -> abilities.flying\n        is LivingEntity -> isFallFlying\n        else -> false\n    }\n\ninternal val CraftEntity.vanillaEntity: Entity\n    get() = if (BetterModelBukkit.IS_PAPER) handleRaw else handle\n\ninternal fun Entity.moveTo(vec: Vec3) = snapTo(vec)\ninternal fun Entity.moveTo(x: Double, y: Double, z: Double, yaw: Float, pitch: Float) = snapTo(x, y, z, yaw, pitch)\n\ninternal inline fun <T> useByteBuf(block: (FriendlyByteBuf) -> T): T {\n    val buffer = FriendlyByteBuf(Unpooled.buffer())\n    return try {\n        block(buffer)\n    } finally {\n        buffer.release()\n    }\n}\n\ninternal fun EntityTrackerRegistry.entityFlag(uuid: UUID, byte: Byte): Byte {\n    var b = byte.toInt()\n    val hideOption = hideOption(uuid)\n    if (hideOption.fire()) b = b and 1.inv()\n    if (hideOption.visibility()) b = b or (1 shl 5)\n    if (hideOption.glowing()) b = b and (1 shl 6).inv()\n    return b.toByte()\n}\n\ninternal fun Vector3f.toVanilla() = Vec3(x.toDouble(), y.toDouble(), z.toDouble())\ninternal fun Vec3.toBukkit() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())\n\ninternal inline fun LivingEntity.toEquipmentPacket(mapper: (EquipmentSlot) -> ItemStack? = { getItemBySlot(it).takeUnless { item -> item.isEmpty } }): ClientboundSetEquipmentPacket? {\n    val equip = EquipmentSlot.entries.mapNotNull {\n        mapper(it)?.let { item -> com.mojang.datafixers.util.Pair.of(it, item) }\n    }\n    return if (equip.isNotEmpty()) ClientboundSetEquipmentPacket(id, equip) else null\n}\ninternal fun LivingEntity.toEmptyEquipmentPacket() = toEquipmentPacket { ItemStack.EMPTY }\n\ninternal val Player.hotbarSlot get() = inventory.selectedSlot + 36\ninternal val PLAYER_EQUIPMENT_SLOT = IntSet.of(45, 5, 6, 7, 8)\ninternal fun ClientboundContainerSetSlotPacket.isEquipment(player: Player) = containerId == 0 && (PLAYER_EQUIPMENT_SLOT.contains(slot) || slot == player.hotbarSlot)\n\ninternal fun Entity.toFakeAddPacket() = ClientboundAddEntityPacket(\n    id,\n    uuid,\n    x,\n    y,\n    z,\n    xRot,\n    yRot,\n    EntityType.ITEM_DISPLAY,\n    0,\n    deltaMovement,\n    yHeadRot.toDouble()\n)\n\ninternal fun Player.toCustomisation() = entityData.get(Player.DATA_PLAYER_MODE_CUSTOMISATION).toInt()\n\ninternal fun VanillaComponent.asAdventure() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asAdventure(this)\n} else {\n    GsonComponentSerializer.gson().deserialize(CraftChatMessage.toJSON(this))\n}\n\ninternal fun AdventureComponent.asVanilla() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asVanilla(this)\n} else {\n    CraftChatMessage.fromJSON(GsonComponentSerializer.gson().serialize(this))\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/HitBoxImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport io.papermc.paper.event.entity.EntityKnockbackEvent\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.event.hitbox.*\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.api.nms.ModelInteractionHand\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.network.protocol.game.ServerboundInteractPacket\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionHand.MAIN_HAND\nimport net.minecraft.world.InteractionHand.OFF_HAND\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.damagesource.DamageSource\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.entity.projectile.Projectile\nimport net.minecraft.world.entity.projectile.ProjectileDeflection\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.level.BlockGetter\nimport net.minecraft.world.phys.AABB\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.Color\nimport org.bukkit.Particle\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftArmorStand\nimport org.bukkit.craftbukkit.entity.CraftLivingEntity\nimport org.bukkit.event.entity.CreatureSpawnEvent\nimport org.bukkit.event.entity.EntityPotionEffectEvent\nimport org.bukkit.event.entity.EntityRemoveEvent\nimport org.bukkit.plugin.Plugin\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal class HitBoxImpl(\n    private val source: ModelBoundingBox,\n    private val bone: RenderedBone,\n    private var listener: HitBoxListener,\n    private val delegate: Entity,\n    private var mountController: MountController\n) : AbstractHitBox(delegate.level()) {\n    private val posCache = BoneMovement()\n    private var initialized = false\n    private var jumpDelay = 0\n    private var mounted = false\n    private var collision = ifLivingEntity { collides } == true\n    private var noGravity = if (delegate is Mob) delegate.isNoAi else delegate.isNoGravity\n    private var forceDismount = false\n    private var onFly = false\n\n    val craftEntity: HitBox by lazy {\n        object : CraftArmorStand(Bukkit.getServer() as CraftServer, this), HitBox by this {}\n    }\n    val dimensions: EntityDimensions get() = source.run {\n        EntityDimensions(\n            (x() + z()).toFloat() / 2,\n            y().toFloat(),\n            delegate.eyeHeight,\n            EntityAttachments.createDefault(0F, 0F),\n            false\n        ).scale(bone.hitBoxScale())\n    }\n    private val interaction by lazy {\n        HitBoxInteraction(this)\n    }\n    private val applier = InsideBlockEffectApplier.StepBasedCollector()\n\n    init {\n        moveTo(delegate.position())\n        isInvisible = true\n        persist = false\n        isSilent = true\n        initialized = true\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        level().addFreshEntity(interaction.apply {\n            moveTo(delegate.position())\n        }, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        interaction.startRiding(this)\n        listener.handle(HitBoxCreateEvent(this))\n    }\n\n    private fun initialSetup() {\n        if (mounted) {\n            mounted = false\n            if (delegate is Mob) delegate.isNoAi = noGravity\n            else delegate.isNoGravity = noGravity\n            ifLivingEntity { collides = collision }\n        }\n    }\n\n    override fun id(): Int = id\n    override fun uuid(): UUID = uuid\n    override fun source(): PlatformEntity = delegate.bukkitEntity.wrap()\n    override fun positionSource(): RenderedBone = bone\n    override fun forceDismount(): Boolean = forceDismount\n    override fun mountController(): MountController = mountController\n    override fun hasMountDriver(): Boolean = controllingPassenger != null\n    override fun mountController(controller: MountController) {\n        this.mountController = controller\n    }\n    override fun relativePosition(): Vector3f = delegate.position().run {\n        bone.hitBoxPosition(posCache).add(x.toFloat(), y.toFloat(), z.toFloat())\n    }\n    override fun listener(): HitBoxListener = listener\n    override fun listener(listener: HitBoxListener) {\n        this.listener = listener\n    }\n    override fun getItemBySlot(slot: EquipmentSlot): ItemStack = ItemStack.EMPTY\n    override fun setItemSlot(slot: EquipmentSlot, stack: ItemStack) {\n    }\n    override fun getMainArm(): HumanoidArm = HumanoidArm.RIGHT\n\n    override fun mount(entity: PlatformEntity) {\n        if (controllingPassenger != null) return\n        if (interaction.bukkitEntity.addPassenger(entity.unwarp())) {\n            if (mountController.canControl()) {\n                mounted = true\n                noGravity = delegate.isNoGravity\n                ifLivingEntity {\n                    collision = collides\n                    collides = false\n                }\n            }\n            listener.handle(HitBoxMountEvent(this, entity))\n        }\n    }\n\n    override fun dismount(entity: PlatformEntity) {\n        forceDismount = true\n        if (interaction.bukkitEntity.removePassenger(entity.unwarp())) listener.handle(HitBoxDismountEvent(this, entity))\n        forceDismount = false\n    }\n\n    override fun dismountAll() {\n        forceDismount = true\n        interaction.passengers.forEach {\n            it.stopRiding(true)\n            listener.handle(HitBoxDismountEvent(this, it.bukkitEntity.wrap()))\n        }\n        forceDismount = false\n    }\n\n    override fun setRemainingFireTicks(remainingFireTicks: Int) {\n        delegate.remainingFireTicks = remainingFireTicks\n    }\n\n    override fun getRemainingFireTicks(): Int {\n        return delegate.remainingFireTicks\n    }\n\n    override fun knockback(\n        d0: Double,\n        d1: Double,\n        d2: Double,\n        attacker: Entity?,\n        cause: EntityKnockbackEvent.Cause\n    ) {\n        if (attacker === delegate) return\n        ifLivingEntity { knockback(d0, d1, d2, attacker, cause) }\n    }\n\n    override fun push(pushingEntity: Entity) {\n        if (pushingEntity === delegate) return\n        delegate.push(pushingEntity)\n    }\n\n    override fun push(x: Double, y: Double, z: Double, pushingEntity: Entity?) {\n        if (pushingEntity === delegate) return\n        delegate.push(x, y, z, pushingEntity)\n    }\n\n    override fun isCollidable(ignoreClimbing: Boolean): Boolean {\n        return delegate.isCollidable(ignoreClimbing)\n    }\n\n    override fun canCollideWith(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWith(entity)\n    }\n\n    override fun canCollideWithBukkit(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWithBukkit(entity)\n    }\n\n    private fun checkCollide(entity: Entity): Boolean {\n        return entity !== delegate\n                && passengers.none { it === entity }\n                && delegate.passengers.none { it === entity }\n                && (entity !is HitBoxImpl || entity.delegate !== delegate)\n    }\n\n    override fun getActiveEffects(): Collection<MobEffectInstance> {\n        return ifLivingEntity { getActiveEffects() } ?: emptyList()\n    }\n\n    override fun getControllingPassenger(): LivingEntity? {\n        return if (mounted) interaction.firstPassenger as? LivingEntity ?: super.getControllingPassenger() else null\n    }\n\n    override fun onWalk(): Boolean {\n        return isWalking()\n    }\n\n    private fun mountControl(player: ServerPlayer) {\n        if (delegate !is LivingEntity) return\n        val travelVector = Vec3(delegate.xxa.toDouble(), delegate.yya.toDouble(), delegate.zza.toDouble())\n        if (!mountController.canFly() && delegate.isFallFlying) return\n\n        updateFlyStatus(player)\n        val riddenInput = rideInput(player, travelVector)\n        if (riddenInput.length() > 0.01) {\n            delegate.yRot = player.yRot\n            if (onFly) delegate.yHeadRot = player.yRot\n            delegate.move(MoverType.SELF, Vec3(riddenInput.x.toDouble(), riddenInput.y.toDouble(), riddenInput.z.toDouble()))\n        }\n        val dy = delegate.deltaMovement.y + delegate.gravity\n        if (!onFly && mountController.canJump() && (delegate.horizontalCollision || player.isJump()) && dy in 0.0..0.01 && jumpDelay == 0) {\n            jumpDelay = 10\n            delegate.jumpFromGround()\n        }\n    }\n\n    private fun movementSpeed() = ifLivingEntity {\n        getAttribute(Attributes.MOVEMENT_SPEED)?.value?.toFloat()?.let {\n            if (!onFly && !shouldDiscardFriction()) level()\n                .getBlockState(blockPosBelowThatAffectsMyMovement)\n                .block\n                .getFriction() * it else it\n        } ?: 0.0F\n    } ?: 0.0F\n\n    private fun updateFlyStatus(player: ServerPlayer) {\n        val fly = (player.isJump() && mountController.canFly()) || noGravity || onFly\n        if (delegate is Mob) delegate.isNoAi = fly\n        else delegate.isNoGravity = fly\n        onFly = fly && !delegate.onGround()\n        if (onFly) delegate.resetFallDistance()\n    }\n\n    private fun rideInput(player: ServerPlayer, travelVector: Vec3) = mountController.move(\n        if (onFly) MountController.MoveType.FLY else MountController.MoveType.DEFAULT,\n        player.bukkitEntity.wrap(),\n        (delegate.bukkitEntity as org.bukkit.entity.LivingEntity).wrap(),\n        Vector3f(\n            player.xMovement(),\n            player.yMovement(),\n            player.zMovement()\n        ),\n        Vector3f(\n            travelVector.x.toFloat(),\n            travelVector.y.toFloat(),\n            travelVector.z.toFloat()\n        )\n    ).mul(movementSpeed()).rotateY(-Math.toRadians(player.yRot.toDouble()).toFloat())\n\n    override fun tick() {\n        delegate.removalReason?.let {\n            if (!isRemoved) remove(it)\n            return\n        }\n        val controller = controllingPassenger\n        if (jumpDelay > 0) jumpDelay--\n        interaction.isInvisible = delegate.isInvisible\n        if (controller is ServerPlayer && !isDeadOrDying && mountController.canControl()) {\n            if (delegate is Mob) delegate.navigation.stop()\n            mountControl(controller)\n        } else initialSetup()\n        yRot = bone.rotation().y\n        yHeadRot = yRot\n        yBodyRot = yRot\n        val pos = relativePosition()\n        val minusHeight = source.minY * bone.hitBoxScale()\n        setPos(\n            pos.x.toDouble(),\n            pos.y.toDouble() + minusHeight,\n            pos.z.toDouble()\n        )\n        BlockGetter.forEachBlockIntersectedBetween(\n            oldPosition(),\n            position(),\n            boundingBox\n        ) { pos, step ->\n            if (BetterModelBukkit.IS_PAPER) applier.advanceStep(step, pos)\n            level().getBlockState(pos).entityInside(level(), pos, delegate, applier)\n            true\n        }\n        applier.applyAndClear(delegate)\n        if (isInLava) delegate.lavaHurt()\n        firstTick = false\n        listener.sync(craftEntity)\n    }\n\n    override fun remove(reason: RemovalReason, cause: EntityRemoveEvent.Cause?) {\n        initialSetup()\n        listener.handle(HitBoxRemoveEvent(craftEntity))\n        interaction.remove(reason)\n        super.remove(reason, cause)\n    }\n\n    override fun getBukkitLivingEntity(): CraftLivingEntity = bukkitEntity\n    override fun getBukkitEntity(): CraftLivingEntity = craftEntity as CraftLivingEntity\n    override fun getBukkitEntityRaw(): CraftLivingEntity = bukkitEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun isDeadOrDying(): Boolean {\n        return ifLivingEntity { isDeadOrDying } == true\n    }\n\n    override fun hide(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            hideEntity(plugin, bukkitEntity)\n            hideEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun show(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            showEntity(plugin, bukkitEntity)\n            showEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        val interact = HitBoxInteractAtEvent(\n            (player.bukkitEntity as org.bukkit.entity.Player).wrap(), craftEntity, when (hand) {\n                MAIN_HAND -> ModelInteractionHand.RIGHT\n                OFF_HAND -> ModelInteractionHand.LEFT\n            }, vec.toBukkit()\n        )\n        if (!listener.handle(interact)) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand, vec))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, cause: EntityPotionEffectEvent.Cause): Boolean {\n        return ifLivingEntity { addEffect(effectInstance, cause) } == true\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, entity: Entity?): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause,\n        fireEvent: Boolean\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause, fireEvent) } == true\n    }\n\n    override fun hurtServer(world: ServerLevel, source: DamageSource, amount: Float): Boolean {\n        if (source.entity === delegate || delegate.isInvulnerable) return false\n        if (source.entity === controllingPassenger && !mountController.canBeDamagedByRider()) return false\n        val ds = ModelDamageSourceImpl(source)\n        val event = HitBoxDamagedEvent(craftEntity, ds, amount)\n        if (!listener.handle(event)) return false\n        return ifLivingEntity { hurtServer(world, source, event.damage) } == true\n    }\n\n    override fun deflection(projectile: Projectile): ProjectileDeflection {\n        if (projectile.owner?.uuid == delegate.uuid) return ProjectileDeflection.NONE\n        return ifLivingEntity { deflection(projectile) } ?: ProjectileDeflection.NONE\n    }\n\n    override fun getHealth(): Float {\n        return ifLivingEntity { health } ?: super.getHealth()\n    }\n\n    override fun makeBoundingBox(vec3: Vec3): AABB {\n        return if (!initialized) {\n            super.makeBoundingBox(vec3)\n        } else {\n            val scale = bone.hitBoxScale()\n            AABB(\n                vec3.x + source.minX * scale,\n                vec3.y,\n                vec3.z + source.minZ * scale,\n                vec3.x + source.maxX * scale,\n                vec3.y + source.y() * scale,\n                vec3.z + source.maxZ * scale\n            ).apply {\n                if (CONFIG.debug().has(DebugConfig.DebugOption.HITBOX)) {\n                    bukkitEntity.world.spawnParticle(Particle.DUST, minX, minY, minZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                    bukkitEntity.world.spawnParticle(Particle.DUST, maxX, maxY, maxZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                }\n            }\n        }\n    }\n    override fun getDefaultDimensions(pose: Pose): EntityDimensions = if (initialized) dimensions else super.getDefaultDimensions(pose)\n\n    override fun removeHitBox() {\n        source().task {\n            dismountAll()\n            remove(ifLivingEntity { removalReason } ?: RemovalReason.KILLED)\n        }\n    }\n\n    private inline fun <T> ifLivingEntity(block: LivingEntity.() -> T): T? {\n        return if (delegate.valid) (delegate as? LivingEntity)?.block() else null\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/HitBoxInteraction.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.nms.HitBox\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.Interaction\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftInteraction\n\ninternal class HitBoxInteraction(\n    val delegate: HitBoxImpl\n) : Interaction(EntityType.INTERACTION, delegate.level()) {\n\n    init {\n        persist = false\n    }\n\n    private val craftEntity: CraftInteraction by lazy {\n        object : CraftInteraction(Bukkit.getServer() as CraftServer, this), HitBox by delegate {}\n    }\n\n    override fun getBukkitEntity(): CraftEntity = craftEntity\n    override fun getBukkitEntityRaw(): CraftEntity = craftEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun tick() {\n        val dimension = delegate.dimensions\n        width = dimension.width\n        height = dimension.height\n        yRot = delegate.yRot\n        xRot = delegate.xRot\n        setSharedFlagOnFire(delegate.remainingFireTicks > 0)\n    }\n\n    override fun skipAttackInteraction(entity: Entity): Boolean {\n        return if (entity is Player) {\n            entity.attack(delegate)\n            true\n        } else false\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        delegate.interact(player, hand)\n        return InteractionResult.FAIL\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        delegate.interactAt(player, vec, hand)\n        return InteractionResult.FAIL\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/ModAnimationBundlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.nms.ModAnimationBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.RegistryFriendlyByteBuf\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket\nimport net.minecraft.server.MinecraftServer\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\ninternal class ModAnimationBundlerImpl(initialCapacity: Int) : ModAnimationBundler {\n\n    companion object {\n\n        const val KEY = \"modelengine:bulk_data\"\n\n        const val PACKET_TYPE_BULK_DATA = 0x00\n\n        const val FIELD_TRANSLATION = 1 shl 0\n        const val FIELD_LEFT_ROTATION = 1 shl 1\n        const val FIELD_SCALE = 1 shl 2\n        const val FIELD_TRANSFORM_DURATION = 1 shl 4\n\n        private val EMPTY_BUILD_TASK: (FriendlyByteBuf) -> Unit = {}\n    }\n\n    private val packet by lazy {\n        useByteBuf { buffer ->\n            ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC.decode(\n                RegistryFriendlyByteBuf(\n                    buffer,\n                    MinecraftServer.getServer().registryAccess()\n                ).apply {\n                    writeUtf(KEY)\n                    useByteBuf {\n                        it.writeByte(PACKET_TYPE_BULK_DATA)\n                        it.writeVarInt(builderList.size)\n                        builderList.forEach { builder -> builder(it) }\n                        writeBytes(it)\n                    }\n                }\n            )\n        }\n    }\n\n    private val builderList = ArrayList<(FriendlyByteBuf) -> Unit>(initialCapacity)\n\n    override fun send(player: PlatformPlayer) {\n        (player.unwarp() as CraftPlayer).handle.connection.send(packet)\n    }\n\n    fun append(id: Int, scope: Appender.() -> Unit) {\n        val build = Appender(id).apply(scope).build()\n        if (build !== EMPTY_BUILD_TASK) builderList += build\n    }\n\n    class Appender(\n        val entityId: Int,\n    ) {\n        private var mask = 0\n        private var buildTask = EMPTY_BUILD_TASK\n        private val isEmpty get() = buildTask === EMPTY_BUILD_TASK\n\n        fun appendPosition(vector: Vector3f) {\n            mask = mask or FIELD_TRANSLATION\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendScale(vector: Vector3f) {\n            mask = mask or FIELD_SCALE\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendRotation(quaternion: Quaternionf) {\n            mask = mask or FIELD_LEFT_ROTATION\n            task {\n                writeFloat(it, quaternion.x)\n                writeFloat(it, quaternion.y)\n                writeFloat(it, quaternion.z)\n                writeFloat(it, quaternion.w)\n            }\n        }\n\n        fun appendDuration(duration: Int) {\n            mask = mask or FIELD_TRANSFORM_DURATION\n            task {\n                writeVarInt(it, duration)\n            }\n        }\n\n        fun build(): (FriendlyByteBuf) -> Unit {\n            if (isEmpty) return EMPTY_BUILD_TASK\n            val m = mask\n            val t = buildTask\n            return {\n                writeVarInt(it,entityId)\n                writeByte(it, m)\n                t(it)\n            }\n        }\n\n        private fun task(task: (FriendlyByteBuf) -> Unit) {\n            if (isEmpty) {\n                buildTask = task\n                return\n            }\n            val last = buildTask\n            buildTask = {\n                last(it)\n                task(it)\n            }\n        }\n\n        private fun writeFloat(buf: FriendlyByteBuf, float: Float) {\n            buf.writeShort(MathUtil.floatToHalf(float).toInt())\n        }\n\n        private fun writeVarInt(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeVarInt(duration)\n        }\n\n        private fun writeByte(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeByte(duration)\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/ModelDamageSourceImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.event.ModelDamageSource\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport net.minecraft.world.damagesource.DamageSource\nimport org.bukkit.craftbukkit.util.CraftLocation\n\ninternal class ModelDamageSourceImpl(\n    private val source: DamageSource\n) : ModelDamageSource {\n    override fun getCausingEntity(): PlatformEntity? = source.entity?.bukkitEntity?.wrap()\n    override fun getDirectEntity(): PlatformEntity? = source.directEntity?.bukkitEntity?.wrap()\n    override fun getDamageLocation(): PlatformLocation? = source.sourcePositionRaw()?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun getSourceLocation(): PlatformLocation? = source.sourcePosition?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun isIndirect(): Boolean = !source.isDirect\n    override fun getFoodExhaustion(): Float = source.foodExhaustion\n    override fun scalesWithDifficulty(): Boolean = source.scalesWithDifficulty()\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/ModelDisplayImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.ModelDisplay\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformItemTransform\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.util.Brightness\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport org.joml.Quaternionf\nimport org.joml.Vector3d\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicBoolean\n\ninternal class ModelDisplayImpl(\n    private val pos: Vector3d,\n    val display: ItemDisplay,\n    val yOffset: Double\n) : ModelDisplay {\n\n    private val entityData = display.entityData\n    private val entityDataLock = SingleLock()\n    private val forceGlow = AtomicBoolean()\n    private val forceInvisibility = AtomicBoolean()\n\n    private val oldPos = Vector3d(pos)\n\n    override fun id(): Int = display.id\n    override fun uuid(): UUID = display.uuid\n    override fun rotate(rotation: ModelRotation, bundler: PacketBundler) {\n        display.xRot = rotation.x\n        display.yRot = rotation.y\n        bundler += ClientboundMoveEntityPacket.Rot(\n            display.id,\n            rotation.packedY(),\n            rotation.packedX(),\n            display.onGround\n        )\n    }\n\n    override fun invisible(invisible: Boolean) {\n        if (forceInvisibility.compareAndSet(!invisible, invisible)) {\n            entityDataLock.accessToLock {\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPotionEffect(entity: BaseEntity) {\n        val beforeInvisible = display.isInvisible\n        val afterInvisible = entity.invisible()\n        entityDataLock.accessToLock {\n            display.setGlowingTag(entity.glow() || forceGlow.get())\n            if (CONFIG.followMobInvisibility() && beforeInvisible != afterInvisible) {\n                display.isInvisible = afterInvisible\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPosition(location: PlatformLocation) {\n        oldPos.set(pos)\n        pos.set(location.x(), location.y(), location.z())\n    }\n\n    override fun spawn(showItem: Boolean, bundler: PacketBundler) {\n        bundler += addPacket\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    override fun teleport(location: PlatformLocation, bundler: PacketBundler) {\n        display.moveTo(\n            location.x(),\n            location.y(),\n            location.z(),\n            location.yaw(),\n            0F\n        )\n        bundler += ClientboundTeleportEntityPacket.teleport(display.id, PositionMoveRotation.of(display), emptySet(), display.onGround)\n    }\n\n    override fun sendPosition(adapter: BaseEntity, bundler: PacketBundler) {\n        val handle = adapter.handle() as Entity\n        if (oldPos.distanceSquared(pos) < 1e-8) return\n        bundler += ClientboundEntityPositionSyncPacket(\n            display.id,\n            PositionMoveRotation.of(handle),\n            handle.onGround()\n        )\n    }\n\n    override fun display(transform: PlatformItemTransform) {\n        entityDataLock.accessToLock {\n            display.itemTransform = ItemDisplayContext.BY_ID.apply(transform.ordinal)\n        }\n    }\n\n    override fun moveDuration(duration: Int) {\n        entityDataLock.accessToLock {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = duration\n        }\n    }\n\n    override fun item(itemStack: PlatformItemStack) {\n        entityDataLock.accessToLock {\n            display.itemStack = itemStack.unwarp().asVanilla()\n        }\n    }\n\n    override fun brightness(block: Int, sky: Int) {\n        entityDataLock.accessToLock {\n            display.brightnessOverride = if (block < 0 && sky < 0) null else Brightness(\n                block,\n                sky\n            )\n        }\n    }\n\n    override fun viewRange(range: Float) {\n        entityDataLock.accessToLock {\n            display.viewRange = range\n        }\n    }\n\n    override fun shadowRadius(radius: Float) {\n        entityDataLock.accessToLock {\n            display.shadowRadius = radius\n        }\n    }\n\n    override fun glow(glow: Boolean) {\n        if (!forceGlow.compareAndSet(!glow, glow)) return\n        entityDataLock.accessToLock {\n            display.setGlowingTag(display.isCurrentlyGlowing || glow)\n        }\n    }\n\n    override fun glowColor(glowColor: Int) {\n        entityDataLock.accessToLock {\n            display.glowColorOverride = glowColor\n        }\n    }\n\n    override fun billboard(billboard: PlatformBillboard) {\n        entityDataLock.accessToLock {\n            display.billboardConstraints = Display.BillboardConstraints.BY_ID.apply(billboard.ordinal)\n        }\n    }\n\n    override fun createTransformer(): DisplayTransformer = DisplayTransformerImpl(display)\n\n    override fun invisible(): Boolean = entityDataLock.accessToLock {\n        display.isInvisible || forceInvisibility.get() || display.itemStack.`is`(Items.AIR)\n    }\n\n    override fun sendDirtyEntityData(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                clean = true,\n                itemFilter = { it.isDirty },\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(!invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    override fun sendEntityData(showItem: Boolean, bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(showItem && !invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    private fun List<SynchedEntityData.DataValue<*>>.markVisible(showItem: Boolean) = map {\n        if (it.id == ITEM_SERIALIZER.id) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.ITEM_STACK,\n            if (showItem) display.itemStack else EMPTY_ITEM\n        ) else it\n    }\n\n    private val addPacket\n        get() = ClientboundAddEntityPacket(\n            display.id,\n            display.uuid,\n            pos.x,\n            pos.y + yOffset,\n            pos.z,\n            display.xRot,\n            display.yRot,\n            display.type,\n            0,\n            display.deltaMovement,\n            display.yHeadRot.toDouble()\n        )\n\n    private val removePacket = ClientboundRemoveEntitiesPacket(display.id)\n\n    private class DisplayTransformerImpl(\n        source: ItemDisplay\n    ) : DisplayTransformer {\n        private val id = source.id\n        private val entityData = TransformationData()\n        private val entityDataLock = SingleLock()\n\n        override fun transform(\n            duration: Int,\n            position: Vector3f,\n            scale: Vector3f,\n            rotation: Quaternionf,\n            bundler: AnimationBundler\n        ) {\n            entityDataLock.accessToLock {\n                entityData.transform(\n                    duration,\n                    position,\n                    scale,\n                    rotation\n                )\n                entityData.packDirty(id, bundler)\n            }\n        }\n\n        override fun sendTransformation(bundler: PacketBundler) {\n            entityDataLock.accessToLock {\n                entityData.pack()\n            }?.run {\n                bundler += ClientboundSetEntityDataPacket(id, this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/ModelGameProfile.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport com.mojang.authlib.GameProfile\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\n\ninternal data class ModelGameProfile(\n    private val gameProfile: GameProfile\n) : ModelProfile {\n\n    private val info = ModelProfileInfo(gameProfile.id, gameProfile.name)\n    private val skin by lazy {\n        gameProfile.properties[\"textures\"].firstOrNull()?.let {\n            BetterModel.platform().profileManager().skin(it.value)\n        } ?: ModelProfileSkin.EMPTY\n    }\n\n    override fun info(): ModelProfileInfo = info\n\n    override fun skin(): ModelProfileSkin = skin\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/ModelNametagImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport com.mojang.math.Transformation\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.BonePosition\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.nms.ModelNametag\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.EntityUtil\nimport net.kyori.adventure.text.Component\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\ninternal class ModelNametagImpl(\n    private val bone: RenderedBone\n) : ModelNametag {\n    private companion object {\n        private val emptyVector = Vector3f()\n        private val emptyTransformation = Transformation(\n            Vector3f(-1F / 40F, -0.2F - 1F / 40F, 0F),\n            null,\n            null,\n            null\n        )\n    }\n\n    private val viewedPlayer = ConcurrentHashMap.newKeySet<UUID>()\n    private val display = Display.TextDisplay(\n        EntityType.TEXT_DISPLAY,\n        MinecraftServer.getServer().overworld()\n    ).apply {\n        entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n        setTransformation(emptyTransformation)\n        billboardConstraints = Display.BillboardConstraints.CENTER\n    }\n    private val posCache = BoneMovement()\n    private var alwaysVisible = false\n    private var location = BetterModel.platform().adapter().zero()\n\n    override fun component(component: Component?) {\n        display.text = component?.asVanilla() ?: VanillaComponent.empty()\n    }\n\n    override fun teleport(location: PlatformLocation) {\n        this.location = location\n    }\n\n    override fun alwaysVisible(alwaysVisible: Boolean) {\n        this.alwaysVisible = alwaysVisible\n    }\n\n    override fun send(player: PlatformPlayer) {\n        if (display.text == VanillaComponent.empty()) return\n        val hb = bone.group.hitBoxPoint\n        val pos = bone.worldPosition(BonePosition(emptyVector, hb, player.uuid()), posCache)\n        display.moveTo(Vec3(\n            location.x() + pos.x,\n            location.y() + pos.y,\n            location.z() + pos.z\n        ))\n        val inPoint = alwaysVisible || EntityUtil.isCustomNameVisible(player.location(), location)\n        when {\n            inPoint && viewedPlayer.add(player.uuid()) -> bundlerOfNotNull(\n                addPacket,\n                display.entityData.pack()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            inPoint -> bundlerOfNotNull(\n                ClientboundEntityPositionSyncPacket(display.id, PositionMoveRotation.of(display), false),\n                display.entityData.packDirty()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            viewedPlayer.remove(player.uuid()) -> bundlerOf(removePacket)\n            else -> null\n        }?.send(player)\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    private val addPacket get() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        display.x,\n        display.y,\n        display.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket get() = ClientboundRemoveEntitiesPacket(display.id)\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/NMSImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup\nimport com.mojang.authlib.GameProfile\nimport io.netty.channel.ChannelDuplexHandler\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.channel.ChannelPromise\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.*\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.network.Connection\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.resources.ResourceLocation\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl\nimport net.minecraft.util.ARGB\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport net.minecraft.world.item.component.CustomModelData\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.level.entity.LevelEntityGetter\nimport net.minecraft.world.level.entity.LevelEntityGetterAdapter\nimport net.minecraft.world.level.entity.PersistentEntitySectionManager\nimport org.bukkit.craftbukkit.CraftWorld\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Vector3d\nimport java.util.*\nimport java.util.function.Consumer\nimport java.util.function.IntConsumer\n\nclass NMSImpl : NMS {\n\n    companion object {\n        private const val INJECT_NAME = \"bettermodel_channel_handler\"\n\n        //Spigot\n        private val getGameProfile: (Player) -> GameProfile = createAdaptedFieldGetter { it.gameProfile }\n        private val getConnection: (ServerCommonPacketListenerImpl) -> Connection = createAdaptedFieldGetter { it.connection }\n        private val spigotChunkAccess = ServerLevel::class.java.fields.firstOrNull {\n            it.type == PersistentEntitySectionManager::class.java\n        }?.apply {\n            isAccessible = true\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        private val ServerLevel.levelGetter\n            get(): LevelEntityGetter<Entity> {\n                return if (BetterModelBukkit.IS_PAPER) {\n                    `moonrise$getEntityLookup`()\n                } else {\n                    spigotChunkAccess?.get(this)?.let {\n                        (it as PersistentEntitySectionManager<*>).entityGetter as LevelEntityGetter<Entity>\n                    } ?: throw RuntimeException(\"LevelEntityGetter\")\n                }\n            }\n        private val getEntityById: (LevelEntityGetter<Entity>, Int) -> Entity? = if (BetterModelBukkit.IS_PAPER) { g, i ->\n            (g as EntityLookup)[i]\n        } else LevelEntityGetterAdapter::class.java.declaredFields.first {\n            net.minecraft.world.level.entity.EntityLookup::class.java.isAssignableFrom(it.type)\n        }.let {\n            it.isAccessible = true\n            { e, i ->\n                (it[e] as net.minecraft.world.level.entity.EntityLookup<*>).getEntity(i) as? Entity\n            }\n        }\n        private fun Int.toEntity(level: ServerLevel) = getEntityById(level.levelGetter, this)\n        //Spigot\n        private val hitBoxData by lazy {\n            ItemDisplay(EntityType.ITEM_DISPLAY, MinecraftServer.getServer().overworld()).run {\n                entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n                entityData.nonDefaultValues!!\n            }\n        }\n    }\n\n    override fun hide(channel: PlayerChannelHandler, registry: EntityTrackerRegistry) {\n        val target = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf()\n        target.entityData.pack(\n            valueFilter = { it.id == SHARED_FLAG }\n        )?.let {\n            list += ClientboundSetEntityDataPacket(target.id, it).toRegistryDataPacket(channel.uuid(), registry)\n        }\n        if (target is LivingEntity) {\n            val packet = if (registry.hideOption(channel.uuid()).equipment) target.toEmptyEquipmentPacket() else target.toEquipmentPacket()\n            packet?.let { list += it }\n        }\n        list.send(channel.player())\n    }\n\n    private fun ClientboundSetEntityDataPacket.toRegistryDataPacket(uuid: UUID, registry: EntityTrackerRegistry) = ClientboundSetEntityDataPacket(id, packedItems().map {\n        if (it.id == SHARED_FLAG) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.BYTE,\n            registry.entityFlag(uuid, it.value() as Byte)\n        ) else it\n    })\n\n    inner class PlayerChannelHandlerImpl(\n        private val player: CraftPlayer\n    ) : PlayerChannelHandler, ChannelDuplexHandler() {\n        private val connection = player.handle.connection\n        private val uuid = player.uniqueId\n        private val base = adapt(player.wrap())\n\n        init {\n            val pipeline = getConnection(connection).channel.pipeline()\n            pipeline.addBefore(pipeline.first { it.value is Connection }.key, INJECT_NAME, this)\n        }\n\n        override fun close() {\n            val channel = getConnection(connection).channel\n            channel.eventLoop().submit {\n                channel.pipeline().remove(INJECT_NAME)\n            }\n        }\n\n        override fun base(): BasePlayer = base\n        override fun isModEnabled(): Boolean = (if (BetterModelBukkit.IS_PAPER) player.channels() else player.listeningPluginChannels).contains(ModAnimationBundlerImpl.KEY)\n\n        private val playerModel get() = connection.player.id.toRegistry()\n\n        private fun Int.toPlayerEntity() = toEntity(connection.player.level())\n        private fun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n        private inline fun Int.toRegistry(\n            ifHitBox: (Entity) -> Unit = {}\n        ) = (EntityTrackerRegistry.registry(this) ?: toPlayerEntity()?.let {\n            if (it is HitBox) ifHitBox(it)\n            it.toRegistry()\n        })?.takeIf {\n            it.isSpawned(player.uniqueId)\n        }\n\n        override fun sendEntityData(registry: EntityTrackerRegistry) {\n            val handle = registry.entity().handle() as? Entity ?: return\n            val list = bundlerOf(\n                ClientboundSetPassengersPacket(handle)\n            )\n            handle.entityData.pack(\n                valueFilter = { it.id == SHARED_FLAG }\n            )?.let {\n                list += ClientboundSetEntityDataPacket(handle.id, it)\n            }\n            if (handle is LivingEntity) handle.toEquipmentPacket()?.let {\n                list += it\n            }\n            list.send(player.wrap())\n        }\n\n        private fun <T : ClientGamePacketListener> Packet<in T>.handle(): Packet<in T>? {\n            when (this) {\n                is ClientboundBundlePacket -> return if (subPackets() is Keyed) this else ClientboundBundlePacket(subPackets().mapNotNull {\n                    it.handle()\n                })\n                is ClientboundAddEntityPacket -> {\n                    val entity = id.toPlayerEntity() ?: return this\n                    if (entity is HitBox) return entity.toFakeAddPacket()\n                    val wrap = entity.bukkitEntity.wrap()\n                    BetterModel.registry(wrap).ifPresent {\n                        wrap.taskLater(1) {\n                            it.spawn(player.wrap())\n                        }\n                    }\n                }\n                is ClientboundRemoveEntitiesPacket -> {\n                    entityIds\n                        .asSequence()\n                        .mapNotNull map@ {\n                            it.toRegistry {\n                                return@map null\n                            }\n                        }\n                        .forEach {\n                            it.remove()\n                        }\n                }\n                is ClientboundSetPassengersPacket -> {\n                    vehicle.toRegistry()?.let {\n                        return it.mountPacket(it.entity().handle() as? Entity ?: return this, array = passengers)\n                    }\n                }\n                is ClientboundUpdateAttributesPacket if entityId.toPlayerEntity() is HitBox -> return null\n                is ClientboundSetEntityDataPacket -> id.toRegistry {\n                    return ClientboundSetEntityDataPacket(id, hitBoxData)\n                }?.let { registry ->\n                    return toRegistryDataPacket(uuid, registry)\n                }\n                is ClientboundSetEquipmentPacket -> entity.toRegistry {\n                    return null\n                }?.let {\n                    if (it.hideOption(uuid).equipment()) (it.entity().handle() as? LivingEntity)?.toEmptyEquipmentPacket()?.let { packet ->\n                        return packet\n                    }\n                }\n                is ClientboundRespawnPacket -> playerModel?.let {\n                    bundlerOf(it.mountPacket(connection.player)).send(player.wrap())\n                }\n                is ClientboundContainerSetSlotPacket if isEquipment(connection.player) && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetSlotPacket(containerId, stateId, slot, EMPTY_ITEM)\n                }\n                is ClientboundContainerSetContentPacket if containerId == 0 && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetContentPacket(\n                        containerId,\n                        stateId,\n                        items.apply {\n                            PLAYER_EQUIPMENT_SLOT.forEach(IntConsumer { set(it, EMPTY_ITEM) })\n                            set(connection.player.hotbarSlot, EMPTY_ITEM)\n                        },\n                        carriedItem\n                    )\n                }\n            }\n            return this\n        }\n\n        override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {\n            super.write(ctx, if (msg is Packet<*>) msg.handle() ?: return else msg, promise)\n        }\n\n        override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {\n            fun EntityTrackerRegistry.updatePlayerLimb() = BetterModel.platform().scheduler().asyncTaskLater(1) {\n                if (isClosed) return@asyncTaskLater\n                player.handle.containerMenu.sendAllDataToRemote()\n                trackers().forEach { tracker ->\n                    tracker.update(TrackerUpdateAction.itemMapping()) { bone ->\n                        !bone.itemMapper.fixed()\n                    }\n                }\n            }\n            when (msg) {\n                is ServerboundSetCarriedItemPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) {\n                            connection.send(ClientboundSetHeldSlotPacket(player.inventory.heldItemSlot))\n                            return\n                        }\n                        registry.updatePlayerLimb()\n                    }\n                }\n                is ServerboundPlayerActionPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) return\n                        registry.updatePlayerLimb()\n                    }\n                }\n            }\n            super.channelRead(ctx, msg)\n        }\n\n        private fun EntityTrackerRegistry.remove() {\n            remove(player.wrap())\n        }\n    }\n\n    override fun mount(registry: EntityTrackerRegistry, bundler: PacketBundler) {\n        val entity = registry.entity().handle()\n        if (entity is Entity) bundler += registry.mountPacket(entity)\n    }\n\n    private fun EntityTrackerRegistry.mountPacket(entity: Entity, array: IntArray = entity.passengers.filter {\n        EntityTrackerRegistry.registry(it.uuid) == null\n    }.map {\n        it.id\n    }.toIntArray()): ClientboundSetPassengersPacket {\n        return useByteBuf { buffer ->\n            buffer.writeVarInt(entity.id)\n            buffer.writeVarIntArray(displays()\n                .mapToInt {\n                    (it as ModelDisplayImpl).display.id\n                }.toArray() + array)\n            ClientboundSetPassengersPacket.STREAM_CODEC.decode(buffer)\n        }\n    }\n\n    override fun inject(player: PlatformPlayer): PlayerChannelHandlerImpl = PlayerChannelHandlerImpl(player.unwarp() as CraftPlayer)\n\n    override fun createBundler(initialCapacity: Int): PacketBundler = bundlerOf(initialCapacity)\n    override fun createParallelBundler(threshold: Int): PacketBundler = parallelBundlerOf(threshold)\n    override fun createModAnimationBuilder(initialCapacity: Int): ModAnimationBundler = ModAnimationBundlerImpl(initialCapacity)\n\n    override fun create(location: PlatformLocation, yOffset: Double, initialConsumer: Consumer<ModelDisplay>): ModelDisplay = ModelDisplayImpl(\n        Vector3d(location.x(), location.y(), location.z()),\n        ItemDisplay(EntityType.ITEM_DISPLAY, (location.world().unwarp() as CraftWorld).handle).apply {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n            billboardConstraints = Display.BillboardConstraints.FIXED\n            valid = true\n            yRot = location.yaw()\n            itemTransform = ItemDisplayContext.FIXED\n        },\n        yOffset\n    ).apply {\n        initialConsumer.accept(this)\n        display.entityData.packDirty()\n    }\n\n    override fun createNametag(bone: RenderedBone): ModelNametag = ModelNametagImpl(bone)\n\n    override fun tint(itemStack: PlatformItemStack, rgb: Int): PlatformItemStack {\n        return itemStack.unwarp().asVanilla().apply {\n            set(DataComponents.DYED_COLOR, DyedItemColor(rgb))\n            set(DataComponents.CUSTOM_MODEL_DATA, get(DataComponents.CUSTOM_MODEL_DATA)?.let {\n                CustomModelData(it.floats, it.flags, it.strings, it.colors\n                    .run {\n                        if (rgb == 0xFFFFFF) this else map { color ->\n                            ARGB.multiply(color, rgb) and 0xFFFFFF\n                        }\n                    }\n                    .ifEmpty { listOf(rgb) })\n            })\n        }.asBukkit().wrap()\n    }\n\n    override fun createHitBox(entity: BaseEntity, bone: RenderedBone, boundingBox: ModelBoundingBox, mountController: MountController, listener: HitBoxListener): HitBox? {\n        val handle = entity.handle() as? Entity ?: return null\n        return HitBoxImpl(\n            boundingBox.center(),\n            bone,\n            listener,\n            handle,\n            mountController\n        ).craftEntity\n    }\n    override fun version(): NMSVersion = NMSVersion.V1_21_R5\n\n    override fun adapt(entity: PlatformEntity): BaseBukkitEntity {\n        val craft = entity.unwarp() as CraftEntity\n        return BaseEntityImpl(craft)\n    }\n\n    override fun adapt(player: PlatformPlayer): BasePlayer {\n        val craft = player.unwarp() as CraftPlayer\n        return BasePlayerImpl(\n            craft,\n            dirtyChecked(\n                { getGameProfile(craft.handle) },\n                { ModelGameProfile(it) },\n                { a, b -> a == b && a.properties[\"texture\"] === b.properties[\"texture\"]}\n            ),\n            dirtyChecked({ craft.handle.toCustomisation() }, { PlayerSkinParts(it) })\n        )\n    }\n\n    override fun profile(player: PlatformPlayer): ModelProfile = ModelGameProfile(getGameProfile((player.unwarp() as CraftPlayer).handle))\n\n    override fun createSkinItem(model: String, floats: List<Float>, flags: List<Boolean>, strings: List<String>, colors: List<Int>): TransformedItemStack {\n        return VanillaItemStack(Items.PLAYER_HEAD).run {\n            set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(floats, flags, strings, colors))\n            set(DataComponents.ITEM_MODEL, ResourceLocation.parse(model))\n            TransformedItemStack.of(asBukkit().wrap())\n        }\n    }\n\n    override fun isProxyOnlineMode(): Boolean = ONLINE_MODE\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/PacketBundlers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.kyori.adventure.key.Key\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.network.PacketSendListener\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\nprivate val KEY = Key.key(\"bettermodel\")\n\ninternal fun bundlerOfNotNull(vararg packets: ClientPacket?) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.filterNotNull().toMutableList())\ninternal fun bundlerOf(vararg packets: ClientPacket) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.toMutableList())\ninternal fun bundlerOf(size: Int) = SimpleBundler(ArrayList(size))\ninternal fun parallelBundlerOf(threshold: Int) = ParallelBundler(threshold)\n\ninternal operator fun PacketBundler.plusAssign(other: ClientPacket) {\n    when (this) {\n        is SimpleBundler -> add(other)\n        is ParallelBundler -> add(other)\n        else -> throw RuntimeException(\"unsupported bundler.\")\n    }\n}\ninternal fun Packet<*>.assumeSize() = when (this) {\n    is ClientboundSetEntityDataPacket -> packedItems.size\n    is ClientboundSetEquipmentPacket -> slots.size\n    else -> 1\n}\n\ninternal interface PluginBundlePacketImpl : Iterable<ClientPacket>, Keyed {\n    val bundlePacket: ClientboundBundlePacket\n    fun size(): Int\n    fun isEmpty(): Boolean\n    fun add(other: ClientPacket)\n}\n\ninternal class SimpleBundler(\n    private val list: MutableList<ClientPacket>\n) : PacketBundler, PluginBundlePacketImpl {\n    override val bundlePacket = ClientboundBundlePacket(this)\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        connection.send(bundlePacket, PacketSendListener.thenRun(onSuccess))\n    }\n    override fun isEmpty(): Boolean = list.isEmpty()\n    override fun size(): Int = list.size\n    override fun key(): Key = KEY\n    override fun iterator(): MutableIterator<ClientPacket> = list.iterator()\n    override fun add(other: ClientPacket) {\n        list += other\n    }\n}\n\ninternal class ParallelBundler(\n    private val threshold: Int\n) : PacketBundler {\n    private val subBundlers = mutableListOf<PluginBundlePacketImpl>()\n    private var sizeAssume = 0\n    private val newBundler get() = bundlerOf().apply {\n        sizeAssume = 0\n        subBundlers += this\n    }\n    private var selectedBundler = newBundler\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        subBundlers.forEach {\n            connection.send(it.bundlePacket)\n        }\n    }\n    override fun isEmpty(): Boolean = selectedBundler.isEmpty()\n    override fun size(): Int = subBundlers.sumOf(PluginBundlePacketImpl::size)\n    fun add(other: ClientPacket) {\n        (if (sizeAssume > threshold) newBundler else selectedBundler)\n            .apply { selectedBundler = this }\n            .add(other)\n        sizeAssume += other.assumeSize()\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/PlayerArmorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.item.equipment.EquipmentAssets\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\ninternal data class PlayerArmorImpl(\n    private val player: CraftPlayer\n) : PlayerArmor {\n\n    override fun helmet(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.HEAD).toArmorItem()\n    }\n\n    override fun leggings(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.LEGS).toArmorItem()\n    }\n\n    override fun chestplate(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.CHEST).toArmorItem()\n    }\n\n    override fun boots(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.FEET).toArmorItem()\n    }\n\n    private fun VanillaItemStack.toArmorItem(): ArmorItem? = get(DataComponents.EQUIPPABLE)?.assetId?.map {\n        val trim = get(DataComponents.TRIM)\n        ArmorItem(\n            get(DataComponents.DYED_COLOR)?.rgb ?: if (it === EquipmentAssets.LEATHER) DyedItemColor.LEATHER_COLOR else 0xFFFFFF,\n            it.location().path,\n            trim?.pattern?.value()?.assetId?.path,\n            trim?.material?.value()?.assets?.base?.suffix\n        )\n    }?.orElse(null)\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/ProfiledImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\n\n\ninternal class ProfiledImpl(\n    private val playerArmor: PlayerArmor,\n    private val modelProfile: () -> ModelProfile,\n    private val playerSkinParts: () -> PlayerSkinParts\n) : Profiled {\n\n    override fun profile(): ModelProfile = modelProfile()\n    override fun armors(): PlayerArmor = playerArmor\n    override fun skinParts(): PlayerSkinParts = playerSkinParts()\n}\n"
  },
  {
    "path": "nms/v1_21_R5/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R5/TypeAliases.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R5\n\nimport net.minecraft.network.chat.Component\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientGamePacketListener\nimport net.minecraft.world.item.ItemStack\n\ninternal typealias VanillaItemStack = ItemStack\ninternal typealias BukkitItemStack = org.bukkit.inventory.ItemStack\ninternal typealias ClientPacket = Packet<ClientGamePacketListener>\ninternal typealias VanillaComponent = Component\ninternal typealias AdventureComponent = net.kyori.adventure.text.Component\n"
  },
  {
    "path": "nms/v1_21_R6/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    alias(libs.plugins.convention.paperweight)\n}\n\ndependencies {\n    paperweight.paperDevBundle(\"1.21.10-R0.1-SNAPSHOT\")\n}\n\ntasks {\n    compileJava {\n        options.release = 21\n    }\n    compileKotlin {\n        compilerOptions.jvmTarget = JvmTarget.JVM_21\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/java/kr/toxicity/model/bukkit/nms/v1_21_R6/AbstractHitBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\npublic abstract class AbstractHitBox extends ArmorStand implements HitBox {\n\n    AbstractHitBox(@NotNull Level level) {\n        super(EntityType.ARMOR_STAND, level);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final boolean equals(@Nullable Object other) {\n        return super.equals(other);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final int hashCode() {\n        return super.hashCode();\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/BaseEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.persistence.PersistentDataHolder\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.stream.Stream\n\ninternal data class BaseEntityImpl(\n    private val delegate: CraftEntity\n) : BaseBukkitEntity, PersistentDataHolder by delegate {\n    override fun customName(): AdventureComponent? = handle().run {\n        if (this is ServerPlayer) (customName ?: name).asAdventure() else customName?.asAdventure()?.takeIf {\n            isCustomNameVisible\n        }\n    }\n\n    override fun entity(): org.bukkit.entity.Entity = delegate\n    override fun handle(): Entity = delegate.vanillaEntity\n    override fun uuid(): UUID = delegate.uniqueId\n    override fun id(): Int = handle().id\n    override fun dead(): Boolean = (handle() as? LivingEntity)?.isDeadOrDying == true || handle().removalReason != null || !handle().valid\n    override fun invisible(): Boolean = handle().isInvisible || (handle() as? LivingEntity)?.hasEffect(MobEffects.INVISIBILITY) == true\n    override fun glow(): Boolean = handle().isCurrentlyGlowing\n\n    override fun onWalk(): Boolean {\n        return handle().isWalking()\n    }\n\n    override fun scale(): Double {\n        val handle = handle()\n        return if (handle is LivingEntity) handle.scale.toDouble() else 1.0\n    }\n\n    override fun pitch(): Float = handle().xRot\n    override fun ground(): Boolean = handle().onGround()\n    override fun bodyYaw(): Float = handle().let { if (it is LivingEntity) it.yBodyRot else it.yRot }\n    override fun yaw(): Float = handle().yRot\n    override fun headYaw(): Float = handle().let { if (it is LivingEntity) it.yHeadRot else it.yRot }\n    override fun fly(): Boolean = handle().isFlying\n\n    override fun damageTick(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        val duration = handle.invulnerableDuration.toFloat()\n        if (duration <= 0F) return 0F\n        val knockBack = 1 - (handle.getAttribute(Attributes.KNOCKBACK_RESISTANCE)?.value?.toFloat() ?: 0F)\n        return handle.invulnerableTime.toFloat() / duration * knockBack\n    }\n\n    override fun walkSpeed(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        if (!handle.onGround) return 1F\n        val speed = handle.getEffect(MobEffects.SPEED)?.amplifier ?: 0\n        val slow = handle.getEffect(MobEffects.SLOWNESS)?.amplifier ?: 0\n        return (1F + (speed - slow) * 0.2F)\n            .coerceAtLeast(0.2F)\n            .coerceAtMost(2F)\n    }\n\n    override fun passengerPosition(dest: Vector3f): Vector3f {\n        return handle().passengerPosition(dest)\n    }\n\n    override fun platform(): PlatformEntity = delegate.wrap()\n    override fun trackedBy(): Stream<PlatformPlayer> = delegate.trackedBy.stream().map { it.wrap() }\n    override fun location(): PlatformLocation = delegate.location.wrap()\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/BasePlayerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitPlayer\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport net.minecraft.util.Mth\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.bukkit.entity.Player\nimport java.util.stream.Stream\n\ninternal data class BasePlayerImpl(\n    private val delegate: CraftPlayer,\n    private val profile: () -> ModelProfile,\n    private val skinParts: () -> PlayerSkinParts\n) : BaseBukkitEntity by BaseEntityImpl(delegate), BaseBukkitPlayer, Profiled by ProfiledImpl(PlayerArmorImpl(delegate), profile, skinParts) {\n\n    override fun entity(): Player = delegate\n\n    override fun updateInventory() {\n        delegate.handle.containerMenu.sendAllDataToRemote()\n    }\n\n    override fun platform(): PlatformPlayer = delegate.wrap()\n\n    override fun trackedBy(): Stream<PlatformPlayer> = Stream.concat(\n        Stream.of(delegate),\n        delegate.trackedBy.stream()\n    ).map {\n        it.wrap()\n    }\n\n    override fun bodyYaw(): Float {\n        val handle = delegate.handle\n        var yaw = -45 * handle.xMovement()\n        if (handle.zMovement() < 0) yaw *= -1\n        return Mth.wrapDegrees(handle.yHeadRot + yaw)\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/BukkitWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.bukkit.platform.*\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter.adapt\nimport kr.toxicity.model.api.bukkit.platform.BukkitItemStack\nimport kr.toxicity.model.api.platform.*\nimport org.bukkit.Location\nimport org.bukkit.OfflinePlayer\nimport org.bukkit.World\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.LivingEntity\nimport org.bukkit.entity.Player\nimport org.bukkit.inventory.ItemStack\n\ninternal fun Entity.wrap() = adapt(this)\ninternal fun LivingEntity.wrap() = adapt(this)\ninternal fun OfflinePlayer.wrap() = adapt(this)\ninternal fun Player.wrap() = adapt(this)\ninternal fun Location.wrap() = adapt(this)\ninternal fun World.wrap() = adapt(this)\ninternal fun ItemStack.wrap() = adapt(this)\n\ninternal fun PlatformEntity.unwarp(): Entity = (this as BukkitEntity).source()\ninternal fun PlatformLivingEntity.unwarp(): LivingEntity = (this as BukkitLivingEntity).source()\ninternal fun PlatformOfflinePlayer.unwarp(): OfflinePlayer = (this as BukkitOfflinePlayer).source()\ninternal fun PlatformPlayer.unwarp(): Player = (this as BukkitPlayer).source()\ninternal fun PlatformLocation.unwarp(): Location = (this as BukkitLocation).source()\ninternal fun PlatformWorld.unwarp(): World = (this as BukkitWorld).source()\ninternal fun PlatformItemStack.unwarp(): ItemStack = (this as BukkitItemStack).source()\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/EntityData.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\nimport java.lang.reflect.Field\n\ninternal fun Field.toEntityDataAccessor() = run {\n    isAccessible = true\n    get(null) as EntityDataAccessor<*>\n}\n\ninternal fun Class<*>.accessors() = declaredFields.filter { f ->\n    EntityDataAccessor::class.java.isAssignableFrom(f.type)\n}.map {\n    it.toEntityDataAccessor()\n}\n\ninternal val DISPLAY_SET = Display::class.java.accessors()\ninternal val SHARED_FLAG = Entity::class.java.accessors().first().id\ninternal val ITEM_DISPLAY_ID = ItemDisplay::class.java.accessors().map {\n    it.id\n}\ninternal val ITEM_SERIALIZER = ItemDisplay::class.java.accessors().first()\ninternal val ITEM_ENTITY_DATA = buildList {\n    add(SHARED_FLAG)\n    addAll(ITEM_DISPLAY_ID)\n    add(Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID.id)\n    DISPLAY_SET.subList(7, DISPLAY_SET.size).mapTo(this) { it.id }\n}.toIntSet()\n\n@Suppress(\"UNCHECKED_CAST\")\nprivate val DISPLAY_INTERPOLATION_DELAY = (DISPLAY_SET.first() as EntityDataAccessor<Int>).run {\n    SynchedEntityData.DataValue(id, serializer, 0)\n}\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_INTERPOLATION_DURATION = DISPLAY_SET[1] as EntityDataAccessor<Int>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_TRANSLATION = DISPLAY_SET[3] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_SCALE = DISPLAY_SET[4] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_ROTATION = DISPLAY_SET[5] as EntityDataAccessor<Quaternionf>\n\n\ninternal class TransformationData {\n\n    private var _duration = 0\n    private val duration get() = SynchedEntityData.DataValue(DISPLAY_INTERPOLATION_DURATION.id, DISPLAY_INTERPOLATION_DURATION.serializer, _duration)\n    private val translation = Item(Vector3f(), DISPLAY_TRANSLATION, MathUtil::isSimilar, Vector3f::set)\n    private val scale = Item(Vector3f(), DISPLAY_SCALE, MathUtil::isSimilar, Vector3f::set)\n    private val rotation = Item(Quaternionf(), DISPLAY_ROTATION, MathUtil::isSimilar, Quaternionf::set)\n\n    fun packDirty(entityId: Int, dest: AnimationBundler) {\n        val i = translation.cleanIndex + scale.cleanIndex + rotation.cleanIndex\n        if (i == 0) return\n        (dest.mod as ModAnimationBundlerImpl).append(entityId) {\n            dest.standard += ClientboundSetEntityDataPacket(entityId, buildList(i + 2) {\n                add(DISPLAY_INTERPOLATION_DELAY)\n                translation.value?.let { appendPosition(it.value); add(it) }\n                rotation.value?.let { appendRotation(it.value); add(it) }\n                scale.value?.let { appendScale(it.value); add(it) }\n                appendDuration(_duration); add(duration)\n            })\n        }\n    }\n\n    fun transform(\n        duration: Int,\n        translation: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf\n    ) {\n        _duration = duration\n        this.translation.set(translation)\n        this.scale.set(scale)\n        this.rotation.set(rotation)\n    }\n\n    fun pack() = listOf(\n        DISPLAY_INTERPOLATION_DELAY,\n        duration,\n        translation.forceValue,\n        scale.forceValue,\n        rotation.forceValue\n    )\n\n    private class Item<T : Any>(\n        initialValue: T,\n        private val accessor: EntityDataAccessor<T>,\n        private val dirtyChecker: (T, T) -> Boolean,\n        private val setter: (T, T) -> Unit\n    ) {\n        private val _t: T = initialValue\n        private var _dirty = false\n\n        val dirty get() = _dirty\n        val cleanIndex get() = if (dirty) 1 else 0\n        val value get() = if (_dirty) {\n            _dirty = false\n            forceValue\n        } else null\n        val forceValue get() = SynchedEntityData.DataValue(accessor.id, accessor.serializer, _t)\n\n        fun set(other: T) {\n            if (dirtyChecker(_t, other)) return\n            _dirty = true\n            setter(_t, other)\n        }\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport io.netty.buffer.Unpooled\nimport io.papermc.paper.adventure.PaperAdventure\nimport io.papermc.paper.configuration.GlobalConfiguration\nimport it.unimi.dsi.fastutil.ints.IntSet\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.network.syncher.SynchedEntityData.DataItem\nimport net.minecraft.network.syncher.SynchedEntityData.DataValue\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.goal.RangedAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedBowAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal\nimport net.minecraft.world.entity.animal.FlyingAnimal\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.inventory.CraftItemStack\nimport org.bukkit.craftbukkit.util.CraftChatMessage\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(noinline paperGetter: (T) -> R): (T) -> R {\n    return if (BetterModelBukkit.IS_PAPER) paperGetter else createAdaptedFieldGetter()\n}\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(): (T) -> R {\n    return T::class.java.declaredFields.first {\n        R::class.java.isAssignableFrom(it.type)\n    }.apply {\n        isAccessible = true\n    }.let { getter ->\n        { t ->\n            getter[t] as R\n        }\n    }\n}\n\ninternal fun <H, T> dirtyChecked(hash: () -> H, function: (H) -> T): () -> T {\n    val lock = Any()\n    var h = hash()\n    var value = function(h)\n    return {\n        val newH = hash()\n        when {\n            h === newH -> value\n            h == newH -> value\n            else -> synchronized(lock) {\n                h = newH\n                value = function(h)\n                value\n            }\n        }\n    }\n}\n\ninternal val CONFIG get() = BetterModel.config()\ninternal val EMPTY_ITEM = VanillaItemStack.EMPTY\ninternal fun BukkitItemStack.asVanilla() = CraftItemStack.asNMSCopy(this)\ninternal fun VanillaItemStack.asBukkit() = CraftItemStack.asCraftMirror(this)\n\ninternal val ONLINE_MODE by lazy(LazyThreadSafetyMode.NONE) {\n    if (BetterModelBukkit.IS_PAPER) GlobalConfiguration.get().proxies.isProxyOnlineMode else Bukkit.getOnlineMode()\n}\n\ninternal fun List<Int>.toIntSet(): IntSet = IntSet.of(*toIntArray())\n\ninternal fun Entity.passengerPosition(dest: Vector3f): Vector3f {\n    return attachments.get(EntityAttachment.PASSENGER, 0, yRot).let { v ->\n        dest.set(v.x.toFloat(), v.y.toFloat(), v.z.toFloat())\n    }\n}\n\nprivate val DATA_ITEMS = SynchedEntityData::class.java.declaredFields.first {\n    it.type.isArray\n}.apply {\n    isAccessible = true\n}\n\ninternal fun SynchedEntityData.pack(\n    clean: Boolean = false,\n    itemFilter: (DataItem<*>) -> Boolean = { true },\n    valueFilter: (DataValue<*>) -> Boolean = { true },\n    required: (List<Pair<DataItem<*>, DataValue<*>>>) -> Boolean = { it.isNotEmpty() }\n): List<DataValue<*>>? = (DATA_ITEMS[this] as Array<*>)\n    .mapNotNull map@ {\n        val item = (it as? DataItem<*>)?.takeIf(itemFilter) ?: return@map null\n        val value = item.value().takeIf(valueFilter) ?: return@map null\n        item to value\n    }\n    .takeIf(required)\n    ?.map {\n        if (clean) it.first.isDirty = false\n        it.second\n    }\n\ninternal fun Entity.isWalking(): Boolean {\n    return controllingPassenger?.isWalking() ?: when (this) {\n        is Mob -> navigation.isInProgress || goalSelector.availableGoals.any {\n            it.isRunning && when (it.goal) {\n                is RangedAttackGoal, is RangedCrossbowAttackGoal<*>, is RangedBowAttackGoal<*> -> true\n                else -> false\n            }\n        }\n        is ServerPlayer -> xMovement() != 0F || zMovement() != 0F\n        else -> false\n    }\n}\n\ninternal fun ServerPlayer.xMovement(): Float {\n    val leftMovement: Boolean = lastClientInput.left()\n    val rightMovement: Boolean = lastClientInput.right()\n    return if (leftMovement == rightMovement) 0F else if (leftMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.yMovement(): Float = if (isJump()) 1F else if (lastClientInput.shift) -1F else 0F\n\ninternal fun ServerPlayer.zMovement(): Float {\n    val forwardMovement: Boolean = lastClientInput.forward()\n    val backwardMovement: Boolean = lastClientInput.backward()\n    return if (forwardMovement == backwardMovement) 0F else if (forwardMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.isJump() = lastClientInput.jump()\n\ninternal val Entity.isFlying: Boolean\n    get() = when (this) {\n        is FlyingAnimal -> isFlying\n        is Mob -> isNoAi\n        is Player -> abilities.flying\n        is LivingEntity -> isFallFlying\n        else -> false\n    }\n\ninternal val CraftEntity.vanillaEntity: Entity\n    get() = if (BetterModelBukkit.IS_PAPER) handleRaw else handle\n\ninternal fun Entity.moveTo(vec: Vec3) = snapTo(vec)\ninternal fun Entity.moveTo(x: Double, y: Double, z: Double, yaw: Float, pitch: Float) = snapTo(x, y, z, yaw, pitch)\n\ninternal inline fun <T> useByteBuf(block: (FriendlyByteBuf) -> T): T {\n    val buffer = FriendlyByteBuf(Unpooled.buffer())\n    return try {\n        block(buffer)\n    } finally {\n        buffer.release()\n    }\n}\n\ninternal fun EntityTrackerRegistry.entityFlag(uuid: UUID, byte: Byte): Byte {\n    var b = byte.toInt()\n    val hideOption = hideOption(uuid)\n    if (hideOption.fire()) b = b and 1.inv()\n    if (hideOption.visibility()) b = b or (1 shl 5)\n    if (hideOption.glowing()) b = b and (1 shl 6).inv()\n    return b.toByte()\n}\n\ninternal fun Vector3f.toVanilla() = Vec3(x.toDouble(), y.toDouble(), z.toDouble())\ninternal fun Vec3.toBukkit() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())\n\ninternal inline fun LivingEntity.toEquipmentPacket(mapper: (EquipmentSlot) -> ItemStack? = { getItemBySlot(it).takeUnless { item -> item.isEmpty } }): ClientboundSetEquipmentPacket? {\n    val equip = EquipmentSlot.entries.mapNotNull {\n        mapper(it)?.let { item -> com.mojang.datafixers.util.Pair.of(it, item) }\n    }\n    return if (equip.isNotEmpty()) ClientboundSetEquipmentPacket(id, equip) else null\n}\ninternal fun LivingEntity.toEmptyEquipmentPacket() = toEquipmentPacket { ItemStack.EMPTY }\n\ninternal val Player.hotbarSlot get() = inventory.selectedSlot + 36\ninternal val PLAYER_EQUIPMENT_SLOT = IntSet.of(45, 5, 6, 7, 8)\ninternal fun ClientboundContainerSetSlotPacket.isEquipment(player: Player) = containerId == 0 && (PLAYER_EQUIPMENT_SLOT.contains(slot) || slot == player.hotbarSlot)\n\ninternal fun Entity.toFakeAddPacket() = ClientboundAddEntityPacket(\n    id,\n    uuid,\n    x,\n    y,\n    z,\n    xRot,\n    yRot,\n    EntityType.ITEM_DISPLAY,\n    0,\n    deltaMovement,\n    yHeadRot.toDouble()\n)\n\ninternal fun Avatar.toCustomisation() = entityData.get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION).toInt()\n\ninternal fun VanillaComponent.asAdventure() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asAdventure(this)\n} else {\n    GsonComponentSerializer.gson().deserialize(CraftChatMessage.toJSON(this))\n}\n\ninternal fun AdventureComponent.asVanilla() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asVanilla(this)\n} else {\n    CraftChatMessage.fromJSON(GsonComponentSerializer.gson().serialize(this))\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/HitBoxImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport io.papermc.paper.event.entity.EntityKnockbackEvent\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.event.hitbox.*\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.api.nms.ModelInteractionHand\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.network.protocol.game.ServerboundInteractPacket\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionHand.MAIN_HAND\nimport net.minecraft.world.InteractionHand.OFF_HAND\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.damagesource.DamageSource\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.entity.projectile.Projectile\nimport net.minecraft.world.entity.projectile.ProjectileDeflection\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.level.BlockGetter\nimport net.minecraft.world.phys.AABB\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.Color\nimport org.bukkit.Particle\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftArmorStand\nimport org.bukkit.craftbukkit.entity.CraftLivingEntity\nimport org.bukkit.event.entity.CreatureSpawnEvent\nimport org.bukkit.event.entity.EntityPotionEffectEvent\nimport org.bukkit.event.entity.EntityRemoveEvent\nimport org.bukkit.plugin.Plugin\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal class HitBoxImpl(\n    private val source: ModelBoundingBox,\n    private val bone: RenderedBone,\n    private var listener: HitBoxListener,\n    private val delegate: Entity,\n    private var mountController: MountController\n) : AbstractHitBox(delegate.level()) {\n    private val posCache = BoneMovement()\n    private var initialized = false\n    private var jumpDelay = 0\n    private var mounted = false\n    private var collision = ifLivingEntity { collides } == true\n    private var noGravity = if (delegate is Mob) delegate.isNoAi else delegate.isNoGravity\n    private var forceDismount = false\n    private var onFly = false\n\n    val craftEntity: HitBox by lazy {\n        object : CraftArmorStand(Bukkit.getServer() as CraftServer, this), HitBox by this {}\n    }\n    val dimensions: EntityDimensions get() = source.run {\n        EntityDimensions(\n            (x() + z()).toFloat() / 2,\n            y().toFloat(),\n            delegate.eyeHeight,\n            EntityAttachments.createDefault(0F, 0F),\n            false\n        ).scale(bone.hitBoxScale())\n    }\n    private val interaction by lazy {\n        HitBoxInteraction(this)\n    }\n    private val applier = InsideBlockEffectApplier.StepBasedCollector()\n\n    init {\n        moveTo(delegate.position())\n        isInvisible = true\n        persist = false\n        isSilent = true\n        initialized = true\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        level().addFreshEntity(interaction.apply {\n            moveTo(delegate.position())\n        }, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        interaction.startRiding(this)\n        listener.handle(HitBoxCreateEvent(this))\n    }\n\n    private fun initialSetup() {\n        if (mounted) {\n            mounted = false\n            if (delegate is Mob) delegate.isNoAi = noGravity\n            else delegate.isNoGravity = noGravity\n            ifLivingEntity { collides = collision }\n        }\n    }\n\n    override fun id(): Int = id\n    override fun uuid(): UUID = uuid\n    override fun source(): PlatformEntity = delegate.bukkitEntity.wrap()\n    override fun positionSource(): RenderedBone = bone\n    override fun forceDismount(): Boolean = forceDismount\n    override fun mountController(): MountController = mountController\n    override fun hasMountDriver(): Boolean = controllingPassenger != null\n    override fun mountController(controller: MountController) {\n        this.mountController = controller\n    }\n    override fun relativePosition(): Vector3f = delegate.position().run {\n        bone.hitBoxPosition(posCache).add(x.toFloat(), y.toFloat(), z.toFloat())\n    }\n    override fun listener(): HitBoxListener = listener\n    override fun listener(listener: HitBoxListener) {\n        this.listener = listener\n    }\n    override fun getItemBySlot(slot: EquipmentSlot): ItemStack = ItemStack.EMPTY\n    override fun setItemSlot(slot: EquipmentSlot, stack: ItemStack) {\n    }\n    override fun getMainArm(): HumanoidArm = HumanoidArm.RIGHT\n\n    override fun mount(entity: PlatformEntity) {\n        if (controllingPassenger != null) return\n        if (interaction.bukkitEntity.addPassenger(entity.unwarp())) {\n            if (mountController.canControl()) {\n                mounted = true\n                noGravity = delegate.isNoGravity\n                ifLivingEntity {\n                    collision = collides\n                    collides = false\n                }\n            }\n            listener.handle(HitBoxMountEvent(this, entity))\n        }\n    }\n\n    override fun dismount(entity: PlatformEntity) {\n        forceDismount = true\n        if (interaction.bukkitEntity.removePassenger(entity.unwarp())) listener.handle(HitBoxDismountEvent(this, entity))\n        forceDismount = false\n    }\n\n    override fun dismountAll() {\n        forceDismount = true\n        interaction.passengers.forEach {\n            it.stopRiding(true)\n            listener.handle(HitBoxDismountEvent(this, it.bukkitEntity.wrap()))\n        }\n        forceDismount = false\n    }\n\n    override fun setRemainingFireTicks(remainingFireTicks: Int) {\n        delegate.remainingFireTicks = remainingFireTicks\n    }\n\n    override fun getRemainingFireTicks(): Int {\n        return delegate.remainingFireTicks\n    }\n\n    override fun knockback(\n        d0: Double,\n        d1: Double,\n        d2: Double,\n        attacker: Entity?,\n        cause: EntityKnockbackEvent.Cause\n    ) {\n        if (attacker === delegate) return\n        ifLivingEntity { knockback(d0, d1, d2, attacker, cause) }\n    }\n\n    override fun push(pushingEntity: Entity) {\n        if (pushingEntity === delegate) return\n        delegate.push(pushingEntity)\n    }\n\n    override fun push(x: Double, y: Double, z: Double, pushingEntity: Entity?) {\n        if (pushingEntity === delegate) return\n        delegate.push(x, y, z, pushingEntity)\n    }\n\n    override fun isCollidable(ignoreClimbing: Boolean): Boolean {\n        return delegate.isCollidable(ignoreClimbing)\n    }\n\n    override fun canCollideWith(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWith(entity)\n    }\n\n    override fun canCollideWithBukkit(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWithBukkit(entity)\n    }\n\n    private fun checkCollide(entity: Entity): Boolean {\n        return entity !== delegate\n                && passengers.none { it === entity }\n                && delegate.passengers.none { it === entity }\n                && (entity !is HitBoxImpl || entity.delegate !== delegate)\n    }\n\n    override fun getActiveEffects(): Collection<MobEffectInstance> {\n        return ifLivingEntity { getActiveEffects() } ?: emptyList()\n    }\n\n    override fun getControllingPassenger(): LivingEntity? {\n        return if (mounted) interaction.firstPassenger as? LivingEntity ?: super.getControllingPassenger() else null\n    }\n\n    override fun onWalk(): Boolean {\n        return isWalking()\n    }\n\n    private fun mountControl(player: ServerPlayer) {\n        if (delegate !is LivingEntity) return\n        val travelVector = Vec3(delegate.xxa.toDouble(), delegate.yya.toDouble(), delegate.zza.toDouble())\n        if (!mountController.canFly() && delegate.isFallFlying) return\n\n        updateFlyStatus(player)\n        val riddenInput = rideInput(player, travelVector)\n        if (riddenInput.length() > 0.01) {\n            delegate.yRot = player.yRot\n            if (onFly) delegate.yHeadRot = player.yRot\n            delegate.move(MoverType.SELF, Vec3(riddenInput.x.toDouble(), riddenInput.y.toDouble(), riddenInput.z.toDouble()))\n        }\n        val dy = delegate.deltaMovement.y + delegate.gravity\n        if (!onFly && mountController.canJump() && (delegate.horizontalCollision || player.isJump()) && dy in 0.0..0.01 && jumpDelay == 0) {\n            jumpDelay = 10\n            delegate.jumpFromGround()\n        }\n    }\n\n    private fun movementSpeed() = ifLivingEntity {\n        getAttribute(Attributes.MOVEMENT_SPEED)?.value?.toFloat()?.let {\n            if (!onFly && !shouldDiscardFriction()) level()\n                .getBlockState(blockPosBelowThatAffectsMyMovement)\n                .block\n                .getFriction() * it else it\n        } ?: 0.0F\n    } ?: 0.0F\n\n    private fun updateFlyStatus(player: ServerPlayer) {\n        val fly = (player.isJump() && mountController.canFly()) || noGravity || onFly\n        if (delegate is Mob) delegate.isNoAi = fly\n        else delegate.isNoGravity = fly\n        onFly = fly && !delegate.onGround()\n        if (onFly) delegate.resetFallDistance()\n    }\n\n    private fun rideInput(player: ServerPlayer, travelVector: Vec3) = mountController.move(\n        if (onFly) MountController.MoveType.FLY else MountController.MoveType.DEFAULT,\n        player.bukkitEntity.wrap(),\n        (delegate.bukkitEntity as org.bukkit.entity.LivingEntity).wrap(),\n        Vector3f(\n            player.xMovement(),\n            player.yMovement(),\n            player.zMovement()\n        ),\n        Vector3f(\n            travelVector.x.toFloat(),\n            travelVector.y.toFloat(),\n            travelVector.z.toFloat()\n        )\n    ).mul(movementSpeed()).rotateY(-Math.toRadians(player.yRot.toDouble()).toFloat())\n\n    override fun tick() {\n        delegate.removalReason?.let {\n            if (!isRemoved) remove(it)\n            return\n        }\n        val controller = controllingPassenger\n        if (jumpDelay > 0) jumpDelay--\n        interaction.isInvisible = delegate.isInvisible\n        if (controller is ServerPlayer && !isDeadOrDying && mountController.canControl()) {\n            if (delegate is Mob) delegate.navigation.stop()\n            mountControl(controller)\n        } else initialSetup()\n        yRot = bone.rotation().y\n        yHeadRot = yRot\n        yBodyRot = yRot\n        val pos = relativePosition()\n        val minusHeight = source.minY * bone.hitBoxScale()\n        setPos(\n            pos.x.toDouble(),\n            pos.y.toDouble() + minusHeight,\n            pos.z.toDouble()\n        )\n        BlockGetter.forEachBlockIntersectedBetween(\n            oldPosition(),\n            position(),\n            boundingBox\n        ) { pos, step ->\n            if (BetterModelBukkit.IS_PAPER) applier.advanceStep(step, pos)\n            level().getBlockState(pos).entityInside(level(), pos, delegate, applier, true)\n            true\n        }\n        applier.applyAndClear(delegate)\n        if (isInLava) delegate.lavaHurt()\n        firstTick = false\n        listener.sync(craftEntity)\n    }\n\n    override fun remove(reason: RemovalReason, cause: EntityRemoveEvent.Cause?) {\n        initialSetup()\n        listener.handle(HitBoxRemoveEvent(craftEntity))\n        interaction.remove(reason)\n        super.remove(reason, cause)\n    }\n\n    override fun getBukkitLivingEntity(): CraftLivingEntity = bukkitEntity\n    override fun getBukkitEntity(): CraftLivingEntity = craftEntity as CraftLivingEntity\n    override fun getBukkitEntityRaw(): CraftLivingEntity = bukkitEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun isDeadOrDying(): Boolean {\n        return ifLivingEntity { isDeadOrDying } == true\n    }\n\n    override fun hide(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            hideEntity(plugin, bukkitEntity)\n            hideEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun show(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            showEntity(plugin, bukkitEntity)\n            showEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        val interact = HitBoxInteractAtEvent(\n            (player.bukkitEntity as org.bukkit.entity.Player).wrap(), craftEntity, when (hand) {\n                MAIN_HAND -> ModelInteractionHand.RIGHT\n                OFF_HAND -> ModelInteractionHand.LEFT\n            }, vec.toBukkit()\n        )\n        if (!listener.handle(interact)) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand, vec))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, cause: EntityPotionEffectEvent.Cause): Boolean {\n        return ifLivingEntity { addEffect(effectInstance, cause) } == true\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, entity: Entity?): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause,\n        fireEvent: Boolean\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause, fireEvent) } == true\n    }\n\n    override fun hurtServer(world: ServerLevel, source: DamageSource, amount: Float): Boolean {\n        if (source.entity === delegate || delegate.isInvulnerable) return false\n        if (source.entity === controllingPassenger && !mountController.canBeDamagedByRider()) return false\n        val ds = ModelDamageSourceImpl(source)\n        val event = HitBoxDamagedEvent(craftEntity, ds, amount)\n        if (!listener.handle(event)) return false\n        return ifLivingEntity { hurtServer(world, source, event.damage) } == true\n    }\n\n    override fun deflection(projectile: Projectile): ProjectileDeflection {\n        if (projectile.owner?.uuid == delegate.uuid) return ProjectileDeflection.NONE\n        return ifLivingEntity { deflection(projectile) } ?: ProjectileDeflection.NONE\n    }\n\n    override fun getHealth(): Float {\n        return ifLivingEntity { health } ?: super.getHealth()\n    }\n\n    override fun makeBoundingBox(vec3: Vec3): AABB {\n        return if (!initialized) {\n            super.makeBoundingBox(vec3)\n        } else {\n            val scale = bone.hitBoxScale()\n            AABB(\n                vec3.x + source.minX * scale,\n                vec3.y,\n                vec3.z + source.minZ * scale,\n                vec3.x + source.maxX * scale,\n                vec3.y + source.y() * scale,\n                vec3.z + source.maxZ * scale\n            ).apply {\n                if (CONFIG.debug().has(DebugConfig.DebugOption.HITBOX)) {\n                    bukkitEntity.world.spawnParticle(Particle.DUST, minX, minY, minZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                    bukkitEntity.world.spawnParticle(Particle.DUST, maxX, maxY, maxZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                }\n            }\n        }\n    }\n    override fun getDefaultDimensions(pose: Pose): EntityDimensions = if (initialized) dimensions else super.getDefaultDimensions(pose)\n\n    override fun removeHitBox() {\n        source().task {\n            dismountAll()\n            remove(ifLivingEntity { removalReason } ?: RemovalReason.KILLED)\n        }\n    }\n\n    private inline fun <T> ifLivingEntity(block: LivingEntity.() -> T): T? {\n        return if (delegate.valid) (delegate as? LivingEntity)?.block() else null\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/HitBoxInteraction.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.nms.HitBox\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.Interaction\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftInteraction\n\ninternal class HitBoxInteraction(\n    val delegate: HitBoxImpl\n) : Interaction(EntityType.INTERACTION, delegate.level()) {\n\n    init {\n        persist = false\n    }\n\n    private val craftEntity: CraftInteraction by lazy {\n        object : CraftInteraction(Bukkit.getServer() as CraftServer, this), HitBox by delegate {}\n    }\n\n    override fun getBukkitEntity(): CraftEntity = craftEntity\n    override fun getBukkitEntityRaw(): CraftEntity = craftEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun tick() {\n        val dimension = delegate.dimensions\n        width = dimension.width\n        height = dimension.height\n        yRot = delegate.yRot\n        xRot = delegate.xRot\n        setSharedFlagOnFire(delegate.remainingFireTicks > 0)\n    }\n\n    override fun skipAttackInteraction(entity: Entity): Boolean {\n        return if (entity is Player) {\n            entity.attack(delegate)\n            true\n        } else false\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        delegate.interact(player, hand)\n        return InteractionResult.FAIL\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        delegate.interactAt(player, vec, hand)\n        return InteractionResult.FAIL\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/ModAnimationBundlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.nms.ModAnimationBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.RegistryFriendlyByteBuf\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket\nimport net.minecraft.server.MinecraftServer\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\ninternal class ModAnimationBundlerImpl(initialCapacity: Int) : ModAnimationBundler {\n\n    companion object {\n\n        const val KEY = \"modelengine:bulk_data\"\n\n        const val PACKET_TYPE_BULK_DATA = 0x00\n\n        const val FIELD_TRANSLATION = 1 shl 0\n        const val FIELD_LEFT_ROTATION = 1 shl 1\n        const val FIELD_SCALE = 1 shl 2\n        const val FIELD_TRANSFORM_DURATION = 1 shl 4\n\n        private val EMPTY_BUILD_TASK: (FriendlyByteBuf) -> Unit = {}\n    }\n\n    private val packet by lazy {\n        useByteBuf { buffer ->\n            ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC.decode(\n                RegistryFriendlyByteBuf(\n                    buffer,\n                    MinecraftServer.getServer().registryAccess()\n                ).apply {\n                    writeUtf(KEY)\n                    useByteBuf {\n                        it.writeByte(PACKET_TYPE_BULK_DATA)\n                        it.writeVarInt(builderList.size)\n                        builderList.forEach { builder -> builder(it) }\n                        writeBytes(it)\n                    }\n                }\n            )\n        }\n    }\n\n    private val builderList = ArrayList<(FriendlyByteBuf) -> Unit>(initialCapacity)\n\n    override fun send(player: PlatformPlayer) {\n        (player.unwarp() as CraftPlayer).handle.connection.send(packet)\n    }\n\n    fun append(id: Int, scope: Appender.() -> Unit) {\n        val build = Appender(id).apply(scope).build()\n        if (build !== EMPTY_BUILD_TASK) builderList += build\n    }\n\n    class Appender(\n        val entityId: Int,\n    ) {\n        private var mask = 0\n        private var buildTask = EMPTY_BUILD_TASK\n        private val isEmpty get() = buildTask === EMPTY_BUILD_TASK\n\n        fun appendPosition(vector: Vector3f) {\n            mask = mask or FIELD_TRANSLATION\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendScale(vector: Vector3f) {\n            mask = mask or FIELD_SCALE\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendRotation(quaternion: Quaternionf) {\n            mask = mask or FIELD_LEFT_ROTATION\n            task {\n                writeFloat(it, quaternion.x)\n                writeFloat(it, quaternion.y)\n                writeFloat(it, quaternion.z)\n                writeFloat(it, quaternion.w)\n            }\n        }\n\n        fun appendDuration(duration: Int) {\n            mask = mask or FIELD_TRANSFORM_DURATION\n            task {\n                writeVarInt(it, duration)\n            }\n        }\n\n        fun build(): (FriendlyByteBuf) -> Unit {\n            if (isEmpty) return EMPTY_BUILD_TASK\n            val m = mask\n            val t = buildTask\n            return {\n                writeVarInt(it,entityId)\n                writeByte(it, m)\n                t(it)\n            }\n        }\n\n        private fun task(task: (FriendlyByteBuf) -> Unit) {\n            if (isEmpty) {\n                buildTask = task\n                return\n            }\n            val last = buildTask\n            buildTask = {\n                last(it)\n                task(it)\n            }\n        }\n\n        private fun writeFloat(buf: FriendlyByteBuf, float: Float) {\n            buf.writeShort(MathUtil.floatToHalf(float).toInt())\n        }\n\n        private fun writeVarInt(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeVarInt(duration)\n        }\n\n        private fun writeByte(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeByte(duration)\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/ModelDamageSourceImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.event.ModelDamageSource\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport net.minecraft.world.damagesource.DamageSource\nimport org.bukkit.craftbukkit.util.CraftLocation\n\ninternal class ModelDamageSourceImpl(\n    private val source: DamageSource\n) : ModelDamageSource {\n    override fun getCausingEntity(): PlatformEntity? = source.entity?.bukkitEntity?.wrap()\n    override fun getDirectEntity(): PlatformEntity? = source.directEntity?.bukkitEntity?.wrap()\n    override fun getDamageLocation(): PlatformLocation? = source.sourcePositionRaw()?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun getSourceLocation(): PlatformLocation? = source.sourcePosition?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun isIndirect(): Boolean = !source.isDirect\n    override fun getFoodExhaustion(): Float = source.foodExhaustion\n    override fun scalesWithDifficulty(): Boolean = source.scalesWithDifficulty()\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/ModelDisplayImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.ModelDisplay\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformItemTransform\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.util.Brightness\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport org.joml.Quaternionf\nimport org.joml.Vector3d\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicBoolean\n\ninternal class ModelDisplayImpl(\n    private val pos: Vector3d,\n    val display: ItemDisplay,\n    val yOffset: Double\n) : ModelDisplay {\n\n    private val entityData = display.entityData\n    private val entityDataLock = SingleLock()\n    private val forceGlow = AtomicBoolean()\n    private val forceInvisibility = AtomicBoolean()\n\n    private val oldPos = Vector3d(pos)\n\n    override fun id(): Int = display.id\n    override fun uuid(): UUID = display.uuid\n    override fun rotate(rotation: ModelRotation, bundler: PacketBundler) {\n        display.xRot = rotation.x\n        display.yRot = rotation.y\n        bundler += ClientboundMoveEntityPacket.Rot(\n            display.id,\n            rotation.packedY(),\n            rotation.packedX(),\n            display.onGround\n        )\n    }\n\n    override fun invisible(invisible: Boolean) {\n        if (forceInvisibility.compareAndSet(!invisible, invisible)) {\n            entityDataLock.accessToLock {\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPotionEffect(entity: BaseEntity) {\n        val beforeInvisible = display.isInvisible\n        val afterInvisible = entity.invisible()\n        entityDataLock.accessToLock {\n            display.setGlowingTag(entity.glow() || forceGlow.get())\n            if (CONFIG.followMobInvisibility() && beforeInvisible != afterInvisible) {\n                display.isInvisible = afterInvisible\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPosition(location: PlatformLocation) {\n        oldPos.set(pos)\n        pos.set(location.x(), location.y(), location.z())\n    }\n\n    override fun spawn(showItem: Boolean, bundler: PacketBundler) {\n        bundler += addPacket\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    override fun teleport(location: PlatformLocation, bundler: PacketBundler) {\n        display.moveTo(\n            location.x(),\n            location.y(),\n            location.z(),\n            location.yaw(),\n            0F\n        )\n        bundler += ClientboundTeleportEntityPacket.teleport(display.id, PositionMoveRotation.of(display), emptySet(), display.onGround)\n    }\n\n    override fun sendPosition(adapter: BaseEntity, bundler: PacketBundler) {\n        val handle = adapter.handle() as Entity\n        if (oldPos.distanceSquared(pos) < 1e-8) return\n        bundler += ClientboundEntityPositionSyncPacket(\n            display.id,\n            PositionMoveRotation.of(handle),\n            handle.onGround()\n        )\n    }\n\n    override fun display(transform: PlatformItemTransform) {\n        entityDataLock.accessToLock {\n            display.itemTransform = ItemDisplayContext.BY_ID.apply(transform.ordinal)\n        }\n    }\n\n    override fun moveDuration(duration: Int) {\n        entityDataLock.accessToLock {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = duration\n        }\n    }\n\n    override fun item(itemStack: PlatformItemStack) {\n        entityDataLock.accessToLock {\n            display.itemStack = itemStack.unwarp().asVanilla()\n        }\n    }\n\n    override fun brightness(block: Int, sky: Int) {\n        entityDataLock.accessToLock {\n            display.brightnessOverride = if (block < 0 && sky < 0) null else Brightness(\n                block,\n                sky\n            )\n        }\n    }\n\n    override fun viewRange(range: Float) {\n        entityDataLock.accessToLock {\n            display.viewRange = range\n        }\n    }\n\n    override fun shadowRadius(radius: Float) {\n        entityDataLock.accessToLock {\n            display.shadowRadius = radius\n        }\n    }\n\n    override fun glow(glow: Boolean) {\n        if (!forceGlow.compareAndSet(!glow, glow)) return\n        entityDataLock.accessToLock {\n            display.setGlowingTag(display.isCurrentlyGlowing || glow)\n        }\n    }\n\n    override fun glowColor(glowColor: Int) {\n        entityDataLock.accessToLock {\n            display.glowColorOverride = glowColor\n        }\n    }\n\n    override fun billboard(billboard: PlatformBillboard) {\n        entityDataLock.accessToLock {\n            display.billboardConstraints = Display.BillboardConstraints.BY_ID.apply(billboard.ordinal)\n        }\n    }\n\n    override fun createTransformer(): DisplayTransformer = DisplayTransformerImpl(display)\n\n    override fun invisible(): Boolean = entityDataLock.accessToLock {\n        display.isInvisible || forceInvisibility.get() || display.itemStack.`is`(Items.AIR)\n    }\n\n    override fun sendDirtyEntityData(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                clean = true,\n                itemFilter = { it.isDirty },\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(!invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    override fun sendEntityData(showItem: Boolean, bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(showItem && !invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    private fun List<SynchedEntityData.DataValue<*>>.markVisible(showItem: Boolean) = map {\n        if (it.id == ITEM_SERIALIZER.id) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.ITEM_STACK,\n            if (showItem) display.itemStack else EMPTY_ITEM\n        ) else it\n    }\n\n    private val addPacket\n        get() = ClientboundAddEntityPacket(\n            display.id,\n            display.uuid,\n            pos.x,\n            pos.y + yOffset,\n            pos.z,\n            display.xRot,\n            display.yRot,\n            display.type,\n            0,\n            display.deltaMovement,\n            display.yHeadRot.toDouble()\n        )\n\n    private val removePacket = ClientboundRemoveEntitiesPacket(display.id)\n\n    private class DisplayTransformerImpl(\n        source: ItemDisplay\n    ) : DisplayTransformer {\n        private val id = source.id\n        private val entityData = TransformationData()\n        private val entityDataLock = SingleLock()\n\n        override fun transform(\n            duration: Int,\n            position: Vector3f,\n            scale: Vector3f,\n            rotation: Quaternionf,\n            bundler: AnimationBundler\n        ) {\n            entityDataLock.accessToLock {\n                entityData.transform(\n                    duration,\n                    position,\n                    scale,\n                    rotation\n                )\n                entityData.packDirty(id, bundler)\n            }\n        }\n\n        override fun sendTransformation(bundler: PacketBundler) {\n            entityDataLock.accessToLock {\n                entityData.pack()\n            }?.run {\n                bundler += ClientboundSetEntityDataPacket(id, this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/ModelGameProfile.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport com.mojang.authlib.GameProfile\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\n\ninternal data class ModelGameProfile(\n    private val gameProfile: GameProfile\n) : ModelProfile {\n\n    private val info = ModelProfileInfo(gameProfile.id, gameProfile.name)\n    private val skin by lazy {\n        gameProfile.properties[\"textures\"].firstOrNull()?.let {\n            BetterModel.platform().profileManager().skin(it.value)\n        } ?: ModelProfileSkin.EMPTY\n    }\n\n    override fun info(): ModelProfileInfo = info\n\n    override fun skin(): ModelProfileSkin = skin\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/ModelNametagImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport com.mojang.math.Transformation\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.BonePosition\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.nms.ModelNametag\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.EntityUtil\nimport net.kyori.adventure.text.Component\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\ninternal class ModelNametagImpl(\n    private val bone: RenderedBone\n) : ModelNametag {\n    private companion object {\n        private val emptyVector = Vector3f()\n        private val emptyTransformation = Transformation(\n            Vector3f(-1F / 40F, -0.2F - 1F / 40F, 0F),\n            null,\n            null,\n            null\n        )\n    }\n\n    private val viewedPlayer = ConcurrentHashMap.newKeySet<UUID>()\n    private val display = Display.TextDisplay(\n        EntityType.TEXT_DISPLAY,\n        MinecraftServer.getServer().overworld()\n    ).apply {\n        entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n        setTransformation(emptyTransformation)\n        billboardConstraints = Display.BillboardConstraints.CENTER\n    }\n    private val posCache = BoneMovement()\n    private var alwaysVisible = false\n    private var location = BetterModel.platform().adapter().zero()\n\n    override fun component(component: Component?) {\n        display.text = component?.asVanilla() ?: VanillaComponent.empty()\n    }\n\n    override fun teleport(location: PlatformLocation) {\n        this.location = location\n    }\n\n    override fun alwaysVisible(alwaysVisible: Boolean) {\n        this.alwaysVisible = alwaysVisible\n    }\n\n    override fun send(player: PlatformPlayer) {\n        if (display.text == VanillaComponent.empty()) return\n        val hb = bone.group.hitBoxPoint\n        val pos = bone.worldPosition(BonePosition(emptyVector, hb, player.uuid()), posCache)\n        display.moveTo(Vec3(\n            location.x() + pos.x,\n            location.y() + pos.y,\n            location.z() + pos.z\n        ))\n        val inPoint = alwaysVisible || EntityUtil.isCustomNameVisible(player.location(), location)\n        when {\n            inPoint && viewedPlayer.add(player.uuid()) -> bundlerOfNotNull(\n                addPacket,\n                display.entityData.pack()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            inPoint -> bundlerOfNotNull(\n                ClientboundEntityPositionSyncPacket(display.id, PositionMoveRotation.of(display), false),\n                display.entityData.packDirty()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            viewedPlayer.remove(player.uuid()) -> bundlerOf(removePacket)\n            else -> null\n        }?.send(player)\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    private val addPacket get() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        display.x,\n        display.y,\n        display.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket get() = ClientboundRemoveEntitiesPacket(display.id)\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/NMSImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup\nimport com.mojang.authlib.GameProfile\nimport io.netty.channel.ChannelDuplexHandler\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.channel.ChannelPromise\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.*\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.network.Connection\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.resources.ResourceLocation\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl\nimport net.minecraft.util.ARGB\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport net.minecraft.world.item.component.CustomModelData\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.level.entity.LevelEntityGetter\nimport net.minecraft.world.level.entity.LevelEntityGetterAdapter\nimport net.minecraft.world.level.entity.PersistentEntitySectionManager\nimport org.bukkit.craftbukkit.CraftWorld\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Vector3d\nimport java.util.*\nimport java.util.function.Consumer\nimport java.util.function.IntConsumer\n\nclass NMSImpl : NMS {\n\n    companion object {\n        private const val INJECT_NAME = \"bettermodel_channel_handler\"\n\n        //Spigot\n        private val getGameProfile: (Player) -> GameProfile = createAdaptedFieldGetter { it.gameProfile }\n        private val getConnection: (ServerCommonPacketListenerImpl) -> Connection = createAdaptedFieldGetter { it.connection }\n        private val spigotChunkAccess = ServerLevel::class.java.fields.firstOrNull {\n            it.type == PersistentEntitySectionManager::class.java\n        }?.apply {\n            isAccessible = true\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        private val ServerLevel.levelGetter\n            get(): LevelEntityGetter<Entity> {\n                return if (BetterModelBukkit.IS_PAPER) {\n                    `moonrise$getEntityLookup`()\n                } else {\n                    spigotChunkAccess?.get(this)?.let {\n                        (it as PersistentEntitySectionManager<*>).entityGetter as LevelEntityGetter<Entity>\n                    } ?: throw RuntimeException(\"LevelEntityGetter\")\n                }\n            }\n        private val getEntityById: (LevelEntityGetter<Entity>, Int) -> Entity? = if (BetterModelBukkit.IS_PAPER) { g, i ->\n            (g as EntityLookup)[i]\n        } else LevelEntityGetterAdapter::class.java.declaredFields.first {\n            net.minecraft.world.level.entity.EntityLookup::class.java.isAssignableFrom(it.type)\n        }.let {\n            it.isAccessible = true\n            { e, i ->\n                (it[e] as net.minecraft.world.level.entity.EntityLookup<*>).getEntity(i) as? Entity\n            }\n        }\n        private fun Int.toEntity(level: ServerLevel) = getEntityById(level.levelGetter, this)\n        //Spigot\n        private val hitBoxData by lazy {\n            ItemDisplay(EntityType.ITEM_DISPLAY, MinecraftServer.getServer().overworld()).run {\n                entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n                entityData.nonDefaultValues!!\n            }\n        }\n    }\n\n    override fun hide(channel: PlayerChannelHandler, registry: EntityTrackerRegistry) {\n        val target = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf()\n        target.entityData.pack(\n            valueFilter = { it.id == SHARED_FLAG }\n        )?.let {\n            list += ClientboundSetEntityDataPacket(target.id, it).toRegistryDataPacket(channel.uuid(), registry)\n        }\n        if (target is LivingEntity) {\n            val packet = if (registry.hideOption(channel.uuid()).equipment) target.toEmptyEquipmentPacket() else target.toEquipmentPacket()\n            packet?.let { list += it }\n        }\n        list.send(channel.player())\n    }\n\n    private fun ClientboundSetEntityDataPacket.toRegistryDataPacket(uuid: UUID, registry: EntityTrackerRegistry) = ClientboundSetEntityDataPacket(id, packedItems().map {\n        if (it.id == SHARED_FLAG) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.BYTE,\n            registry.entityFlag(uuid, it.value() as Byte)\n        ) else it\n    })\n\n    inner class PlayerChannelHandlerImpl(\n        private val player: CraftPlayer\n    ) : PlayerChannelHandler, ChannelDuplexHandler() {\n        private val connection = player.handle.connection\n        private val uuid = player.uniqueId\n        private val base = adapt(player.wrap())\n\n        init {\n            val pipeline = getConnection(connection).channel.pipeline()\n            pipeline.addBefore(pipeline.first { it.value is Connection }.key, INJECT_NAME, this)\n        }\n\n        override fun close() {\n            val channel = getConnection(connection).channel\n            channel.eventLoop().submit {\n                channel.pipeline().remove(INJECT_NAME)\n            }\n        }\n\n        override fun base(): BasePlayer = base\n        override fun isModEnabled(): Boolean = (if (BetterModelBukkit.IS_PAPER) player.channels() else player.listeningPluginChannels).contains(ModAnimationBundlerImpl.KEY)\n\n        private val playerModel get() = connection.player.id.toRegistry()\n\n        private fun Int.toPlayerEntity() = toEntity(connection.player.level())\n        private fun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n        private inline fun Int.toRegistry(\n            ifHitBox: (Entity) -> Unit = {}\n        ) = (EntityTrackerRegistry.registry(this) ?: toPlayerEntity()?.let {\n            if (it is HitBox) ifHitBox(it)\n            it.toRegistry()\n        })?.takeIf {\n            it.isSpawned(player.uniqueId)\n        }\n\n        override fun sendEntityData(registry: EntityTrackerRegistry) {\n            val handle = registry.entity().handle() as? Entity ?: return\n            val list = bundlerOf(\n                ClientboundSetPassengersPacket(handle)\n            )\n            handle.entityData.pack(\n                valueFilter = { it.id == SHARED_FLAG }\n            )?.let {\n                list += ClientboundSetEntityDataPacket(handle.id, it)\n            }\n            if (handle is LivingEntity) handle.toEquipmentPacket()?.let {\n                list += it\n            }\n            list.send(player.wrap())\n        }\n\n        private fun <T : ClientGamePacketListener> Packet<in T>.handle(): Packet<in T>? {\n            when (this) {\n                is ClientboundBundlePacket -> return if (subPackets() is Keyed) this else ClientboundBundlePacket(subPackets().mapNotNull {\n                    it.handle()\n                })\n                is ClientboundAddEntityPacket -> {\n                    val entity = id.toPlayerEntity() ?: return this\n                    if (entity is HitBox) return entity.toFakeAddPacket()\n                    val wrap = entity.bukkitEntity.wrap()\n                    BetterModel.registry(wrap).ifPresent {\n                        wrap.taskLater(1) {\n                            it.spawn(player.wrap())\n                        }\n                    }\n                }\n                is ClientboundRemoveEntitiesPacket -> {\n                    entityIds\n                        .asSequence()\n                        .mapNotNull map@ {\n                            it.toRegistry {\n                                return@map null\n                            }\n                        }\n                        .forEach {\n                            it.remove()\n                        }\n                }\n                is ClientboundSetPassengersPacket -> {\n                    vehicle.toRegistry()?.let {\n                        return it.mountPacket(it.entity().handle() as? Entity ?: return this, array = passengers)\n                    }\n                }\n                is ClientboundUpdateAttributesPacket if entityId.toPlayerEntity() is HitBox -> return null\n                is ClientboundSetEntityDataPacket -> id.toRegistry {\n                    return ClientboundSetEntityDataPacket(id, hitBoxData)\n                }?.let { registry ->\n                    return toRegistryDataPacket(uuid, registry)\n                }\n                is ClientboundSetEquipmentPacket -> entity.toRegistry {\n                    return null\n                }?.let {\n                    if (it.hideOption(uuid).equipment()) (it.entity().handle() as? LivingEntity)?.toEmptyEquipmentPacket()?.let { packet ->\n                        return packet\n                    }\n                }\n                is ClientboundRespawnPacket -> playerModel?.let {\n                    bundlerOf(it.mountPacket(connection.player)).send(player.wrap())\n                }\n                is ClientboundContainerSetSlotPacket if isEquipment(connection.player) && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetSlotPacket(containerId, stateId, slot, EMPTY_ITEM)\n                }\n                is ClientboundContainerSetContentPacket if containerId == 0 && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetContentPacket(\n                        containerId,\n                        stateId,\n                        items.apply {\n                            PLAYER_EQUIPMENT_SLOT.forEach(IntConsumer { set(it, EMPTY_ITEM) })\n                            set(connection.player.hotbarSlot, EMPTY_ITEM)\n                        },\n                        carriedItem\n                    )\n                }\n            }\n            return this\n        }\n\n        override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {\n            super.write(ctx, if (msg is Packet<*>) msg.handle() ?: return else msg, promise)\n        }\n\n        override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {\n            fun EntityTrackerRegistry.updatePlayerLimb() = BetterModel.platform().scheduler().asyncTaskLater(1) {\n                if (isClosed) return@asyncTaskLater\n                player.handle.containerMenu.sendAllDataToRemote()\n                trackers().forEach { tracker ->\n                    tracker.update(TrackerUpdateAction.itemMapping()) { bone ->\n                        !bone.itemMapper.fixed()\n                    }\n                }\n            }\n            when (msg) {\n                is ServerboundSetCarriedItemPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) {\n                            connection.send(ClientboundSetHeldSlotPacket(player.inventory.heldItemSlot))\n                            return\n                        }\n                        registry.updatePlayerLimb()\n                    }\n                }\n                is ServerboundPlayerActionPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) return\n                        registry.updatePlayerLimb()\n                    }\n                }\n            }\n            super.channelRead(ctx, msg)\n        }\n\n        private fun EntityTrackerRegistry.remove() {\n            remove(player.wrap())\n        }\n    }\n\n    override fun mount(registry: EntityTrackerRegistry, bundler: PacketBundler) {\n        val entity = registry.entity().handle()\n        if (entity is Entity) bundler += registry.mountPacket(entity)\n    }\n\n    private fun EntityTrackerRegistry.mountPacket(entity: Entity, array: IntArray = entity.passengers.filter {\n        EntityTrackerRegistry.registry(it.uuid) == null\n    }.map {\n        it.id\n    }.toIntArray()): ClientboundSetPassengersPacket {\n        return useByteBuf { buffer ->\n            buffer.writeVarInt(entity.id)\n            buffer.writeVarIntArray(displays()\n                .mapToInt {\n                    (it as ModelDisplayImpl).display.id\n                }.toArray() + array)\n            ClientboundSetPassengersPacket.STREAM_CODEC.decode(buffer)\n        }\n    }\n\n    override fun inject(player: PlatformPlayer): PlayerChannelHandlerImpl = PlayerChannelHandlerImpl(player.unwarp() as CraftPlayer)\n\n    override fun createBundler(initialCapacity: Int): PacketBundler = bundlerOf(initialCapacity)\n    override fun createParallelBundler(threshold: Int): PacketBundler = parallelBundlerOf(threshold)\n    override fun createModAnimationBuilder(initialCapacity: Int): ModAnimationBundler = ModAnimationBundlerImpl(initialCapacity)\n\n    override fun create(location: PlatformLocation, yOffset: Double, initialConsumer: Consumer<ModelDisplay>): ModelDisplay = ModelDisplayImpl(\n        Vector3d(location.x(), location.y(), location.z()),\n        ItemDisplay(EntityType.ITEM_DISPLAY, (location.world().unwarp() as CraftWorld).handle).apply {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n            billboardConstraints = Display.BillboardConstraints.FIXED\n            valid = true\n            yRot = location.yaw()\n            itemTransform = ItemDisplayContext.FIXED\n        },\n        yOffset\n    ).apply {\n        initialConsumer.accept(this)\n        display.entityData.packDirty()\n    }\n\n    override fun createNametag(bone: RenderedBone): ModelNametag = ModelNametagImpl(bone)\n\n    override fun tint(itemStack: PlatformItemStack, rgb: Int): PlatformItemStack {\n        return itemStack.unwarp().asVanilla().apply {\n            set(DataComponents.DYED_COLOR, DyedItemColor(rgb))\n            set(DataComponents.CUSTOM_MODEL_DATA, get(DataComponents.CUSTOM_MODEL_DATA)?.let {\n                CustomModelData(it.floats, it.flags, it.strings, it.colors\n                    .run {\n                        if (rgb == 0xFFFFFF) this else map { color ->\n                            ARGB.multiply(color, rgb) and 0xFFFFFF\n                        }\n                    }\n                    .ifEmpty { listOf(rgb) })\n            })\n        }.asBukkit().wrap()\n    }\n\n    override fun createHitBox(entity: BaseEntity, bone: RenderedBone, boundingBox: ModelBoundingBox, mountController: MountController, listener: HitBoxListener): HitBox? {\n        val handle = entity.handle() as? Entity ?: return null\n        return HitBoxImpl(\n            boundingBox.center(),\n            bone,\n            listener,\n            handle,\n            mountController\n        ).craftEntity\n    }\n    override fun version(): NMSVersion = NMSVersion.V1_21_R6\n\n    override fun adapt(entity: PlatformEntity): BaseBukkitEntity {\n        val craft = entity.unwarp() as CraftEntity\n        return BaseEntityImpl(craft)\n    }\n\n    override fun adapt(player: PlatformPlayer): BasePlayer {\n        val craft = player.unwarp() as CraftPlayer\n        return BasePlayerImpl(\n            craft,\n            dirtyChecked({ getGameProfile(craft.handle) }, { ModelGameProfile(it) }),\n            dirtyChecked({ craft.handle.toCustomisation() }, { PlayerSkinParts(it) })\n        )\n    }\n\n    override fun profile(player: PlatformPlayer): ModelProfile = ModelGameProfile(getGameProfile((player.unwarp() as CraftPlayer).handle))\n\n    override fun createSkinItem(model: String, floats: List<Float>, flags: List<Boolean>, strings: List<String>, colors: List<Int>): TransformedItemStack {\n        return VanillaItemStack(Items.PLAYER_HEAD).run {\n            set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(floats, flags, strings, colors))\n            set(DataComponents.ITEM_MODEL, ResourceLocation.parse(model))\n            TransformedItemStack.of(asBukkit().wrap())\n        }\n    }\n\n    override fun isProxyOnlineMode(): Boolean = ONLINE_MODE\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/PacketBundlers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.kyori.adventure.key.Key\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.network.PacketSendListener\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\nprivate val KEY = Key.key(\"bettermodel\")\n\ninternal fun bundlerOfNotNull(vararg packets: ClientPacket?) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.filterNotNull().toMutableList())\ninternal fun bundlerOf(vararg packets: ClientPacket) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.toMutableList())\ninternal fun bundlerOf(size: Int) = SimpleBundler(ArrayList(size))\ninternal fun parallelBundlerOf(threshold: Int) = ParallelBundler(threshold)\n\ninternal operator fun PacketBundler.plusAssign(other: ClientPacket) {\n    when (this) {\n        is SimpleBundler -> add(other)\n        is ParallelBundler -> add(other)\n        else -> throw RuntimeException(\"unsupported bundler.\")\n    }\n}\ninternal fun Packet<*>.assumeSize() = when (this) {\n    is ClientboundSetEntityDataPacket -> packedItems.size\n    is ClientboundSetEquipmentPacket -> slots.size\n    else -> 1\n}\n\ninternal interface PluginBundlePacketImpl : Iterable<ClientPacket>, Keyed {\n    val bundlePacket: ClientboundBundlePacket\n    fun size(): Int\n    fun isEmpty(): Boolean\n    fun add(other: ClientPacket)\n}\n\ninternal class SimpleBundler(\n    private val list: MutableList<ClientPacket>\n) : PacketBundler, PluginBundlePacketImpl {\n    override val bundlePacket = ClientboundBundlePacket(this)\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        connection.send(bundlePacket, PacketSendListener.thenRun(onSuccess))\n    }\n    override fun isEmpty(): Boolean = list.isEmpty()\n    override fun size(): Int = list.size\n    override fun key(): Key = KEY\n    override fun iterator(): MutableIterator<ClientPacket> = list.iterator()\n    override fun add(other: ClientPacket) {\n        list += other\n    }\n}\n\ninternal class ParallelBundler(\n    private val threshold: Int\n) : PacketBundler {\n    private val subBundlers = mutableListOf<PluginBundlePacketImpl>()\n    private var sizeAssume = 0\n    private val newBundler get() = bundlerOf().apply {\n        sizeAssume = 0\n        subBundlers += this\n    }\n    private var selectedBundler = newBundler\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        subBundlers.forEach {\n            connection.send(it.bundlePacket)\n        }\n    }\n    override fun isEmpty(): Boolean = selectedBundler.isEmpty()\n    override fun size(): Int = subBundlers.sumOf(PluginBundlePacketImpl::size)\n    fun add(other: ClientPacket) {\n        (if (sizeAssume > threshold) newBundler else selectedBundler)\n            .apply { selectedBundler = this }\n            .add(other)\n        sizeAssume += other.assumeSize()\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/PlayerArmorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.item.equipment.EquipmentAssets\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\ninternal data class PlayerArmorImpl(\n    private val player: CraftPlayer\n) : PlayerArmor {\n\n    override fun helmet(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.HEAD).toArmorItem()\n    }\n\n    override fun leggings(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.LEGS).toArmorItem()\n    }\n\n    override fun chestplate(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.CHEST).toArmorItem()\n    }\n\n    override fun boots(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.FEET).toArmorItem()\n    }\n\n    private fun VanillaItemStack.toArmorItem(): ArmorItem? = get(DataComponents.EQUIPPABLE)?.assetId?.map {\n        val trim = get(DataComponents.TRIM)\n        ArmorItem(\n            get(DataComponents.DYED_COLOR)?.rgb ?: if (it === EquipmentAssets.LEATHER) DyedItemColor.LEATHER_COLOR else 0xFFFFFF,\n            it.location().path,\n            trim?.pattern?.value()?.assetId?.path,\n            trim?.material?.value()?.assets?.base?.suffix\n        )\n    }?.orElse(null)\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/ProfiledImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\n\ninternal class ProfiledImpl(\n    private val playerArmor: PlayerArmor,\n    private val modelProfile: () -> ModelProfile,\n    private val playerSkinParts: () -> PlayerSkinParts\n) : Profiled {\n\n    override fun profile(): ModelProfile = modelProfile()\n    override fun armors(): PlayerArmor = playerArmor\n    override fun skinParts(): PlayerSkinParts = playerSkinParts()\n}\n"
  },
  {
    "path": "nms/v1_21_R6/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R6/TypeAliases.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R6\n\nimport net.minecraft.network.chat.Component\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientGamePacketListener\nimport net.minecraft.world.item.ItemStack\n\ninternal typealias VanillaItemStack = ItemStack\ninternal typealias BukkitItemStack = org.bukkit.inventory.ItemStack\ninternal typealias ClientPacket = Packet<ClientGamePacketListener>\ninternal typealias VanillaComponent = Component\ninternal typealias AdventureComponent = net.kyori.adventure.text.Component\n"
  },
  {
    "path": "nms/v1_21_R7/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    alias(libs.plugins.convention.paperweight)\n}\n\ndependencies {\n    paperweight.paperDevBundle(\"1.21.11-R0.1-SNAPSHOT\")\n}\n\ntasks {\n    compileJava {\n        options.release = 21\n    }\n    compileKotlin {\n        compilerOptions.jvmTarget = JvmTarget.JVM_21\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/java/kr/toxicity/model/bukkit/nms/v1_21_R7/AbstractHitBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\npublic abstract class AbstractHitBox extends ArmorStand implements HitBox {\n\n    AbstractHitBox(@NotNull Level level) {\n        super(EntityType.ARMOR_STAND, level);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final boolean equals(@Nullable Object other) {\n        return super.equals(other);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final int hashCode() {\n        return super.hashCode();\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/BaseEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.persistence.PersistentDataHolder\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.stream.Stream\n\ninternal data class BaseEntityImpl(\n    private val delegate: CraftEntity\n) : BaseBukkitEntity, PersistentDataHolder by delegate {\n    override fun customName(): AdventureComponent? = handle().run {\n        if (this is ServerPlayer) (customName ?: name).asAdventure() else customName?.asAdventure()?.takeIf {\n            isCustomNameVisible\n        }\n    }\n\n    override fun entity(): org.bukkit.entity.Entity = delegate\n    override fun handle(): Entity = delegate.vanillaEntity\n    override fun uuid(): UUID = delegate.uniqueId\n    override fun id(): Int = handle().id\n    override fun dead(): Boolean = (handle() as? LivingEntity)?.isDeadOrDying == true || handle().removalReason != null || !handle().valid\n    override fun invisible(): Boolean = handle().isInvisible || (handle() as? LivingEntity)?.hasEffect(MobEffects.INVISIBILITY) == true\n    override fun glow(): Boolean = handle().isCurrentlyGlowing\n\n    override fun onWalk(): Boolean {\n        return handle().isWalking()\n    }\n\n    override fun scale(): Double {\n        val handle = handle()\n        return if (handle is LivingEntity) handle.scale.toDouble() else 1.0\n    }\n\n    override fun pitch(): Float = handle().xRot\n    override fun ground(): Boolean = handle().onGround()\n    override fun bodyYaw(): Float = handle().let { if (it is LivingEntity) it.yBodyRot else it.yRot }\n    override fun yaw(): Float = handle().yRot\n    override fun headYaw(): Float = handle().let { if (it is LivingEntity) it.yHeadRot else it.yRot }\n    override fun fly(): Boolean = handle().isFlying\n\n    override fun damageTick(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        val duration = handle.invulnerableDuration.toFloat()\n        if (duration <= 0F) return 0F\n        val knockBack = 1 - (handle.getAttribute(Attributes.KNOCKBACK_RESISTANCE)?.value?.toFloat() ?: 0F)\n        return handle.invulnerableTime.toFloat() / duration * knockBack\n    }\n\n    override fun walkSpeed(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        if (!handle.onGround) return 1F\n        val speed = handle.getEffect(MobEffects.SPEED)?.amplifier ?: 0\n        val slow = handle.getEffect(MobEffects.SLOWNESS)?.amplifier ?: 0\n        return (1F + (speed - slow) * 0.2F)\n            .coerceAtLeast(0.2F)\n            .coerceAtMost(2F)\n    }\n\n    override fun passengerPosition(dest: Vector3f): Vector3f {\n        return handle().passengerPosition(dest)\n    }\n\n    override fun platform(): PlatformEntity = delegate.wrap()\n    override fun trackedBy(): Stream<PlatformPlayer> = delegate.trackedBy.stream().map { it.wrap() }\n    override fun location(): PlatformLocation = delegate.location.wrap()\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/BasePlayerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitPlayer\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport net.minecraft.util.Mth\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.bukkit.entity.Player\nimport java.util.stream.Stream\n\ninternal data class BasePlayerImpl(\n    private val delegate: CraftPlayer,\n    private val profile: () -> ModelProfile,\n    private val skinParts: () -> PlayerSkinParts\n) : BaseBukkitEntity by BaseEntityImpl(delegate), BaseBukkitPlayer, Profiled by ProfiledImpl(PlayerArmorImpl(delegate), profile, skinParts) {\n\n    override fun entity(): Player = delegate\n\n    override fun updateInventory() {\n        delegate.handle.containerMenu.sendAllDataToRemote()\n    }\n\n    override fun platform(): PlatformPlayer = delegate.wrap()\n\n    override fun trackedBy(): Stream<PlatformPlayer> = Stream.concat(\n        Stream.of(delegate),\n        delegate.trackedBy.stream()\n    ).map {\n        it.wrap()\n    }\n\n    override fun bodyYaw(): Float {\n        val handle = delegate.handle\n        var yaw = -45 * handle.xMovement()\n        if (handle.zMovement() < 0) yaw *= -1\n        return Mth.wrapDegrees(handle.yHeadRot + yaw)\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/BukkitWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.bukkit.platform.*\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter.adapt\nimport kr.toxicity.model.api.bukkit.platform.BukkitItemStack\nimport kr.toxicity.model.api.platform.*\nimport org.bukkit.Location\nimport org.bukkit.OfflinePlayer\nimport org.bukkit.World\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.LivingEntity\nimport org.bukkit.entity.Player\nimport org.bukkit.inventory.ItemStack\n\ninternal fun Entity.wrap() = adapt(this)\ninternal fun LivingEntity.wrap() = adapt(this)\ninternal fun OfflinePlayer.wrap() = adapt(this)\ninternal fun Player.wrap() = adapt(this)\ninternal fun Location.wrap() = adapt(this)\ninternal fun World.wrap() = adapt(this)\ninternal fun ItemStack.wrap() = adapt(this)\n\ninternal fun PlatformEntity.unwarp(): Entity = (this as BukkitEntity).source()\ninternal fun PlatformLivingEntity.unwarp(): LivingEntity = (this as BukkitLivingEntity).source()\ninternal fun PlatformOfflinePlayer.unwarp(): OfflinePlayer = (this as BukkitOfflinePlayer).source()\ninternal fun PlatformPlayer.unwarp(): Player = (this as BukkitPlayer).source()\ninternal fun PlatformLocation.unwarp(): Location = (this as BukkitLocation).source()\ninternal fun PlatformWorld.unwarp(): World = (this as BukkitWorld).source()\ninternal fun PlatformItemStack.unwarp(): ItemStack = (this as BukkitItemStack).source()\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/EntityData.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\nimport java.lang.reflect.Field\n\ninternal fun Field.toEntityDataAccessor() = run {\n    isAccessible = true\n    get(null) as EntityDataAccessor<*>\n}\n\ninternal fun Class<*>.accessors() = declaredFields.filter { f ->\n    EntityDataAccessor::class.java.isAssignableFrom(f.type)\n}.map {\n    it.toEntityDataAccessor()\n}\n\ninternal val DISPLAY_SET = Display::class.java.accessors()\ninternal val SHARED_FLAG = Entity::class.java.accessors().first().id\ninternal val ITEM_DISPLAY_ID = ItemDisplay::class.java.accessors().map {\n    it.id\n}\ninternal val ITEM_SERIALIZER = ItemDisplay::class.java.accessors().first()\ninternal val ITEM_ENTITY_DATA = buildList {\n    add(SHARED_FLAG)\n    addAll(ITEM_DISPLAY_ID)\n    add(Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID.id)\n    DISPLAY_SET.subList(7, DISPLAY_SET.size).mapTo(this) { it.id }\n}.toIntSet()\n\n@Suppress(\"UNCHECKED_CAST\")\nprivate val DISPLAY_INTERPOLATION_DELAY = (DISPLAY_SET.first() as EntityDataAccessor<Int>).run {\n    SynchedEntityData.DataValue(id, serializer, 0)\n}\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_INTERPOLATION_DURATION = DISPLAY_SET[1] as EntityDataAccessor<Int>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_TRANSLATION = DISPLAY_SET[3] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_SCALE = DISPLAY_SET[4] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_ROTATION = DISPLAY_SET[5] as EntityDataAccessor<Quaternionf>\n\n\ninternal class TransformationData {\n\n    private var _duration = 0\n    private val duration get() = SynchedEntityData.DataValue(DISPLAY_INTERPOLATION_DURATION.id, DISPLAY_INTERPOLATION_DURATION.serializer, _duration)\n    private val translation = Item(Vector3f(), DISPLAY_TRANSLATION, MathUtil::isSimilar, Vector3f::set)\n    private val scale = Item(Vector3f(), DISPLAY_SCALE, MathUtil::isSimilar, Vector3f::set)\n    private val rotation = Item(Quaternionf(), DISPLAY_ROTATION, MathUtil::isSimilar, Quaternionf::set)\n\n    fun packDirty(entityId: Int, dest: AnimationBundler) {\n        val i = translation.cleanIndex + scale.cleanIndex + rotation.cleanIndex\n        if (i == 0) return\n        (dest.mod as ModAnimationBundlerImpl).append(entityId) {\n            dest.standard += ClientboundSetEntityDataPacket(entityId, buildList(i + 2) {\n                add(DISPLAY_INTERPOLATION_DELAY)\n                translation.value?.let { appendPosition(it.value); add(it) }\n                rotation.value?.let { appendRotation(it.value); add(it) }\n                scale.value?.let { appendScale(it.value); add(it) }\n                appendDuration(_duration); add(duration)\n            })\n        }\n    }\n\n    fun transform(\n        duration: Int,\n        translation: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf\n    ) {\n        _duration = duration\n        this.translation.set(translation)\n        this.scale.set(scale)\n        this.rotation.set(rotation)\n    }\n\n    fun pack() = listOf(\n        DISPLAY_INTERPOLATION_DELAY,\n        duration,\n        translation.forceValue,\n        scale.forceValue,\n        rotation.forceValue\n    )\n\n    private class Item<T : Any>(\n        initialValue: T,\n        private val accessor: EntityDataAccessor<T>,\n        private val dirtyChecker: (T, T) -> Boolean,\n        private val setter: (T, T) -> Unit\n    ) {\n        private val _t: T = initialValue\n        private var _dirty = false\n\n        val dirty get() = _dirty\n        val cleanIndex get() = if (dirty) 1 else 0\n        val value get() = if (_dirty) {\n            _dirty = false\n            forceValue\n        } else null\n        val forceValue get() = SynchedEntityData.DataValue(accessor.id, accessor.serializer, _t)\n\n        fun set(other: T) {\n            if (dirtyChecker(_t, other)) return\n            _dirty = true\n            setter(_t, other)\n        }\n    }\n}\n\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport io.netty.buffer.Unpooled\nimport io.papermc.paper.adventure.PaperAdventure\nimport io.papermc.paper.configuration.GlobalConfiguration\nimport it.unimi.dsi.fastutil.ints.IntSet\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.network.syncher.SynchedEntityData.DataItem\nimport net.minecraft.network.syncher.SynchedEntityData.DataValue\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.goal.RangedAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedBowAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal\nimport net.minecraft.world.entity.animal.FlyingAnimal\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.inventory.CraftItemStack\nimport org.bukkit.craftbukkit.util.CraftChatMessage\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(noinline paperGetter: (T) -> R): (T) -> R {\n    return if (BetterModelBukkit.IS_PAPER) paperGetter else createAdaptedFieldGetter()\n}\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(): (T) -> R {\n    return T::class.java.declaredFields.first {\n        R::class.java.isAssignableFrom(it.type)\n    }.apply {\n        isAccessible = true\n    }.let { getter ->\n        { t ->\n            getter[t] as R\n        }\n    }\n}\n\ninternal fun <H, T> dirtyChecked(hash: () -> H, function: (H) -> T): () -> T {\n    val lock = Any()\n    var h = hash()\n    var value = function(h)\n    return {\n        val newH = hash()\n        when {\n            h === newH -> value\n            h == newH -> value\n            else -> synchronized(lock) {\n                h = newH\n                value = function(h)\n                value\n            }\n        }\n    }\n}\n\ninternal val CONFIG get() = BetterModel.config()\ninternal val EMPTY_ITEM = VanillaItemStack.EMPTY\ninternal fun BukkitItemStack.asVanilla() = CraftItemStack.asNMSCopy(this)\ninternal fun VanillaItemStack.asBukkit() = CraftItemStack.asCraftMirror(this)\n\ninternal val ONLINE_MODE by lazy(LazyThreadSafetyMode.NONE) {\n    if (BetterModelBukkit.IS_PAPER) GlobalConfiguration.get().proxies.isProxyOnlineMode else Bukkit.getOnlineMode()\n}\n\ninternal fun List<Int>.toIntSet(): IntSet = IntSet.of(*toIntArray())\n\ninternal fun Entity.passengerPosition(dest: Vector3f): Vector3f {\n    return attachments.get(EntityAttachment.PASSENGER, 0, yRot).let { v ->\n        dest.set(v.x.toFloat(), v.y.toFloat(), v.z.toFloat())\n    }\n}\n\nprivate val DATA_ITEMS = SynchedEntityData::class.java.declaredFields.first {\n    it.type.isArray\n}.apply {\n    isAccessible = true\n}\n\ninternal fun SynchedEntityData.pack(\n    clean: Boolean = false,\n    itemFilter: (DataItem<*>) -> Boolean = { true },\n    valueFilter: (DataValue<*>) -> Boolean = { true },\n    required: (List<Pair<DataItem<*>, DataValue<*>>>) -> Boolean = { it.isNotEmpty() }\n): List<DataValue<*>>? = (DATA_ITEMS[this] as Array<*>)\n    .mapNotNull map@ {\n        val item = (it as? DataItem<*>)?.takeIf(itemFilter) ?: return@map null\n        val value = item.value().takeIf(valueFilter) ?: return@map null\n        item to value\n    }\n    .takeIf(required)\n    ?.map {\n        if (clean) it.first.isDirty = false\n        it.second\n    }\n\ninternal fun Entity.isWalking(): Boolean {\n    return controllingPassenger?.isWalking() ?: when (this) {\n        is Mob -> navigation.isInProgress || goalSelector.availableGoals.any {\n            it.isRunning && when (it.goal) {\n                is RangedAttackGoal, is RangedCrossbowAttackGoal<*>, is RangedBowAttackGoal<*> -> true\n                else -> false\n            }\n        }\n        is ServerPlayer -> xMovement() != 0F || zMovement() != 0F\n        else -> false\n    }\n}\n\ninternal fun ServerPlayer.xMovement(): Float {\n    val leftMovement: Boolean = lastClientInput.left()\n    val rightMovement: Boolean = lastClientInput.right()\n    return if (leftMovement == rightMovement) 0F else if (leftMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.yMovement(): Float = if (isJump()) 1F else if (lastClientInput.shift) -1F else 0F\n\ninternal fun ServerPlayer.zMovement(): Float {\n    val forwardMovement: Boolean = lastClientInput.forward()\n    val backwardMovement: Boolean = lastClientInput.backward()\n    return if (forwardMovement == backwardMovement) 0F else if (forwardMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.isJump() = lastClientInput.jump()\n\ninternal val Entity.isFlying: Boolean\n    get() = when (this) {\n        is FlyingAnimal -> isFlying\n        is Mob -> isNoAi\n        is Player -> abilities.flying\n        is LivingEntity -> isFallFlying\n        else -> false\n    }\n\ninternal val CraftEntity.vanillaEntity: Entity\n    get() = if (BetterModelBukkit.IS_PAPER) handleRaw else handle\n\ninternal fun Entity.moveTo(vec: Vec3) = snapTo(vec)\ninternal fun Entity.moveTo(x: Double, y: Double, z: Double, yaw: Float, pitch: Float) = snapTo(x, y, z, yaw, pitch)\n\ninternal inline fun <T> useByteBuf(block: (FriendlyByteBuf) -> T): T {\n    val buffer = FriendlyByteBuf(Unpooled.buffer())\n    return try {\n        block(buffer)\n    } finally {\n        buffer.release()\n    }\n}\n\ninternal fun EntityTrackerRegistry.entityFlag(uuid: UUID, byte: Byte): Byte {\n    var b = byte.toInt()\n    val hideOption = hideOption(uuid)\n    if (hideOption.fire()) b = b and 1.inv()\n    if (hideOption.visibility()) b = b or (1 shl 5)\n    if (hideOption.glowing()) b = b and (1 shl 6).inv()\n    return b.toByte()\n}\n\ninternal fun Vector3f.toVanilla() = Vec3(x.toDouble(), y.toDouble(), z.toDouble())\ninternal fun Vec3.toBukkit() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())\n\ninternal inline fun LivingEntity.toEquipmentPacket(mapper: (EquipmentSlot) -> ItemStack? = { getItemBySlot(it).takeUnless { item -> item.isEmpty } }): ClientboundSetEquipmentPacket? {\n    val equip = EquipmentSlot.entries.mapNotNull {\n        mapper(it)?.let { item -> com.mojang.datafixers.util.Pair.of(it, item) }\n    }\n    return if (equip.isNotEmpty()) ClientboundSetEquipmentPacket(id, equip) else null\n}\ninternal fun LivingEntity.toEmptyEquipmentPacket() = toEquipmentPacket { ItemStack.EMPTY }\n\ninternal val Player.hotbarSlot get() = inventory.selectedSlot + 36\ninternal val PLAYER_EQUIPMENT_SLOT = IntSet.of(45, 5, 6, 7, 8)\ninternal fun ClientboundContainerSetSlotPacket.isEquipment(player: Player) = containerId == 0 && (PLAYER_EQUIPMENT_SLOT.contains(slot) || slot == player.hotbarSlot)\n\ninternal fun Entity.toFakeAddPacket() = ClientboundAddEntityPacket(\n    id,\n    uuid,\n    x,\n    y,\n    z,\n    xRot,\n    yRot,\n    EntityType.ITEM_DISPLAY,\n    0,\n    deltaMovement,\n    yHeadRot.toDouble()\n)\n\ninternal fun Avatar.toCustomisation() = entityData.get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION).toInt()\n\ninternal fun VanillaComponent.asAdventure() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asAdventure(this)\n} else {\n    GsonComponentSerializer.gson().deserialize(CraftChatMessage.toJSON(this))\n}\n\ninternal fun AdventureComponent.asVanilla() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asVanilla(this)\n} else {\n    CraftChatMessage.fromJSON(GsonComponentSerializer.gson().serialize(this))\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/HitBoxImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport io.papermc.paper.event.entity.EntityKnockbackEvent\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.event.hitbox.*\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.api.nms.ModelInteractionHand\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.network.protocol.game.ServerboundInteractPacket\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionHand.MAIN_HAND\nimport net.minecraft.world.InteractionHand.OFF_HAND\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.damagesource.DamageSource\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.entity.projectile.Projectile\nimport net.minecraft.world.entity.projectile.ProjectileDeflection\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.level.BlockGetter\nimport net.minecraft.world.phys.AABB\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.Color\nimport org.bukkit.Particle\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftArmorStand\nimport org.bukkit.craftbukkit.entity.CraftLivingEntity\nimport org.bukkit.event.entity.CreatureSpawnEvent\nimport org.bukkit.event.entity.EntityPotionEffectEvent\nimport org.bukkit.event.entity.EntityRemoveEvent\nimport org.bukkit.plugin.Plugin\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal class HitBoxImpl(\n    private val source: ModelBoundingBox,\n    private val bone: RenderedBone,\n    private var listener: HitBoxListener,\n    private val delegate: Entity,\n    private var mountController: MountController\n) : AbstractHitBox(delegate.level()) {\n    private val posCache = BoneMovement()\n    private var initialized = false\n    private var jumpDelay = 0\n    private var mounted = false\n    private var collision = ifLivingEntity { collides } == true\n    private var noGravity = if (delegate is Mob) delegate.isNoAi else delegate.isNoGravity\n    private var forceDismount = false\n    private var onFly = false\n\n    val craftEntity: HitBox by lazy {\n        object : CraftArmorStand(Bukkit.getServer() as CraftServer, this), HitBox by this {}\n    }\n    val dimensions: EntityDimensions get() = source.run {\n        EntityDimensions(\n            (x() + z()).toFloat() / 2,\n            y().toFloat(),\n            delegate.eyeHeight,\n            EntityAttachments.createDefault(0F, 0F),\n            false\n        ).scale(bone.hitBoxScale())\n    }\n    private val interaction by lazy {\n        HitBoxInteraction(this)\n    }\n    private val applier = InsideBlockEffectApplier.StepBasedCollector()\n\n    init {\n        moveTo(delegate.position())\n        isInvisible = true\n        persist = false\n        isSilent = true\n        initialized = true\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        level().addFreshEntity(interaction.apply {\n            moveTo(delegate.position())\n        }, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        interaction.startRiding(this)\n        listener.handle(HitBoxCreateEvent(this))\n    }\n\n    private fun initialSetup() {\n        if (mounted) {\n            mounted = false\n            if (delegate is Mob) delegate.isNoAi = noGravity\n            else delegate.isNoGravity = noGravity\n            ifLivingEntity { collides = collision }\n        }\n    }\n\n    override fun id(): Int = id\n    override fun uuid(): UUID = uuid\n    override fun source(): PlatformEntity = delegate.bukkitEntity.wrap()\n    override fun positionSource(): RenderedBone = bone\n    override fun forceDismount(): Boolean = forceDismount\n    override fun mountController(): MountController = mountController\n    override fun hasMountDriver(): Boolean = controllingPassenger != null\n    override fun mountController(controller: MountController) {\n        this.mountController = controller\n    }\n    override fun relativePosition(): Vector3f = delegate.position().run {\n        bone.hitBoxPosition(posCache).add(x.toFloat(), y.toFloat(), z.toFloat())\n    }\n    override fun listener(): HitBoxListener = listener\n    override fun listener(listener: HitBoxListener) {\n        this.listener = listener\n    }\n    override fun getItemBySlot(slot: EquipmentSlot): ItemStack = ItemStack.EMPTY\n    override fun setItemSlot(slot: EquipmentSlot, stack: ItemStack) {\n    }\n    override fun getMainArm(): HumanoidArm = HumanoidArm.RIGHT\n\n    override fun mount(entity: PlatformEntity) {\n        if (controllingPassenger != null) return\n        if (interaction.bukkitEntity.addPassenger(entity.unwarp())) {\n            if (mountController.canControl()) {\n                mounted = true\n                noGravity = delegate.isNoGravity\n                ifLivingEntity {\n                    collision = collides\n                    collides = false\n                }\n            }\n            listener.handle(HitBoxMountEvent(this, entity))\n        }\n    }\n\n    override fun dismount(entity: PlatformEntity) {\n        forceDismount = true\n        if (interaction.bukkitEntity.removePassenger(entity.unwarp())) listener.handle(HitBoxDismountEvent(this, entity))\n        forceDismount = false\n    }\n\n    override fun dismountAll() {\n        forceDismount = true\n        interaction.passengers.forEach {\n            it.stopRiding(true)\n            listener.handle(HitBoxDismountEvent(this, it.bukkitEntity.wrap()))\n        }\n        forceDismount = false\n    }\n\n    override fun setRemainingFireTicks(remainingFireTicks: Int) {\n        delegate.remainingFireTicks = remainingFireTicks\n    }\n\n    override fun getRemainingFireTicks(): Int {\n        return delegate.remainingFireTicks\n    }\n\n    override fun knockback(\n        d0: Double,\n        d1: Double,\n        d2: Double,\n        attacker: Entity?,\n        cause: EntityKnockbackEvent.Cause\n    ) {\n        if (attacker === delegate) return\n        ifLivingEntity { knockback(d0, d1, d2, attacker, cause) }\n    }\n\n    override fun push(pushingEntity: Entity) {\n        if (pushingEntity === delegate) return\n        delegate.push(pushingEntity)\n    }\n\n    override fun push(x: Double, y: Double, z: Double, pushingEntity: Entity?) {\n        if (pushingEntity === delegate) return\n        delegate.push(x, y, z, pushingEntity)\n    }\n\n    override fun isCollidable(ignoreClimbing: Boolean): Boolean {\n        return delegate.isCollidable(ignoreClimbing)\n    }\n\n    override fun canCollideWith(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWith(entity)\n    }\n\n    override fun canCollideWithBukkit(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWithBukkit(entity)\n    }\n\n    private fun checkCollide(entity: Entity): Boolean {\n        return entity !== delegate\n                && passengers.none { it === entity }\n                && delegate.passengers.none { it === entity }\n                && (entity !is HitBoxImpl || entity.delegate !== delegate)\n    }\n\n    override fun getActiveEffects(): Collection<MobEffectInstance> {\n        return ifLivingEntity { getActiveEffects() } ?: emptyList()\n    }\n\n    override fun getControllingPassenger(): LivingEntity? {\n        return if (mounted) interaction.firstPassenger as? LivingEntity ?: super.getControllingPassenger() else null\n    }\n\n    override fun onWalk(): Boolean {\n        return isWalking()\n    }\n\n    private fun mountControl(player: ServerPlayer) {\n        if (delegate !is LivingEntity) return\n        val travelVector = Vec3(delegate.xxa.toDouble(), delegate.yya.toDouble(), delegate.zza.toDouble())\n        if (!mountController.canFly() && delegate.isFallFlying) return\n\n        updateFlyStatus(player)\n        val riddenInput = rideInput(player, travelVector)\n        if (riddenInput.length() > 0.01) {\n            delegate.yRot = player.yRot\n            if (onFly) delegate.yHeadRot = player.yRot\n            delegate.move(MoverType.SELF, Vec3(riddenInput.x.toDouble(), riddenInput.y.toDouble(), riddenInput.z.toDouble()))\n        }\n        val dy = delegate.deltaMovement.y + delegate.gravity\n        if (!onFly && mountController.canJump() && (delegate.horizontalCollision || player.isJump()) && dy in 0.0..0.01 && jumpDelay == 0) {\n            jumpDelay = 10\n            delegate.jumpFromGround()\n        }\n    }\n\n    private fun movementSpeed() = ifLivingEntity {\n        getAttribute(Attributes.MOVEMENT_SPEED)?.value?.toFloat()?.let {\n            if (!onFly && !shouldDiscardFriction()) level()\n                .getBlockState(blockPosBelowThatAffectsMyMovement)\n                .block\n                .getFriction() * it else it\n        } ?: 0.0F\n    } ?: 0.0F\n\n    private fun updateFlyStatus(player: ServerPlayer) {\n        val fly = (player.isJump() && mountController.canFly()) || noGravity || onFly\n        if (delegate is Mob) delegate.isNoAi = fly\n        else delegate.isNoGravity = fly\n        onFly = fly && !delegate.onGround()\n        if (onFly) delegate.resetFallDistance()\n    }\n\n    private fun rideInput(player: ServerPlayer, travelVector: Vec3) = mountController.move(\n        if (onFly) MountController.MoveType.FLY else MountController.MoveType.DEFAULT,\n        player.bukkitEntity.wrap(),\n        (delegate.bukkitEntity as org.bukkit.entity.LivingEntity).wrap(),\n        Vector3f(\n            player.xMovement(),\n            player.yMovement(),\n            player.zMovement()\n        ),\n        Vector3f(\n            travelVector.x.toFloat(),\n            travelVector.y.toFloat(),\n            travelVector.z.toFloat()\n        )\n    ).mul(movementSpeed()).rotateY(-Math.toRadians(player.yRot.toDouble()).toFloat())\n\n    override fun tick() {\n        delegate.removalReason?.let {\n            if (!isRemoved) remove(it)\n            return\n        }\n        val controller = controllingPassenger\n        if (jumpDelay > 0) jumpDelay--\n        interaction.isInvisible = delegate.isInvisible\n        if (controller is ServerPlayer && !isDeadOrDying && mountController.canControl()) {\n            if (delegate is Mob) delegate.navigation.stop()\n            mountControl(controller)\n        } else initialSetup()\n        yRot = bone.rotation().y\n        yHeadRot = yRot\n        yBodyRot = yRot\n        val pos = relativePosition()\n        val minusHeight = source.minY * bone.hitBoxScale()\n        setPos(\n            pos.x.toDouble(),\n            pos.y.toDouble() + minusHeight,\n            pos.z.toDouble()\n        )\n        BlockGetter.forEachBlockIntersectedBetween(\n            oldPosition(),\n            position(),\n            boundingBox\n        ) { pos, step ->\n            if (BetterModelBukkit.IS_PAPER) applier.advanceStep(step, pos)\n            level().getBlockState(pos).entityInside(level(), pos, delegate, applier, true)\n            true\n        }\n        applier.applyAndClear(delegate)\n        if (isInLava) delegate.lavaHurt()\n        firstTick = false\n        listener.sync(craftEntity)\n    }\n\n    override fun remove(reason: RemovalReason, cause: EntityRemoveEvent.Cause?) {\n        initialSetup()\n        listener.handle(HitBoxRemoveEvent(craftEntity))\n        interaction.remove(reason)\n        super.remove(reason, cause)\n    }\n\n    override fun getBukkitLivingEntity(): CraftLivingEntity = bukkitEntity\n    override fun getBukkitEntity(): CraftLivingEntity = craftEntity as CraftLivingEntity\n    override fun getBukkitEntityRaw(): CraftLivingEntity = bukkitEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun isDeadOrDying(): Boolean {\n        return ifLivingEntity { isDeadOrDying } == true\n    }\n\n    override fun hide(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            hideEntity(plugin, bukkitEntity)\n            hideEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun show(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            showEntity(plugin, bukkitEntity)\n            showEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        val interact = HitBoxInteractAtEvent(\n            (player.bukkitEntity as org.bukkit.entity.Player).wrap(), craftEntity, when (hand) {\n                MAIN_HAND -> ModelInteractionHand.RIGHT\n                OFF_HAND -> ModelInteractionHand.LEFT\n            }, vec.toBukkit()\n        )\n        if (!listener.handle(interact)) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket.createInteractionPacket(delegate, player.isShiftKeyDown, hand, vec))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, cause: EntityPotionEffectEvent.Cause): Boolean {\n        return ifLivingEntity { addEffect(effectInstance, cause) } == true\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, entity: Entity?): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause,\n        fireEvent: Boolean\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause, fireEvent) } == true\n    }\n\n    override fun hurtServer(world: ServerLevel, source: DamageSource, amount: Float): Boolean {\n        if (source.entity === delegate || delegate.isInvulnerable) return false\n        if (source.entity === controllingPassenger && !mountController.canBeDamagedByRider()) return false\n        val ds = ModelDamageSourceImpl(source)\n        val event = HitBoxDamagedEvent(craftEntity, ds, amount)\n        if (!listener.handle(event)) return false\n        return ifLivingEntity { hurtServer(world, source, event.damage) } == true\n    }\n\n    override fun deflection(projectile: Projectile): ProjectileDeflection {\n        if (projectile.owner?.uuid == delegate.uuid) return ProjectileDeflection.NONE\n        return ifLivingEntity { deflection(projectile) } ?: ProjectileDeflection.NONE\n    }\n\n    override fun getHealth(): Float {\n        return ifLivingEntity { health } ?: super.getHealth()\n    }\n\n    override fun makeBoundingBox(vec3: Vec3): AABB {\n        return if (!initialized) {\n            super.makeBoundingBox(vec3)\n        } else {\n            val scale = bone.hitBoxScale()\n            AABB(\n                vec3.x + source.minX * scale,\n                vec3.y,\n                vec3.z + source.minZ * scale,\n                vec3.x + source.maxX * scale,\n                vec3.y + source.y() * scale,\n                vec3.z + source.maxZ * scale\n            ).apply {\n                if (CONFIG.debug().has(DebugConfig.DebugOption.HITBOX)) {\n                    bukkitEntity.world.spawnParticle(Particle.DUST, minX, minY, minZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                    bukkitEntity.world.spawnParticle(Particle.DUST, maxX, maxY, maxZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                }\n            }\n        }\n    }\n    override fun getDefaultDimensions(pose: Pose): EntityDimensions = if (initialized) dimensions else super.getDefaultDimensions(pose)\n\n    override fun removeHitBox() {\n        source().task {\n            dismountAll()\n            remove(ifLivingEntity { removalReason } ?: RemovalReason.KILLED)\n        }\n    }\n\n    private inline fun <T> ifLivingEntity(block: LivingEntity.() -> T): T? {\n        return if (delegate.valid) (delegate as? LivingEntity)?.block() else null\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/HitBoxInteraction.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.nms.HitBox\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.Interaction\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftInteraction\n\ninternal class HitBoxInteraction(\n    val delegate: HitBoxImpl\n) : Interaction(EntityType.INTERACTION, delegate.level()) {\n\n    init {\n        persist = false\n    }\n\n    private val craftEntity: CraftInteraction by lazy {\n        object : CraftInteraction(Bukkit.getServer() as CraftServer, this), HitBox by delegate {}\n    }\n\n    override fun getBukkitEntity(): CraftEntity = craftEntity\n    override fun getBukkitEntityRaw(): CraftEntity = craftEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun tick() {\n        val dimension = delegate.dimensions\n        width = dimension.width\n        height = dimension.height\n        yRot = delegate.yRot\n        xRot = delegate.xRot\n        setSharedFlagOnFire(delegate.remainingFireTicks > 0)\n    }\n\n    override fun skipAttackInteraction(entity: Entity): Boolean {\n        return if (entity is Player) {\n            entity.attack(delegate)\n            true\n        } else false\n    }\n\n    override fun interact(player: Player, hand: InteractionHand): InteractionResult {\n        delegate.interact(player, hand)\n        return InteractionResult.FAIL\n    }\n\n    override fun interactAt(player: Player, vec: Vec3, hand: InteractionHand): InteractionResult {\n        delegate.interactAt(player, vec, hand)\n        return InteractionResult.FAIL\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/ModAnimationBundlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.nms.ModAnimationBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.RegistryFriendlyByteBuf\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket\nimport net.minecraft.server.MinecraftServer\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\ninternal class ModAnimationBundlerImpl(initialCapacity: Int) : ModAnimationBundler {\n\n    companion object {\n\n        const val KEY = \"modelengine:bulk_data\"\n\n        const val PACKET_TYPE_BULK_DATA = 0x00\n\n        const val FIELD_TRANSLATION = 1 shl 0\n        const val FIELD_LEFT_ROTATION = 1 shl 1\n        const val FIELD_SCALE = 1 shl 2\n        const val FIELD_TRANSFORM_DURATION = 1 shl 4\n\n        private val EMPTY_BUILD_TASK: (FriendlyByteBuf) -> Unit = {}\n    }\n\n    private val packet by lazy {\n        useByteBuf { buffer ->\n            ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC.decode(\n                RegistryFriendlyByteBuf(\n                    buffer,\n                    MinecraftServer.getServer().registryAccess()\n                ).apply {\n                    writeUtf(KEY)\n                    useByteBuf {\n                        it.writeByte(PACKET_TYPE_BULK_DATA)\n                        it.writeVarInt(builderList.size)\n                        builderList.forEach { builder -> builder(it) }\n                        writeBytes(it)\n                    }\n                }\n            )\n        }\n    }\n\n    private val builderList = ArrayList<(FriendlyByteBuf) -> Unit>(initialCapacity)\n\n    override fun send(player: PlatformPlayer) {\n        (player.unwarp() as CraftPlayer).handle.connection.send(packet)\n    }\n\n    fun append(id: Int, scope: Appender.() -> Unit) {\n        val build = Appender(id).apply(scope).build()\n        if (build !== EMPTY_BUILD_TASK) builderList += build\n    }\n\n    class Appender(\n        val entityId: Int,\n    ) {\n        private var mask = 0\n        private var buildTask = EMPTY_BUILD_TASK\n        private val isEmpty get() = buildTask === EMPTY_BUILD_TASK\n\n        fun appendPosition(vector: Vector3f) {\n            mask = mask or FIELD_TRANSLATION\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendScale(vector: Vector3f) {\n            mask = mask or FIELD_SCALE\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendRotation(quaternion: Quaternionf) {\n            mask = mask or FIELD_LEFT_ROTATION\n            task {\n                writeFloat(it, quaternion.x)\n                writeFloat(it, quaternion.y)\n                writeFloat(it, quaternion.z)\n                writeFloat(it, quaternion.w)\n            }\n        }\n\n        fun appendDuration(duration: Int) {\n            mask = mask or FIELD_TRANSFORM_DURATION\n            task {\n                writeVarInt(it, duration)\n            }\n        }\n\n        fun build(): (FriendlyByteBuf) -> Unit {\n            if (isEmpty) return EMPTY_BUILD_TASK\n            val m = mask\n            val t = buildTask\n            return {\n                writeVarInt(it,entityId)\n                writeByte(it, m)\n                t(it)\n            }\n        }\n\n        private fun task(task: (FriendlyByteBuf) -> Unit) {\n            if (isEmpty) {\n                buildTask = task\n                return\n            }\n            val last = buildTask\n            buildTask = {\n                last(it)\n                task(it)\n            }\n        }\n\n        private fun writeFloat(buf: FriendlyByteBuf, float: Float) {\n            buf.writeShort(MathUtil.floatToHalf(float).toInt())\n        }\n\n        private fun writeVarInt(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeVarInt(duration)\n        }\n\n        private fun writeByte(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeByte(duration)\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/ModelDamageSourceImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.event.ModelDamageSource\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport net.minecraft.world.damagesource.DamageSource\nimport org.bukkit.craftbukkit.util.CraftLocation\n\ninternal class ModelDamageSourceImpl(\n    private val source: DamageSource\n) : ModelDamageSource {\n    override fun getCausingEntity(): PlatformEntity? = source.entity?.bukkitEntity?.wrap()\n    override fun getDirectEntity(): PlatformEntity? = source.directEntity?.bukkitEntity?.wrap()\n    override fun getDamageLocation(): PlatformLocation? = source.sourcePositionRaw()?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun getSourceLocation(): PlatformLocation? = source.sourcePosition?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun isIndirect(): Boolean = !source.isDirect\n    override fun getFoodExhaustion(): Float = source.foodExhaustion\n    override fun scalesWithDifficulty(): Boolean = source.scalesWithDifficulty()\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/ModelDisplayImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.ModelDisplay\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformItemTransform\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.util.Brightness\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport org.joml.Quaternionf\nimport org.joml.Vector3d\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicBoolean\n\ninternal class ModelDisplayImpl(\n    private val pos: Vector3d,\n    val display: ItemDisplay,\n    val yOffset: Double\n) : ModelDisplay {\n\n    private val entityData = display.entityData\n    private val entityDataLock = SingleLock()\n    private val forceGlow = AtomicBoolean()\n    private val forceInvisibility = AtomicBoolean()\n\n    private val oldPos = Vector3d(pos)\n\n    override fun id(): Int = display.id\n    override fun uuid(): UUID = display.uuid\n    override fun rotate(rotation: ModelRotation, bundler: PacketBundler) {\n        display.xRot = rotation.x\n        display.yRot = rotation.y\n        bundler += ClientboundMoveEntityPacket.Rot(\n            display.id,\n            rotation.packedY(),\n            rotation.packedX(),\n            display.onGround\n        )\n    }\n\n    override fun invisible(invisible: Boolean) {\n        if (forceInvisibility.compareAndSet(!invisible, invisible)) {\n            entityDataLock.accessToLock {\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPotionEffect(entity: BaseEntity) {\n        val beforeInvisible = display.isInvisible\n        val afterInvisible = entity.invisible()\n        entityDataLock.accessToLock {\n            display.setGlowingTag(entity.glow() || forceGlow.get())\n            if (CONFIG.followMobInvisibility() && beforeInvisible != afterInvisible) {\n                display.isInvisible = afterInvisible\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPosition(location: PlatformLocation) {\n        oldPos.set(pos)\n        pos.set(location.x(), location.y(), location.z())\n    }\n\n    override fun spawn(showItem: Boolean, bundler: PacketBundler) {\n        bundler += addPacket\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    override fun teleport(location: PlatformLocation, bundler: PacketBundler) {\n        display.moveTo(\n            location.x(),\n            location.y(),\n            location.z(),\n            location.yaw(),\n            0F\n        )\n        bundler += ClientboundTeleportEntityPacket.teleport(display.id, PositionMoveRotation.of(display), emptySet(), display.onGround)\n    }\n\n    override fun sendPosition(adapter: BaseEntity, bundler: PacketBundler) {\n        val handle = adapter.handle() as Entity\n        if (oldPos.distanceSquared(pos) < 1e-8) return\n        bundler += ClientboundEntityPositionSyncPacket(\n            display.id,\n            PositionMoveRotation.of(handle),\n            handle.onGround()\n        )\n    }\n\n    override fun display(transform: PlatformItemTransform) {\n        entityDataLock.accessToLock {\n            display.itemTransform = ItemDisplayContext.BY_ID.apply(transform.ordinal)\n        }\n    }\n\n    override fun moveDuration(duration: Int) {\n        entityDataLock.accessToLock {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = duration\n        }\n    }\n\n    override fun item(itemStack: PlatformItemStack) {\n        entityDataLock.accessToLock {\n            display.itemStack = itemStack.unwarp().asVanilla()\n        }\n    }\n\n    override fun brightness(block: Int, sky: Int) {\n        entityDataLock.accessToLock {\n            display.brightnessOverride = if (block < 0 && sky < 0) null else Brightness(\n                block,\n                sky\n            )\n        }\n    }\n\n    override fun viewRange(range: Float) {\n        entityDataLock.accessToLock {\n            display.viewRange = range\n        }\n    }\n\n    override fun shadowRadius(radius: Float) {\n        entityDataLock.accessToLock {\n            display.shadowRadius = radius\n        }\n    }\n\n    override fun glow(glow: Boolean) {\n        if (!forceGlow.compareAndSet(!glow, glow)) return\n        entityDataLock.accessToLock {\n            display.setGlowingTag(display.isCurrentlyGlowing || glow)\n        }\n    }\n\n    override fun glowColor(glowColor: Int) {\n        entityDataLock.accessToLock {\n            display.glowColorOverride = glowColor\n        }\n    }\n\n    override fun billboard(billboard: PlatformBillboard) {\n        entityDataLock.accessToLock {\n            display.billboardConstraints = Display.BillboardConstraints.BY_ID.apply(billboard.ordinal)\n        }\n    }\n\n    override fun createTransformer(): DisplayTransformer = DisplayTransformerImpl(display)\n\n    override fun invisible(): Boolean = entityDataLock.accessToLock {\n        display.isInvisible || forceInvisibility.get() || display.itemStack.`is`(Items.AIR)\n    }\n\n    override fun sendDirtyEntityData(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                clean = true,\n                itemFilter = { it.isDirty },\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(!invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    override fun sendEntityData(showItem: Boolean, bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(showItem && !invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    private fun List<SynchedEntityData.DataValue<*>>.markVisible(showItem: Boolean) = map {\n        if (it.id == ITEM_SERIALIZER.id) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.ITEM_STACK,\n            if (showItem) display.itemStack else EMPTY_ITEM\n        ) else it\n    }\n\n    private val addPacket\n        get() = ClientboundAddEntityPacket(\n            display.id,\n            display.uuid,\n            pos.x,\n            pos.y + yOffset,\n            pos.z,\n            display.xRot,\n            display.yRot,\n            display.type,\n            0,\n            display.deltaMovement,\n            display.yHeadRot.toDouble()\n        )\n\n    private val removePacket = ClientboundRemoveEntitiesPacket(display.id)\n\n    private class DisplayTransformerImpl(\n        source: ItemDisplay\n    ) : DisplayTransformer {\n        private val id = source.id\n        private val entityData = TransformationData()\n        private val entityDataLock = SingleLock()\n\n        override fun transform(\n            duration: Int,\n            position: Vector3f,\n            scale: Vector3f,\n            rotation: Quaternionf,\n            bundler: AnimationBundler\n        ) {\n            entityDataLock.accessToLock {\n                entityData.transform(\n                    duration,\n                    position,\n                    scale,\n                    rotation\n                )\n                entityData.packDirty(id, bundler)\n            }\n        }\n\n        override fun sendTransformation(bundler: PacketBundler) {\n            entityDataLock.accessToLock {\n                entityData.pack()\n            }?.run {\n                bundler += ClientboundSetEntityDataPacket(id, this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/ModelGameProfile.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport com.mojang.authlib.GameProfile\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\n\ninternal data class ModelGameProfile(\n    private val gameProfile: GameProfile\n) : ModelProfile {\n\n    private val info = ModelProfileInfo(gameProfile.id, gameProfile.name)\n    private val skin by lazy {\n        gameProfile.properties[\"textures\"].firstOrNull()?.let {\n            BetterModel.platform().profileManager().skin(it.value)\n        } ?: ModelProfileSkin.EMPTY\n    }\n\n    override fun info(): ModelProfileInfo = info\n\n    override fun skin(): ModelProfileSkin = skin\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/ModelNametagImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport com.mojang.math.Transformation\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.BonePosition\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.nms.ModelNametag\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.EntityUtil\nimport net.kyori.adventure.text.Component\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\ninternal class ModelNametagImpl(\n    private val bone: RenderedBone\n) : ModelNametag {\n    private companion object {\n        private val emptyVector = Vector3f()\n        private val emptyTransformation = Transformation(\n            Vector3f(-1F / 40F, -0.2F - 1F / 40F, 0F),\n            null,\n            null,\n            null\n        )\n    }\n\n    private val viewedPlayer = ConcurrentHashMap.newKeySet<UUID>()\n    private val display = Display.TextDisplay(\n        EntityType.TEXT_DISPLAY,\n        MinecraftServer.getServer().overworld()\n    ).apply {\n        entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n        setTransformation(emptyTransformation)\n        billboardConstraints = Display.BillboardConstraints.CENTER\n    }\n    private val posCache = BoneMovement()\n    private var alwaysVisible = false\n    private var location = BetterModel.platform().adapter().zero()\n\n    override fun component(component: Component?) {\n        display.text = component?.asVanilla() ?: VanillaComponent.empty()\n    }\n\n    override fun teleport(location: PlatformLocation) {\n        this.location = location\n    }\n\n    override fun alwaysVisible(alwaysVisible: Boolean) {\n        this.alwaysVisible = alwaysVisible\n    }\n\n    override fun send(player: PlatformPlayer) {\n        if (display.text == VanillaComponent.empty()) return\n        val hb = bone.group.hitBoxPoint\n        val pos = bone.worldPosition(BonePosition(emptyVector, hb, player.uuid()), posCache)\n        display.moveTo(Vec3(\n            location.x() + pos.x,\n            location.y() + pos.y,\n            location.z() + pos.z\n        ))\n        val inPoint = alwaysVisible || EntityUtil.isCustomNameVisible(player.location(), location)\n        when {\n            inPoint && viewedPlayer.add(player.uuid()) -> bundlerOfNotNull(\n                addPacket,\n                display.entityData.pack()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            inPoint -> bundlerOfNotNull(\n                ClientboundEntityPositionSyncPacket(display.id, PositionMoveRotation.of(display), false),\n                display.entityData.packDirty()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            viewedPlayer.remove(player.uuid()) -> bundlerOf(removePacket)\n            else -> null\n        }?.send(player)\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    private val addPacket get() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        display.x,\n        display.y,\n        display.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket get() = ClientboundRemoveEntitiesPacket(display.id)\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/NMSImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup\nimport com.mojang.authlib.GameProfile\nimport io.netty.channel.ChannelDuplexHandler\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.channel.ChannelPromise\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.*\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.network.Connection\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.resources.Identifier\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl\nimport net.minecraft.util.ARGB\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport net.minecraft.world.item.component.CustomModelData\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.level.entity.LevelEntityGetter\nimport net.minecraft.world.level.entity.LevelEntityGetterAdapter\nimport net.minecraft.world.level.entity.PersistentEntitySectionManager\nimport org.bukkit.craftbukkit.CraftWorld\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Vector3d\nimport java.util.*\nimport java.util.function.Consumer\nimport java.util.function.IntConsumer\n\nclass NMSImpl : NMS {\n\n    companion object {\n        private const val INJECT_NAME = \"bettermodel_channel_handler\"\n\n        //Spigot\n        private val getGameProfile: (net.minecraft.world.entity.player.Player) -> GameProfile = createAdaptedFieldGetter { it.gameProfile }\n        private val getConnection: (ServerCommonPacketListenerImpl) -> Connection = createAdaptedFieldGetter { it.connection }\n        private val spigotChunkAccess = ServerLevel::class.java.fields.firstOrNull {\n            it.type == PersistentEntitySectionManager::class.java\n        }?.apply {\n            isAccessible = true\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        private val ServerLevel.levelGetter\n            get(): LevelEntityGetter<Entity> {\n                return if (BetterModelBukkit.IS_PAPER) {\n                    `moonrise$getEntityLookup`()\n                } else {\n                    spigotChunkAccess?.get(this)?.let {\n                        (it as PersistentEntitySectionManager<*>).entityGetter as LevelEntityGetter<Entity>\n                    } ?: throw RuntimeException(\"LevelEntityGetter\")\n                }\n            }\n        private val getEntityById: (LevelEntityGetter<Entity>, Int) -> Entity? = if (BetterModelBukkit.IS_PAPER) { g, i ->\n            (g as EntityLookup)[i]\n        } else LevelEntityGetterAdapter::class.java.declaredFields.first {\n            net.minecraft.world.level.entity.EntityLookup::class.java.isAssignableFrom(it.type)\n        }.let {\n            it.isAccessible = true\n            { e, i ->\n                (it[e] as net.minecraft.world.level.entity.EntityLookup<*>).getEntity(i) as? Entity\n            }\n        }\n        private fun Int.toEntity(level: ServerLevel) = getEntityById(level.levelGetter, this)\n        //Spigot\n        private val hitBoxData by lazy {\n            ItemDisplay(EntityType.ITEM_DISPLAY, MinecraftServer.getServer().overworld()).run {\n                entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n                entityData.nonDefaultValues!!\n            }\n        }\n    }\n\n    override fun hide(channel: PlayerChannelHandler, registry: EntityTrackerRegistry) {\n        val target = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf()\n        target.entityData.pack(\n            valueFilter = { it.id == SHARED_FLAG }\n        )?.let {\n            list += ClientboundSetEntityDataPacket(target.id, it).toRegistryDataPacket(channel.uuid(), registry)\n        }\n        if (target is LivingEntity) {\n            val packet = if (registry.hideOption(channel.uuid()).equipment) target.toEmptyEquipmentPacket() else target.toEquipmentPacket()\n            packet?.let { list += it }\n        }\n        list.send(channel.player())\n    }\n\n    private fun ClientboundSetEntityDataPacket.toRegistryDataPacket(uuid: UUID, registry: EntityTrackerRegistry) = ClientboundSetEntityDataPacket(id, packedItems().map {\n        if (it.id == SHARED_FLAG) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.BYTE,\n            registry.entityFlag(uuid, it.value() as Byte)\n        ) else it\n    })\n\n    inner class PlayerChannelHandlerImpl(\n        private val player: CraftPlayer\n    ) : PlayerChannelHandler, ChannelDuplexHandler() {\n        private val connection = player.handle.connection\n        private val uuid = player.uniqueId\n        private val base = adapt(player.wrap())\n\n        init {\n            val pipeline = getConnection(connection).channel.pipeline()\n            pipeline.addBefore(pipeline.first { it.value is Connection }.key, INJECT_NAME, this)\n        }\n\n        override fun close() {\n            val channel = getConnection(connection).channel\n            channel.eventLoop().submit {\n                channel.pipeline().remove(INJECT_NAME)\n            }\n        }\n\n        override fun base(): BasePlayer = base\n        override fun isModEnabled(): Boolean = (if (BetterModelBukkit.IS_PAPER) player.channels() else player.listeningPluginChannels).contains(ModAnimationBundlerImpl.KEY)\n\n        private val playerModel get() = connection.player.id.toRegistry()\n\n        private fun Int.toPlayerEntity() = toEntity(connection.player.level())\n        private fun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n        private inline fun Int.toRegistry(\n            ifHitBox: (Entity) -> Unit = {}\n        ) = (EntityTrackerRegistry.registry(this) ?: toPlayerEntity()?.let {\n            if (it is HitBox) ifHitBox(it)\n            it.toRegistry()\n        })?.takeIf {\n            it.isSpawned(player.uniqueId)\n        }\n\n        override fun sendEntityData(registry: EntityTrackerRegistry) {\n            val handle = registry.entity().handle() as? Entity ?: return\n            val list = bundlerOf(\n                ClientboundSetPassengersPacket(handle)\n            )\n            handle.entityData.pack(\n                valueFilter = { it.id == SHARED_FLAG }\n            )?.let {\n                list += ClientboundSetEntityDataPacket(handle.id, it)\n            }\n            if (handle is LivingEntity) handle.toEquipmentPacket()?.let {\n                list += it\n            }\n            list.send(player.wrap())\n        }\n\n        private fun <T : ClientGamePacketListener> Packet<in T>.handle(): Packet<in T>? {\n            when (this) {\n                is ClientboundBundlePacket -> return if (subPackets() is Keyed) this else ClientboundBundlePacket(subPackets().mapNotNull {\n                    it.handle()\n                })\n                is ClientboundAddEntityPacket -> {\n                    val entity = id.toPlayerEntity() ?: return this\n                    if (entity is HitBox) return entity.toFakeAddPacket()\n                    val wrap = entity.bukkitEntity.wrap()\n                    BetterModel.registry(wrap).ifPresent {\n                        wrap.taskLater(1) {\n                            it.spawn(player.wrap())\n                        }\n                    }\n                }\n                is ClientboundRemoveEntitiesPacket -> {\n                    entityIds\n                        .asSequence()\n                        .mapNotNull map@ {\n                            it.toRegistry {\n                                return@map null\n                            }\n                        }\n                        .forEach {\n                            it.remove()\n                        }\n                }\n                is ClientboundSetPassengersPacket -> {\n                    vehicle.toRegistry()?.let {\n                        return it.mountPacket(it.entity().handle() as? Entity ?: return this, array = passengers)\n                    }\n                }\n                is ClientboundUpdateAttributesPacket if entityId.toPlayerEntity() is HitBox -> return null\n                is ClientboundSetEntityDataPacket -> id.toRegistry {\n                    return ClientboundSetEntityDataPacket(id, hitBoxData)\n                }?.let { registry ->\n                    return toRegistryDataPacket(uuid, registry)\n                }\n                is ClientboundSetEquipmentPacket -> entity.toRegistry {\n                    return null\n                }?.let {\n                    if (it.hideOption(uuid).equipment()) (it.entity().handle() as? LivingEntity)?.toEmptyEquipmentPacket()?.let { packet ->\n                        return packet\n                    }\n                }\n                is ClientboundRespawnPacket -> playerModel?.let {\n                    bundlerOf(it.mountPacket(connection.player)).send(player.wrap())\n                }\n                is ClientboundContainerSetSlotPacket if isEquipment(connection.player) && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetSlotPacket(containerId, stateId, slot, EMPTY_ITEM)\n                }\n                is ClientboundContainerSetContentPacket if containerId == 0 && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetContentPacket(\n                        containerId,\n                        stateId,\n                        items.apply {\n                            PLAYER_EQUIPMENT_SLOT.forEach(IntConsumer { set(it, EMPTY_ITEM) })\n                            set(connection.player.hotbarSlot, EMPTY_ITEM)\n                        },\n                        carriedItem\n                    )\n                }\n            }\n            return this\n        }\n\n        override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {\n            super.write(ctx, if (msg is Packet<*>) msg.handle() ?: return else msg, promise)\n        }\n\n        override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {\n            fun EntityTrackerRegistry.updatePlayerLimb() = BetterModel.platform().scheduler().asyncTaskLater(1) {\n                if (isClosed) return@asyncTaskLater\n                player.handle.containerMenu.sendAllDataToRemote()\n                trackers().forEach { tracker ->\n                    tracker.update(TrackerUpdateAction.itemMapping()) { bone ->\n                        !bone.itemMapper.fixed()\n                    }\n                }\n            }\n            when (msg) {\n                is ServerboundSetCarriedItemPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) {\n                            connection.send(ClientboundSetHeldSlotPacket(player.inventory.heldItemSlot))\n                            return\n                        }\n                        registry.updatePlayerLimb()\n                    }\n                }\n                is ServerboundPlayerActionPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) return\n                        registry.updatePlayerLimb()\n                    }\n                }\n            }\n            super.channelRead(ctx, msg)\n        }\n\n        private fun EntityTrackerRegistry.remove() {\n            remove(player.wrap())\n        }\n    }\n\n    override fun mount(registry: EntityTrackerRegistry, bundler: PacketBundler) {\n        val entity = registry.entity().handle()\n        if (entity is Entity) bundler += registry.mountPacket(entity)\n    }\n\n    private fun EntityTrackerRegistry.mountPacket(entity: Entity, array: IntArray = entity.passengers.filter {\n        EntityTrackerRegistry.registry(it.uuid) == null\n    }.map {\n        it.id\n    }.toIntArray()): ClientboundSetPassengersPacket {\n        return useByteBuf { buffer ->\n            buffer.writeVarInt(entity.id)\n            buffer.writeVarIntArray(displays()\n                .mapToInt {\n                    (it as ModelDisplayImpl).display.id\n                }.toArray() + array)\n            ClientboundSetPassengersPacket.STREAM_CODEC.decode(buffer)\n        }\n    }\n\n    override fun inject(player: PlatformPlayer): PlayerChannelHandlerImpl = PlayerChannelHandlerImpl(player.unwarp() as CraftPlayer)\n\n    override fun createBundler(initialCapacity: Int): PacketBundler = bundlerOf(initialCapacity)\n    override fun createParallelBundler(threshold: Int): PacketBundler = parallelBundlerOf(threshold)\n    override fun createModAnimationBuilder(initialCapacity: Int): ModAnimationBundler = ModAnimationBundlerImpl(initialCapacity)\n\n    override fun create(location: PlatformLocation, yOffset: Double, initialConsumer: Consumer<ModelDisplay>): ModelDisplay = ModelDisplayImpl(\n        Vector3d(location.x(), location.y(), location.z()),\n        ItemDisplay(EntityType.ITEM_DISPLAY, (location.world().unwarp() as CraftWorld).handle).apply {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n            billboardConstraints = Display.BillboardConstraints.FIXED\n            valid = true\n            yRot = location.yaw()\n            itemTransform = ItemDisplayContext.FIXED\n        },\n        yOffset\n    ).apply {\n        initialConsumer.accept(this)\n        display.entityData.packDirty()\n    }\n\n    override fun createNametag(bone: RenderedBone): ModelNametag = ModelNametagImpl(bone)\n\n    override fun tint(itemStack: PlatformItemStack, rgb: Int): PlatformItemStack {\n        return itemStack.unwarp().asVanilla().apply {\n            set(DataComponents.DYED_COLOR, DyedItemColor(rgb))\n            set(DataComponents.CUSTOM_MODEL_DATA, get(DataComponents.CUSTOM_MODEL_DATA)?.let {\n                CustomModelData(it.floats, it.flags, it.strings, it.colors\n                    .run {\n                        if (rgb == 0xFFFFFF) this else map { color ->\n                            ARGB.multiply(color, rgb) and 0xFFFFFF\n                        }\n                    }\n                    .ifEmpty { listOf(rgb) })\n            })\n        }.asBukkit().wrap()\n    }\n\n    override fun createHitBox(entity: BaseEntity, bone: RenderedBone, boundingBox: ModelBoundingBox, mountController: MountController, listener: HitBoxListener): HitBox? {\n        val handle = entity.handle() as? Entity ?: return null\n        return HitBoxImpl(\n            boundingBox.center(),\n            bone,\n            listener,\n            handle,\n            mountController\n        ).craftEntity\n    }\n    override fun version(): NMSVersion = NMSVersion.V1_21_R7\n\n    override fun adapt(entity: PlatformEntity): BaseBukkitEntity {\n        val craft = entity.unwarp() as CraftEntity\n        return BaseEntityImpl(craft)\n    }\n\n    override fun adapt(player: PlatformPlayer): BasePlayer {\n        val craft = player.unwarp() as CraftPlayer\n        return BasePlayerImpl(\n            craft,\n            dirtyChecked({ getGameProfile(craft.handle) }, { ModelGameProfile(it) }),\n            dirtyChecked({ craft.handle.toCustomisation() }, { PlayerSkinParts(it) })\n        )\n    }\n\n    override fun profile(player: PlatformPlayer): ModelProfile = ModelGameProfile(getGameProfile((player.unwarp() as CraftPlayer).handle))\n\n    override fun createSkinItem(model: String, floats: List<Float>, flags: List<Boolean>, strings: List<String>, colors: List<Int>): TransformedItemStack {\n        return VanillaItemStack(Items.PLAYER_HEAD).run {\n            set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(floats, flags, strings, colors))\n            set(DataComponents.ITEM_MODEL, Identifier.parse(model))\n            TransformedItemStack.of(asBukkit().wrap())\n        }\n    }\n\n    override fun isProxyOnlineMode(): Boolean = ONLINE_MODE\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/PacketBundlers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.kyori.adventure.key.Key\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.network.PacketSendListener\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\nprivate val KEY = Key.key(\"bettermodel\")\n\ninternal fun bundlerOfNotNull(vararg packets: ClientPacket?) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.filterNotNull().toMutableList())\ninternal fun bundlerOf(vararg packets: ClientPacket) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.toMutableList())\ninternal fun bundlerOf(size: Int) = SimpleBundler(ArrayList(size))\ninternal fun parallelBundlerOf(threshold: Int) = ParallelBundler(threshold)\n\ninternal operator fun PacketBundler.plusAssign(other: ClientPacket) {\n    when (this) {\n        is SimpleBundler -> add(other)\n        is ParallelBundler -> add(other)\n        else -> throw RuntimeException(\"unsupported bundler.\")\n    }\n}\ninternal fun Packet<*>.assumeSize() = when (this) {\n    is ClientboundSetEntityDataPacket -> packedItems.size\n    is ClientboundSetEquipmentPacket -> slots.size\n    else -> 1\n}\n\ninternal interface PluginBundlePacketImpl : Iterable<ClientPacket>, Keyed {\n    val bundlePacket: ClientboundBundlePacket\n    fun size(): Int\n    fun isEmpty(): Boolean\n    fun add(other: ClientPacket)\n}\n\ninternal class SimpleBundler(\n    private val list: MutableList<ClientPacket>\n) : PacketBundler, PluginBundlePacketImpl {\n    override val bundlePacket = ClientboundBundlePacket(this)\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        connection.send(bundlePacket, PacketSendListener.thenRun(onSuccess))\n    }\n    override fun isEmpty(): Boolean = list.isEmpty()\n    override fun size(): Int = list.size\n    override fun key(): Key = KEY\n    override fun iterator(): MutableIterator<ClientPacket> = list.iterator()\n    override fun add(other: ClientPacket) {\n        list += other\n    }\n}\n\ninternal class ParallelBundler(\n    private val threshold: Int\n) : PacketBundler {\n    private val subBundlers = mutableListOf<PluginBundlePacketImpl>()\n    private var sizeAssume = 0\n    private val newBundler get() = bundlerOf().apply {\n        sizeAssume = 0\n        subBundlers += this\n    }\n    private var selectedBundler = newBundler\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        subBundlers.forEach {\n            connection.send(it.bundlePacket)\n        }\n    }\n    override fun isEmpty(): Boolean = selectedBundler.isEmpty()\n    override fun size(): Int = subBundlers.sumOf(PluginBundlePacketImpl::size)\n    fun add(other: ClientPacket) {\n        (if (sizeAssume > threshold) newBundler else selectedBundler)\n            .apply { selectedBundler = this }\n            .add(other)\n        sizeAssume += other.assumeSize()\n    }\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/PlayerArmorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.item.equipment.EquipmentAssets\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\ninternal data class PlayerArmorImpl(\n    private val player: CraftPlayer\n) : PlayerArmor {\n\n    override fun helmet(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.HEAD).toArmorItem()\n    }\n\n    override fun leggings(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.LEGS).toArmorItem()\n    }\n\n    override fun chestplate(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.CHEST).toArmorItem()\n    }\n\n    override fun boots(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.FEET).toArmorItem()\n    }\n\n    private fun VanillaItemStack.toArmorItem(): ArmorItem? = get(DataComponents.EQUIPPABLE)?.assetId?.map {\n        val trim = get(DataComponents.TRIM)\n        ArmorItem(\n            get(DataComponents.DYED_COLOR)?.rgb ?: if (it === EquipmentAssets.LEATHER) DyedItemColor.LEATHER_COLOR else 0xFFFFFF,\n            it.identifier().path,\n            trim?.pattern?.value()?.assetId?.path,\n            trim?.material?.value()?.assets?.base?.suffix\n        )\n    }?.orElse(null)\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/ProfiledImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\n\ninternal class ProfiledImpl(\n    private val playerArmor: PlayerArmor,\n    private val modelProfile: () -> ModelProfile,\n    private val playerSkinParts: () -> PlayerSkinParts\n) : Profiled {\n\n    override fun profile(): ModelProfile = modelProfile()\n    override fun armors(): PlayerArmor = playerArmor\n    override fun skinParts(): PlayerSkinParts = playerSkinParts()\n}\n"
  },
  {
    "path": "nms/v1_21_R7/src/main/kotlin/kr/toxicity/model/bukkit/nms/v1_21_R7/TypeAliases.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v1_21_R7\n\nimport net.minecraft.network.chat.Component\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientGamePacketListener\nimport net.minecraft.world.item.ItemStack\n\ninternal typealias VanillaItemStack = ItemStack\ninternal typealias BukkitItemStack = org.bukkit.inventory.ItemStack\ninternal typealias ClientPacket = Packet<ClientGamePacketListener>\ninternal typealias VanillaComponent = Component\ninternal typealias AdventureComponent = net.kyori.adventure.text.Component\n"
  },
  {
    "path": "nms/v26_R1/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.paperweight)\n}\n\ndependencies {\n    paperweight.paperDevBundle(\"26.1.1.build.+\")\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/java/kr/toxicity/model/bukkit/nms/v26_R1/AbstractHitBox.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1;\n\nimport kr.toxicity.model.api.nms.HitBox;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\npublic abstract class AbstractHitBox extends ArmorStand implements HitBox {\n\n    AbstractHitBox(@NotNull Level level) {\n        super(EntityType.ARMOR_STAND, level);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final boolean equals(@Nullable Object other) {\n        return super.equals(other);\n    }\n\n    @Override //Only for provide compiler hint for Kotlin jvm\n    public final int hashCode() {\n        return super.hashCode();\n    }\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/BaseEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.persistence.PersistentDataHolder\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.stream.Stream\n\ninternal data class BaseEntityImpl(\n    private val delegate: CraftEntity\n) : BaseBukkitEntity, PersistentDataHolder by delegate {\n    override fun customName(): AdventureComponent? = handle().run {\n        if (this is ServerPlayer) (customName ?: name).asAdventure() else customName?.asAdventure()?.takeIf {\n            isCustomNameVisible\n        }\n    }\n\n    override fun entity(): org.bukkit.entity.Entity = delegate\n    override fun handle(): Entity = delegate.vanillaEntity\n    override fun uuid(): UUID = delegate.uniqueId\n    override fun id(): Int = handle().id\n    override fun dead(): Boolean = (handle() as? LivingEntity)?.isDeadOrDying == true || handle().removalReason != null || !handle().valid\n    override fun invisible(): Boolean = handle().isInvisible || (handle() as? LivingEntity)?.hasEffect(MobEffects.INVISIBILITY) == true\n    override fun glow(): Boolean = handle().isCurrentlyGlowing\n\n    override fun onWalk(): Boolean {\n        return handle().isWalking()\n    }\n\n    override fun scale(): Double {\n        val handle = handle()\n        return if (handle is LivingEntity) handle.scale.toDouble() else 1.0\n    }\n\n    override fun pitch(): Float = handle().xRot\n    override fun ground(): Boolean = handle().onGround()\n    override fun bodyYaw(): Float = handle().let { if (it is LivingEntity) it.yBodyRot else it.yRot }\n    override fun yaw(): Float = handle().yRot\n    override fun headYaw(): Float = handle().let { if (it is LivingEntity) it.yHeadRot else it.yRot }\n    override fun fly(): Boolean = handle().isFlying\n\n    override fun damageTick(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        val duration = handle.invulnerableDuration.toFloat()\n        if (duration <= 0F) return 0F\n        val knockBack = 1 - (handle.getAttribute(Attributes.KNOCKBACK_RESISTANCE)?.value?.toFloat() ?: 0F)\n        return handle.invulnerableTime.toFloat() / duration * knockBack\n    }\n\n    override fun walkSpeed(): Float {\n        val handle = handle()\n        if (handle !is LivingEntity) return 0F\n        if (!handle.onGround) return 1F\n        val speed = handle.getEffect(MobEffects.SPEED)?.amplifier ?: 0\n        val slow = handle.getEffect(MobEffects.SLOWNESS)?.amplifier ?: 0\n        return (1F + (speed - slow) * 0.2F)\n            .coerceAtLeast(0.2F)\n            .coerceAtMost(2F)\n    }\n\n    override fun passengerPosition(dest: Vector3f): Vector3f {\n        return handle().passengerPosition(dest)\n    }\n\n    override fun platform(): PlatformEntity = delegate.wrap()\n    override fun trackedBy(): Stream<PlatformPlayer> = delegate.trackedBy.stream().map { it.wrap() }\n    override fun location(): PlatformLocation = delegate.location.wrap()\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/BasePlayerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitPlayer\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport net.minecraft.util.Mth\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.bukkit.entity.Player\nimport java.util.stream.Stream\n\ninternal data class BasePlayerImpl(\n    private val delegate: CraftPlayer,\n    private val profile: () -> ModelProfile,\n    private val skinParts: () -> PlayerSkinParts\n) : BaseBukkitEntity by BaseEntityImpl(delegate), BaseBukkitPlayer, Profiled by ProfiledImpl(PlayerArmorImpl(delegate), profile, skinParts) {\n\n    override fun entity(): Player = delegate\n\n    override fun updateInventory() {\n        delegate.handle.containerMenu.sendAllDataToRemote()\n    }\n\n    override fun platform(): PlatformPlayer = delegate.wrap()\n\n    override fun trackedBy(): Stream<PlatformPlayer> = Stream.concat(\n        Stream.of(delegate),\n        delegate.trackedBy.stream()\n    ).map {\n        it.wrap()\n    }\n\n    override fun bodyYaw(): Float {\n        val handle = delegate.handle\n        var yaw = -45 * handle.xMovement()\n        if (handle.zMovement() < 0) yaw *= -1\n        return Mth.wrapDegrees(handle.yHeadRot + yaw)\n    }\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/BukkitWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.bukkit.platform.*\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter.adapt\nimport kr.toxicity.model.api.bukkit.platform.BukkitItemStack\nimport kr.toxicity.model.api.platform.*\nimport org.bukkit.Location\nimport org.bukkit.OfflinePlayer\nimport org.bukkit.World\nimport org.bukkit.entity.Entity\nimport org.bukkit.entity.LivingEntity\nimport org.bukkit.entity.Player\nimport org.bukkit.inventory.ItemStack\n\ninternal fun Entity.wrap() = adapt(this)\ninternal fun LivingEntity.wrap() = adapt(this)\ninternal fun OfflinePlayer.wrap() = adapt(this)\ninternal fun Player.wrap() = adapt(this)\ninternal fun Location.wrap() = adapt(this)\ninternal fun World.wrap() = adapt(this)\ninternal fun ItemStack.wrap() = adapt(this)\n\ninternal fun PlatformEntity.unwarp(): Entity = (this as BukkitEntity).source()\ninternal fun PlatformLivingEntity.unwarp(): LivingEntity = (this as BukkitLivingEntity).source()\ninternal fun PlatformOfflinePlayer.unwarp(): OfflinePlayer = (this as BukkitOfflinePlayer).source()\ninternal fun PlatformPlayer.unwarp(): Player = (this as BukkitPlayer).source()\ninternal fun PlatformLocation.unwarp(): Location = (this as BukkitLocation).source()\ninternal fun PlatformWorld.unwarp(): World = (this as BukkitWorld).source()\ninternal fun PlatformItemStack.unwarp(): ItemStack = (this as BukkitItemStack).source()\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/EntityData.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\nimport java.lang.reflect.Field\n\ninternal fun Field.toEntityDataAccessor() = run {\n    isAccessible = true\n    get(null) as EntityDataAccessor<*>\n}\n\ninternal fun Class<*>.accessors() = declaredFields.filter { f ->\n    EntityDataAccessor::class.java.isAssignableFrom(f.type)\n}.map {\n    it.toEntityDataAccessor()\n}\n\ninternal val DISPLAY_SET = Display::class.java.accessors()\ninternal val SHARED_FLAG = Entity::class.java.accessors().first().id\ninternal val ITEM_DISPLAY_ID = ItemDisplay::class.java.accessors().map {\n    it.id\n}\ninternal val ITEM_SERIALIZER = ItemDisplay::class.java.accessors().first()\ninternal val ITEM_ENTITY_DATA = buildList {\n    add(SHARED_FLAG)\n    addAll(ITEM_DISPLAY_ID)\n    add(Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID.id)\n    DISPLAY_SET.subList(7, DISPLAY_SET.size).mapTo(this) { it.id }\n}.toIntSet()\n\n@Suppress(\"UNCHECKED_CAST\")\nprivate val DISPLAY_INTERPOLATION_DELAY = (DISPLAY_SET.first() as EntityDataAccessor<Int>).run {\n    SynchedEntityData.DataValue(id, serializer, 0)\n}\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_INTERPOLATION_DURATION = DISPLAY_SET[1] as EntityDataAccessor<Int>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_TRANSLATION = DISPLAY_SET[3] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_SCALE = DISPLAY_SET[4] as EntityDataAccessor<Vector3f>\n@Suppress(\"UNCHECKED_CAST\")\ninternal val DISPLAY_ROTATION = DISPLAY_SET[5] as EntityDataAccessor<Quaternionf>\n\n\ninternal class TransformationData {\n\n    private var _duration = 0\n    private val duration get() = SynchedEntityData.DataValue(DISPLAY_INTERPOLATION_DURATION.id, DISPLAY_INTERPOLATION_DURATION.serializer, _duration)\n    private val translation = Item(Vector3f(), DISPLAY_TRANSLATION, MathUtil::isSimilar, Vector3f::set)\n    private val scale = Item(Vector3f(), DISPLAY_SCALE, MathUtil::isSimilar, Vector3f::set)\n    private val rotation = Item(Quaternionf(), DISPLAY_ROTATION, MathUtil::isSimilar, Quaternionf::set)\n\n    fun packDirty(entityId: Int, dest: AnimationBundler) {\n        val i = translation.cleanIndex + scale.cleanIndex + rotation.cleanIndex\n        if (i == 0) return\n        (dest.mod as ModAnimationBundlerImpl).append(entityId) {\n            dest.standard += ClientboundSetEntityDataPacket(entityId, buildList(i + 2) {\n                add(DISPLAY_INTERPOLATION_DELAY)\n                translation.value?.let { appendPosition(it.value); add(it) }\n                rotation.value?.let { appendRotation(it.value); add(it) }\n                scale.value?.let { appendScale(it.value); add(it) }\n                appendDuration(_duration); add(duration)\n            })\n        }\n    }\n\n    fun transform(\n        duration: Int,\n        translation: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf\n    ) {\n        _duration = duration\n        this.translation.set(translation)\n        this.scale.set(scale)\n        this.rotation.set(rotation)\n    }\n\n    fun pack() = listOf(\n        DISPLAY_INTERPOLATION_DELAY,\n        duration,\n        translation.forceValue,\n        scale.forceValue,\n        rotation.forceValue\n    )\n\n    private class Item<T : Any>(\n        initialValue: T,\n        private val accessor: EntityDataAccessor<T>,\n        private val dirtyChecker: (T, T) -> Boolean,\n        private val setter: (T, T) -> Unit\n    ) {\n        private val _t: T = initialValue\n        private var _dirty = false\n\n        val dirty get() = _dirty\n        val cleanIndex get() = if (dirty) 1 else 0\n        val value get() = if (_dirty) {\n            _dirty = false\n            forceValue\n        } else null\n        val forceValue get() = SynchedEntityData.DataValue(accessor.id, accessor.serializer, _t)\n\n        fun set(other: T) {\n            if (dirtyChecker(_t, other)) return\n            _dirty = true\n            setter(_t, other)\n        }\n    }\n}\n\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport io.netty.buffer.Unpooled\nimport io.papermc.paper.adventure.PaperAdventure\nimport io.papermc.paper.configuration.GlobalConfiguration\nimport it.unimi.dsi.fastutil.ints.IntSet\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.network.syncher.SynchedEntityData.DataItem\nimport net.minecraft.network.syncher.SynchedEntityData.DataValue\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.goal.RangedAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedBowAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal\nimport net.minecraft.world.entity.animal.FlyingAnimal\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.inventory.CraftItemStack\nimport org.bukkit.craftbukkit.util.CraftChatMessage\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(noinline paperGetter: (T) -> R): (T) -> R {\n    return if (BetterModelBukkit.IS_PAPER) paperGetter else createAdaptedFieldGetter()\n}\ninternal inline fun <reified T, reified R> createAdaptedFieldGetter(): (T) -> R {\n    return T::class.java.declaredFields.first {\n        R::class.java.isAssignableFrom(it.type)\n    }.apply {\n        isAccessible = true\n    }.let { getter ->\n        { t ->\n            getter[t] as R\n        }\n    }\n}\n\ninternal fun <H, T> dirtyChecked(hash: () -> H, function: (H) -> T): () -> T {\n    val lock = Any()\n    var h = hash()\n    var value = function(h)\n    return {\n        val newH = hash()\n        when {\n            h === newH -> value\n            h == newH -> value\n            else -> synchronized(lock) {\n                h = newH\n                value = function(h)\n                value\n            }\n        }\n    }\n}\n\ninternal val CONFIG get() = BetterModel.config()\ninternal val EMPTY_ITEM = VanillaItemStack.EMPTY\ninternal fun BukkitItemStack.asVanilla() = CraftItemStack.asNMSCopy(this)\ninternal fun VanillaItemStack.asBukkit() = CraftItemStack.asCraftMirror(this)\n\ninternal val ONLINE_MODE by lazy(LazyThreadSafetyMode.NONE) {\n    if (BetterModelBukkit.IS_PAPER) GlobalConfiguration.get().proxies.isProxyOnlineMode else Bukkit.getOnlineMode()\n}\n\ninternal fun List<Int>.toIntSet(): IntSet = IntSet.of(*toIntArray())\n\ninternal fun Entity.passengerPosition(dest: Vector3f): Vector3f {\n    return attachments.get(EntityAttachment.PASSENGER, 0, yRot).let { v ->\n        dest.set(v.x.toFloat(), v.y.toFloat(), v.z.toFloat())\n    }\n}\n\nprivate val DATA_ITEMS = SynchedEntityData::class.java.declaredFields.first {\n    it.type.isArray\n}.apply {\n    isAccessible = true\n}\n\ninternal fun SynchedEntityData.pack(\n    clean: Boolean = false,\n    itemFilter: (DataItem<*>) -> Boolean = { true },\n    valueFilter: (DataValue<*>) -> Boolean = { true },\n    required: (List<Pair<DataItem<*>, DataValue<*>>>) -> Boolean = { it.isNotEmpty() }\n): List<DataValue<*>>? = (DATA_ITEMS[this] as Array<*>)\n    .mapNotNull map@ {\n        val item = (it as? DataItem<*>)?.takeIf(itemFilter) ?: return@map null\n        val value = item.value().takeIf(valueFilter) ?: return@map null\n        item to value\n    }\n    .takeIf(required)\n    ?.map {\n        if (clean) it.first.isDirty = false\n        it.second\n    }\n\ninternal fun Entity.isWalking(): Boolean {\n    return controllingPassenger?.isWalking() ?: when (this) {\n        is Mob -> navigation.isInProgress || goalSelector.availableGoals.any {\n            it.isRunning && when (it.goal) {\n                is RangedAttackGoal, is RangedCrossbowAttackGoal<*>, is RangedBowAttackGoal<*> -> true\n                else -> false\n            }\n        }\n        is ServerPlayer -> xMovement() != 0F || zMovement() != 0F\n        else -> false\n    }\n}\n\ninternal fun ServerPlayer.xMovement(): Float {\n    val leftMovement: Boolean = lastClientInput.left()\n    val rightMovement: Boolean = lastClientInput.right()\n    return if (leftMovement == rightMovement) 0F else if (leftMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.yMovement(): Float = if (isJump()) 1F else if (lastClientInput.shift) -1F else 0F\n\ninternal fun ServerPlayer.zMovement(): Float {\n    val forwardMovement: Boolean = lastClientInput.forward()\n    val backwardMovement: Boolean = lastClientInput.backward()\n    return if (forwardMovement == backwardMovement) 0F else if (forwardMovement) 1F else -1F\n}\n\ninternal fun ServerPlayer.isJump() = lastClientInput.jump()\n\ninternal val Entity.isFlying: Boolean\n    get() = when (this) {\n        is FlyingAnimal -> isFlying\n        is Mob -> isNoAi\n        is Player -> abilities.flying\n        is LivingEntity -> isFallFlying\n        else -> false\n    }\n\ninternal val CraftEntity.vanillaEntity: Entity\n    get() = if (BetterModelBukkit.IS_PAPER) handleRaw else handle\n\ninternal fun Entity.moveTo(vec: Vec3) = snapTo(vec)\ninternal fun Entity.moveTo(x: Double, y: Double, z: Double, yaw: Float, pitch: Float) = snapTo(x, y, z, yaw, pitch)\n\ninternal inline fun <T> useByteBuf(block: (FriendlyByteBuf) -> T): T {\n    val buffer = FriendlyByteBuf(Unpooled.buffer())\n    return try {\n        block(buffer)\n    } finally {\n        buffer.release()\n    }\n}\n\ninternal fun EntityTrackerRegistry.entityFlag(uuid: UUID, byte: Byte): Byte {\n    var b = byte.toInt()\n    val hideOption = hideOption(uuid)\n    if (hideOption.fire()) b = b and 1.inv()\n    if (hideOption.visibility()) b = b or (1 shl 5)\n    if (hideOption.glowing()) b = b and (1 shl 6).inv()\n    return b.toByte()\n}\n\ninternal fun Vector3f.toVanilla() = Vec3(x.toDouble(), y.toDouble(), z.toDouble())\ninternal fun Vec3.toBukkit() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())\n\ninternal inline fun LivingEntity.toEquipmentPacket(mapper: (EquipmentSlot) -> ItemStack? = { getItemBySlot(it).takeUnless { item -> item.isEmpty } }): ClientboundSetEquipmentPacket? {\n    val equip = EquipmentSlot.entries.mapNotNull {\n        mapper(it)?.let { item -> com.mojang.datafixers.util.Pair.of(it, item) }\n    }\n    return if (equip.isNotEmpty()) ClientboundSetEquipmentPacket(id, equip) else null\n}\ninternal fun LivingEntity.toEmptyEquipmentPacket() = toEquipmentPacket { ItemStack.EMPTY }\n\ninternal val Player.hotbarSlot get() = inventory.selectedSlot + 36\ninternal val PLAYER_EQUIPMENT_SLOT = IntSet.of(45, 5, 6, 7, 8)\ninternal fun ClientboundContainerSetSlotPacket.isEquipment(player: Player) = containerId == 0 && (PLAYER_EQUIPMENT_SLOT.contains(slot) || slot == player.hotbarSlot)\n\ninternal fun Entity.toFakeAddPacket() = ClientboundAddEntityPacket(\n    id,\n    uuid,\n    x,\n    y,\n    z,\n    xRot,\n    yRot,\n    EntityType.ITEM_DISPLAY,\n    0,\n    deltaMovement,\n    yHeadRot.toDouble()\n)\n\ninternal fun Avatar.toCustomisation() = entityData.get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION).toInt()\n\ninternal fun VanillaComponent.asAdventure() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asAdventure(this)\n} else {\n    GsonComponentSerializer.gson().deserialize(CraftChatMessage.toJSON(this))\n}\n\ninternal fun AdventureComponent.asVanilla() = if (BetterModelBukkit.IS_PAPER) {\n    PaperAdventure.asVanilla(this)\n} else {\n    CraftChatMessage.fromJSON(GsonComponentSerializer.gson().serialize(this))\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/HitBoxImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport io.papermc.paper.event.entity.EntityKnockbackEvent\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.event.hitbox.*\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.api.nms.ModelInteractionHand\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.minecraft.network.protocol.game.ServerboundInteractPacket\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionHand.MAIN_HAND\nimport net.minecraft.world.InteractionHand.OFF_HAND\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.damagesource.DamageSource\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.entity.projectile.Projectile\nimport net.minecraft.world.entity.projectile.ProjectileDeflection\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.level.BlockGetter\nimport net.minecraft.world.phys.AABB\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.Color\nimport org.bukkit.Particle\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftArmorStand\nimport org.bukkit.craftbukkit.entity.CraftLivingEntity\nimport org.bukkit.event.entity.CreatureSpawnEvent\nimport org.bukkit.event.entity.EntityPotionEffectEvent\nimport org.bukkit.event.entity.EntityRemoveEvent\nimport org.bukkit.plugin.Plugin\nimport org.joml.Vector3f\nimport java.util.*\n\ninternal class HitBoxImpl(\n    private val source: ModelBoundingBox,\n    private val bone: RenderedBone,\n    private var listener: HitBoxListener,\n    private val delegate: Entity,\n    private var mountController: MountController\n) : AbstractHitBox(delegate.level()) {\n    private val posCache = BoneMovement()\n    private var initialized = false\n    private var jumpDelay = 0\n    private var mounted = false\n    private var collision = ifLivingEntity { collides } == true\n    private var noGravity = if (delegate is Mob) delegate.isNoAi else delegate.isNoGravity\n    private var forceDismount = false\n    private var onFly = false\n\n    val craftEntity: HitBox by lazy {\n        object : CraftArmorStand(Bukkit.getServer() as CraftServer, this), HitBox by this {}\n    }\n    val dimensions: EntityDimensions get() = source.run {\n        EntityDimensions(\n            (x() + z()).toFloat() / 2,\n            y().toFloat(),\n            delegate.eyeHeight,\n            EntityAttachments.createDefault(0F, 0F),\n            false\n        ).scale(bone.hitBoxScale())\n    }\n    private val interaction by lazy {\n        HitBoxInteraction(this)\n    }\n    private val applier = InsideBlockEffectApplier.StepBasedCollector()\n\n    init {\n        moveTo(delegate.position())\n        isInvisible = true\n        persist = false\n        isSilent = true\n        initialized = true\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        level().addFreshEntity(interaction.apply {\n            moveTo(delegate.position())\n        }, CreatureSpawnEvent.SpawnReason.CUSTOM)\n        interaction.startRiding(this)\n        listener.handle(HitBoxCreateEvent(this))\n    }\n\n    private fun initialSetup() {\n        if (mounted) {\n            mounted = false\n            if (delegate is Mob) delegate.isNoAi = noGravity\n            else delegate.isNoGravity = noGravity\n            ifLivingEntity { collides = collision }\n        }\n    }\n\n    override fun id(): Int = id\n    override fun uuid(): UUID = uuid\n    override fun source(): PlatformEntity = delegate.bukkitEntity.wrap()\n    override fun positionSource(): RenderedBone = bone\n    override fun forceDismount(): Boolean = forceDismount\n    override fun mountController(): MountController = mountController\n    override fun hasMountDriver(): Boolean = controllingPassenger != null\n    override fun mountController(controller: MountController) {\n        this.mountController = controller\n    }\n    override fun relativePosition(): Vector3f = delegate.position().run {\n        bone.hitBoxPosition(posCache).add(x.toFloat(), y.toFloat(), z.toFloat())\n    }\n    override fun listener(): HitBoxListener = listener\n    override fun listener(listener: HitBoxListener) {\n        this.listener = listener\n    }\n    override fun getItemBySlot(slot: EquipmentSlot): ItemStack = ItemStack.EMPTY\n    override fun setItemSlot(slot: EquipmentSlot, stack: ItemStack) {\n    }\n    override fun getMainArm(): HumanoidArm = HumanoidArm.RIGHT\n\n    override fun mount(entity: PlatformEntity) {\n        if (controllingPassenger != null) return\n        if (interaction.bukkitEntity.addPassenger(entity.unwarp())) {\n            if (mountController.canControl()) {\n                mounted = true\n                noGravity = delegate.isNoGravity\n                ifLivingEntity {\n                    collision = collides\n                    collides = false\n                }\n            }\n            listener.handle(HitBoxMountEvent(this, entity))\n        }\n    }\n\n    override fun dismount(entity: PlatformEntity) {\n        forceDismount = true\n        if (interaction.bukkitEntity.removePassenger(entity.unwarp())) listener.handle(HitBoxDismountEvent(this, entity))\n        forceDismount = false\n    }\n\n    override fun dismountAll() {\n        forceDismount = true\n        interaction.passengers.forEach {\n            it.stopRiding(true)\n            listener.handle(HitBoxDismountEvent(this, it.bukkitEntity.wrap()))\n        }\n        forceDismount = false\n    }\n\n    override fun setRemainingFireTicks(remainingFireTicks: Int) {\n        delegate.remainingFireTicks = remainingFireTicks\n    }\n\n    override fun getRemainingFireTicks(): Int {\n        return delegate.remainingFireTicks\n    }\n\n    override fun knockback(\n        d0: Double,\n        d1: Double,\n        d2: Double,\n        attacker: Entity?,\n        cause: EntityKnockbackEvent.Cause\n    ) {\n        if (attacker === delegate) return\n        ifLivingEntity { knockback(d0, d1, d2, attacker, cause) }\n    }\n\n    override fun push(pushingEntity: Entity) {\n        if (pushingEntity === delegate) return\n        delegate.push(pushingEntity)\n    }\n\n    override fun push(x: Double, y: Double, z: Double, pushingEntity: Entity?) {\n        if (pushingEntity === delegate) return\n        delegate.push(x, y, z, pushingEntity)\n    }\n\n    override fun isCollidable(ignoreClimbing: Boolean): Boolean {\n        return delegate.isCollidable(ignoreClimbing)\n    }\n\n    override fun canCollideWith(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWith(entity)\n    }\n\n    override fun canCollideWithBukkit(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWithBukkit(entity)\n    }\n\n    private fun checkCollide(entity: Entity): Boolean {\n        return entity !== delegate\n                && passengers.none { it === entity }\n                && delegate.passengers.none { it === entity }\n                && (entity !is HitBoxImpl || entity.delegate !== delegate)\n    }\n\n    override fun getActiveEffects(): Collection<MobEffectInstance> {\n        return ifLivingEntity { getActiveEffects() } ?: emptyList()\n    }\n\n    override fun getControllingPassenger(): LivingEntity? {\n        return if (mounted) interaction.firstPassenger as? LivingEntity ?: super.getControllingPassenger() else null\n    }\n\n    override fun onWalk(): Boolean {\n        return isWalking()\n    }\n\n    private fun mountControl(player: ServerPlayer) {\n        if (delegate !is LivingEntity) return\n        val travelVector = Vec3(delegate.xxa.toDouble(), delegate.yya.toDouble(), delegate.zza.toDouble())\n        if (!mountController.canFly() && delegate.isFallFlying) return\n\n        updateFlyStatus(player)\n        val riddenInput = rideInput(player, travelVector)\n        if (riddenInput.length() > 0.01) {\n            delegate.yRot = player.yRot\n            if (onFly) delegate.yHeadRot = player.yRot\n            delegate.move(MoverType.SELF, Vec3(riddenInput.x.toDouble(), riddenInput.y.toDouble(), riddenInput.z.toDouble()))\n        }\n        val dy = delegate.deltaMovement.y + delegate.gravity\n        if (!onFly && mountController.canJump() && (delegate.horizontalCollision || player.isJump()) && dy in 0.0..0.01 && jumpDelay == 0) {\n            jumpDelay = 10\n            delegate.jumpFromGround()\n        }\n    }\n\n    private fun movementSpeed() = ifLivingEntity {\n        getAttribute(Attributes.MOVEMENT_SPEED)?.value?.toFloat()?.let {\n            if (!onFly && !shouldDiscardFriction()) level()\n                .getBlockState(blockPosBelowThatAffectsMyMovement)\n                .block\n                .getFriction() * it else it\n        } ?: 0.0F\n    } ?: 0.0F\n\n    private fun updateFlyStatus(player: ServerPlayer) {\n        val fly = (player.isJump() && mountController.canFly()) || noGravity || onFly\n        if (delegate is Mob) delegate.isNoAi = fly\n        else delegate.isNoGravity = fly\n        onFly = fly && !delegate.onGround()\n        if (onFly) delegate.resetFallDistance()\n    }\n\n    private fun rideInput(player: ServerPlayer, travelVector: Vec3) = mountController.move(\n        if (onFly) MountController.MoveType.FLY else MountController.MoveType.DEFAULT,\n        player.bukkitEntity.wrap(),\n        (delegate.bukkitEntity as org.bukkit.entity.LivingEntity).wrap(),\n        Vector3f(\n            player.xMovement(),\n            player.yMovement(),\n            player.zMovement()\n        ),\n        Vector3f(\n            travelVector.x.toFloat(),\n            travelVector.y.toFloat(),\n            travelVector.z.toFloat()\n        )\n    ).mul(movementSpeed()).rotateY(-Math.toRadians(player.yRot.toDouble()).toFloat())\n\n    override fun tick() {\n        delegate.removalReason?.let {\n            if (!isRemoved) remove(it)\n            return\n        }\n        val controller = controllingPassenger\n        if (jumpDelay > 0) jumpDelay--\n        interaction.isInvisible = delegate.isInvisible\n        if (controller is ServerPlayer && !isDeadOrDying && mountController.canControl()) {\n            if (delegate is Mob) delegate.navigation.stop()\n            mountControl(controller)\n        } else initialSetup()\n        yRot = bone.rotation().y\n        yHeadRot = yRot\n        yBodyRot = yRot\n        val pos = relativePosition()\n        val minusHeight = source.minY * bone.hitBoxScale()\n        setPos(\n            pos.x.toDouble(),\n            pos.y.toDouble() + minusHeight,\n            pos.z.toDouble()\n        )\n        BlockGetter.forEachBlockIntersectedBetween(\n            oldPosition(),\n            position(),\n            boundingBox\n        ) { pos, step ->\n            if (BetterModelBukkit.IS_PAPER) applier.advanceStep(step, pos)\n            level().getBlockState(pos).entityInside(level(), pos, delegate, applier, true)\n            true\n        }\n        applier.applyAndClear(delegate)\n        if (isInLava) delegate.lavaHurt()\n        firstTick = false\n        listener.sync(craftEntity)\n    }\n\n    override fun remove(reason: RemovalReason, cause: EntityRemoveEvent.Cause?) {\n        initialSetup()\n        listener.handle(HitBoxRemoveEvent(craftEntity))\n        interaction.remove(reason)\n        super.remove(reason, cause)\n    }\n\n    override fun getBukkitLivingEntity(): CraftLivingEntity = bukkitEntity\n    override fun getBukkitEntity(): CraftLivingEntity = craftEntity as CraftLivingEntity\n    override fun getBukkitEntityRaw(): CraftLivingEntity = bukkitEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun isDeadOrDying(): Boolean {\n        return ifLivingEntity { isDeadOrDying } == true\n    }\n\n    override fun hide(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            hideEntity(plugin, bukkitEntity)\n            hideEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun show(player: PlatformPlayer) {\n        val plugin = BetterModel.platform() as Plugin\n        player.unwarp().run {\n            showEntity(plugin, bukkitEntity)\n            showEntity(plugin, interaction.bukkitEntity)\n        }\n    }\n\n    override fun interact(player: Player, hand: InteractionHand, vec: Vec3): InteractionResult {\n        if (player === delegate) return InteractionResult.FAIL\n        val interact = HitBoxInteractAtEvent(\n            (player.bukkitEntity as org.bukkit.entity.Player).wrap(), craftEntity, when (hand) {\n                MAIN_HAND -> ModelInteractionHand.RIGHT\n                OFF_HAND -> ModelInteractionHand.LEFT\n            }, vec.toBukkit()\n        )\n        if (!listener.handle(interact)) return InteractionResult.FAIL\n        (player as ServerPlayer).connection.handleInteract(ServerboundInteractPacket(\n            delegate.id,\n            hand,\n            vec,\n            player.isShiftKeyDown\n        ))\n        return InteractionResult.SUCCESS\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, cause: EntityPotionEffectEvent.Cause): Boolean {\n        return ifLivingEntity { addEffect(effectInstance, cause) } == true\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, entity: Entity?): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause) } == true\n    }\n\n    override fun addEffect(\n        effectInstance: MobEffectInstance,\n        entity: Entity?,\n        cause: EntityPotionEffectEvent.Cause,\n        fireEvent: Boolean\n    ): Boolean {\n        if (entity === delegate) return false\n        return ifLivingEntity { addEffect(effectInstance, entity, cause, fireEvent) } == true\n    }\n\n    override fun hurtServer(world: ServerLevel, source: DamageSource, amount: Float): Boolean {\n        if (source.entity === delegate || delegate.isInvulnerable) return false\n        if (source.entity === controllingPassenger && !mountController.canBeDamagedByRider()) return false\n        val ds = ModelDamageSourceImpl(source)\n        val event = HitBoxDamagedEvent(craftEntity, ds, amount)\n        if (!listener.handle(event)) return false\n        return ifLivingEntity { hurtServer(world, source, event.damage) } == true\n    }\n\n    override fun deflection(projectile: Projectile): ProjectileDeflection {\n        if (projectile.owner?.uuid == delegate.uuid) return ProjectileDeflection.NONE\n        return ifLivingEntity { deflection(projectile) } ?: ProjectileDeflection.NONE\n    }\n\n    override fun getHealth(): Float {\n        return ifLivingEntity { health } ?: super.getHealth()\n    }\n\n    override fun makeBoundingBox(vec3: Vec3): AABB {\n        return if (!initialized) {\n            super.makeBoundingBox(vec3)\n        } else {\n            val scale = bone.hitBoxScale()\n            AABB(\n                vec3.x + source.minX * scale,\n                vec3.y,\n                vec3.z + source.minZ * scale,\n                vec3.x + source.maxX * scale,\n                vec3.y + source.y() * scale,\n                vec3.z + source.maxZ * scale\n            ).apply {\n                if (CONFIG.debug().has(DebugConfig.DebugOption.HITBOX)) {\n                    bukkitEntity.world.spawnParticle(Particle.DUST, minX, minY, minZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                    bukkitEntity.world.spawnParticle(Particle.DUST, maxX, maxY, maxZ, 1, 0.0, 0.0, 0.0, 0.0, Particle.DustOptions(Color.RED, 1F))\n                }\n            }\n        }\n    }\n    override fun getDefaultDimensions(pose: Pose): EntityDimensions = if (initialized) dimensions else super.getDefaultDimensions(pose)\n\n    override fun removeHitBox() {\n        source().task {\n            dismountAll()\n            remove(ifLivingEntity { removalReason } ?: RemovalReason.KILLED)\n        }\n    }\n\n    private inline fun <T> ifLivingEntity(block: LivingEntity.() -> T): T? {\n        return if (delegate.valid) (delegate as? LivingEntity)?.block() else null\n    }\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/HitBoxInteraction.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.nms.HitBox\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.Interaction\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.phys.Vec3\nimport org.bukkit.Bukkit\nimport org.bukkit.craftbukkit.CraftServer\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftInteraction\n\ninternal class HitBoxInteraction(\n    val delegate: HitBoxImpl\n) : Interaction(EntityType.INTERACTION, delegate.level()) {\n\n    init {\n        persist = false\n    }\n\n    private val craftEntity: CraftInteraction by lazy {\n        object : CraftInteraction(Bukkit.getServer() as CraftServer, this), HitBox by delegate {}\n    }\n\n    override fun getBukkitEntity(): CraftEntity = craftEntity\n    override fun getBukkitEntityRaw(): CraftEntity = craftEntity\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun tick() {\n        val dimension = delegate.dimensions\n        width = dimension.width\n        height = dimension.height\n        yRot = delegate.yRot\n        xRot = delegate.xRot\n        setSharedFlagOnFire(delegate.remainingFireTicks > 0)\n    }\n\n    override fun skipAttackInteraction(entity: Entity): Boolean {\n        return if (entity is Player) {\n            entity.attack(delegate)\n            true\n        } else false\n    }\n\n    override fun interact(player: Player, hand: InteractionHand, vec: Vec3): InteractionResult {\n        delegate.interact(player, hand, vec)\n        return InteractionResult.FAIL\n    }\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/ModAnimationBundlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.nms.ModAnimationBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.MathUtil\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.RegistryFriendlyByteBuf\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket\nimport net.minecraft.server.MinecraftServer\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\ninternal class ModAnimationBundlerImpl(initialCapacity: Int) : ModAnimationBundler {\n\n    companion object {\n\n        const val KEY = \"modelengine:bulk_data\"\n\n        const val PACKET_TYPE_BULK_DATA = 0x00\n\n        const val FIELD_TRANSLATION = 1 shl 0\n        const val FIELD_LEFT_ROTATION = 1 shl 1\n        const val FIELD_SCALE = 1 shl 2\n        const val FIELD_TRANSFORM_DURATION = 1 shl 4\n\n        private val EMPTY_BUILD_TASK: (FriendlyByteBuf) -> Unit = {}\n    }\n\n    private val packet by lazy {\n        useByteBuf { buffer ->\n            ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC.decode(\n                RegistryFriendlyByteBuf(\n                    buffer,\n                    MinecraftServer.getServer().registryAccess()\n                ).apply {\n                    writeUtf(KEY)\n                    useByteBuf {\n                        it.writeByte(PACKET_TYPE_BULK_DATA)\n                        it.writeVarInt(builderList.size)\n                        builderList.forEach { builder -> builder(it) }\n                        writeBytes(it)\n                    }\n                }\n            )\n        }\n    }\n\n    private val builderList = ArrayList<(FriendlyByteBuf) -> Unit>(initialCapacity)\n\n    override fun send(player: PlatformPlayer) {\n        (player.unwarp() as CraftPlayer).handle.connection.send(packet)\n    }\n\n    fun append(id: Int, scope: Appender.() -> Unit) {\n        val build = Appender(id).apply(scope).build()\n        if (build !== EMPTY_BUILD_TASK) builderList += build\n    }\n\n    class Appender(\n        val entityId: Int,\n    ) {\n        private var mask = 0\n        private var buildTask = EMPTY_BUILD_TASK\n        private val isEmpty get() = buildTask === EMPTY_BUILD_TASK\n\n        fun appendPosition(vector: Vector3f) {\n            mask = mask or FIELD_TRANSLATION\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendScale(vector: Vector3f) {\n            mask = mask or FIELD_SCALE\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendRotation(quaternion: Quaternionf) {\n            mask = mask or FIELD_LEFT_ROTATION\n            task {\n                writeFloat(it, quaternion.x)\n                writeFloat(it, quaternion.y)\n                writeFloat(it, quaternion.z)\n                writeFloat(it, quaternion.w)\n            }\n        }\n\n        fun appendDuration(duration: Int) {\n            mask = mask or FIELD_TRANSFORM_DURATION\n            task {\n                writeVarInt(it, duration)\n            }\n        }\n\n        fun build(): (FriendlyByteBuf) -> Unit {\n            if (isEmpty) return EMPTY_BUILD_TASK\n            val m = mask\n            val t = buildTask\n            return {\n                writeVarInt(it,entityId)\n                writeByte(it, m)\n                t(it)\n            }\n        }\n\n        private fun task(task: (FriendlyByteBuf) -> Unit) {\n            if (isEmpty) {\n                buildTask = task\n                return\n            }\n            val last = buildTask\n            buildTask = {\n                last(it)\n                task(it)\n            }\n        }\n\n        private fun writeFloat(buf: FriendlyByteBuf, float: Float) {\n            buf.writeShort(MathUtil.floatToHalf(float).toInt())\n        }\n\n        private fun writeVarInt(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeVarInt(duration)\n        }\n\n        private fun writeByte(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeByte(duration)\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/ModelDamageSourceImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.event.ModelDamageSource\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport net.minecraft.world.damagesource.DamageSource\nimport org.bukkit.craftbukkit.util.CraftLocation\n\ninternal class ModelDamageSourceImpl(\n    private val source: DamageSource\n) : ModelDamageSource {\n    override fun getCausingEntity(): PlatformEntity? = source.entity?.bukkitEntity?.wrap()\n    override fun getDirectEntity(): PlatformEntity? = source.directEntity?.bukkitEntity?.wrap()\n    override fun getDamageLocation(): PlatformLocation? = source.sourcePositionRaw()?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun getSourceLocation(): PlatformLocation? = source.sourcePosition?.let {\n        CraftLocation.toBukkit(it, causingEntity?.unwarp()?.world).wrap()\n    }\n    override fun isIndirect(): Boolean = !source.isDirect\n    override fun getFoodExhaustion(): Float = source.foodExhaustion\n    override fun scalesWithDifficulty(): Boolean = source.scalesWithDifficulty()\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/ModelDisplayImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.ModelDisplay\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformItemTransform\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.util.Brightness\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport org.joml.Quaternionf\nimport org.joml.Vector3d\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicBoolean\n\ninternal class ModelDisplayImpl(\n    private val pos: Vector3d,\n    val display: ItemDisplay,\n    val yOffset: Double\n) : ModelDisplay {\n\n    private val entityData = display.entityData\n    private val entityDataLock = SingleLock()\n    private val forceGlow = AtomicBoolean()\n    private val forceInvisibility = AtomicBoolean()\n\n    private val oldPos = Vector3d(pos)\n\n    override fun id(): Int = display.id\n    override fun uuid(): UUID = display.uuid\n    override fun rotate(rotation: ModelRotation, bundler: PacketBundler) {\n        display.xRot = rotation.x\n        display.yRot = rotation.y\n        bundler += ClientboundMoveEntityPacket.Rot(\n            display.id,\n            rotation.packedY(),\n            rotation.packedX(),\n            display.onGround\n        )\n    }\n\n    override fun invisible(invisible: Boolean) {\n        if (forceInvisibility.compareAndSet(!invisible, invisible)) {\n            entityDataLock.accessToLock {\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPotionEffect(entity: BaseEntity) {\n        val beforeInvisible = display.isInvisible\n        val afterInvisible = entity.invisible()\n        entityDataLock.accessToLock {\n            display.setGlowingTag(entity.glow() || forceGlow.get())\n            if (CONFIG.followMobInvisibility() && beforeInvisible != afterInvisible) {\n                display.isInvisible = afterInvisible\n                entityData.markDirty(ITEM_SERIALIZER)\n            }\n        }\n    }\n\n    override fun syncPosition(location: PlatformLocation) {\n        oldPos.set(pos)\n        pos.set(location.x(), location.y(), location.z())\n    }\n\n    override fun spawn(showItem: Boolean, bundler: PacketBundler) {\n        bundler += addPacket\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    override fun teleport(location: PlatformLocation, bundler: PacketBundler) {\n        display.moveTo(\n            location.x(),\n            location.y(),\n            location.z(),\n            location.yaw(),\n            0F\n        )\n        bundler += ClientboundTeleportEntityPacket.teleport(display.id, PositionMoveRotation.of(display), emptySet(), display.onGround)\n    }\n\n    override fun sendPosition(adapter: BaseEntity, bundler: PacketBundler) {\n        val handle = adapter.handle() as Entity\n        if (oldPos.distanceSquared(pos) < 1e-8) return\n        bundler += ClientboundEntityPositionSyncPacket(\n            display.id,\n            PositionMoveRotation.of(handle),\n            handle.onGround()\n        )\n    }\n\n    override fun display(transform: PlatformItemTransform) {\n        entityDataLock.accessToLock {\n            display.itemTransform = ItemDisplayContext.BY_ID.apply(transform.ordinal)\n        }\n    }\n\n    override fun moveDuration(duration: Int) {\n        entityDataLock.accessToLock {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = duration\n        }\n    }\n\n    override fun item(itemStack: PlatformItemStack) {\n        entityDataLock.accessToLock {\n            display.itemStack = itemStack.unwarp().asVanilla()\n        }\n    }\n\n    override fun brightness(block: Int, sky: Int) {\n        entityDataLock.accessToLock {\n            display.brightnessOverride = if (block < 0 && sky < 0) null else Brightness(\n                block,\n                sky\n            )\n        }\n    }\n\n    override fun viewRange(range: Float) {\n        entityDataLock.accessToLock {\n            display.viewRange = range\n        }\n    }\n\n    override fun shadowRadius(radius: Float) {\n        entityDataLock.accessToLock {\n            display.shadowRadius = radius\n        }\n    }\n\n    override fun glow(glow: Boolean) {\n        if (!forceGlow.compareAndSet(!glow, glow)) return\n        entityDataLock.accessToLock {\n            display.setGlowingTag(display.isCurrentlyGlowing || glow)\n        }\n    }\n\n    override fun glowColor(glowColor: Int) {\n        entityDataLock.accessToLock {\n            display.glowColorOverride = glowColor\n        }\n    }\n\n    override fun billboard(billboard: PlatformBillboard) {\n        entityDataLock.accessToLock {\n            display.billboardConstraints = Display.BillboardConstraints.BY_ID.apply(billboard.ordinal)\n        }\n    }\n\n    override fun createTransformer(): DisplayTransformer = DisplayTransformerImpl(display)\n\n    override fun invisible(): Boolean = entityDataLock.accessToLock {\n        display.isInvisible || forceInvisibility.get() || display.itemStack.`is`(Items.AIR)\n    }\n\n    override fun sendDirtyEntityData(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                clean = true,\n                itemFilter = { it.isDirty },\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(!invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    override fun sendEntityData(showItem: Boolean, bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                valueFilter = { ITEM_ENTITY_DATA.contains(it.id) }\n            )\n        }?.markVisible(showItem && !invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    private fun List<SynchedEntityData.DataValue<*>>.markVisible(showItem: Boolean) = map {\n        if (it.id == ITEM_SERIALIZER.id) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.ITEM_STACK,\n            if (showItem) display.itemStack else EMPTY_ITEM\n        ) else it\n    }\n\n    private val addPacket\n        get() = ClientboundAddEntityPacket(\n            display.id,\n            display.uuid,\n            pos.x,\n            pos.y + yOffset,\n            pos.z,\n            display.xRot,\n            display.yRot,\n            display.type,\n            0,\n            display.deltaMovement,\n            display.yHeadRot.toDouble()\n        )\n\n    private val removePacket = ClientboundRemoveEntitiesPacket(display.id)\n\n    private class DisplayTransformerImpl(\n        source: ItemDisplay\n    ) : DisplayTransformer {\n        private val id = source.id\n        private val entityData = TransformationData()\n        private val entityDataLock = SingleLock()\n\n        override fun transform(\n            duration: Int,\n            position: Vector3f,\n            scale: Vector3f,\n            rotation: Quaternionf,\n            bundler: AnimationBundler\n        ) {\n            entityDataLock.accessToLock {\n                entityData.transform(\n                    duration,\n                    position,\n                    scale,\n                    rotation\n                )\n                entityData.packDirty(id, bundler)\n            }\n        }\n\n        override fun sendTransformation(bundler: PacketBundler) {\n            entityDataLock.accessToLock {\n                entityData.pack()\n            }?.run {\n                bundler += ClientboundSetEntityDataPacket(id, this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/ModelGameProfile.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport com.mojang.authlib.GameProfile\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\n\ninternal data class ModelGameProfile(\n    private val gameProfile: GameProfile\n) : ModelProfile {\n\n    private val info = ModelProfileInfo(gameProfile.id, gameProfile.name)\n    private val skin by lazy {\n        gameProfile.properties[\"textures\"].firstOrNull()?.let {\n            BetterModel.platform().profileManager().skin(it.value)\n        } ?: ModelProfileSkin.EMPTY\n    }\n\n    override fun info(): ModelProfileInfo = info\n\n    override fun skin(): ModelProfileSkin = skin\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/ModelNametagImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport com.mojang.math.Transformation\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.BonePosition\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.nms.ModelNametag\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.EntityUtil\nimport net.kyori.adventure.text.Component\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\ninternal class ModelNametagImpl(\n    private val bone: RenderedBone\n) : ModelNametag {\n    private companion object {\n        private val emptyVector = Vector3f()\n        private val emptyTransformation = Transformation(\n            Vector3f(-1F / 40F, -0.2F - 1F / 40F, 0F),\n            null,\n            null,\n            null\n        )\n    }\n\n    private val viewedPlayer = ConcurrentHashMap.newKeySet<UUID>()\n    private val display = Display.TextDisplay(\n        EntityType.TEXT_DISPLAY,\n        MinecraftServer.getServer().overworld()\n    ).apply {\n        entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n        setTransformation(emptyTransformation)\n        billboardConstraints = Display.BillboardConstraints.CENTER\n    }\n    private val posCache = BoneMovement()\n    private var alwaysVisible = false\n    private var location = BetterModel.platform().adapter().zero()\n\n    override fun component(component: Component?) {\n        display.text = component?.asVanilla() ?: VanillaComponent.empty()\n    }\n\n    override fun teleport(location: PlatformLocation) {\n        this.location = location\n    }\n\n    override fun alwaysVisible(alwaysVisible: Boolean) {\n        this.alwaysVisible = alwaysVisible\n    }\n\n    override fun send(player: PlatformPlayer) {\n        if (display.text == VanillaComponent.empty()) return\n        val hb = bone.group.hitBoxPoint\n        val pos = bone.worldPosition(BonePosition(emptyVector, hb, player.uuid()), posCache)\n        display.moveTo(Vec3(\n            location.x() + pos.x,\n            location.y() + pos.y,\n            location.z() + pos.z\n        ))\n        val inPoint = alwaysVisible || EntityUtil.isCustomNameVisible(player.location(), location)\n        when {\n            inPoint && viewedPlayer.add(player.uuid()) -> bundlerOfNotNull(\n                addPacket,\n                display.entityData.pack()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            inPoint -> bundlerOfNotNull(\n                ClientboundEntityPositionSyncPacket(display.id, PositionMoveRotation.of(display), false),\n                display.entityData.packDirty()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            viewedPlayer.remove(player.uuid()) -> bundlerOf(removePacket)\n            else -> null\n        }?.send(player)\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    private val addPacket get() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        display.x,\n        display.y,\n        display.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket get() = ClientboundRemoveEntitiesPacket(display.id)\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/NMSImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup\nimport com.mojang.authlib.GameProfile\nimport io.netty.channel.ChannelDuplexHandler\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.channel.ChannelPromise\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit\nimport kr.toxicity.model.api.bukkit.entity.BaseBukkitEntity\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.*\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.network.Connection\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.resources.Identifier\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl\nimport net.minecraft.util.ARGB\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Display.ItemDisplay\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.Items\nimport net.minecraft.world.item.component.CustomModelData\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.level.entity.LevelEntityGetter\nimport net.minecraft.world.level.entity.LevelEntityGetterAdapter\nimport net.minecraft.world.level.entity.PersistentEntitySectionManager\nimport org.bukkit.craftbukkit.CraftWorld\nimport org.bukkit.craftbukkit.entity.CraftEntity\nimport org.bukkit.craftbukkit.entity.CraftPlayer\nimport org.joml.Vector3d\nimport java.util.*\nimport java.util.function.Consumer\nimport java.util.function.IntConsumer\n\nclass NMSImpl : NMS {\n\n    companion object {\n        private const val INJECT_NAME = \"bettermodel_channel_handler\"\n\n        //Spigot\n        private val getGameProfile: (net.minecraft.world.entity.player.Player) -> GameProfile = createAdaptedFieldGetter { it.gameProfile }\n        private val getConnection: (ServerCommonPacketListenerImpl) -> Connection = createAdaptedFieldGetter { it.connection }\n        private val spigotChunkAccess = ServerLevel::class.java.fields.firstOrNull {\n            it.type == PersistentEntitySectionManager::class.java\n        }?.apply {\n            isAccessible = true\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        private val ServerLevel.levelGetter\n            get(): LevelEntityGetter<Entity> {\n                return if (BetterModelBukkit.IS_PAPER) {\n                    `moonrise$getEntityLookup`()\n                } else {\n                    spigotChunkAccess?.get(this)?.let {\n                        (it as PersistentEntitySectionManager<*>).entityGetter as LevelEntityGetter<Entity>\n                    } ?: throw RuntimeException(\"LevelEntityGetter\")\n                }\n            }\n        private val getEntityById: (LevelEntityGetter<Entity>, Int) -> Entity? = if (BetterModelBukkit.IS_PAPER) { g, i ->\n            (g as EntityLookup)[i]\n        } else LevelEntityGetterAdapter::class.java.declaredFields.first {\n            net.minecraft.world.level.entity.EntityLookup::class.java.isAssignableFrom(it.type)\n        }.let {\n            it.isAccessible = true\n            { e, i ->\n                (it[e] as net.minecraft.world.level.entity.EntityLookup<*>).getEntity(i) as? Entity\n            }\n        }\n        private fun Int.toEntity(level: ServerLevel) = getEntityById(level.levelGetter, this)\n        //Spigot\n        private val hitBoxData by lazy {\n            ItemDisplay(EntityType.ITEM_DISPLAY, MinecraftServer.getServer().overworld()).run {\n                entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n                entityData.nonDefaultValues!!\n            }\n        }\n    }\n\n    override fun hide(channel: PlayerChannelHandler, registry: EntityTrackerRegistry) {\n        val target = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf()\n        target.entityData.pack(\n            valueFilter = { it.id == SHARED_FLAG }\n        )?.let {\n            list += ClientboundSetEntityDataPacket(target.id, it).toRegistryDataPacket(channel.uuid(), registry)\n        }\n        if (target is LivingEntity) {\n            val packet = if (registry.hideOption(channel.uuid()).equipment) target.toEmptyEquipmentPacket() else target.toEquipmentPacket()\n            packet?.let { list += it }\n        }\n        list.send(channel.player())\n    }\n\n    private fun ClientboundSetEntityDataPacket.toRegistryDataPacket(uuid: UUID, registry: EntityTrackerRegistry) = ClientboundSetEntityDataPacket(id, packedItems().map {\n        if (it.id == SHARED_FLAG) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.BYTE,\n            registry.entityFlag(uuid, it.value() as Byte)\n        ) else it\n    })\n\n    inner class PlayerChannelHandlerImpl(\n        private val player: CraftPlayer\n    ) : PlayerChannelHandler, ChannelDuplexHandler() {\n        private val connection = player.handle.connection\n        private val uuid = player.uniqueId\n        private val base = adapt(player.wrap())\n\n        init {\n            val pipeline = getConnection(connection).channel.pipeline()\n            pipeline.addBefore(pipeline.first { it.value is Connection }.key, INJECT_NAME, this)\n        }\n\n        override fun close() {\n            val channel = getConnection(connection).channel\n            channel.eventLoop().submit {\n                channel.pipeline().remove(INJECT_NAME)\n            }\n        }\n\n        override fun base(): BasePlayer = base\n        override fun isModEnabled(): Boolean = (if (BetterModelBukkit.IS_PAPER) player.channels() else player.listeningPluginChannels).contains(ModAnimationBundlerImpl.KEY)\n\n        private val playerModel get() = connection.player.id.toRegistry()\n\n        private fun Int.toPlayerEntity() = toEntity(connection.player.level())\n        private fun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n        private inline fun Int.toRegistry(\n            ifHitBox: (Entity) -> Unit = {}\n        ) = (EntityTrackerRegistry.registry(this) ?: toPlayerEntity()?.let {\n            if (it is HitBox) ifHitBox(it)\n            it.toRegistry()\n        })?.takeIf {\n            it.isSpawned(player.uniqueId)\n        }\n\n        override fun sendEntityData(registry: EntityTrackerRegistry) {\n            val handle = registry.entity().handle() as? Entity ?: return\n            val list = bundlerOf(\n                ClientboundSetPassengersPacket(handle)\n            )\n            handle.entityData.pack(\n                valueFilter = { it.id == SHARED_FLAG }\n            )?.let {\n                list += ClientboundSetEntityDataPacket(handle.id, it)\n            }\n            if (handle is LivingEntity) handle.toEquipmentPacket()?.let {\n                list += it\n            }\n            list.send(player.wrap())\n        }\n\n        private fun <T : ClientGamePacketListener> Packet<in T>.handle(): Packet<in T>? {\n            when (this) {\n                is ClientboundBundlePacket -> return if (subPackets() is Keyed) this else ClientboundBundlePacket(subPackets().mapNotNull {\n                    it.handle()\n                })\n                is ClientboundAddEntityPacket -> {\n                    val entity = id.toPlayerEntity() ?: return this\n                    if (entity is HitBox) return entity.toFakeAddPacket()\n                    val wrap = entity.bukkitEntity.wrap()\n                    BetterModel.registry(wrap).ifPresent {\n                        wrap.taskLater(1) {\n                            it.spawn(player.wrap())\n                        }\n                    }\n                }\n                is ClientboundRemoveEntitiesPacket -> {\n                    entityIds\n                        .asSequence()\n                        .mapNotNull map@ {\n                            it.toRegistry {\n                                return@map null\n                            }\n                        }\n                        .forEach {\n                            it.remove()\n                        }\n                }\n                is ClientboundSetPassengersPacket -> {\n                    vehicle.toRegistry()?.let {\n                        return it.mountPacket(it.entity().handle() as? Entity ?: return this, array = passengers)\n                    }\n                }\n                is ClientboundUpdateAttributesPacket if entityId.toPlayerEntity() is HitBox -> return null\n                is ClientboundSetEntityDataPacket -> id.toRegistry {\n                    return ClientboundSetEntityDataPacket(id, hitBoxData)\n                }?.let { registry ->\n                    return toRegistryDataPacket(uuid, registry)\n                }\n                is ClientboundSetEquipmentPacket -> entity.toRegistry {\n                    return null\n                }?.let {\n                    if (it.hideOption(uuid).equipment()) (it.entity().handle() as? LivingEntity)?.toEmptyEquipmentPacket()?.let { packet ->\n                        return packet\n                    }\n                }\n                is ClientboundRespawnPacket -> playerModel?.let {\n                    bundlerOf(it.mountPacket(connection.player)).send(player.wrap())\n                }\n                is ClientboundContainerSetSlotPacket if isEquipment(connection.player) && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetSlotPacket(containerId, stateId, slot, EMPTY_ITEM)\n                }\n                is ClientboundContainerSetContentPacket if containerId == 0 && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                    return ClientboundContainerSetContentPacket(\n                        containerId,\n                        stateId,\n                        items.apply {\n                            PLAYER_EQUIPMENT_SLOT.forEach(IntConsumer { set(it, EMPTY_ITEM) })\n                            set(connection.player.hotbarSlot, EMPTY_ITEM)\n                        },\n                        carriedItem\n                    )\n                }\n            }\n            return this\n        }\n\n        override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {\n            super.write(ctx, if (msg is Packet<*>) msg.handle() ?: return else msg, promise)\n        }\n\n        override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {\n            fun EntityTrackerRegistry.updatePlayerLimb() = BetterModel.platform().scheduler().asyncTaskLater(1) {\n                if (isClosed) return@asyncTaskLater\n                player.handle.containerMenu.sendAllDataToRemote()\n                trackers().forEach { tracker ->\n                    tracker.update(TrackerUpdateAction.itemMapping()) { bone ->\n                        !bone.itemMapper.fixed()\n                    }\n                }\n            }\n            when (msg) {\n                is ServerboundSetCarriedItemPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) {\n                            connection.send(ClientboundSetHeldSlotPacket(player.inventory.heldItemSlot))\n                            return\n                        }\n                        registry.updatePlayerLimb()\n                    }\n                }\n                is ServerboundPlayerActionPacket -> {\n                    playerModel?.let { registry ->\n                        if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                        if (CONFIG.cancelPlayerModelInventory()) return\n                        registry.updatePlayerLimb()\n                    }\n                }\n            }\n            super.channelRead(ctx, msg)\n        }\n\n        private fun EntityTrackerRegistry.remove() {\n            remove(player.wrap())\n        }\n    }\n\n    override fun mount(registry: EntityTrackerRegistry, bundler: PacketBundler) {\n        val entity = registry.entity().handle()\n        if (entity is Entity) bundler += registry.mountPacket(entity)\n    }\n\n    private fun EntityTrackerRegistry.mountPacket(entity: Entity, array: IntArray = entity.passengers.filter {\n        EntityTrackerRegistry.registry(it.uuid) == null\n    }.map {\n        it.id\n    }.toIntArray()): ClientboundSetPassengersPacket {\n        return useByteBuf { buffer ->\n            buffer.writeVarInt(entity.id)\n            buffer.writeVarIntArray(displays()\n                .mapToInt {\n                    (it as ModelDisplayImpl).display.id\n                }.toArray() + array)\n            ClientboundSetPassengersPacket.STREAM_CODEC.decode(buffer)\n        }\n    }\n\n    override fun inject(player: PlatformPlayer): PlayerChannelHandlerImpl = PlayerChannelHandlerImpl(player.unwarp() as CraftPlayer)\n\n    override fun createBundler(initialCapacity: Int): PacketBundler = bundlerOf(initialCapacity)\n    override fun createParallelBundler(threshold: Int): PacketBundler = parallelBundlerOf(threshold)\n    override fun createModAnimationBuilder(initialCapacity: Int): ModAnimationBundler = ModAnimationBundlerImpl(initialCapacity)\n\n    override fun create(location: PlatformLocation, yOffset: Double, initialConsumer: Consumer<ModelDisplay>): ModelDisplay = ModelDisplayImpl(\n        Vector3d(location.x(), location.y(), location.z()),\n        ItemDisplay(EntityType.ITEM_DISPLAY, (location.world().unwarp() as CraftWorld).handle).apply {\n            entityData[Display.DATA_POS_ROT_INTERPOLATION_DURATION_ID] = 3\n            billboardConstraints = Display.BillboardConstraints.FIXED\n            valid = true\n            yRot = location.yaw()\n            itemTransform = ItemDisplayContext.FIXED\n        },\n        yOffset\n    ).apply {\n        initialConsumer.accept(this)\n        display.entityData.packDirty()\n    }\n\n    override fun createNametag(bone: RenderedBone): ModelNametag = ModelNametagImpl(bone)\n\n    override fun tint(itemStack: PlatformItemStack, rgb: Int): PlatformItemStack {\n        return itemStack.unwarp().asVanilla().apply {\n            set(DataComponents.DYED_COLOR, DyedItemColor(rgb))\n            set(DataComponents.CUSTOM_MODEL_DATA, get(DataComponents.CUSTOM_MODEL_DATA)?.let {\n                CustomModelData(it.floats, it.flags, it.strings, it.colors\n                    .run {\n                        if (rgb == 0xFFFFFF) this else map { color ->\n                            ARGB.multiply(color, rgb) and 0xFFFFFF\n                        }\n                    }\n                    .ifEmpty { listOf(rgb) })\n            })\n        }.asBukkit().wrap()\n    }\n\n    override fun createHitBox(entity: BaseEntity, bone: RenderedBone, boundingBox: ModelBoundingBox, mountController: MountController, listener: HitBoxListener): HitBox? {\n        val handle = entity.handle() as? Entity ?: return null\n        return HitBoxImpl(\n            boundingBox.center(),\n            bone,\n            listener,\n            handle,\n            mountController\n        ).craftEntity\n    }\n    override fun version(): NMSVersion = NMSVersion.V26_R1\n\n    override fun adapt(entity: PlatformEntity): BaseBukkitEntity {\n        val craft = entity.unwarp() as CraftEntity\n        return BaseEntityImpl(craft)\n    }\n\n    override fun adapt(player: PlatformPlayer): BasePlayer {\n        val craft = player.unwarp() as CraftPlayer\n        return BasePlayerImpl(\n            craft,\n            dirtyChecked({ getGameProfile(craft.handle) }, { ModelGameProfile(it) }),\n            dirtyChecked({ craft.handle.toCustomisation() }, { PlayerSkinParts(it) })\n        )\n    }\n\n    override fun profile(player: PlatformPlayer): ModelProfile = ModelGameProfile(getGameProfile((player.unwarp() as CraftPlayer).handle))\n\n    override fun createSkinItem(model: String, floats: List<Float>, flags: List<Boolean>, strings: List<String>, colors: List<Int>): TransformedItemStack {\n        return VanillaItemStack(Items.PLAYER_HEAD).run {\n            set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(floats, flags, strings, colors))\n            set(DataComponents.ITEM_MODEL, Identifier.parse(model))\n            TransformedItemStack.of(asBukkit().wrap())\n        }\n    }\n\n    override fun isProxyOnlineMode(): Boolean = ONLINE_MODE\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/PacketBundlers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport net.kyori.adventure.key.Key\nimport net.kyori.adventure.key.Keyed\nimport net.minecraft.network.PacketSendListener\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\nprivate val KEY = Key.key(\"bettermodel\")\n\ninternal fun bundlerOfNotNull(vararg packets: ClientPacket?) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.filterNotNull().toMutableList())\ninternal fun bundlerOf(vararg packets: ClientPacket) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.toMutableList())\ninternal fun bundlerOf(size: Int) = SimpleBundler(ArrayList(size))\ninternal fun parallelBundlerOf(threshold: Int) = ParallelBundler(threshold)\n\ninternal operator fun PacketBundler.plusAssign(other: ClientPacket) {\n    when (this) {\n        is SimpleBundler -> add(other)\n        is ParallelBundler -> add(other)\n        else -> throw RuntimeException(\"unsupported bundler.\")\n    }\n}\ninternal fun Packet<*>.assumeSize() = when (this) {\n    is ClientboundSetEntityDataPacket -> packedItems.size\n    is ClientboundSetEquipmentPacket -> slots.size\n    else -> 1\n}\n\ninternal interface PluginBundlePacketImpl : Iterable<ClientPacket>, Keyed {\n    val bundlePacket: ClientboundBundlePacket\n    fun size(): Int\n    fun isEmpty(): Boolean\n    fun add(other: ClientPacket)\n}\n\ninternal class SimpleBundler(\n    private val list: MutableList<ClientPacket>\n) : PacketBundler, PluginBundlePacketImpl {\n    override val bundlePacket = ClientboundBundlePacket(this)\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        connection.send(bundlePacket, PacketSendListener.thenRun(onSuccess))\n    }\n    override fun isEmpty(): Boolean = list.isEmpty()\n    override fun size(): Int = list.size\n    override fun key(): Key = KEY\n    override fun iterator(): MutableIterator<ClientPacket> = list.iterator()\n    override fun add(other: ClientPacket) {\n        list += other\n    }\n}\n\ninternal class ParallelBundler(\n    private val threshold: Int\n) : PacketBundler {\n    private val subBundlers = mutableListOf<PluginBundlePacketImpl>()\n    private var sizeAssume = 0\n    private val newBundler get() = bundlerOf().apply {\n        sizeAssume = 0\n        subBundlers += this\n    }\n    private var selectedBundler = newBundler\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = (player.unwarp() as CraftPlayer).handle.connection\n        subBundlers.forEach {\n            connection.send(it.bundlePacket)\n        }\n    }\n    override fun isEmpty(): Boolean = selectedBundler.isEmpty()\n    override fun size(): Int = subBundlers.sumOf(PluginBundlePacketImpl::size)\n    fun add(other: ClientPacket) {\n        (if (sizeAssume > threshold) newBundler else selectedBundler)\n            .apply { selectedBundler = this }\n            .add(other)\n        sizeAssume += other.assumeSize()\n    }\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/PlayerArmorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.item.equipment.EquipmentAssets\nimport org.bukkit.craftbukkit.entity.CraftPlayer\n\ninternal data class PlayerArmorImpl(\n    private val player: CraftPlayer\n) : PlayerArmor {\n\n    override fun helmet(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.HEAD).toArmorItem()\n    }\n\n    override fun leggings(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.LEGS).toArmorItem()\n    }\n\n    override fun chestplate(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.CHEST).toArmorItem()\n    }\n\n    override fun boots(): ArmorItem? {\n        return player.handle.getItemBySlot(EquipmentSlot.FEET).toArmorItem()\n    }\n\n    private fun VanillaItemStack.toArmorItem(): ArmorItem? = get(DataComponents.EQUIPPABLE)?.assetId?.map {\n        val trim = get(DataComponents.TRIM)\n        ArmorItem(\n            get(DataComponents.DYED_COLOR)?.rgb ?: if (it === EquipmentAssets.LEATHER) DyedItemColor.LEATHER_COLOR else 0xFFFFFF,\n            it.identifier().path,\n            trim?.pattern?.value()?.assetId?.path,\n            trim?.material?.value()?.assets?.base?.suffix\n        )\n    }?.orElse(null)\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/ProfiledImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\n\ninternal class ProfiledImpl(\n    private val playerArmor: PlayerArmor,\n    private val modelProfile: () -> ModelProfile,\n    private val playerSkinParts: () -> PlayerSkinParts\n) : Profiled {\n\n    override fun profile(): ModelProfile = modelProfile()\n    override fun armors(): PlayerArmor = playerArmor\n    override fun skinParts(): PlayerSkinParts = playerSkinParts()\n}\n"
  },
  {
    "path": "nms/v26_R1/src/main/kotlin/kr/toxicity/model/bukkit/nms/v26_R1/TypeAliases.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.nms.v26_R1\n\nimport net.minecraft.network.chat.Component\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientGamePacketListener\nimport net.minecraft.world.item.ItemStack\n\ninternal typealias VanillaItemStack = ItemStack\ninternal typealias BukkitItemStack = org.bukkit.inventory.ItemStack\ninternal typealias ClientPacket = Packet<ClientGamePacketListener>\ninternal typealias VanillaComponent = Component\ninternal typealias AdventureComponent = net.kyori.adventure.text.Component\n"
  },
  {
    "path": "platform/fabric/build.gradle.kts",
    "content": "import xyz.jpenilla.resourcefactory.fabric.Environment\n\nplugins {\n    alias(libs.plugins.convention.publish)\n    alias(libs.plugins.convention.modrinth)\n    alias(libs.plugins.resourcefactory.fabric)\n    id(\"net.fabricmc.fabric-loom\")\n}\n\nval versionString = \"${rootProject.version}+${property(\"minecraft_version\")}\"\n\nval jarName = \"${rootProject.name}-$versionString-${project.name.substringAfterLast('-')}.jar\"\nval jarDir = rootProject.layout.buildDirectory.dir(\"libs\")\n\nsourceSets {\n    create(\"testmod\") {\n        compileClasspath += main.get().compileClasspath + main.get().output\n        runtimeClasspath += main.get().runtimeClasspath + main.get().output\n    }\n}\n\nloom {\n    // Access winder\n    //accessWidenerPath = file(\"src/main/resources/bettermodel.accesswidener\")\n\n    // Run\n    runs {\n        create(\"testClient\") {\n            client()\n            configName = \"Test Minecraft Client\"\n            source(\"testmod\")\n        }\n\n        create(\"testServer\") {\n            server()\n            configName = \"Test Minecraft Server\"\n            source(\"testmod\")\n        }\n    }\n\n    // Test mod\n    //createRemapConfigurations(sourceSets[\"testmod\"])\n}\n\ndependencies {\n    // Minecraft\n    minecraft(\"com.mojang:minecraft:${property(\"minecraft_version\")}\")\n\n    api(project(\":bettermodel-api\")); include(project(\":bettermodel-api\"))\n    api(project(\":bettermodel-api:bettermodel-mod-api\")); include(project(\":bettermodel-api:bettermodel-mod-api\"))\n    api(project(\":bettermodel-core\")); include(project(\":bettermodel-core\"))\n\n    setOf(\n        \"fabric-api-base\",\n        \"fabric-command-api-v2\",\n        \"fabric-data-attachment-api-v1\",\n        \"fabric-entity-events-v1\",\n        \"fabric-events-interaction-v0\",\n        \"fabric-lifecycle-events-v1\",\n        \"fabric-networking-api-v1\",\n        \"fabric-transitive-access-wideners-v1\"\n    ).forEach {\n        implementation(fabricApi.module(it, libs.versions.fabric.api.get()))\n    }\n\n    implementation(libs.bundles.fabric)\n\n    implementation(libs.bundles.fabric.library); include(libs.bundles.fabric.library)\n    api(libs.bundles.fabric.mod); include(libs.bundles.fabric.mod)\n\n    implementation(libs.bundles.core); include(libs.bundles.core)\n    include(libs.bundles.library)\n}\n\nfabricModJson {\n    id = \"bettermodel\"\n    name = \"BetterModel\"\n    description = \"Modern Bedrock model engine for Minecraft Java Edition\"\n\n    entrypoints = listOf(\n        mainEntrypoint(\"$group.impl.fabric.BetterModelFabricImpl\") {\n            adapter = \"kotlin\"\n        }\n    )\n\n    environment = Environment.ANY\n\n    depends = mapOf(\n        \"minecraft\" to listOf(\">=${LATEST_VERSION.first()}\"),\n        \"fabricloader\" to listOf(\"*\"),\n        \"fabric-language-kotlin\" to listOf(\">=${libs.versions.fabric.language.kotlin.get()}\"),\n\n        // fabric-api\n        \"fabric-api-base\" to listOf(\"*\"),\n        \"fabric-command-api-v2\" to listOf(\"*\"),\n        \"fabric-data-attachment-api-v1\" to listOf(\"*\"),\n        \"fabric-entity-events-v1\" to listOf(\"*\"),\n        \"fabric-events-interaction-v0\" to listOf(\"*\"),\n        \"fabric-lifecycle-events-v1\" to listOf(\"*\"),\n        \"fabric-networking-api-v1\" to listOf(\"*\"),\n        \"fabric-transitive-access-wideners-v1\" to listOf(\"*\"),\n\n        // mod libraries\n        \"adventure-platform-fabric\" to listOf(\"*\"),\n        \"cloud\" to listOf(\"*\"),\n        \"polymer-resource-pack\" to listOf(\"*\")\n    )\n\n    mixins = listOf(\n        mixin(\"bettermodel.mixins.json\")\n    )\n\n    authors = listOf(\n        person(\"toxicity188\")\n    )\n    contributors = listOf(\n        person(\"Kouvali (Fabric Port)\")\n    )\n    contact {\n        sources = \"https://github.com/toxicity188/BetterModel/\"\n        issues = \"https://github.com/toxicity188/BetterModel/issues\"\n        homepage = \"https://modrinth.com/plugin/bettermodel\"\n    }\n    icon(\"assets/icon.png\")\n    mitLicense()\n\n    version = project.version.toString()\n}\n\nsourceSets[\"testmod\"].resourceFactory {\n    fabricModJson {\n        id = \"bettermodel-testmod\"\n        version = project.version.toString()\n\n        entrypoints = listOf(\n            mainEntrypoint(\n                \"$group.test.RollTest\"\n            )\n        )\n\n        depends = mapOf(\n            // mod modules\n            \"bettermodel\" to listOf(\"*\")\n        )\n    }\n}\n\ninterface FsInjected {\n    @get:Inject val fs: FileSystemOperations\n}\nval copyModJar by tasks.registering {\n    val injected = objects.newInstance<FsInjected>()\n    val archiveFile = tasks.jar.flatMap { it.archiveFile }\n    val jarName = jarName\n    val jarDir = jarDir\n    doLast {\n        injected.fs.copy {\n            from(archiveFile)\n            rename { jarName }\n            into(jarDir)\n        }\n    }\n}\n\ntasks {\n    jar {\n        from(rootProject.layout.projectDirectory.file(\"LICENSE.md\"))\n        from(rootProject.layout.projectDirectory.file(\".idea/icon.png\")) {\n            rename { \"assets/icon.png\" }\n        }\n        manifest {\n            attributes(\n                mapOf(\n                    \"Dev-Build\" to (BUILD_NUMBER ?: -1),\n                    \"Version\" to versionString,\n                    \"Author\" to \"toxicity188\",\n                    \"Url\" to \"https://github.com/toxicity188/BetterModel\",\n                    \"Created-By\" to \"Gradle $gradle\",\n                    \"Build-Jdk\" to \"${System.getProperty(\"java.vendor\")} ${System.getProperty(\"java.version\")}\",\n                    \"Build-OS\" to \"${System.getProperty(\"os.arch\")} ${System.getProperty(\"os.name\")}\"\n                )\n            )\n        }\n        finalizedBy(copyModJar)\n    }\n    runServer {\n        enabled = false\n    }\n}\n\nmodrinth {\n    loaders = listOf(\"fabric\", \"quilt\")\n    uploadFile.set(jarDir.map { it.file(jarName) })\n    gameVersions = LATEST_VERSION\n    dependencies {\n        required.version(\"fabric-api\", libs.versions.fabric.api.get())\n        required.version(\"fabric-language-kotlin\", libs.versions.fabric.language.kotlin.get())\n//        optional.project(\n//            \"skinsrestorer\"\n//        )\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/impl/fabric/entity/AbstractArmorStand.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity;\n\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.Nullable;\n\npublic abstract class AbstractArmorStand extends ArmorStand {\n    public AbstractArmorStand(EntityType<? extends ArmorStand> type, Level level) {\n        super(type, level);\n    }\n\n    @Override\n    public final boolean equals(@Nullable Object object) {\n        return super.equals(object);\n    }\n\n    @Override\n    public final int hashCode() {\n        return super.hashCode();\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/impl/fabric/entity/EntityHook.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity;\n\nimport org.jetbrains.annotations.Nullable;\n\npublic interface EntityHook {\n    @Nullable String bettermodel$getModelData();\n\n    void bettermodel$setModelData(@Nullable String modelData);\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/impl/fabric/network/BetterModelBundlePacket.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.network;\n\npublic interface BetterModelBundlePacket {\n    boolean bettermodel$isBetterModelPacket();\n    void bettermodel$setBetterModelPacket(boolean isBetterModel);\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/AvatarAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport net.minecraft.network.syncher.EntityDataAccessor;\nimport net.minecraft.world.entity.Avatar;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\n\n@Mixin(value = Avatar.class)\npublic interface AvatarAccessor {\n    @Accessor(\"DATA_PLAYER_MODE_CUSTOMISATION\")\n    static @NotNull EntityDataAccessor<Byte> bettermodel$getDataPlayerModeCustomisation() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/ClientboundBundlePacketMixin.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport kr.toxicity.model.impl.fabric.network.BetterModelBundlePacket;\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.Unique;\n\n@Mixin(value = ClientboundBundlePacket.class)\npublic abstract class ClientboundBundlePacketMixin implements BetterModelBundlePacket {\n    @Unique\n    private boolean bettermodel$isBetterModelPacket;\n\n    @Override\n    public boolean bettermodel$isBetterModelPacket() {\n        return bettermodel$isBetterModelPacket;\n    }\n\n    @Override\n    public void bettermodel$setBetterModelPacket(boolean isBetterModel) {\n        bettermodel$isBetterModelPacket = isBetterModel;\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/ConnectionAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport io.netty.channel.Channel;\nimport net.minecraft.network.Connection;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\n\n@Mixin(value = Connection.class)\npublic interface ConnectionAccessor {\n    @Accessor(value = \"channel\")\n    @NotNull Channel bettermodel$getChannel();\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/DisplayAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport net.minecraft.network.syncher.EntityDataAccessor;\nimport net.minecraft.world.entity.Display;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaternionfc;\nimport org.joml.Vector3fc;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\n\n@Mixin(value = Display.class)\npublic interface DisplayAccessor {\n    @Accessor(\"DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID\")\n    static @NotNull EntityDataAccessor<Integer> bettermodel$getDataTransformationInterpolationStartDeltaTicksId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID\")\n    static @NotNull EntityDataAccessor<Integer> bettermodel$getDataTransformationInterpolationDurationId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_POS_ROT_INTERPOLATION_DURATION_ID\")\n    static @NotNull EntityDataAccessor<Integer> bettermodel$getDataPosRotInterpolationDurationId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_TRANSLATION_ID\")\n    static @NotNull EntityDataAccessor<Vector3fc> bettermodel$getDataTranslationId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_SCALE_ID\")\n    static @NotNull EntityDataAccessor<Vector3fc> bettermodel$getDataScaleId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_LEFT_ROTATION_ID\")\n    static @NotNull EntityDataAccessor<Quaternionfc> bettermodel$getDataLeftRotationId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_RIGHT_ROTATION_ID\")\n    static @NotNull EntityDataAccessor<Quaternionfc> bettermodel$getDataRightRotationId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_BILLBOARD_RENDER_CONSTRAINTS_ID\")\n    static @NotNull EntityDataAccessor<Byte> bettermodel$getDataBillboardRenderConstraintsId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_BRIGHTNESS_OVERRIDE_ID\")\n    static @NotNull EntityDataAccessor<Integer> bettermodel$getDataBrightnessOverrideId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_VIEW_RANGE_ID\")\n    static @NotNull EntityDataAccessor<Float> bettermodel$getDataViewRangeId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_SHADOW_RADIUS_ID\")\n    static @NotNull EntityDataAccessor<Float> bettermodel$getDataShadowRadiusId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_SHADOW_STRENGTH_ID\")\n    static @NotNull EntityDataAccessor<Float> bettermodel$getDataShadowStrengthId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_WIDTH_ID\")\n    static @NotNull EntityDataAccessor<Float> bettermodel$getDataWidthId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_HEIGHT_ID\")\n    static @NotNull EntityDataAccessor<Float> bettermodel$getDataHeightId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(\"DATA_GLOW_COLOR_OVERRIDE_ID\")\n    static @NotNull EntityDataAccessor<Integer> bettermodel$getDataGlowColorOverrideId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/EntityAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport net.minecraft.network.syncher.EntityDataAccessor;\nimport net.minecraft.world.entity.Entity;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\n\n@Mixin(value = Entity.class)\npublic interface EntityAccessor {\n    @Accessor(value = \"DATA_SHARED_FLAGS_ID\")\n    static @NotNull EntityDataAccessor<Byte> bettermodel$getDataSharedFlagsId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/EntityMixin.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport kr.toxicity.model.impl.fabric.attachment.BetterModelAttachments;\nimport kr.toxicity.model.impl.fabric.entity.EntityHook;\nimport kr.toxicity.model.impl.fabric.events.ServerEntityDismountCallback;\nimport net.fabricmc.fabric.api.attachment.v1.AttachmentTarget;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.Shadow;\nimport org.spongepowered.asm.mixin.Unique;\nimport org.spongepowered.asm.mixin.injection.At;\nimport org.spongepowered.asm.mixin.injection.Inject;\nimport org.spongepowered.asm.mixin.injection.callback.CallbackInfo;\n\n@Mixin(value = Entity.class)\npublic abstract class EntityMixin implements EntityHook {\n    @Shadow\n    public abstract Level level();\n\n    @Override\n    public @Nullable String bettermodel$getModelData() {\n        return ((AttachmentTarget) this).getAttached(BetterModelAttachments.MODEL_DATA);\n    }\n\n    @Override\n    public void bettermodel$setModelData(@Nullable String modelData) {\n        ((AttachmentTarget) this).setAttached(BetterModelAttachments.MODEL_DATA, modelData);\n    }\n\n    @Inject(\n        method = \"removePassenger\",\n        at = @At(value = \"HEAD\"),\n        cancellable = true\n    )\n    private void bettermodel$invokeDismountCallbacks(@NotNull Entity passenger, @NotNull CallbackInfo ci) {\n        if (level().isClientSide()) return;\n\n        Entity vehicle = betterModel$entity();\n        if (vehicle != passenger.getVehicle() &&\n            !ServerEntityDismountCallback.EVENT.invoker().onDismount(passenger, vehicle)\n        ) {\n            ci.cancel();\n        }\n    }\n\n    @Unique\n    private @NotNull Entity betterModel$entity() {\n        return (Entity) (Object) this;\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/ItemDisplayAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport net.minecraft.network.syncher.EntityDataAccessor;\nimport net.minecraft.world.entity.Display;\nimport net.minecraft.world.item.ItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\n\n@Mixin(value = Display.ItemDisplay.class)\npublic interface ItemDisplayAccessor {\n    @Accessor(value = \"DATA_ITEM_STACK_ID\")\n    static @NotNull EntityDataAccessor<ItemStack> bettermodel$getDataItemStackId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n\n    @Accessor(value = \"DATA_ITEM_DISPLAY_ID\")\n    static @NotNull EntityDataAccessor<Byte> bettermodel$getDataItemDisplayId() {\n        throw new UnsupportedOperationException(\"Implemented via mixin\");\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/LivingEntityMixin.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport com.llamalad7.mixinextras.sugar.Local;\nimport kr.toxicity.model.impl.fabric.events.ServerLivingEntityJumpCallback;\nimport kr.toxicity.model.impl.fabric.events.ServerMobEffectLoadCallback;\nimport kr.toxicity.model.impl.fabric.events.ServerMobEffectUnloadCallback;\nimport net.minecraft.world.effect.MobEffectInstance;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.level.Level;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.Unique;\nimport org.spongepowered.asm.mixin.injection.At;\nimport org.spongepowered.asm.mixin.injection.Inject;\nimport org.spongepowered.asm.mixin.injection.callback.CallbackInfo;\n\nimport java.util.Collection;\n\n@Mixin(value = LivingEntity.class)\npublic abstract class LivingEntityMixin extends Entity {\n    private LivingEntityMixin(EntityType<?> type, Level level) {\n        super(type, level);\n    }\n\n    @Inject(\n        method = \"aiStep\",\n        at = @At(\n            value = \"INVOKE\",\n            target = \"Lnet/minecraft/world/entity/LivingEntity;jumpFromGround()V\"\n        )\n    )\n    private void bettermodel$invokeJumpCallbacks(@NotNull CallbackInfo ci) {\n        if (level().isClientSide()) return;\n\n        ServerLivingEntityJumpCallback.EVENT.invoker().onJump(bettermodel$livingEntity());\n    }\n\n    @Inject(\n        method = \"onEffectAdded\",\n        at = @At(\n            value = \"INVOKE\",\n            target = \"Lnet/minecraft/world/effect/MobEffect;addAttributeModifiers(Lnet/minecraft/world/entity/ai/attributes/AttributeMap;I)V\",\n            shift = At.Shift.AFTER\n        )\n    )\n    private void bettermodel$invokeEffectLoadCallbacks(@NotNull MobEffectInstance effect, @NotNull Entity source, @NotNull CallbackInfo ci) {\n        if (level().isClientSide()) return;\n\n        ServerMobEffectLoadCallback.EVENT.invoker().onLoad(bettermodel$livingEntity(), effect);\n    }\n\n    @Inject(\n        method = \"onEffectsRemoved\",\n        at = @At(\n            value = \"INVOKE\",\n            target = \"Lnet/minecraft/world/effect/MobEffect;removeAttributeModifiers(Lnet/minecraft/world/entity/ai/attributes/AttributeMap;)V\",\n            shift = At.Shift.AFTER\n        )\n    )\n    private void bettermodel$invokeEffectUnloadCallbacks(@NotNull Collection<MobEffectInstance> effects, @NotNull CallbackInfo ci, @Local @NotNull MobEffectInstance effect) {\n        if (level().isClientSide()) return;\n\n        ServerMobEffectUnloadCallback.EVENT.invoker().onUnload(bettermodel$livingEntity(), effect);\n    }\n\n    @Unique\n    private @NotNull LivingEntity bettermodel$livingEntity() {\n        return (LivingEntity) (Object) this;\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/MobAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport net.minecraft.world.entity.Mob;\nimport net.minecraft.world.entity.ai.goal.GoalSelector;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\n\n@Mixin(value = Mob.class)\npublic interface MobAccessor {\n    @Accessor(value = \"goalSelector\")\n    @NotNull GoalSelector bettermodel$getGoalSelector();\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/ServerCommonPacketListenerImplAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport net.minecraft.network.Connection;\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\n\n@Mixin(value = ServerCommonPacketListenerImpl.class)\npublic interface ServerCommonPacketListenerImplAccessor {\n    @Accessor(value = \"connection\")\n    @NotNull Connection bettermodel$getConnection();\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/ServerLevelEntityCallbacksMixin.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport kr.toxicity.model.impl.fabric.events.ServerMobEffectLoadCallback;\nimport kr.toxicity.model.impl.fabric.events.ServerMobEffectUnloadCallback;\nimport net.minecraft.world.effect.MobEffectInstance;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.LivingEntity;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.injection.At;\nimport org.spongepowered.asm.mixin.injection.Inject;\nimport org.spongepowered.asm.mixin.injection.callback.CallbackInfo;\n\n@Mixin(targets = \"net.minecraft.server.level.ServerLevel$EntityCallbacks\")\npublic abstract class ServerLevelEntityCallbacksMixin {\n\n    @Inject(\n        method = \"onTrackingStart(Lnet/minecraft/world/entity/Entity;)V\",\n        at = @At(value = \"TAIL\")\n    )\n    private void bettermodel$invokeLoadCallbacks(Entity entity, CallbackInfo ci) {\n        if (entity.level().isClientSide()) return;\n\n        if (entity instanceof LivingEntity livingEntity) {\n            for (MobEffectInstance effect : livingEntity.getActiveEffects()) {\n                ServerMobEffectLoadCallback.EVENT.invoker().onLoad(livingEntity, effect);\n            }\n        }\n    }\n\n    @Inject(\n        method = \"onTrackingEnd(Lnet/minecraft/world/entity/Entity;)V\",\n        at = @At(value = \"HEAD\")\n    )\n    private void bettermodel$invokeUnloadCallbacks(Entity entity, CallbackInfo ci) {\n        if (entity.level().isClientSide()) return;\n\n        if (entity instanceof LivingEntity livingEntity) {\n            for (MobEffectInstance effect : livingEntity.getActiveEffects()) {\n                ServerMobEffectUnloadCallback.EVENT.invoker().onUnload(livingEntity, effect);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/java/kr/toxicity/model/mixin/SynchedEntityDataAccessor.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.mixin;\n\nimport net.minecraft.network.syncher.EntityDataAccessor;\nimport net.minecraft.network.syncher.SynchedEntityData;\nimport org.jetbrains.annotations.NotNull;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\nimport org.spongepowered.asm.mixin.gen.Invoker;\n\n@Mixin(value = SynchedEntityData.class)\npublic interface SynchedEntityDataAccessor {\n    @Accessor(value = \"itemsById\")\n    @NotNull SynchedEntityData.DataItem<?>[] bettermodel$getItemsById();\n\n    @Accessor(value = \"isDirty\")\n    void bettermodel$setDirty(boolean dirty);\n\n    @Invoker(value = \"getItem\")\n    <T> SynchedEntityData.@NotNull DataItem<T> bettermodel$getItem(@NotNull EntityDataAccessor<T> key);\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/BetterModelFabricImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric\n\nimport eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils\nimport eu.pb4.polymer.resourcepack.api.ResourcePackBuilder\nimport kr.toxicity.model.BetterModelEvaluatorImpl\nimport kr.toxicity.model.BetterModelEventBusImpl\nimport kr.toxicity.model.BetterModelPlatformImpl\nimport kr.toxicity.model.api.*\nimport kr.toxicity.model.api.BetterModelPlatform.ReloadResult.*\nimport kr.toxicity.model.api.event.PluginEndReloadEvent\nimport kr.toxicity.model.api.event.PluginStartReloadEvent\nimport kr.toxicity.model.api.manager.*\nimport kr.toxicity.model.api.mod.BetterModelMod\nimport kr.toxicity.model.api.mod.platform.ModAdapter\nimport kr.toxicity.model.api.mod.scheduler.ModModelScheduler\nimport kr.toxicity.model.api.nms.NMS\nimport kr.toxicity.model.api.pack.PackResult\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.platform.PlatformAdapter\nimport kr.toxicity.model.api.version.MinecraftVersion\nimport kr.toxicity.model.impl.fabric.attachment.BetterModelAttachments\nimport kr.toxicity.model.impl.fabric.command.startFabricCommand\nimport kr.toxicity.model.impl.fabric.config.BetterModelConfigImpl\nimport kr.toxicity.model.impl.fabric.config.toConfig\nimport kr.toxicity.model.impl.fabric.manager.EntityManager\nimport kr.toxicity.model.impl.fabric.manager.PlayerManagerImpl\nimport kr.toxicity.model.impl.fabric.scheduler.FabricModelSchedulerImpl\nimport kr.toxicity.model.manager.*\nimport kr.toxicity.model.util.*\nimport net.fabricmc.api.ModInitializer\nimport net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents\nimport net.fabricmc.loader.api.FabricLoader\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.text.format.NamedTextColor.AQUA\nimport net.kyori.adventure.text.format.NamedTextColor.GREEN\nimport net.minecraft.DetectedVersion\nimport net.minecraft.WorldVersion\nimport net.minecraft.server.MinecraftServer\nimport net.minecraft.server.packs.metadata.pack.PackFormat\nimport net.minecraft.util.InclusiveRange\nimport org.semver4j.Semver\nimport java.io.File\nimport java.io.InputStream\nimport java.nio.file.Files\nimport java.nio.file.Path\nimport java.util.concurrent.atomic.AtomicBoolean\nimport java.util.function.BiConsumer\nimport java.util.function.Consumer\nimport java.util.jar.JarFile\nimport kotlin.io.path.exists\nimport kotlin.system.measureTimeMillis\n\nclass BetterModelFabricImpl : ModInitializer, BetterModelPlatformImpl, BetterModelMod {\n    private lateinit var server: MinecraftServer\n\n    private val configDir: Path = FabricLoader.getInstance()\n        .configDir\n        .resolve(modId()).apply {\n            toFile().mkdirs()\n        }\n\n    private val jarFile: JarFile\n        get() = JarFile(\n            File(javaClass.protectionDomain.codeSource.location.toURI())\n        )\n\n    private lateinit var config: BetterModelConfigImpl\n\n    private val worldVersion: WorldVersion = DetectedVersion.tryDetectVersion()\n    private val minecraftVersion: MinecraftVersion = MinecraftVersion.parse(worldVersion.id())\n\n    private val semver: Semver = FabricLoader.getInstance()\n        .getModContainer(modId())\n        .map { modContainer ->\n            Semver.coerce(modContainer.metadata.version.friendlyString)\n                .ifNull { \"Unable to load BetterModel's semver.\" }\n        }\n        .orElseThrow()\n\n    private val nms by lazy {\n        BetterModelNMSImpl()\n    }\n    private val logger = BetterModelLoggerImpl()\n    private val evaluator = BetterModelEvaluatorImpl()\n    private val eventBus = BetterModelEventBusImpl()\n    private val adapter = ModAdapter()\n\n    private var reloadStartTask: (PackZipper) -> Unit = { zipper ->\n        callEvent {\n            PluginStartReloadEvent(zipper)\n        }\n    }\n\n    private var reloadEndTask: (BetterModelPlatform.ReloadResult) -> Unit = { result ->\n        callEvent {\n            PluginEndReloadEvent(result)\n        }\n    }\n\n    private val isLoadingProvider: AtomicBoolean = AtomicBoolean()\n    private val isFirstLoadProvider: AtomicBoolean = AtomicBoolean()\n\n    private val allManagers by lazy {\n        listOf(\n            ArmorManager,\n            ProfileManagerImpl,\n            SkinManagerImpl,\n            ModelManagerImpl,\n            PlayerManagerImpl,\n            EntityManager,\n            ScriptManagerImpl\n        )\n    }\n\n    override fun onInitialize() {\n        BetterModel.register(this)\n        startFabricCommand()\n        ServerLifecycleEvents.SERVER_STARTING.register { server ->\n            this.server = server\n        }\n\n        PolymerResourcePackUtils.addModAssets(modId())\n        PolymerResourcePackUtils.markAsRequired()\n\n        config = loadOrSaveConfig()\n\n        val initialLoad = AtomicBoolean()\n\n        PolymerResourcePackUtils.RESOURCE_PACK_CREATION_EVENT.register { builder ->\n            val isInitialLoad = initialLoad.compareAndSet(false, true)\n            reload {\n                includeAssets(builder, it.packResult)\n                if (isInitialLoad) loadLog(it)\n            }\n        }\n\n        ServerLifecycleEvents.SERVER_STARTED.register {\n            allManagers.forEach {\n                it.start()\n            }\n            if (initialLoad.compareAndSet(false, true)) reload { loadLog(it) }\n        }\n\n        BetterModelAttachments.init()\n        FabricModelSchedulerImpl.init()\n\n        ServerLifecycleEvents.SERVER_STOPPED.register {\n            allManagers.forEach { manager ->\n                manager.end()\n            }\n        }\n    }\n\n    private fun reload(callback: (Success) -> Unit) {\n        when (val result = reload(ReloadInfo(true, Audience.empty()))) {\n            is Failure -> result.throwable.handleException(\"Unable to load mod properly.\")\n            is OnReload -> throw RuntimeException(\"mod load failed.\")\n            is Success -> callback(result)\n        }\n    }\n\n    private fun includeAssets(builder: ResourcePackBuilder, packResult: PackResult) {\n        packResult.stream().forEach { packByte ->\n            when (val path = packByte.path.path) {\n                \"pack.png\", \"pack.mcmeta\" -> return@forEach\n                else ->  builder.addData(path, packByte.bytes)\n            }\n        }\n        packResult.meta().overlays?.entries?.forEach { entry ->\n            if (entry.directory == \"bettermodel_legacy\") {\n                return@forEach\n            }\n\n            val min = entry.minFormat.run { PackFormat(major, minor) }\n            val max = entry.maxFormat.run { PackFormat(major, minor) }\n            val range = InclusiveRange(min, max)\n\n            builder.packMcMetaBuilder.addOverlay(range, entry.directory)\n        }\n    }\n\n    private fun loadLog(success: Success) {\n        info(\n            \"Mod is loaded. (${success.totalTime().withComma()} ms)\".toComponent(GREEN),\n            \"Platform: Fabric\".toComponent(AQUA)\n        )\n    }\n\n    override fun getResource(fileName: String): InputStream? {\n        return javaClass.getResourceAsStream(\"/$fileName\")\n    }\n\n    override fun saveResource(fileName: String) {\n        getResource(fileName)?.use { input ->\n            Files.copy(input, configDir.resolve(fileName))\n        }\n    }\n\n    override fun loadAssets(pipeline: ReloadPipeline, prefix: String, consumer: BiConsumer<String, InputStream>) {\n        jarFile.use { jarFile ->\n            val jarEntries = jarFile.entries()\n                .asSequence()\n                .filter { jarEntry ->\n                    jarEntry.name.startsWith(prefix) &&\n                        jarEntry.name.length > prefix.length + 1 &&\n                        !jarEntry.isDirectory\n                }\n                .toList()\n\n            pipeline.forEachParallel(\n                jarEntries,\n                { it.size }\n            ) { jarEntry ->\n                jarFile.getInputStream(jarEntry).use { stream ->\n                    consumer.accept(jarEntry.name.substring(prefix.length + 1), stream)\n                }\n            }\n        }\n    }\n\n    override fun dataFolder(): File = configDir.toFile()\n\n    override fun jarType(): BetterModelPlatform.JarType = BetterModelPlatform.JarType.FABRIC\n\n    override fun reload(info: ReloadInfo): BetterModelPlatform.ReloadResult {\n        if (!isLoadingProvider.compareAndSet(false, true)) {\n            return OnReload.INSTANCE\n        }\n\n        return runCatching {\n            if (!info.skipConfig) {\n                config = loadOrSaveConfig()\n            }\n\n            val zipper = PackZipper.zipper()\n            reloadStartTask(zipper)\n\n            val indicators = config().indicator().options.toIndicator(info)\n            ReloadPipeline(indicators).use { pipeline ->\n                val assetsTime = measureTimeMillis {\n                    allManagers.forEach { manager ->\n                        manager.reload(pipeline, zipper)\n                    }\n                }\n\n                pipeline.run {\n                    status = \"Generating files...\"\n                    goal = zipper.size()\n                }\n\n                val isFirstLoad = isFirstLoadProvider.compareAndSet(false, true)\n                val packResult = config().packType().toGenerator().create(zipper, pipeline)\n\n                Success(\n                    isFirstLoad,\n                    assetsTime,\n                    packResult\n                )\n            }\n\n        }\n            .getOrElse { throwable ->\n                Failure(throwable)\n            }\n            .also { result ->\n                isLoadingProvider.set(false)\n                reloadEndTask(result)\n            }\n    }\n\n    private fun loadOrSaveConfig(): BetterModelConfigImpl {\n        return configDir.resolve(\"config.yml\").run {\n            if (!exists()) saveResource(\"config.yml\")\n            toConfig()\n        }\n    }\n\n    override fun isSnapshot(): Boolean = !worldVersion.stable()\n\n    override fun config(): BetterModelConfig = config\n\n    override fun version(): MinecraftVersion = minecraftVersion\n\n    override fun semver(): Semver = semver\n\n    override fun nms(): NMS = nms\n\n    override fun modelManager(): ModelManager = ModelManagerImpl\n\n    override fun playerManager(): PlayerManager = PlayerManagerImpl\n\n    override fun scriptManager(): ScriptManager = ScriptManagerImpl\n\n    override fun skinManager(): SkinManager = SkinManagerImpl\n\n    override fun profileManager(): ProfileManager = ProfileManagerImpl\n\n    override fun addReloadStartHandler(consumer: Consumer<PackZipper>) {\n        val oldHandler = reloadStartTask\n        reloadStartTask = { zipper ->\n            oldHandler(zipper)\n            consumer.accept(zipper)\n        }\n    }\n\n    override fun addReloadEndHandler(consumer: Consumer<BetterModelPlatform.ReloadResult>) {\n        val oldHandler = reloadEndTask\n        reloadEndTask = { result ->\n            oldHandler(result)\n            consumer.accept(result)\n        }\n    }\n\n    override fun logger(): BetterModelLogger = logger\n\n    override fun evaluator(): BetterModelEvaluator = evaluator\n\n    override fun eventBus(): BetterModelEventBus = eventBus\n\n    override fun server(): MinecraftServer = server\n\n    override fun scheduler(): ModModelScheduler = FabricModelSchedulerImpl\n\n    override fun adapter(): PlatformAdapter = adapter\n\n    override fun isEnabled(): Boolean = true\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/BetterModelLoggerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric\n\nimport kr.toxicity.model.api.BetterModelLogger\nimport net.kyori.adventure.text.Component\nimport net.kyori.adventure.text.logger.slf4j.ComponentLogger\nimport java.util.logging.Logger\n\nclass BetterModelLoggerImpl : BetterModelLogger {\n\n    private val logger by lazy {\n        ComponentLogger.logger(LOGGER.name)\n    }\n\n    override fun info(vararg messages: Component) {\n        synchronized(this) {\n            for (message in messages) {\n                logger.info(message)\n            }\n        }\n    }\n\n    override fun warn(vararg messages: Component) {\n        synchronized(this) {\n            for (message in messages) {\n                logger.warn(message)\n            }\n        }\n    }\n\n    companion object {\n        private val LOGGER: Logger = Logger.getLogger(modId())\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/BetterModelNMSImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric\n\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mod.BetterModelMod\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.*\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport kr.toxicity.model.impl.fabric.entity.*\nimport kr.toxicity.model.impl.fabric.network.*\nimport kr.toxicity.model.impl.fabric.profile.ModelProfileImpl\nimport kr.toxicity.model.mixin.DisplayAccessor\nimport kr.toxicity.model.mixin.EntityAccessor\nimport kr.toxicity.model.util.PLATFORM\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.resources.Identifier\nimport net.minecraft.util.ARGB\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.item.Items\nimport net.minecraft.world.item.component.CustomModelData\nimport net.minecraft.world.item.component.DyedItemColor\nimport org.joml.Vector3d\nimport java.util.function.Consumer\n\nclass BetterModelNMSImpl : NMS {\n    override fun create(\n        location: PlatformLocation,\n        yOffset: Double,\n        initialConsumer: Consumer<ModelDisplay>\n    ): ModelDisplay {\n        val type = EntityType.ITEM_DISPLAY\n        val level = location.asFabric.level()!!\n\n        val itemDisplay = Display.ItemDisplay(type, level).apply {\n            billboardConstraints = Display.BillboardConstraints.FIXED\n            entityData[DisplayAccessor.`bettermodel$getDataPosRotInterpolationDurationId`()] = 3\n            itemTransform = ItemDisplayContext.FIXED\n            yRot = location.yaw()\n        }\n\n        val modelDisplay = ModelDisplayEntityImpl(Vector3d(location.x(), location.y(), location.z()), itemDisplay, yOffset).apply {\n            initialConsumer.accept(this)\n            display.entityData.packDirty()\n        }\n\n        return modelDisplay\n    }\n\n    override fun createNametag(bone: RenderedBone): ModelNametag = ModelNametagImpl(bone)\n\n    override fun inject(player: PlatformPlayer): PlayerChannelHandler = PlayerChannelHandlerImpl(player.unwarp())\n\n    override fun createBundler(initialCapacity: Int): PacketBundler = bundlerOf(initialCapacity)\n\n    override fun createParallelBundler(threshold: Int): PacketBundler = parallelBundlerOf(threshold)\n\n    override fun createModAnimationBuilder(initialCapacity: Int): ModAnimationBundler = ModAnimationBundlerImpl(initialCapacity)\n\n    override fun tint(itemStack: PlatformItemStack, rgb: Int): PlatformItemStack {\n        return itemStack.clone().unwarp().apply {\n            set(DataComponents.DYED_COLOR, DyedItemColor(rgb))\n            set(DataComponents.CUSTOM_MODEL_DATA, get(DataComponents.CUSTOM_MODEL_DATA)?.withMappedColors(rgb))\n        }.wrap()\n    }\n\n    private fun CustomModelData.withMappedColors(rgb: Int): CustomModelData {\n        return CustomModelData(\n            floats,\n            flags,\n            strings,\n            getMappedColors(rgb)\n        )\n    }\n\n    private fun CustomModelData.getMappedColors(rgb: Int): List<Int> {\n        if (colors.isEmpty()) {\n            return listOf(rgb)\n        }\n\n        if (rgb == 0xFFFFFF) {\n            return colors\n        }\n\n        return colors.map { color ->\n            ARGB.multiply(color, rgb) and 0xFFFFFF\n        }\n    }\n\n    override fun mount(registry: EntityTrackerRegistry, bundler: PacketBundler) {\n        (registry.entity().handle() as? Entity)?.let {\n            bundler += registry.mountPacket(it)\n        }\n    }\n\n    override fun hide(channel: PlayerChannelHandler, registry: EntityTrackerRegistry) {\n        val target = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf()\n        target.entityData.pack(\n            valueFilter = { it.id == EntityAccessor.`bettermodel$getDataSharedFlagsId`().id }\n        )?.let {\n            list += ClientboundSetEntityDataPacket(target.id, it).toRegistryDataPacket(channel.uuid(), registry)\n        }\n        if (target is LivingEntity) {\n            val packet = if (registry.hideOption(channel.uuid()).equipment) target.toEmptyEquipmentPacket() else target.toEquipmentPacket()\n            packet?.let { list += it }\n        }\n        list.send(channel.player())\n    }\n\n    override fun createHitBox(\n        entity: BaseEntity,\n        bone: RenderedBone,\n        boundingBox: ModelBoundingBox,\n        controller: MountController,\n        listener: HitBoxListener\n    ): HitBox {\n        return HitBoxEntityImpl(\n            boundingBox.center(),\n            bone,\n            listener,\n            entity.handle() as Entity,\n            controller\n        )\n    }\n\n    override fun version(): NMSVersion = NMSVersion.V26_R1\n\n    override fun adapt(entity: PlatformEntity): BaseEntity = BaseFabricEntityImpl(entity.unwarp())\n\n    override fun adapt(player: PlatformPlayer): BasePlayer {\n        val connection = player.unwarp()\n        return BaseFabricPlayerImpl(\n            connection, dirtyChecked(\n                { connection.player.gameProfile },\n                { ModelProfileImpl(it) }\n            ),\n            dirtyChecked(\n                { connection.player.getCustomisation() },\n                { PlayerSkinParts(it) }\n            )\n        )\n    }\n\n    override fun profile(player: PlatformPlayer): ModelProfile = ModelProfileImpl(player.unwarp().player.gameProfile)\n\n    override fun isProxyOnlineMode(): Boolean = (PLATFORM as BetterModelMod).server().usesAuthentication()\n\n    override fun createSkinItem(model: String, floats: List<Float>, flags: List<Boolean>, strings: List<String>, colors: List<Int>): TransformedItemStack {\n        return ItemStack(Items.PLAYER_HEAD).run {\n            set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(floats, flags, strings, colors))\n            set(DataComponents.ITEM_MODEL, Identifier.parse(model))\n            TransformedItemStack.of(wrap())\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/Constants.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric\n\nimport org.slf4j.Logger\nimport org.slf4j.LoggerFactory\n\nfun modId() = MOD_ID\n\nprivate const val MOD_ID = \"bettermodel\"\n\nfun logger(): Logger = LOGGER\n\nprivate val LOGGER: Logger = LoggerFactory.getLogger(modId())\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/Entities.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n@file:Suppress(\"UnstableApiUsage\")\n\npackage kr.toxicity.model.impl.fabric\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.impl.fabric.entity.EntityHook\nimport kr.toxicity.model.impl.fabric.world.entityMap\nimport kr.toxicity.model.mixin.AvatarAccessor\nimport kr.toxicity.model.mixin.MobAccessor\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.server.network.ServerPlayerConnection\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.goal.Goal\nimport net.minecraft.world.entity.ai.goal.RangedAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedBowAttackGoal\nimport net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal\nimport net.minecraft.world.entity.animal.FlyingAnimal\nimport net.minecraft.world.entity.player.Player\nimport org.joml.Vector3f\n\nfun Entity.toTracker(model: String?) = toRegistry()?.tracker(model)\n\nfun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n\nval Entity.isWalking: Boolean\n    get() {\n        return controllingPassenger?.isWalking ?: checkEntityWalkingState()\n    }\n\nval Entity.isFlying: Boolean\n    get() {\n        return this is FlyingAnimal && isFlying ||\n            this is Mob && isNoAi ||\n            this is Player && abilities.flying ||\n            this is LivingEntity && isFallFlying\n    }\n\nval Entity.seenBy: Set<ServerPlayerConnection>\n    get() {\n        val level = level() as ServerLevel\n        val tracker = level.chunkSource.chunkMap.entityMap.get(id)\n            ?: return emptySet()\n\n        return tracker.seenBy\n    }\n\nvar Entity.modelData: String?\n    get() {\n        return (this as EntityHook).`bettermodel$getModelData`()\n    }\n    set(value) {\n        (this as EntityHook).`bettermodel$setModelData`(value)\n    }\n\nfun Entity.passengerPosition(dest: Vector3f): Vector3f {\n    val point = attachments.get(EntityAttachment.PASSENGER, 0, yRot)\n    return dest.set(\n        point.x.toFloat(),\n        point.y.toFloat(),\n        point.z.toFloat()\n    )\n}\n\nprivate fun Entity.checkEntityWalkingState(): Boolean {\n    return when (this) {\n        is Mob -> navigation.isInProgress || isRangedAttacking()\n        is ServerPlayer -> xMovement() != 0.0f || zMovement() != 0.0f\n        else -> false\n    }\n}\n\nprivate fun Mob.isRangedAttacking(): Boolean {\n    return (this as MobAccessor).`bettermodel$getGoalSelector`().availableGoals.any { wrapper ->\n        wrapper.isRunning && wrapper.goal.isRangedAttackGoal()\n    }\n}\n\nprivate fun Goal.isRangedAttackGoal(): Boolean {\n    return this is RangedAttackGoal ||\n        this is RangedCrossbowAttackGoal<*> ||\n        this is RangedBowAttackGoal<*>\n}\n\nfun Avatar.getCustomisation(): Int {\n    return entityData.get(\n        AvatarAccessor.`bettermodel$getDataPlayerModeCustomisation`()\n    ).toInt()\n}\n\nfun ServerPlayer.xMovement(): Float {\n    return when {\n        lastClientInput.left() == lastClientInput.right() -> 0.0f\n        lastClientInput.left() -> 1.0f\n        else -> -1.0f\n    }\n}\n\nfun ServerPlayer.yMovement(): Float {\n    return when {\n        lastClientInput.jump -> 1.0f\n        lastClientInput.shift -> -1.0f\n        else -> 0.0f\n    }\n}\n\nfun ServerPlayer.zMovement(): Float {\n    return when {\n        lastClientInput.forward() == lastClientInput.backward() -> 0.0f\n        lastClientInput.forward() -> 1.0f\n        else -> -1.0f\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/FabricWrappers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric\n\nimport kr.toxicity.model.api.mod.platform.*\nimport kr.toxicity.model.api.mod.platform.ModAdapter.adapt\nimport kr.toxicity.model.api.platform.*\nimport net.minecraft.server.network.ServerPlayerConnection\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.item.ItemStack\n\nval PlatformLocation.asFabric get() = this as ModLocation\n\nfun Entity.wrap() = adapt(this)\nfun LivingEntity.wrap() = adapt(this)\nfun ServerPlayerConnection.wrap() = adapt(this)\nfun ItemStack.wrap() = adapt(this)\n\nfun PlatformEntity.unwarp(): Entity = (this as ModEntity).source()\nfun PlatformLivingEntity.unwarp(): LivingEntity = (this as ModLivingEntity).source()\nfun PlatformPlayer.unwarp(): ServerPlayerConnection = (this as ModPlayer).source()\nfun PlatformItemStack.unwarp(): ItemStack = (this as ModItemStack).source()\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/Functions.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric\n\ninline fun <H, T> dirtyChecked(crossinline hash: () -> H, crossinline function: (H) -> T): () -> T {\n    val lock = Any()\n    var h = hash()\n    var value = function(h)\n    return {\n        val newH = hash()\n        when {\n            h === newH -> value\n            h == newH -> value\n            else -> synchronized(lock) {\n                h = newH\n                value = function(h)\n                value\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/armor/PlayerArmorImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.armor\n\nimport kr.toxicity.model.api.armor.ArmorItem\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport net.minecraft.core.component.DataComponents\nimport net.minecraft.server.network.ServerPlayerConnection\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.item.component.DyedItemColor\nimport net.minecraft.world.item.equipment.EquipmentAssets\nimport net.minecraft.world.item.equipment.trim.ArmorTrim\nimport kotlin.jvm.optionals.getOrNull\n\nclass PlayerArmorImpl(private val connection: ServerPlayerConnection) : PlayerArmor {\n\n    private val player get() = connection.player\n\n    override fun helmet(): ArmorItem? = player.getItemBySlot(EquipmentSlot.HEAD).toArmorItem()\n\n    override fun leggings(): ArmorItem? = player.getItemBySlot(EquipmentSlot.LEGS).toArmorItem()\n\n    override fun chestplate(): ArmorItem? = player.getItemBySlot(EquipmentSlot.CHEST).toArmorItem()\n\n    override fun boots(): ArmorItem? = player.getItemBySlot(EquipmentSlot.FEET).toArmorItem()\n\n    private fun ItemStack.assetIdOrNull() = get(DataComponents.EQUIPPABLE)?.assetId?.getOrNull()\n\n    private fun ItemStack.toArmorItem() = assetIdOrNull()?.let { asset ->\n        val trim = get(DataComponents.TRIM)\n        val tint = get(DataComponents.DYED_COLOR)?.rgb\n            ?: if (asset === EquipmentAssets.LEATHER) DyedItemColor.LEATHER_COLOR else 0xFFFFFF\n\n        ArmorItem(\n            tint,\n            asset.identifier().path,\n            trim?.getPath(),\n            trim?.getPalette()\n        )\n    }\n\n    private fun ArmorTrim.getPath() = pattern.value().assetId.path\n\n    private fun ArmorTrim.getPalette() = material.value().assets.base.suffix\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/attachment/BetterModelAttachments.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n@file:Suppress(\"UnstableApiUsage\")\n\npackage kr.toxicity.model.impl.fabric.attachment\n\nimport com.mojang.serialization.Codec\nimport kr.toxicity.model.impl.fabric.modId\nimport net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry\nimport net.fabricmc.fabric.api.attachment.v1.AttachmentType\nimport net.minecraft.resources.Identifier\n\nobject BetterModelAttachments {\n    @JvmField\n    val MODEL_DATA: AttachmentType<String> = AttachmentRegistry.create(\n        Identifier.fromNamespaceAndPath(modId(), \"model_data\")\n    ) { builder ->\n        builder\n            .persistent(Codec.STRING)\n            .copyOnDeath()\n    }\n\n    internal fun init() = Unit\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/audience/AudienceCommandSource.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.audience\n\nimport net.kyori.adventure.audience.Audience\nimport net.minecraft.commands.CommandSourceStack\n\ninterface AudienceCommandSource : Audience {\n    val source: CommandSourceStack\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/audience/AudiencePlayer.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.audience\n\nimport net.kyori.adventure.bossbar.BossBar\nimport net.kyori.adventure.text.Component\nimport net.minecraft.commands.CommandSourceStack\nimport net.minecraft.server.level.ServerPlayer\n\ndata class AudiencePlayer(\n    override val source: CommandSourceStack,\n    val player: ServerPlayer\n) : AudienceCommandSource {\n\n    override fun sendMessage(message: Component) {\n        player.sendMessage(message)\n    }\n\n    override fun showBossBar(bar: BossBar) {\n        player.showBossBar(bar)\n    }\n\n    override fun hideBossBar(bar: BossBar) {\n        player.hideBossBar(bar)\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/audience/AudienceSourceStack.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.audience\n\nimport net.kyori.adventure.bossbar.BossBar\nimport net.kyori.adventure.text.Component\nimport net.minecraft.commands.CommandSourceStack\n\ndata class AudienceSourceStack(override val source: CommandSourceStack) : AudienceCommandSource {\n    override fun sendMessage(message: Component) {\n        source.sendMessage(message)\n    }\n\n    override fun showBossBar(bar: BossBar) {\n        source.showBossBar(bar)\n    }\n\n    override fun hideBossBar(bar: BossBar) {\n        source.hideBossBar(bar)\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/chat/Components.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.chat\n\nimport net.kyori.adventure.platform.modcommon.impl.NonWrappingComponentSerializer\nimport net.minecraft.network.chat.Component\n\nfun net.kyori.adventure.text.Component.asVanilla(): Component = NonWrappingComponentSerializer.INSTANCE.serialize(this)\nfun Component.asAdventure(): net.kyori.adventure.text.Component = NonWrappingComponentSerializer.INSTANCE.deserialize(this)\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/command/Commands.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.command\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.BetterModelPlatform.ReloadResult.*\nimport kr.toxicity.model.api.animation.AnimationIterator\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.mod.platform.ModLocation\nimport kr.toxicity.model.api.tracker.EntityHideOption\nimport kr.toxicity.model.api.tracker.ModelScaler\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerModifier\nimport kr.toxicity.model.command.*\nimport kr.toxicity.model.impl.fabric.audience.AudienceCommandSource\nimport kr.toxicity.model.impl.fabric.audience.AudiencePlayer\nimport kr.toxicity.model.impl.fabric.audience.AudienceSourceStack\nimport kr.toxicity.model.impl.fabric.toRegistry\nimport kr.toxicity.model.impl.fabric.toTracker\nimport kr.toxicity.model.impl.fabric.wrap\nimport kr.toxicity.model.util.*\nimport net.kyori.adventure.audience.Audience\nimport net.kyori.adventure.text.format.NamedTextColor.*\nimport net.minecraft.commands.CommandSourceStack\nimport net.minecraft.core.registries.Registries\nimport net.minecraft.world.entity.EntitySpawnReason\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.phys.Vec3\nimport org.incendo.cloud.SenderMapper\nimport org.incendo.cloud.context.CommandContext\nimport org.incendo.cloud.execution.ExecutionCoordinator\nimport org.incendo.cloud.fabric.FabricServerCommandManager\nimport org.incendo.cloud.minecraft.modded.data.Coordinates\nimport org.incendo.cloud.minecraft.modded.data.MultipleEntitySelector\nimport org.incendo.cloud.minecraft.modded.data.SinglePlayerSelector\nimport org.incendo.cloud.minecraft.modded.parser.RegistryEntryParser.registryEntryParser\nimport org.incendo.cloud.minecraft.modded.parser.VanillaArgumentParsers.singlePlayerSelectorParser\nimport org.incendo.cloud.minecraft.modded.parser.VanillaArgumentParsers.vec3Parser\nimport org.incendo.cloud.parser.standard.BooleanParser.booleanParser\nimport org.incendo.cloud.parser.standard.DoubleParser.doubleParser\nimport org.incendo.cloud.parser.standard.EnumParser.enumParser\nimport org.incendo.cloud.parser.standard.StringParser.stringParser\nimport org.incendo.cloud.suggestion.SuggestionProvider.blockingStrings\n\nprivate val MODEL_SUGGESTION = blockingStrings<Audience> { _, _ -> BetterModel.modelKeys() }\nprivate val LIMB_SUGGESTION = blockingStrings<Audience> { _, _ -> BetterModel.limbKeys() }\n\nfun startFabricCommand() {\n    FabricServerCommandManager(\n        ExecutionCoordinator.simpleCoordinator(),\n        SenderMapper.create<CommandSourceStack, Audience>(\n            { stack -> stack.player?.let { player -> AudiencePlayer(stack, player) } ?: AudienceSourceStack(stack) },\n            { audience -> (audience as AudienceCommandSource).source }\n        )\n    ).register(\n        \"bettermodel\",\n        \"All-related command.\",\n        { it },\n        \"bm\", \"model\"\n    ) {\n        create(\n            \"reload\",\n            \"Reloads BetterModel.\",\n            \"re\", \"rl\"\n        ) {\n            handler(::reload)\n        }\n        create(\n            \"spawn\",\n            \"Summons some model to given type\",\n            \"s\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .optional(\"type\", registryEntryParser(Registries.ENTITY_TYPE, EntityType::class.java))\n                .optional(\"scale\", doubleParser(0.0625, 16.0))\n                .optional(\"location\", vec3Parser(true))\n                .senderType(AudiencePlayer::class.java)\n                .handler(::spawn)\n        }\n        create(\n            \"test\",\n            \"Tests some model's animation to specific source\",\n            \"t\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .required(\n                    \"animation\",\n                    stringParser(),\n                    blockingStrings { ctx, _ -> ctx.nullableString(\"model\") { BetterModel.modelOrNull(it)?.animations()?.keys } ?: emptySet()  }\n                )\n                .optional(\"source\", singlePlayerSelectorParser())\n                .optional(\"location\", vec3Parser(false))\n                .handler(::test)\n        }\n        create(\n            \"disguise\",\n            \"Disguises self.\",\n            \"d\"\n        ) {\n            required(\"model\", stringParser(), MODEL_SUGGESTION)\n                .optional(\"scaling\", booleanParser())\n                .senderType(AudiencePlayer::class.java)\n                .handler(::disguise)\n        }\n        create(\n            \"undisguise\",\n            \"Undisguises self.\",\n            \"ud\"\n        ) {\n            senderType(AudiencePlayer::class.java)\n                .optional(\"model\", stringParser(), blockingStrings { ctx, _ -> ctx.sender().player.toRegistry()?.trackers()?.map(Tracker::name) ?: emptyList() })\n                .handler(::undisguise)\n        }\n        create(\n            \"play\",\n            \"Plays source animation\",\n            \"p\"\n        ) {\n            required(\"limb\", stringParser(), LIMB_SUGGESTION)\n                .required(\n                    \"animation\",\n                    stringParser(),\n                    blockingStrings { ctx, _ -> ctx.nullableString(\"limb\") { BetterModel.limbOrNull(it)?.animations()?.keys } ?: emptySet()  }\n                )\n                .optional(\"loop_type\", enumParser(AnimationIterator.Type::class.java))\n                .optional(\"hide\", booleanParser())\n                .senderType(AudiencePlayer::class.java)\n                .handler(::play)\n        }\n        create(\n            \"version\",\n            \"Checks BetterModel's version\",\n            \"v\"\n        ) {\n            handler(::version)\n        }\n// TODO NOT implemented yet\n//        create(\n//            \"hide\",\n//            \"Hides some entities from target source.\"\n//        ) {\n//            required(\"model\", stringParser(), MODEL_SUGGESTION)\n//                .required(\"source\", singlePlayerSelectorParser())\n//                .required(\"entities\", multipleEntitySelectorParser())\n//                .handler(::hide)\n//        }\n//        create(\n//            \"show\",\n//            \"Shows some entities to target source.\"\n//        ) {\n//            required(\"model\", stringParser(), MODEL_SUGGESTION)\n//                .required(\"source\", singlePlayerSelectorParser())\n//                .required(\"entities\", multipleEntitySelectorParser())\n//                .handler(::show)\n//        }\n    }\n}\n\nprivate fun hide(context: CommandContext<Audience>) {\n    val sender = context.sender()\n    val model = context.get<String>(\"model\")\n    val player = context.get<SinglePlayerSelector>(\"source\").single().connection.wrap()\n    var success = false\n    context.get<MultipleEntitySelector>(\"entities\").values().forEach {\n        if (it.toRegistry()?.tracker(model)?.hide(player) == true) success = true\n    }\n    if (!success) sender.warn(\"Failed to hide any of provided entities.\")\n}\n\nprivate fun show(context: CommandContext<Audience>) {\n    val sender = context.sender()\n    val model = context.get<String>(\"model\")\n    val player = context.get<SinglePlayerSelector>(\"source\").single().connection.wrap()\n    var success = false\n    context.get<MultipleEntitySelector>(\"entities\").values().forEach {\n        if (it.toRegistry()?.tracker(model)?.show(player) == true) success = true\n    }\n    if (!success) sender.warn(\"Failed to show any of provided entities.\")\n}\n\nprivate fun disguise(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.player\n    val scaling = if (context.getOrDefault(\"scaling\", true)) ModelScaler.entity() else ModelScaler.defaultScaler()\n    context.model(\"model\") { return audience.warn(\"Unable to find this model: $it\") }.getOrCreate(player.connection.wrap(), TrackerModifier.DEFAULT) {\n        it.scaler(scaling)\n    }\n}\n\nprivate fun undisguise(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.player\n    val model = context.nullable<String>(\"model\")\n    if (model != null) {\n        player.toTracker(model)?.close() ?: audience.warn(\"Cannot find this model to undisguise: $model\")\n    } else player.toRegistry()?.close() ?: audience.warn(\"Cannot find any model to undisguise\")\n}\n\nprivate fun spawn(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.player\n    val model = context.model(\"model\") { return audience.warn(\"Unable to find this model: $it\") }\n    val type = context.nullable<EntityType<*>>(\"type\", EntityType.HUSK)\n    val scale = context.nullable(\"scale\", 1.0)\n    val loc = context.nullable<Coordinates>(\"location\")\n    type.spawn(\n        player.level(),\n        loc?.blockPos() ?: player.blockPosition(),\n        EntitySpawnReason.COMMAND\n    )?.let { entity ->\n        model.create(entity.wrap(), TrackerModifier.DEFAULT) { tracker -> tracker.scaler(ModelScaler.entity().multiply(scale.toFloat())) }\n    } ?: audience.warn(\"Entity spawning has been blocked.\")\n}\n\nprivate fun version(context: CommandContext<Audience>) {\n    val sender = context.sender()\n    sender.info(\"Searching version, please wait...\")\n    PLATFORM.scheduler().asyncTask {\n        val version = LATEST_VERSION\n        sender.infoNotNull(\n            emptyComponentOf(),\n            \"Current: ${PLATFORM.semver()}\".toComponent(),\n            version.release?.let { version -> componentOf(\"Latest release: \") { append(version.toURLComponent()) } },\n            version.snapshot?.let { version -> componentOf(\"Latest snapshot: \") { append(version.toURLComponent()) } }\n        )\n    }\n}\n\nprivate fun reload(context: CommandContext<Audience>) {\n    val audience = context.sender()\n    PLATFORM.scheduler().asyncTask {\n        audience.info(\"Start reloading. please wait...\")\n        when (val result = PLATFORM.reload(audience)) {\n            is OnReload -> audience.warn(\"BetterModel is still on reload!\")\n            is Success -> {\n                audience.info(\n                    emptyComponentOf(),\n                    \"Reload completed. (${result.totalTime().withComma()}ms)\".toComponent(GREEN),\n                    \"Assets reload time - ${result.assetsTime().withComma()}ms\".toComponent {\n                        color(GRAY)\n                        hoverEvent(\"Reading all config and model.\".toComponent().toHoverEvent())\n                    },\n                    \"Packing time - ${result.packingTime().withComma()}ms\".toComponent {\n                        color(GRAY)\n                        hoverEvent(\"Packing all model to resource pack.\".toComponent().toHoverEvent())\n                    },\n                    \"${BetterModel.models().size.withComma()} of models are loaded successfully. (${result.length().toByteFormat()})\".toComponent(YELLOW),\n                    (if (result.packResult.changed()) \"${result.packResult.size().withComma()} of files are zipped.\" else \"Zipping is skipped due to the same result.\").toComponent(YELLOW),\n                    emptyComponentOf()\n                )\n            }\n            is Failure -> {\n                audience.warn(\n                    emptyComponentOf(),\n                    \"Reload failed.\".toComponent(),\n                    \"Please read the log to find the problem.\".toComponent(),\n                    emptyComponentOf()\n                )\n                audience.warn()\n                result.throwable.handleException(\"Reload failed.\")\n            }\n        }\n    }\n}\n\nprivate fun play(context: CommandContext<AudiencePlayer>) {\n    val audience = context.sender()\n    val player = audience.player\n    val limb = context.limb(\"limb\") { return audience.warn(\"Unable to find this limb: $it\") }\n    val animation = context.string(\"animation\") { limb.animation(it).orElse(null) ?: return audience.warn(\"Unable to find this animation: $it\") }\n    val loopType = context.nullable(\"loop_type\", AnimationIterator.Type.PLAY_ONCE)\n    val hide = context.nullable<Boolean>(\"hide\") != false\n    limb.getOrCreate(player.connection.wrap(), TrackerModifier.DEFAULT) {\n        it.hideOption(if (hide) EntityHideOption.DEFAULT else EntityHideOption.FALSE)\n    }.run {\n        if (!animate(animation, AnimationModifier(0, 0, loopType), ::close)) close()\n    }\n}\n\nprivate fun test(context: CommandContext<Audience>) {\n    val audience = context.sender()\n    val model = context.model(\"model\") { return audience.warn(\"Unable to find this model: $it\") }\n    val animation = context.string(\"animation\") { str -> model.animation(str).orElse(null) ?: return audience.warn(\"Unable to find this animation: $str\") }\n    val player = context.nullable<SinglePlayerSelector>(\"source\")?.single() ?: (audience as? AudiencePlayer)?.player ?: return audience.warn(\"Unable to find target source.\")\n    val location = context.nullable<Coordinates>(\"location\")?.position() ?: player.position()\n        .add(Vec3(0.0, 0.0, 10.0).yRot(-Math.toRadians(player.yRot.toDouble()).toFloat()))\n\n    model.create(ModLocation.of(\n        player.level(),\n        location.x,\n        location.y,\n        location.z,\n        player.xRot,\n        player.yRot + 180\n    )).run {\n        spawn(player.connection.wrap())\n        animate(animation, AnimationModifier(0, 0, AnimationIterator.Type.PLAY_ONCE), ::close)\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/config/BetterModelConfigImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.config\n\nimport kr.toxicity.model.api.BetterModelConfig\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.config.IndicatorConfig\nimport kr.toxicity.model.api.config.ModuleConfig\nimport kr.toxicity.model.api.config.PackConfig\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.mount.MountControllers\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.util.EntityUtil\nimport kr.toxicity.model.impl.fabric.wrap\nimport kr.toxicity.model.util.toPackName\nimport net.minecraft.core.registries.BuiltInRegistries\nimport net.minecraft.resources.Identifier\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.item.Items\nimport org.spongepowered.configurate.ConfigurationNode\nimport org.spongepowered.configurate.yaml.YamlConfigurationLoader\nimport java.io.File\nimport java.nio.file.Path\nimport java.util.function.Supplier\n\nfun Path.toConfig() = BetterModelConfigImpl(YamlConfigurationLoader.builder().path(this).build().load())\n\nclass BetterModelConfigImpl(yaml: ConfigurationNode) : BetterModelConfig {\n\n    private val debug = yaml.node(\"debug\")?.let { node ->\n        DebugConfig.from { node.node(it).getBoolean(false) }\n    } ?: DebugConfig.DEFAULT\n    private val indicator = yaml.node(\"indicator\")?.let { node ->\n        IndicatorConfig.from { node.node(it).getBoolean(false) }\n    } ?: IndicatorConfig.DEFAULT\n    private val module = yaml.node(\"module\")?.let { node ->\n        ModuleConfig.from { node.node(it).getBoolean(false) }\n    } ?: ModuleConfig.DEFAULT\n    private val pack = yaml.node(\"pack\")?.let { node ->\n        PackConfig.from { node.node(it).getBoolean(false) }\n    } ?: PackConfig.DEFAULT\n    private val sightTrace = yaml.node(\"sight-trace\").getBoolean(true)\n    private val mergeWithExternalResources = yaml.node(\"merge-with-external-resources\").getBoolean(false)\n    private val itemModel = yaml.node(\"item\").getString(\"leather_horse_armor\")\n    private val item = runCatching {\n        BuiltInRegistries.ITEM.getValue(\n            Identifier.withDefaultNamespace(itemModel)\n        )\n    }.getOrDefault(Items.LEATHER_HORSE_ARMOR).let {\n        Supplier { ItemStack(it).wrap() }\n    }\n    private val itemNamespace = yaml.node(\"item-namespace\").getString(\"bm_models\").toPackName()\n    private val maxSight by lazy {\n        yaml.node(\"max-sight\").getDouble(-1.0).run {\n            if (this <= 0.0) EntityUtil.renderDistance() else this\n        }\n    }\n\n    private val minSight = yaml.node(\"min-sight\").getDouble(5.0)\n    private val namespace = yaml.node(\"namespace\").getString(\"bettermodel\")\n    private val packType = yaml.node(\"pack-type\").getString(\"zip\")?.let {\n        runCatching {\n            BetterModelConfig.PackType.valueOf(it.uppercase())\n        }.getOrNull()\n    } ?: BetterModelConfig.PackType.ZIP\n    private val buildFolderLocation = (yaml.node(\"build-folder-location\").getString(\"BetterModel/build\")).replace('/', File.separatorChar)\n    private val followMobInvisibility = yaml.node(\"follow-mob-invisibility\").getBoolean(true)\n    private val versionCheck = yaml.node(\"version-check\").getBoolean(true)\n    private val defaultMountController = when (yaml.node(\"default-mount-controller\").getString(\"walk\")?.lowercase()) {\n        \"invalid\" -> MountControllers.INVALID\n        \"none\" -> MountControllers.NONE\n        \"fly\" -> MountControllers.FLY\n        else -> MountControllers.WALK\n    }\n    private val lerpFrameTime = yaml.node(\"lerp-frame-time\").getInt(5)\n    private val cancelPlayerModelInventory = yaml.node(\"cancel-player-model-inventory\").getBoolean(false)\n    private val playerHideDelay = yaml.node(\"player-hide-delay\").getLong(3L).coerceAtLeast(1L)\n    private val packetBundlingSize = yaml.node(\"packet-bundling-size\").getInt(16)\n    private val enableStrictLoading = yaml.node(\"enable-strict-loading\").getBoolean(false)\n\n    override fun debug(): DebugConfig = debug\n    override fun indicator(): IndicatorConfig = indicator\n    override fun module(): ModuleConfig = module\n    override fun pack(): PackConfig = pack\n    override fun item(): Supplier<PlatformItemStack> = item\n    override fun itemModel(): String = itemModel\n    override fun itemNamespace(): String = itemNamespace\n    override fun metrics(): Boolean = false\n    override fun sightTrace(): Boolean = sightTrace\n    override fun mergeWithExternalResources(): Boolean = mergeWithExternalResources\n    override fun maxSight(): Double = maxSight\n    override fun minSight(): Double = minSight\n    override fun namespace(): String = namespace\n    override fun packType(): BetterModelConfig.PackType = packType\n    override fun buildFolderLocation(): String = buildFolderLocation\n    override fun followMobInvisibility(): Boolean = followMobInvisibility\n    override fun usePurpurAfk(): Boolean = false\n    override fun versionCheck(): Boolean = versionCheck\n    override fun defaultMountController(): MountController = defaultMountController\n    override fun lerpFrameTime(): Int = lerpFrameTime\n    override fun cancelPlayerModelInventory(): Boolean = cancelPlayerModelInventory\n    override fun playerHideDelay(): Long = playerHideDelay\n    override fun packetBundlingSize(): Int = packetBundlingSize\n    override fun enableStrictLoading(): Boolean = enableStrictLoading\n}\n\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/BaseFabricEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport kr.toxicity.model.api.mod.entity.BaseModEntity\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.TransformedItemStack\nimport kr.toxicity.model.impl.fabric.*\nimport kr.toxicity.model.impl.fabric.chat.asAdventure\nimport net.kyori.adventure.text.Component\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.stream.Stream\n\nclass BaseFabricEntityImpl(private var entity: Entity) : BaseModEntity {\n    override fun entity(entity: Entity) {\n        this.entity = entity\n    }\n\n    override fun platform(): PlatformEntity = entity.wrap()\n\n    override fun customName(): Component? {\n        return if (entity is ServerPlayer) {\n            (entity.customName ?: entity.name).asAdventure()\n        } else {\n            entity.customName?.takeIf { entity.isCustomNameVisible }?.asAdventure()\n        }\n    }\n\n    override fun handle(): Entity = entity\n\n    override fun id(): Int = entity.id\n\n    override fun dead(): Boolean {\n        val entity = entity\n        return entity.removalReason != null || entity is LivingEntity && entity.isDeadOrDying\n    }\n\n    override fun ground(): Boolean = entity.onGround()\n\n    override fun invisible(): Boolean {\n        val entity = entity\n        return entity.isInvisible || entity is LivingEntity && entity.hasEffect(MobEffects.INVISIBILITY)\n    }\n\n    override fun glow(): Boolean = entity.isCurrentlyGlowing\n\n    override fun onWalk(): Boolean = entity.isWalking\n\n    override fun fly(): Boolean = entity.isFlying\n\n    override fun scale(): Double {\n        val entity = entity\n        return if (entity is LivingEntity) {\n            entity.scale.toDouble()\n        } else {\n            1.0\n        }\n    }\n\n    override fun pitch(): Float = entity.xRot\n\n    override fun bodyYaw(): Float = entity.let { if (it is LivingEntity) it.yBodyRot else it.yRot }\n\n    override fun yaw(): Float = entity.yRot\n\n    override fun headYaw(): Float = entity.let { if (it is LivingEntity) it.yHeadRot else it.yRot }\n\n    override fun damageTick(): Float {\n        val entity = entity\n        if (entity !is LivingEntity || entity.invulnerableTime <= 0.0f) {\n            return 0F\n        }\n\n        val knockbackResistant = entity.getAttribute(Attributes.KNOCKBACK_RESISTANCE)?.value ?: 0.0\n        val knockBack = 1 - knockbackResistant.toFloat()\n\n        return entity.hurtTime.toFloat() / entity.hurtDuration * knockBack\n    }\n\n    override fun walkSpeed(): Float {\n        val entity = entity\n        if (entity !is LivingEntity) {\n            return 0.0f\n        }\n\n        if (!entity.onGround()) {\n            return 1.0f\n        }\n\n        val speed = entity.getEffect(MobEffects.SPEED)?.amplifier ?: 0\n        val slow = entity.getEffect(MobEffects.SLOWNESS)?.amplifier ?: 0\n\n        return (1.0f + (speed - slow) * 0.2f).coerceIn(0.2f..2.0f)\n    }\n\n    override fun passengerPosition(dest: Vector3f): Vector3f = entity.passengerPosition(dest)\n\n    override fun trackedBy(): Stream<PlatformPlayer> = entity.seenBy.stream().map {\n        it.wrap()\n    }\n\n    override fun mainHand(): TransformedItemStack {\n        val entity = entity\n        return if (entity is LivingEntity) {\n            TransformedItemStack.of(entity.mainHandItem.wrap())\n        } else {\n            TransformedItemStack.empty()\n        }\n    }\n\n    override fun offHand(): TransformedItemStack {\n        val entity = entity\n        return if (entity is LivingEntity) {\n            TransformedItemStack.of(entity.offhandItem.wrap())\n        } else {\n            TransformedItemStack.empty()\n        }\n    }\n\n    override fun modelData(): String? {\n        return entity.modelData\n    }\n\n    override fun modelData(modelData: String?) {\n        entity.modelData = modelData\n    }\n\n    override fun uuid(): UUID = entity.uuid\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/BaseFabricPlayerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport kr.toxicity.model.api.mod.entity.BaseModEntity\nimport kr.toxicity.model.api.mod.entity.BaseModPlayer\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.impl.fabric.armor.PlayerArmorImpl\nimport kr.toxicity.model.impl.fabric.seenBy\nimport kr.toxicity.model.impl.fabric.wrap\nimport kr.toxicity.model.impl.fabric.xMovement\nimport kr.toxicity.model.impl.fabric.zMovement\nimport net.minecraft.server.network.ServerPlayerConnection\nimport net.minecraft.util.Mth\nimport java.util.stream.Stream\n\nclass BaseFabricPlayerImpl(\n    private val connection: ServerPlayerConnection,\n    private val profile: () -> ModelProfile,\n    private val skinParts: () -> PlayerSkinParts\n) : BaseModPlayer, BaseModEntity by BaseFabricEntityImpl(connection.player), Profiled by ProfiledImpl(PlayerArmorImpl(connection), profile, skinParts) {\n\n    override fun updateInventory() {\n        connection.player.containerMenu.sendAllDataToRemote()\n    }\n\n    override fun platform(): PlatformPlayer = connection.wrap()\n\n    override fun trackedBy(): Stream<PlatformPlayer> = Stream.concat(\n        Stream.of(connection),\n        connection.player.seenBy.stream()\n    ).map {\n        it.wrap()\n    }\n\n    override fun bodyYaw(): Float {\n        val handle = connection.player\n        var yaw = -45 * handle.xMovement()\n        if (handle.zMovement() < 0) yaw *= -1\n        return Mth.wrapDegrees(handle.yHeadRot + yaw)\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/DisplayTransformerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport kr.toxicity.model.impl.fabric.network.plusAssign\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.world.entity.Display\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\nclass DisplayTransformerImpl(source: Display.ItemDisplay) :\n    DisplayTransformer\n{\n    private val id = source.id\n\n    private val entityData = TransformationData()\n    private val entityDataLock = SingleLock()\n\n    override fun transform(\n        duration: Int,\n        position: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf,\n        bundler: AnimationBundler\n    ) {\n        entityDataLock.accessToLock {\n            entityData.transform(\n                duration,\n                position,\n                scale,\n                rotation\n            )\n            entityData.packDirty(id, bundler)\n        }\n    }\n\n    override fun sendTransformation(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack()\n        }?.let {\n            bundler += ClientboundSetEntityDataPacket(id, it)\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/HitBoxEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.config.DebugConfig\nimport kr.toxicity.model.api.data.blueprint.ModelBoundingBox\nimport kr.toxicity.model.api.event.hitbox.*\nimport kr.toxicity.model.api.mount.MountController\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.HitBoxListener\nimport kr.toxicity.model.api.nms.ModelInteractionHand\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.impl.fabric.*\nimport kr.toxicity.model.impl.fabric.world.damagesource.ModelDamageSourceImpl\nimport kr.toxicity.model.util.CONFIG\nimport net.minecraft.core.particles.DustParticleOptions\nimport net.minecraft.network.protocol.game.ServerboundInteractPacket\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.damagesource.DamageSource\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.*\nimport net.minecraft.world.entity.ai.attributes.Attributes\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.entity.projectile.Projectile\nimport net.minecraft.world.entity.projectile.ProjectileDeflection\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.level.BlockGetter\nimport net.minecraft.world.phys.AABB\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.awt.Color\nimport java.util.*\n\nclass HitBoxEntityImpl(\n    private val source: ModelBoundingBox,\n    private val bone: RenderedBone,\n    private var listener: HitBoxListener,\n    private val delegate: Entity,\n    private var mountController: MountController\n) :\n    AbstractArmorStand(EntityType.ARMOR_STAND, delegate.level()),\n    HitBox\n{\n    private val posCache = BoneMovement()\n    private var initialized = false\n    private var jumpDelay = 0\n    private var mounted = false\n    private var noGravity = if (delegate is Mob) delegate.isNoAi else delegate.isNoGravity\n    private var forceDismount = false\n    private var onFly = false\n\n    fun calculateDimensions(): EntityDimensions {\n        val width = (source.x() + source.z()) * 0.5\n        val height = source.y()\n\n        return EntityDimensions(\n            width.toFloat(),\n            height.toFloat(),\n            delegate.eyeHeight,\n            EntityAttachments.createDefault(0F, 0F),\n            false\n        ).scale(\n            bone.hitBoxScale()\n        )\n    }\n\n    private val interaction by lazy {\n        InteractionEntityImpl(this)\n    }\n\n    private val applier = InsideBlockEffectApplier.StepBasedCollector()\n\n    init {\n        snapTo(delegate.position())\n        isInvisible = true\n        isSilent = true\n        initialized = true\n        level().addFreshEntity(this)\n\n        interaction.snapTo(delegate.position())\n        interaction.startRiding(this)\n        level().addFreshEntity(interaction)\n        listener.handle(HitBoxCreateEvent(this))\n    }\n\n    private fun initialSetup() {\n        if (mounted) {\n            mounted = false\n            if (delegate is Mob) delegate.isNoAi = noGravity\n            else delegate.isNoGravity = noGravity\n        }\n    }\n\n    override fun id(): Int = id\n\n    override fun uuid(): UUID = uuid\n\n    override fun source(): PlatformEntity = delegate.wrap()\n\n    override fun positionSource(): RenderedBone = bone\n\n    override fun forceDismount(): Boolean = forceDismount\n\n    override fun mountController(): MountController = mountController\n\n    override fun hasMountDriver(): Boolean = controllingPassenger != null\n\n    override fun mountController(controller: MountController) {\n        this.mountController = controller\n    }\n\n    override fun relativePosition(): Vector3f {\n        return bone.hitBoxPosition(posCache).add(\n            delegate.x.toFloat(),\n            delegate.y.toFloat(),\n            delegate.z.toFloat()\n        )\n    }\n\n    override fun listener(): HitBoxListener = listener\n\n    override fun listener(listener: HitBoxListener) {\n        this.listener = listener\n    }\n\n    override fun getItemBySlot(slot: EquipmentSlot): ItemStack = ItemStack.EMPTY\n\n    override fun setItemSlot(slot: EquipmentSlot, stack: ItemStack) = Unit\n\n    override fun getMainArm(): HumanoidArm = HumanoidArm.RIGHT\n\n    override fun mount(entity: PlatformEntity) {\n        if (controllingPassenger != null) {\n            return\n        }\n\n        entity.unwarp().startRiding(this, true, true)\n        if (mountController.canControl()) {\n            mounted = true\n            noGravity = delegate.isNoGravity\n        }\n\n        listener.handle(HitBoxMountEvent(this, entity))\n    }\n\n    override fun dismount(entity: PlatformEntity) {\n        forceDismount = true\n\n        entity.unwarp().stopRiding()\n        listener.handle(HitBoxDismountEvent(this, entity))\n\n        forceDismount = false\n    }\n\n    override fun dismountAll() {\n        forceDismount = true\n\n        interaction.passengers.forEach { passenger ->\n            passenger.stopRiding()\n            listener.handle(HitBoxDismountEvent(this, passenger.wrap()))\n        }\n\n        forceDismount = false\n    }\n\n    override fun setRemainingFireTicks(remainingFireTicks: Int) {\n        delegate.remainingFireTicks = remainingFireTicks\n    }\n\n    override fun getRemainingFireTicks(): Int {\n        return delegate.remainingFireTicks\n    }\n\n    override fun knockback(d: Double, e: Double, f: Double) {\n        (delegate as? LivingEntity)?.knockback(d, e, f)\n    }\n\n    override fun push(pushingEntity: Entity) {\n        if (pushingEntity !== delegate) {\n            delegate.push(pushingEntity)\n        }\n    }\n\n    override fun canCollideWith(entity: Entity): Boolean {\n        return checkCollide(entity) && delegate.canCollideWith(entity)\n    }\n\n    private fun checkCollide(entity: Entity): Boolean {\n        return entity !== delegate &&\n            passengers.none { it === entity } &&\n            delegate.passengers.none { it === entity } &&\n            (entity !is HitBoxEntityImpl || entity.delegate !== delegate)\n    }\n\n    override fun getActiveEffects(): Collection<MobEffectInstance> {\n        return (delegate as? LivingEntity)?.activeEffects ?: emptyList()\n    }\n\n    override fun getControllingPassenger(): LivingEntity? {\n        return if (!mounted) {\n            null\n        } else {\n            interaction.firstPassenger as? LivingEntity ?: super.getControllingPassenger()\n        }\n    }\n\n    override fun onWalk(): Boolean = isWalking\n\n    private fun mountControl(player: ServerPlayer) {\n        if (delegate !is LivingEntity ||\n            !mountController.canFly() && delegate.isFallFlying\n        ) {\n            return\n        }\n\n        val travelVector = Vec3(\n            delegate.xxa.toDouble(),\n            delegate.yya.toDouble(),\n            delegate.zza.toDouble()\n        )\n        updateFlyStatus(player)\n\n        val riddenInput = rideInput(player, travelVector)\n        if (riddenInput.length() > 0.01) {\n            delegate.yRot = player.yRot\n            if (onFly) {\n                delegate.yHeadRot = player.yRot\n            }\n\n            val movementVector = Vec3(\n                riddenInput.x.toDouble(),\n                riddenInput.y.toDouble(),\n                riddenInput.z.toDouble()\n            )\n            delegate.move(MoverType.SELF, movementVector)\n        }\n\n        if (!onFly &&\n            mountController.canJump() &&\n            (delegate.horizontalCollision || player.lastClientInput.jump()) &&\n            (delegate.deltaMovement.y + delegate.gravity) in 0.0..0.01 && jumpDelay == 0\n        ) {\n            jumpDelay = 10\n            delegate.jumpFromGround()\n        }\n    }\n\n    private fun movementSpeed(): Float {\n        if (delegate !is LivingEntity) {\n            return 0.0f\n        }\n\n        val attribute = delegate.getAttribute(Attributes.MOVEMENT_SPEED) ?: return 0.0f\n        val attributeValue = attribute.value.toFloat()\n\n        if (onFly || shouldDiscardFriction()) {\n            return attributeValue\n        }\n\n        return level()\n            .getBlockState(blockPosBelowThatAffectsMyMovement)\n            .block\n            .getFriction() * attributeValue\n    }\n\n    private fun updateFlyStatus(player: ServerPlayer) {\n        val fly = (player.lastClientInput.jump() && mountController.canFly()) ||\n            noGravity ||\n            onFly\n\n        if (delegate is Mob) {\n            delegate.isNoAi = fly\n        } else {\n            delegate.isNoGravity = fly\n        }\n\n        onFly = fly && !delegate.onGround()\n        if (onFly) {\n            delegate.resetFallDistance()\n        }\n    }\n\n    private fun rideInput(player: ServerPlayer, travelVector: Vec3): Vector3f {\n        return mountController.move(\n            if (onFly) {\n                MountController.MoveType.FLY\n            } else {\n                MountController.MoveType.DEFAULT\n            },\n            player.connection.wrap(),\n            (delegate as LivingEntity).wrap(),\n            Vector3f(\n                player.xMovement(),\n                player.yMovement(),\n                player.zMovement()\n            ),\n            Vector3f(\n                travelVector.x.toFloat(),\n                travelVector.y.toFloat(),\n                travelVector.z.toFloat()\n            )\n        )\n            .mul(movementSpeed())\n            .rotateY(-Math.toRadians(player.yRot.toDouble()).toFloat())\n    }\n\n    override fun tick() {\n        delegate.removalReason?.let { removalReason ->\n            if (!isRemoved) {\n                remove(removalReason)\n            }\n\n            return\n        }\n\n        val controller = controllingPassenger\n        if (jumpDelay > 0) {\n            jumpDelay--\n        }\n\n        interaction.isInvisible = delegate.isInvisible\n        if (controller is ServerPlayer && !isDeadOrDying && mountController.canControl()) {\n            if (delegate is Mob) {\n                delegate.navigation.stop()\n            }\n\n            mountControl(controller)\n        } else {\n            initialSetup()\n        }\n\n        yRot = bone.rotation().y\n        yHeadRot = yRot\n        yBodyRot = yRot\n\n        val pos = relativePosition()\n        val minusHeight = source.minY * bone.hitBoxScale()\n        setPos(\n            pos.x.toDouble(),\n            pos.y.toDouble() + minusHeight,\n            pos.z.toDouble()\n        )\n\n        BlockGetter.forEachBlockIntersectedBetween(\n            oldPosition(),\n            position(),\n            boundingBox\n        ) { pos, _ ->\n            level().getBlockState(pos).entityInside(\n                level(),\n                pos,\n                delegate,\n                applier,\n                true\n            )\n            true\n        }\n        applier.applyAndClear(delegate)\n\n        if (isInLava) {\n            delegate.lavaHurt()\n        }\n\n        firstTick = false\n        listener.sync(this)\n    }\n\n    override fun remove(reason: RemovalReason) {\n        initialSetup()\n\n        listener.handle(HitBoxRemoveEvent(this))\n        interaction.remove(reason)\n\n        super.remove(reason)\n    }\n\n    override fun hasExactlyOnePlayerPassenger(): Boolean = false\n\n    override fun isDeadOrDying(): Boolean = delegate is LivingEntity && delegate.isDeadOrDying\n\n    override fun hide(player: PlatformPlayer) {\n        TODO(\"with mixin\")\n    }\n\n    override fun show(player: PlatformPlayer) {\n        TODO(\"with mixin\")\n    }\n\n\n    override fun interact(player: Player, hand: InteractionHand, vec: Vec3): InteractionResult {\n        if (player === delegate) {\n            return InteractionResult.FAIL\n        }\n        val serverPlayer = player as ServerPlayer\n\n        val interact = HitBoxInteractAtEvent(\n            serverPlayer.connection.wrap(),\n            this,\n            when (hand) {\n                InteractionHand.MAIN_HAND -> ModelInteractionHand.RIGHT\n                InteractionHand.OFF_HAND -> ModelInteractionHand.LEFT\n            },\n            vec.toVector3f()\n        )\n        if (!listener.handle(interact)) return InteractionResult.FAIL\n\n        serverPlayer.connection.handleInteract(\n            ServerboundInteractPacket(\n                delegate.id,\n                hand,\n                vec,\n                player.isShiftKeyDown\n            )\n        )\n        return InteractionResult.SUCCESS\n    }\n\n    override fun addEffect(effectInstance: MobEffectInstance, entity: Entity?): Boolean {\n        return if (entity == delegate) {\n            false\n        } else {\n            delegate is LivingEntity &&\n                delegate.addEffect(effectInstance, entity)\n        }\n    }\n\n    override fun hurtServer(world: ServerLevel, source: DamageSource, amount: Float): Boolean {\n        if (delegate == source.entity ||\n            delegate.isInvulnerable ||\n            source.entity == controllingPassenger && !mountController.canBeDamagedByRider()\n        ) {\n            return false\n        }\n\n        val sourceImpl = ModelDamageSourceImpl(source)\n        val event = HitBoxDamagedEvent(this, sourceImpl, amount)\n        if (!listener.handle(event)) return false\n\n        return delegate is LivingEntity &&\n            delegate.hurtServer(world, source, event.damage)\n    }\n\n    override fun deflection(projectile: Projectile): ProjectileDeflection {\n        if (projectile.owner?.uuid == delegate.uuid) {\n            return ProjectileDeflection.NONE\n        }\n\n        return (delegate as? LivingEntity)?.deflection(projectile)\n            ?: ProjectileDeflection.NONE\n    }\n\n    override fun getHealth(): Float {\n        return (delegate as? LivingEntity)?.health ?: super.getHealth()\n    }\n\n    override fun makeBoundingBox(vec3: Vec3): AABB {\n        if (!initialized) {\n            return super.makeBoundingBox(vec3)\n        }\n\n        val scale = bone.hitBoxScale()\n        val boundingBox = AABB(\n            vec3.x + source.minX * scale,\n            vec3.y,\n            vec3.z + source.minZ * scale,\n            vec3.x + source.maxX * scale,\n            vec3.y + source.y() * scale,\n            vec3.z + source.maxZ * scale\n        )\n\n        if (CONFIG.debug().has(DebugConfig.DebugOption.HITBOX)) {\n            val level = level() as ServerLevel\n            val particleOptions = DustParticleOptions(Color.RED.rgb, 1F)\n\n            level.sendParticles(\n                particleOptions,\n                true, true,\n                boundingBox.minX, boundingBox.minY, boundingBox.minZ,\n                1,\n                0.0, 0.0, 0.0,\n                1.0\n            )\n            level.sendParticles(\n                particleOptions,\n                true, true,\n                boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ,\n                1,\n                0.0, 0.0, 0.0,\n                1.0\n            )\n        }\n\n        return boundingBox\n    }\n\n    override fun getDefaultDimensions(pose: Pose): EntityDimensions {\n        return if (initialized) {\n            calculateDimensions()\n        } else {\n            super.getDefaultDimensions(pose)\n        }\n    }\n\n    override fun removeHitBox() {\n        source().task {\n            dismountAll()\n            remove((delegate as? LivingEntity)?.removalReason ?: RemovalReason.KILLED)\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/InteractionEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.Interaction\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.phys.Vec3\n\nclass InteractionEntityImpl(val delegate: HitBoxEntityImpl) :\n    Interaction(EntityType.INTERACTION, delegate.level())\n{\n    override fun tick() {\n        delegate.calculateDimensions().let { dimensions ->\n            width = dimensions.width\n            height = dimensions.height\n        }\n\n        yRot = delegate.yRot\n        xRot = delegate.xRot\n        setSharedFlagOnFire(delegate.remainingFireTicks > 0)\n    }\n\n    override fun skipAttackInteraction(entity: Entity): Boolean {\n        if (entity !is Player) {\n            return false\n        }\n\n        entity.attack(delegate)\n        return true\n    }\n\n    override fun interact(player: Player, hand: InteractionHand, vec: Vec3): InteractionResult {\n        delegate.interact(player, hand, vec)\n        return InteractionResult.FAIL\n    }\n\n    override fun shouldBeSaved(): Boolean = false\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/ModelDisplayEntityImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport it.unimi.dsi.fastutil.ints.IntOpenHashSet\nimport kr.toxicity.model.api.entity.BaseEntity\nimport kr.toxicity.model.api.nms.DisplayTransformer\nimport kr.toxicity.model.api.nms.ModelDisplay\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformBillboard\nimport kr.toxicity.model.api.platform.PlatformItemStack\nimport kr.toxicity.model.api.platform.PlatformItemTransform\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.util.lock.SingleLock\nimport kr.toxicity.model.impl.fabric.manager.markDirty\nimport kr.toxicity.model.impl.fabric.network.pack\nimport kr.toxicity.model.impl.fabric.network.plusAssign\nimport kr.toxicity.model.impl.fabric.unwarp\nimport kr.toxicity.model.mixin.DisplayAccessor\nimport kr.toxicity.model.mixin.EntityAccessor\nimport kr.toxicity.model.mixin.ItemDisplayAccessor\nimport kr.toxicity.model.util.CONFIG\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.util.Brightness\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.item.ItemDisplayContext\nimport net.minecraft.world.item.ItemStack\nimport net.minecraft.world.item.Items\nimport org.joml.Vector3d\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicBoolean\n\nclass ModelDisplayEntityImpl(\n    private val pos: Vector3d,\n    val display: Display.ItemDisplay,\n    val yOffset: Double\n) :\n    ModelDisplay {\n    private val entityData: SynchedEntityData = display.entityData\n    private val entityDataLock: SingleLock = SingleLock()\n\n    private val forceGlow = AtomicBoolean()\n    private val forceInvisibility = AtomicBoolean()\n\n    private val oldPos = Vector3d(pos)\n\n    override fun id(): Int = display.id\n\n    override fun uuid(): UUID = display.uuid\n\n    override fun rotate(rotation: ModelRotation, bundler: PacketBundler) {\n        display.xRot = rotation.x\n        display.yRot = rotation.y\n        bundler += ClientboundMoveEntityPacket.Rot(\n            display.id,\n            rotation.packedY(),\n            rotation.packedX(),\n            display.onGround()\n        )\n    }\n\n    override fun invisible(invisible: Boolean) {\n        if (forceInvisibility.compareAndSet(!invisible, invisible)) {\n            entityData.packDirty()\n            entityDataLock.accessToLock {\n                entityData.markDirty(ItemDisplayAccessor.`bettermodel$getDataItemStackId`())\n            }\n        }\n    }\n\n    override fun syncPotionEffect(entity: BaseEntity) {\n        val beforeInvisible = display.isInvisible\n        val afterInvisible = entity.invisible()\n\n        entityDataLock.accessToLock {\n            display.setGlowingTag(entity.glow() || forceGlow.get())\n            if (CONFIG.followMobInvisibility() && beforeInvisible != afterInvisible) {\n                display.isInvisible = afterInvisible\n                entityData.markDirty(ItemDisplayAccessor.`bettermodel$getDataItemStackId`())\n            }\n        }\n    }\n\n    override fun syncPosition(location: PlatformLocation) {\n        oldPos.set(pos)\n        pos.set(location.x(), location.y(), location.z())\n    }\n\n    override fun spawn(showItem: Boolean, bundler: PacketBundler) {\n        bundler += createAddPacket()\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    override fun teleport(location: PlatformLocation, bundler: PacketBundler) {\n        display.snapTo(\n            location.x(),\n            location.y(),\n            location.z(),\n            location.yaw(),\n            0F\n        )\n\n        bundler += ClientboundTeleportEntityPacket.teleport(\n            display.id,\n            PositionMoveRotation.of(display),\n            emptySet(),\n            display.onGround()\n        )\n    }\n\n    override fun sendPosition(adapter: BaseEntity, bundler: PacketBundler) {\n        val handle = adapter.handle() as Entity\n        if (oldPos.distanceSquared(pos) < 1e-8) return\n        bundler += ClientboundEntityPositionSyncPacket(\n            display.id,\n            PositionMoveRotation.of(handle),\n            handle.onGround()\n        )\n    }\n\n    override fun display(transform: PlatformItemTransform) {\n        entityDataLock.accessToLock {\n            display.itemTransform = ItemDisplayContext.BY_ID.apply(transform.ordinal)\n        }\n    }\n\n    override fun moveDuration(duration: Int) {\n        entityDataLock.accessToLock {\n            entityData[DisplayAccessor.`bettermodel$getDataPosRotInterpolationDurationId`()] = duration\n        }\n    }\n\n    override fun item(itemStack: PlatformItemStack) {\n        entityDataLock.accessToLock {\n            display.itemStack = itemStack.clone().unwarp()\n        }\n    }\n\n    override fun brightness(block: Int, sky: Int) {\n        entityDataLock.accessToLock {\n            display.brightnessOverride = if (block < 0 && sky < 0) null else Brightness(\n                block,\n                sky\n            )\n        }\n    }\n\n    override fun viewRange(range: Float) {\n        entityDataLock.accessToLock {\n            display.viewRange = range\n        }\n    }\n\n    override fun shadowRadius(radius: Float) {\n        entityDataLock.accessToLock {\n            display.shadowRadius = radius\n        }\n    }\n\n    override fun glow(glow: Boolean) {\n        if (!forceGlow.compareAndSet(!glow, glow)) return\n        entityDataLock.accessToLock {\n            display.setGlowingTag(display.isCurrentlyGlowing || glow)\n        }\n    }\n\n    override fun glowColor(glowColor: Int) {\n        entityDataLock.accessToLock {\n            display.glowColorOverride = glowColor\n        }\n    }\n\n    override fun billboard(billboard: PlatformBillboard) {\n        entityDataLock.accessToLock {\n            display.billboardConstraints = Display.BillboardConstraints.BY_ID.apply(billboard.ordinal)\n        }\n    }\n\n    override fun createTransformer(): DisplayTransformer = DisplayTransformerImpl(display)\n\n    override fun invisible(): Boolean {\n        return entityDataLock.accessToLock {\n            display.isInvisible ||\n                forceInvisibility.get() ||\n                display.itemStack.`is`(Items.AIR)\n        }\n    }\n\n    override fun sendDirtyEntityData(bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                clean = true,\n                itemFilter = { it.isDirty },\n                valueFilter = { ACCESSOR_IDS.contains(it.id) }\n            )\n        }?.markVisible(!invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    override fun sendEntityData(showItem: Boolean, bundler: PacketBundler) {\n        entityDataLock.accessToLock {\n            entityData.pack(\n                valueFilter = { ACCESSOR_IDS.contains(it.id) }\n            )\n        }?.markVisible(showItem && !invisible())?.run {\n            bundler += ClientboundSetEntityDataPacket(display.id, this)\n        }\n    }\n\n    private fun List<SynchedEntityData.DataValue<*>>.markVisible(showItem: Boolean) = map {\n        if (it.id == ItemDisplayAccessor.`bettermodel$getDataItemStackId`().id) SynchedEntityData.DataValue(\n            it.id,\n            EntityDataSerializers.ITEM_STACK,\n            if (showItem) display.itemStack else ItemStack.EMPTY\n        ) else it\n    }\n\n    private fun createAddPacket() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        pos.x,\n        pos.y + yOffset,\n        pos.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket = ClientboundRemoveEntitiesPacket(display.id)\n\n    companion object {\n        private val ACCESSOR_IDS by lazy {\n            IntOpenHashSet().apply {\n                setOf(\n                    EntityAccessor.`bettermodel$getDataSharedFlagsId`(),\n\n                    DisplayAccessor.`bettermodel$getDataPosRotInterpolationDurationId`(),\n\n                    // index: 7 ~ last\n                    DisplayAccessor.`bettermodel$getDataBillboardRenderConstraintsId`(),\n                    DisplayAccessor.`bettermodel$getDataBrightnessOverrideId`(),\n                    DisplayAccessor.`bettermodel$getDataViewRangeId`(),\n                    DisplayAccessor.`bettermodel$getDataShadowRadiusId`(),\n                    DisplayAccessor.`bettermodel$getDataShadowStrengthId`(),\n                    DisplayAccessor.`bettermodel$getDataWidthId`(),\n                    DisplayAccessor.`bettermodel$getDataHeightId`(),\n                    DisplayAccessor.`bettermodel$getDataGlowColorOverrideId`(),\n\n                    // all\n                    ItemDisplayAccessor.`bettermodel$getDataItemStackId`(),\n                    ItemDisplayAccessor.`bettermodel$getDataItemDisplayId`()\n                ).mapTo(this) {\n                    it.id\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/ModelNametagImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport com.mojang.math.Transformation\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bone.BoneMovement\nimport kr.toxicity.model.api.bone.BonePosition\nimport kr.toxicity.model.api.bone.RenderedBone\nimport kr.toxicity.model.api.mod.BetterModelMod\nimport kr.toxicity.model.api.nms.ModelNametag\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.EntityUtil\nimport kr.toxicity.model.impl.fabric.chat.asVanilla\nimport kr.toxicity.model.impl.fabric.network.bundlerOf\nimport kr.toxicity.model.impl.fabric.network.bundlerOfNotNull\nimport kr.toxicity.model.impl.fabric.network.pack\nimport kr.toxicity.model.impl.fabric.network.plusAssign\nimport kr.toxicity.model.mixin.DisplayAccessor\nimport net.minecraft.network.chat.Component\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket\nimport net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.PositionMoveRotation\nimport net.minecraft.world.phys.Vec3\nimport org.joml.Vector3f\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\nclass ModelNametagImpl(\n    private val bone: RenderedBone\n) : ModelNametag {\n    private companion object {\n        private val emptyVector = Vector3f()\n        private val emptyTransformation = Transformation(\n            Vector3f(-1F / 40F, -0.2F - 1F / 40F, 0F),\n            null,\n            null,\n            null\n        )\n    }\n\n    private val viewedPlayer = ConcurrentHashMap.newKeySet<UUID>()\n    private val display = Display.TextDisplay(\n        EntityType.TEXT_DISPLAY,\n        BetterModelMod.platform().server().overworld()\n    ).apply {\n        entityData[DisplayAccessor.`bettermodel$getDataPosRotInterpolationDurationId`()] = 3\n        setTransformation(emptyTransformation)\n        billboardConstraints = Display.BillboardConstraints.CENTER\n    }\n    private val posCache = BoneMovement()\n    private var alwaysVisible = false\n    private var location = BetterModel.platform().adapter().zero()\n\n    override fun component(component: net.kyori.adventure.text.Component?) {\n        display.text = component?.asVanilla() ?: Component.empty()\n    }\n\n    override fun teleport(location: PlatformLocation) {\n        this.location = location\n    }\n\n    override fun alwaysVisible(alwaysVisible: Boolean) {\n        this.alwaysVisible = alwaysVisible\n    }\n\n    override fun send(player: PlatformPlayer) {\n        if (display.text == Component.empty()) return\n        val hb = bone.group.hitBoxPoint\n        val pos = bone.worldPosition(BonePosition(emptyVector, hb, player.uuid()), posCache)\n        display.snapTo(Vec3(\n            location.x() + pos.x,\n            location.y() + pos.y,\n            location.z() + pos.z\n        ))\n        val inPoint = alwaysVisible || EntityUtil.isCustomNameVisible(player.location(), location)\n        when {\n            inPoint && viewedPlayer.add(player.uuid()) -> bundlerOfNotNull(\n                addPacket,\n                display.entityData.pack()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            inPoint -> bundlerOfNotNull(\n                ClientboundEntityPositionSyncPacket(display.id, PositionMoveRotation.of(display), false),\n                display.entityData.packDirty()?.let {\n                    ClientboundSetEntityDataPacket(display.id, it)\n                }\n            )\n            viewedPlayer.remove(player.uuid()) -> bundlerOf(removePacket)\n            else -> null\n        }?.send(player)\n    }\n\n    override fun remove(bundler: PacketBundler) {\n        bundler += removePacket\n    }\n\n    private val addPacket get() = ClientboundAddEntityPacket(\n        display.id,\n        display.uuid,\n        display.x,\n        display.y,\n        display.z,\n        display.xRot,\n        display.yRot,\n        display.type,\n        0,\n        display.deltaMovement,\n        display.yHeadRot.toDouble()\n    )\n\n    private val removePacket get() = ClientboundRemoveEntitiesPacket(display.id)\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/PlayerChannelHandlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport io.netty.channel.ChannelDuplexHandler\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.channel.ChannelPromise\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.entity.BasePlayer\nimport kr.toxicity.model.api.mod.BetterModelMod\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.nms.PlayerChannelHandler\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.TrackerUpdateAction\nimport kr.toxicity.model.impl.fabric.network.*\nimport kr.toxicity.model.impl.fabric.wrap\nimport kr.toxicity.model.mixin.DisplayAccessor\nimport kr.toxicity.model.mixin.EntityAccessor\nimport kr.toxicity.model.util.CONFIG\nimport kr.toxicity.model.util.PLATFORM\nimport net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking\nimport net.minecraft.network.Connection\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.*\nimport net.minecraft.server.level.ServerLevel\nimport net.minecraft.server.network.ServerPlayerConnection\nimport net.minecraft.world.entity.Display\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EntityType\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.item.ItemStack\nimport java.util.stream.IntStream\n\nclass PlayerChannelHandlerImpl(\n    private val connection: ServerPlayerConnection\n) : PlayerChannelHandler, ChannelDuplexHandler() {\n    private val player get() = connection.player\n    private val uuid = player.uuid\n\n    private val basePlayer = PLATFORM.nms().adapt(connection.wrap())\n\n    init {\n        val pipeline = connection.player.connection.connection.channel.pipeline()\n        pipeline.addBefore(pipeline.first { it.value is Connection }.key, INJECT_NAME, this)\n    }\n\n    override fun close() {\n        val channel = connection.player.connection.connection.channel\n        channel.eventLoop().submit {\n            channel.pipeline().remove(INJECT_NAME)\n        }\n    }\n\n    override fun base(): BasePlayer = basePlayer\n    override fun isModEnabled(): Boolean = ServerPlayNetworking.getReceived(player).contains(ModAnimationBundlerImpl.IDENTIFIER)\n\n    private val playerModel get() = connection.player.id.toRegistry()\n\n    private fun getEntity(id: Int, level: ServerLevel) = level.getEntity(id)\n\n    private fun getPlayerEntity(id: Int) = getEntity(id, connection.player.level())\n\n    private fun Entity.toRegistry() = BetterModel.registryOrNull(uuid)\n\n    private inline fun Int.toRegistry(ifHitBox: (Entity) -> Unit = {}) =\n        (EntityTrackerRegistry.registry(this) ?: getPlayerEntity(this)?.let {\n            if (it is HitBox) ifHitBox(it)\n            it.toRegistry()\n        })?.takeIf {\n            it.isSpawned(player.uuid)\n        }\n\n    override fun sendEntityData(registry: EntityTrackerRegistry) {\n        val handle = registry.entity().handle() as? Entity ?: return\n        val list = bundlerOf(\n            ClientboundSetPassengersPacket(handle)\n        )\n\n        handle.entityData.pack(\n            valueFilter = { it.id == EntityAccessor.`bettermodel$getDataSharedFlagsId`().id }\n        )?.let {\n            list.add(ClientboundSetEntityDataPacket(handle.id, it))\n        }\n\n        if (handle is LivingEntity) handle.toEquipmentPacket()?.let {\n            list.add(it)\n        }\n\n        list.send(connection.wrap())\n    }\n\n    private fun Entity.toFakeAddPacket() = ClientboundAddEntityPacket(\n        id,\n        uuid,\n        x, y, z,\n        xRot, yRot,\n        EntityType.ITEM_DISPLAY,\n        0,\n        deltaMovement,\n        yHeadRot.toDouble()\n    )\n\n    private fun <T : ClientGamePacketListener> Packet<in T>.handle(): Packet<in T>? {\n        when (this) {\n            is ClientboundBundlePacket -> {\n                return if ((this as BetterModelBundlePacket).`bettermodel$isBetterModelPacket`()) this else ClientboundBundlePacket(subPackets().mapNotNull {\n                    it.handle()\n                })\n            }\n\n            is ClientboundAddEntityPacket -> {\n                val entity = getPlayerEntity(id) ?: return this\n                if (entity is HitBox) return entity.toFakeAddPacket()\n                val wrap = entity.wrap()\n                BetterModel.registry(wrap).ifPresent {\n                    wrap.taskLater(1) {\n                        it.spawn(connection.wrap())\n                    }\n                }\n            }\n\n            is ClientboundRemoveEntitiesPacket -> {\n                entityIds\n                    .asSequence()\n                    .mapNotNull map@{\n                        it.toRegistry {\n                            return@map null\n                        }\n                    }\n                    .forEach {\n                        it.remove()\n                    }\n            }\n\n            is ClientboundSetPassengersPacket -> {\n                vehicle.toRegistry()?.let { registry ->\n                    return registry.mountPacket(\n                        entity = registry.entity().handle() as? Entity ?: return this,\n                        passengerIds = IntStream.of(*passengers)\n                    )\n                }\n            }\n\n            is ClientboundUpdateAttributesPacket if getPlayerEntity(entityId) is HitBox -> return null\n            is ClientboundSetEntityDataPacket -> id.toRegistry {\n                return ClientboundSetEntityDataPacket(id, hitBoxData)\n            }?.let { registry ->\n                return toRegistryDataPacket(uuid, registry)\n            }\n\n            is ClientboundSetEquipmentPacket -> entity.toRegistry {\n                return null\n            }?.let { registry ->\n                if (registry.hideOption(uuid).equipment()) {\n                    (registry.entity().handle() as? LivingEntity)\n                        ?.toEquipmentPacket { ItemStack.EMPTY }\n                        ?.let { packet ->\n                            return packet\n                        }\n                }\n            }\n\n            is ClientboundRespawnPacket -> playerModel?.let {\n                bundlerOf(it.mountPacket(connection.player)).send(connection.wrap())\n            }\n\n            is ClientboundContainerSetSlotPacket if isEquipment(connection.player) && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                return ClientboundContainerSetSlotPacket(containerId, stateId, slot, ItemStack.EMPTY)\n            }\n\n            is ClientboundContainerSetContentPacket if containerId == 0 && playerModel?.hideOption(uuid)?.equipment() == true -> {\n                return ClientboundContainerSetContentPacket(\n                    containerId,\n                    stateId,\n                    items.apply {\n                        eachEquipmentSlots { set(it, ItemStack.EMPTY) }\n                        set(connection.player.hotbarSlot, ItemStack.EMPTY)\n                    },\n                    carriedItem\n                )\n            }\n        }\n        return this\n    }\n\n    override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {\n        super.write(ctx, if (msg is Packet<*>) msg.handle() ?: return else msg, promise)\n    }\n\n    override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {\n        fun EntityTrackerRegistry.updatePlayerLimb() = BetterModel.platform().scheduler().asyncTaskLater(1) {\n            if (isClosed) return@asyncTaskLater\n            player.containerMenu.sendAllDataToRemote()\n            trackers().forEach { tracker ->\n                tracker.update(TrackerUpdateAction.itemMapping()) { bone ->\n                    !bone.itemMapper.fixed()\n                }\n            }\n        }\n        when (msg) {\n            is ServerboundSetCarriedItemPacket -> {\n                playerModel?.let { registry ->\n                    if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                    if (CONFIG.cancelPlayerModelInventory()) {\n                        connection.send(ClientboundSetHeldSlotPacket(player.inventory.selectedSlot))\n                        return\n                    }\n                    registry.updatePlayerLimb()\n                }\n            }\n\n            is ServerboundPlayerActionPacket -> {\n                playerModel?.let { registry ->\n                    if (!registry.hideOption(uuid).equipment()) return super.channelRead(ctx, msg)\n                    if (CONFIG.cancelPlayerModelInventory()) return\n                    registry.updatePlayerLimb()\n                }\n            }\n        }\n        super.channelRead(ctx, msg)\n    }\n\n    private fun EntityTrackerRegistry.remove() {\n        remove(connection.wrap())\n    }\n\n    companion object {\n        private const val INJECT_NAME = \"bettermodel_channel_handler\"\n\n        private val hitBoxData by lazy {\n            Display.ItemDisplay(\n                EntityType.ITEM_DISPLAY,\n                (PLATFORM as BetterModelMod).server().overworld()\n            ).run {\n                entityData.set(DisplayAccessor.`bettermodel$getDataPosRotInterpolationDurationId`(), 3)\n                entityData.nonDefaultValues!!\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/ProfiledImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport kr.toxicity.model.api.armor.PlayerArmor\nimport kr.toxicity.model.api.nms.Profiled\nimport kr.toxicity.model.api.player.PlayerSkinParts\nimport kr.toxicity.model.api.profile.ModelProfile\n\ninternal class ProfiledImpl(\n    private val playerArmor: PlayerArmor,\n    private val modelProfile: () -> ModelProfile,\n    private val playerSkinParts: () -> PlayerSkinParts\n) : Profiled {\n\n    override fun profile(): ModelProfile = modelProfile()\n    override fun armors(): PlayerArmor = playerArmor\n    override fun skinParts(): PlayerSkinParts = playerSkinParts()\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/entity/TransformationData.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.entity\n\nimport kr.toxicity.model.api.nms.AnimationBundler\nimport kr.toxicity.model.api.util.MathUtil\nimport kr.toxicity.model.impl.fabric.network.plusAssign\nimport kr.toxicity.model.mixin.DisplayAccessor\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\nclass TransformationData {\n    private var duration = 0\n\n    private val durationDataValue\n        get() = SynchedEntityData.DataValue(\n            DisplayAccessor.`bettermodel$getDataTransformationInterpolationDurationId`().id,\n            DisplayAccessor.`bettermodel$getDataTransformationInterpolationDurationId`().serializer,\n            duration\n        )\n\n    private val translation = Item(\n        Vector3f(),\n        DisplayAccessor.`bettermodel$getDataTranslationId`(),\n        { a, b ->\n            // unchecked cast\n            MathUtil.isSimilar(a, b)\n        },\n        { a, b ->\n            // unchecked cast\n            (a as Vector3f).set(b)\n        }\n    )\n\n    private val scale = Item(\n        Vector3f(),\n        DisplayAccessor.`bettermodel$getDataScaleId`(),\n        { a, b ->\n            // unchecked cast\n            MathUtil.isSimilar(a, b)\n        },\n        { a, b ->\n            // unchecked cast\n            (a as Vector3f).set(b)\n        }\n    )\n\n    private val rotation = Item(\n        Quaternionf(),\n        DisplayAccessor.`bettermodel$getDataLeftRotationId`(),\n        { a, b ->\n            // unchecked cast\n            MathUtil.isSimilar(a as Quaternionf, b as Quaternionf)\n        },\n        { a, b ->\n            // unchecked cast\n            (a as Quaternionf).set(b)\n        }\n    )\n\n    fun packDirty(entityId: Int, dest: AnimationBundler) {\n        val i = translation.cleanIndex + scale.cleanIndex + rotation.cleanIndex\n        if (i == 0) return\n        dest.standard += ClientboundSetEntityDataPacket(entityId, buildList(i + 2) {\n            add(INTERPOLATION_DELAY_VALUE)\n            translation.value?.let { add(it) }\n            rotation.value?.let { add(it) }\n            scale.value?.let { add(it) }\n            add(durationDataValue)\n        })\n    }\n\n    fun transform(\n        duration: Int,\n        translation: Vector3f,\n        scale: Vector3f,\n        rotation: Quaternionf\n    ) {\n        this.duration = duration\n        this.translation.set(translation)\n        this.scale.set(scale)\n        this.rotation.set(rotation)\n    }\n\n    fun pack(): List<SynchedEntityData.DataValue<*>> {\n        return listOf(\n            INTERPOLATION_DELAY_VALUE,\n            durationDataValue,\n            translation.forceValue,\n            scale.forceValue,\n            rotation.forceValue\n        )\n    }\n\n    private class Item<T : Any>(\n        initialValue: T,\n        private val accessor: EntityDataAccessor<T>,\n        private val dirtyChecker: (T, T) -> Boolean,\n        private val setter: (T, T) -> Unit\n    ) {\n        private val _value: T = initialValue\n        private var _dirty = false\n\n        val dirty\n            get() = _dirty\n\n        val cleanIndex\n            get() = if (dirty) 1 else 0\n\n        val value\n            get() = if (_dirty) {\n                _dirty = false\n                forceValue\n            } else {\n                null\n            }\n\n        val forceValue\n            get() = SynchedEntityData.DataValue(\n                accessor.id,\n                accessor.serializer,\n                _value\n            )\n\n        fun set(other: T) {\n            if (dirtyChecker(_value, other)) {\n                return\n            }\n\n            _dirty = true\n            setter(_value, other)\n        }\n    }\n\n    companion object {\n        private val INTERPOLATION_DELAY_VALUE = DisplayAccessor.`bettermodel$getDataTransformationInterpolationStartDeltaTicksId`()\n            .let { accessor ->\n                SynchedEntityData.DataValue(accessor.id, accessor.serializer, 0)\n            }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/events/ServerEntityDismountCallback.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.events\n\nimport net.fabricmc.fabric.api.event.EventFactory\nimport net.minecraft.world.entity.Entity\n\nfun interface ServerEntityDismountCallback {\n    fun onDismount(passenger: Entity, vehicle: Entity): Boolean\n\n    companion object {\n        @JvmField\n        val EVENT = EventFactory.createArrayBacked(ServerEntityDismountCallback::class.java) { callbacks ->\n            { passenger, vehicle ->\n                callbacks.all { it.onDismount(passenger, vehicle) }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/events/ServerLivingEntityJumpCallback.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.events\n\nimport net.fabricmc.fabric.api.event.EventFactory\nimport net.minecraft.world.entity.LivingEntity\n\nfun interface ServerLivingEntityJumpCallback {\n    fun onJump(entity: LivingEntity)\n\n    companion object {\n        @JvmField\n        val EVENT = EventFactory.createArrayBacked(ServerLivingEntityJumpCallback::class.java) { callbacks ->\n            { entity ->\n                callbacks.forEach { it.onJump(entity) }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/events/ServerMobEffectLoadCallback.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.events\n\nimport net.fabricmc.fabric.api.event.EventFactory\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.LivingEntity\n\nfun interface ServerMobEffectLoadCallback {\n    fun onLoad(entity: LivingEntity, effect: MobEffectInstance)\n\n    companion object {\n        @JvmField\n        val EVENT = EventFactory.createArrayBacked(ServerMobEffectLoadCallback::class.java) { callbacks ->\n            { entity, effect ->\n                callbacks.forEach { it.onLoad(entity, effect) }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/events/ServerMobEffectUnloadCallback.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.events\n\nimport net.fabricmc.fabric.api.event.EventFactory\nimport net.minecraft.world.effect.MobEffectInstance\nimport net.minecraft.world.entity.LivingEntity\n\nfun interface ServerMobEffectUnloadCallback {\n    fun onUnload(entity: LivingEntity, effect: MobEffectInstance)\n\n    companion object {\n        @JvmField\n        val EVENT = EventFactory.createArrayBacked(ServerMobEffectUnloadCallback::class.java) { callbacks ->\n            { entity, effect ->\n                callbacks.forEach { it.onUnload(entity, effect) }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/manager/EntityManager.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.manager\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.mod.entity.BaseModEntity\nimport kr.toxicity.model.api.nms.HitBox\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.tracker.EntityTracker\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.api.tracker.Tracker\nimport kr.toxicity.model.api.tracker.TrackerExtraAnimation\nimport kr.toxicity.model.impl.fabric.events.ServerEntityDismountCallback\nimport kr.toxicity.model.impl.fabric.events.ServerLivingEntityJumpCallback\nimport kr.toxicity.model.impl.fabric.events.ServerMobEffectLoadCallback\nimport kr.toxicity.model.impl.fabric.events.ServerMobEffectUnloadCallback\nimport kr.toxicity.model.impl.fabric.wrap\nimport kr.toxicity.model.manager.GlobalManager\nimport kr.toxicity.model.manager.ReloadPipeline\nimport kr.toxicity.model.util.PLATFORM\nimport net.fabricmc.fabric.api.entity.event.v1.ServerEntityLevelChangeEvents\nimport net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents\nimport net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents\nimport net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents\nimport net.fabricmc.fabric.api.event.player.UseEntityCallback\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.InteractionHand\nimport net.minecraft.world.InteractionResult\nimport net.minecraft.world.effect.MobEffects\nimport net.minecraft.world.entity.Entity\n\nobject EntityManager : GlobalManager {\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) {\n        EntityTrackerRegistry.registries { registry ->\n            registry.reload()\n        }\n    }\n\n    override fun end() {\n        EntityTrackerRegistry.registries { registry ->\n            registry.save()\n            registry.close(Tracker.CloseReason.PLUGIN_DISABLE)\n        }\n    }\n\n    /*\n        EntityAddToWorldEvent ✅\n        EntityRemoveFromWorldEvent ✅\n        EntityJumpEvent ✅\n        EntityDamageEvent ✅\n        EntityDamageByEntityEvent ✅\n        EntityDeathEvent ✅\n        EntityDismountEvent ✅\n        EntityPotionEffectEvent ✅\n        EntityRemoveEvent ✅\n        EntitySpawnEvent ✅\n        PlayerChangedWorldEvent ✅\n        PlayerDeathEvent ✅\n        PlayerInteractAtEntityEvent ✅\n        PlayerInteractEntityEvent ✅\n        PlayerQuitEvent ✅\n        EntitiesUnloadEvent ❌ probably because ENTITY_UNLOAD contains this\n     */\n    override fun start() {\n        registerStateEvents()\n        registerLifecycleEvents()\n        registerCombatEvents()\n        registerInteractionEvents()\n    }\n\n    private fun registerStateEvents() {\n        // same as EntityPotionEffectEvent (added)\n        ServerMobEffectLoadCallback.EVENT.register { entity, instance ->\n            if (instance.effect == MobEffects.GLOWING ||\n                instance.effect == MobEffects.INVISIBILITY\n            ) {\n                entity.eachTracker { tracker ->\n                    tracker.updateBaseEntity()\n                }\n            }\n        }\n\n        // same as EntityPotionEffectEvent (removed)\n        ServerMobEffectUnloadCallback.EVENT.register { entity, instance ->\n            if (instance.effect == MobEffects.GLOWING ||\n                instance.effect == MobEffects.INVISIBILITY\n            ) {\n                entity.eachTracker { tracker ->\n                    tracker.updateBaseEntity()\n                }\n            }\n        }\n\n        // same as EntityDismountEvent\n        ServerEntityDismountCallback.EVENT.register { _, vehicle ->\n            vehicle !is HitBox ||\n                !(vehicle.mountController().canFly() || !vehicle.mountController().canDismountBySelf()) ||\n                vehicle.forceDismount()\n        }\n\n        // same as EntityJumpEvent\n        ServerLivingEntityJumpCallback.EVENT.register { entity ->\n            entity.eachTracker { tracker ->\n                tracker.animate(TrackerExtraAnimation.JUMP)\n            }\n        }\n    }\n\n    private fun registerLifecycleEvents() {\n        ServerEntityLevelChangeEvents.AFTER_ENTITY_CHANGE_LEVEL.register { oldEntity, newEntity, _, _ ->\n            BetterModel.registryOrNull(oldEntity.uuid)?.let { registry ->\n                (registry.entity() as BaseModEntity).entity(newEntity)\n            }\n        }\n\n        // same as EntityAddToWorldEvent, EntitySpawnEvent\n        ServerEntityEvents.ENTITY_LOAD.register { entity, _ ->\n            BetterModel.registryOrNull(entity.uuid)?.refresh()\n        }\n\n        // same as EntityRemoveFromWorldEvent, EntityRemoveEvent\n        ServerEntityEvents.ENTITY_UNLOAD.register { entity, _ ->\n            BetterModel.registryOrNull(entity.uuid)?.despawn()\n        }\n\n        // same as PlayerChangedWorldEvent\n        ServerEntityLevelChangeEvents.AFTER_PLAYER_CHANGE_LEVEL.register { player, _, _ ->\n            BetterModel.registryOrNull(player.uuid)?.let { registry ->\n                registry.despawn()\n                registry.refresh()\n            }\n        }\n\n        // same as PlayerQuitEvent\n        ServerPlayerEvents.LEAVE.register { player ->\n            val fabricPlayer = player.connection.wrap()\n            BetterModel.registryOrNull(fabricPlayer.uuid())?.close()\n\n            PLATFORM.scheduler().asyncTask {\n                EntityTrackerRegistry.registries { registry ->\n                    registry.remove(fabricPlayer)\n                }\n            }\n\n            (player.vehicle as? HitBox)?.dismount(fabricPlayer)\n        }\n    }\n\n    private fun registerCombatEvents() {\n        // same as EntityDamageByEntityEvent\n        //\n        // EntityDamageByEntityEvent are not expected to be called for non-living entities.\n        // therefore, ServerLivingEntityEvents is used.\n        //\n        ServerLivingEntityEvents.ALLOW_DAMAGE.register { entity, source, _ ->\n            val damager = source.entity\n            if (damager != null) {\n                val victim = if (entity is HitBox) entity.source().uuid() else entity.uuid\n                val vehicle = damager.vehicle\n                if (vehicle is HitBox &&\n                    !vehicle.mountController().canBeDamagedByRider() &&\n                    vehicle.source().uuid() == victim\n                ) {\n                    return@register false\n                }\n            }\n\n            return@register true\n        }\n\n        // same as EntityDamageEvent\n        //\n        // EntityDamageEvent and EntityDamageByEntityEvent are not expected to be called for non-living entities.\n        // therefore, ServerLivingEntityEvents is used.\n        //\n        ServerLivingEntityEvents.AFTER_DAMAGE.register { entity, _, _, _, _ ->\n            entity.eachTracker { tracker ->\n                tracker.animate(TrackerExtraAnimation.DAMAGE)\n                tracker.damageTint()\n            }\n        }\n\n        // same as EntityDeathEvent, PlayerDeathEvent\n        ServerLivingEntityEvents.AFTER_DEATH.register { entity, _ ->\n            entity.eachTracker { tracker ->\n                tracker.animate(TrackerExtraAnimation.DEATH)\n            }\n\n            if (entity is ServerPlayer) {\n                BetterModel.registryOrNull(entity.uuid)?.despawn()\n            }\n        }\n    }\n\n    private fun registerInteractionEvents() {\n        // same as PlayerInteractAtEntityEvent, PlayerInteractEntityEvent\n        UseEntityCallback.EVENT.register { clicker, _, hand, clicked, _ ->\n            if (clicker !is ServerPlayer) {\n                return@register InteractionResult.PASS\n            }\n\n            // for PlayerInteractAtEntityEvent\n            (clicked as? HitBox)?.let { hitBox ->\n                if (hand == InteractionHand.MAIN_HAND && !clicker.triggerDismount(clicked)) {\n                    clicker.triggerMount(hitBox)\n                }\n            }\n\n            return@register InteractionResult.PASS\n        }\n    }\n\n    private fun ServerPlayer.triggerDismount(entity: Entity): Boolean {\n        val oldVehicle = vehicle\n        if (oldVehicle !is HitBox) {\n            return false\n        }\n\n        val uuid = if (entity is HitBox) entity.source().uuid() else entity.uuid\n        if (oldVehicle.source().uuid() != uuid || !oldVehicle.mountController().canDismountBySelf()) {\n            return false\n        }\n\n        oldVehicle.dismount(connection.wrap())\n        return true\n    }\n\n    private fun ServerPlayer.triggerMount(hitBox: HitBox) {\n        if (hitBox.mountController().canMount()) {\n            hitBox.mount(connection.wrap())\n        }\n    }\n\n    private fun Entity.eachTracker(block: (EntityTracker) -> Unit) {\n        BetterModel.registryOrNull(uuid)?.trackers()?.forEach { tracker ->\n            block(tracker)\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/manager/PlayerManagerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.manager\n\nimport kr.toxicity.model.api.manager.PlayerManager\nimport kr.toxicity.model.api.nms.PlayerChannelHandler\nimport kr.toxicity.model.api.pack.PackZipper\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.impl.fabric.wrap\nimport kr.toxicity.model.manager.GlobalManager\nimport kr.toxicity.model.manager.ReloadPipeline\nimport kr.toxicity.model.manager.SkinManagerImpl\nimport kr.toxicity.model.util.PLATFORM\nimport kr.toxicity.model.util.handleFailure\nimport net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents\nimport net.minecraft.server.level.ServerPlayer\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\nobject PlayerManagerImpl : PlayerManager, GlobalManager {\n    private val playerMap = ConcurrentHashMap<UUID, PlayerChannelHandler>()\n\n    override fun start() {\n        ServerPlayerEvents.JOIN.register { handleJoin(it) }\n        ServerPlayerEvents.LEAVE.register { handleLeave(it) }\n    }\n\n    private fun handleJoin(player: ServerPlayer) {\n        runCatching {\n            player.connection.wrap().register()\n        }.handleFailure {\n            \"Unable to load ${player.name}'s data.\"\n        }\n    }\n\n    private fun handleLeave(player: ServerPlayer) {\n        playerMap.remove(player.uuid)?.use {\n            SkinManagerImpl.removeCache(it.base().profile())\n        }\n    }\n\n    override fun end() {\n        playerMap.values.forEach { handler ->\n            handler.use { used ->\n                SkinManagerImpl.removeCache(used.base().profile())\n            }\n        }\n\n        playerMap.clear()\n    }\n\n    override fun player(uuid: UUID): PlayerChannelHandler? = playerMap[uuid]\n\n    override fun player(player: PlatformPlayer): PlayerChannelHandler = player.register()\n\n    private fun PlatformPlayer.register(): PlayerChannelHandler {\n        return playerMap.computeIfAbsent(uuid()) {\n            PLATFORM.nms().inject(this)\n        }.apply {\n            SkinManagerImpl.complete(base().profile().asUncompleted())\n        }\n    }\n\n    override fun reload(pipeline: ReloadPipeline, zipper: PackZipper) = Unit\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/manager/Syncers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.manager\n\nimport kr.toxicity.model.mixin.SynchedEntityDataAccessor\nimport net.minecraft.network.syncher.EntityDataAccessor\nimport net.minecraft.network.syncher.SynchedEntityData\n\nfun <T : Any> SynchedEntityData.markDirty(accessor: EntityDataAccessor<T>) {\n    (this as SynchedEntityDataAccessor).`bettermodel$getItem`(accessor).isDirty = true\n    `bettermodel$setDirty`(true)\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/network/ModAnimationBundlerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.network\n\nimport kr.toxicity.model.api.mod.BetterModelMod\nimport kr.toxicity.model.api.nms.ModAnimationBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.api.util.MathUtil\nimport kr.toxicity.model.impl.fabric.unwarp\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.RegistryFriendlyByteBuf\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket\nimport net.minecraft.resources.Identifier\nimport org.joml.Quaternionf\nimport org.joml.Vector3f\n\ninternal class ModAnimationBundlerImpl(initialCapacity: Int) : ModAnimationBundler {\n\n    companion object {\n\n        const val KEY = \"modelengine:bulk_data\"\n        val IDENTIFIER = Identifier.parse(KEY)\n\n        const val PACKET_TYPE_BULK_DATA = 0x00\n\n        const val FIELD_TRANSLATION = 1 shl 0\n        const val FIELD_LEFT_ROTATION = 1 shl 1\n        const val FIELD_SCALE = 1 shl 2\n        const val FIELD_TRANSFORM_DURATION = 1 shl 4\n\n        private val EMPTY_BUILD_TASK: (FriendlyByteBuf) -> Unit = {}\n    }\n\n    private val packet by lazy {\n        useByteBuf { buffer ->\n            ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC.decode(\n                RegistryFriendlyByteBuf(\n                    buffer,\n                    BetterModelMod.platform().server().registryAccess()\n                ).apply {\n                    writeUtf(KEY)\n                    useByteBuf {\n                        it.writeByte(PACKET_TYPE_BULK_DATA)\n                        it.writeVarInt(builderList.size)\n                        builderList.forEach { builder -> builder(it) }\n                        writeBytes(it)\n                    }\n                }\n            )\n        }\n    }\n\n    private val builderList = ArrayList<(FriendlyByteBuf) -> Unit>(initialCapacity)\n\n    override fun send(player: PlatformPlayer) {\n        player.unwarp().send(packet)\n    }\n\n    fun append(id: Int, scope: Appender.() -> Unit) {\n        val build = Appender(id).apply(scope).build()\n        if (build !== EMPTY_BUILD_TASK) builderList += build\n    }\n\n    class Appender(\n        val entityId: Int,\n    ) {\n        private var mask = 0\n        private var buildTask = EMPTY_BUILD_TASK\n        private val isEmpty get() = buildTask === EMPTY_BUILD_TASK\n\n        fun appendPosition(vector: Vector3f) {\n            mask = mask or FIELD_TRANSLATION\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendScale(vector: Vector3f) {\n            mask = mask or FIELD_SCALE\n            task {\n                writeFloat(it, vector.x)\n                writeFloat(it, vector.y)\n                writeFloat(it, vector.z)\n            }\n        }\n\n        fun appendRotation(quaternion: Quaternionf) {\n            mask = mask or FIELD_LEFT_ROTATION\n            task {\n                writeFloat(it, quaternion.x)\n                writeFloat(it, quaternion.y)\n                writeFloat(it, quaternion.z)\n                writeFloat(it, quaternion.w)\n            }\n        }\n\n        fun appendDuration(duration: Int) {\n            mask = mask or FIELD_TRANSFORM_DURATION\n            task {\n                writeVarInt(it, duration)\n            }\n        }\n\n        fun build(): (FriendlyByteBuf) -> Unit {\n            if (isEmpty) return EMPTY_BUILD_TASK\n            val m = mask\n            val t = buildTask\n            return {\n                writeVarInt(it,entityId)\n                writeByte(it, m)\n                t(it)\n            }\n        }\n\n        private fun task(task: (FriendlyByteBuf) -> Unit) {\n            if (isEmpty) {\n                buildTask = task\n                return\n            }\n            val last = buildTask\n            buildTask = {\n                last(it)\n                task(it)\n            }\n        }\n\n        private fun writeFloat(buf: FriendlyByteBuf, float: Float) {\n            buf.writeShort(MathUtil.floatToHalf(float).toInt())\n        }\n\n        private fun writeVarInt(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeVarInt(duration)\n        }\n\n        private fun writeByte(buf: FriendlyByteBuf, duration: Int) {\n            buf.writeByte(duration)\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/network/PacketBundlers.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.network\n\nimport kr.toxicity.model.api.nms.PacketBundler\nimport kr.toxicity.model.api.platform.PlatformPlayer\nimport kr.toxicity.model.impl.fabric.unwarp\nimport net.minecraft.network.PacketSendListener\nimport net.minecraft.network.protocol.Packet\nimport net.minecraft.network.protocol.game.ClientGamePacketListener\nimport net.minecraft.network.protocol.game.ClientboundBundlePacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\n\ninternal typealias ClientPacket = Packet<ClientGamePacketListener>\n\ninternal fun bundlerOfNotNull(vararg packets: ClientPacket?) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.filterNotNull().toMutableList())\ninternal fun bundlerOf(vararg packets: ClientPacket) = SimpleBundler(if (packets.isEmpty()) arrayListOf() else packets.toMutableList())\ninternal fun bundlerOf(size: Int) = SimpleBundler(ArrayList(size))\ninternal fun parallelBundlerOf(threshold: Int) = ParallelBundler(threshold)\n\ninternal operator fun PacketBundler.plusAssign(other: ClientPacket) {\n    when (this) {\n        is SimpleBundler -> add(other)\n        is ParallelBundler -> add(other)\n        else -> throw RuntimeException(\"unsupported bundler.\")\n    }\n}\ninternal fun Packet<*>.assumeSize() = when (this) {\n    is ClientboundSetEntityDataPacket -> packedItems.size\n    is ClientboundSetEquipmentPacket -> slots.size\n    else -> 1\n}\n\ninternal interface PluginBundlePacketImpl : Iterable<ClientPacket> {\n    val bundlePacket: ClientboundBundlePacket\n    fun size(): Int\n    fun isEmpty(): Boolean\n    fun add(other: ClientPacket)\n}\n\ninternal class SimpleBundler(\n    private val list: MutableList<ClientPacket>\n) : PacketBundler, PluginBundlePacketImpl {\n    override val bundlePacket by lazy {\n        ClientboundBundlePacket(this).apply {\n            (this as BetterModelBundlePacket).`bettermodel$setBetterModelPacket`(true)\n        }\n    }\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = player.unwarp().player.connection\n        connection.send(bundlePacket, PacketSendListener.thenRun(onSuccess))\n    }\n    override fun isEmpty(): Boolean = list.isEmpty()\n    override fun size(): Int = list.size\n    override fun iterator(): MutableIterator<ClientPacket> = list.iterator()\n    override fun add(other: ClientPacket) {\n        list += other\n    }\n}\n\ninternal class ParallelBundler(\n    private val threshold: Int\n) : PacketBundler {\n    private val subBundlers = mutableListOf<PluginBundlePacketImpl>()\n    private var sizeAssume = 0\n    private val newBundler get() = bundlerOf().apply {\n        sizeAssume = 0\n        subBundlers += this\n    }\n    private var selectedBundler = newBundler\n    override fun send(player: PlatformPlayer, onSuccess: Runnable) {\n        if (isEmpty) return\n        val connection = player.unwarp()\n        subBundlers.forEach {\n            connection.send(it.bundlePacket)\n        }\n    }\n    override fun isEmpty(): Boolean = selectedBundler.isEmpty()\n    override fun size(): Int = subBundlers.sumOf(PluginBundlePacketImpl::size)\n    fun add(other: ClientPacket) {\n        (if (sizeAssume > threshold) newBundler else selectedBundler)\n            .apply { selectedBundler = this }\n            .add(other)\n        sizeAssume += other.assumeSize()\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/network/Packets.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.network\n\nimport com.mojang.datafixers.util.Pair.of\nimport io.netty.buffer.Unpooled\nimport it.unimi.dsi.fastutil.ints.IntSet\nimport kr.toxicity.model.api.tracker.EntityTrackerRegistry\nimport kr.toxicity.model.mixin.ConnectionAccessor\nimport kr.toxicity.model.mixin.EntityAccessor\nimport kr.toxicity.model.mixin.ServerCommonPacketListenerImplAccessor\nimport kr.toxicity.model.mixin.SynchedEntityDataAccessor\nimport net.minecraft.network.Connection\nimport net.minecraft.network.FriendlyByteBuf\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\nimport net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket\nimport net.minecraft.network.protocol.game.ClientboundSetPassengersPacket\nimport net.minecraft.network.syncher.EntityDataSerializers\nimport net.minecraft.network.syncher.SynchedEntityData\nimport net.minecraft.network.syncher.SynchedEntityData.DataItem\nimport net.minecraft.network.syncher.SynchedEntityData.DataValue\nimport net.minecraft.server.network.ServerGamePacketListenerImpl\nimport net.minecraft.world.entity.Entity\nimport net.minecraft.world.entity.EquipmentSlot\nimport net.minecraft.world.entity.LivingEntity\nimport net.minecraft.world.entity.player.Player\nimport net.minecraft.world.item.ItemStack\nimport java.util.*\nimport java.util.function.IntConsumer\nimport java.util.stream.IntStream\n\nval Connection.channel get() = (this as ConnectionAccessor).`bettermodel$getChannel`()\n\nval ServerGamePacketListenerImpl.connection get() = (this as ServerCommonPacketListenerImplAccessor).`bettermodel$getConnection`()\n\nval Player.hotbarSlot get() = inventory.selectedSlot + 36\n\nfun EntityTrackerRegistry.mountPacket(\n    entity: Entity,\n    passengerIds: IntStream = entity.getUnregisteredPassengerIds()\n): ClientboundSetPassengersPacket {\n    return useByteBuf { buffer ->\n        val displayIds = displays().mapToInt { display -> display.id() }\n        val ids = IntStream.concat(displayIds, passengerIds)\n\n        buffer.writeVarInt(entity.id)\n        buffer.writeVarIntArray(ids.toArray())\n\n        ClientboundSetPassengersPacket.STREAM_CODEC.decode(buffer)\n    }\n}\n\nfun Entity.getUnregisteredPassengerIds(): IntStream {\n    return passengers.stream()\n        .filter { passenger ->\n            EntityTrackerRegistry.registry(passenger.uuid) == null\n        }\n        .mapToInt { passenger ->\n            passenger.id\n        }\n}\n\ninline fun <T> useByteBuf(block: (FriendlyByteBuf) -> T): T {\n    val buffer = FriendlyByteBuf(Unpooled.buffer())\n    return try {\n        block(buffer)\n    } finally {\n        buffer.release()\n    }\n}\n\ninline fun SynchedEntityData.pack(\n    clean: Boolean = false,\n    itemFilter: (DataItem<*>) -> Boolean = { true },\n    crossinline valueFilter: (DataValue<*>) -> Boolean = { true },\n    crossinline required: (List<Pair<DataItem<*>, DataValue<*>>>) -> Boolean = { it.isNotEmpty() }\n): List<DataValue<*>>? {\n    return (this as SynchedEntityDataAccessor)\n        .`bettermodel$getItemsById`()\n        .mapNotNull {\n            val item = it.takeIf(itemFilter)\n                ?: return@mapNotNull null\n\n            val value = item.value().takeIf(valueFilter)\n                ?: return@mapNotNull null\n\n            item to value\n        }\n        .takeIf(required)\n        ?.map {\n            if (clean) {\n                it.first.isDirty = false\n            }\n\n            it.second\n        }\n}\n\nfun ClientboundSetEntityDataPacket.toRegistryDataPacket(uuid: UUID, registry: EntityTrackerRegistry) = ClientboundSetEntityDataPacket(\n    id, packedItems().map {\n    if (it.id == EntityAccessor.`bettermodel$getDataSharedFlagsId`().id) DataValue(\n        it.id,\n        EntityDataSerializers.BYTE,\n        registry.entityFlag(uuid, it.value() as Byte)\n    ) else it\n})\n\nfun EntityTrackerRegistry.entityFlag(uuid: UUID, byte: Byte): Byte {\n    var b = byte.toInt()\n    val hideOption = hideOption(uuid)\n    if (hideOption.fire()) b = b and 1.inv()\n    if (hideOption.visibility()) b = b or (1 shl 5)\n    if (hideOption.glowing()) b = b and (1 shl 6).inv()\n    return b.toByte()\n}\n\ninline fun LivingEntity.toEquipmentPacket(mapper: (EquipmentSlot) -> ItemStack? = { getItemBySlot(it).takeUnless { item -> item.isEmpty } }): ClientboundSetEquipmentPacket? {\n    val equip = EquipmentSlot.entries.mapNotNull {\n        mapper(it)?.let { item -> of(it, item) }\n    }\n    return if (equip.isNotEmpty()) ClientboundSetEquipmentPacket(id, equip) else null\n}\nfun LivingEntity.toEmptyEquipmentPacket() = toEquipmentPacket { ItemStack.EMPTY }\n\nfun ClientboundContainerSetSlotPacket.isEquipment(player: Player): Boolean {\n    return containerId == 0 &&\n        (PLAYER_EQUIPMENT_SLOT.contains(slot) || slot == player.hotbarSlot)\n}\n\nfun eachEquipmentSlots(block: (Int) -> Unit) {\n    PLAYER_EQUIPMENT_SLOT.forEach(IntConsumer { slot ->\n        block(slot)\n    })\n}\n\nprivate val PLAYER_EQUIPMENT_SLOT = IntSet.of(45, 5, 6, 7, 8)\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/profile/ModelProfileImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.profile\n\nimport com.mojang.authlib.GameProfile\nimport kr.toxicity.model.api.profile.ModelProfile\nimport kr.toxicity.model.api.profile.ModelProfileInfo\nimport kr.toxicity.model.api.profile.ModelProfileSkin\nimport kr.toxicity.model.util.PLATFORM\n\nclass ModelProfileImpl(private val profile: GameProfile) : ModelProfile {\n    private val info = ModelProfileInfo(\n        profile.id,\n        profile.name\n    )\n\n    private val skin by lazy {\n        val properties = profile.properties\n        val property = properties[\"textures\"].firstOrNull()\n\n        if (property == null) {\n            ModelProfileSkin.EMPTY\n        } else {\n            PLATFORM.profileManager().skin(property.value)\n        }\n    }\n\n    override fun info(): ModelProfileInfo = info\n\n    override fun skin(): ModelProfileSkin = skin\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/scheduler/FabricModelSchedulerImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.scheduler\n\nimport kr.toxicity.model.api.mod.platform.ModRegionHolder\nimport kr.toxicity.model.api.mod.scheduler.ModModelScheduler\nimport kr.toxicity.model.api.scheduler.ModelTask\nimport kr.toxicity.model.api.util.LogUtil\nimport net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents\nimport net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents\nimport java.util.concurrent.*\nimport java.util.concurrent.atomic.AtomicBoolean\nimport java.util.concurrent.atomic.AtomicInteger\nimport java.util.concurrent.atomic.AtomicLong\n\nobject FabricModelSchedulerImpl : ModModelScheduler, ModRegionHolder {\n\n    private val scheduler = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), object : ThreadFactory {\n\n        private val integer = AtomicInteger()\n\n        override fun newThread(r: Runnable): Thread {\n            val thread = Thread(r)\n            thread.setDaemon(true)\n            thread.setName(\"BetterModel-Async-Scheduler-\" + integer.getAndIncrement())\n            thread.setUncaughtExceptionHandler { t: Thread, e: Throwable -> LogUtil.handleException(\"Exception has occurred in \" + t.name, e) }\n            return thread\n        }\n    })\n\n    private val enabled = AtomicBoolean(true)\n\n    private val queue = ConcurrentLinkedQueue<SyncTask>()\n\n    override fun asyncTask(runnable: Runnable): ModelTask {\n        return AsyncTask(runnable) {\n            submit(it)\n        }\n    }\n\n    override fun asyncTaskLater(delay: Long, runnable: Runnable): ModelTask {\n        return AsyncTask(runnable) {\n           schedule(it, delay * 50, TimeUnit.MILLISECONDS)\n        }\n    }\n\n    override fun asyncTaskTimer(delay: Long, period: Long, runnable: Runnable): ModelTask {\n        return AsyncTask(runnable) {\n            scheduleAtFixedRate(it, delay * 50, period * 50, TimeUnit.MILLISECONDS)\n        }\n    }\n\n    override fun task(runnable: Runnable): ModelTask? {\n        if (!enabled.get()) return null\n        return SyncTask(runnable).apply { queue += this }\n    }\n\n    override fun taskLater(delay: Long, runnable: Runnable): ModelTask? {\n        if (!enabled.get()) return null\n        return SyncTask(runnable, delay).apply { queue += this }\n    }\n\n    private class AsyncTask(\n        private val runnable: Runnable,\n        scheduleFunction: (ScheduledExecutorService).(Runnable) -> Future<*>\n    ) : Runnable, ModelTask {\n\n        private val future = scheduler.scheduleFunction(this)\n\n        override fun run() {\n            if (enabled.get()) {\n                runnable.run()\n            } else {\n                future.cancel(true)\n            }\n        }\n        override fun isCancelled(): Boolean = future.isCancelled\n\n        override fun cancel() {\n            future.cancel(true)\n        }\n    }\n\n    private class SyncTask(\n        @Volatile\n        var task: Runnable,\n        counter: Long = 0L\n    ) : ModelTask {\n        private val atomicCounter = AtomicLong(counter)\n\n        fun run() = if (atomicCounter.getAndDecrement() <= 0) {\n            synchronized(this) {\n                if (enabled.get()) task.run()\n            }\n            true\n        } else false\n\n        override fun isCancelled(): Boolean {\n            return task === CANCELLED_TASK\n        }\n\n        override fun cancel() {\n            if (isCancelled) return\n            synchronized(this) {\n                if (isCancelled) return\n                task = CANCELLED_TASK\n                atomicCounter.set(0)\n            }\n        }\n\n        companion object {\n            val CANCELLED_TASK: Runnable = {}\n        }\n    }\n\n    private fun tick() {\n        queue.removeIf {\n            it.run()\n        }\n    }\n\n    fun init() {\n        ServerTickEvents.START_LEVEL_TICK.register {\n            tick()\n        }\n\n        ServerLifecycleEvents.SERVER_STARTING.register {\n            enabled.set(true)\n        }\n\n        ServerLifecycleEvents.SERVER_STOPPED.register {\n            enabled.set(false)\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/world/Chunks.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n@file:Suppress(\"UnstableApiUsage\")\n\npackage kr.toxicity.model.impl.fabric.world\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap\nimport net.fabricmc.fabric.mixin.networking.accessor.ChunkMapAccessor\nimport net.fabricmc.fabric.mixin.networking.accessor.EntityTrackerAccessor\nimport net.minecraft.server.level.ChunkMap\n\nval ChunkMap.entityMap: Int2ObjectMap<EntityTrackerAccessor>\n    get() {\n        return (this as ChunkMapAccessor).entityMap\n    }\n"
  },
  {
    "path": "platform/fabric/src/main/kotlin/kr/toxicity/model/impl/fabric/world/damagesource/ModelDamageSourceImpl.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.impl.fabric.world.damagesource\n\nimport kr.toxicity.model.api.event.ModelDamageSource\nimport kr.toxicity.model.api.mod.platform.ModLocation\nimport kr.toxicity.model.api.platform.PlatformEntity\nimport kr.toxicity.model.api.platform.PlatformLocation\nimport kr.toxicity.model.impl.fabric.wrap\nimport net.minecraft.world.damagesource.DamageSource\n\nclass ModelDamageSourceImpl(private val source: DamageSource) : ModelDamageSource {\n    override fun getCausingEntity(): PlatformEntity? = source.entity?.wrap()\n\n    override fun getDirectEntity(): PlatformEntity? = source.directEntity?.wrap()\n\n    override fun getDamageLocation(): PlatformLocation? {\n        return source.sourcePositionRaw()?.let { pos ->\n            ModLocation.of(\n                source.entity?.level(),\n                pos.x, pos.y, pos.z,\n                0f, 0f\n            )\n        }\n    }\n\n    override fun getSourceLocation(): PlatformLocation? {\n        return source.sourcePosition?.let { pos ->\n            ModLocation.of(\n                source.entity?.level(),\n                pos.x, pos.y, pos.z,\n                0f, 0f\n            )\n        }\n    }\n\n    override fun isIndirect(): Boolean = !source.isDirect\n\n    override fun getFoodExhaustion(): Float = source.foodExhaustion\n\n    override fun scalesWithDifficulty(): Boolean = source.scalesWithDifficulty()\n}\n"
  },
  {
    "path": "platform/fabric/src/main/resources/bettermodel.accesswidener",
    "content": "accessWidener v1 named\n"
  },
  {
    "path": "platform/fabric/src/main/resources/bettermodel.mixins.json",
    "content": "{\n    \"required\": true,\n    \"package\": \"kr.toxicity.model.mixin\",\n    \"compatibilityLevel\": \"JAVA_25\",\n    \"mixins\": [\n        \"AvatarAccessor\",\n        \"ClientboundBundlePacketMixin\",\n        \"ConnectionAccessor\",\n        \"DisplayAccessor\",\n        \"EntityAccessor\",\n        \"EntityMixin\",\n        \"ItemDisplayAccessor\",\n        \"LivingEntityMixin\",\n        \"MobAccessor\",\n        \"ServerCommonPacketListenerImplAccessor\",\n        \"ServerLevelEntityCallbacksMixin\",\n        \"SynchedEntityDataAccessor\"\n    ],\n    \"injectors\": {\n        \"defaultRequire\": 1\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/testmod/kotlin/kr/toxicity/model/test/RollTest.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.test\n\nimport com.mojang.brigadier.builder.LiteralArgumentBuilder\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.animation.AnimationModifier\nimport kr.toxicity.model.api.mod.platform.ModPlayer\nimport kr.toxicity.model.api.tracker.ModelRotation\nimport kr.toxicity.model.api.tracker.TrackerModifier\nimport net.fabricmc.api.ModInitializer\nimport net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback\nimport net.minecraft.commands.CommandSourceStack\nimport net.minecraft.commands.Commands\nimport net.minecraft.network.chat.Component\nimport net.minecraft.server.level.ServerPlayer\nimport net.minecraft.world.entity.player.Input\nimport kotlin.jvm.optionals.getOrNull\nimport kotlin.math.atan2\n\nclass RollTest : ModInitializer {\n    override fun onInitialize() {\n        CommandRegistrationCallback.EVENT.register { dispatcher, _, _ ->\n            dispatcher.register(argumentRoll())\n        }\n    }\n\n    private fun argumentRoll(): LiteralArgumentBuilder<CommandSourceStack?>? {\n        return Commands.literal(\"roll\")\n            .requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))\n            .then(argumentInfo())\n            .then(argumentPlay())\n    }\n\n    private fun argumentInfo(): LiteralArgumentBuilder<CommandSourceStack> {\n        return Commands.literal(\"info\")\n            .executes { context ->\n                executeInfo(context.source)\n            }\n    }\n\n    private fun argumentPlay(): LiteralArgumentBuilder<CommandSourceStack> {\n        return Commands.literal(\"play\")\n            .executes { context ->\n                executePlay(context.source, context.source.playerOrException)\n            }\n    }\n\n    private fun executeInfo(source: CommandSourceStack): Int {\n        val renderer = BetterModel.limb(\"steve\").getOrNull()\n            ?: let {\n                source.sendFailure(Component.literal(\"Renderer not found: steve\"))\n                return 0\n            }\n\n        val animation = renderer.animation(\"roll\").getOrNull()\n            ?: let {\n                source.sendFailure(Component.literal(\"Animation not found: roll\"))\n                return 0\n            }\n\n        source.sendSuccess(\n            {\n                Component.empty()\n                    .append(\"Loop mode: \" + animation.loop)\n                    .append(\"\\n\")\n                    .append(\"Length: \" + animation.length + \" second\")\n            },\n            true\n        )\n        return 1\n    }\n\n    private fun executePlay(source: CommandSourceStack, player: ServerPlayer): Int {\n        val renderer = BetterModel.limb(\"steve\").getOrNull()\n            ?: let {\n                source.sendFailure(Component.literal(\"Renderer not found: steve\"))\n                return 0\n            }\n\n        val yaw = player.lastClientInput.toYaw()\n        val tracker = renderer.getOrCreate(\n            ModPlayer.of(player.connection),\n            TrackerModifier.DEFAULT\n        ) { tracker ->\n            tracker.rotation {\n                ModelRotation(\n                    player.xRot,\n                    (yaw + tracker.registry().entity().bodyYaw()).packDegrees()\n                )\n            }\n        }\n\n        val isAnimated = tracker.animate(\n            \"roll\",\n            AnimationModifier.DEFAULT_WITH_PLAY_ONCE\n        ) {\n            tracker.close()\n        }\n\n        if (!isAnimated) {\n            tracker.close()\n        }\n\n        return 1\n    }\n\n    private fun Input.toYaw(): Float {\n        val forward = (if (forward) 1 else 0) - (if (backward) 1 else 0)\n        val right = (if (right) 1 else 0) - (if (left) 1 else 0)\n\n        return if (forward == 0 && right == 0) {\n            0f\n        } else {\n            Math.toDegrees(atan2(right.toDouble(), forward.toDouble())).toFloat()\n        }\n    }\n\n    private fun Float.packDegrees(): Float {\n        return if (this > 180) {\n            this - 360\n        } else {\n            this\n        }\n    }\n}\n"
  },
  {
    "path": "platform/fabric/src/testmod/resources/knight.bbmodel",
    "content": "{\"meta\":{\"format_version\":\"5.0\",\"model_format\":\"free\",\"box_uv\":false},\"name\":\"knight\",\"model_identifier\":\"\",\"visible_box\":[1,1,0],\"variable_placeholders\":\"degrees=180\",\"variable_placeholder_buttons\":[],\"timeline_setups\":[],\"unhandled_root_fields\":{},\"resolution\":{\"width\":64,\"height\":64},\"elements\":[{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,11.25,-1.875],\"to\":[3.75,15,1.875],\"autouv\":0,\"color\":1,\"origin\":[0,11.25,0],\"uv_offset\":[16,24],\"faces\":{\"north\":{\"uv\":[20,28,28,32],\"texture\":0},\"east\":{\"uv\":[16,28,20,32],\"texture\":0},\"south\":{\"uv\":[32,28,40,32],\"texture\":0},\"west\":{\"uv\":[28,28,32,32],\"texture\":0},\"up\":{\"uv\":[28,28,20,24],\"texture\":0},\"down\":{\"uv\":[36,16,28,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ea42f7f7-a6f1-4479-c43f-48211bab5ed2\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,15,-1.875],\"to\":[3.75,18.75,1.875],\"autouv\":0,\"color\":2,\"origin\":[0,15,0],\"uv_offset\":[16,20],\"faces\":{\"north\":{\"uv\":[20,24,28,28],\"texture\":0},\"east\":{\"uv\":[16,24,20,28],\"texture\":0},\"south\":{\"uv\":[32,24,40,28],\"texture\":0},\"west\":{\"uv\":[28,24,32,28],\"texture\":0},\"up\":{\"uv\":[28,24,20,20],\"texture\":0},\"down\":{\"uv\":[28,28,20,32],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5ea74bdb-ba28-b8e3-103b-9be6ff2262da\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,18.75,-1.875],\"to\":[3.75,22.5,1.875],\"autouv\":0,\"color\":3,\"origin\":[0,18.75,0],\"uv_offset\":[16,16],\"faces\":{\"north\":{\"uv\":[20,20,28,24],\"texture\":0},\"east\":{\"uv\":[16,20,20,24],\"texture\":0},\"south\":{\"uv\":[32,20,40,24],\"texture\":0},\"west\":{\"uv\":[28,20,32,24],\"texture\":0},\"up\":{\"uv\":[28,20,20,16],\"texture\":0},\"down\":{\"uv\":[28,24,20,28],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc1510db-a719-17b4-e253-c992a92c5d25\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,18.76563,-1.85937],\"to\":[3.73438,22.48438,1.85938],\"autouv\":0,\"color\":3,\"inflate\":0.25,\"origin\":[0,18.75,0],\"uv_offset\":[16,32],\"faces\":{\"north\":{\"uv\":[20,36,28,40],\"texture\":0},\"east\":{\"uv\":[16,36,20,40],\"texture\":0},\"south\":{\"uv\":[32,36,40,40],\"texture\":0},\"west\":{\"uv\":[28,36,32,40],\"texture\":0},\"up\":{\"uv\":[28,36,20,32],\"texture\":0},\"down\":{\"uv\":[8,0,0,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d51a8665-a2bc-af6e-1230-5acba07248e7\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,15.01563,-1.85937],\"to\":[3.73438,18.73438,1.85938],\"autouv\":0,\"color\":2,\"inflate\":0.25,\"origin\":[0,15,0],\"uv_offset\":[16,36],\"faces\":{\"north\":{\"uv\":[20,40,28,44],\"texture\":0},\"east\":{\"uv\":[16,40,20,44],\"texture\":0},\"south\":{\"uv\":[32,40,40,44],\"texture\":0},\"west\":{\"uv\":[28,40,32,44],\"texture\":0},\"up\":{\"uv\":[8,4,0,0],\"texture\":0},\"down\":{\"uv\":[8,0,0,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f5b9f499-b26b-f912-1a54-151d702e13ed\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,11.26563,-1.85937],\"to\":[3.73438,14.98438,1.85938],\"autouv\":0,\"color\":1,\"inflate\":0.25,\"origin\":[0,11.25,0],\"uv_offset\":[16,40],\"faces\":{\"north\":{\"uv\":[20,44,28,48],\"texture\":0},\"east\":{\"uv\":[16,44,20,48],\"texture\":0},\"south\":{\"uv\":[32,44,40,48],\"texture\":0},\"west\":{\"uv\":[28,44,32,48],\"texture\":0},\"up\":{\"uv\":[8,4,0,0],\"texture\":0},\"down\":{\"uv\":[36,32,28,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"357ebf82-23ba-edb1-081f-dca75d94b83c\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.75,16.875,-1.875],\"to\":[7.5,22.5,1.875],\"autouv\":0,\"color\":4,\"origin\":[5.15625,21.5625,0],\"uv_offset\":[40,16],\"faces\":{\"north\":{\"uv\":[44,20.1,48,26.1],\"texture\":0},\"east\":{\"uv\":[40,20,44,26],\"texture\":0},\"south\":{\"uv\":[52,20,56,26],\"texture\":0},\"west\":{\"uv\":[48,20,52,26],\"texture\":0},\"up\":{\"uv\":[48,20.1,44,16.1],\"texture\":0},\"down\":{\"uv\":[48,26.1,44,30.1],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"53d40d2e-0941-29f9-00ed-1b19c941dcd8\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.76563,16.89063,-1.85937],\"to\":[7.48438,22.48438,1.85938],\"autouv\":0,\"color\":4,\"inflate\":0.25,\"origin\":[5.15625,21.5625,0],\"uv_offset\":[40,32],\"faces\":{\"north\":{\"uv\":[44,36,48,42],\"texture\":0},\"east\":{\"uv\":[40,36,44,42],\"texture\":0},\"south\":{\"uv\":[52,36,56,42],\"texture\":0},\"west\":{\"uv\":[48,36,52,42],\"texture\":0},\"up\":{\"uv\":[48,36,44,32],\"texture\":0},\"down\":{\"uv\":[56,32,52,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b17452ef-afbe-e010-f2c9-e0ba945faa1f\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.5,16.875,-1.875],\"to\":[-3.75,22.5,1.875],\"autouv\":0,\"color\":4,\"origin\":[-5.15625,21.5625,0],\"uv_offset\":[32,48],\"faces\":{\"north\":{\"uv\":[36,52,40,58],\"texture\":0},\"east\":{\"uv\":[32,52,36,58],\"texture\":0},\"south\":{\"uv\":[44,52,48,58],\"texture\":0},\"west\":{\"uv\":[40,52,44,58],\"texture\":0},\"up\":{\"uv\":[40,52,36,48],\"texture\":0},\"down\":{\"uv\":[40,58,36,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"addb9f66-a2a4-46f7-b6af-f5a23c00fe70\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.48437,16.89063,-1.85937],\"to\":[-3.76562,22.48438,1.85938],\"autouv\":0,\"color\":4,\"inflate\":0.25,\"origin\":[-5.15625,21.5625,0],\"uv_offset\":[48,48],\"faces\":{\"north\":{\"uv\":[52,52,56,58],\"texture\":0},\"east\":{\"uv\":[48,52,52,58],\"texture\":0},\"south\":{\"uv\":[60,52,64,58],\"texture\":0},\"west\":{\"uv\":[56,52,60,58],\"texture\":0},\"up\":{\"uv\":[56,52,52,48],\"texture\":0},\"down\":{\"uv\":[64,48,60,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5e7dc31c-c64a-ff15-90d9-52a6f77cb14b\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.75,11.25,-1.875],\"to\":[7.5,16.875,1.875],\"autouv\":0,\"color\":8,\"origin\":[5.15625,15.9375,0],\"uv_offset\":[40,22],\"faces\":{\"north\":{\"uv\":[44,26,48,32],\"texture\":0},\"east\":{\"uv\":[40,26,44,32],\"texture\":0},\"south\":{\"uv\":[52,26,56,32],\"texture\":0},\"west\":{\"uv\":[48,26,52,32],\"texture\":0},\"up\":{\"uv\":[48,26,44,22],\"texture\":0},\"down\":{\"uv\":[52,16,48,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e02c395d-e1bc-1375-a8ed-729e19544ce9\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.76563,11.26563,-1.85937],\"to\":[7.48438,16.85938,1.85938],\"autouv\":0,\"color\":8,\"inflate\":0.25,\"origin\":[5.15625,15.9375,0],\"uv_offset\":[40,38],\"faces\":{\"north\":{\"uv\":[44,42,48,48],\"texture\":0},\"east\":{\"uv\":[40,42,44,48],\"texture\":0},\"south\":{\"uv\":[52,42,56,48],\"texture\":0},\"west\":{\"uv\":[48,42,52,48],\"texture\":0},\"up\":{\"uv\":[44,36,40,32],\"texture\":0},\"down\":{\"uv\":[52,32,48,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a509c2d7-53ef-2b11-1331-f716b9c210d6\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.5,11.25,-1.875],\"to\":[-3.75,16.875,1.875],\"autouv\":0,\"color\":8,\"origin\":[-5.15625,15.9375,0],\"uv_offset\":[32,54],\"faces\":{\"north\":{\"uv\":[36,58,40,64],\"texture\":0},\"east\":{\"uv\":[32,58,36,64],\"texture\":0},\"south\":{\"uv\":[44,58,48,64],\"texture\":0},\"west\":{\"uv\":[40,58,44,64],\"texture\":0},\"up\":{\"uv\":[40,58,36,54],\"texture\":0},\"down\":{\"uv\":[44,48,40,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1433ee62-21ac-d72c-0fd0-37000e5eb221\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.48437,11.26563,-1.85937],\"to\":[-3.76562,16.85938,1.85938],\"autouv\":0,\"color\":8,\"inflate\":0.25,\"origin\":[-5.15625,15.9375,0],\"uv_offset\":[48,54],\"faces\":{\"north\":{\"uv\":[52,58,56,64],\"texture\":0},\"east\":{\"uv\":[48,58,52,64],\"texture\":0},\"south\":{\"uv\":[60,58,64,64],\"texture\":0},\"west\":{\"uv\":[56,58,60,64],\"texture\":0},\"up\":{\"uv\":[52,52,48,48],\"texture\":0},\"down\":{\"uv\":[60,48,56,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"24bacfd6-cde8-81a0-f620-998cf5393d48\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0,5.625,-1.875],\"to\":[3.75,11.25,1.875],\"autouv\":0,\"color\":7,\"origin\":[1.40625,10.3125,0],\"uv_offset\":[0,16],\"faces\":{\"north\":{\"uv\":[4,20,8,26],\"texture\":0},\"east\":{\"uv\":[0,20,4,26],\"texture\":0},\"south\":{\"uv\":[12,20,16,26],\"texture\":0},\"west\":{\"uv\":[8,20,12,26],\"texture\":0},\"up\":{\"uv\":[8,20,4,16],\"texture\":0},\"down\":{\"uv\":[8,26,4,30],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b17675fc-b79b-0ad9-8f81-aa2dd1fc8c97\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.01563,5.64063,-1.85937],\"to\":[3.73438,11.23438,1.85938],\"autouv\":0,\"color\":7,\"inflate\":0.25,\"origin\":[1.40625,10.3125,0],\"uv_offset\":[0,32],\"faces\":{\"north\":{\"uv\":[4,36,8,42],\"texture\":0},\"east\":{\"uv\":[0,36,4,42],\"texture\":0},\"south\":{\"uv\":[12,36,16,42],\"texture\":0},\"west\":{\"uv\":[8,36,12,42],\"texture\":0},\"up\":{\"uv\":[8,36,4,32],\"texture\":0},\"down\":{\"uv\":[16,32,12,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2e42e483-1557-d21e-aee0-94af7bedfd40\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0,0,-1.875],\"to\":[3.75,5.625,1.875],\"autouv\":0,\"color\":6,\"origin\":[1.40625,4.6875,0],\"uv_offset\":[0,22],\"faces\":{\"north\":{\"uv\":[4,26,8,32],\"texture\":0},\"east\":{\"uv\":[0,26,4,32],\"texture\":0},\"south\":{\"uv\":[12,26,16,32],\"texture\":0},\"west\":{\"uv\":[8,26,12,32],\"texture\":0},\"up\":{\"uv\":[8,26,4,22],\"texture\":0},\"down\":{\"uv\":[12,16,8,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9455d16e-4bbf-4b63-881d-7daf943e782b\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.01563,0.01563,-1.85937],\"to\":[3.73438,5.60938,1.85938],\"autouv\":0,\"color\":6,\"inflate\":0.25,\"origin\":[1.40625,4.6875,0],\"uv_offset\":[0,38],\"faces\":{\"north\":{\"uv\":[4,42,8,48],\"texture\":0},\"east\":{\"uv\":[0,42,4,48],\"texture\":0},\"south\":{\"uv\":[12,42,16,48],\"texture\":0},\"west\":{\"uv\":[8,42,12,48],\"texture\":0},\"up\":{\"uv\":[4,36,0,32],\"texture\":0},\"down\":{\"uv\":[12,32,8,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"16aee685-0ead-a542-5b3c-a62467ca45e3\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,5.625,-1.875],\"to\":[0,11.25,1.875],\"autouv\":0,\"color\":7,\"origin\":[-1.40625,10.3125,0],\"uv_offset\":[16,48],\"faces\":{\"north\":{\"uv\":[20,52,24,58],\"texture\":0},\"east\":{\"uv\":[16,52,20,58],\"texture\":0},\"south\":{\"uv\":[28,52,32,58],\"texture\":0},\"west\":{\"uv\":[24,52,28,58],\"texture\":0},\"up\":{\"uv\":[24,52,20,48],\"texture\":0},\"down\":{\"uv\":[24,58,20,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0e370edc-7b05-dccf-dd2a-a92cfe9f3e22\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,5.64063,-1.85937],\"to\":[-0.01562,11.23438,1.85938],\"autouv\":0,\"color\":7,\"inflate\":0.25,\"origin\":[-1.40625,10.3125,0],\"uv_offset\":[0,48],\"faces\":{\"north\":{\"uv\":[4,52,8,58],\"texture\":0},\"east\":{\"uv\":[0,52,4,58],\"texture\":0},\"south\":{\"uv\":[12,52,16,58],\"texture\":0},\"west\":{\"uv\":[8,52,12,58],\"texture\":0},\"up\":{\"uv\":[8,52,4,48],\"texture\":0},\"down\":{\"uv\":[16,48,12,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc189735-0a58-619b-07ef-feec37d65e7d\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,0,-1.875],\"to\":[0,5.625,1.875],\"autouv\":0,\"color\":6,\"origin\":[-1.40625,4.6875,0],\"uv_offset\":[16,54],\"faces\":{\"north\":{\"uv\":[20,58,24,64],\"texture\":0},\"east\":{\"uv\":[16,58,20,64],\"texture\":0},\"south\":{\"uv\":[28,58,32,64],\"texture\":0},\"west\":{\"uv\":[24,58,28,64],\"texture\":0},\"up\":{\"uv\":[24,58,20,54],\"texture\":0},\"down\":{\"uv\":[28,48,24,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6bcf768b-beab-4c39-6d96-91266036a4e1\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,0.01563,-1.85938],\"to\":[-0.01562,5.60938,1.85937],\"autouv\":0,\"color\":6,\"inflate\":0.25,\"origin\":[-1.40625,4.6875,-0.00001],\"uv_offset\":[0,54],\"faces\":{\"north\":{\"uv\":[4,58,8,64],\"texture\":0},\"east\":{\"uv\":[0,58,4,64],\"texture\":0},\"south\":{\"uv\":[12,58,16,64],\"texture\":0},\"west\":{\"uv\":[8,58,12,64],\"texture\":0},\"up\":{\"uv\":[4,52,0,48],\"texture\":0},\"down\":{\"uv\":[12,48,8,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"274282a7-bc7b-02f8-4dce-84226e9dd9c1\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,22.5,-3.75],\"to\":[3.75,30,3.75],\"autouv\":0,\"color\":4,\"origin\":[0,22.5,-1.875],\"faces\":{\"north\":{\"uv\":[8,8,16,16],\"texture\":0},\"east\":{\"uv\":[0,8,8,16],\"texture\":0},\"south\":{\"uv\":[24,8,32,16],\"texture\":0},\"west\":{\"uv\":[16,8,24,16],\"texture\":0},\"up\":{\"uv\":[16,8,8,0],\"texture\":0},\"down\":{\"uv\":[24,0,16,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7f60fbaf-510d-2e5f-b7d2-9111e08443cd\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,22.51563,-3.73437],\"to\":[3.73438,29.98438,3.73438],\"autouv\":0,\"color\":4,\"inflate\":0.5,\"origin\":[0,22.5,-1.875],\"uv_offset\":[32,0],\"faces\":{\"north\":{\"uv\":[40,8,48,16],\"texture\":0},\"east\":{\"uv\":[32,8,40,16],\"texture\":0},\"south\":{\"uv\":[56,8,64,16],\"texture\":0},\"west\":{\"uv\":[48,8,56,16],\"texture\":0},\"up\":{\"uv\":[48,8,40,0],\"texture\":0},\"down\":{\"uv\":[56,0,48,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e0f94313-bf88-492d-0c68-bd6b466a3b68\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.15,15,-0.75],\"to\":[6.85,16.5,0.75],\"autouv\":0,\"color\":2,\"origin\":[5,10.5,-1],\"faces\":{\"north\":{\"uv\":[27,22,29,24],\"texture\":1},\"east\":{\"uv\":[27,24,29,26],\"texture\":1},\"south\":{\"uv\":[27,26,29,28],\"texture\":1},\"west\":{\"uv\":[0,28,2,30],\"texture\":1},\"up\":{\"uv\":[30,2,28,0],\"texture\":1},\"down\":{\"uv\":[30,9,28,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"aa7d3639-52cb-087e-0f54-7e31afe0d4b3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,19,0.25],\"to\":[6.8,21,3.75],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[28,11,30,13],\"texture\":1},\"east\":{\"uv\":[17,21,21,23],\"texture\":1},\"south\":{\"uv\":[19,28,21,30],\"texture\":1},\"west\":{\"uv\":[21,21,25,23],\"texture\":1},\"up\":{\"uv\":[24,4,22,0],\"texture\":1},\"down\":{\"uv\":[24,4,22,8],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"596ead89-97bf-b419-dc31-372fe3dee9b9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.75,18.5,-1.25],\"to\":[7.25,21,1.25],\"autouv\":0,\"color\":2,\"rotation\":[-45,0,0],\"origin\":[6,19.75,0],\"faces\":{\"north\":{\"uv\":[17,18,20,21],\"texture\":1},\"east\":{\"uv\":[19,0,22,3],\"texture\":1},\"south\":{\"uv\":[19,3,22,6],\"texture\":1},\"west\":{\"uv\":[19,6,22,9],\"texture\":1},\"up\":{\"uv\":[22,12,19,9],\"texture\":1},\"down\":{\"uv\":[22,12,19,15],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"4853fc6c-23a8-6a4b-07e2-d89b3aaa8c1c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.225,19,0.375],\"to\":[6.775,20.5,2.5],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,19.5,2.625],\"faces\":{\"north\":{\"uv\":[21,28,23,30],\"texture\":1},\"east\":{\"uv\":[23,28,25,30],\"texture\":1},\"south\":{\"uv\":[25,28,27,30],\"texture\":1},\"west\":{\"uv\":[27,28,29,30],\"texture\":1},\"up\":{\"uv\":[31,4,29,2],\"texture\":1},\"down\":{\"uv\":[31,4,29,6],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a04ef22a-5322-93ad-5281-e6b33566d6af\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.225,19,-2.5],\"to\":[6.775,20.5,-0.375],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,19.5,-2.625],\"faces\":{\"north\":{\"uv\":[29,6,31,8],\"texture\":1},\"east\":{\"uv\":[29,13,31,15],\"texture\":1},\"south\":{\"uv\":[29,15,31,17],\"texture\":1},\"west\":{\"uv\":[29,17,31,19],\"texture\":1},\"up\":{\"uv\":[31,21,29,19],\"texture\":1},\"down\":{\"uv\":[31,21,29,23],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"dbcf2b15-20a0-6f43-48de-9c73557825c3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.15,17.5,-0.75],\"to\":[6.85,19,0.75],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[29,23,31,25],\"texture\":1},\"east\":{\"uv\":[29,25,31,27],\"texture\":1},\"south\":{\"uv\":[29,27,31,29],\"texture\":1},\"west\":{\"uv\":[29,29,31,31],\"texture\":1},\"up\":{\"uv\":[2,32,0,30],\"texture\":1},\"down\":{\"uv\":[32,0,30,2],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"656eb85b-2130-71d1-8400-036016caf911\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.4,10.05,-0.6],\"to\":[6.6,10.75,0.6],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,10.4,0],\"faces\":{\"north\":{\"uv\":[28,2,29,3],\"texture\":1},\"east\":{\"uv\":[29,8,30,9],\"texture\":1},\"south\":{\"uv\":[30,12,31,13],\"texture\":1},\"west\":{\"uv\":[27,35,28,36],\"texture\":1},\"up\":{\"uv\":[36,29,35,28],\"texture\":1},\"down\":{\"uv\":[30,35,29,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"61014d9b-3a39-4bbb-24b1-c1796ff81448\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,19.925,2.375],\"to\":[6.825,21.675,3.6],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,20.925,2.3],\"faces\":{\"north\":{\"uv\":[5,30,7,32],\"texture\":1},\"east\":{\"uv\":[22,16,23,18],\"texture\":1},\"south\":{\"uv\":[30,8,32,10],\"texture\":1},\"west\":{\"uv\":[24,14,25,16],\"texture\":1},\"up\":{\"uv\":[25,21,23,20],\"texture\":1},\"down\":{\"uv\":[34,11,32,12],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"6e13574b-8944-aaee-8ad1-92edda71d4a3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,19.925,-3.6],\"to\":[6.825,21.675,-2.375],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,20.925,-2.3],\"faces\":{\"north\":{\"uv\":[30,10,32,12],\"texture\":1},\"east\":{\"uv\":[33,9,34,11],\"texture\":1},\"south\":{\"uv\":[17,30,19,32],\"texture\":1},\"west\":{\"uv\":[33,12,34,14],\"texture\":1},\"up\":{\"uv\":[35,4,33,3],\"texture\":1},\"down\":{\"uv\":[35,14,33,15],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"5e0081ae-2c0e-03b7-b533-f33b34b8b9c8\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.25,6.25,-0.5],\"to\":[6.75,7.5,0.75],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,7,0],\"faces\":{\"north\":{\"uv\":[33,31,35,32],\"texture\":1},\"east\":{\"uv\":[34,35,35,36],\"texture\":1},\"south\":{\"uv\":[33,32,35,33],\"texture\":1},\"west\":{\"uv\":[35,34,36,35],\"texture\":1},\"up\":{\"uv\":[35,34,33,33],\"texture\":1},\"down\":{\"uv\":[36,0,34,1],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"926dfb9a-8a36-2d53-6026-df9e212d6187\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5,13.75,-0.9],\"to\":[7,15,0.9],\"autouv\":0,\"color\":2,\"origin\":[5,10.25,-1],\"faces\":{\"north\":{\"uv\":[34,1,36,2],\"texture\":1},\"east\":{\"uv\":[34,2,36,3],\"texture\":1},\"south\":{\"uv\":[34,4,36,5],\"texture\":1},\"west\":{\"uv\":[5,34,7,35],\"texture\":1},\"up\":{\"uv\":[23,32,21,30],\"texture\":1},\"down\":{\"uv\":[25,30,23,32],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"34f7ace6-abb3-82f1-ffe6-aa94ccf29240\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.025,14.05,0.725],\"to\":[6.975,15.2,1.2],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,14.675,0.3],\"faces\":{\"north\":{\"uv\":[35,3,37,4],\"texture\":1},\"east\":{\"uv\":[18,38,19,39],\"texture\":1},\"south\":{\"uv\":[4,35,6,36],\"texture\":1},\"west\":{\"uv\":[38,18,39,19],\"texture\":1},\"up\":{\"uv\":[8,36,6,35],\"texture\":1},\"down\":{\"uv\":[15,35,13,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"3616c0c5-2bd5-b180-4ed5-2708c59c41bc\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.025,14.05,-1.2],\"to\":[6.975,15.2,-0.725],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,14.675,-0.3],\"faces\":{\"north\":{\"uv\":[35,14,37,15],\"texture\":1},\"east\":{\"uv\":[19,38,20,39],\"texture\":1},\"south\":{\"uv\":[15,35,17,36],\"texture\":1},\"west\":{\"uv\":[38,19,39,20],\"texture\":1},\"up\":{\"uv\":[19,36,17,35],\"texture\":1},\"down\":{\"uv\":[21,35,19,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"20fe1d98-3b1b-7a8f-7945-78be2423e61c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.05,13.025,-0.9],\"to\":[6.95,14.3,0.375],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,13.4,0],\"faces\":{\"north\":{\"uv\":[34,30,36,31],\"texture\":1},\"east\":{\"uv\":[17,38,18,39],\"texture\":1},\"south\":{\"uv\":[31,34,33,35],\"texture\":1},\"west\":{\"uv\":[38,17,39,18],\"texture\":1},\"up\":{\"uv\":[35,35,33,34],\"texture\":1},\"down\":{\"uv\":[4,35,2,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"bec338ce-2cb6-bade-c2a0-bf4b0256b6b2\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.4,9.05,-0.6],\"to\":[6.6,9.75,0.6],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,9.4,0],\"faces\":{\"north\":{\"uv\":[22,38,23,39],\"texture\":1},\"east\":{\"uv\":[38,22,39,23],\"texture\":1},\"south\":{\"uv\":[23,38,24,39],\"texture\":1},\"west\":{\"uv\":[38,23,39,24],\"texture\":1},\"up\":{\"uv\":[25,39,24,38],\"texture\":1},\"down\":{\"uv\":[39,24,38,25],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"cc8271b8-7122-2c3d-7a10-390515d38ea2\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.4,8.05,-0.6],\"to\":[6.6,8.75,0.6],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,8.4,0],\"faces\":{\"north\":{\"uv\":[35,35,36,36],\"texture\":1},\"east\":{\"uv\":[0,36,1,37],\"texture\":1},\"south\":{\"uv\":[36,0,37,1],\"texture\":1},\"west\":{\"uv\":[1,36,2,37],\"texture\":1},\"up\":{\"uv\":[37,2,36,1],\"texture\":1},\"down\":{\"uv\":[3,36,2,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2d28b727-641e-2dc9-8e5f-15c9ed002244\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,7,-0.5],\"to\":[6.5,13.75,0.5],\"autouv\":0,\"color\":2,\"origin\":[5,10.5,-1],\"faces\":{\"north\":{\"uv\":[2,24,3,31],\"texture\":1},\"east\":{\"uv\":[3,24,4,31],\"texture\":1},\"south\":{\"uv\":[4,24,5,31],\"texture\":1},\"west\":{\"uv\":[24,4,25,11],\"texture\":1},\"up\":{\"uv\":[37,3,36,2],\"texture\":1},\"down\":{\"uv\":[4,36,3,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"68112592-c5eb-c6ba-7370-8df3fd15c5be\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[6.3,14.7,0.925],\"to\":[6.75,19.15,1.675],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6.55,17.075,1.425],\"faces\":{\"north\":{\"uv\":[25,30,26,34],\"texture\":1},\"east\":{\"uv\":[26,30,27,34],\"texture\":1},\"south\":{\"uv\":[27,30,28,34],\"texture\":1},\"west\":{\"uv\":[28,30,29,34],\"texture\":1},\"up\":{\"uv\":[5,37,4,36],\"texture\":1},\"down\":{\"uv\":[37,4,36,5],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"6bfcb5a6-b98e-2362-4350-0cff8e7c8417\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.25,14.7,0.925],\"to\":[5.7,19.15,1.675],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[5.45,17.075,1.425],\"faces\":{\"north\":{\"uv\":[2,31,3,35],\"texture\":1},\"east\":{\"uv\":[31,2,32,6],\"texture\":1},\"south\":{\"uv\":[3,31,4,35],\"texture\":1},\"west\":{\"uv\":[4,31,5,35],\"texture\":1},\"up\":{\"uv\":[6,37,5,36],\"texture\":1},\"down\":{\"uv\":[37,5,36,6],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"8ec387cf-bccc-0ef1-a478-2c3c0c430d41\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.95,18.575,-5.425],\"to\":[7.05,21.45,-3.75],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,20.925,-4.45],\"faces\":{\"north\":{\"uv\":[21,25,23,28],\"texture\":1},\"east\":{\"uv\":[23,25,25,28],\"texture\":1},\"south\":{\"uv\":[25,25,27,28],\"texture\":1},\"west\":{\"uv\":[26,0,28,3],\"texture\":1},\"up\":{\"uv\":[33,8,31,6],\"texture\":1},\"down\":{\"uv\":[33,12,31,14],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"da16de9a-4659-1141-c033-4ab1a11199da\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,19,-3.75],\"to\":[6.8,21,-0.25],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[31,14,33,16],\"texture\":1},\"east\":{\"uv\":[19,23,23,25],\"texture\":1},\"south\":{\"uv\":[31,16,33,18],\"texture\":1},\"west\":{\"uv\":[23,23,27,25],\"texture\":1},\"up\":{\"uv\":[2,28,0,24],\"texture\":1},\"down\":{\"uv\":[26,0,24,4],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"b55c73af-0b1d-14ae-5198-2782a6d7294a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,18.075,5.2],\"to\":[6.8,20.675,6.3],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,1.75],\"faces\":{\"north\":{\"uv\":[26,10,28,13],\"texture\":1},\"east\":{\"uv\":[32,28,33,31],\"texture\":1},\"south\":{\"uv\":[27,3,29,6],\"texture\":1},\"west\":{\"uv\":[32,31,33,34],\"texture\":1},\"up\":{\"uv\":[36,14,34,13],\"texture\":1},\"down\":{\"uv\":[36,15,34,16],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a2bc1c86-9f7f-2d61-139f-584cbbdc9ca8\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.425,5.875],\"to\":[6.55,20.325,6.65],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,19.475,6.2],\"faces\":{\"north\":{\"uv\":[21,35,22,37],\"texture\":1},\"east\":{\"uv\":[35,21,36,23],\"texture\":1},\"south\":{\"uv\":[22,35,23,37],\"texture\":1},\"west\":{\"uv\":[23,35,24,37],\"texture\":1},\"up\":{\"uv\":[21,39,20,38],\"texture\":1},\"down\":{\"uv\":[39,20,38,21],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"976bb6d9-d09f-36bb-8bb6-62fe808734f1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.425,-6.65],\"to\":[6.55,20.325,-5.875],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,19.475,-6.2],\"faces\":{\"north\":{\"uv\":[35,23,36,25],\"texture\":1},\"east\":{\"uv\":[24,35,25,37],\"texture\":1},\"south\":{\"uv\":[35,25,36,27],\"texture\":1},\"west\":{\"uv\":[26,35,27,37],\"texture\":1},\"up\":{\"uv\":[22,39,21,38],\"texture\":1},\"down\":{\"uv\":[39,21,38,22],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"db87f23a-29dc-e8a1-0d0d-9a2f369185af\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.95,18.575,3.75],\"to\":[7.05,21.45,5.425],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,20.925,4.45],\"faces\":{\"north\":{\"uv\":[5,27,7,30],\"texture\":1},\"east\":{\"uv\":[27,6,29,9],\"texture\":1},\"south\":{\"uv\":[27,13,29,16],\"texture\":1},\"west\":{\"uv\":[27,16,29,19],\"texture\":1},\"up\":{\"uv\":[33,20,31,18],\"texture\":1},\"down\":{\"uv\":[33,20,31,22],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"0714f281-db15-8143-c32f-e89576734cad\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.725,4.475],\"to\":[6.55,20.2,5.2],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,1.75],\"faces\":{\"north\":{\"uv\":[8,36,9,37],\"texture\":1},\"east\":{\"uv\":[36,8,37,9],\"texture\":1},\"south\":{\"uv\":[9,36,10,37],\"texture\":1},\"west\":{\"uv\":[36,9,37,10],\"texture\":1},\"up\":{\"uv\":[11,37,10,36],\"texture\":1},\"down\":{\"uv\":[37,10,36,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a4916b0b-ece2-a56b-c694-cb963a9a80b5\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,18.075,-6.3],\"to\":[6.8,20.675,-5.2],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,-1.75],\"faces\":{\"north\":{\"uv\":[17,27,19,30],\"texture\":1},\"east\":{\"uv\":[33,0,34,3],\"texture\":1},\"south\":{\"uv\":[27,19,29,22],\"texture\":1},\"west\":{\"uv\":[33,6,34,9],\"texture\":1},\"up\":{\"uv\":[36,17,34,16],\"texture\":1},\"down\":{\"uv\":[36,17,34,18],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"9916eeef-31e1-9c75-ffda-14bd4d589032\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.725,-5.2],\"to\":[6.55,20.2,-4.475],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,-1.75],\"faces\":{\"north\":{\"uv\":[11,36,12,37],\"texture\":1},\"east\":{\"uv\":[36,11,37,12],\"texture\":1},\"south\":{\"uv\":[12,36,13,37],\"texture\":1},\"west\":{\"uv\":[36,12,37,13],\"texture\":1},\"up\":{\"uv\":[14,37,13,36],\"texture\":1},\"down\":{\"uv\":[37,13,36,14],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"cb63eadb-42fb-9f2c-0cb2-f6b85a7607f9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.25,14.7,-1.675],\"to\":[5.7,19.15,-0.925],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[5.45,17.075,-1.425],\"faces\":{\"north\":{\"uv\":[31,22,32,26],\"texture\":1},\"east\":{\"uv\":[31,26,32,30],\"texture\":1},\"south\":{\"uv\":[29,31,30,35],\"texture\":1},\"west\":{\"uv\":[30,31,31,35],\"texture\":1},\"up\":{\"uv\":[15,37,14,36],\"texture\":1},\"down\":{\"uv\":[16,36,15,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"8ae9fcff-9cca-67c2-69e2-f50e85162de2\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[6.3,14.7,-1.675],\"to\":[6.75,19.15,-0.925],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6.55,17.075,-1.425],\"faces\":{\"north\":{\"uv\":[31,30,32,34],\"texture\":1},\"east\":{\"uv\":[0,32,1,36],\"texture\":1},\"south\":{\"uv\":[32,0,33,4],\"texture\":1},\"west\":{\"uv\":[1,32,2,36],\"texture\":1},\"up\":{\"uv\":[37,16,36,15],\"texture\":1},\"down\":{\"uv\":[17,36,16,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"792785c8-f110-dcf7-d0e4-61f223963cfd\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5,16.5,-1.05],\"to\":[7,17.5,1.05],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[34,18,36,19],\"texture\":1},\"east\":{\"uv\":[19,34,21,35],\"texture\":1},\"south\":{\"uv\":[34,19,36,20],\"texture\":1},\"west\":{\"uv\":[34,20,36,21],\"texture\":1},\"up\":{\"uv\":[34,6,32,4],\"texture\":1},\"down\":{\"uv\":[7,32,5,34],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2ad8829a-0eaf-c32f-765a-76ad72a1b336\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.75,16.75,1],\"to\":[6.25,17.25,1.7],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[36,16,37,17],\"texture\":1},\"east\":{\"uv\":[17,36,18,37],\"texture\":1},\"south\":{\"uv\":[36,17,37,18],\"texture\":1},\"west\":{\"uv\":[18,36,19,37],\"texture\":1},\"up\":{\"uv\":[37,19,36,18],\"texture\":1},\"down\":{\"uv\":[20,36,19,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2181542e-699e-c408-2ec8-b291a6325c33\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,16.75,1.55],\"to\":[6.5,17.25,2.05],\"autouv\":0,\"color\":2,\"rotation\":[-45,0,0],\"origin\":[6,17,1.8],\"faces\":{\"north\":{\"uv\":[36,19,37,20],\"texture\":1},\"east\":{\"uv\":[20,36,21,37],\"texture\":1},\"south\":{\"uv\":[36,20,37,21],\"texture\":1},\"west\":{\"uv\":[36,21,37,22],\"texture\":1},\"up\":{\"uv\":[37,23,36,22],\"texture\":1},\"down\":{\"uv\":[37,23,36,24],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"58cd5f7e-7a90-1ed7-b5c8-5d739d36bb28\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.75,16.75,-1.7],\"to\":[6.25,17.25,-1],\"autouv\":0,\"color\":2,\"origin\":[5,13,1],\"faces\":{\"north\":{\"uv\":[36,24,37,25],\"texture\":1},\"east\":{\"uv\":[25,36,26,37],\"texture\":1},\"south\":{\"uv\":[36,25,37,26],\"texture\":1},\"west\":{\"uv\":[36,26,37,27],\"texture\":1},\"up\":{\"uv\":[28,37,27,36],\"texture\":1},\"down\":{\"uv\":[37,27,36,28],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"71ebf1c3-a5f4-6dbf-8533-0b7bdc6093f4\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,16.75,-2.05],\"to\":[6.5,17.25,-1.55],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,17,-1.8],\"faces\":{\"north\":{\"uv\":[28,36,29,37],\"texture\":1},\"east\":{\"uv\":[36,28,37,29],\"texture\":1},\"south\":{\"uv\":[29,36,30,37],\"texture\":1},\"west\":{\"uv\":[36,29,37,30],\"texture\":1},\"up\":{\"uv\":[31,37,30,36],\"texture\":1},\"down\":{\"uv\":[37,30,36,31],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"3f2aa919-4264-20d2-4dc0-5b3963226082\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,21.3,-3.725],\"to\":[6.825,21.95,-3.5],\"autouv\":0,\"color\":2,\"rotation\":[-45,0,0],\"origin\":[6,21.2,-3.425],\"faces\":{\"north\":{\"uv\":[34,5,36,6],\"texture\":1},\"east\":{\"uv\":[6,36,7,37],\"texture\":1},\"south\":{\"uv\":[34,6,36,7],\"texture\":1},\"west\":{\"uv\":[36,6,37,7],\"texture\":1},\"up\":{\"uv\":[36,8,34,7],\"texture\":1},\"down\":{\"uv\":[36,8,34,9],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"188d0aa7-7147-c70b-58ea-850b803f6a8f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,21.3,3.5],\"to\":[6.825,21.95,3.725],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,21.2,3.425],\"faces\":{\"north\":{\"uv\":[34,9,36,10],\"texture\":1},\"east\":{\"uv\":[7,36,8,37],\"texture\":1},\"south\":{\"uv\":[34,10,36,11],\"texture\":1},\"west\":{\"uv\":[36,7,37,8],\"texture\":1},\"up\":{\"uv\":[36,12,34,11],\"texture\":1},\"down\":{\"uv\":[36,12,34,13],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"7de5534c-d30a-43f1-6eb9-25858b29fce0\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,41.05,-1.3],\"to\":[6.5,44.55,0.7],\"autouv\":0,\"color\":1,\"rotation\":[-20,0,0],\"origin\":[6,48.8,-0.55],\"faces\":{\"north\":{\"uv\":[19,30,20,34],\"texture\":1},\"east\":{\"uv\":[23,16,25,20],\"texture\":1},\"south\":{\"uv\":[20,30,21,34],\"texture\":1},\"west\":{\"uv\":[17,23,19,27],\"texture\":1},\"up\":{\"uv\":[34,21,33,19],\"texture\":1},\"down\":{\"uv\":[34,21,33,23],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e85c96f1-e8a9-dc1b-28ef-5270bd4fdeea\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24,1],\"to\":[6.5,41.75,2.25],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[8,0,9,18],\"texture\":1},\"east\":{\"uv\":[9,0,10,18],\"texture\":1},\"south\":{\"uv\":[10,0,11,18],\"texture\":1},\"west\":{\"uv\":[11,0,12,18],\"texture\":1},\"up\":{\"uv\":[26,11,25,10],\"texture\":1},\"down\":{\"uv\":[27,3,26,4],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e2626689-c30d-fc58-7143-b714a916e0e3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,21,2.45],\"to\":[6.5,24,3.15],\"autouv\":0,\"color\":1,\"rotation\":[0,45,0],\"origin\":[6,28.5,2.95],\"faces\":{\"north\":{\"uv\":[13,32,14,35],\"texture\":1},\"east\":{\"uv\":[14,32,15,35],\"texture\":1},\"south\":{\"uv\":[15,32,16,35],\"texture\":1},\"west\":{\"uv\":[16,32,17,35],\"texture\":1},\"up\":{\"uv\":[36,30,35,29],\"texture\":1},\"down\":{\"uv\":[31,35,30,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"b4e24ddd-0288-312f-4d29-4ee1bcad5926\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,24,1.95],\"to\":[6.5,41.75,2.65],\"autouv\":0,\"color\":1,\"rotation\":[0,45,0],\"origin\":[6,31.5,2.45],\"faces\":{\"north\":{\"uv\":[9,18,10,36],\"texture\":1},\"east\":{\"uv\":[10,18,11,36],\"texture\":1},\"south\":{\"uv\":[11,18,12,36],\"texture\":1},\"west\":{\"uv\":[12,18,13,36],\"texture\":1},\"up\":{\"uv\":[34,36,33,35],\"texture\":1},\"down\":{\"uv\":[36,33,35,34],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"c7057fe5-eff1-3481-3701-b964a84f1c58\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,24,-2.65],\"to\":[6.5,41.75,-1.95],\"autouv\":0,\"color\":1,\"rotation\":[0,-45,0],\"origin\":[6,31.5,-2.45],\"faces\":{\"north\":{\"uv\":[16,0,17,18],\"texture\":1},\"east\":{\"uv\":[17,0,18,18],\"texture\":1},\"south\":{\"uv\":[18,0,19,18],\"texture\":1},\"west\":{\"uv\":[8,18,9,36],\"texture\":1},\"up\":{\"uv\":[32,36,31,35],\"texture\":1},\"down\":{\"uv\":[36,31,35,32],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"662518b3-6709-eb4e-6278-8c012d80c705\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,21,-3.15],\"to\":[6.5,24,-2.45],\"autouv\":0,\"color\":1,\"rotation\":[0,-45,0],\"origin\":[6,28.5,-2.95],\"faces\":{\"north\":{\"uv\":[32,22,33,25],\"texture\":1},\"east\":{\"uv\":[23,32,24,35],\"texture\":1},\"south\":{\"uv\":[24,32,25,35],\"texture\":1},\"west\":{\"uv\":[32,25,33,28],\"texture\":1},\"up\":{\"uv\":[33,36,32,35],\"texture\":1},\"down\":{\"uv\":[36,32,35,33],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"5bf81b09-08ec-d7a1-c186-9a432aac7ca1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.75,21,-1.25],\"to\":[6.25,44.5,1.25],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[6,0,7,24],\"texture\":1},\"east\":{\"uv\":[0,0,3,24],\"texture\":1},\"south\":{\"uv\":[7,0,8,24],\"texture\":1},\"west\":{\"uv\":[3,0,6,24],\"texture\":1},\"up\":{\"uv\":[8,35,7,32],\"texture\":1},\"down\":{\"uv\":[33,8,32,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a9a094c2-0f3d-58d6-b21e-6ba6ba3b526b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,43.85,-2.15],\"to\":[6.5,46.9,0.9],\"autouv\":0,\"color\":1,\"rotation\":[-45,0,0],\"origin\":[6,46,0],\"faces\":{\"north\":{\"uv\":[5,24,7,27],\"texture\":1},\"east\":{\"uv\":[19,15,22,18],\"texture\":1},\"south\":{\"uv\":[24,11,26,14],\"texture\":1},\"west\":{\"uv\":[20,18,23,21],\"texture\":1},\"up\":{\"uv\":[27,7,25,4],\"texture\":1},\"down\":{\"uv\":[27,7,25,10],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"64526f07-ded7-1117-c95e-5e72febd29f7\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24,-2.25],\"to\":[6.5,41.75,-1],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[12,0,13,18],\"texture\":1},\"east\":{\"uv\":[13,0,14,18],\"texture\":1},\"south\":{\"uv\":[14,0,15,18],\"texture\":1},\"west\":{\"uv\":[15,0,16,18],\"texture\":1},\"up\":{\"uv\":[27,14,26,13],\"texture\":1},\"down\":{\"uv\":[28,9,27,10],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"d6541026-10ee-fe6a-8fe9-72cf75d33923\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,21,1.25],\"to\":[6.5,24,2.75],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[21,32,22,35],\"texture\":1},\"east\":{\"uv\":[19,25,21,28],\"texture\":1},\"south\":{\"uv\":[22,32,23,35],\"texture\":1},\"west\":{\"uv\":[25,20,27,23],\"texture\":1},\"up\":{\"uv\":[34,29,33,27],\"texture\":1},\"down\":{\"uv\":[34,29,33,31],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"791744bb-3a31-3779-4bdf-f486a6933875\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,21,-2.75],\"to\":[6.5,24,-1.25],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[17,32,18,35],\"texture\":1},\"east\":{\"uv\":[25,14,27,17],\"texture\":1},\"south\":{\"uv\":[18,32,19,35],\"texture\":1},\"west\":{\"uv\":[25,17,27,20],\"texture\":1},\"up\":{\"uv\":[34,25,33,23],\"texture\":1},\"down\":{\"uv\":[34,25,33,27],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"d9dda869-2759-4199-c1d8-b42d195aa834\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,41.05,-0.7],\"to\":[6.5,44.55,1.3],\"autouv\":0,\"color\":1,\"rotation\":[20,0,0],\"origin\":[6,48.8,0.55],\"faces\":{\"north\":{\"uv\":[7,24,8,28],\"texture\":1},\"east\":{\"uv\":[22,8,24,12],\"texture\":1},\"south\":{\"uv\":[7,28,8,32],\"texture\":1},\"west\":{\"uv\":[22,12,24,16],\"texture\":1},\"up\":{\"uv\":[34,17,33,15],\"texture\":1},\"down\":{\"uv\":[34,17,33,19],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"abcc99c6-960c-1045-82bd-0a39d53f7a49\"},{\"name\":\"rune_1\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,21.975,-0.125],\"to\":[6.5,22.475,0.125],\"autouv\":0,\"color\":0,\"origin\":[6,35.975,0.375],\"faces\":{\"north\":{\"uv\":[2,38,3,39],\"texture\":1},\"east\":{\"uv\":[38,2,39,3],\"texture\":1},\"south\":{\"uv\":[3,38,4,39],\"texture\":1},\"west\":{\"uv\":[38,3,39,4],\"texture\":1},\"up\":{\"uv\":[5,39,4,38],\"texture\":1},\"down\":{\"uv\":[39,4,38,5],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"44592a16-3ff8-e5d6-5197-a3d5fdb91e94\"},{\"name\":\"rune_2\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,22.5625,-0.75],\"to\":[6.5,23.3125,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,21.8125,0],\"faces\":{\"north\":{\"uv\":[37,29,38,30],\"texture\":1},\"east\":{\"uv\":[30,37,31,38],\"texture\":1},\"south\":{\"uv\":[37,30,38,31],\"texture\":1},\"west\":{\"uv\":[31,37,32,38],\"texture\":1},\"up\":{\"uv\":[38,32,37,31],\"texture\":1},\"down\":{\"uv\":[33,37,32,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"49542da5-539a-7743-3bbc-afdee9d770c3\"},{\"name\":\"rune_2\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,22.1875,-1.5],\"to\":[6.5,22.5625,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,21.8125,0],\"faces\":{\"north\":{\"uv\":[37,36,38,37],\"texture\":1},\"east\":{\"uv\":[37,37,38,38],\"texture\":1},\"south\":{\"uv\":[0,38,1,39],\"texture\":1},\"west\":{\"uv\":[38,0,39,1],\"texture\":1},\"up\":{\"uv\":[2,39,1,38],\"texture\":1},\"down\":{\"uv\":[39,1,38,2],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a79acc07-9567-e68d-0a57-109e40d9acbc\"},{\"name\":\"rune_3\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24.25,-1.875],\"to\":[6.5,24.625,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,25,0],\"faces\":{\"north\":{\"uv\":[37,35,38,36],\"texture\":1},\"east\":{\"uv\":[26,34,28,35],\"texture\":1},\"south\":{\"uv\":[36,37,37,38],\"texture\":1},\"west\":{\"uv\":[34,27,36,28],\"texture\":1},\"up\":{\"uv\":[29,36,28,34],\"texture\":1},\"down\":{\"uv\":[35,28,34,30],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"8cadf035-ee63-7e4e-858c-fc3f8115fa7e\"},{\"name\":\"rune_3\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,23.125,-0.75],\"to\":[6.5,24.25,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,25,0],\"faces\":{\"north\":{\"uv\":[37,32,38,33],\"texture\":1},\"east\":{\"uv\":[33,37,34,38],\"texture\":1},\"south\":{\"uv\":[37,33,38,34],\"texture\":1},\"west\":{\"uv\":[34,37,35,38],\"texture\":1},\"up\":{\"uv\":[38,35,37,34],\"texture\":1},\"down\":{\"uv\":[36,37,35,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a9027c5e-52c6-19e9-e232-a35125734ea3\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24.35,-0.125],\"to\":[6.5,37.85,0.125],\"autouv\":0,\"color\":0,\"origin\":[6,36.1,0.375],\"faces\":{\"north\":{\"uv\":[13,18,14,32],\"texture\":1},\"east\":{\"uv\":[14,18,15,32],\"texture\":1},\"south\":{\"uv\":[15,18,16,32],\"texture\":1},\"west\":{\"uv\":[16,18,17,32],\"texture\":1},\"up\":{\"uv\":[38,11,37,10],\"texture\":1},\"down\":{\"uv\":[12,37,11,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2cec3ae3-79ee-9f96-74f5-0e90611c5e6d\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,-0.75],\"to\":[6.5,26.05,-0.3],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[5.875,25.725,-0.425],\"faces\":{\"north\":{\"uv\":[5,38,6,39],\"texture\":1},\"east\":{\"uv\":[38,5,39,6],\"texture\":1},\"south\":{\"uv\":[6,38,7,39],\"texture\":1},\"west\":{\"uv\":[38,6,39,7],\"texture\":1},\"up\":{\"uv\":[8,39,7,38],\"texture\":1},\"down\":{\"uv\":[39,7,38,8],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e204bb47-1379-ea43-8d02-0b48c88a6282\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,-0.375],\"to\":[6.5,25.85,-0.125],\"autouv\":0,\"color\":0,\"origin\":[6,30.1,0.375],\"faces\":{\"north\":{\"uv\":[8,38,9,39],\"texture\":1},\"east\":{\"uv\":[38,8,39,9],\"texture\":1},\"south\":{\"uv\":[9,38,10,39],\"texture\":1},\"west\":{\"uv\":[38,9,39,10],\"texture\":1},\"up\":{\"uv\":[11,39,10,38],\"texture\":1},\"down\":{\"uv\":[39,10,38,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"4c44df55-108a-c9c8-3c9c-3c3bdd7c3a6a\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,0.3],\"to\":[6.5,26.05,0.75],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[5.875,25.725,0.425],\"faces\":{\"north\":{\"uv\":[11,38,12,39],\"texture\":1},\"east\":{\"uv\":[38,11,39,12],\"texture\":1},\"south\":{\"uv\":[12,38,13,39],\"texture\":1},\"west\":{\"uv\":[38,12,39,13],\"texture\":1},\"up\":{\"uv\":[14,39,13,38],\"texture\":1},\"down\":{\"uv\":[39,13,38,14],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a4083602-2242-1b62-262d-bfd38f8c77f4\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,0.125],\"to\":[6.5,25.85,0.375],\"autouv\":0,\"color\":0,\"origin\":[6,30.1,-0.375],\"faces\":{\"north\":{\"uv\":[14,38,15,39],\"texture\":1},\"east\":{\"uv\":[38,14,39,15],\"texture\":1},\"south\":{\"uv\":[15,38,16,39],\"texture\":1},\"west\":{\"uv\":[38,15,39,16],\"texture\":1},\"up\":{\"uv\":[17,39,16,38],\"texture\":1},\"down\":{\"uv\":[39,16,38,17],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"138546ac-7f07-33e3-b36d-0e06f98d386c\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.875,-0.5],\"to\":[6.5,38.375,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,37.375,0],\"faces\":{\"north\":{\"uv\":[37,4,38,5],\"texture\":1},\"east\":{\"uv\":[5,37,6,38],\"texture\":1},\"south\":{\"uv\":[37,5,38,6],\"texture\":1},\"west\":{\"uv\":[6,37,7,38],\"texture\":1},\"up\":{\"uv\":[38,7,37,6],\"texture\":1},\"down\":{\"uv\":[8,37,7,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"34d77773-c97c-7edd-a72c-887e3f21afcf\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.625,-1],\"to\":[6.5,37.875,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,37.375,0],\"faces\":{\"north\":{\"uv\":[37,7,38,8],\"texture\":1},\"east\":{\"uv\":[8,37,9,38],\"texture\":1},\"south\":{\"uv\":[37,8,38,9],\"texture\":1},\"west\":{\"uv\":[9,37,10,38],\"texture\":1},\"up\":{\"uv\":[38,10,37,9],\"texture\":1},\"down\":{\"uv\":[11,37,10,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"952d8c6c-c534-3ded-ebbb-8560169862f5\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.425,-0.825],\"to\":[6.5,37.575,-0.075],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[5.875,37.55,-0.2],\"faces\":{\"north\":{\"uv\":[37,11,38,12],\"texture\":1},\"east\":{\"uv\":[12,37,13,38],\"texture\":1},\"south\":{\"uv\":[37,12,38,13],\"texture\":1},\"west\":{\"uv\":[13,37,14,38],\"texture\":1},\"up\":{\"uv\":[38,14,37,13],\"texture\":1},\"down\":{\"uv\":[15,37,14,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"677371f7-759a-8b3b-225b-89324ccd9e5c\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.925,-0.575],\"to\":[6.5,37.075,-0.075],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[5.875,37.05,-0.2],\"faces\":{\"north\":{\"uv\":[37,14,38,15],\"texture\":1},\"east\":{\"uv\":[15,37,16,38],\"texture\":1},\"south\":{\"uv\":[37,15,38,16],\"texture\":1},\"west\":{\"uv\":[16,37,17,38],\"texture\":1},\"up\":{\"uv\":[38,17,37,16],\"texture\":1},\"down\":{\"uv\":[18,37,17,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"09b6fa84-9268-c1f0-d593-f49988b0a6a8\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.425,-0.325],\"to\":[6.5,36.575,-0.075],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[5.875,36.55,-0.2],\"faces\":{\"north\":{\"uv\":[37,17,38,18],\"texture\":1},\"east\":{\"uv\":[18,37,19,38],\"texture\":1},\"south\":{\"uv\":[37,18,38,19],\"texture\":1},\"west\":{\"uv\":[19,37,20,38],\"texture\":1},\"up\":{\"uv\":[38,20,37,19],\"texture\":1},\"down\":{\"uv\":[21,37,20,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2f533cab-6d66-5ebb-be57-520ac9cf1da9\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.425,0.075],\"to\":[6.5,36.575,0.325],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[5.875,36.55,0.2],\"faces\":{\"north\":{\"uv\":[37,20,38,21],\"texture\":1},\"east\":{\"uv\":[21,37,22,38],\"texture\":1},\"south\":{\"uv\":[37,21,38,22],\"texture\":1},\"west\":{\"uv\":[22,37,23,38],\"texture\":1},\"up\":{\"uv\":[38,23,37,22],\"texture\":1},\"down\":{\"uv\":[24,37,23,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e5a7d019-ec5d-ef93-5ffd-c092c664d2cb\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.925,0.075],\"to\":[6.5,37.075,0.575],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[5.875,37.05,0.2],\"faces\":{\"north\":{\"uv\":[37,23,38,24],\"texture\":1},\"east\":{\"uv\":[24,37,25,38],\"texture\":1},\"south\":{\"uv\":[37,24,38,25],\"texture\":1},\"west\":{\"uv\":[25,37,26,38],\"texture\":1},\"up\":{\"uv\":[38,26,37,25],\"texture\":1},\"down\":{\"uv\":[27,37,26,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"11316f78-2485-c065-fa4d-7b1225df2bf6\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.425,0.075],\"to\":[6.5,37.575,0.825],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[5.875,37.55,0.2],\"faces\":{\"north\":{\"uv\":[37,26,38,27],\"texture\":1},\"east\":{\"uv\":[27,37,28,38],\"texture\":1},\"south\":{\"uv\":[37,27,38,28],\"texture\":1},\"west\":{\"uv\":[28,37,29,38],\"texture\":1},\"up\":{\"uv\":[38,29,37,28],\"texture\":1},\"down\":{\"uv\":[30,37,29,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e0d85bd5-8a87-a483-22e2-833204176141\"},{\"name\":\"rune_6\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,38.25,-0.5],\"to\":[6.5,39,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,39.5,0],\"faces\":{\"north\":{\"uv\":[37,1,38,2],\"texture\":1},\"east\":{\"uv\":[2,37,3,38],\"texture\":1},\"south\":{\"uv\":[37,2,38,3],\"texture\":1},\"west\":{\"uv\":[3,37,4,38],\"texture\":1},\"up\":{\"uv\":[38,4,37,3],\"texture\":1},\"down\":{\"uv\":[5,37,4,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"101292a8-6c83-621a-11ff-e6b17d4a82e2\"},{\"name\":\"rune_6\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,39,-1.25],\"to\":[6.5,39.25,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,39.5,0],\"faces\":{\"north\":{\"uv\":[35,36,36,37],\"texture\":1},\"east\":{\"uv\":[36,35,37,36],\"texture\":1},\"south\":{\"uv\":[36,36,37,37],\"texture\":1},\"west\":{\"uv\":[0,37,1,38],\"texture\":1},\"up\":{\"uv\":[38,1,37,0],\"texture\":1},\"down\":{\"uv\":[2,37,1,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a7c81b6d-2419-772e-27a1-0068365ebce7\"},{\"name\":\"rune_7\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,40.9,-0.125],\"to\":[6.5,42.15,1.125],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[5.875,41.025,0],\"faces\":{\"north\":{\"uv\":[31,36,32,37],\"texture\":1},\"east\":{\"uv\":[36,31,37,32],\"texture\":1},\"south\":{\"uv\":[32,36,33,37],\"texture\":1},\"west\":{\"uv\":[36,32,37,33],\"texture\":1},\"up\":{\"uv\":[34,37,33,36],\"texture\":1},\"down\":{\"uv\":[37,33,36,34],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"0ac8e87a-c521-a8ee-e14d-e205560a7626\"},{\"name\":\"rune_7\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,39.025,-0.125],\"to\":[6.5,41.025,0.125],\"autouv\":0,\"color\":0,\"origin\":[6,39.525,0.375],\"faces\":{\"north\":{\"uv\":[34,21,35,23],\"texture\":1},\"east\":{\"uv\":[34,23,35,25],\"texture\":1},\"south\":{\"uv\":[25,34,26,36],\"texture\":1},\"west\":{\"uv\":[34,25,35,27],\"texture\":1},\"up\":{\"uv\":[35,37,34,36],\"texture\":1},\"down\":{\"uv\":[37,34,36,35],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"fc987521-07e4-10f9-b6fc-1512cb2da68b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1,35.8,-1],\"to\":[1,37.8,1],\"autouv\":1,\"color\":1,\"visibility\":false,\"origin\":[-1,36.8,-1],\"faces\":{\"north\":{\"uv\":[0,0,2,2]},\"east\":{\"uv\":[0,0,2,2]},\"south\":{\"uv\":[0,0,2,2]},\"west\":{\"uv\":[0,0,2,2]},\"up\":{\"uv\":[0,0,2,2]},\"down\":{\"uv\":[0,0,2,2]}},\"type\":\"cube\",\"uuid\":\"c8c75bdd-53bc-eb81-a2cb-0ea0dca06f35\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-8,0,-8],\"to\":[8,0,8],\"autouv\":1,\"color\":9,\"visibility\":false,\"origin\":[0,0,0],\"faces\":{\"north\":{\"uv\":[0,0,16,0]},\"east\":{\"uv\":[0,0,16,0]},\"south\":{\"uv\":[0,0,16,0]},\"west\":{\"uv\":[0,0,16,0]},\"up\":{\"uv\":[0,0,16,16]},\"down\":{\"uv\":[0,0,16,16]}},\"type\":\"cube\",\"uuid\":\"164a0f3b-aa15-e098-8e5a-f1fdf3e78bdd\"}],\"groups\":[{\"uuid\":\"778fa89c-759a-8884-89d9-238c555d2dc1\",\"export\":true,\"locked\":false,\"origin\":[0,17,0],\"rotation\":[0,0,0],\"color\":9,\"name\":\"player_root\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"7e09ae80-d41f-d669-2538-64dec33e0e24\",\"export\":true,\"locked\":false,\"origin\":[0,17,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"shadow\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":false},{\"uuid\":\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\",\"export\":true,\"locked\":false,\"origin\":[0,11.25,0],\"rotation\":[0,0,0],\"color\":1,\"name\":\"phip_hip\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"e297aef6-7dfd-f100-2e7c-ab113699b922\",\"export\":true,\"locked\":false,\"origin\":[0,15,0],\"rotation\":[0,0,0],\"color\":2,\"name\":\"pw_waist\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"a0c01522-9040-7533-fa11-f6a45d3d96ac\",\"export\":true,\"locked\":false,\"origin\":[0,18.75,0],\"rotation\":[0,0,0],\"color\":3,\"name\":\"pc_chest\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"34097e46-c233-c03c-d8b9-aee154c9946f\",\"export\":true,\"locked\":false,\"origin\":[0,22.5,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"h_ph_head\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\",\"export\":true,\"locked\":false,\"origin\":[5,22,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"pra_right_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"cf1618da-24d8-aab8-eebc-128815c02d35\",\"export\":true,\"locked\":false,\"origin\":[5,16.5,0],\"rotation\":[0,0,0],\"color\":8,\"name\":\"prfa_right_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"fcaf8da0-0146-2587-b578-3e1af888deaa\",\"export\":true,\"locked\":false,\"origin\":[5.625,10.875,0],\"rotation\":[-90,0,0],\"color\":0,\"name\":\"pri_right_item\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"859e9460-0016-acb1-017b-25fe27244cac\",\"export\":true,\"locked\":false,\"origin\":[5.625,44.875,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"sword_point\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"dcb3a2dc-0f46-4628-586d-44f4265cc61c\",\"export\":true,\"locked\":false,\"origin\":[6,32.15507,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"sword_body\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"b3135254-0351-3462-2479-e6a3286c89ff\",\"export\":true,\"locked\":false,\"origin\":[-5,22,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"pla_left_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\",\"export\":true,\"locked\":false,\"origin\":[-5,16.5,0],\"rotation\":[0,0,0],\"color\":8,\"name\":\"plfa_left_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"0e94ff40-15b6-0b24-d0a0-447f000d761b\",\"export\":true,\"locked\":false,\"origin\":[-5.625,10.875,0],\"rotation\":[-90,0,0],\"color\":0,\"name\":\"pli_left_item\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\",\"export\":true,\"locked\":false,\"origin\":[1.875,11.25,0],\"rotation\":[0,0,0],\"color\":7,\"name\":\"prl_right_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"c6d9e946-1d10-482d-14b1-0766027adba8\",\"export\":true,\"locked\":false,\"origin\":[1.875,5.625,0],\"rotation\":[0,0,0],\"color\":6,\"name\":\"prfl_right_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\",\"export\":true,\"locked\":false,\"origin\":[-1.875,11.25,0],\"rotation\":[0,0,0],\"color\":7,\"name\":\"pll_left_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\",\"export\":true,\"locked\":false,\"origin\":[-1.875,5.625,0],\"rotation\":[0,0,0],\"color\":6,\"name\":\"plfl_left_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":false},{\"uuid\":\"529b75e5-967f-8e76-9d18-0fc49998293a\",\"export\":true,\"locked\":false,\"origin\":[0,36.8,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"tag_name\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":false},{\"uuid\":\"dcbc3011-d7b7-bf7c-0fae-067573db08f5\",\"export\":true,\"locked\":false,\"origin\":[0,22.75,2],\"rotation\":[0,0,0],\"color\":0,\"name\":\"cape_cape\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true}],\"outliner\":[{\"uuid\":\"778fa89c-759a-8884-89d9-238c555d2dc1\",\"isOpen\":true,\"children\":[{\"uuid\":\"7e09ae80-d41f-d669-2538-64dec33e0e24\",\"isOpen\":false,\"children\":[\"164a0f3b-aa15-e098-8e5a-f1fdf3e78bdd\"]},{\"uuid\":\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\",\"isOpen\":true,\"children\":[\"ea42f7f7-a6f1-4479-c43f-48211bab5ed2\",\"357ebf82-23ba-edb1-081f-dca75d94b83c\",{\"uuid\":\"e297aef6-7dfd-f100-2e7c-ab113699b922\",\"isOpen\":true,\"children\":[\"5ea74bdb-ba28-b8e3-103b-9be6ff2262da\",\"f5b9f499-b26b-f912-1a54-151d702e13ed\",{\"uuid\":\"a0c01522-9040-7533-fa11-f6a45d3d96ac\",\"isOpen\":true,\"children\":[{\"uuid\":\"dcbc3011-d7b7-bf7c-0fae-067573db08f5\",\"isOpen\":true,\"children\":[]},\"dc1510db-a719-17b4-e253-c992a92c5d25\",\"d51a8665-a2bc-af6e-1230-5acba07248e7\",{\"uuid\":\"34097e46-c233-c03c-d8b9-aee154c9946f\",\"isOpen\":true,\"children\":[\"7f60fbaf-510d-2e5f-b7d2-9111e08443cd\",\"e0f94313-bf88-492d-0c68-bd6b466a3b68\"]},{\"uuid\":\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\",\"isOpen\":true,\"children\":[\"53d40d2e-0941-29f9-00ed-1b19c941dcd8\",\"b17452ef-afbe-e010-f2c9-e0ba945faa1f\",{\"uuid\":\"cf1618da-24d8-aab8-eebc-128815c02d35\",\"isOpen\":true,\"children\":[\"e02c395d-e1bc-1375-a8ed-729e19544ce9\",\"a509c2d7-53ef-2b11-1331-f716b9c210d6\",{\"uuid\":\"fcaf8da0-0146-2587-b578-3e1af888deaa\",\"isOpen\":true,\"children\":[{\"uuid\":\"859e9460-0016-acb1-017b-25fe27244cac\",\"isOpen\":true,\"children\":[]},\"44592a16-3ff8-e5d6-5197-a3d5fdb91e94\",\"49542da5-539a-7743-3bbc-afdee9d770c3\",\"a79acc07-9567-e68d-0a57-109e40d9acbc\",\"8cadf035-ee63-7e4e-858c-fc3f8115fa7e\",\"a9027c5e-52c6-19e9-e232-a35125734ea3\",\"2cec3ae3-79ee-9f96-74f5-0e90611c5e6d\",\"e204bb47-1379-ea43-8d02-0b48c88a6282\",\"4c44df55-108a-c9c8-3c9c-3c3bdd7c3a6a\",\"a4083602-2242-1b62-262d-bfd38f8c77f4\",\"138546ac-7f07-33e3-b36d-0e06f98d386c\",\"34d77773-c97c-7edd-a72c-887e3f21afcf\",\"952d8c6c-c534-3ded-ebbb-8560169862f5\",\"677371f7-759a-8b3b-225b-89324ccd9e5c\",\"09b6fa84-9268-c1f0-d593-f49988b0a6a8\",\"2f533cab-6d66-5ebb-be57-520ac9cf1da9\",\"e5a7d019-ec5d-ef93-5ffd-c092c664d2cb\",\"11316f78-2485-c065-fa4d-7b1225df2bf6\",\"e0d85bd5-8a87-a483-22e2-833204176141\",\"101292a8-6c83-621a-11ff-e6b17d4a82e2\",\"a7c81b6d-2419-772e-27a1-0068365ebce7\",\"0ac8e87a-c521-a8ee-e14d-e205560a7626\",\"fc987521-07e4-10f9-b6fc-1512cb2da68b\",\"aa7d3639-52cb-087e-0f54-7e31afe0d4b3\",\"596ead89-97bf-b419-dc31-372fe3dee9b9\",\"4853fc6c-23a8-6a4b-07e2-d89b3aaa8c1c\",\"a04ef22a-5322-93ad-5281-e6b33566d6af\",\"dbcf2b15-20a0-6f43-48de-9c73557825c3\",\"656eb85b-2130-71d1-8400-036016caf911\",\"61014d9b-3a39-4bbb-24b1-c1796ff81448\",\"6e13574b-8944-aaee-8ad1-92edda71d4a3\",\"5e0081ae-2c0e-03b7-b533-f33b34b8b9c8\",\"926dfb9a-8a36-2d53-6026-df9e212d6187\",\"34f7ace6-abb3-82f1-ffe6-aa94ccf29240\",\"3616c0c5-2bd5-b180-4ed5-2708c59c41bc\",\"20fe1d98-3b1b-7a8f-7945-78be2423e61c\",\"bec338ce-2cb6-bade-c2a0-bf4b0256b6b2\",\"cc8271b8-7122-2c3d-7a10-390515d38ea2\",\"2d28b727-641e-2dc9-8e5f-15c9ed002244\",\"68112592-c5eb-c6ba-7370-8df3fd15c5be\",\"6bfcb5a6-b98e-2362-4350-0cff8e7c8417\",\"8ec387cf-bccc-0ef1-a478-2c3c0c430d41\",\"da16de9a-4659-1141-c033-4ab1a11199da\",\"b55c73af-0b1d-14ae-5198-2782a6d7294a\",\"a2bc1c86-9f7f-2d61-139f-584cbbdc9ca8\",\"976bb6d9-d09f-36bb-8bb6-62fe808734f1\",\"db87f23a-29dc-e8a1-0d0d-9a2f369185af\",\"0714f281-db15-8143-c32f-e89576734cad\",\"a4916b0b-ece2-a56b-c694-cb963a9a80b5\",\"9916eeef-31e1-9c75-ffda-14bd4d589032\",\"cb63eadb-42fb-9f2c-0cb2-f6b85a7607f9\",\"8ae9fcff-9cca-67c2-69e2-f50e85162de2\",\"792785c8-f110-dcf7-d0e4-61f223963cfd\",\"2ad8829a-0eaf-c32f-765a-76ad72a1b336\",\"2181542e-699e-c408-2ec8-b291a6325c33\",\"58cd5f7e-7a90-1ed7-b5c8-5d739d36bb28\",\"71ebf1c3-a5f4-6dbf-8533-0b7bdc6093f4\",\"3f2aa919-4264-20d2-4dc0-5b3963226082\",\"188d0aa7-7147-c70b-58ea-850b803f6a8f\",\"7de5534c-d30a-43f1-6eb9-25858b29fce0\",{\"uuid\":\"dcb3a2dc-0f46-4628-586d-44f4265cc61c\",\"isOpen\":true,\"children\":[\"e85c96f1-e8a9-dc1b-28ef-5270bd4fdeea\",\"e2626689-c30d-fc58-7143-b714a916e0e3\",\"b4e24ddd-0288-312f-4d29-4ee1bcad5926\",\"c7057fe5-eff1-3481-3701-b964a84f1c58\",\"662518b3-6709-eb4e-6278-8c012d80c705\",\"5bf81b09-08ec-d7a1-c186-9a432aac7ca1\",\"a9a094c2-0f3d-58d6-b21e-6ba6ba3b526b\",\"64526f07-ded7-1117-c95e-5e72febd29f7\",\"d6541026-10ee-fe6a-8fe9-72cf75d33923\",\"791744bb-3a31-3779-4bdf-f486a6933875\",\"d9dda869-2759-4199-c1d8-b42d195aa834\",\"abcc99c6-960c-1045-82bd-0a39d53f7a49\"]}]}]}]},{\"uuid\":\"b3135254-0351-3462-2479-e6a3286c89ff\",\"isOpen\":true,\"children\":[\"addb9f66-a2a4-46f7-b6af-f5a23c00fe70\",\"5e7dc31c-c64a-ff15-90d9-52a6f77cb14b\",{\"uuid\":\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\",\"isOpen\":true,\"children\":[\"1433ee62-21ac-d72c-0fd0-37000e5eb221\",\"24bacfd6-cde8-81a0-f620-998cf5393d48\",{\"uuid\":\"0e94ff40-15b6-0b24-d0a0-447f000d761b\",\"isOpen\":true,\"children\":[]}]}]}]}]}]},{\"uuid\":\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\",\"isOpen\":true,\"children\":[\"b17675fc-b79b-0ad9-8f81-aa2dd1fc8c97\",\"2e42e483-1557-d21e-aee0-94af7bedfd40\",{\"uuid\":\"c6d9e946-1d10-482d-14b1-0766027adba8\",\"isOpen\":true,\"children\":[\"9455d16e-4bbf-4b63-881d-7daf943e782b\",\"16aee685-0ead-a542-5b3c-a62467ca45e3\"]}]},{\"uuid\":\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\",\"isOpen\":true,\"children\":[\"0e370edc-7b05-dccf-dd2a-a92cfe9f3e22\",\"dc189735-0a58-619b-07ef-feec37d65e7d\",{\"uuid\":\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\",\"isOpen\":false,\"children\":[\"6bcf768b-beab-4c39-6d96-91266036a4e1\",\"274282a7-bc7b-02f8-4dce-84226e9dd9c1\"]}]}]},{\"uuid\":\"529b75e5-967f-8e76-9d18-0fc49998293a\",\"isOpen\":false,\"children\":[\"c8c75bdd-53bc-eb81-a2cb-0ea0dca06f35\"]}],\"textures\":[{\"name\":\"-steve_template.png\",\"path\":\"\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"0\",\"group\":\"\",\"width\":64,\"height\":64,\"uv_width\":64,\"uv_height\":64,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"44166cb6-b1bf-f227-4398-96c94f36d7f7\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAApBJREFUeF7tWztLA0EQvoCgEGsTiPhAwcbOJr12WmlhYWVlr1b+gFRqoZVVKkutLLUXwc5GUHxgIBFLBa1OJrmDzbi3O+u6SHJf2rndmftudl7fphBZfmc3T7HpkfreoXGHk/puwabDJA+t32ocGVCdHtXaeHn3EhEAlVJZK2+0mtFfALA0O661k2wj/Vk6Vta3Y5t+AGBzT3gAjgBiAIJgX6fB+eaeMc+XbzaMcfLo6sAoH1h9M8oXixWjPLT+AgFQLY3o83zrNSIDJspFrfyx+RERAMXJMa384+E5IgAGKzNa+VfjNiIALspb2nRMtpH+44VhrXzt/D0m/cM7O1r5e60Wk/6hqX2t/PN+MwYAEg/YnOt4wGmj8yGXE6/dv86JB+QeAMSAfg+CpjwUOg39exrkL8/7b97v29rL0P27r338fX/kR7X74/2+pL8P3T362gcAGAJaD1Cf6fsjwHsBHvR4rc9rex7EXIOma6/gal9WGZx+5K5S+JLV/rzW57V9WsunvQRfz8+bbT/+vG1/2360HgBkNELwgAQBHAG1G0QMQBDsngBJoiylQWSBZKSGNOg4Q5TODLM8TOKhqAMkdYBagbmWmj1fCmMewBCgfjvl4zn/LuHb1fU6cH35fF/7RPOA3AOAeYCCQC7mASk3x7k4zr1xro24NZXbc+XyJNydaX+JfaI6AAAk7Cw8gNHREhfDEVD4fcQAxwsNCIKCGxzIAoYjJolRSIOSdjj3dQDmAQoCvvx7r63XssO+7XD6/wLf+wW/Xe9iPwAIPRLzvV8Qen2bGwwZBH3nCaHXtwHwTYM+c3sTsySZ+/tWigAAHoAjgBiAIIgs4DkURRrMuCAhzeO+APp4MOqAvJfC38z1ql4UO+HHAAAAAElFTkSuQmCC\"},{\"name\":\"-knight_sword.png\",\"path\":\"\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"1\",\"group\":\"\",\"width\":64,\"height\":64,\"uv_width\":64,\"uv_height\":64,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"c2982d79-5e5d-fbcd-faa3-d19a766eaaa5\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAEMRJREFUeF7tmmdYVVfWx//3ntsvcCnSLqCiGDWmzUSNZUzMq0mc2BtSbGhQYyxEBEIsY9Qx1hiFGCIqoIKABUssCVhiS2KLGonRqEhXeru9nMlZR+7I67wf5n0ez8MH9pd99in37P3b/73W2utc0cmTqaxExsBoMEMiZSCR8Mc7xROwPXApliTXY9aMqTCZGtBcRCIxdqTsw6yZk+Dr0w3DG9JxVFeKUmM5qioLUFNdgpOpBsf93EFIXA+IREqUa7bS+ZQNCVRPGJiFyu/NYP8qoXZiIovgSWPpePnyBFGLH3kODdG3x75hDXoTDV6pkMNqtcJqtWOvejIBWL6jCVHz56GqutDxerncGdu270bk9DB4e3fBiMZMApBfdhkVFQXQG0Q4vqMRIjGgUTKw2lgMneWHqqo6OPc+/wyA/IMG9BilpPNLl9mEBZBz6CtWKpXAYDBBpZLDZrXBbLYiRzONAKxMM+KD6ZOg19dArfaCTlcBTgG7Mw5j2tQwSKUqREjPEIAzN7JhMIpgswGHdzRADBHaOTGws8BfRyphtwOrVtux/siPNNiTX/8FfXurcW6PDqELe8DGMqj3TcOl7PXCKSAzayPbubMW5Y9qwDAMpBIGZosVe2ThBGBNhgXhYWNgNuvh5OSFpqYKKBQapO3MxJTJoZDLnTBZnMcvAf0DrNlwGrELXsGSj25CxAJaN17awxZ+ibK0WBisdnjHXqJzVzN6ontXBWrOm1BvYnFzcgGdD/WpxuNLGZg+Lf75L4EjR5NYra8nyVMkFsHD3YVg7JGFEYD1WXaEhY6F0dgAtdoTOl0lVKp22LkrHRPDg6FSuSOcPU4AbpcdhN1uhFyhRepKfjBX75px/MZdMBIpVozvBp2FhejJsDSvinFqr5HuO34skurwlWL0CDDhnXf7YMDfwp8/gJyDX7Ht23ujuroBDY06BHb0RXl5NfbIeQVs2i/C2LHDYTbrIJM5wWxugkrlgfSMLISGjIVS4YqJou94G1CS6bATe9ab6Pjy72YcunKNlsWysDfoHAdAJhHhaQDar8rpmjF9GQEQzAgeOJjIduzAAWhEbV0jOnfyQ0lpJbKVkwjA5hwGwePHkA2QSBSwWo0QiUQ4dPgUxo8bDrXKA+HgAeReyYa3jz9cXTXYuvgKDeJivhH7L16AWCxCRWE89LoabLbm0bVFzl0hlXfA6Q33IXMW05LRqMXwGLUWud//JIwX4BQQGKhFZWUd6uob8UKXABQWPkLWUwDGjxsNg6G2xRLI3rsP48eNgUrlijD7MQKw7/ReeHsCWt/2SFnxOw3y7K9GvBk8gY7f6pkLmxXY63Wb2v/wDIFYLMHPX/wMTfRPdI5J74fdg+6g89l4YQAcOryFANy7VwK1kxJ+2nYoLa1yxAGcAsaNHQWjsQ4ymfrJUlDjQM5RjBr5HlxcfBHOniAA2XnZ8HBn4e4G+Hc7AFeNNwZ0747BYaE0uL6v5NJS4Fzd9bwQx5rXlu9Ame80avc0pcIQxN8vSBzwfd521svTHQ8elELtpIKf1uMZAMOGvv3nfLFgGDlsNhOkUiVyDh7HmNHDSAGTRE+8gOEPrFl/Fp8sCADjuhXubn7o27UrhkwMowFxv5OQeoOO0xfbHQCifDNRrLOTm7xVbILsFf5+QQCcPJ3Kurs5o7i4EnKF9BkFfLEPCAnmvEAdLFYJpBIrucGs7ByETBgLJyd3hFiPkgLuVZ2DyVjKmTm4+O2Ep2dH9O3aA8OmhENy5yAN6q5bBNWK8GVUc0ZvvNNujIriB704Kg3dRy9E4cMyYQCcOp3Guro6obikAgq5DH5+nigtrXQsgTUZVoSGjCTjJxYzsNtt4CLB3en7MGVyGDQaL4wz5jzjBTT+6fD3fxFWi4WeWxr8Eg2wPOrfEWUzAK6OiJ5L180pfXFdLWAozCnASa1CScljKFUKcC6xtOTfAFak6BEREUJrn3N/en01L+H0I5gxYzqkUjkmiXIdNoAuAhg78Wc06Spgs4kJ2prpg+l8zcIiqq9GvoC4GDEYBuhYIMKmx7yh5OzBuq+mYlWaRRgF5J1MYZ2d1aira0RTkx7dunUgBaSJnmyGvmlAZGQoKYCbeZOpkTqakpqDOR/NgFyuQjjLu8GME1mQSPhQeEz4WRiM9TCbbeBC7S82JuC9/t9xpgQntvEbpReHKGFnWQSUA8XFNqikIvxQaIR3v4l0XRAbwMUBnQK1ePy4Fjq9AV1faI/83wpwyHU6xQGxCVWYN3c6BUDNAJRKN2z8MgnRC+aQG5sqOU0Adh7NgkIugs0OhE27iqrqIhQkjIHHlK1QKFzw40ne8p9Mb4RcIkZ9LK8Gzg5wZfa4i5BKJMi78KpwAPbnJLJBnf3x6FE17QQDArzJJe53nkoAFmx4hAXRM2gJcNbfYjGAA7BubSJi4+YRgAjpDwTAro2BWMTAYjXCbrPCaGrCz6uGIGhmMhQKV5z/npd58OBgqqcXLKeai/2/W/gXOn5v/S8YrNwk3BLg8gHePu4ke4vVggB/7xaB0EefFyE+fg4B4AbOBURSqRqrVydiyZIYSCRSTGF4BcAvDg2N5RQp3towFG8suYya2iKUlhbCZrNBZktCfV0x+ncf1WwqqM4zzEfFut6QM2IcC/uDgqAOHbXCbIbyTqWy3l5uKCurgsVihb+/Fx4+LHfkA2atfIj4+A9hs5mhVLrCYOADopUrN+Ozz+IgkcgwWXyKAFi85qNJV4na2sc4uW4aIjZfQpOuBjqdjqAU3YlGbW0ZLmToaOArv5xCdYUsDOsi36XjZgM4dFA53nhr3/PfDB06soXt2EFLS8BitVIcUFxc4QiFTe3mY/ZlDzx66RucaBcBk9GAtWtXt5jBIL8cR3vMxB8hkyooZ2CzWVBZVQSLxQyjsR5Xzs6EWAx8t6sRZiuLZZuDIJUHwslrCVaGDqG8we0yHe0Mz9w4gCFjHj5/AAdyEtkOHXxx/34xFEo5mrfGu5gQsgF6t9mYe82HAOSogiFmxFi7piWAQJ8ccmdWGzBiwnGIRAxUSjcKiDgAJSX3KNdwcNVsAtV/5TWqu9pnQsxocJtdi2Nz34BYBLw5RYpb90eh32tHhAGwJ/ML1t/fG0VFjyCVMfDx8UBJSSWOtptBAAweczHniicByJKOhMHQgOStKS0UEOibA0YMWK0s+vxPMtzdfKBWe2Dzh68DMiD4032QSORInDOMnnt7NQ+g6lx/sCzg+eYF5EX1gYwRIWRZP2zQpcJtfQAST9Q+fwWkpK1mfX08KCFiMlmh9WuHmqp6HPOeRQB0bh9i3jXfJwBGQK+vx7bktBYAJHey8FqAFCq5CDWNdtQY7OiikcIMFpeLzRgatxXu7l74YhYPQC7lx/XKMD4POCrsItavW4eexgNgenvg55tvQf9LBtYfqX7+AHanr2PlShnMRgvMJgs0rk5oajLgO9/ZBKDe+QMsuNmeByAZAb3hWQDyu5mw2VlUNtgdgzNZWNjsdihlYoxb/BVYlsW26Hmw2Fh4e/H8Rs+RI+DFw7RUtiWnwnIzg5YHlxPM3rUfvxeUPX8A6RkbWIbhwlU7GUG5TEYZm8PukQSgVh2BhbcCCcAeZih5g6+3JLdQwAeR4XBx9kTesgEIUDLQW1icKzbTPf3ayyCSiFCht+HgBT79VdzA1/Er3PFyn/1gGAmSt6bitdr9sPZkcMjvNoXKggAI/ZuGmxw8ruK3p1x5feR4quNio2Gx2BCT34kApOM9yu0lbE5sAWDGzIlwdvLEiom9YDDb4eUihkLGQCxi0WS0o7LRDpVMjMoqESngYR0fCs+L94A2aBWCgl5D0tdbweRn4u6HBRQHfJ2QisraxuevgGmDXQnAgyIbp0SK1XuN5gEs+vQT6HU6xN7uQgBSrW9DpXLBpi83tQAwNWIcbX2rqspgMNTg1qVZdJ3zLozYCXV1d6mdvFxPtclqx58pQkyMcsaA945i7qc7KQ/46RQpXRdqI8S9SzR1kIblZuf6HV6yXOk3jg9ZY2NiKDyOyeeXQKp1IBQKZyRs5r/qNJew8GHw03ZDRWURfUG6fpEHoNEwUDt5oqz0EbWPbtVDyohQqTfDYmXx8kApohedaQHg7+8ng0uQpg92eu6zTwAWDvdguQMud9dcVm3mfDjQo9dxiMQMon99YgSlw2l/v2VLUgsA7vey4Ktg0H7GRkp+3vklmq7LuY2RjXePBCCZV0CzDXh7hBrjJ6XA3d3H4VnOZaViwISpguwECcCEnu6sk5zB1cImMn7ccoj9rB119Nqdd/Bx1DzE/hb0xA0Og8lkxDdJ21oAUP6WCReZGNNXhNP55k2O76YO1G4OeblsD1cu3uFtQP8hSvg+FmPc+uPIzc3FuhUJSExoj7OXegkHIKSXO6uQiHGlsIncEWcEYv/BA7h5/11EzZ+LuCc2IFs2gkLhpKSWXqD2Ujrdz8XxTwNQ/NMfNhZI2s4nPCMjtlNdU8e9hUXA6wwCG6W47DwZ7w/wwNChI/DHzXnIPfeicAC+vVVI+owZ3NUxq+/0UoKRisAEjUJcbAyirgeQAvbKR8Nk0uHrLfwX3uZizc8AFIDZbyTmzZ2NK2fHUFi7K5ff9nJfgDt1ag+TvgA535pw94IFDUYb/rlRg8yNRsoIN+cIR5Z2R6NttiA7QVoCzQD0V/8GPx87lOpOyFjzGz+27iMRFxuHqOt+BGCfYjSamuqeCYXNNzJg5bxo95H4OGo+LuTyEV/WGd6YcgCCuvRExaMruHzNiK5dFPD3BapqAfkPdjBDA3HvjwLk3gglbxAQ4IPIyEXCGMHLP/Rmd2feQ/8+aiiVLDzavYJFH56DWs5gVsYN9NNaHJshTgE6XR0++f0FApJmGwSxWExxgjR9Mtp5uiImeiHiPlmMpMUdMVTtR9vkw+f3OtRy+aoevV5Xwde3PUpLi6D80YbirvxYT9/iAXBFiHQYKeDiqYHs8RO38HIPJVlsrmxdU0cS/njfLbzhY0LUda0jErRYjA6jmGIZCIlEgpj8zg4A0QsWID5+6f8J4OSZJgwa6AQ39wDU1hTTp/FBEV0gldiw49uBmBHaHRfOXxcOQE6GP3vxJwPNCucBuOLs4gq1SoFG/33o5anDgiducA/zPmw2qyM0Xv5ASyF0knEkAQiouASj1YZKbd8WALjA5unyUmf+G0FzUb3O/2mi8Pt+OPVrKKXIh73UQZglkJ0WwDbPCtcJpVIDmUwJTy+/Px2YBH5Byx2boXTR32G3Wylw4Urz153bleWQyVRYmsDHD5xBm3qsGxpgJ9uQ7zIW7kXZSPqBoX9/cGHum6Nm0kB7ejbhSqUTjL/0w+Kl/N9jhEqH0RLoFqilea+ua3R0imvXJvWBh7MY6YPu0KaIy9vteeThOObuaf6fT83FbxyzuWjDS0hR5KJDYkeY7Xb6i8yp60a82FGC3x5a8eWuYbhz+zy4WW/+Jwj3cO/ghS3agtmApUvnPhF+C1VSI79Y7jBK/6n97BP//Zmn38HNPPdJjCuCAdi+43O2+aX/aSbuv/k5zfr/XsdPn3/3rQdoqs+HwWCH9NVzNICFw/s6aAixrf3v0fNPCGJo/r+dE+K5NgBCUG7N72hTQGueHSH61qYAISi35ne0KaA1z44QfWtTgBCUW/M72hTQmmdHiL61KUAIyq35HW0KaM2zI0Tf2hQgBOXW/I42BbTm2RGib20KEIJya35HmwJa8+wI0bd/AQszlaq8R8kcAAAAAElFTkSuQmCC\"}],\"animations\":[{\"uuid\":\"91f45b91-0388-39e0-52fd-e98e668d0d06\",\"name\":\"roll\",\"loop\":\"once\",\"override\":false,\"length\":0.66667,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10.3452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"4e015e1c-020c-c0cc-cf03-d0f365b91b4f\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"96a1cbef-dc1c-8cb3-893d-1844d13a8898\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-55.3452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"cffaa113-7472-9a2d-879c-3b37b7918233\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.8452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"1859ff86-2a12-36c8-57b7-cf230e86e3e3\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-173.4065947693\",\"y\":\"12.6083678689\",\"z\":\"-8.1925230243\"}],\"uuid\":\"6c944778-0f30-a404-20be-7d985529dcc3\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-254.4306397576\",\"y\":\"-5.2125464926\",\"z\":\"-2.586642225\"}],\"uuid\":\"6fea6dfb-ac36-8bb7-7b6c-f1b31c3fb09c\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-299.316821318\",\"y\":\"-7.7099874859\",\"z\":\"-2.5994534331\"}],\"uuid\":\"19e7bcfa-34ef-ca1f-9316-34616fa523cb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-334.7684189709\",\"y\":\"2.2798591799\",\"z\":\"-2.5779800118\"}],\"uuid\":\"143b95c6-51d7-3d73-dab9-4afc76a18bee\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-359.7452658674\",\"y\":\"0.977484586\",\"z\":\"-0.7993651888\"}],\"uuid\":\"ca8b487a-5316-821c-6a0d-2b17dc09e2ce\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"739f6fd0-eeff-515d-bc2c-05459d38fb35\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"610dffa2-b064-c32e-49b0-95e662c9fbe0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f8040d86-2633-7d51-02f2-d276ac128c20\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-15\",\"z\":\"-5\"}],\"uuid\":\"2613c6a2-076c-8a2d-7022-314ada456d6a\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-16\",\"z\":\"3\"}],\"uuid\":\"65475ddb-0901-3649-c168-8f332b92d1c4\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-4\",\"z\":\"5\"}],\"uuid\":\"d26510d5-a53d-5570-114b-1a49178fc6f7\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"4\"}],\"uuid\":\"03b08547-82b6-c8ce-eb7e-8147925b4d3c\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3d14037c-74a9-abc2-b13d-3600ae6c5dd0\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3\",\"z\":\"0.31\"}],\"uuid\":\"7a5c383e-b443-3773-3fc7-b41a95b66aa3\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f218f334-8ebe-66d8-1769-41de9f9f7ad0\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"75314a09-9386-3e4d-3f96-a8e59911fe5e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e579328d-a99d-9a7f-0a05-746e42159080\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5169123047\",\"y\":\"4.8873117739\",\"z\":\"2.6276738824\"}],\"uuid\":\"57494920-9783-7099-8971-f8828309b07f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.2554184495\",\"y\":\"1.9324031631\",\"z\":\"0.6242872643\"}],\"uuid\":\"14faa28e-7b7c-4fca-04bd-fb15427d1d14\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.321532076\",\"y\":\"-0.1985844587\",\"z\":\"-0.7364643904\"}],\"uuid\":\"2960010b-2391-100b-9059-499f90d98595\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.7446837252\",\"y\":\"-2.2894808314\",\"z\":\"1.027565895\"}],\"uuid\":\"6436ac0d-b7c1-eb4c-741b-c6fec8760846\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6cb82785-ecee-7220-9b21-7f748ca4ff73\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"554f1255-7efc-bebf-d907-e6b38e212768\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"918337ed-db4f-55f0-7a21-d10d25500ae6\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"66b19558-a367-598b-50c7-142c4211a07d\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"af84a634-f0ee-f31b-42da-a9789b3af450\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9b812e3b-ff6a-5bcf-9b14-d88149ef2a29\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.9335647827\",\"y\":\"0.1910653446\",\"z\":\"0.5860712492\"}],\"uuid\":\"d26f71b7-3eb6-c334-8e41-f3048cc39bfd\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40.8337650092\",\"y\":\"1.5719388815\",\"z\":\"0.836263831\"}],\"uuid\":\"908fd1af-b92a-7c86-ec15-33aa208e85d9\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.5153158634\",\"y\":\"1.5694142029\",\"z\":\"1.1566234175\"}],\"uuid\":\"06e3b992-d5bc-6bd5-da5e-ec28155c8af4\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"af2c3045-852e-36da-7224-00ca89c0c360\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4e57e7e0-bff8-8f86-f5a9-14379ed8f0cc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7e8f0cb8-aacf-3a06-0990-319fcca439ca\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"951b375f-8b17-0bce-c1e5-b020c20b1378\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"96e0a9d7-f9a1-ca27-c4c1-49b9b9ae9739\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d691f682-5653-24b5-64cd-f593d802ae7a\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"64d27d7c-16f1-4e24-11de-1df5b650e5b7\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-44.7041455577\",\"y\":\"-0.5463397482\",\"z\":\"2.4016359177\"}],\"uuid\":\"cf7881c9-f9af-c342-d155-dda6ca40df0f\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-42.1987087714\",\"y\":\"0.9199484607\",\"z\":\"2.3855070851\"}],\"uuid\":\"1e94d2b6-12f7-f0b3-5513-e088c678cddc\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"4.7824447755\",\"y\":\"-15.0272212866\",\"z\":\"0.6748976469\"}],\"uuid\":\"d578b08c-e775-fb98-c9f8-42f4e180dc8f\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3a4776d8-2065-0aff-5901-b4d3d6ecf2f6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.9790874962\",\"y\":\"-5.8032396565\",\"z\":\"15.815264958\"}],\"uuid\":\"f9ce9b56-16a1-0ade-1b32-395dc6a6508f\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.1815443852\",\"y\":\"7.1155834398\",\"z\":\"18.1304160091\"}],\"uuid\":\"14d812e3-97c4-fb55-ae8c-7ec92768c5bd\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.9790874962\",\"y\":\"-5.8032396565\",\"z\":\"15.815264958\"}],\"uuid\":\"5afbcdab-4795-1ca8-59a9-285e3dfac932\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.9171104584\",\"y\":\"-4.5000466504\",\"z\":\"5.8600103802\"}],\"uuid\":\"6499750a-a4fb-5c57-4995-c4d47b630672\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"8.2765048988\",\"y\":\"-2.9212329552\",\"z\":\"-1.4866588636\"}],\"uuid\":\"7f018859-2372-9fd4-17e0-756d8fffc147\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"aba6447d-d798-c3c4-8579-72b987b8b41c\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.7013003421\",\"y\":\"1.5426491326\",\"z\":\"16.086881186\"}],\"uuid\":\"3896e9cb-f48c-75cc-888d-769cf83332f2\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"55\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8e1409c4-941b-084c-02e5-df03cc1a4690\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"92ea27f8-0e0a-b629-1027-b86cf738d7ec\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"79.7622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"fc368334-2af0-e8a5-5d0a-5cbdb752e343\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"117.2622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"7f4efd9b-c5ba-5977-b5fa-b1b9799bbac1\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"94.7622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"7db6c762-536f-e3b9-482d-8337cffe5746\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"95.0395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"bbc95573-feb7-ffe2-345c-af69f5f8f939\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"72.5395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"ab7c0856-f341-34ea-f57c-6904b0e30d92\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"9e4fcb79-0aa2-9377-3fc6-ab698d600e8a\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"63743b29-3a44-0bc8-f5a5-8903bb15d390\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25.6848204073\",\"y\":\"10.1778091184\",\"z\":\"-20.173933666\"}],\"uuid\":\"7a3b3bdf-dd57-badd-a6dd-7580d30e23e0\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4323899f-7d3f-557a-beda-d05603cd2f96\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.4403124196\",\"y\":\"16.3255534092\",\"z\":\"-34.2402741209\"}],\"uuid\":\"744316fc-f3d8-67fd-9a68-2b2c568b639e\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.2988707081\",\"y\":\"3.4553272208\",\"z\":\"-6.6606725408\"}],\"uuid\":\"6cbe013b-e0b9-702c-e627-f2c374f53718\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fd38995a-2f81-eb5e-6c06-404eceec04f6\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5f5fe94f-19c5-c019-7a47-ac924d74285c\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"339cdfba-9029-f75f-9d19-ac8567b07424\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"87d530dc-a3ca-5bc5-53fd-d0b58e67c115\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"95\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4056d3f0-9227-879d-0ccd-a715a6acefd8\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"60\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5cb622b9-5c77-b222-ba79-a62ddbf1e801\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.9979365876\",\"y\":\"19.3545961515\",\"z\":\"-11.7009195082\"}],\"uuid\":\"d08bec63-419b-45f1-d3ac-4bf88c534563\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.9979365876\",\"y\":\"19.3545961515\",\"z\":\"-11.7009195082\"}],\"uuid\":\"aeaa276f-1181-1922-960d-7fb6845fd9f3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"13.2306976448\",\"y\":\"4.1075261935\",\"z\":\"1.5640491387\"}],\"uuid\":\"2416d5e6-91bb-6991-a145-7778a9ab717b\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ce1a67cc-be9b-632e-7d0f-1ea657ce8f06\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3e549bb1-58c3-92b5-d13c-9c3c0772ea12\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c6b905c0-5196-e52b-59c7-8d3927a73397\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"82.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e4592899-dc47-7f8c-acd9-92811b5fdd77\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"82.1404769782\",\"y\":\"-17.3455144203\",\"z\":\"2.3566635967\"}],\"uuid\":\"07d4219f-d8d7-811e-fba2-06fb667c4f62\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"31.427095159\",\"y\":\"-13.5306627252\",\"z\":\"-4.344153106\"}],\"uuid\":\"7a496aba-7df1-b5b5-4dbe-7e6420e13fef\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"11.427095159\",\"y\":\"-13.5306627252\",\"z\":\"-4.344153106\"}],\"uuid\":\"446c86e4-1c3b-eff5-a180-a62769af7bb8\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"95fe2e07-173b-a066-a66d-0be58ae890a5\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"48.3424105805\",\"y\":\"1.1690538795\",\"z\":\"-1.2575696869\"}],\"uuid\":\"06246a98-556c-f22c-45a0-44083c73dbe9\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.8934503775\",\"y\":\"-3.8213109421\",\"z\":\"17.9129813754\"}],\"uuid\":\"aead35ef-607e-93dc-f018-85e424e6a137\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"447233aa-66b2-dbd7-e45d-483f46dadbae\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bd388449-ced9-61f6-e408-4f913fc0355d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.4009318272\",\"y\":\"-4.2154088774\",\"z\":\"2.6913572849\"}],\"uuid\":\"7b84f346-ab75-70f4-a8f4-67879637f9e5\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.4009318272\",\"y\":\"-4.2154088774\",\"z\":\"2.6913572849\"}],\"uuid\":\"724ba19e-1a1c-00e9-111a-d67ed5aa5c83\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"48.3698475508\",\"y\":\"-13.3767559929\",\"z\":\"0.8884687898\"}],\"uuid\":\"28e08960-9fa6-1789-1edf-5b1e443db7bd\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.7930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"3629a4e5-a2bf-6722-7a63-824e74693c95\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-23.2930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"fc5d0a27-b180-4a2f-5fb1-c25e9d8d0cbc\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.2930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"0471b600-cd96-fb88-b394-25b68b236191\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b3528148-886c-4981-aad9-a449912e4ff3\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c6d9e946-1d10-482d-14b1-0766027adba8\":{\"name\":\"prfl_right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4d65f4ff-4666-1d6e-e2a2-5754675f39d3\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"713091ea-3c5f-2107-8abf-ee97bc6176ee\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-35\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"020d854c-8f1d-eea6-0d44-f050772acad3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"747e19b0-5b0d-1bde-37c5-d61eae5ee649\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1a3a7fe0-9ac6-642d-1655-a02cbedc50c0\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-63.5104629601\",\"y\":\"-0.0562419487\",\"z\":\"-3.8034923955\"}],\"uuid\":\"cf494402-d95a-c9a9-0666-1105ac4ff1ba\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30.3667568215\",\"y\":\"-5.6104628256\",\"z\":\"0.822128921\"}],\"uuid\":\"338edd44-0be5-f9d0-495a-16b3c7a47468\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5627211181\",\"y\":\"5.0414022802\",\"z\":\"3.6575498102\"}],\"uuid\":\"601767a5-ac9a-bc96-4b0b-237160c36700\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50.8538632696\",\"y\":\"5.4159878628\",\"z\":\"2.605219904\"}],\"uuid\":\"b124849a-6e1c-463f-9163-98272fab4254\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.1096711992\",\"y\":\"-3.7317133585\",\"z\":\"-11.938445897\"}],\"uuid\":\"486ede20-d032-4d46-d9cd-95cf257c04d9\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1e3e41fd-1374-7b20-aada-7d42393cb399\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-93.8329224881\",\"y\":\"-12.0025641778\",\"z\":\"-18.1816740733\"}],\"uuid\":\"13a334d3-48cd-1136-e910-d86f10ecd6ff\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25.8053212577\",\"y\":\"7.0178718272\",\"z\":\"-29.2161110452\"}],\"uuid\":\"7e87c3b5-9d38-b35b-df3b-aa05fc47517d\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-39.0037108296\",\"y\":\"-5.9031625207\",\"z\":\"-19.1431448415\"}],\"uuid\":\"6601f44d-7b3e-1933-edfb-aaf37ed8f707\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-14.2395920994\",\"y\":\"3.5940186635\",\"z\":\"-7.5169369411\"}],\"uuid\":\"284cc08b-c152-0ab3-3689-6d441335ef56\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8085208f-1d05-3f74-9820-0173fe43fd6b\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.9519624501\",\"y\":\"-7.2690960426\",\"z\":\"-16.7194764292\"}],\"uuid\":\"487c4a04-c29a-1aa1-e26a-aa6dbafe7e0a\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"18.247962068\",\"y\":\"7.8313214321\",\"z\":\"-24.4844657083\"}],\"uuid\":\"73a7fea9-6029-f2f6-ba64-9449179096e1\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\":{\"name\":\"plfl_left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a0002446-b908-df56-d45b-79e09a54036d\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"71dde296-8a96-6c52-d883-110fe28f6083\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18.550117808\",\"y\":\"-0.4065298268\",\"z\":\"4.9663131482\"}],\"uuid\":\"a9e10a80-2787-3737-8868-9c28c2cbafdb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5298058614\",\"y\":\"-2.9158033357\",\"z\":\"6.6597637739\"}],\"uuid\":\"3ec121c2-1295-28ed-2ca7-22374f35c684\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c43f7153-1df2-38ec-1edd-b3694bed6dc8\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40.1236819605\",\"y\":\"-0.9306502051\",\"z\":\"1.542865843\"}],\"uuid\":\"fefea430-0ffa-21c0-738a-21ab44462a5e\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-33.1776348771\",\"y\":\"-2.2590643664\",\"z\":\"4.7558127555\"}],\"uuid\":\"3245ce43-4453-1563-6426-957785fa545f\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-53.9268912309\",\"y\":\"-7.3104700825\",\"z\":\"8.1053141589\"}],\"uuid\":\"8d32a863-19e6-66eb-dd63-bc4a4490440e\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.6010831098\",\"y\":\"0.530086938\",\"z\":\"2.60850085\"}],\"uuid\":\"1a5cc3fb-2736-9e73-929b-2b1f796b4824\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"6f35223a-32fc-86de-273e-bf2a2dab29d9\",\"name\":\"left_attack_1\",\"loop\":\"once\",\"override\":false,\"length\":0.75,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1.4426116839\",\"y\":\"-29.9685 + 60 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"-2.8861405929\"}],\"uuid\":\"9cea2183-ab9a-e257-3abe-870ecefb3983\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"4.3169042909\",\"y\":\"30  \",\"z\":\"2.8752087286\"}],\"uuid\":\"dbc63c99-e467-c1e1-657c-cb6af7445267\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.6830957091\",\"y\":\"30\",\"z\":\"2.8752087286\"}],\"uuid\":\"fd08cba1-1f23-bfc2-9441-476ca36f74cb\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"06b875b6-cb7b-6d84-3602-76e8d8392a4d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"f0fba0f9-402e-360e-0a94-d64aeb5c5c6d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366\",\"z\":\"0.3377321701\"}],\"uuid\":\"b50394e2-3528-65dd-22fb-512dbfc2f810\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"b375bdf1-a5b9-5f1e-8257-47941e069d20\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"6671e902-6e55-2c82-dded-c5df61e041e5\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366\",\"z\":\"0.3377321701\"}],\"uuid\":\"df42619a-31b1-392a-4568-0f18b8533893\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"fc915813-af51-faa4-ecdc-03c64965677b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"e225cf07-6e84-70ca-6695-b29a2846ed8b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"15\",\"z\":\"0\"}],\"uuid\":\"c25352f3-66c2-2b02-bd35-d880f2b176f3\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"-75 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"b43318a9-dcd4-607f-65e1-8b00cc43b3a0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"-75\",\"z\":\"0\"}],\"uuid\":\"93681b83-7b39-874b-e811-a67b95855b3f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4079498817\",\"y\":\"-69.6105803251\",\"z\":\"7.2928402867\"}],\"uuid\":\"9a3a0d44-c650-ac83-4813-2a10370f9eb4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"226.442612794\",\"y\":\"-29.9685204144\",\"z\":\"-92.8861411476\"}],\"uuid\":\"e93ee5dc-184d-1bc3-117e-457e5f8d35b0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"118.942612794\",\"y\":\"-29.9685204144\",\"z\":\"-92.8861411476\"}],\"uuid\":\"abee3b46-8686-08a6-5fc2-88f23a08686b\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"91.4812744911\",\"y\":\"-32.4677070432\",\"z\":\"-92.9607170541\"}],\"uuid\":\"f5310bc1-f902-b35d-17f6-5c492a08e390\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"104.2082921546\",\"y\":\"-34.614390135\",\"z\":\"-84.3352155418\"}],\"uuid\":\"546f17cc-3f95-55f6-ce39-34d45c4bc65f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"120.1958079089\",\"y\":\"-58.4299921475\",\"z\":\"-95.76303279\"}],\"uuid\":\"50a71a0f-23a3-58bf-a9ca-dfd506c8fc73\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cfdee1e2-175a-bfa2-c031-d225ff9b5d21\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"74aba8f3-7600-c98b-c42b-8ae7248974c0\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-48.85\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"982fbd70-9b5f-e211-2106-7bb13df19813\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-77.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"093ab868-a3e5-1727-de2d-9699650ad3b7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"fcaf8da0-0146-2587-b578-3e1af888deaa\":{\"name\":\"pri_right_item\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"47ab1042-3dbe-9fbe-5b2f-2d4e55759eb9\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"68868a70-d07d-55bc-ea5e-d62b6d58ee36\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-70\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"26f156a2-f872-2e31-e28b-5cd012a57108\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-72.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"81cb1fac-09fb-254d-c7ef-605c48f3687c\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-28.75\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7128c27e-62a5-eab2-1edc-b9fdd5a2d1db\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-87.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"09cbf278-b5ef-45bf-9f24-057b421dce72\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-102.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1cae9cba-9706-843e-87c5-c87e76ba05a8\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-102.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ea0dd9ba-ef9e-ae70-1647-8ded38c17181\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"630a55a7-df7d-e844-f270-7bc23e9091e0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a7909b5c-2b7b-30d8-daa2-ce4e1eb0ae89\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a2809a1d-00c4-c013-9496-dafe98018493\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6360ce54-fddd-4efd-f7e0-bf14889ca2f5\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"5bafd1e1-dd4f-e647-0ae9-7a670f0d888d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"2b40d9f0-62fd-aaa3-6543-793a1dbb7a30\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"df09c19b-92e5-fda1-3fc0-88b5f94777c3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7ec93d93-fd31-0af1-95b5-7b2a760ebf0e\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"74458596-1e8e-e629-df60-95abd2e494a9\",\"name\":\"left_attack_2\",\"loop\":\"once\",\"override\":false,\"length\":0.75,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.6830957091\",\"y\":\"30 - 45 * math.sin(query.anim_time * degrees)\",\"z\":\"2.8752087286\"}],\"uuid\":\"10958ace-f54f-8523-893a-df0d2fa7ee2b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1.992544964\",\"y\":\"-14.8854093306\",\"z\":\"-7.4750460859\"}],\"uuid\":\"6cd93535-eeb9-7d73-b0ac-d4d382bbbb01\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"-29.9409605825\",\"z\":\"-5.1865092834\"}],\"uuid\":\"33442214-ce7c-a7f0-da2c-c32579010ed2\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"-29.9409605825\",\"z\":\"-5.1865092834\"}],\"uuid\":\"893ec734-086c-2dc3-e43c-1881431cc207\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366 - 45 * math.sin(query.anim_time / 0.75 * degrees * 0.5)\",\"z\":\"0.3377321701\"}],\"uuid\":\"feec51b1-b546-e183-06af-f2fd4e999baf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-30\",\"z\":\"0.3377321701\"}],\"uuid\":\"bbb6334b-d352-6cc7-7708-a92e8267d1a9\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-15\",\"z\":\"0.3377321701\"}],\"uuid\":\"977401eb-1cd9-d20f-ba13-fcc04bb0bbe4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1118499707\",\"y\":\"-19.9999907433\",\"z\":\"0.3276399241\"}],\"uuid\":\"7c4918c9-c0da-f306-f08f-5065e20e6365\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366 - 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.3377321701\"}],\"uuid\":\"039aa55f-3a2b-951f-687c-39c2028a075b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-30\",\"z\":\"0.3377321701\"}],\"uuid\":\"743c1219-e220-c341-e567-62633a006f8e\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"-15.0000239764\",\"z\":\"0.3668883205\"}],\"uuid\":\"d78bfc1e-149a-c305-1cab-d11998cd8fa4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1002819443\",\"y\":\"-20.0000165354\",\"z\":\"0.3578398578\"}],\"uuid\":\"c993c4bb-f2dd-0d3e-2a90-ccdd9a6e28bf\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"15 - 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0\"}],\"uuid\":\"bc207a6b-8c2e-fae1-615a-58640bfd30a4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"-30\",\"z\":\"0\"}],\"uuid\":\"99c4936a-ccb2-0076-d4a6-3f602366dd4b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"-15.0126547749\",\"z\":\"0.6697153441\"}],\"uuid\":\"dc834c65-8788-5a0c-5e8e-b17cbc524ed7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.3040300919\",\"y\":\"-20.0087274139\",\"z\":\"0.4618653633\"}],\"uuid\":\"2b68f55d-7ce7-d9ac-6140-1e11f21bea83\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4079498817\",\"y\":\"110\",\"z\":\"7.2928402867\"}],\"uuid\":\"63e11b48-1acd-31fb-9aec-038ef11318d1\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.2466262711\",\"y\":\"74.742560924\",\"z\":\"4.9451721289\"}],\"uuid\":\"272befd3-7dac-c524-f191-be00f2b480f7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10.6822205364\",\"y\":\"79.6837812733\",\"z\":\"1.4255362599\"}],\"uuid\":\"665dcf82-4d8a-60f1-a147-3ea0fd090a3b\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4079498817\",\"y\":\"-70 + 180 * math.sin(query.anim_time * degrees)\",\"z\":\"7.2928402867\"}],\"uuid\":\"fc76d21e-d81d-fe56-8713-8c4511a33f4c\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"88.1929174825 + 135 * math.sin(query.anim_time * degrees)\",\"y\":\"-6.5328079718\",\"z\":\"-99.7693965866\"}],\"uuid\":\"f15b8a26-69eb-3236-f121-089f6c12adac\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"225\",\"y\":\"-6.5328079718\",\"z\":\"-99.7693965866\"}],\"uuid\":\"c4d1ddb3-58fe-1339-9935-9c017764aa14\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"181.7685110117\",\"y\":\"-29.2024066267\",\"z\":\"-94.1691034871\"}],\"uuid\":\"52ecc208-4ae8-93fa-fb45-2116be5c1bd7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"200.7469404305\",\"y\":\"-14.5514885328\",\"z\":\"-96.9206545934\"}],\"uuid\":\"291f2fd2-9348-7e45-94ec-a65ccfe23fb2\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-87.5 + 90 * math.sin(query.anim_time * degrees)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cbc64268-9d72-314a-bb49-cd0bb865f92d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"abe0830d-48f3-34ee-0d56-79a82a40d0fd\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8475acbb-ec58-046f-cf73-1d5f1bd2f682\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"47670f80-2a64-0e92-40cf-cf7dba16da3a\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"fcaf8da0-0146-2587-b578-3e1af888deaa\":{\"name\":\"pri_right_item\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-122.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7be4764d-8e4f-962f-b52e-22c0e27e24a6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-122.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4cbdb4a2-378e-76db-2887-6c510059585f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-147.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"23006a51-cd73-e3ba-b213-c537823211c8\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-107.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d388d056-819e-3f5c-244d-1a7c2779def9\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ad988529-fbb5-7dee-e2be-6470903df197\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cd2ae947-1b0d-2cb4-55b3-e73bfe412ae4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"65109525-d50a-8c57-a1a1-c1f845f31b89\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"81c482f3-e0f4-8689-5723-b978e50693d7\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"852792fa-dba4-c696-8903-76d0cf6cd30a\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e03c676a-178b-f03e-b40d-4404ee7b6eaa\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"21117162-b28a-dbe7-5b2d-ba2da62704d0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e9534f91-cc2b-e3cf-3dda-d16bcac0381b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"59988b0f-f288-1498-fd3f-e5e76fb31afd\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2b7c6f42-5bc9-0d92-f071-1774547278c1\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"a4e124d9-47e1-c56a-d58f-03a3b368a9a0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"73269add-beb1-4df4-dd72-7882b3fc38fb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"3b636b10-cdb7-a40f-b218-8eb1b3aa0008\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"f3b94148-4f92-306c-6646-68077a96e7fc\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"43625c14-44b7-769e-d20c-b7ae66efc0c9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ad20692b-165d-cf72-0674-86e893570e2e\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9001b4a2-3e10-4782-15de-b440b94a0265\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b40d0e65-f781-9910-9f5d-6872fb4aab46\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\":{\"name\":\"plfl_left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"eae385ac-0193-0adf-dd33-7896c8c38519\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1906f341-ffb9-6bcf-a293-56f56d5a7a7d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"46b1e55d-2185-fe99-fc55-d1de75cdd0cd\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"929e78b1-3d73-f810-5d38-67582cb8f00e\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"3868a416-df35-aa7c-72ca-aa2f7000eb3e\",\"name\":\"left_attack_3\",\"loop\":\"once\",\"override\":false,\"length\":0.75,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"-30 + 30 * math.sin(query.anim_time * degrees)\",\"z\":\"-5.1865092834\"}],\"uuid\":\"d0abd38c-d0de-ed94-01d0-fe17313caa21\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"0\",\"z\":\"-5.1865092834\"}],\"uuid\":\"5d322262-38d5-191f-9f2e-ba246377ea6b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"0\",\"z\":\"-2.6865092834\"}],\"uuid\":\"f42022d3-fbfb-e7b9-fb75-d9607fdff0ec\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-15 + 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.3377321701\"}],\"uuid\":\"a026bb6e-9f3c-f381-1672-4fdb60d5d173\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"30\",\"z\":\"0.3377321701\"}],\"uuid\":\"f49eded7-50f0-328f-f9bc-832f8306614c\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15\",\"z\":\"0.3377321701\"}],\"uuid\":\"132e27b6-de90-f3e8-0f91-a1a0326dd4bb\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"-15 + 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.3668883205\"}],\"uuid\":\"83b142f3-3039-78b5-5027-82fc96140962\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"30\",\"z\":\"0.3668883205\"}],\"uuid\":\"9b1408ae-bacb-3696-9353-f583fe01691d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"15\",\"z\":\"0.3668883205\"}],\"uuid\":\"72b4cbd4-6cd8-ecf3-49e1-ba9bdca95ed5\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"-15 + 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.6697153441\"}],\"uuid\":\"080664d7-7ba7-0e3b-eadf-4aeca7d6ab28\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"30\",\"z\":\"0.6697153441\"}],\"uuid\":\"32c655bc-7cb1-4989-8d04-ba6601a20ffa\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"15\",\"z\":\"0.6697153441\"}],\"uuid\":\"2394fbbe-c4be-f4a9-7147-6f775caa2b57\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.2466262711\",\"y\":\"74.742560924\",\"z\":\"4.9451721289\"}],\"uuid\":\"0ca4d269-75b1-053b-2e29-97ed95ee8a27\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.25\",\"y\":\"-90.26\",\"z\":\"4.95\"}],\"uuid\":\"9d554f75-1c6d-a52e-231c-5b3fccb718f0\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.25\",\"y\":\"-45.26\",\"z\":\"4.95\"}],\"uuid\":\"1dd76bcb-6748-32e6-f1c0-af82dbaaf6ef\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25.2303398612\",\"y\":\"-66.3113221393\",\"z\":\"25.0049672424\"}],\"uuid\":\"f6e86818-45b8-d124-5048-235d4438e1f2\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.7996254024\",\"y\":\"-18.7498221312\",\"z\":\"46.5234420106\"}],\"uuid\":\"0475513c-2096-eade-6e28-e9543b1326dc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-64.1068768939\",\"y\":\"-7.3820305827\",\"z\":\"59.216259349\"}],\"uuid\":\"c9030e72-7c34-0050-fac4-653b7eaf67a5\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-39.6615555673\",\"y\":\"10.04018335\",\"z\":\"57.8138546454\"}],\"uuid\":\"8ec4a464-cce1-1660-9e7e-aed5dc55ef21\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.1615555673\",\"y\":\"10.04018335\",\"z\":\"65.3138546454\"}],\"uuid\":\"631fb268-9cc4-cad3-c10f-1b50204f5032\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.1615555673\",\"y\":\"10.04018335\",\"z\":\"87.8138546454\"}],\"uuid\":\"7bd8dd80-fa0e-a6db-44f9-3641c7a860a6\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.928317261\",\"y\":\"43.8968318011\",\"z\":\"33.8368278453\"}],\"uuid\":\"9ae0c3e6-af01-5eff-89d3-e344ae80d232\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.7412720053\",\"y\":\"-29.1799804704\",\"z\":\"-21.7474034651\"}],\"uuid\":\"6a40116a-b813-1b1d-0348-7c2e57651f0e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.3747088224\",\"y\":\"-26.0561020798\",\"z\":\"-53.9720884106\"}],\"uuid\":\"60015b32-254c-b385-5730-f1fb2dca341d\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"-42.5\"}],\"uuid\":\"4ad84a41-5d07-01a0-8466-4e63692e80ab\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"25\"}],\"uuid\":\"152728f0-c892-e72c-83ce-c307706a04aa\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"42.5\"}],\"uuid\":\"6a9ba9d9-8596-88fd-cbd3-56574d16d159\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.379429148\",\"y\":\"-0.4004441987\",\"z\":\"30.0108045086\"}],\"uuid\":\"2443bfb5-32c5-7ceb-7748-f884f86bff3f\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"fcaf8da0-0146-2587-b578-3e1af888deaa\":{\"name\":\"pri_right_item\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.5768860212\",\"y\":\"23.1473092203\",\"z\":\"-27.013532475\"}],\"uuid\":\"c6821d4f-794f-c7c7-0f89-80033761c353\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.6994195496\",\"y\":\"13.8964693362\",\"z\":\"-30.1451170148\"}],\"uuid\":\"275ad9e7-67ec-4daf-b4c5-c49ff92c0c7c\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.697928082\",\"y\":\"-29.8984523542\",\"z\":\"-46.3767048046\"}],\"uuid\":\"2908fdad-6769-ad97-152b-f2e86f8fba88\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"81.4976285226\",\"y\":\"-85.6361454504\",\"z\":\"-145.5405300408\"}],\"uuid\":\"e38f1288-48fd-30ea-940e-a1713aa5e533\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"111.4976285226\",\"y\":\"-85.6361454504\",\"z\":\"-145.5405300408\"}],\"uuid\":\"0bb1777f-8437-9e14-420b-ad2af484cf2e\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"66.4976285226\",\"y\":\"-85.6361454504\",\"z\":\"-145.5405300408\"}],\"uuid\":\"f77bbe2b-eb20-5dca-9e04-984240155daa\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"218.1984733219\",\"y\":\"-82.2634224785\",\"z\":\"-252.6403714152\"}],\"uuid\":\"282590c6-2b2c-80b1-d27d-1cf6d8b8d539\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"241ef05d-3297-bd29-63f7-9ac98d2fab11\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"762de5c9-6726-ade1-3357-92c739fda0b1\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ab041c20-61fb-26af-0653-35423dce4973\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6f854563-d459-37eb-82ce-ce7b114058d3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"35\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0751f55e-78d5-7ea0-ae92-a3f56f61570d\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9e37eb37-bc4a-ce3e-c99e-d247cc12acf4\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"dfdbf96d-60fd-d00b-d057-7a0fddebd2bf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.6809719992\",\"y\":\"2.3896261399\",\"z\":\"5.3762737066\"}],\"uuid\":\"d8c7c5f2-d9bb-8be8-4395-282a02893319\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.6809719992\",\"y\":\"2.3896261399\",\"z\":\"5.3762737066\"}],\"uuid\":\"1f138668-b13a-8c4c-6d27-ae7c168a168b\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c6d9e946-1d10-482d-14b1-0766027adba8\":{\"name\":\"prfl_right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"522f3da4-0892-8397-935d-de98f65aa7ad\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.9759091971\",\"y\":\"-0.569987466\",\"z\":\"-0.8300310517\"}],\"uuid\":\"484dd03c-d5cd-3c01-f305-7b0a3e0b4524\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.9759091971\",\"y\":\"-0.569987466\",\"z\":\"-0.8300310517\"}],\"uuid\":\"7222f20e-0201-a3b1-7e99-6902147d0697\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9a8681c9-5339-28f0-edfd-2b8d0c193484\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-28.2171371372\",\"y\":\"-0.2383668654\",\"z\":\"6.7491823315\"}],\"uuid\":\"1e3c6439-78ef-20ec-5850-64f6d2e9b77b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-28.2171371372\",\"y\":\"-0.2383668654\",\"z\":\"6.7491823315\"}],\"uuid\":\"55cf2e15-14ac-bb80-69de-3db8dff8ff47\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\":{\"name\":\"plfl_left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b91601e4-1df3-23af-4d55-d2a8849a97b9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e7b6f9bd-8abf-e22f-a9c9-9bc55e7aa644\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bb218f38-7f01-5744-470c-7767bc629b9e\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}}],\"animation_variable_placeholders\":\"degrees=180\"}"
  },
  {
    "path": "platform/fabric/src/testmod/resources/knight_line.json",
    "content": "{\n\t\"format_version\": \"1.21.6\",\n\t\"credit\": \"Made with Blockbench\",\n\t\"textures\": {\n\t\t\"0\": \"bettermodel:item/knight_line\",\n\t\t\"particle\": \"bettermodel:item/knight_line\"\n\t},\n\t\"elements\": [\n\t\t{\n\t\t\t\"from\": [3, 8.5, -8],\n\t\t\t\"to\": [8, 8.5, 8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [-0.5, 0, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 2.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 2.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.5, 8, 0, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [2.5, 0, 0, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [4.725, 7.975, -8],\n\t\t\t\"to\": [6.725, 7.975, 8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [-1.775, 16.475, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 1, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 1, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.5, 16, 1.5, 8], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3.5, 8, 2.5, 16], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [5.5, 8.5, -8],\n\t\t\t\"to\": [8, 8.5, 8],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"z\", \"origin\": [8, 8.5, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.5, 8, 5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8, 0, 6.5, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [5.5, 8.5, -8],\n\t\t\t\"to\": [8, 8.5, 8],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"z\", \"origin\": [8, 8.5, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8, 8, 6.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.5, 0, 5, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 6.5, 0.5],\n\t\t\t\"to\": [7.75, 7.5, 2.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7.75, 6.5, -0.5]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7, 6.75, -5.5],\n\t\t\t\"to\": [7, 7.75, -3.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, 6.75, -6.5]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [4.5, 9.5, -3.5],\n\t\t\t\"to\": [4.5, 10.5, -1.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [4.5, 9.5, -3.5]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [4.5, 9, 2.75],\n\t\t\t\"to\": [4.5, 10, 4.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [4.5, 9, 2.75]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t}\n\t],\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [0, 0, 0],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [0, 1, 2, 3, 4, 5, 6, 7]\n\t\t}\n\t]\n}"
  },
  {
    "path": "platform/fabric/src/testmod/resources/knight_sword.json",
    "content": "{\n\t\"textures\": {\n\t\t\"0\": \"bettermodel:item/knight_sword\",\n\t\t\"particle\": \"bettermodel:item/knight_sword\"\n\t},\n\t\"elements\": [\n\t\t{\n\t\t\t\"from\": [7.15, -4, 7.25],\n\t\t\t\"to\": [8.85, -2.5, 8.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -8.5, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6.75, 5.5, 7.25, 6], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.75, 6, 7.25, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 6.5, 7.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 7, 0.5, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.5, 0.5, 7, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.5, 2.25, 7, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, 0, 8.25],\n\t\t\t\"to\": [8.8, 2, 11.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7, 2.75, 7.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 5.25, 5.25, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.75, 7, 5.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.25, 5.25, 6.25, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6, 1, 5.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6, 1, 5.5, 2], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6.75, -0.5, 6.75],\n\t\t\t\"to\": [9.25, 2, 9.25],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 0.75, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.25, 4.5, 5, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 0, 5.5, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.75, 0.75, 5.5, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.75, 1.5, 5.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.5, 3, 4.75, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5.5, 3, 4.75, 3.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.225, 0, 8.375],\n\t\t\t\"to\": [8.775, 1.5, 10.5],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 0.5, 10.625]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 7, 5.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 7, 6.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.25, 7, 6.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.75, 7, 7.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.75, 1, 7.25, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.75, 1, 7.25, 1.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.225, 0, 5.5],\n\t\t\t\"to\": [8.775, 1.5, 7.625],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 0.5, 5.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.25, 1.5, 7.75, 2], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.25, 3.25, 7.75, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 3.75, 7.75, 4.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.25, 4.25, 7.75, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.75, 5.25, 7.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.75, 5.25, 7.25, 5.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.15, -1.5, 7.25],\n\t\t\t\"to\": [8.85, 0, 8.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.25, 5.75, 7.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.25, 6.25, 7.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 6.75, 7.75, 7.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.25, 7.25, 7.75, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [0.5, 8, 0, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8, 0, 7.5, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.4, -8.95, 7.4],\n\t\t\t\"to\": [8.6, -8.25, 8.6],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -8.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7, 0.5, 7.25, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.25, 2, 7.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.5, 3, 7.75, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.75, 8.75, 7, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 7.25, 8.75, 7], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.5, 8.75, 7.25, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 0.925, 10.375],\n\t\t\t\"to\": [8.825, 2.675, 11.6],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 10.3]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 7.5, 1.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.5, 4, 5.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.5, 2, 8, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6, 3.5, 6.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.25, 5.25, 5.75, 5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 2.75, 8, 3], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 0.925, 4.4],\n\t\t\t\"to\": [8.825, 2.675, 5.625],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 5.7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.5, 2.5, 8, 3], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.25, 2.25, 8.5, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.25, 7.5, 4.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.25, 3, 8.5, 3.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 1, 8.25, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.75, 3.5, 8.25, 3.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, -12.75, 7.5],\n\t\t\t\"to\": [8.75, -11.5, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, -12, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.25, 7.75, 8.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.5, 8.75, 8.75, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.25, 8, 8.75, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.75, 8.5, 9, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 8.5, 8.25, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 0, 8.5, 0.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7, -5.25, 7.1],\n\t\t\t\"to\": [9, -4, 8.9],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -8.75, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 0.25, 9, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.5, 0.5, 9, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 1, 9, 1.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1.25, 8.5, 1.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.75, 8, 5.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.25, 7.5, 5.75, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.025, -4.95, 8.725],\n\t\t\t\"to\": [8.975, -3.8, 9.2],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, -4.325, 8.3]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 0.75, 9.25, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 9.5, 4.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1, 8.75, 1.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 4.5, 9.75, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2, 9, 1.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3.75, 8.75, 3.25, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.025, -4.95, 6.8],\n\t\t\t\"to\": [8.975, -3.8, 7.275],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -4.325, 7.7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 3.5, 9.25, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 9.5, 5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 8.75, 4.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 4.75, 9.75, 5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [4.75, 9, 4.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5.25, 8.75, 4.75, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.05, -5.975, 7.1],\n\t\t\t\"to\": [8.95, -4.7, 8.375],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, -5.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 7.5, 9, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 9.5, 4.5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.75, 8.5, 8.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 4.25, 9.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 8.75, 8.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1, 8.75, 0.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.4, -9.95, 7.4],\n\t\t\t\"to\": [8.6, -9.25, 8.6],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -9.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.5, 9.5, 5.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 5.5, 9.75, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5.75, 9.5, 6, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 5.75, 9.75, 6], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.25, 9.75, 6, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 6, 9.5, 6.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.4, -10.95, 7.4],\n\t\t\t\"to\": [8.6, -10.25, 8.6],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -10.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 8.75, 9, 9], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 9, 0.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 0, 9.25, 0.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.25, 9, 0.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 0.5, 9, 0.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [0.75, 9, 0.5, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, -12, 7.5],\n\t\t\t\"to\": [8.5, -5.25, 8.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -8.5, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 6, 0.75, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0.75, 6, 1, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1, 6, 1.25, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6, 1, 6.25, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 0.75, 9, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1, 9, 0.75, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8.3, -4.3, 8.925],\n\t\t\t\"to\": [8.75, 0.15, 9.675],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8.55, -1.925, 9.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6.25, 7.5, 6.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.5, 7.5, 6.75, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 7.5, 7, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7, 7.5, 7.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1.25, 9.25, 1, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 1, 9, 1.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, -4.3, 8.925],\n\t\t\t\"to\": [7.7, 0.15, 9.675],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.45, -1.925, 9.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 7.75, 0.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.75, 0.5, 8, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0.75, 7.75, 1, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 7.75, 1.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1.5, 9.25, 1.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 1.25, 9, 1.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6.95, -0.425, 2.575],\n\t\t\t\"to\": [9.05, 2.45, 4.25],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 3.55]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 6.25, 5.75, 7], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 6.25, 6.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.25, 6.25, 6.75, 7], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.5, 0, 7, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.25, 2, 7.75, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 3, 7.75, 3.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, 0, 4.25],\n\t\t\t\"to\": [8.8, 2, 7.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 3.5, 8.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 5.75, 5.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.75, 4, 8.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.75, 5.75, 6.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [0.5, 7, 0, 6], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.5, 0, 6, 1], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, -0.925, 13.2],\n\t\t\t\"to\": [8.8, 1.675, 14.3],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 9.75]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6.5, 2.5, 7, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8, 7, 8.25, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 0.75, 7.25, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8, 7.75, 8.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 3.5, 8.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 3.75, 8.5, 4], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.575, 13.875],\n\t\t\t\"to\": [8.55, 1.325, 14.65],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 0.475, 14.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 8.75, 5.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.75, 5.25, 9, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5.5, 8.75, 5.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.75, 8.75, 6, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.25, 9.75, 5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 5, 9.5, 5.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.575, 1.35],\n\t\t\t\"to\": [8.55, 1.325, 2.125],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 0.475, 1.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 5.75, 9, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6, 8.75, 6.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.75, 6.25, 9, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.5, 8.75, 6.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.5, 9.75, 5.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 5.25, 9.5, 5.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6.95, -0.425, 11.75],\n\t\t\t\"to\": [9.05, 2.45, 13.425],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 12.45]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 6.75, 1.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.75, 1.5, 7.25, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 3.25, 7.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.75, 4, 7.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.25, 5, 7.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 5, 7.75, 5.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.275, 12.475],\n\t\t\t\"to\": [8.55, 1.2, 13.2],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 9.75]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2, 9, 2.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 2, 9.25, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.25, 9, 2.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 2.25, 9.25, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.75, 9.25, 2.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 2.5, 9, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, -0.925, 1.7],\n\t\t\t\"to\": [8.8, 1.675, 2.8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 6.25]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.25, 6.75, 4.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.25, 0, 8.5, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 4.75, 7.25, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.25, 1.5, 8.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 4.25, 8.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 4.25, 8.5, 4.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.275, 2.8],\n\t\t\t\"to\": [8.55, 1.2, 3.525],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 6.25]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.75, 9, 3, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 2.75, 9.25, 3], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3, 9, 3.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 3, 9.25, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [3.5, 9.25, 3.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 3.25, 9, 3.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, -4.3, 6.325],\n\t\t\t\"to\": [7.7, 0.15, 7.075],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.45, -1.925, 6.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 5.5, 8, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.75, 6.5, 8, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 7.75, 7.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.5, 7.75, 7.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [3.75, 9.25, 3.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4, 9, 3.75, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8.3, -4.3, 6.325],\n\t\t\t\"to\": [8.75, 0.15, 7.075],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8.55, -1.925, 6.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 7.5, 8, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 8, 0.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8, 0, 8.25, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.25, 8, 0.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 4, 9, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4.25, 9, 4, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7, -2.5, 6.95],\n\t\t\t\"to\": [9, -1.5, 9.05],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 4.5, 9, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 8.5, 5.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 4.75, 9, 5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 5, 9, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 1.5, 8, 1], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.75, 8, 1.25, 8.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, -2.25, 9],\n\t\t\t\"to\": [8.25, -1.75, 9.7],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9, 4, 9.25, 4.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 9, 4.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 4.25, 9.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.5, 9, 4.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 4.75, 9, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5, 9, 4.75, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, -2.25, 9.55],\n\t\t\t\"to\": [8.5, -1.75, 10.05],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, -2, 9.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9, 4.75, 9.25, 5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5, 9, 5.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 5, 9.25, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 5.25, 9.25, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 5.75, 9, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 5.75, 9, 6], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, -2.25, 6.3],\n\t\t\t\"to\": [8.25, -1.75, 7],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 9]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9, 6, 9.25, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.25, 9, 6.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 6.25, 9.25, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 6.5, 9.25, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7, 9.25, 6.75, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 6.75, 9, 7], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, -2.25, 5.95],\n\t\t\t\"to\": [8.5, -1.75, 6.45],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, -2, 6.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7, 9, 7.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 7, 9.25, 7.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 9, 7.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 7.25, 9.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.75, 9.25, 7.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 7.5, 9, 7.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 2.3, 4.275],\n\t\t\t\"to\": [8.825, 2.95, 4.5],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 2.2, 4.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 1.25, 9, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [1.5, 9, 1.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 1.5, 9, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 1.5, 9.25, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 2, 8.5, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 2, 8.5, 2.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 2.3, 11.5],\n\t\t\t\"to\": [8.825, 2.95, 11.725],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 2.2, 11.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 2.25, 9, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [1.75, 9, 2, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 2.5, 9, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 1.75, 9.25, 2], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 3, 8.5, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 3, 8.5, 3.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 22.05, 6.7],\n\t\t\t\"to\": [8.5, 25.4, 8.75],\n\t\t\t\"rotation\": {\"angle\": -20, \"axis\": \"x\", \"origin\": [8, 29.8, 7.45]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.75, 7.5, 5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 4, 6.25, 5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5, 7.5, 5.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.25, 5.75, 4.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 5.25, 8.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 5.25, 8.25, 5.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 5, 9],\n\t\t\t\"to\": [8.5, 22.75, 10.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2, 0, 2.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2.25, 0, 2.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.5, 0, 2.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [2.75, 0, 3, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.5, 2.75, 6.25, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.75, 0.75, 6.5, 1], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 2, 10.45],\n\t\t\t\"to\": [8.5, 5, 11.15],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"y\", \"origin\": [8, 9.5, 10.95]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3.25, 8, 3.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.5, 8, 3.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 8, 4, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 8, 4.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 7.5, 8.75, 7.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.75, 8.75, 7.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 5, 9.95],\n\t\t\t\"to\": [8.5, 22.75, 10.65],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"y\", \"origin\": [8, 12.5, 10.45]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.25, 4.5, 2.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2.5, 4.5, 2.75, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.75, 4.5, 3, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [3, 4.5, 3.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 9, 8.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 8.25, 8.75, 8.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 5, 5.35],\n\t\t\t\"to\": [8.5, 22.75, 6.05],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"y\", \"origin\": [8, 12.5, 5.55]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4, 0, 4.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 0, 4.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.5, 0, 4.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [2, 4.5, 2.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8, 9, 7.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 7.75, 8.75, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 2, 4.85],\n\t\t\t\"to\": [8.5, 5, 5.55],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"y\", \"origin\": [8, 9.5, 5.05]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8, 5.5, 8.25, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 8, 6, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6, 8, 6.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8, 6.25, 8.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.25, 9, 8, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 8, 8.75, 8.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 2, 6.75],\n\t\t\t\"to\": [8.25, 25.5, 9.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.5, 0, 1.75, 6], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 0.75, 6], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.75, 0, 2, 6], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.75, 0, 1.5, 6], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2, 8.75, 1.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 2, 8, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 24.825, 5.825],\n\t\t\t\"to\": [8.5, 27.9, 8.9],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 27, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 6, 1.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 3.75, 5.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6, 2.75, 6.5, 3.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5, 4.5, 5.75, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.75, 1.75, 6.25, 1], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.75, 1.75, 6.25, 2.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 5, 5.75],\n\t\t\t\"to\": [8.5, 22.75, 7],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3, 0, 3.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.25, 0, 3.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.5, 0, 3.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [3.75, 0, 4, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.75, 3.5, 6.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7, 2.25, 6.75, 2.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 2, 9.25],\n\t\t\t\"to\": [8.5, 5, 10.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 8, 5.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 6.25, 5.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5.5, 8, 5.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.25, 5, 6.75, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 7.25, 8.25, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 7.25, 8.25, 7.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 2, 5.25],\n\t\t\t\"to\": [8.5, 5, 6.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.25, 8, 4.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.25, 3.5, 6.75, 4.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.5, 8, 4.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.25, 4.25, 6.75, 5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 6.25, 8.25, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 6.25, 8.25, 6.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 22.05, 7.25],\n\t\t\t\"to\": [8.5, 25.4, 9.3],\n\t\t\t\"rotation\": {\"angle\": 20, \"axis\": \"x\", \"origin\": [8, 29.8, 8.55]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.75, 6, 2, 7], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.5, 2, 6, 3], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.75, 7, 2, 8], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 3, 6, 4], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 4.25, 8.25, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 4.25, 8.25, 4.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_1\",\n\t\t\t\"from\": [7.5, 2.975, 7.875],\n\t\t\t\"to\": [8.5, 3.475, 8.125],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 16.975, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 9.5, 0.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 0.5, 9.75, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0.75, 9.5, 1, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 0.75, 9.75, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1.25, 9.75, 1, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 1, 9.5, 1.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_2\",\n\t\t\t\"from\": [7.5, 3.5625, 7.25],\n\t\t\t\"to\": [8.5, 4.3125, 7.625],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 2.8125, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 7.25, 9.5, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.5, 9.25, 7.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 7.5, 9.5, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.75, 9.25, 8, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 8, 9.25, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 9.25, 8, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_2\",\n\t\t\t\"from\": [7.5, 3.1875, 6.5],\n\t\t\t\"to\": [8.5, 3.5625, 7.625],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 2.8125, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 9, 9.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.25, 9.25, 9.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 9.5, 0.25, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 0, 9.75, 0.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [0.5, 9.75, 0.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 0.25, 9.5, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_3\",\n\t\t\t\"from\": [7.5, 5.25, 6.125],\n\t\t\t\"to\": [8.5, 5.625, 7.625],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 8.75, 9.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.5, 8.5, 7, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 9.25, 9.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 6.75, 9, 7], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.25, 9, 7, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.75, 7, 8.5, 7.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_3\",\n\t\t\t\"from\": [7.5, 4.125, 7.25],\n\t\t\t\"to\": [8.5, 5.25, 7.625],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 8, 9.5, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.25, 9.25, 8.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 8.25, 9.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 9.25, 8.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 8.75, 9.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 9.25, 8.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 5.35, 7.875],\n\t\t\t\"to\": [8.5, 18.85, 8.125],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 17.1, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3.25, 4.5, 3.5, 8], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.5, 4.5, 3.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 4.5, 4, 8], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 4.5, 4.25, 8], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 2.75, 9.25, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3, 9.25, 2.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 7.25],\n\t\t\t\"to\": [8.5, 7.05, 7.7],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [7.875, 6.725, 7.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 9.5, 1.5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 1.25, 9.75, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 9.5, 1.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 1.5, 9.75, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2, 9.75, 1.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 1.75, 9.5, 2], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 7.625],\n\t\t\t\"to\": [8.5, 6.85, 7.875],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.1, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2, 9.5, 2.25, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 2, 9.75, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.25, 9.5, 2.5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 2.25, 9.75, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.75, 9.75, 2.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 2.5, 9.5, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 8.3],\n\t\t\t\"to\": [8.5, 7.05, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [7.875, 6.725, 8.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.75, 9.5, 3, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 2.75, 9.75, 3], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3, 9.5, 3.25, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 3, 9.75, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [3.5, 9.75, 3.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 3.25, 9.5, 3.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 8.125],\n\t\t\t\"to\": [8.5, 6.85, 8.375],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.1, 7.625]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3.5, 9.5, 3.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 3.5, 9.75, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 9.5, 4, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 3.75, 9.75, 4], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [4.25, 9.75, 4, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 4, 9.5, 4.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.875, 7.5],\n\t\t\t\"to\": [8.5, 19.375, 7.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 18.375, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 1, 9.5, 1.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [1.25, 9.25, 1.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 1.25, 9.5, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1.5, 9.25, 1.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 1.75, 9.25, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [2, 9.25, 1.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.625, 7],\n\t\t\t\"to\": [8.5, 18.875, 7.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 18.375, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 1.75, 9.5, 2], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2, 9.25, 2.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 2, 9.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [2.25, 9.25, 2.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 2.5, 9.25, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [2.75, 9.25, 2.5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.425, 7.175],\n\t\t\t\"to\": [8.5, 18.575, 7.925],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.875, 18.55, 7.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 2.75, 9.5, 3], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3, 9.25, 3.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 3, 9.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [3.25, 9.25, 3.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 3.5, 9.25, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3.75, 9.25, 3.5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.925, 7.425],\n\t\t\t\"to\": [8.5, 18.075, 7.925],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.875, 18.05, 7.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 3.5, 9.5, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.75, 9.25, 4, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 3.75, 9.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 9.25, 4.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 4.25, 9.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4.5, 9.25, 4.25, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.425, 7.675],\n\t\t\t\"to\": [8.5, 17.575, 7.925],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.875, 17.55, 7.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 4.25, 9.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 9.25, 4.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 4.5, 9.5, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.75, 9.25, 5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 5, 9.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5.25, 9.25, 5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.425, 8.075],\n\t\t\t\"to\": [8.5, 17.575, 8.325],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.875, 17.55, 8.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 5, 9.5, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.25, 9.25, 5.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 5.25, 9.5, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 9.25, 5.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 5.75, 9.25, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6, 9.25, 5.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.925, 8.075],\n\t\t\t\"to\": [8.5, 18.075, 8.575],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.875, 18.05, 8.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 5.75, 9.5, 6], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6, 9.25, 6.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 6, 9.5, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.25, 9.25, 6.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 6.5, 9.25, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.75, 9.25, 6.5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.425, 8.075],\n\t\t\t\"to\": [8.5, 18.575, 8.825],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.875, 18.55, 8.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 6.5, 9.5, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.75, 9.25, 7, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 6.75, 9.5, 7], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7, 9.25, 7.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 7.25, 9.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.5, 9.25, 7.25, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_6\",\n\t\t\t\"from\": [7.5, 19.25, 7.5],\n\t\t\t\"to\": [8.5, 20, 7.75],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 20.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 0.25, 9.5, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0.5, 9.25, 0.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 0.5, 9.5, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.75, 9.25, 1, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 1, 9.25, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.25, 9.25, 1, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_6\",\n\t\t\t\"from\": [7.5, 20, 6.75],\n\t\t\t\"to\": [8.5, 20.25, 7.75],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 20.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 9, 9, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 8.75, 9.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 9, 9.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 9.25, 0.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 0.25, 9.25, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [0.5, 9.25, 0.25, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_7\",\n\t\t\t\"from\": [7.5, 21.9, 7.875],\n\t\t\t\"to\": [8.5, 23.15, 9.125],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [7.875, 22.025, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 9, 8, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 7.75, 9.25, 8], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8, 9, 8.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 8, 9.25, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 9.25, 8.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 8.25, 9, 8.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_7\",\n\t\t\t\"from\": [7.5, 20.025, 7.875],\n\t\t\t\"to\": [8.5, 22.025, 8.125],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 20.525, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 5.25, 8.75, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.5, 5.75, 8.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.25, 8.5, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 6.25, 8.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 9.25, 8.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 8.5, 9, 8.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t}\n\t],\n\t\"display\": {\n\t\t\"thirdperson_righthand\": {\n\t\t\t\"translation\": [0, 14.25, 0]\n\t\t},\n\t\t\"thirdperson_lefthand\": {\n\t\t\t\"translation\": [0, 14.25, 0]\n\t\t},\n\t\t\"firstperson_righthand\": {\n\t\t\t\"rotation\": [-5, 5, -5],\n\t\t\t\"translation\": [0, 9.25, 0]\n\t\t},\n\t\t\"firstperson_lefthand\": {\n\t\t\t\"rotation\": [-5, 5, -5],\n\t\t\t\"translation\": [0, 9.25, 0]\n\t\t},\n\t\t\"ground\": {\n\t\t\t\"rotation\": [0, 0, 90]\n\t\t},\n\t\t\"gui\": {\n\t\t\t\"rotation\": [90, -135, 90],\n\t\t\t\"scale\": [1, 0.5, 0.5]\n\t\t},\n\t\t\"fixed\": {\n\t\t\t\"rotation\": [90, -45, 90],\n\t\t\t\"scale\": [1, 0.5, 0.5]\n\t\t}\n\t},\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [7, -8.5, 7],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]\n\t\t},\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [8, 29.8, 7.45],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48]\n\t\t},\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [8, 11.1, 7.625],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70]\n\t\t}\n\t]\n}"
  },
  {
    "path": "platform/paper/build.gradle.kts",
    "content": "import xyz.jpenilla.resourcefactory.bukkit.Permission\nimport xyz.jpenilla.resourcefactory.paper.PaperPluginYaml\n\nplugins {\n    alias(libs.plugins.convention.plugin)\n    alias(libs.plugins.resourcefactory.paper)\n}\n\nval libraryDir: Provider<RegularFile> = layout.buildDirectory.file(\"generated/paper-library\")\nval dependenciesContent: String = libs.bundles.library.map { bundle ->\n    bundle.joinToString(\"\\n\") { dep -> dep.toString() }\n}.get()\n\ndependencies {\n    shade(project(\":nms:v1_21_R3\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R4\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R5\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R6\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R7\")) { isTransitive = false }\n    shade(project(\":nms:v26_R1\")) { isTransitive = false }\n}\n\nmodrinth {\n    gameVersions = SUPPORTED_VERSIONS\n    loaders = PAPER_LOADERS\n}\n\ntasks.modrinth {\n    dependsOn(tasks.modrinthSyncBody)\n}\n\nval generatePaperLibrary by tasks.registering {\n    val outputProvider = libraryDir\n    val contentProvider = dependenciesContent\n\n    outputs.file(outputProvider)\n\n    doLast {\n        val file = outputProvider.get().asFile\n        file.parentFile.mkdirs()\n        file.writeText(contentProvider)\n    }\n}\n\ntasks.shadowJar {\n    dependsOn(generatePaperLibrary)\n    from(libraryDir)\n    manifest {\n        attributes[\"paperweight-mappings-namespace\"] = \"mojang\"\n    }\n}\n\npaperPluginYaml {\n    main = \"$group.paper.BetterModelPaper\"\n    loader = \"$group.paper.BetterModelLoader\"\n    version = project.version.toString()\n    name = \"BetterModel\"\n    foliaSupported = true\n    apiVersion = \"1.21.4\"\n    author = \"toxicity188\"\n    contributors = listOf(\"https://github.com/toxicity188/BetterModel/graphs/contributors\")\n    description = \"Modern Bedrock model engine for Minecraft Java Edition\"\n    website = \"https://modrinth.com/plugin/bettermodel\"\n    dependencies {\n        server(\n            name = \"MythicMobs\",\n            required = false,\n            load = PaperPluginYaml.Load.BEFORE\n        )\n        server(\n            name = \"Citizens\",\n            required = false,\n            load = PaperPluginYaml.Load.BEFORE\n        )\n        server(\n            name = \"SkinsRestorer\",\n            required = false,\n            load = PaperPluginYaml.Load.BEFORE\n        )\n        server(\n            name = \"Nexo\",\n            required = false,\n            load = PaperPluginYaml.Load.OMIT\n        )\n    }\n    permissions.create(\"bettermodel\") {\n        default = Permission.Default.OP\n        description = \"Accesses to command.\"\n        children = mapOf(\n            \"reload\" to true,\n            \"spawn\" to true,\n            \"disguise\" to true,\n            \"undisguise\" to true,\n            \"test\" to true,\n            \"play\" to true,\n            \"version\" to true,\n            \"hide\" to true,\n            \"show\" to true\n        )\n    }\n}\n"
  },
  {
    "path": "platform/paper/src/main/java/kr/toxicity/model/paper/BetterModelLoader.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.paper;\n\nimport io.papermc.paper.plugin.loader.PluginClasspathBuilder;\nimport io.papermc.paper.plugin.loader.PluginLoader;\nimport io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver;\nimport org.eclipse.aether.artifact.DefaultArtifact;\nimport org.eclipse.aether.graph.Dependency;\nimport org.eclipse.aether.repository.RemoteRepository;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Objects;\n\n@SuppressWarnings({\"UnstableApiUsage\", \"unused\"})\npublic final class BetterModelLoader implements PluginLoader {\n    @Override\n    public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) {\n        var lib = new MavenLibraryResolver();\n        lib.addRepository(new RemoteRepository.Builder(\n            null,\n            \"default\",\n            \"https://maven-central.storage-download.googleapis.com/maven2\"\n        ).build());\n        try (\n            var stream = Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(\"paper-library\"));\n            var streamReader = new InputStreamReader(stream, StandardCharsets.UTF_8);\n            var reader = new BufferedReader(streamReader)\n        ) {\n            String next;\n            while ((next = reader.readLine()) != null) {\n                lib.addDependency(new Dependency(new DefaultArtifact(next), null));\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        classpathBuilder.addLibrary(lib);\n    }\n}\n"
  },
  {
    "path": "platform/paper/src/main/kotlin/kr/toxicity/model/paper/BetterModelPaper.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.paper\n\nimport kr.toxicity.model.api.BetterModelPlatform\nimport kr.toxicity.model.bukkit.BetterModelPlugin\n\n@Suppress(\"UNUSED\")\nclass BetterModelPaper : BetterModelPlugin() {\n\n    override fun jarType(): BetterModelPlatform.JarType {\n        return BetterModelPlatform.JarType.PAPER\n    }\n}\n"
  },
  {
    "path": "platform/spigot/build.gradle.kts",
    "content": "import xyz.jpenilla.resourcefactory.bukkit.Permission\n\nplugins {\n    alias(libs.plugins.convention.plugin)\n    alias(libs.plugins.resourcefactory.bukkit)\n}\n\nval dependenciesContent: List<String> = libs.bundles.library.map {\n    it.map(Any::toString)\n}.get()\n\ndependencies {\n    shade(project(\":nms:v1_21_R3\", configuration = \"reobf\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R4\", configuration = \"reobf\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R5\", configuration = \"reobf\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R6\", configuration = \"reobf\")) { isTransitive = false }\n    shade(project(\":nms:v1_21_R7\", configuration = \"reobf\")) { isTransitive = false }\n    shade(project(\":nms:v26_R1\")) { isTransitive = false }\n}\n\nmodrinth {\n    gameVersions = SUPPORTED_VERSIONS\n    loaders = BUKKIT_LOADERS\n}\n\ntasks.shadowJar {\n    manifest {\n        attributes[\"paperweight-mappings-namespace\"] = \"spigot\"\n    }\n}\n\nbukkitPluginYaml {\n    main = \"$group.spigot.BetterModelSpigot\"\n    version = project.version.toString()\n    name = \"BetterModel\"\n    foliaSupported = true\n    apiVersion = \"1.21.4\"\n    author = \"toxicity188\"\n    description = \"Modern Bedrock model engine for Minecraft Java Edition\"\n    website = \"https://modrinth.com/plugin/bettermodel\"\n    softDepend = listOf(\n        \"MythicMobs\",\n        \"Citizens\",\n        \"SkinsRestorer\"\n    )\n    libraries = dependenciesContent\n    permissions.create(\"bettermodel\") {\n        default = Permission.Default.OP\n        description = \"Accesses to command.\"\n        children = mapOf(\n            \"reload\" to true,\n            \"spawn\" to true,\n            \"disguise\" to true,\n            \"undisguise\" to true,\n            \"test\" to true,\n            \"play\" to true,\n            \"version\" to true,\n            \"hide\" to true,\n            \"show\" to true\n        )\n    }\n}\n"
  },
  {
    "path": "platform/spigot/src/main/kotlin/kr/toxicity/model/spigot/BetterModelSpigot.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.spigot\n\nimport kr.toxicity.model.api.BetterModelPlatform\nimport kr.toxicity.model.bukkit.BetterModelPlugin\nimport kr.toxicity.model.util.toComponent\nimport kr.toxicity.model.util.warn\nimport org.bukkit.Bukkit\n\n@Suppress(\"UNUSED\")\nclass BetterModelSpigot : BetterModelPlugin() {\n\n    override fun onEnable() {\n        if (IS_PAPER) {\n            warn(\n                \"You're using Paper, so you have to use Paper jar!\".toComponent(),\n                \"Please download Paper jar from Modrinth! (https://modrinth.com/plugin/bettermodel)\".toComponent()\n            )\n            return Bukkit.getPluginManager().disablePlugin(this)\n        }\n        super.onEnable()\n    }\n\n    override fun jarType(): BetterModelPlatform.JarType {\n        return BetterModelPlatform.JarType.SPIGOT\n    }\n}\n"
  },
  {
    "path": "purpur/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.standard)\n}\n\ndependencies {\n    compileOnly(project(\":bettermodel-api\"))\n    compileOnly(project(\":bettermodel-api:bettermodel-bukkit-api\"))\n    compileOnly(\"org.purpurmc.purpur:purpur-api:${property(\"minecraft_version\")}.build.+\")\n}\n"
  },
  {
    "path": "purpur/src/main/kotlin/kr/toxicity/model/bukkit/purpur/PurpurHook.kt",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2026 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.bukkit.purpur\n\nimport kr.toxicity.model.api.BetterModel\nimport kr.toxicity.model.api.bukkit.platform.BukkitPlayer\nimport kr.toxicity.model.api.event.CreateDummyTrackerEvent\nimport kr.toxicity.model.api.event.CreateEntityTrackerEvent\nimport net.kyori.adventure.text.Component\nimport net.kyori.adventure.text.format.NamedTextColor\n\nobject PurpurHook {\n    fun start() {\n        val platform = BetterModel.platform()\n        val config = BetterModel.config()\n        platform.logger().info(\n            Component.text(\"BetterModel is currently running in Purpur.\").color(NamedTextColor.LIGHT_PURPLE),\n            Component.text(\"Some Purpur features will be enabled.\").color(NamedTextColor.LIGHT_PURPLE)\n        )\n        BetterModel.eventBus().subscribe(platform, CreateDummyTrackerEvent::class.java) { event ->\n            event.tracker().pipeline.viewFilter {\n                !config.usePurpurAfk() || !(it as BukkitPlayer).source().isAfk\n            }\n        }\n        BetterModel.eventBus().subscribe(platform, CreateEntityTrackerEvent::class.java) { event ->\n            event.tracker().pipeline.viewFilter {\n                !config.usePurpurAfk() || !(it as BukkitPlayer).source().isAfk\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n    \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n    \"extends\": [\n        \"config:recommended\"\n    ],\n    \"ignoreDeps\": [\n        \"org.jetbrains:annotations\",\n        \"org.incendo:cloud-paper\",\n        \"org.incendo:cloud-fabric\",\n        \"io.papermc.parchment.data:parchment\",\n        \"net.fabricmc.fabric-api:fabric-api\",\n        \"net.fabricmc.fabric-loom-repositories\",\n        \"eu.pb4:polymer-resource-pack\"\n    ],\n    \"baseBranches\": [\n        \"v3-dev\"\n    ]\n}\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        gradlePluginPortal()\n        mavenCentral()\n\n        maven(\"https://repo.papermc.io/repository/maven-public/\")\n        maven(\"https://maven.fabricmc.net/\")\n        maven(\"https://maven.neoforged.net/releases/\")\n    }\n}\n\nplugins {\n    id(\"org.gradle.toolchains.foojay-resolver-convention\") version \"1.0.0\"\n    id(\"net.fabricmc.fabric-loom-repositories\") version \"1.16-SNAPSHOT\"\n    id(\"net.neoforged.moddev.repositories\") version \"2.0.141\"\n}\n\ndependencyResolutionManagement {\n    repositories {\n        mavenCentral()\n        maven(\"https://repo.papermc.io/repository/maven-public/\")\n        maven(\"https://maven.fabricmc.net/\")\n        maven(\"https://maven.neoforged.net/releases/\")\n        maven(\"https://repo.codemc.org/repository/maven-public/\")\n        maven(\"https://repo.alessiodp.com/releases/\")\n        maven(\"https://maven.blamejared.com/\")\n        maven(\"https://repo.purpurmc.org/snapshots\")\n        maven(\"https://maven.citizensnpcs.co/repo/\")\n        maven(\"https://mvn.lumine.io/repository/maven-public/\")\n        maven(\"https://maven.nucleoid.xyz/\")\n        maven(\"https://repo.nexomc.com/releases/\")\n        // for development builds\n        maven(url = \"https://central.sonatype.com/repository/maven-snapshots/\") {\n            name = \"central-snapshots\"\n            mavenContent { snapshotsOnly() }\n        }\n    }\n}\n\nrootProject.name = \"bettermodel\"\n\nval published = setOf(\n    \"api\",\n    \"api:bukkit-api\",\n    \"api:mod-api\",\n\n    \"core\",\n    \"core:bukkit-core\",\n\n    \"platform:spigot\",\n    \"platform:paper\",\n    \"platform:fabric\",\n)\n\ninclude(published)\ninclude(\n    \"purpur\",\n\n    //nms\n    \"nms:v1_21_R3\",\n    \"nms:v1_21_R4\",\n    \"nms:v1_21_R5\",\n    \"nms:v1_21_R6\",\n    \"nms:v1_21_R7\",\n    \"nms:v26_R1\",\n\n    //test\n    \"test-plugin\"\n)\n\npublished.forEach { target ->\n    findProject(\":$target\")?.let {\n        it.name = \"${rootProject.name}-${it.name}\"\n    }\n}\n"
  },
  {
    "path": "test-plugin/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.convention.bukkit)\n    alias(libs.plugins.resourcefactory.bukkit)\n}\n\ndependencies {\n    compileOnly(project(\":bettermodel-api\"))\n    compileOnly(project(\":bettermodel-api:bettermodel-bukkit-api\"))\n\n    compileOnly(libs.lombok)\n    annotationProcessor(libs.lombok)\n\n    testCompileOnly(libs.lombok)\n    testAnnotationProcessor(libs.lombok)\n}\n\nval pluginName = \"BetterModel-TestPlugin\"\n\ntasks.jar {\n    archiveBaseName = pluginName\n}\n\nbukkitPluginYaml {\n    main = \"$group.test.BetterModelTest\"\n    version = project.version.toString()\n    name = pluginName\n    foliaSupported = true\n    apiVersion = \"1.20\"\n    author = \"toxicity\"\n    description = \"BetterModel's test plugin\"\n    depend = listOf(\n        \"BetterModel\"\n    )\n    commands.register(\"rollinfo\") {\n        usage = \"/<command>\"\n        description = \"Gets roll animation's info.\"\n        permission = \"bettermodel.rollinfo\"\n    }\n    commands.register(\"knightsword\") {\n        usage = \"/<command>\"\n        description = \"Gets knight sword\"\n        permission = \"bettermodel.knightsword\"\n    }\n}\n"
  },
  {
    "path": "test-plugin/src/main/java/kr/toxicity/model/test/BetterModelTest.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.test;\n\nimport org.bukkit.plugin.java.JavaPlugin;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n@SuppressWarnings(\"unused\")\npublic final class BetterModelTest extends JavaPlugin {\n\n    private final List<ModelTester> testers = List.of(\n        new RollTester(),\n        new FightTester()\n    );\n\n    @Override\n    public void onEnable() {\n        for (ModelTester tester : testers) {\n            tester.start(this);\n        }\n        getLogger().info(\"Plugin enabled.\");\n    }\n\n    @Override\n    public void onDisable() {\n        for (ModelTester tester : testers) {\n            tester.end(this);\n        }\n        getLogger().info(\"Plugin disabled.\");\n    }\n\n    public @NotNull Supplier<byte[]> asByte(@NotNull String path) {\n        try (\n            var get = Objects.requireNonNull(getResource(path))\n        ) {\n            var bytes = get.readAllBytes();\n            return () -> bytes;\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "test-plugin/src/main/java/kr/toxicity/model/test/FightTester.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.test;\n\nimport com.google.gson.JsonObject;\nimport io.papermc.paper.threadedregions.scheduler.ScheduledTask;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport kr.toxicity.model.api.bone.RenderedBone;\nimport kr.toxicity.model.api.bukkit.BetterModelBukkit;\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter;\nimport kr.toxicity.model.api.data.ModelAsset;\nimport kr.toxicity.model.api.data.renderer.ModelRenderer;\nimport kr.toxicity.model.api.event.ModelAssetsEvent;\nimport kr.toxicity.model.api.event.PluginStartReloadEvent;\nimport kr.toxicity.model.api.nms.AnimationBundler;\nimport kr.toxicity.model.api.pack.PackNamespace;\nimport kr.toxicity.model.api.platform.PlatformPlayer;\nimport lombok.RequiredArgsConstructor;\nimport net.kyori.adventure.text.minimessage.MiniMessage;\nimport org.bukkit.*;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.HandlerList;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerInteractEvent;\nimport org.bukkit.inventory.ItemFlag;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.persistence.PersistentDataType;\nimport org.bukkit.plugin.Plugin;\nimport org.bukkit.util.Vector;\nimport org.jetbrains.annotations.NotNull;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.BooleanSupplier;\nimport java.util.stream.Stream;\n\nimport static java.lang.Math.*;\n\npublic final class FightTester implements ModelTester, Listener {\n\n    @NotNull\n    private static final NamespacedKey KNIGHT_SWORD_KEY = Objects.requireNonNull(NamespacedKey.fromString(\"knight_sword\"));\n\n    private final Map<UUID, PlayerSkillCounter> playerCounterMap = new ConcurrentHashMap<>();\n    private ItemStack lineItem;\n    private BetterModelTest test;\n\n    @Override\n    public void start(@NotNull BetterModelTest test) {\n        this.test = test;\n        this.lineItem = createLine();\n        Bukkit.getPluginManager().registerEvents(this, test);\n        var command = test.getCommand(\"knightsword\");\n        if (command != null) command.setExecutor((sender, command1, label, args) -> {\n            if (sender instanceof Player player) giveKnightSword(player);\n            return true;\n        });\n        BetterModelBukkit.platform().eventBus().subscribe(test, PluginStartReloadEvent.class, event -> {\n            var path = event.zipper()\n                .modern()\n                .bettermodel();\n            loadItem(path, \"knight_sword\");\n            loadItem(path, \"knight_line\");\n        });\n        BetterModelBukkit.platform().eventBus().subscribe(test, ModelAssetsEvent.class, event -> {\n            if (event.type() == ModelRenderer.Type.PLAYER) event.addAsset(ModelAsset.of(\n                \"knight\",\n                () -> Objects.requireNonNull(test.getResource(\"knight.bbmodel\"))\n            ));\n        });\n    }\n\n    @Override\n    public void end(@NotNull BetterModelTest test) {\n        HandlerList.unregisterAll(this);\n    }\n\n    private void loadItem(@NotNull PackNamespace path, @NotNull String itemName) {\n        path.models().resolve(\"class_item\").add(itemName + \".json\", test.asByte(itemName + \".json\"));\n        path.textures().add(itemName + \".png\", test.asByte(itemName + \".png\"));\n        var model = new JsonObject();\n        model.addProperty(\"type\", \"minecraft:model\");\n        model.addProperty(\"model\", \"bettermodel:class_item/\" + itemName);\n        var json = new JsonObject();\n        json.add(\"model\", model);\n        path.items().add(itemName + \".json\", () -> json.toString().getBytes(StandardCharsets.UTF_8));\n    }\n\n    @EventHandler\n    public void rightClick(@NotNull PlayerInteractEvent event) {\n        var player = event.getPlayer();\n        var uuid = player.getUniqueId();\n        switch (event.getAction()) {\n            case LEFT_CLICK_AIR, LEFT_CLICK_BLOCK -> {}\n            default -> {\n                return;\n            }\n        }\n        if (!player.getInventory().getItemInMainHand().getPersistentDataContainer().has(KNIGHT_SWORD_KEY)) return;\n        playerCounterMap.computeIfAbsent(uuid, u -> new PlayerSkillCounter(player)\n            .skill(\"left_attack_1\")\n            .skill(\"left_attack_2\")\n            .skill(\"left_attack_3\")).execute();\n    }\n\n    private void giveKnightSword(@NotNull Player player) {\n        var sword = new ItemStack(Material.NETHERITE_SWORD);\n        sword.editMeta(meta -> {\n            meta.displayName(MiniMessage.miniMessage().deserialize(\"<gradient:#FF6A00:#FFD800><b>Knight Sword\"));\n            meta.setUnbreakable(true);\n            meta.setItemModel(new NamespacedKey(\n                (Plugin) BetterModel.platform(),\n                \"knight_sword\"\n            ));\n            meta.addItemFlags(ItemFlag.values());\n            meta.getPersistentDataContainer().set(KNIGHT_SWORD_KEY, PersistentDataType.BOOLEAN, true);\n        });\n        player.getInventory().addItem(sword);\n    }\n\n    private static @NotNull ItemStack createLine() {\n        var line = new ItemStack(Material.PAPER);\n        line.editMeta(meta -> meta.setItemModel(new NamespacedKey(\n            (Plugin) BetterModel.platform(),\n            \"knight_line\"\n        )));\n        return line;\n    }\n\n    private static @NotNull Vector3f toDeltaVector(@NotNull Location before, @NotNull Location after, float yRot) {\n        var rd = after.toVector().subtract(before.toVector()).rotateAroundY(yRot);\n        return new Vector3f((float) rd.getX(), (float) rd.getY(), (float) rd.getZ());\n    }\n\n    @RequiredArgsConstructor\n    private class PlayerSkillCounter {\n        private final Player player;\n        private final Queue<String> skillQueue = new LinkedList<>();\n        private LineDrawer lineDrawer;\n        private long nextCooldown;\n\n        @NotNull PlayerSkillCounter skill(@NotNull String name) {\n            skillQueue.add(name);\n            return this;\n        }\n\n        void execute() {\n            if (nextCooldown > System.currentTimeMillis()) return;\n            var dequeue = skillQueue.poll();\n            if (dequeue != null) execute(dequeue);\n        }\n        private void execute(@NotNull String target) {\n            BetterModel.limb(\"knight\")\n                .map(limb -> limb.getOrCreate(BukkitAdapter.adapt(player)))\n                .ifPresent(tracker -> {\n                    var drawer = tracker.bone(\"sword_point\");\n                    if (drawer == null) {\n                        tracker.close();\n                        return;\n                    }\n                    lineDrawer = new LineDrawer(player, drawer, 30);\n                    Runnable cancel = () -> {\n                        tracker.close();\n                        cancelDrawer();\n                        playerCounterMap.remove(player.getUniqueId());\n                    };\n                    var animation = tracker.renderer().animation(target).orElse(null);\n                    if (animation == null) cancel.run();\n                    else {\n                        tracker.animate(animation, AnimationModifier.DEFAULT, cancel);\n                        nextCooldown = (long) ((animation.length() - 0.25) * 1000) + System.currentTimeMillis();\n                        playSound();\n                    }\n                });\n        }\n\n        private void playSound() {\n            var loc = player.getLocation();\n            player.playSound(\n                loc,\n                Sound.ENTITY_BREEZE_SHOOT,\n                0.75F,\n                0.5F\n            );\n            player.playSound(\n                loc,\n                Sound.ENTITY_DROWNED_SHOOT,\n                2.0F,\n                0.75F\n            );\n        }\n\n        private void cancelDrawer() {\n            if (lineDrawer != null) {\n                lineDrawer.cancel();\n                lineDrawer = null;\n            }\n        }\n    }\n\n    private record DrawerFrame(\n        float yaw,\n        @NotNull Location location,\n        Vector3f vector\n    ) {\n    }\n\n    private class LineDrawer {\n        private final List<PlatformPlayer> players;\n        private DrawerFrame after;\n        private final AtomicInteger counter = new AtomicInteger();\n        private final List<BooleanSupplier> queuedTask = new ArrayList<>();\n        private final ScheduledTask task;\n\n        LineDrawer(@NotNull Player player, @NotNull RenderedBone bone, int count) {\n            players = Stream.concat(\n                Stream.of(BukkitAdapter.adapt(player)),\n                player.getTrackedBy().stream().map(BukkitAdapter::adapt)\n            ).toList();\n            task = Bukkit.getAsyncScheduler().runAtFixedRate((Plugin) BetterModel.platform(), task -> {\n                queuedTask.removeIf(BooleanSupplier::getAsBoolean);\n                var c = counter.incrementAndGet();\n                if (c >= count) return;\n                var before = after;\n                after = new DrawerFrame(\n                    bone.rotation().radianY(),\n                    player.getLocation(),\n                    bone.hitBoxPosition().rotateY(bone.rotation().radianY())\n                );\n                if (before == null) return;\n                var delta = toDeltaVector(\n                    relativeLocation(before.location, before.vector, before.yaw),\n                    relativeLocation(after.location, after.vector, after.yaw),\n                    (float) toRadians(after.location.getYaw())\n                );\n                var yaw = atan2(delta.x, delta.z);\n                var pitch = atan2(-delta.y, sqrt(fma(delta.x, delta.x, delta.z * delta.z)));\n                createDisplay(relativeLocation(before.location, before.vector, before.yaw), delta.length(), new Quaternionf()\n                    .rotateLocalX((float) pitch)\n                    .rotateLocalY((float) yaw));\n            }, 50, 10, TimeUnit.MILLISECONDS);\n        }\n\n        void cancel() {\n            task.cancel();\n            queuedTask.removeIf(BooleanSupplier::getAsBoolean);\n        }\n\n        @NotNull Location relativeLocation(@NotNull Location location, @NotNull Vector3f vector3f, float originYaw) {\n            var loc = location.clone();\n            loc.setPitch(0);\n            loc.add(new Vector(vector3f.x, vector3f.y, vector3f.z).rotateAroundY(-originYaw));\n            return loc;\n        }\n\n        void createDisplay(@NotNull Location start, float length, @NotNull Quaternionf quaternionf) {\n            if (length <= 0.1) return;\n            start.getWorld().spawnParticle(\n                Particle.DUST,\n                start,\n                3,\n                0.2,\n                0.2,\n                0.2,\n                0,\n                new Particle.DustOptions(Color.YELLOW, 1)\n            );\n            var display = BetterModel.nms().create(BukkitAdapter.adapt(start), 0, d -> {\n                d.item(BukkitAdapter.adapt(lineItem));\n                d.brightness(15, 15);\n            });\n            var transformer = display.createTransformer();\n            var bundler = BetterModel.nms().createBundler(2);\n            display.spawn(bundler);\n            display.sendEntityData(true, bundler);\n            transformer.transform(\n                0,\n                new Vector3f(),\n                new Vector3f(1, 1, length),\n                quaternionf,\n                new AnimationBundler(bundler, BetterModel.nms().createModAnimationBuilder(2))\n            );\n            players.forEach(bundler::send);\n            var displayCounter = new AtomicInteger();\n            queuedTask.add(() -> {\n                if (displayCounter.incrementAndGet() >= 20 || task.isCancelled()) {\n                    var removeBundler = BetterModel.nms().createBundler(1);\n                    display.remove(removeBundler);\n                    players.forEach(removeBundler::send);\n                    return true;\n                }\n                return false;\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "test-plugin/src/main/java/kr/toxicity/model/test/ModelTester.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.test;\n\nimport org.jetbrains.annotations.NotNull;\n\npublic interface ModelTester {\n    void start(@NotNull BetterModelTest test);\n    void end(@NotNull BetterModelTest test);\n}\n"
  },
  {
    "path": "test-plugin/src/main/java/kr/toxicity/model/test/RollTester.java",
    "content": "/*\n * This source file is part of BetterModel.\n * Copyright (c) 2025 toxicity188\n * Licensed under the MIT License.\n * See LICENSE.md file for full license text.\n */\n\npackage kr.toxicity.model.test;\n\nimport io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent;\nimport kr.toxicity.model.api.BetterModel;\nimport kr.toxicity.model.api.animation.AnimationModifier;\nimport kr.toxicity.model.api.bukkit.platform.BukkitAdapter;\nimport kr.toxicity.model.api.tracker.ModelRotation;\nimport kr.toxicity.model.api.tracker.TrackerModifier;\nimport net.kyori.adventure.audience.Audience;\nimport net.kyori.adventure.text.Component;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Particle;\nimport org.bukkit.block.Block;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.HandlerList;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\nimport org.bukkit.event.entity.PlayerDeathEvent;\nimport org.bukkit.event.entity.ProjectileHitEvent;\nimport org.bukkit.event.player.PlayerQuitEvent;\nimport org.bukkit.event.player.PlayerSwapHandItemsEvent;\nimport org.bukkit.potion.PotionEffect;\nimport org.bukkit.potion.PotionEffectType;\nimport org.bukkit.util.Vector;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic final class RollTester implements ModelTester, Listener {\n\n    private final Set<UUID> coolTimeSet = ConcurrentHashMap.newKeySet();\n    private final Set<UUID> invulnerableSet = ConcurrentHashMap.newKeySet();\n\n    @Override\n    public void start(@NotNull BetterModelTest test) {\n        Bukkit.getPluginManager().registerEvents(this, test);\n        var command = test.getCommand(\"rollinfo\");\n        if (command != null) command.setExecutor((sender, command1, label, args) -> sendRollTime(sender));\n    }\n\n    @Override\n    public void end(@NotNull BetterModelTest test) {\n        HandlerList.unregisterAll(this);\n    }\n\n    @EventHandler\n    public void swap(@NotNull PlayerSwapHandItemsEvent event) {\n        var player = event.getPlayer();\n        event.setCancelled(true);\n        var block = underBlock(player);\n        if (block.isEmpty()) return;\n        if (coolTimeSet.contains(player.getUniqueId())) return;\n        var loc = player.getLocation();\n        var data = block.getBlockData();\n        loc.getWorld().spawnParticle(\n            Particle.BLOCK,\n            loc,\n            20,\n            1,\n            0.25,\n            1,\n            0.2,\n            data\n        );\n        loc.getWorld().playSound(loc, data.getSoundGroup().getBreakSound(), 0.5F, 1.0F);\n        playRoll(player);\n    }\n\n    @EventHandler\n    public void hit(@NotNull ProjectileHitEvent event) {\n        if (event.getHitEntity() instanceof Player player && invulnerableSet.contains(player.getUniqueId())) event.setCancelled(true);\n    }\n    @EventHandler\n    public void quit(@NotNull PlayerQuitEvent event) {\n        var get = event.getPlayer().getUniqueId();\n        invulnerableSet.remove(get);\n        coolTimeSet.remove(get);\n    }\n    @EventHandler\n    public void death(@NotNull PlayerDeathEvent event) {\n        var get = event.getPlayer().getUniqueId();\n        invulnerableSet.remove(get);\n        coolTimeSet.remove(get);\n    }\n    @EventHandler\n    public void damage(@NotNull EntityDamageByEntityEvent event) {\n        if (event.getEntity() instanceof Player player && invulnerableSet.contains(player.getUniqueId())) event.setCancelled(true);\n    }\n    @EventHandler\n    public void push(@NotNull EntityPushedByEntityAttackEvent event) {\n        if (event.getEntity() instanceof Player player && invulnerableSet.contains(player.getUniqueId())) event.setCancelled(true);\n    }\n\n    private static @NotNull Block underBlock(@NotNull Player player) {\n        return player.getLocation().add(0, -1, 0).getBlock();\n    }\n\n    private static boolean sendRollTime(@NotNull Audience audience) {\n        return BetterModel.limb(\"steve\")\n            .flatMap(r -> r.animation(\"roll\"))\n            .map(animation -> {\n                audience.sendMessage(Component.text()\n                    .append(Component.text(\"Loop mode: \" + animation.loop()))\n                    .appendNewline()\n                    .append(Component.text(\"Length: \" + animation.length() + \" second\")));\n                return audience;\n            })\n            .isPresent();\n    }\n\n    private void playRoll(@NotNull Player player) {\n        var input = inputToYaw(player);\n        BetterModel.limb(\"steve\")\n            .map(r -> r.getOrCreate(BukkitAdapter.adapt(player), TrackerModifier.DEFAULT, t -> {\n                t.bodyRotator().lockRotation(true);\n                t.rotation(() -> new ModelRotation(player.getPitch(), packDegree(input + t.registry().entity().yaw())));\n            }))\n            .ifPresent(t -> {\n                if (t.animate(\"roll\", AnimationModifier.DEFAULT_WITH_PLAY_ONCE, () -> {\n                    BetterModel.platform().scheduler().asyncTaskLater(3, () -> coolTimeSet.remove(player.getUniqueId()));\n                    t.close();\n                })) {\n                    if (coolTimeSet.add(player.getUniqueId()) && invulnerableSet.add(player.getUniqueId())) {\n                        player.addPotionEffect(new PotionEffect(\n                            PotionEffectType.LUCK,\n                            8,\n                            5,\n                            true,\n                            false\n                        ));\n                        BetterModel.platform().scheduler().asyncTaskLater(8, () -> invulnerableSet.remove(player.getUniqueId()));\n                        player.setVelocity(player.getVelocity()\n                            .add(new Vector(0, 0, 0.75).rotateAroundY(-Math.toRadians(input + t.registry().entity().yaw())))\n                            .setY(0.15));\n                    }\n                } else t.close();\n            });\n    }\n\n    private static float inputToYaw(@NotNull Player player) {\n        var input = player.getCurrentInput();\n        var leftRightDegree = switch (TriState.of(input.isLeft(), input.isRight())) {\n            case LEFT -> 270F;\n            case RIGHT -> 90F;\n            case NOT_FOUND -> -1F;\n        };\n        var forwardBackwardDegree = switch (TriState.of(input.isForward(), input.isBackward())) {\n            case LEFT -> 0F;\n            case RIGHT -> 180F;\n            case NOT_FOUND -> -1F;\n        };\n        if (forwardBackwardDegree < 0) return Math.max(leftRightDegree, 0);\n        else if (leftRightDegree < 0) return forwardBackwardDegree;\n        if (leftRightDegree - forwardBackwardDegree > 180) forwardBackwardDegree += 360;\n        return (forwardBackwardDegree + leftRightDegree) / 2;\n    }\n\n    private static float packDegree(float degree) {\n        return degree > 180 ? degree - 360 : degree;\n    }\n\n\n    private enum TriState {\n        LEFT,\n        RIGHT,\n        NOT_FOUND\n        ;\n        static @NotNull TriState of(boolean left, boolean right) {\n            return left ? LEFT : right ? RIGHT : NOT_FOUND;\n        }\n    }\n}\n"
  },
  {
    "path": "test-plugin/src/main/resources/knight.bbmodel",
    "content": "{\"meta\":{\"format_version\":\"5.0\",\"model_format\":\"free\",\"box_uv\":false},\"name\":\"knight\",\"model_identifier\":\"\",\"visible_box\":[1,1,0],\"variable_placeholders\":\"degrees=180\",\"variable_placeholder_buttons\":[],\"timeline_setups\":[],\"unhandled_root_fields\":{},\"resolution\":{\"width\":64,\"height\":64},\"elements\":[{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,11.25,-1.875],\"to\":[3.75,15,1.875],\"autouv\":0,\"color\":1,\"origin\":[0,11.25,0],\"uv_offset\":[16,24],\"faces\":{\"north\":{\"uv\":[20,28,28,32],\"texture\":0},\"east\":{\"uv\":[16,28,20,32],\"texture\":0},\"south\":{\"uv\":[32,28,40,32],\"texture\":0},\"west\":{\"uv\":[28,28,32,32],\"texture\":0},\"up\":{\"uv\":[28,28,20,24],\"texture\":0},\"down\":{\"uv\":[36,16,28,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"ea42f7f7-a6f1-4479-c43f-48211bab5ed2\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,15,-1.875],\"to\":[3.75,18.75,1.875],\"autouv\":0,\"color\":2,\"origin\":[0,15,0],\"uv_offset\":[16,20],\"faces\":{\"north\":{\"uv\":[20,24,28,28],\"texture\":0},\"east\":{\"uv\":[16,24,20,28],\"texture\":0},\"south\":{\"uv\":[32,24,40,28],\"texture\":0},\"west\":{\"uv\":[28,24,32,28],\"texture\":0},\"up\":{\"uv\":[28,24,20,20],\"texture\":0},\"down\":{\"uv\":[28,28,20,32],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5ea74bdb-ba28-b8e3-103b-9be6ff2262da\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,18.75,-1.875],\"to\":[3.75,22.5,1.875],\"autouv\":0,\"color\":3,\"origin\":[0,18.75,0],\"uv_offset\":[16,16],\"faces\":{\"north\":{\"uv\":[20,20,28,24],\"texture\":0},\"east\":{\"uv\":[16,20,20,24],\"texture\":0},\"south\":{\"uv\":[32,20,40,24],\"texture\":0},\"west\":{\"uv\":[28,20,32,24],\"texture\":0},\"up\":{\"uv\":[28,20,20,16],\"texture\":0},\"down\":{\"uv\":[28,24,20,28],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc1510db-a719-17b4-e253-c992a92c5d25\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,18.76563,-1.85937],\"to\":[3.73438,22.48438,1.85938],\"autouv\":0,\"color\":3,\"inflate\":0.25,\"origin\":[0,18.75,0],\"uv_offset\":[16,32],\"faces\":{\"north\":{\"uv\":[20,36,28,40],\"texture\":0},\"east\":{\"uv\":[16,36,20,40],\"texture\":0},\"south\":{\"uv\":[32,36,40,40],\"texture\":0},\"west\":{\"uv\":[28,36,32,40],\"texture\":0},\"up\":{\"uv\":[28,36,20,32],\"texture\":0},\"down\":{\"uv\":[8,0,0,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"d51a8665-a2bc-af6e-1230-5acba07248e7\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,15.01563,-1.85937],\"to\":[3.73438,18.73438,1.85938],\"autouv\":0,\"color\":2,\"inflate\":0.25,\"origin\":[0,15,0],\"uv_offset\":[16,36],\"faces\":{\"north\":{\"uv\":[20,40,28,44],\"texture\":0},\"east\":{\"uv\":[16,40,20,44],\"texture\":0},\"south\":{\"uv\":[32,40,40,44],\"texture\":0},\"west\":{\"uv\":[28,40,32,44],\"texture\":0},\"up\":{\"uv\":[8,4,0,0],\"texture\":0},\"down\":{\"uv\":[8,0,0,4],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"f5b9f499-b26b-f912-1a54-151d702e13ed\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,11.26563,-1.85937],\"to\":[3.73438,14.98438,1.85938],\"autouv\":0,\"color\":1,\"inflate\":0.25,\"origin\":[0,11.25,0],\"uv_offset\":[16,40],\"faces\":{\"north\":{\"uv\":[20,44,28,48],\"texture\":0},\"east\":{\"uv\":[16,44,20,48],\"texture\":0},\"south\":{\"uv\":[32,44,40,48],\"texture\":0},\"west\":{\"uv\":[28,44,32,48],\"texture\":0},\"up\":{\"uv\":[8,4,0,0],\"texture\":0},\"down\":{\"uv\":[36,32,28,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"357ebf82-23ba-edb1-081f-dca75d94b83c\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.75,16.875,-1.875],\"to\":[7.5,22.5,1.875],\"autouv\":0,\"color\":4,\"origin\":[5.15625,21.5625,0],\"uv_offset\":[40,16],\"faces\":{\"north\":{\"uv\":[44,20.1,48,26.1],\"texture\":0},\"east\":{\"uv\":[40,20,44,26],\"texture\":0},\"south\":{\"uv\":[52,20,56,26],\"texture\":0},\"west\":{\"uv\":[48,20,52,26],\"texture\":0},\"up\":{\"uv\":[48,20.1,44,16.1],\"texture\":0},\"down\":{\"uv\":[48,26.1,44,30.1],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"53d40d2e-0941-29f9-00ed-1b19c941dcd8\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.76563,16.89063,-1.85937],\"to\":[7.48438,22.48438,1.85938],\"autouv\":0,\"color\":4,\"inflate\":0.25,\"origin\":[5.15625,21.5625,0],\"uv_offset\":[40,32],\"faces\":{\"north\":{\"uv\":[44,36,48,42],\"texture\":0},\"east\":{\"uv\":[40,36,44,42],\"texture\":0},\"south\":{\"uv\":[52,36,56,42],\"texture\":0},\"west\":{\"uv\":[48,36,52,42],\"texture\":0},\"up\":{\"uv\":[48,36,44,32],\"texture\":0},\"down\":{\"uv\":[56,32,52,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b17452ef-afbe-e010-f2c9-e0ba945faa1f\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.5,16.875,-1.875],\"to\":[-3.75,22.5,1.875],\"autouv\":0,\"color\":4,\"origin\":[-5.15625,21.5625,0],\"uv_offset\":[32,48],\"faces\":{\"north\":{\"uv\":[36,52,40,58],\"texture\":0},\"east\":{\"uv\":[32,52,36,58],\"texture\":0},\"south\":{\"uv\":[44,52,48,58],\"texture\":0},\"west\":{\"uv\":[40,52,44,58],\"texture\":0},\"up\":{\"uv\":[40,52,36,48],\"texture\":0},\"down\":{\"uv\":[40,58,36,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"addb9f66-a2a4-46f7-b6af-f5a23c00fe70\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.48437,16.89063,-1.85937],\"to\":[-3.76562,22.48438,1.85938],\"autouv\":0,\"color\":4,\"inflate\":0.25,\"origin\":[-5.15625,21.5625,0],\"uv_offset\":[48,48],\"faces\":{\"north\":{\"uv\":[52,52,56,58],\"texture\":0},\"east\":{\"uv\":[48,52,52,58],\"texture\":0},\"south\":{\"uv\":[60,52,64,58],\"texture\":0},\"west\":{\"uv\":[56,52,60,58],\"texture\":0},\"up\":{\"uv\":[56,52,52,48],\"texture\":0},\"down\":{\"uv\":[64,48,60,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"5e7dc31c-c64a-ff15-90d9-52a6f77cb14b\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.75,11.25,-1.875],\"to\":[7.5,16.875,1.875],\"autouv\":0,\"color\":8,\"origin\":[5.15625,15.9375,0],\"uv_offset\":[40,22],\"faces\":{\"north\":{\"uv\":[44,26,48,32],\"texture\":0},\"east\":{\"uv\":[40,26,44,32],\"texture\":0},\"south\":{\"uv\":[52,26,56,32],\"texture\":0},\"west\":{\"uv\":[48,26,52,32],\"texture\":0},\"up\":{\"uv\":[48,26,44,22],\"texture\":0},\"down\":{\"uv\":[52,16,48,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e02c395d-e1bc-1375-a8ed-729e19544ce9\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[3.76563,11.26563,-1.85937],\"to\":[7.48438,16.85938,1.85938],\"autouv\":0,\"color\":8,\"inflate\":0.25,\"origin\":[5.15625,15.9375,0],\"uv_offset\":[40,38],\"faces\":{\"north\":{\"uv\":[44,42,48,48],\"texture\":0},\"east\":{\"uv\":[40,42,44,48],\"texture\":0},\"south\":{\"uv\":[52,42,56,48],\"texture\":0},\"west\":{\"uv\":[48,42,52,48],\"texture\":0},\"up\":{\"uv\":[44,36,40,32],\"texture\":0},\"down\":{\"uv\":[52,32,48,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"a509c2d7-53ef-2b11-1331-f716b9c210d6\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.5,11.25,-1.875],\"to\":[-3.75,16.875,1.875],\"autouv\":0,\"color\":8,\"origin\":[-5.15625,15.9375,0],\"uv_offset\":[32,54],\"faces\":{\"north\":{\"uv\":[36,58,40,64],\"texture\":0},\"east\":{\"uv\":[32,58,36,64],\"texture\":0},\"south\":{\"uv\":[44,58,48,64],\"texture\":0},\"west\":{\"uv\":[40,58,44,64],\"texture\":0},\"up\":{\"uv\":[40,58,36,54],\"texture\":0},\"down\":{\"uv\":[44,48,40,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"1433ee62-21ac-d72c-0fd0-37000e5eb221\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-7.48437,11.26563,-1.85937],\"to\":[-3.76562,16.85938,1.85938],\"autouv\":0,\"color\":8,\"inflate\":0.25,\"origin\":[-5.15625,15.9375,0],\"uv_offset\":[48,54],\"faces\":{\"north\":{\"uv\":[52,58,56,64],\"texture\":0},\"east\":{\"uv\":[48,58,52,64],\"texture\":0},\"south\":{\"uv\":[60,58,64,64],\"texture\":0},\"west\":{\"uv\":[56,58,60,64],\"texture\":0},\"up\":{\"uv\":[52,52,48,48],\"texture\":0},\"down\":{\"uv\":[60,48,56,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"24bacfd6-cde8-81a0-f620-998cf5393d48\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0,5.625,-1.875],\"to\":[3.75,11.25,1.875],\"autouv\":0,\"color\":7,\"origin\":[1.40625,10.3125,0],\"uv_offset\":[0,16],\"faces\":{\"north\":{\"uv\":[4,20,8,26],\"texture\":0},\"east\":{\"uv\":[0,20,4,26],\"texture\":0},\"south\":{\"uv\":[12,20,16,26],\"texture\":0},\"west\":{\"uv\":[8,20,12,26],\"texture\":0},\"up\":{\"uv\":[8,20,4,16],\"texture\":0},\"down\":{\"uv\":[8,26,4,30],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"b17675fc-b79b-0ad9-8f81-aa2dd1fc8c97\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.01563,5.64063,-1.85937],\"to\":[3.73438,11.23438,1.85938],\"autouv\":0,\"color\":7,\"inflate\":0.25,\"origin\":[1.40625,10.3125,0],\"uv_offset\":[0,32],\"faces\":{\"north\":{\"uv\":[4,36,8,42],\"texture\":0},\"east\":{\"uv\":[0,36,4,42],\"texture\":0},\"south\":{\"uv\":[12,36,16,42],\"texture\":0},\"west\":{\"uv\":[8,36,12,42],\"texture\":0},\"up\":{\"uv\":[8,36,4,32],\"texture\":0},\"down\":{\"uv\":[16,32,12,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"2e42e483-1557-d21e-aee0-94af7bedfd40\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0,0,-1.875],\"to\":[3.75,5.625,1.875],\"autouv\":0,\"color\":6,\"origin\":[1.40625,4.6875,0],\"uv_offset\":[0,22],\"faces\":{\"north\":{\"uv\":[4,26,8,32],\"texture\":0},\"east\":{\"uv\":[0,26,4,32],\"texture\":0},\"south\":{\"uv\":[12,26,16,32],\"texture\":0},\"west\":{\"uv\":[8,26,12,32],\"texture\":0},\"up\":{\"uv\":[8,26,4,22],\"texture\":0},\"down\":{\"uv\":[12,16,8,20],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"9455d16e-4bbf-4b63-881d-7daf943e782b\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[0.01563,0.01563,-1.85937],\"to\":[3.73438,5.60938,1.85938],\"autouv\":0,\"color\":6,\"inflate\":0.25,\"origin\":[1.40625,4.6875,0],\"uv_offset\":[0,38],\"faces\":{\"north\":{\"uv\":[4,42,8,48],\"texture\":0},\"east\":{\"uv\":[0,42,4,48],\"texture\":0},\"south\":{\"uv\":[12,42,16,48],\"texture\":0},\"west\":{\"uv\":[8,42,12,48],\"texture\":0},\"up\":{\"uv\":[4,36,0,32],\"texture\":0},\"down\":{\"uv\":[12,32,8,36],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"16aee685-0ead-a542-5b3c-a62467ca45e3\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,5.625,-1.875],\"to\":[0,11.25,1.875],\"autouv\":0,\"color\":7,\"origin\":[-1.40625,10.3125,0],\"uv_offset\":[16,48],\"faces\":{\"north\":{\"uv\":[20,52,24,58],\"texture\":0},\"east\":{\"uv\":[16,52,20,58],\"texture\":0},\"south\":{\"uv\":[28,52,32,58],\"texture\":0},\"west\":{\"uv\":[24,52,28,58],\"texture\":0},\"up\":{\"uv\":[24,52,20,48],\"texture\":0},\"down\":{\"uv\":[24,58,20,62],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"0e370edc-7b05-dccf-dd2a-a92cfe9f3e22\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,5.64063,-1.85937],\"to\":[-0.01562,11.23438,1.85938],\"autouv\":0,\"color\":7,\"inflate\":0.25,\"origin\":[-1.40625,10.3125,0],\"uv_offset\":[0,48],\"faces\":{\"north\":{\"uv\":[4,52,8,58],\"texture\":0},\"east\":{\"uv\":[0,52,4,58],\"texture\":0},\"south\":{\"uv\":[12,52,16,58],\"texture\":0},\"west\":{\"uv\":[8,52,12,58],\"texture\":0},\"up\":{\"uv\":[8,52,4,48],\"texture\":0},\"down\":{\"uv\":[16,48,12,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"dc189735-0a58-619b-07ef-feec37d65e7d\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,0,-1.875],\"to\":[0,5.625,1.875],\"autouv\":0,\"color\":6,\"origin\":[-1.40625,4.6875,0],\"uv_offset\":[16,54],\"faces\":{\"north\":{\"uv\":[20,58,24,64],\"texture\":0},\"east\":{\"uv\":[16,58,20,64],\"texture\":0},\"south\":{\"uv\":[28,58,32,64],\"texture\":0},\"west\":{\"uv\":[24,58,28,64],\"texture\":0},\"up\":{\"uv\":[24,58,20,54],\"texture\":0},\"down\":{\"uv\":[28,48,24,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"6bcf768b-beab-4c39-6d96-91266036a4e1\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,0.01563,-1.85938],\"to\":[-0.01562,5.60938,1.85937],\"autouv\":0,\"color\":6,\"inflate\":0.25,\"origin\":[-1.40625,4.6875,-0.00001],\"uv_offset\":[0,54],\"faces\":{\"north\":{\"uv\":[4,58,8,64],\"texture\":0},\"east\":{\"uv\":[0,58,4,64],\"texture\":0},\"south\":{\"uv\":[12,58,16,64],\"texture\":0},\"west\":{\"uv\":[8,58,12,64],\"texture\":0},\"up\":{\"uv\":[4,52,0,48],\"texture\":0},\"down\":{\"uv\":[12,48,8,52],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"274282a7-bc7b-02f8-4dce-84226e9dd9c1\"},{\"name\":\"skin\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.75,22.5,-3.75],\"to\":[3.75,30,3.75],\"autouv\":0,\"color\":4,\"origin\":[0,22.5,-1.875],\"faces\":{\"north\":{\"uv\":[8,8,16,16],\"texture\":0},\"east\":{\"uv\":[0,8,8,16],\"texture\":0},\"south\":{\"uv\":[24,8,32,16],\"texture\":0},\"west\":{\"uv\":[16,8,24,16],\"texture\":0},\"up\":{\"uv\":[16,8,8,0],\"texture\":0},\"down\":{\"uv\":[24,0,16,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"7f60fbaf-510d-2e5f-b7d2-9111e08443cd\"},{\"name\":\"hat\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-3.73437,22.51563,-3.73437],\"to\":[3.73438,29.98438,3.73438],\"autouv\":0,\"color\":4,\"inflate\":0.5,\"origin\":[0,22.5,-1.875],\"uv_offset\":[32,0],\"faces\":{\"north\":{\"uv\":[40,8,48,16],\"texture\":0},\"east\":{\"uv\":[32,8,40,16],\"texture\":0},\"south\":{\"uv\":[56,8,64,16],\"texture\":0},\"west\":{\"uv\":[48,8,56,16],\"texture\":0},\"up\":{\"uv\":[48,8,40,0],\"texture\":0},\"down\":{\"uv\":[56,0,48,8],\"texture\":0}},\"type\":\"cube\",\"uuid\":\"e0f94313-bf88-492d-0c68-bd6b466a3b68\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.15,15,-0.75],\"to\":[6.85,16.5,0.75],\"autouv\":0,\"color\":2,\"origin\":[5,10.5,-1],\"faces\":{\"north\":{\"uv\":[27,22,29,24],\"texture\":1},\"east\":{\"uv\":[27,24,29,26],\"texture\":1},\"south\":{\"uv\":[27,26,29,28],\"texture\":1},\"west\":{\"uv\":[0,28,2,30],\"texture\":1},\"up\":{\"uv\":[30,2,28,0],\"texture\":1},\"down\":{\"uv\":[30,9,28,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"aa7d3639-52cb-087e-0f54-7e31afe0d4b3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,19,0.25],\"to\":[6.8,21,3.75],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[28,11,30,13],\"texture\":1},\"east\":{\"uv\":[17,21,21,23],\"texture\":1},\"south\":{\"uv\":[19,28,21,30],\"texture\":1},\"west\":{\"uv\":[21,21,25,23],\"texture\":1},\"up\":{\"uv\":[24,4,22,0],\"texture\":1},\"down\":{\"uv\":[24,4,22,8],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"596ead89-97bf-b419-dc31-372fe3dee9b9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.75,18.5,-1.25],\"to\":[7.25,21,1.25],\"autouv\":0,\"color\":2,\"rotation\":[-45,0,0],\"origin\":[6,19.75,0],\"faces\":{\"north\":{\"uv\":[17,18,20,21],\"texture\":1},\"east\":{\"uv\":[19,0,22,3],\"texture\":1},\"south\":{\"uv\":[19,3,22,6],\"texture\":1},\"west\":{\"uv\":[19,6,22,9],\"texture\":1},\"up\":{\"uv\":[22,12,19,9],\"texture\":1},\"down\":{\"uv\":[22,12,19,15],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"4853fc6c-23a8-6a4b-07e2-d89b3aaa8c1c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.225,19,0.375],\"to\":[6.775,20.5,2.5],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,19.5,2.625],\"faces\":{\"north\":{\"uv\":[21,28,23,30],\"texture\":1},\"east\":{\"uv\":[23,28,25,30],\"texture\":1},\"south\":{\"uv\":[25,28,27,30],\"texture\":1},\"west\":{\"uv\":[27,28,29,30],\"texture\":1},\"up\":{\"uv\":[31,4,29,2],\"texture\":1},\"down\":{\"uv\":[31,4,29,6],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a04ef22a-5322-93ad-5281-e6b33566d6af\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.225,19,-2.5],\"to\":[6.775,20.5,-0.375],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,19.5,-2.625],\"faces\":{\"north\":{\"uv\":[29,6,31,8],\"texture\":1},\"east\":{\"uv\":[29,13,31,15],\"texture\":1},\"south\":{\"uv\":[29,15,31,17],\"texture\":1},\"west\":{\"uv\":[29,17,31,19],\"texture\":1},\"up\":{\"uv\":[31,21,29,19],\"texture\":1},\"down\":{\"uv\":[31,21,29,23],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"dbcf2b15-20a0-6f43-48de-9c73557825c3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.15,17.5,-0.75],\"to\":[6.85,19,0.75],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[29,23,31,25],\"texture\":1},\"east\":{\"uv\":[29,25,31,27],\"texture\":1},\"south\":{\"uv\":[29,27,31,29],\"texture\":1},\"west\":{\"uv\":[29,29,31,31],\"texture\":1},\"up\":{\"uv\":[2,32,0,30],\"texture\":1},\"down\":{\"uv\":[32,0,30,2],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"656eb85b-2130-71d1-8400-036016caf911\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.4,10.05,-0.6],\"to\":[6.6,10.75,0.6],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,10.4,0],\"faces\":{\"north\":{\"uv\":[28,2,29,3],\"texture\":1},\"east\":{\"uv\":[29,8,30,9],\"texture\":1},\"south\":{\"uv\":[30,12,31,13],\"texture\":1},\"west\":{\"uv\":[27,35,28,36],\"texture\":1},\"up\":{\"uv\":[36,29,35,28],\"texture\":1},\"down\":{\"uv\":[30,35,29,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"61014d9b-3a39-4bbb-24b1-c1796ff81448\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,19.925,2.375],\"to\":[6.825,21.675,3.6],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,20.925,2.3],\"faces\":{\"north\":{\"uv\":[5,30,7,32],\"texture\":1},\"east\":{\"uv\":[22,16,23,18],\"texture\":1},\"south\":{\"uv\":[30,8,32,10],\"texture\":1},\"west\":{\"uv\":[24,14,25,16],\"texture\":1},\"up\":{\"uv\":[25,21,23,20],\"texture\":1},\"down\":{\"uv\":[34,11,32,12],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"6e13574b-8944-aaee-8ad1-92edda71d4a3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,19.925,-3.6],\"to\":[6.825,21.675,-2.375],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,20.925,-2.3],\"faces\":{\"north\":{\"uv\":[30,10,32,12],\"texture\":1},\"east\":{\"uv\":[33,9,34,11],\"texture\":1},\"south\":{\"uv\":[17,30,19,32],\"texture\":1},\"west\":{\"uv\":[33,12,34,14],\"texture\":1},\"up\":{\"uv\":[35,4,33,3],\"texture\":1},\"down\":{\"uv\":[35,14,33,15],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"5e0081ae-2c0e-03b7-b533-f33b34b8b9c8\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.25,6.25,-0.5],\"to\":[6.75,7.5,0.75],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,7,0],\"faces\":{\"north\":{\"uv\":[33,31,35,32],\"texture\":1},\"east\":{\"uv\":[34,35,35,36],\"texture\":1},\"south\":{\"uv\":[33,32,35,33],\"texture\":1},\"west\":{\"uv\":[35,34,36,35],\"texture\":1},\"up\":{\"uv\":[35,34,33,33],\"texture\":1},\"down\":{\"uv\":[36,0,34,1],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"926dfb9a-8a36-2d53-6026-df9e212d6187\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5,13.75,-0.9],\"to\":[7,15,0.9],\"autouv\":0,\"color\":2,\"origin\":[5,10.25,-1],\"faces\":{\"north\":{\"uv\":[34,1,36,2],\"texture\":1},\"east\":{\"uv\":[34,2,36,3],\"texture\":1},\"south\":{\"uv\":[34,4,36,5],\"texture\":1},\"west\":{\"uv\":[5,34,7,35],\"texture\":1},\"up\":{\"uv\":[23,32,21,30],\"texture\":1},\"down\":{\"uv\":[25,30,23,32],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"34f7ace6-abb3-82f1-ffe6-aa94ccf29240\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.025,14.05,0.725],\"to\":[6.975,15.2,1.2],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,14.675,0.3],\"faces\":{\"north\":{\"uv\":[35,3,37,4],\"texture\":1},\"east\":{\"uv\":[18,38,19,39],\"texture\":1},\"south\":{\"uv\":[4,35,6,36],\"texture\":1},\"west\":{\"uv\":[38,18,39,19],\"texture\":1},\"up\":{\"uv\":[8,36,6,35],\"texture\":1},\"down\":{\"uv\":[15,35,13,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"3616c0c5-2bd5-b180-4ed5-2708c59c41bc\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.025,14.05,-1.2],\"to\":[6.975,15.2,-0.725],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,14.675,-0.3],\"faces\":{\"north\":{\"uv\":[35,14,37,15],\"texture\":1},\"east\":{\"uv\":[19,38,20,39],\"texture\":1},\"south\":{\"uv\":[15,35,17,36],\"texture\":1},\"west\":{\"uv\":[38,19,39,20],\"texture\":1},\"up\":{\"uv\":[19,36,17,35],\"texture\":1},\"down\":{\"uv\":[21,35,19,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"20fe1d98-3b1b-7a8f-7945-78be2423e61c\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.05,13.025,-0.9],\"to\":[6.95,14.3,0.375],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,13.4,0],\"faces\":{\"north\":{\"uv\":[34,30,36,31],\"texture\":1},\"east\":{\"uv\":[17,38,18,39],\"texture\":1},\"south\":{\"uv\":[31,34,33,35],\"texture\":1},\"west\":{\"uv\":[38,17,39,18],\"texture\":1},\"up\":{\"uv\":[35,35,33,34],\"texture\":1},\"down\":{\"uv\":[4,35,2,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"bec338ce-2cb6-bade-c2a0-bf4b0256b6b2\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.4,9.05,-0.6],\"to\":[6.6,9.75,0.6],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,9.4,0],\"faces\":{\"north\":{\"uv\":[22,38,23,39],\"texture\":1},\"east\":{\"uv\":[38,22,39,23],\"texture\":1},\"south\":{\"uv\":[23,38,24,39],\"texture\":1},\"west\":{\"uv\":[38,23,39,24],\"texture\":1},\"up\":{\"uv\":[25,39,24,38],\"texture\":1},\"down\":{\"uv\":[39,24,38,25],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"cc8271b8-7122-2c3d-7a10-390515d38ea2\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.4,8.05,-0.6],\"to\":[6.6,8.75,0.6],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,8.4,0],\"faces\":{\"north\":{\"uv\":[35,35,36,36],\"texture\":1},\"east\":{\"uv\":[0,36,1,37],\"texture\":1},\"south\":{\"uv\":[36,0,37,1],\"texture\":1},\"west\":{\"uv\":[1,36,2,37],\"texture\":1},\"up\":{\"uv\":[37,2,36,1],\"texture\":1},\"down\":{\"uv\":[3,36,2,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2d28b727-641e-2dc9-8e5f-15c9ed002244\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,7,-0.5],\"to\":[6.5,13.75,0.5],\"autouv\":0,\"color\":2,\"origin\":[5,10.5,-1],\"faces\":{\"north\":{\"uv\":[2,24,3,31],\"texture\":1},\"east\":{\"uv\":[3,24,4,31],\"texture\":1},\"south\":{\"uv\":[4,24,5,31],\"texture\":1},\"west\":{\"uv\":[24,4,25,11],\"texture\":1},\"up\":{\"uv\":[37,3,36,2],\"texture\":1},\"down\":{\"uv\":[4,36,3,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"68112592-c5eb-c6ba-7370-8df3fd15c5be\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[6.3,14.7,0.925],\"to\":[6.75,19.15,1.675],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6.55,17.075,1.425],\"faces\":{\"north\":{\"uv\":[25,30,26,34],\"texture\":1},\"east\":{\"uv\":[26,30,27,34],\"texture\":1},\"south\":{\"uv\":[27,30,28,34],\"texture\":1},\"west\":{\"uv\":[28,30,29,34],\"texture\":1},\"up\":{\"uv\":[5,37,4,36],\"texture\":1},\"down\":{\"uv\":[37,4,36,5],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"6bfcb5a6-b98e-2362-4350-0cff8e7c8417\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.25,14.7,0.925],\"to\":[5.7,19.15,1.675],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[5.45,17.075,1.425],\"faces\":{\"north\":{\"uv\":[2,31,3,35],\"texture\":1},\"east\":{\"uv\":[31,2,32,6],\"texture\":1},\"south\":{\"uv\":[3,31,4,35],\"texture\":1},\"west\":{\"uv\":[4,31,5,35],\"texture\":1},\"up\":{\"uv\":[6,37,5,36],\"texture\":1},\"down\":{\"uv\":[37,5,36,6],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"8ec387cf-bccc-0ef1-a478-2c3c0c430d41\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.95,18.575,-5.425],\"to\":[7.05,21.45,-3.75],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,20.925,-4.45],\"faces\":{\"north\":{\"uv\":[21,25,23,28],\"texture\":1},\"east\":{\"uv\":[23,25,25,28],\"texture\":1},\"south\":{\"uv\":[25,25,27,28],\"texture\":1},\"west\":{\"uv\":[26,0,28,3],\"texture\":1},\"up\":{\"uv\":[33,8,31,6],\"texture\":1},\"down\":{\"uv\":[33,12,31,14],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"da16de9a-4659-1141-c033-4ab1a11199da\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,19,-3.75],\"to\":[6.8,21,-0.25],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[31,14,33,16],\"texture\":1},\"east\":{\"uv\":[19,23,23,25],\"texture\":1},\"south\":{\"uv\":[31,16,33,18],\"texture\":1},\"west\":{\"uv\":[23,23,27,25],\"texture\":1},\"up\":{\"uv\":[2,28,0,24],\"texture\":1},\"down\":{\"uv\":[26,0,24,4],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"b55c73af-0b1d-14ae-5198-2782a6d7294a\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,18.075,5.2],\"to\":[6.8,20.675,6.3],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,1.75],\"faces\":{\"north\":{\"uv\":[26,10,28,13],\"texture\":1},\"east\":{\"uv\":[32,28,33,31],\"texture\":1},\"south\":{\"uv\":[27,3,29,6],\"texture\":1},\"west\":{\"uv\":[32,31,33,34],\"texture\":1},\"up\":{\"uv\":[36,14,34,13],\"texture\":1},\"down\":{\"uv\":[36,15,34,16],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a2bc1c86-9f7f-2d61-139f-584cbbdc9ca8\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.425,5.875],\"to\":[6.55,20.325,6.65],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6,19.475,6.2],\"faces\":{\"north\":{\"uv\":[21,35,22,37],\"texture\":1},\"east\":{\"uv\":[35,21,36,23],\"texture\":1},\"south\":{\"uv\":[22,35,23,37],\"texture\":1},\"west\":{\"uv\":[23,35,24,37],\"texture\":1},\"up\":{\"uv\":[21,39,20,38],\"texture\":1},\"down\":{\"uv\":[39,20,38,21],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"976bb6d9-d09f-36bb-8bb6-62fe808734f1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.425,-6.65],\"to\":[6.55,20.325,-5.875],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,19.475,-6.2],\"faces\":{\"north\":{\"uv\":[35,23,36,25],\"texture\":1},\"east\":{\"uv\":[24,35,25,37],\"texture\":1},\"south\":{\"uv\":[35,25,36,27],\"texture\":1},\"west\":{\"uv\":[26,35,27,37],\"texture\":1},\"up\":{\"uv\":[22,39,21,38],\"texture\":1},\"down\":{\"uv\":[39,21,38,22],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"db87f23a-29dc-e8a1-0d0d-9a2f369185af\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[4.95,18.575,3.75],\"to\":[7.05,21.45,5.425],\"autouv\":0,\"color\":2,\"rotation\":[22.5,0,0],\"origin\":[6,20.925,4.45],\"faces\":{\"north\":{\"uv\":[5,27,7,30],\"texture\":1},\"east\":{\"uv\":[27,6,29,9],\"texture\":1},\"south\":{\"uv\":[27,13,29,16],\"texture\":1},\"west\":{\"uv\":[27,16,29,19],\"texture\":1},\"up\":{\"uv\":[33,20,31,18],\"texture\":1},\"down\":{\"uv\":[33,20,31,22],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"0714f281-db15-8143-c32f-e89576734cad\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.725,4.475],\"to\":[6.55,20.2,5.2],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,1.75],\"faces\":{\"north\":{\"uv\":[8,36,9,37],\"texture\":1},\"east\":{\"uv\":[36,8,37,9],\"texture\":1},\"south\":{\"uv\":[9,36,10,37],\"texture\":1},\"west\":{\"uv\":[36,9,37,10],\"texture\":1},\"up\":{\"uv\":[11,37,10,36],\"texture\":1},\"down\":{\"uv\":[37,10,36,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a4916b0b-ece2-a56b-c694-cb963a9a80b5\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.2,18.075,-6.3],\"to\":[6.8,20.675,-5.2],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,-1.75],\"faces\":{\"north\":{\"uv\":[17,27,19,30],\"texture\":1},\"east\":{\"uv\":[33,0,34,3],\"texture\":1},\"south\":{\"uv\":[27,19,29,22],\"texture\":1},\"west\":{\"uv\":[33,6,34,9],\"texture\":1},\"up\":{\"uv\":[36,17,34,16],\"texture\":1},\"down\":{\"uv\":[36,17,34,18],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"9916eeef-31e1-9c75-ffda-14bd4d589032\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.45,18.725,-5.2],\"to\":[6.55,20.2,-4.475],\"autouv\":0,\"color\":2,\"origin\":[5,12.75,-1.75],\"faces\":{\"north\":{\"uv\":[11,36,12,37],\"texture\":1},\"east\":{\"uv\":[36,11,37,12],\"texture\":1},\"south\":{\"uv\":[12,36,13,37],\"texture\":1},\"west\":{\"uv\":[36,12,37,13],\"texture\":1},\"up\":{\"uv\":[14,37,13,36],\"texture\":1},\"down\":{\"uv\":[37,13,36,14],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"cb63eadb-42fb-9f2c-0cb2-f6b85a7607f9\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.25,14.7,-1.675],\"to\":[5.7,19.15,-0.925],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[5.45,17.075,-1.425],\"faces\":{\"north\":{\"uv\":[31,22,32,26],\"texture\":1},\"east\":{\"uv\":[31,26,32,30],\"texture\":1},\"south\":{\"uv\":[29,31,30,35],\"texture\":1},\"west\":{\"uv\":[30,31,31,35],\"texture\":1},\"up\":{\"uv\":[15,37,14,36],\"texture\":1},\"down\":{\"uv\":[16,36,15,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"8ae9fcff-9cca-67c2-69e2-f50e85162de2\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[6.3,14.7,-1.675],\"to\":[6.75,19.15,-0.925],\"autouv\":0,\"color\":2,\"rotation\":[-22.5,0,0],\"origin\":[6.55,17.075,-1.425],\"faces\":{\"north\":{\"uv\":[31,30,32,34],\"texture\":1},\"east\":{\"uv\":[0,32,1,36],\"texture\":1},\"south\":{\"uv\":[32,0,33,4],\"texture\":1},\"west\":{\"uv\":[1,32,2,36],\"texture\":1},\"up\":{\"uv\":[37,16,36,15],\"texture\":1},\"down\":{\"uv\":[17,36,16,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"792785c8-f110-dcf7-d0e4-61f223963cfd\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5,16.5,-1.05],\"to\":[7,17.5,1.05],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[34,18,36,19],\"texture\":1},\"east\":{\"uv\":[19,34,21,35],\"texture\":1},\"south\":{\"uv\":[34,19,36,20],\"texture\":1},\"west\":{\"uv\":[34,20,36,21],\"texture\":1},\"up\":{\"uv\":[34,6,32,4],\"texture\":1},\"down\":{\"uv\":[7,32,5,34],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2ad8829a-0eaf-c32f-765a-76ad72a1b336\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.75,16.75,1],\"to\":[6.25,17.25,1.7],\"autouv\":0,\"color\":2,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[36,16,37,17],\"texture\":1},\"east\":{\"uv\":[17,36,18,37],\"texture\":1},\"south\":{\"uv\":[36,17,37,18],\"texture\":1},\"west\":{\"uv\":[18,36,19,37],\"texture\":1},\"up\":{\"uv\":[37,19,36,18],\"texture\":1},\"down\":{\"uv\":[20,36,19,37],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2181542e-699e-c408-2ec8-b291a6325c33\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,16.75,1.55],\"to\":[6.5,17.25,2.05],\"autouv\":0,\"color\":2,\"rotation\":[-45,0,0],\"origin\":[6,17,1.8],\"faces\":{\"north\":{\"uv\":[36,19,37,20],\"texture\":1},\"east\":{\"uv\":[20,36,21,37],\"texture\":1},\"south\":{\"uv\":[36,20,37,21],\"texture\":1},\"west\":{\"uv\":[36,21,37,22],\"texture\":1},\"up\":{\"uv\":[37,23,36,22],\"texture\":1},\"down\":{\"uv\":[37,23,36,24],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"58cd5f7e-7a90-1ed7-b5c8-5d739d36bb28\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.75,16.75,-1.7],\"to\":[6.25,17.25,-1],\"autouv\":0,\"color\":2,\"origin\":[5,13,1],\"faces\":{\"north\":{\"uv\":[36,24,37,25],\"texture\":1},\"east\":{\"uv\":[25,36,26,37],\"texture\":1},\"south\":{\"uv\":[36,25,37,26],\"texture\":1},\"west\":{\"uv\":[36,26,37,27],\"texture\":1},\"up\":{\"uv\":[28,37,27,36],\"texture\":1},\"down\":{\"uv\":[37,27,36,28],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"71ebf1c3-a5f4-6dbf-8533-0b7bdc6093f4\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,16.75,-2.05],\"to\":[6.5,17.25,-1.55],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,17,-1.8],\"faces\":{\"north\":{\"uv\":[28,36,29,37],\"texture\":1},\"east\":{\"uv\":[36,28,37,29],\"texture\":1},\"south\":{\"uv\":[29,36,30,37],\"texture\":1},\"west\":{\"uv\":[36,29,37,30],\"texture\":1},\"up\":{\"uv\":[31,37,30,36],\"texture\":1},\"down\":{\"uv\":[37,30,36,31],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"3f2aa919-4264-20d2-4dc0-5b3963226082\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,21.3,-3.725],\"to\":[6.825,21.95,-3.5],\"autouv\":0,\"color\":2,\"rotation\":[-45,0,0],\"origin\":[6,21.2,-3.425],\"faces\":{\"north\":{\"uv\":[34,5,36,6],\"texture\":1},\"east\":{\"uv\":[6,36,7,37],\"texture\":1},\"south\":{\"uv\":[34,6,36,7],\"texture\":1},\"west\":{\"uv\":[36,6,37,7],\"texture\":1},\"up\":{\"uv\":[36,8,34,7],\"texture\":1},\"down\":{\"uv\":[36,8,34,9],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"188d0aa7-7147-c70b-58ea-850b803f6a8f\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.175,21.3,3.5],\"to\":[6.825,21.95,3.725],\"autouv\":0,\"color\":2,\"rotation\":[45,0,0],\"origin\":[6,21.2,3.425],\"faces\":{\"north\":{\"uv\":[34,9,36,10],\"texture\":1},\"east\":{\"uv\":[7,36,8,37],\"texture\":1},\"south\":{\"uv\":[34,10,36,11],\"texture\":1},\"west\":{\"uv\":[36,7,37,8],\"texture\":1},\"up\":{\"uv\":[36,12,34,11],\"texture\":1},\"down\":{\"uv\":[36,12,34,13],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"7de5534c-d30a-43f1-6eb9-25858b29fce0\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,41.05,-1.3],\"to\":[6.5,44.55,0.7],\"autouv\":0,\"color\":1,\"rotation\":[-20,0,0],\"origin\":[6,48.8,-0.55],\"faces\":{\"north\":{\"uv\":[19,30,20,34],\"texture\":1},\"east\":{\"uv\":[23,16,25,20],\"texture\":1},\"south\":{\"uv\":[20,30,21,34],\"texture\":1},\"west\":{\"uv\":[17,23,19,27],\"texture\":1},\"up\":{\"uv\":[34,21,33,19],\"texture\":1},\"down\":{\"uv\":[34,21,33,23],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e85c96f1-e8a9-dc1b-28ef-5270bd4fdeea\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24,1],\"to\":[6.5,41.75,2.25],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[8,0,9,18],\"texture\":1},\"east\":{\"uv\":[9,0,10,18],\"texture\":1},\"south\":{\"uv\":[10,0,11,18],\"texture\":1},\"west\":{\"uv\":[11,0,12,18],\"texture\":1},\"up\":{\"uv\":[26,11,25,10],\"texture\":1},\"down\":{\"uv\":[27,3,26,4],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e2626689-c30d-fc58-7143-b714a916e0e3\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,21,2.45],\"to\":[6.5,24,3.15],\"autouv\":0,\"color\":1,\"rotation\":[0,45,0],\"origin\":[6,28.5,2.95],\"faces\":{\"north\":{\"uv\":[13,32,14,35],\"texture\":1},\"east\":{\"uv\":[14,32,15,35],\"texture\":1},\"south\":{\"uv\":[15,32,16,35],\"texture\":1},\"west\":{\"uv\":[16,32,17,35],\"texture\":1},\"up\":{\"uv\":[36,30,35,29],\"texture\":1},\"down\":{\"uv\":[31,35,30,36],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"b4e24ddd-0288-312f-4d29-4ee1bcad5926\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,24,1.95],\"to\":[6.5,41.75,2.65],\"autouv\":0,\"color\":1,\"rotation\":[0,45,0],\"origin\":[6,31.5,2.45],\"faces\":{\"north\":{\"uv\":[9,18,10,36],\"texture\":1},\"east\":{\"uv\":[10,18,11,36],\"texture\":1},\"south\":{\"uv\":[11,18,12,36],\"texture\":1},\"west\":{\"uv\":[12,18,13,36],\"texture\":1},\"up\":{\"uv\":[34,36,33,35],\"texture\":1},\"down\":{\"uv\":[36,33,35,34],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"c7057fe5-eff1-3481-3701-b964a84f1c58\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,24,-2.65],\"to\":[6.5,41.75,-1.95],\"autouv\":0,\"color\":1,\"rotation\":[0,-45,0],\"origin\":[6,31.5,-2.45],\"faces\":{\"north\":{\"uv\":[16,0,17,18],\"texture\":1},\"east\":{\"uv\":[17,0,18,18],\"texture\":1},\"south\":{\"uv\":[18,0,19,18],\"texture\":1},\"west\":{\"uv\":[8,18,9,36],\"texture\":1},\"up\":{\"uv\":[32,36,31,35],\"texture\":1},\"down\":{\"uv\":[36,31,35,32],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"662518b3-6709-eb4e-6278-8c012d80c705\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.8,21,-3.15],\"to\":[6.5,24,-2.45],\"autouv\":0,\"color\":1,\"rotation\":[0,-45,0],\"origin\":[6,28.5,-2.95],\"faces\":{\"north\":{\"uv\":[32,22,33,25],\"texture\":1},\"east\":{\"uv\":[23,32,24,35],\"texture\":1},\"south\":{\"uv\":[24,32,25,35],\"texture\":1},\"west\":{\"uv\":[32,25,33,28],\"texture\":1},\"up\":{\"uv\":[33,36,32,35],\"texture\":1},\"down\":{\"uv\":[36,32,35,33],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"5bf81b09-08ec-d7a1-c186-9a432aac7ca1\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.75,21,-1.25],\"to\":[6.25,44.5,1.25],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[6,0,7,24],\"texture\":1},\"east\":{\"uv\":[0,0,3,24],\"texture\":1},\"south\":{\"uv\":[7,0,8,24],\"texture\":1},\"west\":{\"uv\":[3,0,6,24],\"texture\":1},\"up\":{\"uv\":[8,35,7,32],\"texture\":1},\"down\":{\"uv\":[33,8,32,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a9a094c2-0f3d-58d6-b21e-6ba6ba3b526b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,43.85,-2.15],\"to\":[6.5,46.9,0.9],\"autouv\":0,\"color\":1,\"rotation\":[-45,0,0],\"origin\":[6,46,0],\"faces\":{\"north\":{\"uv\":[5,24,7,27],\"texture\":1},\"east\":{\"uv\":[19,15,22,18],\"texture\":1},\"south\":{\"uv\":[24,11,26,14],\"texture\":1},\"west\":{\"uv\":[20,18,23,21],\"texture\":1},\"up\":{\"uv\":[27,7,25,4],\"texture\":1},\"down\":{\"uv\":[27,7,25,10],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"64526f07-ded7-1117-c95e-5e72febd29f7\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24,-2.25],\"to\":[6.5,41.75,-1],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[12,0,13,18],\"texture\":1},\"east\":{\"uv\":[13,0,14,18],\"texture\":1},\"south\":{\"uv\":[14,0,15,18],\"texture\":1},\"west\":{\"uv\":[15,0,16,18],\"texture\":1},\"up\":{\"uv\":[27,14,26,13],\"texture\":1},\"down\":{\"uv\":[28,9,27,10],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"d6541026-10ee-fe6a-8fe9-72cf75d33923\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,21,1.25],\"to\":[6.5,24,2.75],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[21,32,22,35],\"texture\":1},\"east\":{\"uv\":[19,25,21,28],\"texture\":1},\"south\":{\"uv\":[22,32,23,35],\"texture\":1},\"west\":{\"uv\":[25,20,27,23],\"texture\":1},\"up\":{\"uv\":[34,29,33,27],\"texture\":1},\"down\":{\"uv\":[34,29,33,31],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"791744bb-3a31-3779-4bdf-f486a6933875\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,21,-2.75],\"to\":[6.5,24,-1.25],\"autouv\":0,\"color\":1,\"origin\":[5,13,-1],\"faces\":{\"north\":{\"uv\":[17,32,18,35],\"texture\":1},\"east\":{\"uv\":[25,14,27,17],\"texture\":1},\"south\":{\"uv\":[18,32,19,35],\"texture\":1},\"west\":{\"uv\":[25,17,27,20],\"texture\":1},\"up\":{\"uv\":[34,25,33,23],\"texture\":1},\"down\":{\"uv\":[34,25,33,27],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"d9dda869-2759-4199-c1d8-b42d195aa834\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,41.05,-0.7],\"to\":[6.5,44.55,1.3],\"autouv\":0,\"color\":1,\"rotation\":[20,0,0],\"origin\":[6,48.8,0.55],\"faces\":{\"north\":{\"uv\":[7,24,8,28],\"texture\":1},\"east\":{\"uv\":[22,8,24,12],\"texture\":1},\"south\":{\"uv\":[7,28,8,32],\"texture\":1},\"west\":{\"uv\":[22,12,24,16],\"texture\":1},\"up\":{\"uv\":[34,17,33,15],\"texture\":1},\"down\":{\"uv\":[34,17,33,19],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"abcc99c6-960c-1045-82bd-0a39d53f7a49\"},{\"name\":\"rune_1\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,21.975,-0.125],\"to\":[6.5,22.475,0.125],\"autouv\":0,\"color\":0,\"origin\":[6,35.975,0.375],\"faces\":{\"north\":{\"uv\":[2,38,3,39],\"texture\":1},\"east\":{\"uv\":[38,2,39,3],\"texture\":1},\"south\":{\"uv\":[3,38,4,39],\"texture\":1},\"west\":{\"uv\":[38,3,39,4],\"texture\":1},\"up\":{\"uv\":[5,39,4,38],\"texture\":1},\"down\":{\"uv\":[39,4,38,5],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"44592a16-3ff8-e5d6-5197-a3d5fdb91e94\"},{\"name\":\"rune_2\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,22.5625,-0.75],\"to\":[6.5,23.3125,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,21.8125,0],\"faces\":{\"north\":{\"uv\":[37,29,38,30],\"texture\":1},\"east\":{\"uv\":[30,37,31,38],\"texture\":1},\"south\":{\"uv\":[37,30,38,31],\"texture\":1},\"west\":{\"uv\":[31,37,32,38],\"texture\":1},\"up\":{\"uv\":[38,32,37,31],\"texture\":1},\"down\":{\"uv\":[33,37,32,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"49542da5-539a-7743-3bbc-afdee9d770c3\"},{\"name\":\"rune_2\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,22.1875,-1.5],\"to\":[6.5,22.5625,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,21.8125,0],\"faces\":{\"north\":{\"uv\":[37,36,38,37],\"texture\":1},\"east\":{\"uv\":[37,37,38,38],\"texture\":1},\"south\":{\"uv\":[0,38,1,39],\"texture\":1},\"west\":{\"uv\":[38,0,39,1],\"texture\":1},\"up\":{\"uv\":[2,39,1,38],\"texture\":1},\"down\":{\"uv\":[39,1,38,2],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a79acc07-9567-e68d-0a57-109e40d9acbc\"},{\"name\":\"rune_3\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24.25,-1.875],\"to\":[6.5,24.625,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,25,0],\"faces\":{\"north\":{\"uv\":[37,35,38,36],\"texture\":1},\"east\":{\"uv\":[26,34,28,35],\"texture\":1},\"south\":{\"uv\":[36,37,37,38],\"texture\":1},\"west\":{\"uv\":[34,27,36,28],\"texture\":1},\"up\":{\"uv\":[29,36,28,34],\"texture\":1},\"down\":{\"uv\":[35,28,34,30],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"8cadf035-ee63-7e4e-858c-fc3f8115fa7e\"},{\"name\":\"rune_3\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,23.125,-0.75],\"to\":[6.5,24.25,-0.375],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,25,0],\"faces\":{\"north\":{\"uv\":[37,32,38,33],\"texture\":1},\"east\":{\"uv\":[33,37,34,38],\"texture\":1},\"south\":{\"uv\":[37,33,38,34],\"texture\":1},\"west\":{\"uv\":[34,37,35,38],\"texture\":1},\"up\":{\"uv\":[38,35,37,34],\"texture\":1},\"down\":{\"uv\":[36,37,35,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a9027c5e-52c6-19e9-e232-a35125734ea3\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,24.35,-0.125],\"to\":[6.5,37.85,0.125],\"autouv\":0,\"color\":0,\"origin\":[6,36.1,0.375],\"faces\":{\"north\":{\"uv\":[13,18,14,32],\"texture\":1},\"east\":{\"uv\":[14,18,15,32],\"texture\":1},\"south\":{\"uv\":[15,18,16,32],\"texture\":1},\"west\":{\"uv\":[16,18,17,32],\"texture\":1},\"up\":{\"uv\":[38,11,37,10],\"texture\":1},\"down\":{\"uv\":[12,37,11,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2cec3ae3-79ee-9f96-74f5-0e90611c5e6d\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,-0.75],\"to\":[6.5,26.05,-0.3],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[5.875,25.725,-0.425],\"faces\":{\"north\":{\"uv\":[5,38,6,39],\"texture\":1},\"east\":{\"uv\":[38,5,39,6],\"texture\":1},\"south\":{\"uv\":[6,38,7,39],\"texture\":1},\"west\":{\"uv\":[38,6,39,7],\"texture\":1},\"up\":{\"uv\":[8,39,7,38],\"texture\":1},\"down\":{\"uv\":[39,7,38,8],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e204bb47-1379-ea43-8d02-0b48c88a6282\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,-0.375],\"to\":[6.5,25.85,-0.125],\"autouv\":0,\"color\":0,\"origin\":[6,30.1,0.375],\"faces\":{\"north\":{\"uv\":[8,38,9,39],\"texture\":1},\"east\":{\"uv\":[38,8,39,9],\"texture\":1},\"south\":{\"uv\":[9,38,10,39],\"texture\":1},\"west\":{\"uv\":[38,9,39,10],\"texture\":1},\"up\":{\"uv\":[11,39,10,38],\"texture\":1},\"down\":{\"uv\":[39,10,38,11],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"4c44df55-108a-c9c8-3c9c-3c3bdd7c3a6a\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,0.3],\"to\":[6.5,26.05,0.75],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[5.875,25.725,0.425],\"faces\":{\"north\":{\"uv\":[11,38,12,39],\"texture\":1},\"east\":{\"uv\":[38,11,39,12],\"texture\":1},\"south\":{\"uv\":[12,38,13,39],\"texture\":1},\"west\":{\"uv\":[38,12,39,13],\"texture\":1},\"up\":{\"uv\":[14,39,13,38],\"texture\":1},\"down\":{\"uv\":[39,13,38,14],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a4083602-2242-1b62-262d-bfd38f8c77f4\"},{\"name\":\"rune_4\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,25.6,0.125],\"to\":[6.5,25.85,0.375],\"autouv\":0,\"color\":0,\"origin\":[6,30.1,-0.375],\"faces\":{\"north\":{\"uv\":[14,38,15,39],\"texture\":1},\"east\":{\"uv\":[38,14,39,15],\"texture\":1},\"south\":{\"uv\":[15,38,16,39],\"texture\":1},\"west\":{\"uv\":[38,15,39,16],\"texture\":1},\"up\":{\"uv\":[17,39,16,38],\"texture\":1},\"down\":{\"uv\":[39,16,38,17],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"138546ac-7f07-33e3-b36d-0e06f98d386c\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.875,-0.5],\"to\":[6.5,38.375,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,37.375,0],\"faces\":{\"north\":{\"uv\":[37,4,38,5],\"texture\":1},\"east\":{\"uv\":[5,37,6,38],\"texture\":1},\"south\":{\"uv\":[37,5,38,6],\"texture\":1},\"west\":{\"uv\":[6,37,7,38],\"texture\":1},\"up\":{\"uv\":[38,7,37,6],\"texture\":1},\"down\":{\"uv\":[8,37,7,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"34d77773-c97c-7edd-a72c-887e3f21afcf\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.625,-1],\"to\":[6.5,37.875,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[45,0,0],\"origin\":[6,37.375,0],\"faces\":{\"north\":{\"uv\":[37,7,38,8],\"texture\":1},\"east\":{\"uv\":[8,37,9,38],\"texture\":1},\"south\":{\"uv\":[37,8,38,9],\"texture\":1},\"west\":{\"uv\":[9,37,10,38],\"texture\":1},\"up\":{\"uv\":[38,10,37,9],\"texture\":1},\"down\":{\"uv\":[11,37,10,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"952d8c6c-c534-3ded-ebbb-8560169862f5\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.425,-0.825],\"to\":[6.5,37.575,-0.075],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[5.875,37.55,-0.2],\"faces\":{\"north\":{\"uv\":[37,11,38,12],\"texture\":1},\"east\":{\"uv\":[12,37,13,38],\"texture\":1},\"south\":{\"uv\":[37,12,38,13],\"texture\":1},\"west\":{\"uv\":[13,37,14,38],\"texture\":1},\"up\":{\"uv\":[38,14,37,13],\"texture\":1},\"down\":{\"uv\":[15,37,14,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"677371f7-759a-8b3b-225b-89324ccd9e5c\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.925,-0.575],\"to\":[6.5,37.075,-0.075],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[5.875,37.05,-0.2],\"faces\":{\"north\":{\"uv\":[37,14,38,15],\"texture\":1},\"east\":{\"uv\":[15,37,16,38],\"texture\":1},\"south\":{\"uv\":[37,15,38,16],\"texture\":1},\"west\":{\"uv\":[16,37,17,38],\"texture\":1},\"up\":{\"uv\":[38,17,37,16],\"texture\":1},\"down\":{\"uv\":[18,37,17,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"09b6fa84-9268-c1f0-d593-f49988b0a6a8\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.425,-0.325],\"to\":[6.5,36.575,-0.075],\"autouv\":0,\"color\":0,\"rotation\":[-22.5,0,0],\"origin\":[5.875,36.55,-0.2],\"faces\":{\"north\":{\"uv\":[37,17,38,18],\"texture\":1},\"east\":{\"uv\":[18,37,19,38],\"texture\":1},\"south\":{\"uv\":[37,18,38,19],\"texture\":1},\"west\":{\"uv\":[19,37,20,38],\"texture\":1},\"up\":{\"uv\":[38,20,37,19],\"texture\":1},\"down\":{\"uv\":[21,37,20,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"2f533cab-6d66-5ebb-be57-520ac9cf1da9\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.425,0.075],\"to\":[6.5,36.575,0.325],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[5.875,36.55,0.2],\"faces\":{\"north\":{\"uv\":[37,20,38,21],\"texture\":1},\"east\":{\"uv\":[21,37,22,38],\"texture\":1},\"south\":{\"uv\":[37,21,38,22],\"texture\":1},\"west\":{\"uv\":[22,37,23,38],\"texture\":1},\"up\":{\"uv\":[38,23,37,22],\"texture\":1},\"down\":{\"uv\":[24,37,23,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e5a7d019-ec5d-ef93-5ffd-c092c664d2cb\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,36.925,0.075],\"to\":[6.5,37.075,0.575],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[5.875,37.05,0.2],\"faces\":{\"north\":{\"uv\":[37,23,38,24],\"texture\":1},\"east\":{\"uv\":[24,37,25,38],\"texture\":1},\"south\":{\"uv\":[37,24,38,25],\"texture\":1},\"west\":{\"uv\":[25,37,26,38],\"texture\":1},\"up\":{\"uv\":[38,26,37,25],\"texture\":1},\"down\":{\"uv\":[27,37,26,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"11316f78-2485-c065-fa4d-7b1225df2bf6\"},{\"name\":\"rune_5\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,37.425,0.075],\"to\":[6.5,37.575,0.825],\"autouv\":0,\"color\":0,\"rotation\":[22.5,0,0],\"origin\":[5.875,37.55,0.2],\"faces\":{\"north\":{\"uv\":[37,26,38,27],\"texture\":1},\"east\":{\"uv\":[27,37,28,38],\"texture\":1},\"south\":{\"uv\":[37,27,38,28],\"texture\":1},\"west\":{\"uv\":[28,37,29,38],\"texture\":1},\"up\":{\"uv\":[38,29,37,28],\"texture\":1},\"down\":{\"uv\":[30,37,29,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"e0d85bd5-8a87-a483-22e2-833204176141\"},{\"name\":\"rune_6\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,38.25,-0.5],\"to\":[6.5,39,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,39.5,0],\"faces\":{\"north\":{\"uv\":[37,1,38,2],\"texture\":1},\"east\":{\"uv\":[2,37,3,38],\"texture\":1},\"south\":{\"uv\":[37,2,38,3],\"texture\":1},\"west\":{\"uv\":[3,37,4,38],\"texture\":1},\"up\":{\"uv\":[38,4,37,3],\"texture\":1},\"down\":{\"uv\":[5,37,4,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"101292a8-6c83-621a-11ff-e6b17d4a82e2\"},{\"name\":\"rune_6\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,39,-1.25],\"to\":[6.5,39.25,-0.25],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[6,39.5,0],\"faces\":{\"north\":{\"uv\":[35,36,36,37],\"texture\":1},\"east\":{\"uv\":[36,35,37,36],\"texture\":1},\"south\":{\"uv\":[36,36,37,37],\"texture\":1},\"west\":{\"uv\":[0,37,1,38],\"texture\":1},\"up\":{\"uv\":[38,1,37,0],\"texture\":1},\"down\":{\"uv\":[2,37,1,38],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"a7c81b6d-2419-772e-27a1-0068365ebce7\"},{\"name\":\"rune_7\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,40.9,-0.125],\"to\":[6.5,42.15,1.125],\"autouv\":0,\"color\":0,\"rotation\":[-45,0,0],\"origin\":[5.875,41.025,0],\"faces\":{\"north\":{\"uv\":[31,36,32,37],\"texture\":1},\"east\":{\"uv\":[36,31,37,32],\"texture\":1},\"south\":{\"uv\":[32,36,33,37],\"texture\":1},\"west\":{\"uv\":[36,32,37,33],\"texture\":1},\"up\":{\"uv\":[34,37,33,36],\"texture\":1},\"down\":{\"uv\":[37,33,36,34],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"0ac8e87a-c521-a8ee-e14d-e205560a7626\"},{\"name\":\"rune_7\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[5.5,39.025,-0.125],\"to\":[6.5,41.025,0.125],\"autouv\":0,\"color\":0,\"origin\":[6,39.525,0.375],\"faces\":{\"north\":{\"uv\":[34,21,35,23],\"texture\":1},\"east\":{\"uv\":[34,23,35,25],\"texture\":1},\"south\":{\"uv\":[25,34,26,36],\"texture\":1},\"west\":{\"uv\":[34,25,35,27],\"texture\":1},\"up\":{\"uv\":[35,37,34,36],\"texture\":1},\"down\":{\"uv\":[37,34,36,35],\"texture\":1}},\"type\":\"cube\",\"uuid\":\"fc987521-07e4-10f9-b6fc-1512cb2da68b\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-1,35.8,-1],\"to\":[1,37.8,1],\"autouv\":1,\"color\":1,\"visibility\":false,\"origin\":[-1,36.8,-1],\"faces\":{\"north\":{\"uv\":[0,0,2,2]},\"east\":{\"uv\":[0,0,2,2]},\"south\":{\"uv\":[0,0,2,2]},\"west\":{\"uv\":[0,0,2,2]},\"up\":{\"uv\":[0,0,2,2]},\"down\":{\"uv\":[0,0,2,2]}},\"type\":\"cube\",\"uuid\":\"c8c75bdd-53bc-eb81-a2cb-0ea0dca06f35\"},{\"name\":\"cube\",\"box_uv\":false,\"render_order\":\"default\",\"locked\":false,\"allow_mirror_modeling\":true,\"from\":[-8,0,-8],\"to\":[8,0,8],\"autouv\":1,\"color\":9,\"visibility\":false,\"origin\":[0,0,0],\"faces\":{\"north\":{\"uv\":[0,0,16,0]},\"east\":{\"uv\":[0,0,16,0]},\"south\":{\"uv\":[0,0,16,0]},\"west\":{\"uv\":[0,0,16,0]},\"up\":{\"uv\":[0,0,16,16]},\"down\":{\"uv\":[0,0,16,16]}},\"type\":\"cube\",\"uuid\":\"164a0f3b-aa15-e098-8e5a-f1fdf3e78bdd\"}],\"groups\":[{\"uuid\":\"778fa89c-759a-8884-89d9-238c555d2dc1\",\"export\":true,\"locked\":false,\"origin\":[0,17,0],\"rotation\":[0,0,0],\"color\":9,\"name\":\"player_root\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"7e09ae80-d41f-d669-2538-64dec33e0e24\",\"export\":true,\"locked\":false,\"origin\":[0,17,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"shadow\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":false},{\"uuid\":\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\",\"export\":true,\"locked\":false,\"origin\":[0,11.25,0],\"rotation\":[0,0,0],\"color\":1,\"name\":\"phip_hip\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"e297aef6-7dfd-f100-2e7c-ab113699b922\",\"export\":true,\"locked\":false,\"origin\":[0,15,0],\"rotation\":[0,0,0],\"color\":2,\"name\":\"pw_waist\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"a0c01522-9040-7533-fa11-f6a45d3d96ac\",\"export\":true,\"locked\":false,\"origin\":[0,18.75,0],\"rotation\":[0,0,0],\"color\":3,\"name\":\"pc_chest\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"34097e46-c233-c03c-d8b9-aee154c9946f\",\"export\":true,\"locked\":false,\"origin\":[0,22.5,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"h_ph_head\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\",\"export\":true,\"locked\":false,\"origin\":[5,22,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"pra_right_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"cf1618da-24d8-aab8-eebc-128815c02d35\",\"export\":true,\"locked\":false,\"origin\":[5,16.5,0],\"rotation\":[0,0,0],\"color\":8,\"name\":\"prfa_right_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"fcaf8da0-0146-2587-b578-3e1af888deaa\",\"export\":true,\"locked\":false,\"origin\":[5.625,10.875,0],\"rotation\":[-90,0,0],\"color\":0,\"name\":\"pri_right_item\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"859e9460-0016-acb1-017b-25fe27244cac\",\"export\":true,\"locked\":false,\"origin\":[5.625,44.875,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"sword_point\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"dcb3a2dc-0f46-4628-586d-44f4265cc61c\",\"export\":true,\"locked\":false,\"origin\":[6,32.15507,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"sword_body\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"b3135254-0351-3462-2479-e6a3286c89ff\",\"export\":true,\"locked\":false,\"origin\":[-5,22,0],\"rotation\":[0,0,0],\"color\":4,\"name\":\"pla_left_arm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\",\"export\":true,\"locked\":false,\"origin\":[-5,16.5,0],\"rotation\":[0,0,0],\"color\":8,\"name\":\"plfa_left_forearm\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"0e94ff40-15b6-0b24-d0a0-447f000d761b\",\"export\":true,\"locked\":false,\"origin\":[-5.625,10.875,0],\"rotation\":[-90,0,0],\"color\":0,\"name\":\"pli_left_item\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\",\"export\":true,\"locked\":false,\"origin\":[1.875,11.25,0],\"rotation\":[0,0,0],\"color\":7,\"name\":\"prl_right_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"c6d9e946-1d10-482d-14b1-0766027adba8\",\"export\":true,\"locked\":false,\"origin\":[1.875,5.625,0],\"rotation\":[0,0,0],\"color\":6,\"name\":\"prfl_right_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\",\"export\":true,\"locked\":false,\"origin\":[-1.875,11.25,0],\"rotation\":[0,0,0],\"color\":7,\"name\":\"pll_left_leg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true},{\"uuid\":\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\",\"export\":true,\"locked\":false,\"origin\":[-1.875,5.625,0],\"rotation\":[0,0,0],\"color\":6,\"name\":\"plfl_left_foreleg\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":false},{\"uuid\":\"529b75e5-967f-8e76-9d18-0fc49998293a\",\"export\":true,\"locked\":false,\"origin\":[0,36.8,0],\"rotation\":[0,0,0],\"color\":0,\"name\":\"tag_name\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":false},{\"uuid\":\"dcbc3011-d7b7-bf7c-0fae-067573db08f5\",\"export\":true,\"locked\":false,\"origin\":[0,22.75,2],\"rotation\":[0,0,0],\"color\":0,\"name\":\"cape_cape\",\"children\":[],\"reset\":false,\"shade\":true,\"mirror_uv\":false,\"selected\":false,\"visibility\":true,\"autouv\":0,\"isOpen\":true}],\"outliner\":[{\"uuid\":\"778fa89c-759a-8884-89d9-238c555d2dc1\",\"isOpen\":true,\"children\":[{\"uuid\":\"7e09ae80-d41f-d669-2538-64dec33e0e24\",\"isOpen\":false,\"children\":[\"164a0f3b-aa15-e098-8e5a-f1fdf3e78bdd\"]},{\"uuid\":\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\",\"isOpen\":true,\"children\":[\"ea42f7f7-a6f1-4479-c43f-48211bab5ed2\",\"357ebf82-23ba-edb1-081f-dca75d94b83c\",{\"uuid\":\"e297aef6-7dfd-f100-2e7c-ab113699b922\",\"isOpen\":true,\"children\":[\"5ea74bdb-ba28-b8e3-103b-9be6ff2262da\",\"f5b9f499-b26b-f912-1a54-151d702e13ed\",{\"uuid\":\"a0c01522-9040-7533-fa11-f6a45d3d96ac\",\"isOpen\":true,\"children\":[{\"uuid\":\"dcbc3011-d7b7-bf7c-0fae-067573db08f5\",\"isOpen\":true,\"children\":[]},\"dc1510db-a719-17b4-e253-c992a92c5d25\",\"d51a8665-a2bc-af6e-1230-5acba07248e7\",{\"uuid\":\"34097e46-c233-c03c-d8b9-aee154c9946f\",\"isOpen\":true,\"children\":[\"7f60fbaf-510d-2e5f-b7d2-9111e08443cd\",\"e0f94313-bf88-492d-0c68-bd6b466a3b68\"]},{\"uuid\":\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\",\"isOpen\":true,\"children\":[\"53d40d2e-0941-29f9-00ed-1b19c941dcd8\",\"b17452ef-afbe-e010-f2c9-e0ba945faa1f\",{\"uuid\":\"cf1618da-24d8-aab8-eebc-128815c02d35\",\"isOpen\":true,\"children\":[\"e02c395d-e1bc-1375-a8ed-729e19544ce9\",\"a509c2d7-53ef-2b11-1331-f716b9c210d6\",{\"uuid\":\"fcaf8da0-0146-2587-b578-3e1af888deaa\",\"isOpen\":true,\"children\":[{\"uuid\":\"859e9460-0016-acb1-017b-25fe27244cac\",\"isOpen\":true,\"children\":[]},\"44592a16-3ff8-e5d6-5197-a3d5fdb91e94\",\"49542da5-539a-7743-3bbc-afdee9d770c3\",\"a79acc07-9567-e68d-0a57-109e40d9acbc\",\"8cadf035-ee63-7e4e-858c-fc3f8115fa7e\",\"a9027c5e-52c6-19e9-e232-a35125734ea3\",\"2cec3ae3-79ee-9f96-74f5-0e90611c5e6d\",\"e204bb47-1379-ea43-8d02-0b48c88a6282\",\"4c44df55-108a-c9c8-3c9c-3c3bdd7c3a6a\",\"a4083602-2242-1b62-262d-bfd38f8c77f4\",\"138546ac-7f07-33e3-b36d-0e06f98d386c\",\"34d77773-c97c-7edd-a72c-887e3f21afcf\",\"952d8c6c-c534-3ded-ebbb-8560169862f5\",\"677371f7-759a-8b3b-225b-89324ccd9e5c\",\"09b6fa84-9268-c1f0-d593-f49988b0a6a8\",\"2f533cab-6d66-5ebb-be57-520ac9cf1da9\",\"e5a7d019-ec5d-ef93-5ffd-c092c664d2cb\",\"11316f78-2485-c065-fa4d-7b1225df2bf6\",\"e0d85bd5-8a87-a483-22e2-833204176141\",\"101292a8-6c83-621a-11ff-e6b17d4a82e2\",\"a7c81b6d-2419-772e-27a1-0068365ebce7\",\"0ac8e87a-c521-a8ee-e14d-e205560a7626\",\"fc987521-07e4-10f9-b6fc-1512cb2da68b\",\"aa7d3639-52cb-087e-0f54-7e31afe0d4b3\",\"596ead89-97bf-b419-dc31-372fe3dee9b9\",\"4853fc6c-23a8-6a4b-07e2-d89b3aaa8c1c\",\"a04ef22a-5322-93ad-5281-e6b33566d6af\",\"dbcf2b15-20a0-6f43-48de-9c73557825c3\",\"656eb85b-2130-71d1-8400-036016caf911\",\"61014d9b-3a39-4bbb-24b1-c1796ff81448\",\"6e13574b-8944-aaee-8ad1-92edda71d4a3\",\"5e0081ae-2c0e-03b7-b533-f33b34b8b9c8\",\"926dfb9a-8a36-2d53-6026-df9e212d6187\",\"34f7ace6-abb3-82f1-ffe6-aa94ccf29240\",\"3616c0c5-2bd5-b180-4ed5-2708c59c41bc\",\"20fe1d98-3b1b-7a8f-7945-78be2423e61c\",\"bec338ce-2cb6-bade-c2a0-bf4b0256b6b2\",\"cc8271b8-7122-2c3d-7a10-390515d38ea2\",\"2d28b727-641e-2dc9-8e5f-15c9ed002244\",\"68112592-c5eb-c6ba-7370-8df3fd15c5be\",\"6bfcb5a6-b98e-2362-4350-0cff8e7c8417\",\"8ec387cf-bccc-0ef1-a478-2c3c0c430d41\",\"da16de9a-4659-1141-c033-4ab1a11199da\",\"b55c73af-0b1d-14ae-5198-2782a6d7294a\",\"a2bc1c86-9f7f-2d61-139f-584cbbdc9ca8\",\"976bb6d9-d09f-36bb-8bb6-62fe808734f1\",\"db87f23a-29dc-e8a1-0d0d-9a2f369185af\",\"0714f281-db15-8143-c32f-e89576734cad\",\"a4916b0b-ece2-a56b-c694-cb963a9a80b5\",\"9916eeef-31e1-9c75-ffda-14bd4d589032\",\"cb63eadb-42fb-9f2c-0cb2-f6b85a7607f9\",\"8ae9fcff-9cca-67c2-69e2-f50e85162de2\",\"792785c8-f110-dcf7-d0e4-61f223963cfd\",\"2ad8829a-0eaf-c32f-765a-76ad72a1b336\",\"2181542e-699e-c408-2ec8-b291a6325c33\",\"58cd5f7e-7a90-1ed7-b5c8-5d739d36bb28\",\"71ebf1c3-a5f4-6dbf-8533-0b7bdc6093f4\",\"3f2aa919-4264-20d2-4dc0-5b3963226082\",\"188d0aa7-7147-c70b-58ea-850b803f6a8f\",\"7de5534c-d30a-43f1-6eb9-25858b29fce0\",{\"uuid\":\"dcb3a2dc-0f46-4628-586d-44f4265cc61c\",\"isOpen\":true,\"children\":[\"e85c96f1-e8a9-dc1b-28ef-5270bd4fdeea\",\"e2626689-c30d-fc58-7143-b714a916e0e3\",\"b4e24ddd-0288-312f-4d29-4ee1bcad5926\",\"c7057fe5-eff1-3481-3701-b964a84f1c58\",\"662518b3-6709-eb4e-6278-8c012d80c705\",\"5bf81b09-08ec-d7a1-c186-9a432aac7ca1\",\"a9a094c2-0f3d-58d6-b21e-6ba6ba3b526b\",\"64526f07-ded7-1117-c95e-5e72febd29f7\",\"d6541026-10ee-fe6a-8fe9-72cf75d33923\",\"791744bb-3a31-3779-4bdf-f486a6933875\",\"d9dda869-2759-4199-c1d8-b42d195aa834\",\"abcc99c6-960c-1045-82bd-0a39d53f7a49\"]}]}]}]},{\"uuid\":\"b3135254-0351-3462-2479-e6a3286c89ff\",\"isOpen\":true,\"children\":[\"addb9f66-a2a4-46f7-b6af-f5a23c00fe70\",\"5e7dc31c-c64a-ff15-90d9-52a6f77cb14b\",{\"uuid\":\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\",\"isOpen\":true,\"children\":[\"1433ee62-21ac-d72c-0fd0-37000e5eb221\",\"24bacfd6-cde8-81a0-f620-998cf5393d48\",{\"uuid\":\"0e94ff40-15b6-0b24-d0a0-447f000d761b\",\"isOpen\":true,\"children\":[]}]}]}]}]}]},{\"uuid\":\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\",\"isOpen\":true,\"children\":[\"b17675fc-b79b-0ad9-8f81-aa2dd1fc8c97\",\"2e42e483-1557-d21e-aee0-94af7bedfd40\",{\"uuid\":\"c6d9e946-1d10-482d-14b1-0766027adba8\",\"isOpen\":true,\"children\":[\"9455d16e-4bbf-4b63-881d-7daf943e782b\",\"16aee685-0ead-a542-5b3c-a62467ca45e3\"]}]},{\"uuid\":\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\",\"isOpen\":true,\"children\":[\"0e370edc-7b05-dccf-dd2a-a92cfe9f3e22\",\"dc189735-0a58-619b-07ef-feec37d65e7d\",{\"uuid\":\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\",\"isOpen\":false,\"children\":[\"6bcf768b-beab-4c39-6d96-91266036a4e1\",\"274282a7-bc7b-02f8-4dce-84226e9dd9c1\"]}]}]},{\"uuid\":\"529b75e5-967f-8e76-9d18-0fc49998293a\",\"isOpen\":false,\"children\":[\"c8c75bdd-53bc-eb81-a2cb-0ea0dca06f35\"]}],\"textures\":[{\"name\":\"-steve_template.png\",\"path\":\"\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"0\",\"group\":\"\",\"width\":64,\"height\":64,\"uv_width\":64,\"uv_height\":64,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"44166cb6-b1bf-f227-4398-96c94f36d7f7\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAApBJREFUeF7tWztLA0EQvoCgEGsTiPhAwcbOJr12WmlhYWVlr1b+gFRqoZVVKkutLLUXwc5GUHxgIBFLBa1OJrmDzbi3O+u6SHJf2rndmftudl7fphBZfmc3T7HpkfreoXGHk/puwabDJA+t32ocGVCdHtXaeHn3EhEAlVJZK2+0mtFfALA0O661k2wj/Vk6Vta3Y5t+AGBzT3gAjgBiAIJgX6fB+eaeMc+XbzaMcfLo6sAoH1h9M8oXixWjPLT+AgFQLY3o83zrNSIDJspFrfyx+RERAMXJMa384+E5IgAGKzNa+VfjNiIALspb2nRMtpH+44VhrXzt/D0m/cM7O1r5e60Wk/6hqX2t/PN+MwYAEg/YnOt4wGmj8yGXE6/dv86JB+QeAMSAfg+CpjwUOg39exrkL8/7b97v29rL0P27r338fX/kR7X74/2+pL8P3T362gcAGAJaD1Cf6fsjwHsBHvR4rc9rex7EXIOma6/gal9WGZx+5K5S+JLV/rzW57V9WsunvQRfz8+bbT/+vG1/2360HgBkNELwgAQBHAG1G0QMQBDsngBJoiylQWSBZKSGNOg4Q5TODLM8TOKhqAMkdYBagbmWmj1fCmMewBCgfjvl4zn/LuHb1fU6cH35fF/7RPOA3AOAeYCCQC7mASk3x7k4zr1xro24NZXbc+XyJNydaX+JfaI6AAAk7Cw8gNHREhfDEVD4fcQAxwsNCIKCGxzIAoYjJolRSIOSdjj3dQDmAQoCvvx7r63XssO+7XD6/wLf+wW/Xe9iPwAIPRLzvV8Qen2bGwwZBH3nCaHXtwHwTYM+c3sTsySZ+/tWigAAHoAjgBiAIIgs4DkURRrMuCAhzeO+APp4MOqAvJfC38z1ql4UO+HHAAAAAElFTkSuQmCC\"},{\"name\":\"-knight_sword.png\",\"path\":\"\",\"folder\":\"\",\"namespace\":\"\",\"id\":\"1\",\"group\":\"\",\"width\":64,\"height\":64,\"uv_width\":64,\"uv_height\":64,\"particle\":false,\"use_as_default\":false,\"layers_enabled\":false,\"sync_to_project\":\"\",\"render_mode\":\"default\",\"render_sides\":\"auto\",\"pbr_channel\":\"color\",\"frame_time\":1,\"frame_order_type\":\"loop\",\"frame_order\":\"\",\"frame_interpolate\":false,\"visible\":true,\"internal\":true,\"saved\":false,\"uuid\":\"c2982d79-5e5d-fbcd-faa3-d19a766eaaa5\",\"source\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAEMRJREFUeF7tmmdYVVfWx//3ntsvcCnSLqCiGDWmzUSNZUzMq0mc2BtSbGhQYyxEBEIsY9Qx1hiFGCIqoIKABUssCVhiS2KLGonRqEhXeru9nMlZR+7I67wf5n0ez8MH9pd99in37P3b/73W2utc0cmTqaxExsBoMEMiZSCR8Mc7xROwPXApliTXY9aMqTCZGtBcRCIxdqTsw6yZk+Dr0w3DG9JxVFeKUmM5qioLUFNdgpOpBsf93EFIXA+IREqUa7bS+ZQNCVRPGJiFyu/NYP8qoXZiIovgSWPpePnyBFGLH3kODdG3x75hDXoTDV6pkMNqtcJqtWOvejIBWL6jCVHz56GqutDxerncGdu270bk9DB4e3fBiMZMApBfdhkVFQXQG0Q4vqMRIjGgUTKw2lgMneWHqqo6OPc+/wyA/IMG9BilpPNLl9mEBZBz6CtWKpXAYDBBpZLDZrXBbLYiRzONAKxMM+KD6ZOg19dArfaCTlcBTgG7Mw5j2tQwSKUqREjPEIAzN7JhMIpgswGHdzRADBHaOTGws8BfRyphtwOrVtux/siPNNiTX/8FfXurcW6PDqELe8DGMqj3TcOl7PXCKSAzayPbubMW5Y9qwDAMpBIGZosVe2ThBGBNhgXhYWNgNuvh5OSFpqYKKBQapO3MxJTJoZDLnTBZnMcvAf0DrNlwGrELXsGSj25CxAJaN17awxZ+ibK0WBisdnjHXqJzVzN6ontXBWrOm1BvYnFzcgGdD/WpxuNLGZg+Lf75L4EjR5NYra8nyVMkFsHD3YVg7JGFEYD1WXaEhY6F0dgAtdoTOl0lVKp22LkrHRPDg6FSuSOcPU4AbpcdhN1uhFyhRepKfjBX75px/MZdMBIpVozvBp2FhejJsDSvinFqr5HuO34skurwlWL0CDDhnXf7YMDfwp8/gJyDX7Ht23ujuroBDY06BHb0RXl5NfbIeQVs2i/C2LHDYTbrIJM5wWxugkrlgfSMLISGjIVS4YqJou94G1CS6bATe9ab6Pjy72YcunKNlsWysDfoHAdAJhHhaQDar8rpmjF9GQEQzAgeOJjIduzAAWhEbV0jOnfyQ0lpJbKVkwjA5hwGwePHkA2QSBSwWo0QiUQ4dPgUxo8bDrXKA+HgAeReyYa3jz9cXTXYuvgKDeJivhH7L16AWCxCRWE89LoabLbm0bVFzl0hlXfA6Q33IXMW05LRqMXwGLUWud//JIwX4BQQGKhFZWUd6uob8UKXABQWPkLWUwDGjxsNg6G2xRLI3rsP48eNgUrlijD7MQKw7/ReeHsCWt/2SFnxOw3y7K9GvBk8gY7f6pkLmxXY63Wb2v/wDIFYLMHPX/wMTfRPdI5J74fdg+6g89l4YQAcOryFANy7VwK1kxJ+2nYoLa1yxAGcAsaNHQWjsQ4ymfrJUlDjQM5RjBr5HlxcfBHOniAA2XnZ8HBn4e4G+Hc7AFeNNwZ0747BYaE0uL6v5NJS4Fzd9bwQx5rXlu9Ame80avc0pcIQxN8vSBzwfd521svTHQ8elELtpIKf1uMZAMOGvv3nfLFgGDlsNhOkUiVyDh7HmNHDSAGTRE+8gOEPrFl/Fp8sCADjuhXubn7o27UrhkwMowFxv5OQeoOO0xfbHQCifDNRrLOTm7xVbILsFf5+QQCcPJ3Kurs5o7i4EnKF9BkFfLEPCAnmvEAdLFYJpBIrucGs7ByETBgLJyd3hFiPkgLuVZ2DyVjKmTm4+O2Ep2dH9O3aA8OmhENy5yAN6q5bBNWK8GVUc0ZvvNNujIriB704Kg3dRy9E4cMyYQCcOp3Guro6obikAgq5DH5+nigtrXQsgTUZVoSGjCTjJxYzsNtt4CLB3en7MGVyGDQaL4wz5jzjBTT+6fD3fxFWi4WeWxr8Eg2wPOrfEWUzAK6OiJ5L180pfXFdLWAozCnASa1CScljKFUKcC6xtOTfAFak6BEREUJrn3N/en01L+H0I5gxYzqkUjkmiXIdNoAuAhg78Wc06Spgs4kJ2prpg+l8zcIiqq9GvoC4GDEYBuhYIMKmx7yh5OzBuq+mYlWaRRgF5J1MYZ2d1aira0RTkx7dunUgBaSJnmyGvmlAZGQoKYCbeZOpkTqakpqDOR/NgFyuQjjLu8GME1mQSPhQeEz4WRiM9TCbbeBC7S82JuC9/t9xpgQntvEbpReHKGFnWQSUA8XFNqikIvxQaIR3v4l0XRAbwMUBnQK1ePy4Fjq9AV1faI/83wpwyHU6xQGxCVWYN3c6BUDNAJRKN2z8MgnRC+aQG5sqOU0Adh7NgkIugs0OhE27iqrqIhQkjIHHlK1QKFzw40ne8p9Mb4RcIkZ9LK8Gzg5wZfa4i5BKJMi78KpwAPbnJLJBnf3x6FE17QQDArzJJe53nkoAFmx4hAXRM2gJcNbfYjGAA7BubSJi4+YRgAjpDwTAro2BWMTAYjXCbrPCaGrCz6uGIGhmMhQKV5z/npd58OBgqqcXLKeai/2/W/gXOn5v/S8YrNwk3BLg8gHePu4ke4vVggB/7xaB0EefFyE+fg4B4AbOBURSqRqrVydiyZIYSCRSTGF4BcAvDg2N5RQp3towFG8suYya2iKUlhbCZrNBZktCfV0x+ncf1WwqqM4zzEfFut6QM2IcC/uDgqAOHbXCbIbyTqWy3l5uKCurgsVihb+/Fx4+LHfkA2atfIj4+A9hs5mhVLrCYOADopUrN+Ozz+IgkcgwWXyKAFi85qNJV4na2sc4uW4aIjZfQpOuBjqdjqAU3YlGbW0ZLmToaOArv5xCdYUsDOsi36XjZgM4dFA53nhr3/PfDB06soXt2EFLS8BitVIcUFxc4QiFTe3mY/ZlDzx66RucaBcBk9GAtWtXt5jBIL8cR3vMxB8hkyooZ2CzWVBZVQSLxQyjsR5Xzs6EWAx8t6sRZiuLZZuDIJUHwslrCVaGDqG8we0yHe0Mz9w4gCFjHj5/AAdyEtkOHXxx/34xFEo5mrfGu5gQsgF6t9mYe82HAOSogiFmxFi7piWAQJ8ccmdWGzBiwnGIRAxUSjcKiDgAJSX3KNdwcNVsAtV/5TWqu9pnQsxocJtdi2Nz34BYBLw5RYpb90eh32tHhAGwJ/ML1t/fG0VFjyCVMfDx8UBJSSWOtptBAAweczHniicByJKOhMHQgOStKS0UEOibA0YMWK0s+vxPMtzdfKBWe2Dzh68DMiD4032QSORInDOMnnt7NQ+g6lx/sCzg+eYF5EX1gYwRIWRZP2zQpcJtfQAST9Q+fwWkpK1mfX08KCFiMlmh9WuHmqp6HPOeRQB0bh9i3jXfJwBGQK+vx7bktBYAJHey8FqAFCq5CDWNdtQY7OiikcIMFpeLzRgatxXu7l74YhYPQC7lx/XKMD4POCrsItavW4eexgNgenvg55tvQf9LBtYfqX7+AHanr2PlShnMRgvMJgs0rk5oajLgO9/ZBKDe+QMsuNmeByAZAb3hWQDyu5mw2VlUNtgdgzNZWNjsdihlYoxb/BVYlsW26Hmw2Fh4e/H8Rs+RI+DFw7RUtiWnwnIzg5YHlxPM3rUfvxeUPX8A6RkbWIbhwlU7GUG5TEYZm8PukQSgVh2BhbcCCcAeZih5g6+3JLdQwAeR4XBx9kTesgEIUDLQW1icKzbTPf3ayyCSiFCht+HgBT79VdzA1/Er3PFyn/1gGAmSt6bitdr9sPZkcMjvNoXKggAI/ZuGmxw8ruK3p1x5feR4quNio2Gx2BCT34kApOM9yu0lbE5sAWDGzIlwdvLEiom9YDDb4eUihkLGQCxi0WS0o7LRDpVMjMoqESngYR0fCs+L94A2aBWCgl5D0tdbweRn4u6HBRQHfJ2QisraxuevgGmDXQnAgyIbp0SK1XuN5gEs+vQT6HU6xN7uQgBSrW9DpXLBpi83tQAwNWIcbX2rqspgMNTg1qVZdJ3zLozYCXV1d6mdvFxPtclqx58pQkyMcsaA945i7qc7KQ/46RQpXRdqI8S9SzR1kIblZuf6HV6yXOk3jg9ZY2NiKDyOyeeXQKp1IBQKZyRs5r/qNJew8GHw03ZDRWURfUG6fpEHoNEwUDt5oqz0EbWPbtVDyohQqTfDYmXx8kApohedaQHg7+8ng0uQpg92eu6zTwAWDvdguQMud9dcVm3mfDjQo9dxiMQMon99YgSlw2l/v2VLUgsA7vey4Ktg0H7GRkp+3vklmq7LuY2RjXePBCCZV0CzDXh7hBrjJ6XA3d3H4VnOZaViwISpguwECcCEnu6sk5zB1cImMn7ccoj9rB119Nqdd/Bx1DzE/hb0xA0Og8lkxDdJ21oAUP6WCReZGNNXhNP55k2O76YO1G4OeblsD1cu3uFtQP8hSvg+FmPc+uPIzc3FuhUJSExoj7OXegkHIKSXO6uQiHGlsIncEWcEYv/BA7h5/11EzZ+LuCc2IFs2gkLhpKSWXqD2Ujrdz8XxTwNQ/NMfNhZI2s4nPCMjtlNdU8e9hUXA6wwCG6W47DwZ7w/wwNChI/DHzXnIPfeicAC+vVVI+owZ3NUxq+/0UoKRisAEjUJcbAyirgeQAvbKR8Nk0uHrLfwX3uZizc8AFIDZbyTmzZ2NK2fHUFi7K5ff9nJfgDt1ag+TvgA535pw94IFDUYb/rlRg8yNRsoIN+cIR5Z2R6NttiA7QVoCzQD0V/8GPx87lOpOyFjzGz+27iMRFxuHqOt+BGCfYjSamuqeCYXNNzJg5bxo95H4OGo+LuTyEV/WGd6YcgCCuvRExaMruHzNiK5dFPD3BapqAfkPdjBDA3HvjwLk3gglbxAQ4IPIyEXCGMHLP/Rmd2feQ/8+aiiVLDzavYJFH56DWs5gVsYN9NNaHJshTgE6XR0++f0FApJmGwSxWExxgjR9Mtp5uiImeiHiPlmMpMUdMVTtR9vkw+f3OtRy+aoevV5Xwde3PUpLi6D80YbirvxYT9/iAXBFiHQYKeDiqYHs8RO38HIPJVlsrmxdU0cS/njfLbzhY0LUda0jErRYjA6jmGIZCIlEgpj8zg4A0QsWID5+6f8J4OSZJgwa6AQ39wDU1hTTp/FBEV0gldiw49uBmBHaHRfOXxcOQE6GP3vxJwPNCucBuOLs4gq1SoFG/33o5anDgiducA/zPmw2qyM0Xv5ASyF0knEkAQiouASj1YZKbd8WALjA5unyUmf+G0FzUb3O/2mi8Pt+OPVrKKXIh73UQZglkJ0WwDbPCtcJpVIDmUwJTy+/Px2YBH5Byx2boXTR32G3Wylw4Urz153bleWQyVRYmsDHD5xBm3qsGxpgJ9uQ7zIW7kXZSPqBoX9/cGHum6Nm0kB7ejbhSqUTjL/0w+Kl/N9jhEqH0RLoFqilea+ua3R0imvXJvWBh7MY6YPu0KaIy9vteeThOObuaf6fT83FbxyzuWjDS0hR5KJDYkeY7Xb6i8yp60a82FGC3x5a8eWuYbhz+zy4WW/+Jwj3cO/ghS3agtmApUvnPhF+C1VSI79Y7jBK/6n97BP//Zmn38HNPPdJjCuCAdi+43O2+aX/aSbuv/k5zfr/XsdPn3/3rQdoqs+HwWCH9NVzNICFw/s6aAixrf3v0fNPCGJo/r+dE+K5NgBCUG7N72hTQGueHSH61qYAISi35ne0KaA1z44QfWtTgBCUW/M72hTQmmdHiL61KUAIyq35HW0KaM2zI0Tf2hQgBOXW/I42BbTm2RGib20KEIJya35HmwJa8+wI0bd/AQszlaq8R8kcAAAAAElFTkSuQmCC\"}],\"animations\":[{\"uuid\":\"91f45b91-0388-39e0-52fd-e98e668d0d06\",\"name\":\"roll\",\"loop\":\"once\",\"override\":false,\"length\":0.66667,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10.3452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"4e015e1c-020c-c0cc-cf03-d0f365b91b4f\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"96a1cbef-dc1c-8cb3-893d-1844d13a8898\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-55.3452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"cffaa113-7472-9a2d-879c-3b37b7918233\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.8452700461\",\"y\":\"14.7668896086\",\"z\":\"-2.6639876167\"}],\"uuid\":\"1859ff86-2a12-36c8-57b7-cf230e86e3e3\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-173.4065947693\",\"y\":\"12.6083678689\",\"z\":\"-8.1925230243\"}],\"uuid\":\"6c944778-0f30-a404-20be-7d985529dcc3\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-254.4306397576\",\"y\":\"-5.2125464926\",\"z\":\"-2.586642225\"}],\"uuid\":\"6fea6dfb-ac36-8bb7-7b6c-f1b31c3fb09c\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-299.316821318\",\"y\":\"-7.7099874859\",\"z\":\"-2.5994534331\"}],\"uuid\":\"19e7bcfa-34ef-ca1f-9316-34616fa523cb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-334.7684189709\",\"y\":\"2.2798591799\",\"z\":\"-2.5779800118\"}],\"uuid\":\"143b95c6-51d7-3d73-dab9-4afc76a18bee\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-359.7452658674\",\"y\":\"0.977484586\",\"z\":\"-0.7993651888\"}],\"uuid\":\"ca8b487a-5316-821c-6a0d-2b17dc09e2ce\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"0\"}],\"uuid\":\"739f6fd0-eeff-515d-bc2c-05459d38fb35\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"610dffa2-b064-c32e-49b0-95e662c9fbe0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f8040d86-2633-7d51-02f2-d276ac128c20\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-15\",\"z\":\"-5\"}],\"uuid\":\"2613c6a2-076c-8a2d-7022-314ada456d6a\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-16\",\"z\":\"3\"}],\"uuid\":\"65475ddb-0901-3649-c168-8f332b92d1c4\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-4\",\"z\":\"5\"}],\"uuid\":\"d26510d5-a53d-5570-114b-1a49178fc6f7\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-1\",\"z\":\"4\"}],\"uuid\":\"03b08547-82b6-c8ce-eb7e-8147925b4d3c\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3d14037c-74a9-abc2-b13d-3600ae6c5dd0\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"position\",\"data_points\":[{\"x\":\"0\",\"y\":\"-3\",\"z\":\"0.31\"}],\"uuid\":\"7a5c383e-b443-3773-3fc7-b41a95b66aa3\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"f218f334-8ebe-66d8-1769-41de9f9f7ad0\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"75314a09-9386-3e4d-3f96-a8e59911fe5e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e579328d-a99d-9a7f-0a05-746e42159080\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5169123047\",\"y\":\"4.8873117739\",\"z\":\"2.6276738824\"}],\"uuid\":\"57494920-9783-7099-8971-f8828309b07f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.2554184495\",\"y\":\"1.9324031631\",\"z\":\"0.6242872643\"}],\"uuid\":\"14faa28e-7b7c-4fca-04bd-fb15427d1d14\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.321532076\",\"y\":\"-0.1985844587\",\"z\":\"-0.7364643904\"}],\"uuid\":\"2960010b-2391-100b-9059-499f90d98595\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.7446837252\",\"y\":\"-2.2894808314\",\"z\":\"1.027565895\"}],\"uuid\":\"6436ac0d-b7c1-eb4c-741b-c6fec8760846\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6cb82785-ecee-7220-9b21-7f748ca4ff73\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"554f1255-7efc-bebf-d907-e6b38e212768\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"918337ed-db4f-55f0-7a21-d10d25500ae6\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"66b19558-a367-598b-50c7-142c4211a07d\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-15\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"af84a634-f0ee-f31b-42da-a9789b3af450\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9b812e3b-ff6a-5bcf-9b14-d88149ef2a29\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.9335647827\",\"y\":\"0.1910653446\",\"z\":\"0.5860712492\"}],\"uuid\":\"d26f71b7-3eb6-c334-8e41-f3048cc39bfd\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40.8337650092\",\"y\":\"1.5719388815\",\"z\":\"0.836263831\"}],\"uuid\":\"908fd1af-b92a-7c86-ec15-33aa208e85d9\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-36.5153158634\",\"y\":\"1.5694142029\",\"z\":\"1.1566234175\"}],\"uuid\":\"06e3b992-d5bc-6bd5-da5e-ec28155c8af4\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"af2c3045-852e-36da-7224-00ca89c0c360\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4e57e7e0-bff8-8f86-f5a9-14379ed8f0cc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7e8f0cb8-aacf-3a06-0990-319fcca439ca\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"951b375f-8b17-0bce-c1e5-b020c20b1378\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"96e0a9d7-f9a1-ca27-c4c1-49b9b9ae9739\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d691f682-5653-24b5-64cd-f593d802ae7a\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"64d27d7c-16f1-4e24-11de-1df5b650e5b7\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-44.7041455577\",\"y\":\"-0.5463397482\",\"z\":\"2.4016359177\"}],\"uuid\":\"cf7881c9-f9af-c342-d155-dda6ca40df0f\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-42.1987087714\",\"y\":\"0.9199484607\",\"z\":\"2.3855070851\"}],\"uuid\":\"1e94d2b6-12f7-f0b3-5513-e088c678cddc\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"4.7824447755\",\"y\":\"-15.0272212866\",\"z\":\"0.6748976469\"}],\"uuid\":\"d578b08c-e775-fb98-c9f8-42f4e180dc8f\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3a4776d8-2065-0aff-5901-b4d3d6ecf2f6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.9790874962\",\"y\":\"-5.8032396565\",\"z\":\"15.815264958\"}],\"uuid\":\"f9ce9b56-16a1-0ade-1b32-395dc6a6508f\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.1815443852\",\"y\":\"7.1155834398\",\"z\":\"18.1304160091\"}],\"uuid\":\"14d812e3-97c4-fb55-ae8c-7ec92768c5bd\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.9790874962\",\"y\":\"-5.8032396565\",\"z\":\"15.815264958\"}],\"uuid\":\"5afbcdab-4795-1ca8-59a9-285e3dfac932\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.9171104584\",\"y\":\"-4.5000466504\",\"z\":\"5.8600103802\"}],\"uuid\":\"6499750a-a4fb-5c57-4995-c4d47b630672\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"8.2765048988\",\"y\":\"-2.9212329552\",\"z\":\"-1.4866588636\"}],\"uuid\":\"7f018859-2372-9fd4-17e0-756d8fffc147\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"aba6447d-d798-c3c4-8579-72b987b8b41c\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.7013003421\",\"y\":\"1.5426491326\",\"z\":\"16.086881186\"}],\"uuid\":\"3896e9cb-f48c-75cc-888d-769cf83332f2\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"55\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8e1409c4-941b-084c-02e5-df03cc1a4690\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"92ea27f8-0e0a-b629-1027-b86cf738d7ec\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"79.7622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"fc368334-2af0-e8a5-5d0a-5cbdb752e343\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"117.2622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"7f4efd9b-c5ba-5977-b5fa-b1b9799bbac1\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"94.7622809499\",\"y\":\"12.3070971455\",\"z\":\"-2.2046197553\"}],\"uuid\":\"7db6c762-536f-e3b9-482d-8337cffe5746\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"95.0395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"bbc95573-feb7-ffe2-345c-af69f5f8f939\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"72.5395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"ab7c0856-f341-34ea-f57c-6904b0e30d92\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.5395494766\",\"y\":\"-22.5728078416\",\"z\":\"-5.1606746124\"}],\"uuid\":\"9e4fcb79-0aa2-9377-3fc6-ab698d600e8a\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"63743b29-3a44-0bc8-f5a5-8903bb15d390\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25.6848204073\",\"y\":\"10.1778091184\",\"z\":\"-20.173933666\"}],\"uuid\":\"7a3b3bdf-dd57-badd-a6dd-7580d30e23e0\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4323899f-7d3f-557a-beda-d05603cd2f96\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"22.4403124196\",\"y\":\"16.3255534092\",\"z\":\"-34.2402741209\"}],\"uuid\":\"744316fc-f3d8-67fd-9a68-2b2c568b639e\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"7.2988707081\",\"y\":\"3.4553272208\",\"z\":\"-6.6606725408\"}],\"uuid\":\"6cbe013b-e0b9-702c-e627-f2c374f53718\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"fd38995a-2f81-eb5e-6c06-404eceec04f6\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5f5fe94f-19c5-c019-7a47-ac924d74285c\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"339cdfba-9029-f75f-9d19-ac8567b07424\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"87d530dc-a3ca-5bc5-53fd-d0b58e67c115\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"95\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4056d3f0-9227-879d-0ccd-a715a6acefd8\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"60\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"5cb622b9-5c77-b222-ba79-a62ddbf1e801\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.9979365876\",\"y\":\"19.3545961515\",\"z\":\"-11.7009195082\"}],\"uuid\":\"d08bec63-419b-45f1-d3ac-4bf88c534563\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.9979365876\",\"y\":\"19.3545961515\",\"z\":\"-11.7009195082\"}],\"uuid\":\"aeaa276f-1181-1922-960d-7fb6845fd9f3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"13.2306976448\",\"y\":\"4.1075261935\",\"z\":\"1.5640491387\"}],\"uuid\":\"2416d5e6-91bb-6991-a145-7778a9ab717b\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ce1a67cc-be9b-632e-7d0f-1ea657ce8f06\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"3e549bb1-58c3-92b5-d13c-9c3c0772ea12\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c6b905c0-5196-e52b-59c7-8d3927a73397\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"82.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e4592899-dc47-7f8c-acd9-92811b5fdd77\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"82.1404769782\",\"y\":\"-17.3455144203\",\"z\":\"2.3566635967\"}],\"uuid\":\"07d4219f-d8d7-811e-fba2-06fb667c4f62\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"31.427095159\",\"y\":\"-13.5306627252\",\"z\":\"-4.344153106\"}],\"uuid\":\"7a496aba-7df1-b5b5-4dbe-7e6420e13fef\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"11.427095159\",\"y\":\"-13.5306627252\",\"z\":\"-4.344153106\"}],\"uuid\":\"446c86e4-1c3b-eff5-a180-a62769af7bb8\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"95fe2e07-173b-a066-a66d-0be58ae890a5\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"48.3424105805\",\"y\":\"1.1690538795\",\"z\":\"-1.2575696869\"}],\"uuid\":\"06246a98-556c-f22c-45a0-44083c73dbe9\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.8934503775\",\"y\":\"-3.8213109421\",\"z\":\"17.9129813754\"}],\"uuid\":\"aead35ef-607e-93dc-f018-85e424e6a137\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"447233aa-66b2-dbd7-e45d-483f46dadbae\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bd388449-ced9-61f6-e408-4f913fc0355d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"57.4009318272\",\"y\":\"-4.2154088774\",\"z\":\"2.6913572849\"}],\"uuid\":\"7b84f346-ab75-70f4-a8f4-67879637f9e5\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.4009318272\",\"y\":\"-4.2154088774\",\"z\":\"2.6913572849\"}],\"uuid\":\"724ba19e-1a1c-00e9-111a-d67ed5aa5c83\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"48.3698475508\",\"y\":\"-13.3767559929\",\"z\":\"0.8884687898\"}],\"uuid\":\"28e08960-9fa6-1789-1edf-5b1e443db7bd\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.7930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"3629a4e5-a2bf-6722-7a63-824e74693c95\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-23.2930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"fc5d0a27-b180-4a2f-5fb1-c25e9d8d0cbc\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.2930143118\",\"y\":\"-7.6305598405\",\"z\":\"9.4055935229\"}],\"uuid\":\"0471b600-cd96-fb88-b394-25b68b236191\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b3528148-886c-4981-aad9-a449912e4ff3\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c6d9e946-1d10-482d-14b1-0766027adba8\":{\"name\":\"prfl_right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4d65f4ff-4666-1d6e-e2a2-5754675f39d3\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"713091ea-3c5f-2107-8abf-ee97bc6176ee\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-35\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"020d854c-8f1d-eea6-0d44-f050772acad3\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-12.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"747e19b0-5b0d-1bde-37c5-d61eae5ee649\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1a3a7fe0-9ac6-642d-1655-a02cbedc50c0\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-63.5104629601\",\"y\":\"-0.0562419487\",\"z\":\"-3.8034923955\"}],\"uuid\":\"cf494402-d95a-c9a9-0666-1105ac4ff1ba\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30.3667568215\",\"y\":\"-5.6104628256\",\"z\":\"0.822128921\"}],\"uuid\":\"338edd44-0be5-f9d0-495a-16b3c7a47468\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5627211181\",\"y\":\"5.0414022802\",\"z\":\"3.6575498102\"}],\"uuid\":\"601767a5-ac9a-bc96-4b0b-237160c36700\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50.8538632696\",\"y\":\"5.4159878628\",\"z\":\"2.605219904\"}],\"uuid\":\"b124849a-6e1c-463f-9163-98272fab4254\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-17.1096711992\",\"y\":\"-3.7317133585\",\"z\":\"-11.938445897\"}],\"uuid\":\"486ede20-d032-4d46-d9cd-95cf257c04d9\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1e3e41fd-1374-7b20-aada-7d42393cb399\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-93.8329224881\",\"y\":\"-12.0025641778\",\"z\":\"-18.1816740733\"}],\"uuid\":\"13a334d3-48cd-1136-e910-d86f10ecd6ff\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"25.8053212577\",\"y\":\"7.0178718272\",\"z\":\"-29.2161110452\"}],\"uuid\":\"7e87c3b5-9d38-b35b-df3b-aa05fc47517d\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-39.0037108296\",\"y\":\"-5.9031625207\",\"z\":\"-19.1431448415\"}],\"uuid\":\"6601f44d-7b3e-1933-edfb-aaf37ed8f707\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-14.2395920994\",\"y\":\"3.5940186635\",\"z\":\"-7.5169369411\"}],\"uuid\":\"284cc08b-c152-0ab3-3689-6d441335ef56\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8085208f-1d05-3f74-9820-0173fe43fd6b\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.9519624501\",\"y\":\"-7.2690960426\",\"z\":\"-16.7194764292\"}],\"uuid\":\"487c4a04-c29a-1aa1-e26a-aa6dbafe7e0a\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"18.247962068\",\"y\":\"7.8313214321\",\"z\":\"-24.4844657083\"}],\"uuid\":\"73a7fea9-6029-f2f6-ba64-9449179096e1\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\":{\"name\":\"plfl_left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-30\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a0002446-b908-df56-d45b-79e09a54036d\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"71dde296-8a96-6c52-d883-110fe28f6083\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-18.550117808\",\"y\":\"-0.4065298268\",\"z\":\"4.9663131482\"}],\"uuid\":\"a9e10a80-2787-3737-8868-9c28c2cbafdb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-37.5298058614\",\"y\":\"-2.9158033357\",\"z\":\"6.6597637739\"}],\"uuid\":\"3ec121c2-1295-28ed-2ca7-22374f35c684\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"c43f7153-1df2-38ec-1edd-b3694bed6dc8\",\"time\":0.66667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-40.1236819605\",\"y\":\"-0.9306502051\",\"z\":\"1.542865843\"}],\"uuid\":\"fefea430-0ffa-21c0-738a-21ab44462a5e\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-33.1776348771\",\"y\":\"-2.2590643664\",\"z\":\"4.7558127555\"}],\"uuid\":\"3245ce43-4453-1563-6426-957785fa545f\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-53.9268912309\",\"y\":\"-7.3104700825\",\"z\":\"8.1053141589\"}],\"uuid\":\"8d32a863-19e6-66eb-dd63-bc4a4490440e\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.6010831098\",\"y\":\"0.530086938\",\"z\":\"2.60850085\"}],\"uuid\":\"1a5cc3fb-2736-9e73-929b-2b1f796b4824\",\"time\":0.58333,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"6f35223a-32fc-86de-273e-bf2a2dab29d9\",\"name\":\"left_attack_1\",\"loop\":\"once\",\"override\":false,\"length\":0.75,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1.4426116839\",\"y\":\"-29.9685 + 60 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"-2.8861405929\"}],\"uuid\":\"9cea2183-ab9a-e257-3abe-870ecefb3983\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"4.3169042909\",\"y\":\"30  \",\"z\":\"2.8752087286\"}],\"uuid\":\"dbc63c99-e467-c1e1-657c-cb6af7445267\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.6830957091\",\"y\":\"30\",\"z\":\"2.8752087286\"}],\"uuid\":\"fd08cba1-1f23-bfc2-9441-476ca36f74cb\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"06b875b6-cb7b-6d84-3602-76e8d8392a4d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"f0fba0f9-402e-360e-0a94-d64aeb5c5c6d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366\",\"z\":\"0.3377321701\"}],\"uuid\":\"b50394e2-3528-65dd-22fb-512dbfc2f810\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"b375bdf1-a5b9-5f1e-8257-47941e069d20\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.5\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"6671e902-6e55-2c82-dded-c5df61e041e5\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366\",\"z\":\"0.3377321701\"}],\"uuid\":\"df42619a-31b1-392a-4568-0f18b8533893\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"fc915813-af51-faa4-ecdc-03c64965677b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"22.5\",\"z\":\"0\"}],\"uuid\":\"e225cf07-6e84-70ca-6695-b29a2846ed8b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"15\",\"z\":\"0\"}],\"uuid\":\"c25352f3-66c2-2b02-bd35-d880f2b176f3\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"-75 * math.sin(query.anim_time / 0.5 * degrees / 2)\",\"z\":\"0\"}],\"uuid\":\"b43318a9-dcd4-607f-65e1-8b00cc43b3a0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10\",\"y\":\"-75\",\"z\":\"0\"}],\"uuid\":\"93681b83-7b39-874b-e811-a67b95855b3f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4079498817\",\"y\":\"-69.6105803251\",\"z\":\"7.2928402867\"}],\"uuid\":\"9a3a0d44-c650-ac83-4813-2a10370f9eb4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"226.442612794\",\"y\":\"-29.9685204144\",\"z\":\"-92.8861411476\"}],\"uuid\":\"e93ee5dc-184d-1bc3-117e-457e5f8d35b0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"118.942612794\",\"y\":\"-29.9685204144\",\"z\":\"-92.8861411476\"}],\"uuid\":\"abee3b46-8686-08a6-5fc2-88f23a08686b\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"91.4812744911\",\"y\":\"-32.4677070432\",\"z\":\"-92.9607170541\"}],\"uuid\":\"f5310bc1-f902-b35d-17f6-5c492a08e390\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"104.2082921546\",\"y\":\"-34.614390135\",\"z\":\"-84.3352155418\"}],\"uuid\":\"546f17cc-3f95-55f6-ce39-34d45c4bc65f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"120.1958079089\",\"y\":\"-58.4299921475\",\"z\":\"-95.76303279\"}],\"uuid\":\"50a71a0f-23a3-58bf-a9ca-dfd506c8fc73\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cfdee1e2-175a-bfa2-c031-d225ff9b5d21\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-50\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"74aba8f3-7600-c98b-c42b-8ae7248974c0\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-48.85\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"982fbd70-9b5f-e211-2106-7bb13df19813\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-77.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"093ab868-a3e5-1727-de2d-9699650ad3b7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"fcaf8da0-0146-2587-b578-3e1af888deaa\":{\"name\":\"pri_right_item\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"47ab1042-3dbe-9fbe-5b2f-2d4e55759eb9\",\"time\":0.08333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-32.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"68868a70-d07d-55bc-ea5e-d62b6d58ee36\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-70\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"26f156a2-f872-2e31-e28b-5cd012a57108\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-72.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"81cb1fac-09fb-254d-c7ef-605c48f3687c\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-28.75\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7128c27e-62a5-eab2-1edc-b9fdd5a2d1db\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-87.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"09cbf278-b5ef-45bf-9f24-057b421dce72\",\"time\":0.41667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-102.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1cae9cba-9706-843e-87c5-c87e76ba05a8\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-102.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ea0dd9ba-ef9e-ae70-1647-8ded38c17181\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"630a55a7-df7d-e844-f270-7bc23e9091e0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a7909b5c-2b7b-30d8-daa2-ce4e1eb0ae89\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"a2809a1d-00c4-c013-9496-dafe98018493\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6360ce54-fddd-4efd-f7e0-bf14889ca2f5\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"5bafd1e1-dd4f-e647-0ae9-7a670f0d888d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"2b40d9f0-62fd-aaa3-6543-793a1dbb7a30\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"df09c19b-92e5-fda1-3fc0-88b5f94777c3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7ec93d93-fd31-0af1-95b5-7b2a760ebf0e\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"74458596-1e8e-e629-df60-95abd2e494a9\",\"name\":\"left_attack_2\",\"loop\":\"once\",\"override\":false,\"length\":0.75,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-0.6830957091\",\"y\":\"30 - 45 * math.sin(query.anim_time * degrees)\",\"z\":\"2.8752087286\"}],\"uuid\":\"10958ace-f54f-8523-893a-df0d2fa7ee2b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"1.992544964\",\"y\":\"-14.8854093306\",\"z\":\"-7.4750460859\"}],\"uuid\":\"6cd93535-eeb9-7d73-b0ac-d4d382bbbb01\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"-29.9409605825\",\"z\":\"-5.1865092834\"}],\"uuid\":\"33442214-ce7c-a7f0-da2c-c32579010ed2\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"-29.9409605825\",\"z\":\"-5.1865092834\"}],\"uuid\":\"893ec734-086c-2dc3-e43c-1881431cc207\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366 - 45 * math.sin(query.anim_time / 0.75 * degrees * 0.5)\",\"z\":\"0.3377321701\"}],\"uuid\":\"feec51b1-b546-e183-06af-f2fd4e999baf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-30\",\"z\":\"0.3377321701\"}],\"uuid\":\"bbb6334b-d352-6cc7-7708-a92e8267d1a9\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-15\",\"z\":\"0.3377321701\"}],\"uuid\":\"977401eb-1cd9-d20f-ba13-fcc04bb0bbe4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1118499707\",\"y\":\"-19.9999907433\",\"z\":\"0.3276399241\"}],\"uuid\":\"7c4918c9-c0da-f306-f08f-5065e20e6365\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15.0068082366 - 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.3377321701\"}],\"uuid\":\"039aa55f-3a2b-951f-687c-39c2028a075b\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-30\",\"z\":\"0.3377321701\"}],\"uuid\":\"743c1219-e220-c341-e567-62633a006f8e\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"-15.0000239764\",\"z\":\"0.3668883205\"}],\"uuid\":\"d78bfc1e-149a-c305-1cab-d11998cd8fa4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1002819443\",\"y\":\"-20.0000165354\",\"z\":\"0.3578398578\"}],\"uuid\":\"c993c4bb-f2dd-0d3e-2a90-ccdd9a6e28bf\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"15 - 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0\"}],\"uuid\":\"bc207a6b-8c2e-fae1-615a-58640bfd30a4\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"-30\",\"z\":\"0\"}],\"uuid\":\"99c4936a-ccb2-0076-d4a6-3f602366dd4b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"-15.0126547749\",\"z\":\"0.6697153441\"}],\"uuid\":\"dc834c65-8788-5a0c-5e8e-b17cbc524ed7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.3040300919\",\"y\":\"-20.0087274139\",\"z\":\"0.4618653633\"}],\"uuid\":\"2b68f55d-7ce7-d9ac-6140-1e11f21bea83\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4079498817\",\"y\":\"110\",\"z\":\"7.2928402867\"}],\"uuid\":\"63e11b48-1acd-31fb-9aec-038ef11318d1\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.2466262711\",\"y\":\"74.742560924\",\"z\":\"4.9451721289\"}],\"uuid\":\"272befd3-7dac-c524-f191-be00f2b480f7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-10.6822205364\",\"y\":\"79.6837812733\",\"z\":\"1.4255362599\"}],\"uuid\":\"665dcf82-4d8a-60f1-a147-3ea0fd090a3b\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.4079498817\",\"y\":\"-70 + 180 * math.sin(query.anim_time * degrees)\",\"z\":\"7.2928402867\"}],\"uuid\":\"fc76d21e-d81d-fe56-8713-8c4511a33f4c\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"88.1929174825 + 135 * math.sin(query.anim_time * degrees)\",\"y\":\"-6.5328079718\",\"z\":\"-99.7693965866\"}],\"uuid\":\"f15b8a26-69eb-3236-f121-089f6c12adac\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"225\",\"y\":\"-6.5328079718\",\"z\":\"-99.7693965866\"}],\"uuid\":\"c4d1ddb3-58fe-1339-9935-9c017764aa14\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"181.7685110117\",\"y\":\"-29.2024066267\",\"z\":\"-94.1691034871\"}],\"uuid\":\"52ecc208-4ae8-93fa-fb45-2116be5c1bd7\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"200.7469404305\",\"y\":\"-14.5514885328\",\"z\":\"-96.9206545934\"}],\"uuid\":\"291f2fd2-9348-7e45-94ec-a65ccfe23fb2\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-87.5 + 90 * math.sin(query.anim_time * degrees)\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cbc64268-9d72-314a-bb49-cd0bb865f92d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"abe0830d-48f3-34ee-0d56-79a82a40d0fd\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"8475acbb-ec58-046f-cf73-1d5f1bd2f682\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"47670f80-2a64-0e92-40cf-cf7dba16da3a\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"fcaf8da0-0146-2587-b578-3e1af888deaa\":{\"name\":\"pri_right_item\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-122.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"7be4764d-8e4f-962f-b52e-22c0e27e24a6\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-122.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"4cbdb4a2-378e-76db-2887-6c510059585f\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-147.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"23006a51-cd73-e3ba-b213-c537823211c8\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-107.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"d388d056-819e-3f5c-244d-1a7c2779def9\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ad988529-fbb5-7dee-e2be-6470903df197\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"cd2ae947-1b0d-2cb4-55b3-e73bfe412ae4\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"65109525-d50a-8c57-a1a1-c1f845f31b89\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"81c482f3-e0f4-8689-5723-b978e50693d7\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"852792fa-dba4-c696-8903-76d0cf6cd30a\",\"time\":0.16667,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-27.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e03c676a-178b-f03e-b40d-4404ee7b6eaa\",\"time\":0.33333,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"21117162-b28a-dbe7-5b2d-ba2da62704d0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"47.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e9534f91-cc2b-e3cf-3dda-d16bcac0381b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"59988b0f-f288-1498-fd3f-e5e76fb31afd\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"2b7c6f42-5bc9-0d92-f071-1774547278c1\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"a4e124d9-47e1-c56a-d58f-03a3b368a9a0\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"73269add-beb1-4df4-dd72-7882b3fc38fb\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"3b636b10-cdb7-a40f-b218-8eb1b3aa0008\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"f3b94148-4f92-306c-6646-68077a96e7fc\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"43625c14-44b7-769e-d20c-b7ae66efc0c9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ad20692b-165d-cf72-0674-86e893570e2e\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9001b4a2-3e10-4782-15de-b440b94a0265\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b40d0e65-f781-9910-9f5d-6872fb4aab46\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\":{\"name\":\"plfl_left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"eae385ac-0193-0adf-dd33-7896c8c38519\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"1906f341-ffb9-6bcf-a293-56f56d5a7a7d\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"46b1e55d-2185-fe99-fc55-d1de75cdd0cd\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"929e78b1-3d73-f810-5d38-67582cb8f00e\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}},{\"uuid\":\"3868a416-df35-aa7c-72ca-aa2f7000eb3e\",\"name\":\"left_attack_3\",\"loop\":\"once\",\"override\":false,\"length\":0.75,\"snapping\":24,\"selected\":false,\"group_name\":\"\",\"anim_time_update\":\"\",\"blend_weight\":\"\",\"start_delay\":\"\",\"loop_delay\":\"\",\"animators\":{\"778fa89c-759a-8884-89d9-238c555d2dc1\":{\"name\":\"player_root\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"-30 + 30 * math.sin(query.anim_time * degrees)\",\"z\":\"-5.1865092834\"}],\"uuid\":\"d0abd38c-d0de-ed94-01d0-fe17313caa21\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"0\",\"z\":\"-5.1865092834\"}],\"uuid\":\"5d322262-38d5-191f-9f2e-ba246377ea6b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.783096475\",\"y\":\"0\",\"z\":\"-2.6865092834\"}],\"uuid\":\"f42022d3-fbfb-e7b9-fb75-d9607fdff0ec\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"9dc65952-10a9-876f-bd47-d6a7e9ec6183\":{\"name\":\"phip_hip\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"-15 + 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.3377321701\"}],\"uuid\":\"a026bb6e-9f3c-f381-1672-4fdb60d5d173\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"30\",\"z\":\"0.3377321701\"}],\"uuid\":\"f49eded7-50f0-328f-f9bc-832f8306614c\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.1088122834\",\"y\":\"15\",\"z\":\"0.3377321701\"}],\"uuid\":\"132e27b6-de90-f3e8-0f91-a1a0326dd4bb\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"e297aef6-7dfd-f100-2e7c-ab113699b922\":{\"name\":\"pw_waist\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"-15 + 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.3668883205\"}],\"uuid\":\"83b142f3-3039-78b5-5027-82fc96140962\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"30\",\"z\":\"0.3668883205\"}],\"uuid\":\"9b1408ae-bacb-3696-9353-f583fe01691d\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0.0975584238\",\"y\":\"15\",\"z\":\"0.3668883205\"}],\"uuid\":\"72b4cbd4-6cd8-ecf3-49e1-ba9bdca95ed5\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"a0c01522-9040-7533-fa11-f6a45d3d96ac\":{\"name\":\"pc_chest\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"-15 + 45 * math.sin(query.anim_time * degrees)\",\"z\":\"0.6697153441\"}],\"uuid\":\"080664d7-7ba7-0e3b-eadf-4aeca7d6ab28\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"30\",\"z\":\"0.6697153441\"}],\"uuid\":\"32c655bc-7cb1-4989-8d04-ba6601a20ffa\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"2.2414318612\",\"y\":\"15\",\"z\":\"0.6697153441\"}],\"uuid\":\"2394fbbe-c4be-f4a9-7147-6f775caa2b57\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"34097e46-c233-c03c-d8b9-aee154c9946f\":{\"name\":\"h_ph_head\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.2466262711\",\"y\":\"74.742560924\",\"z\":\"4.9451721289\"}],\"uuid\":\"0ca4d269-75b1-053b-2e29-97ed95ee8a27\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.25\",\"y\":\"-90.26\",\"z\":\"4.95\"}],\"uuid\":\"9d554f75-1c6d-a52e-231c-5b3fccb718f0\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.25\",\"y\":\"-45.26\",\"z\":\"4.95\"}],\"uuid\":\"1dd76bcb-6748-32e6-f1c0-af82dbaaf6ef\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-25.2303398612\",\"y\":\"-66.3113221393\",\"z\":\"25.0049672424\"}],\"uuid\":\"f6e86818-45b8-d124-5048-235d4438e1f2\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"bfc2f156-b48b-dd08-1b9e-777d8ada16b2\":{\"name\":\"pra_right_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-67.7996254024\",\"y\":\"-18.7498221312\",\"z\":\"46.5234420106\"}],\"uuid\":\"0475513c-2096-eade-6e28-e9543b1326dc\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-64.1068768939\",\"y\":\"-7.3820305827\",\"z\":\"59.216259349\"}],\"uuid\":\"c9030e72-7c34-0050-fac4-653b7eaf67a5\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-39.6615555673\",\"y\":\"10.04018335\",\"z\":\"57.8138546454\"}],\"uuid\":\"8ec4a464-cce1-1660-9e7e-aed5dc55ef21\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.1615555673\",\"y\":\"10.04018335\",\"z\":\"65.3138546454\"}],\"uuid\":\"631fb268-9cc4-cad3-c10f-1b50204f5032\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-7.1615555673\",\"y\":\"10.04018335\",\"z\":\"87.8138546454\"}],\"uuid\":\"7bd8dd80-fa0e-a6db-44f9-3641c7a860a6\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20.928317261\",\"y\":\"43.8968318011\",\"z\":\"33.8368278453\"}],\"uuid\":\"9ae0c3e6-af01-5eff-89d3-e344ae80d232\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"cf1618da-24d8-aab8-eebc-128815c02d35\":{\"name\":\"prfa_right_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"17.7412720053\",\"y\":\"-29.1799804704\",\"z\":\"-21.7474034651\"}],\"uuid\":\"6a40116a-b813-1b1d-0348-7c2e57651f0e\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-2.3747088224\",\"y\":\"-26.0561020798\",\"z\":\"-53.9720884106\"}],\"uuid\":\"60015b32-254c-b385-5730-f1fb2dca341d\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"-42.5\"}],\"uuid\":\"4ad84a41-5d07-01a0-8466-4e63692e80ab\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"25\"}],\"uuid\":\"152728f0-c892-e72c-83ce-c307706a04aa\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5\",\"y\":\"0\",\"z\":\"42.5\"}],\"uuid\":\"6a9ba9d9-8596-88fd-cbd3-56574d16d159\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"5.379429148\",\"y\":\"-0.4004441987\",\"z\":\"30.0108045086\"}],\"uuid\":\"2443bfb5-32c5-7ceb-7748-f884f86bff3f\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"fcaf8da0-0146-2587-b578-3e1af888deaa\":{\"name\":\"pri_right_item\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-13.5768860212\",\"y\":\"23.1473092203\",\"z\":\"-27.013532475\"}],\"uuid\":\"c6821d4f-794f-c7c7-0f89-80033761c353\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-4.6994195496\",\"y\":\"13.8964693362\",\"z\":\"-30.1451170148\"}],\"uuid\":\"275ad9e7-67ec-4daf-b4c5-c49ff92c0c7c\",\"time\":0.125,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"27.697928082\",\"y\":\"-29.8984523542\",\"z\":\"-46.3767048046\"}],\"uuid\":\"2908fdad-6769-ad97-152b-f2e86f8fba88\",\"time\":0.25,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"81.4976285226\",\"y\":\"-85.6361454504\",\"z\":\"-145.5405300408\"}],\"uuid\":\"e38f1288-48fd-30ea-940e-a1713aa5e533\",\"time\":0.375,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"111.4976285226\",\"y\":\"-85.6361454504\",\"z\":\"-145.5405300408\"}],\"uuid\":\"0bb1777f-8437-9e14-420b-ad2af484cf2e\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"66.4976285226\",\"y\":\"-85.6361454504\",\"z\":\"-145.5405300408\"}],\"uuid\":\"f77bbe2b-eb20-5dca-9e04-984240155daa\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"218.1984733219\",\"y\":\"-82.2634224785\",\"z\":\"-252.6403714152\"}],\"uuid\":\"282590c6-2b2c-80b1-d27d-1cf6d8b8d539\",\"time\":0.625,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"b3135254-0351-3462-2479-e6a3286c89ff\":{\"name\":\"pla_left_arm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"241ef05d-3297-bd29-63f7-9ac98d2fab11\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"762de5c9-6726-ade1-3357-92c739fda0b1\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-57.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"ab041c20-61fb-26af-0653-35423dce4973\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1a9070b5-b8b6-b955-9f31-54f9625f8f3d\":{\"name\":\"plfa_left_forearm\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"6f854563-d459-37eb-82ce-ce7b114058d3\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"35\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"0751f55e-78d5-7ea0-ae92-a3f56f61570d\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"67.5\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9e37eb37-bc4a-ce3e-c99e-d247cc12acf4\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"7e8426f1-08b2-81a2-7703-cb76ff5e7003\":{\"name\":\"prl_right_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"12.5\"}],\"uuid\":\"dfdbf96d-60fd-d00b-d057-7a0fddebd2bf\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.6809719992\",\"y\":\"2.3896261399\",\"z\":\"5.3762737066\"}],\"uuid\":\"d8c7c5f2-d9bb-8be8-4395-282a02893319\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"26.6809719992\",\"y\":\"2.3896261399\",\"z\":\"5.3762737066\"}],\"uuid\":\"1f138668-b13a-8c4c-6d27-ae7c168a168b\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"c6d9e946-1d10-482d-14b1-0766027adba8\":{\"name\":\"prfl_right_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"0\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"522f3da4-0892-8397-935d-de98f65aa7ad\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.9759091971\",\"y\":\"-0.569987466\",\"z\":\"-0.8300310517\"}],\"uuid\":\"484dd03c-d5cd-3c01-f305-7b0a3e0b4524\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-24.9759091971\",\"y\":\"-0.569987466\",\"z\":\"-0.8300310517\"}],\"uuid\":\"7222f20e-0201-a3b1-7e99-6902147d0697\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"5ef5d225-d5ae-6787-8838-b75ccb7a7a81\":{\"name\":\"pll_left_leg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"9a8681c9-5339-28f0-edfd-2b8d0c193484\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-28.2171371372\",\"y\":\"-0.2383668654\",\"z\":\"6.7491823315\"}],\"uuid\":\"1e3c6439-78ef-20ec-5850-64f6d2e9b77b\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-28.2171371372\",\"y\":\"-0.2383668654\",\"z\":\"6.7491823315\"}],\"uuid\":\"55cf2e15-14ac-bb80-69de-3db8dff8ff47\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]},\"1b5cc202-c09e-faa0-5057-eb4ae60bf336\":{\"name\":\"plfl_left_foreleg\",\"type\":\"bone\",\"rotation_global\":false,\"quaternion_interpolation\":false,\"keyframes\":[{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"b91601e4-1df3-23af-4d55-d2a8849a97b9\",\"time\":0,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"e7b6f9bd-8abf-e22f-a9c9-9bc55e7aa644\",\"time\":0.5,\"color\":-1,\"interpolation\":\"catmullrom\"},{\"channel\":\"rotation\",\"data_points\":[{\"x\":\"-20\",\"y\":\"0\",\"z\":\"0\"}],\"uuid\":\"bb218f38-7f01-5744-470c-7767bc629b9e\",\"time\":0.75,\"color\":-1,\"interpolation\":\"catmullrom\"}]}}}],\"animation_variable_placeholders\":\"degrees=180\"}"
  },
  {
    "path": "test-plugin/src/main/resources/knight_line.json",
    "content": "{\n\t\"format_version\": \"1.21.6\",\n\t\"credit\": \"Made with Blockbench\",\n\t\"textures\": {\n\t\t\"0\": \"bettermodel:item/knight_line\",\n\t\t\"particle\": \"bettermodel:item/knight_line\"\n\t},\n\t\"elements\": [\n\t\t{\n\t\t\t\"from\": [3, 8.5, -8],\n\t\t\t\"to\": [8, 8.5, 8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [-0.5, 0, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 2.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 2.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.5, 8, 0, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [2.5, 0, 0, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [4.725, 7.975, -8],\n\t\t\t\"to\": [6.725, 7.975, 8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [-1.775, 16.475, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 1, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 1, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.5, 16, 1.5, 8], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3.5, 8, 2.5, 16], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [5.5, 8.5, -8],\n\t\t\t\"to\": [8, 8.5, 8],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"z\", \"origin\": [8, 8.5, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.5, 8, 5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8, 0, 6.5, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [5.5, 8.5, -8],\n\t\t\t\"to\": [8, 8.5, 8],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"z\", \"origin\": [8, 8.5, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 0, 1.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 0, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8, 8, 6.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.5, 0, 5, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 6.5, 0.5],\n\t\t\t\"to\": [7.75, 7.5, 2.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7.75, 6.5, -0.5]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7, 6.75, -5.5],\n\t\t\t\"to\": [7, 7.75, -3.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, 6.75, -6.5]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [4.5, 9.5, -3.5],\n\t\t\t\"to\": [4.5, 10.5, -1.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [4.5, 9.5, -3.5]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [4.5, 9, 2.75],\n\t\t\t\"to\": [4.5, 10, 4.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [4.5, 9, 2.75]},\n\t\t\t\"faces\": {\n\t\t\t\t\"east\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 8, 6.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t}\n\t],\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [0, 0, 0],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [0, 1, 2, 3, 4, 5, 6, 7]\n\t\t}\n\t]\n}"
  },
  {
    "path": "test-plugin/src/main/resources/knight_sword.json",
    "content": "{\n\t\"textures\": {\n\t\t\"0\": \"bettermodel:item/knight_sword\",\n\t\t\"particle\": \"bettermodel:item/knight_sword\"\n\t},\n\t\"elements\": [\n\t\t{\n\t\t\t\"from\": [7.15, -4, 7.25],\n\t\t\t\"to\": [8.85, -2.5, 8.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -8.5, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6.75, 5.5, 7.25, 6], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.75, 6, 7.25, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 6.5, 7.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 7, 0.5, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.5, 0.5, 7, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.5, 2.25, 7, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, 0, 8.25],\n\t\t\t\"to\": [8.8, 2, 11.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7, 2.75, 7.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 5.25, 5.25, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.75, 7, 5.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.25, 5.25, 6.25, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6, 1, 5.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6, 1, 5.5, 2], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6.75, -0.5, 6.75],\n\t\t\t\"to\": [9.25, 2, 9.25],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 0.75, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.25, 4.5, 5, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 0, 5.5, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.75, 0.75, 5.5, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.75, 1.5, 5.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.5, 3, 4.75, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5.5, 3, 4.75, 3.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.225, 0, 8.375],\n\t\t\t\"to\": [8.775, 1.5, 10.5],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 0.5, 10.625]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 7, 5.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 7, 6.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.25, 7, 6.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.75, 7, 7.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.75, 1, 7.25, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.75, 1, 7.25, 1.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.225, 0, 5.5],\n\t\t\t\"to\": [8.775, 1.5, 7.625],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 0.5, 5.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.25, 1.5, 7.75, 2], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.25, 3.25, 7.75, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 3.75, 7.75, 4.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.25, 4.25, 7.75, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.75, 5.25, 7.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.75, 5.25, 7.25, 5.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.15, -1.5, 7.25],\n\t\t\t\"to\": [8.85, 0, 8.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.25, 5.75, 7.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.25, 6.25, 7.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 6.75, 7.75, 7.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.25, 7.25, 7.75, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [0.5, 8, 0, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8, 0, 7.5, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.4, -8.95, 7.4],\n\t\t\t\"to\": [8.6, -8.25, 8.6],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -8.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7, 0.5, 7.25, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.25, 2, 7.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.5, 3, 7.75, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.75, 8.75, 7, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 7.25, 8.75, 7], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.5, 8.75, 7.25, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 0.925, 10.375],\n\t\t\t\"to\": [8.825, 2.675, 11.6],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 10.3]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 7.5, 1.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.5, 4, 5.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.5, 2, 8, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6, 3.5, 6.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.25, 5.25, 5.75, 5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 2.75, 8, 3], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 0.925, 4.4],\n\t\t\t\"to\": [8.825, 2.675, 5.625],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 5.7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.5, 2.5, 8, 3], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.25, 2.25, 8.5, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.25, 7.5, 4.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.25, 3, 8.5, 3.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 1, 8.25, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.75, 3.5, 8.25, 3.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, -12.75, 7.5],\n\t\t\t\"to\": [8.75, -11.5, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, -12, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.25, 7.75, 8.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.5, 8.75, 8.75, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.25, 8, 8.75, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.75, 8.5, 9, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 8.5, 8.25, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 0, 8.5, 0.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7, -5.25, 7.1],\n\t\t\t\"to\": [9, -4, 8.9],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -8.75, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 0.25, 9, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.5, 0.5, 9, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 1, 9, 1.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1.25, 8.5, 1.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.75, 8, 5.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.25, 7.5, 5.75, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.025, -4.95, 8.725],\n\t\t\t\"to\": [8.975, -3.8, 9.2],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, -4.325, 8.3]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 0.75, 9.25, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 9.5, 4.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1, 8.75, 1.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 4.5, 9.75, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2, 9, 1.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3.75, 8.75, 3.25, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.025, -4.95, 6.8],\n\t\t\t\"to\": [8.975, -3.8, 7.275],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -4.325, 7.7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 3.5, 9.25, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 9.5, 5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 8.75, 4.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 4.75, 9.75, 5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [4.75, 9, 4.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5.25, 8.75, 4.75, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.05, -5.975, 7.1],\n\t\t\t\"to\": [8.95, -4.7, 8.375],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, -5.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 7.5, 9, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 9.5, 4.5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.75, 8.5, 8.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 4.25, 9.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 8.75, 8.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1, 8.75, 0.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.4, -9.95, 7.4],\n\t\t\t\"to\": [8.6, -9.25, 8.6],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -9.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.5, 9.5, 5.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 5.5, 9.75, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5.75, 9.5, 6, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 5.75, 9.75, 6], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.25, 9.75, 6, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 6, 9.5, 6.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.4, -10.95, 7.4],\n\t\t\t\"to\": [8.6, -10.25, 8.6],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, -10.6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 8.75, 9, 9], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 9, 0.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 0, 9.25, 0.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.25, 9, 0.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 0.5, 9, 0.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [0.75, 9, 0.5, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, -12, 7.5],\n\t\t\t\"to\": [8.5, -5.25, 8.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -8.5, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 6, 0.75, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0.75, 6, 1, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1, 6, 1.25, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6, 1, 6.25, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 0.75, 9, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1, 9, 0.75, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8.3, -4.3, 8.925],\n\t\t\t\"to\": [8.75, 0.15, 9.675],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8.55, -1.925, 9.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6.25, 7.5, 6.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.5, 7.5, 6.75, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 7.5, 7, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7, 7.5, 7.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1.25, 9.25, 1, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 1, 9, 1.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, -4.3, 8.925],\n\t\t\t\"to\": [7.7, 0.15, 9.675],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.45, -1.925, 9.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 7.75, 0.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.75, 0.5, 8, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0.75, 7.75, 1, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 7.75, 1.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1.5, 9.25, 1.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 1.25, 9, 1.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6.95, -0.425, 2.575],\n\t\t\t\"to\": [9.05, 2.45, 4.25],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 3.55]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 6.25, 5.75, 7], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 6.25, 6.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.25, 6.25, 6.75, 7], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.5, 0, 7, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.25, 2, 7.75, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 3, 7.75, 3.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, 0, 4.25],\n\t\t\t\"to\": [8.8, 2, 7.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 3.5, 8.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 5.75, 5.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.75, 4, 8.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.75, 5.75, 6.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [0.5, 7, 0, 6], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.5, 0, 6, 1], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, -0.925, 13.2],\n\t\t\t\"to\": [8.8, 1.675, 14.3],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 9.75]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6.5, 2.5, 7, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8, 7, 8.25, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 0.75, 7.25, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8, 7.75, 8.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 3.5, 8.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 3.75, 8.5, 4], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.575, 13.875],\n\t\t\t\"to\": [8.55, 1.325, 14.65],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8, 0.475, 14.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 8.75, 5.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.75, 5.25, 9, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5.5, 8.75, 5.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.75, 8.75, 6, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.25, 9.75, 5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 5, 9.5, 5.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.575, 1.35],\n\t\t\t\"to\": [8.55, 1.325, 2.125],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 0.475, 1.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 5.75, 9, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6, 8.75, 6.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.75, 6.25, 9, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.5, 8.75, 6.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [5.5, 9.75, 5.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 5.25, 9.5, 5.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6.95, -0.425, 11.75],\n\t\t\t\"to\": [9.05, 2.45, 13.425],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [8, 1.925, 12.45]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 6.75, 1.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.75, 1.5, 7.25, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 3.25, 7.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.75, 4, 7.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.25, 5, 7.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 5, 7.75, 5.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.275, 12.475],\n\t\t\t\"to\": [8.55, 1.2, 13.2],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 9.75]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2, 9, 2.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 2, 9.25, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.25, 9, 2.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 2.25, 9.25, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.75, 9.25, 2.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 2.5, 9, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.2, -0.925, 1.7],\n\t\t\t\"to\": [8.8, 1.675, 2.8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 6.25]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.25, 6.75, 4.75, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.25, 0, 8.5, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 4.75, 7.25, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.25, 1.5, 8.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 4.25, 8.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 4.25, 8.5, 4.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.45, -0.275, 2.8],\n\t\t\t\"to\": [8.55, 1.2, 3.525],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6.25, 6.25]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.75, 9, 3, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 2.75, 9.25, 3], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3, 9, 3.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 3, 9.25, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [3.5, 9.25, 3.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 3.25, 9, 3.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, -4.3, 6.325],\n\t\t\t\"to\": [7.7, 0.15, 7.075],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.45, -1.925, 6.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 5.5, 8, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.75, 6.5, 8, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 7.75, 7.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.5, 7.75, 7.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [3.75, 9.25, 3.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4, 9, 3.75, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8.3, -4.3, 6.325],\n\t\t\t\"to\": [8.75, 0.15, 7.075],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [8.55, -1.925, 6.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 7.5, 8, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 8, 0.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8, 0, 8.25, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.25, 8, 0.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 4, 9, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4.25, 9, 4, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7, -2.5, 6.95],\n\t\t\t\"to\": [9, -1.5, 9.05],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 4.5, 9, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 8.5, 5.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 4.75, 9, 5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 5, 9, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 1.5, 8, 1], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.75, 8, 1.25, 8.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, -2.25, 9],\n\t\t\t\"to\": [8.25, -1.75, 9.7],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9, 4, 9.25, 4.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 9, 4.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 4.25, 9.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.5, 9, 4.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 4.75, 9, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5, 9, 4.75, 9.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, -2.25, 9.55],\n\t\t\t\"to\": [8.5, -1.75, 10.05],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, -2, 9.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9, 4.75, 9.25, 5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5, 9, 5.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 5, 9.25, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 5.25, 9.25, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.25, 5.75, 9, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 5.75, 9, 6], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, -2.25, 6.3],\n\t\t\t\"to\": [8.25, -1.75, 7],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 9]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9, 6, 9.25, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.25, 9, 6.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 6.25, 9.25, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 6.5, 9.25, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7, 9.25, 6.75, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 6.75, 9, 7], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, -2.25, 5.95],\n\t\t\t\"to\": [8.5, -1.75, 6.45],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, -2, 6.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7, 9, 7.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 7, 9.25, 7.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7.25, 9, 7.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 7.25, 9.25, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.75, 9.25, 7.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 7.5, 9, 7.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 2.3, 4.275],\n\t\t\t\"to\": [8.825, 2.95, 4.5],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 2.2, 4.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 1.25, 9, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [1.5, 9, 1.75, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 1.5, 9, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 1.5, 9.25, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 2, 8.5, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 2, 8.5, 2.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.175, 2.3, 11.5],\n\t\t\t\"to\": [8.825, 2.95, 11.725],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 2.2, 11.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 2.25, 9, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [1.75, 9, 2, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 2.5, 9, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 1.75, 9.25, 2], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 3, 8.5, 2.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 3, 8.5, 3.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 22.05, 6.7],\n\t\t\t\"to\": [8.5, 25.4, 8.75],\n\t\t\t\"rotation\": {\"angle\": -20, \"axis\": \"x\", \"origin\": [8, 29.8, 7.45]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.75, 7.5, 5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 4, 6.25, 5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5, 7.5, 5.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.25, 5.75, 4.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 5.25, 8.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 5.25, 8.25, 5.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 5, 9],\n\t\t\t\"to\": [8.5, 22.75, 10.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2, 0, 2.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2.25, 0, 2.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.5, 0, 2.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [2.75, 0, 3, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.5, 2.75, 6.25, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.75, 0.75, 6.5, 1], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 2, 10.45],\n\t\t\t\"to\": [8.5, 5, 11.15],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"y\", \"origin\": [8, 9.5, 10.95]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3.25, 8, 3.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.5, 8, 3.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 8, 4, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 8, 4.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9, 7.5, 8.75, 7.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.75, 8.75, 7.5, 9], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 5, 9.95],\n\t\t\t\"to\": [8.5, 22.75, 10.65],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"y\", \"origin\": [8, 12.5, 10.45]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.25, 4.5, 2.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2.5, 4.5, 2.75, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.75, 4.5, 3, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [3, 4.5, 3.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 9, 8.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 8.25, 8.75, 8.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 5, 5.35],\n\t\t\t\"to\": [8.5, 22.75, 6.05],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"y\", \"origin\": [8, 12.5, 5.55]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4, 0, 4.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 0, 4.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.5, 0, 4.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [2, 4.5, 2.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8, 9, 7.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 7.75, 8.75, 8], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.8, 2, 4.85],\n\t\t\t\"to\": [8.5, 5, 5.55],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"y\", \"origin\": [8, 9.5, 5.05]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8, 5.5, 8.25, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.75, 8, 6, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6, 8, 6.25, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8, 6.25, 8.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.25, 9, 8, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 8, 8.75, 8.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 2, 6.75],\n\t\t\t\"to\": [8.25, 25.5, 9.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.5, 0, 1.75, 6], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0, 0.75, 6], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.75, 0, 2, 6], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.75, 0, 1.5, 6], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2, 8.75, 1.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 2, 8, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 24.825, 5.825],\n\t\t\t\"to\": [8.5, 27.9, 8.9],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 27, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 6, 1.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 3.75, 5.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6, 2.75, 6.5, 3.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5, 4.5, 5.75, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.75, 1.75, 6.25, 1], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.75, 1.75, 6.25, 2.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 5, 5.75],\n\t\t\t\"to\": [8.5, 22.75, 7],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3, 0, 3.25, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.25, 0, 3.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.5, 0, 3.75, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [3.75, 0, 4, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.75, 3.5, 6.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7, 2.25, 6.75, 2.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 2, 9.25],\n\t\t\t\"to\": [8.5, 5, 10.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 8, 5.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.75, 6.25, 5.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [5.5, 8, 5.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.25, 5, 6.75, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 7.25, 8.25, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 7.25, 8.25, 7.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 2, 5.25],\n\t\t\t\"to\": [8.5, 5, 6.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [7, -6, 7]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.25, 8, 4.5, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.25, 3.5, 6.75, 4.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.5, 8, 4.75, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.25, 4.25, 6.75, 5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 6.25, 8.25, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 6.25, 8.25, 6.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.5, 22.05, 7.25],\n\t\t\t\"to\": [8.5, 25.4, 9.3],\n\t\t\t\"rotation\": {\"angle\": 20, \"axis\": \"x\", \"origin\": [8, 29.8, 8.55]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.75, 6, 2, 7], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.5, 2, 6, 3], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.75, 7, 2, 8], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 3, 6, 4], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 4.25, 8.25, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 4.25, 8.25, 4.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_1\",\n\t\t\t\"from\": [7.5, 2.975, 7.875],\n\t\t\t\"to\": [8.5, 3.475, 8.125],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 16.975, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 9.5, 0.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 0.5, 9.75, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0.75, 9.5, 1, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 0.75, 9.75, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1.25, 9.75, 1, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 1, 9.5, 1.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_2\",\n\t\t\t\"from\": [7.5, 3.5625, 7.25],\n\t\t\t\"to\": [8.5, 4.3125, 7.625],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 2.8125, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 7.25, 9.5, 7.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.5, 9.25, 7.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 7.5, 9.5, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7.75, 9.25, 8, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 8, 9.25, 7.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.25, 9.25, 8, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_2\",\n\t\t\t\"from\": [7.5, 3.1875, 6.5],\n\t\t\t\"to\": [8.5, 3.5625, 7.625],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 2.8125, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 9, 9.5, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.25, 9.25, 9.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [0, 9.5, 0.25, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 0, 9.75, 0.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [0.5, 9.75, 0.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 0.25, 9.5, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_3\",\n\t\t\t\"from\": [7.5, 5.25, 6.125],\n\t\t\t\"to\": [8.5, 5.625, 7.625],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 8.75, 9.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.5, 8.5, 7, 8.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 9.25, 9.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 6.75, 9, 7], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7.25, 9, 7, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.75, 7, 8.5, 7.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_3\",\n\t\t\t\"from\": [7.5, 4.125, 7.25],\n\t\t\t\"to\": [8.5, 5.25, 7.625],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 6, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 8, 9.5, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.25, 9.25, 8.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 8.25, 9.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 9.25, 8.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 8.75, 9.25, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 9.25, 8.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 5.35, 7.875],\n\t\t\t\"to\": [8.5, 18.85, 8.125],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 17.1, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3.25, 4.5, 3.5, 8], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.5, 4.5, 3.75, 8], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 4.5, 4, 8], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 4.5, 4.25, 8], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 2.75, 9.25, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3, 9.25, 2.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 7.25],\n\t\t\t\"to\": [8.5, 7.05, 7.7],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [7.875, 6.725, 7.575]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 9.5, 1.5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 1.25, 9.75, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 9.5, 1.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 1.5, 9.75, 1.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2, 9.75, 1.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 1.75, 9.5, 2], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 7.625],\n\t\t\t\"to\": [8.5, 6.85, 7.875],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.1, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2, 9.5, 2.25, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 2, 9.75, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2.25, 9.5, 2.5, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 2.25, 9.75, 2.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.75, 9.75, 2.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 2.5, 9.5, 2.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 8.3],\n\t\t\t\"to\": [8.5, 7.05, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [7.875, 6.725, 8.425]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.75, 9.5, 3, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 2.75, 9.75, 3], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3, 9.5, 3.25, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 3, 9.75, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [3.5, 9.75, 3.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 3.25, 9.5, 3.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_4\",\n\t\t\t\"from\": [7.5, 6.6, 8.125],\n\t\t\t\"to\": [8.5, 6.85, 8.375],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.1, 7.625]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3.5, 9.5, 3.75, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9.5, 3.5, 9.75, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3.75, 9.5, 4, 9.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9.5, 3.75, 9.75, 4], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [4.25, 9.75, 4, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.75, 4, 9.5, 4.25], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.875, 7.5],\n\t\t\t\"to\": [8.5, 19.375, 7.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 18.375, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 1, 9.5, 1.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [1.25, 9.25, 1.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 1.25, 9.5, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1.5, 9.25, 1.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 1.75, 9.25, 1.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [2, 9.25, 1.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.625, 7],\n\t\t\t\"to\": [8.5, 18.875, 7.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 18.375, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 1.75, 9.5, 2], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2, 9.25, 2.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 2, 9.5, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [2.25, 9.25, 2.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 2.5, 9.25, 2.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [2.75, 9.25, 2.5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.425, 7.175],\n\t\t\t\"to\": [8.5, 18.575, 7.925],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.875, 18.55, 7.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 2.75, 9.5, 3], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3, 9.25, 3.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 3, 9.5, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [3.25, 9.25, 3.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 3.5, 9.25, 3.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3.75, 9.25, 3.5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.925, 7.425],\n\t\t\t\"to\": [8.5, 18.075, 7.925],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.875, 18.05, 7.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 3.5, 9.5, 3.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.75, 9.25, 4, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 3.75, 9.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 9.25, 4.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 4.25, 9.25, 4], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4.5, 9.25, 4.25, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.425, 7.675],\n\t\t\t\"to\": [8.5, 17.575, 7.925],\n\t\t\t\"rotation\": {\"angle\": -22.5, \"axis\": \"x\", \"origin\": [7.875, 17.55, 7.8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 4.25, 9.5, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 9.25, 4.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 4.5, 9.5, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.75, 9.25, 5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 5, 9.25, 4.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5.25, 9.25, 5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.425, 8.075],\n\t\t\t\"to\": [8.5, 17.575, 8.325],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.875, 17.55, 8.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 5, 9.5, 5.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5.25, 9.25, 5.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 5.25, 9.5, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [5.5, 9.25, 5.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 5.75, 9.25, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6, 9.25, 5.75, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 17.925, 8.075],\n\t\t\t\"to\": [8.5, 18.075, 8.575],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.875, 18.05, 8.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 5.75, 9.5, 6], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6, 9.25, 6.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 6, 9.5, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.25, 9.25, 6.5, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 6.5, 9.25, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.75, 9.25, 6.5, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_5\",\n\t\t\t\"from\": [7.5, 18.425, 8.075],\n\t\t\t\"to\": [8.5, 18.575, 8.825],\n\t\t\t\"rotation\": {\"angle\": 22.5, \"axis\": \"x\", \"origin\": [7.875, 18.55, 8.2]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 6.5, 9.5, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6.75, 9.25, 7, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 6.75, 9.5, 7], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7, 9.25, 7.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 7.25, 9.25, 7], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [7.5, 9.25, 7.25, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_6\",\n\t\t\t\"from\": [7.5, 19.25, 7.5],\n\t\t\t\"to\": [8.5, 20, 7.75],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 20.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [9.25, 0.25, 9.5, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0.5, 9.25, 0.75, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9.25, 0.5, 9.5, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0.75, 9.25, 1, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 1, 9.25, 0.75], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.25, 9.25, 1, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_6\",\n\t\t\t\"from\": [7.5, 20, 6.75],\n\t\t\t\"to\": [8.5, 20.25, 7.75],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [8, 20.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.75, 9, 9, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 8.75, 9.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [9, 9, 9.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [0, 9.25, 0.25, 9.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [9.5, 0.25, 9.25, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [0.5, 9.25, 0.25, 9.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_7\",\n\t\t\t\"from\": [7.5, 21.9, 7.875],\n\t\t\t\"to\": [8.5, 23.15, 9.125],\n\t\t\t\"rotation\": {\"angle\": -45, \"axis\": \"x\", \"origin\": [7.875, 22.025, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.75, 9, 8, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [9, 7.75, 9.25, 8], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8, 9, 8.25, 9.25], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [9, 8, 9.25, 8.25], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 9.25, 8.25, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 8.25, 9, 8.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"rune_7\",\n\t\t\t\"from\": [7.5, 20.025, 7.875],\n\t\t\t\"to\": [8.5, 22.025, 8.125],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 20.525, 8.375]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 5.25, 8.75, 5.75], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.5, 5.75, 8.75, 6.25], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6.25, 8.5, 6.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 6.25, 8.75, 6.75], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.75, 9.25, 8.5, 9], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.25, 8.5, 9, 8.75], \"texture\": \"#0\"}\n\t\t\t}\n\t\t}\n\t],\n\t\"display\": {\n\t\t\"thirdperson_righthand\": {\n\t\t\t\"translation\": [0, 14.25, 0]\n\t\t},\n\t\t\"thirdperson_lefthand\": {\n\t\t\t\"translation\": [0, 14.25, 0]\n\t\t},\n\t\t\"firstperson_righthand\": {\n\t\t\t\"rotation\": [-5, 5, -5],\n\t\t\t\"translation\": [0, 9.25, 0]\n\t\t},\n\t\t\"firstperson_lefthand\": {\n\t\t\t\"rotation\": [-5, 5, -5],\n\t\t\t\"translation\": [0, 9.25, 0]\n\t\t},\n\t\t\"ground\": {\n\t\t\t\"rotation\": [0, 0, 90]\n\t\t},\n\t\t\"gui\": {\n\t\t\t\"rotation\": [90, -135, 90],\n\t\t\t\"scale\": [1, 0.5, 0.5]\n\t\t},\n\t\t\"fixed\": {\n\t\t\t\"rotation\": [90, -45, 90],\n\t\t\t\"scale\": [1, 0.5, 0.5]\n\t\t}\n\t},\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [7, -8.5, 7],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]\n\t\t},\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [8, 29.8, 7.45],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48]\n\t\t},\n\t\t{\n\t\t\t\"name\": \"group\",\n\t\t\t\"origin\": [8, 11.1, 7.625],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70]\n\t\t}\n\t]\n}"
  }
]