├── .github ├── ISSUE_TEMPLATE │ ├── api-bug-report.md │ ├── buildscript-error.md │ ├── feature_request.md │ └── library-crash.md └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── coreLib ├── build.gradle └── src │ ├── main │ └── java │ │ └── dev │ │ └── kosmx │ │ └── playerAnim │ │ ├── api │ │ ├── AnimUtils.java │ │ ├── IPlayable.java │ │ ├── IPlayer.java │ │ ├── TransformType.java │ │ ├── firstPerson │ │ │ ├── FirstPersonConfiguration.java │ │ │ └── FirstPersonMode.java │ │ ├── layered │ │ │ ├── AnimationContainer.java │ │ │ ├── AnimationStack.java │ │ │ ├── IActualAnimation.java │ │ │ ├── IAnimation.java │ │ │ ├── KeyframeAnimationPlayer.java │ │ │ ├── ModifierLayer.java │ │ │ ├── PlayerAnimationFrame.java │ │ │ └── modifier │ │ │ │ ├── AbstractFadeModifier.java │ │ │ │ ├── AbstractModifier.java │ │ │ │ ├── AdjustmentModifier.java │ │ │ │ ├── FirstPersonModifier.java │ │ │ │ ├── MirrorModifier.java │ │ │ │ └── SpeedModifier.java │ │ └── package-info.java │ │ └── core │ │ ├── data │ │ ├── AnimationBinary.java │ │ ├── AnimationFormat.java │ │ ├── KeyframeAnimation.java │ │ ├── gson │ │ │ ├── AnimationJson.java │ │ │ ├── AnimationSerializing.java │ │ │ └── GeckoLibSerializer.java │ │ ├── opennbs │ │ │ ├── NBS.java │ │ │ ├── NBSFileUtils.java │ │ │ ├── SoundPlayer.java │ │ │ ├── format │ │ │ │ ├── CustomInstrument.java │ │ │ │ ├── Header.java │ │ │ │ ├── Layer.java │ │ │ │ └── package-info.java │ │ │ ├── network │ │ │ │ └── NBSPacket.java │ │ │ └── package-info.java │ │ └── quarktool │ │ │ ├── InverseEase.java │ │ │ ├── Move.java │ │ │ ├── PartMap.java │ │ │ ├── Pause.java │ │ │ ├── Pauseable.java │ │ │ ├── Playable.java │ │ │ ├── QuarkParsingError.java │ │ │ ├── QuarkReader.java │ │ │ ├── Repeat.java │ │ │ ├── Reset.java │ │ │ ├── Section.java │ │ │ └── Yoyo.java │ │ ├── impl │ │ ├── AnimationProcessor.java │ │ └── event │ │ │ ├── Event.java │ │ │ └── EventResult.java │ │ └── util │ │ ├── Ease.java │ │ ├── Easing.java │ │ ├── MathHelper.java │ │ ├── NetworkHelper.java │ │ ├── Pair.java │ │ ├── SetableSupplier.java │ │ ├── UUIDMap.java │ │ ├── Vec3d.java │ │ ├── Vec3f.java │ │ └── Vector3.java │ └── test │ └── java │ └── dev │ └── kosmx │ └── playerAnim │ └── core │ └── data │ ├── KeyframeAnimationTest.java │ ├── LayerTest.java │ └── StringTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── icon.png ├── minecraft ├── build.gradle ├── common │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── dev │ │ │ └── kosmx │ │ │ └── playerAnim │ │ │ ├── impl │ │ │ ├── Helper.java │ │ │ ├── IAnimatedPlayer.java │ │ │ ├── IMutableModel.java │ │ │ ├── IPlayerModel.java │ │ │ ├── IUpperPartHelper.java │ │ │ ├── animation │ │ │ │ ├── AnimationApplier.java │ │ │ │ ├── BendHelper.java │ │ │ │ └── IBendHelper.java │ │ │ ├── compat │ │ │ │ └── skinLayers │ │ │ │ │ └── SkinLayersTransformer.java │ │ │ └── mixin │ │ │ │ └── MixinConfig.java │ │ │ ├── minecraftApi │ │ │ ├── PlayerAnimationAccess.java │ │ │ ├── PlayerAnimationFactory.java │ │ │ ├── PlayerAnimationRegistry.java │ │ │ ├── codec │ │ │ │ ├── AbstractGsonCodec.java │ │ │ │ ├── AnimationCodec.java │ │ │ │ ├── AnimationCodecs.java │ │ │ │ ├── AnimationDecoder.java │ │ │ │ ├── AnimationEncoder.java │ │ │ │ ├── EmotecraftGsonCodec.java │ │ │ │ └── LegacyGeckoJsonCodec.java │ │ │ └── layers │ │ │ │ └── LeftHandedHelperModifier.java │ │ │ └── mixin │ │ │ ├── ArmorFeatureRendererMixin.java │ │ │ ├── BipedEntityModelMixin.java │ │ │ ├── CapeLayerMixin.java │ │ │ ├── ElytraLayerMixin.java │ │ │ ├── FeatureRendererMixin.java │ │ │ ├── HeldItemMixin.java │ │ │ ├── LivingEntityRenderRedirect_bendOnly.java │ │ │ ├── ModelPartMixin.java │ │ │ ├── PlayerEntityMixin.java │ │ │ ├── PlayerModelAccessor.java │ │ │ ├── PlayerModelMixin.java │ │ │ ├── PlayerRendererMixin.java │ │ │ └── firstPerson │ │ │ ├── CameraAccessor.java │ │ │ ├── EntityRenderDispatcherMixin.java │ │ │ ├── ItemInHandRendererMixin.java │ │ │ ├── LevelRendererMixin.java │ │ │ └── LivingEntityRendererMixin.java │ │ └── resources │ │ ├── architectury.common.json │ │ ├── playerAnimator-common.mixins.json │ │ └── playerAnimator.accesswidener ├── fabric │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── dev │ │ │ │ └── kosmx │ │ │ │ └── playerAnim │ │ │ │ ├── fabric │ │ │ │ └── client │ │ │ │ │ └── FabricClientInitializer.java │ │ │ │ └── impl │ │ │ │ └── fabric │ │ │ │ └── HelperImpl.java │ │ └── resources │ │ │ ├── assets │ │ │ └── player-animator │ │ │ │ └── lang │ │ │ │ └── tt_ru.json │ │ │ └── fabric.mod.json │ │ ├── test.md │ │ └── testmod │ │ ├── java │ │ └── dev │ │ │ └── kosmx │ │ │ └── animatorTestmod │ │ │ ├── PlayerAnimTestmod.java │ │ │ └── mixin │ │ │ ├── ClientPlayerMixin.java │ │ │ └── MinecraftClientMixin.java │ │ └── resources │ │ ├── assets │ │ └── testmod │ │ │ └── player_animations │ │ │ ├── note.md │ │ │ ├── player.animation.json │ │ │ ├── two_handed_slash_horizontal_right.json │ │ │ └── two_handed_slash_vertical_right.json │ │ ├── fabric.mod.json │ │ └── testmod.mixins.json ├── forge │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ └── main │ │ ├── java │ │ └── dev │ │ │ └── kosmx │ │ │ └── playerAnim │ │ │ ├── forge │ │ │ └── ForgeClientEvent.java │ │ │ └── impl │ │ │ └── neoforge │ │ │ └── HelperImpl.java │ │ └── resources │ │ └── META-INF │ │ └── neoforge.mods.toml └── gradle.properties └── settings.gradle /.github/ISSUE_TEMPLATE/api-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: API bug report 3 | about: Issue about the API (not the buildscript) 4 | title: "[API]" 5 | labels: bug 6 | assignees: KosmX 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Setup config (minecraft, modloader, libraries):** 27 | - Minecraft version: [e.g. 1.19.3] 28 | - Mod loader: [e.g. Fabric 0.14.11] 29 | - PlayerAnimator Library version [e.g. 0.4.0+1.19.3] 30 | - MC library version (e.g. Fabric API version) 31 | 32 | **Other mods (If you use other mods, please specify! it might be an incompatibility):** 33 | 34 | *Optional:* 35 | ** Used library methods, ideas (If you're a developer, what function is not working as expected)** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/buildscript-error.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Buildscript error 3 | about: Something is wrong with buildscript 4 | title: '' 5 | labels: build 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Buildscript content or link to content (If you don't share your code, I cannot debug it):** 14 | https://github.com/KosmX/fabricPlayerAnimatorExample/blob/1.19/build.gradle 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Run `gradle runClient` 19 | 2. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Environment (please complete the following information):** 25 | - OS: [e.g. Windows 11, Archlinux, Ubuntu 22.04] 26 | - IDE [e.g. IntelliJ, Eclipse, vim+bash] 27 | - Gradle version [e.g. 7.5.1] 28 | - JDK version [e.g. 17.0.5 - Adoptium] 29 | - gradle plugin [e.g. Fabric loom, ForgeGradle] (if I don't see it in your project) 30 | - Minecraft, Loader: (Fabric 0.14.11+1.19.2) 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/library-crash.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Library crash 3 | about: User crash report 4 | title: '' 5 | labels: bug, library 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Crash report (please copy the whole crash report!)** 24 | ``` 25 | Minecraft 1.19.3 26 | ... 27 | ``` 28 | 29 | **Logs (if you have logs from that run, please share it):** 30 | ``` 31 | ... 32 | ``` 33 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Publish 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | type: 8 | description: 'release type' 9 | required: false 10 | type: choice 11 | options: 12 | - alpha 13 | - beta 14 | - release 15 | changelog: 16 | description: 'changelog' 17 | required: false 18 | default: '' 19 | upload: 20 | required: true 21 | type: choice 22 | options: 23 | - true 24 | - false 25 | 26 | 27 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 28 | jobs: 29 | # This workflow contains a single job called "build" 30 | publish: 31 | # The type of runner that the job will run on 32 | runs-on: ubuntu-latest 33 | 34 | # Steps represent a sequence of tasks that will be executed as part of the job 35 | steps: 36 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 37 | - name: Check for valid input 38 | run: | 39 | if ! ( [ "${{github.event.inputs.type}}" = "alpha" ] || [ "${{github.event.inputs.type}}" = "beta" ] || [ "${{github.event.inputs.type}}" = "release" ] ) 40 | then 41 | return -1 42 | fi 43 | - uses: actions/checkout@v3 44 | - uses: actions/setup-java@v3 45 | with: 46 | distribution: 'microsoft' 47 | java-version: '21' 48 | 49 | # Runs a single command using the runners shell 50 | - name: validate gradle wrapper 51 | uses: gradle/wrapper-validation-action@v1 52 | - name: make gradle wrapper executable 53 | if: ${{ runner.os != 'Windows' }} 54 | run: chmod +x ./gradlew 55 | - name: Publish package 56 | run: ./gradlew publish --no-daemon 57 | env: 58 | KOSMX_MAVEN_USER: ${{ secrets.MAVEN_USER }} 59 | KOSMX_MAVEN_TOKEN: ${{ secrets.MAVEN_PASS }} 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} 62 | CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} 63 | CHANGELOG: ${{github.event.inputs.changelog}} 64 | RELEASE_TYPE: ${{github.event.inputs.type}} 65 | UPLOAD_TO_PORTAL: ${{github.event.inputs.upload}} 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.ipr 3 | run/ 4 | *.iws 5 | out/ 6 | *.iml 7 | .gradle/ 8 | output/ 9 | bin/ 10 | libs/ 11 | 12 | .classpath 13 | .project 14 | .idea/ 15 | classes/ 16 | .metadata 17 | .vscode 18 | .settings 19 | *.launch 20 | .architectury-transformer -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 KosmX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "architectury-plugin" version "3.4-SNAPSHOT" 3 | id "dev.architectury.loom" version "1.7-SNAPSHOT" apply false 4 | 5 | //Publishing 6 | id 'com.matthewprenger.cursegradle' version '1.4.0' apply false 7 | id "com.modrinth.minotaur" version "2.7.4" apply false 8 | id 'com.github.johnrengelman.shadow' version '8.1.1' apply false 9 | } 10 | 11 | architectury { 12 | minecraft = rootProject.minecraft_version 13 | } 14 | 15 | project.mod_version = mod_version + "+" + minecraft_version 16 | 17 | allprojects { 18 | apply plugin: "java" 19 | apply plugin: "architectury-plugin" 20 | apply plugin: "maven-publish" 21 | 22 | group = rootProject.maven_group 23 | archivesBaseName = rootProject.archives_base_name 24 | version = rootProject.mod_version 25 | 26 | repositories { 27 | maven { url "https://maven.neoforged.net/releases" } 28 | mavenCentral() 29 | maven { 30 | name 'KosmX\'s maven' 31 | url 'https://maven.kosmx.dev/' 32 | } 33 | exclusiveContent { 34 | forRepository { 35 | maven { 36 | name = "Modrinth" 37 | url = "https://api.modrinth.com/maven" 38 | } 39 | } 40 | filter { 41 | includeGroup "maven.modrinth" 42 | } 43 | } 44 | } 45 | 46 | dependencies { 47 | compileOnly 'com.google.code.findbugs:jsr305:3.0.2' 48 | } 49 | 50 | tasks.withType(JavaCompile) { 51 | options.encoding = "UTF-8" 52 | options.release = 21 53 | } 54 | 55 | java { 56 | withSourcesJar() 57 | } 58 | } 59 | 60 | ext.ENV = System.getenv() 61 | 62 | ext.cfType = ENV.RELEASE_TYPE ? ENV.RELEASE_TYPE : "alpha" 63 | ext.changes = ENV.CHANGELOG ? ENV.CHANGELOG.replaceAll("\\\\n", "\n") : "" 64 | ext.ENV = System.getenv() 65 | boolean upload = ENV.UPLOAD_TO_PORTAL ? ENV.UPLOAD_TO_PORTAL == "true" : false 66 | 67 | ext.keysExists = ENV.KOSMX_MAVEN_USER != null || project.getGradle().getStartParameter().isDryRun() 68 | 69 | if(keysExists) { 70 | project.ext.keys = new Properties() 71 | if (project.getGradle().getStartParameter().isDryRun()) { 72 | println("Dry run, loading publish scripts") 73 | //All of these are fake, don't waste your time with it. (Copied from API docs and random generated) 74 | project.ext.keys.modrinth_token = "gho_pJ9dGXVKpfzZp4PUHSxYEq9hjk0h288Gwj4S" 75 | project.ext.keys.curseforge_key = "00000000-0000-0000-0000-000000000000" 76 | project.ext.keys.kosmx_maven = "V2h5IGRpZCB5b3UgZGVjb2RlIGl0PyAg" 77 | project.ext.keys.kosmx_maven_user = "username" 78 | } else { 79 | println("Keys loaded, loading publish scripts") 80 | project.ext.keys.modrinth_token = ENV.MODRINTH_TOKEN 81 | project.ext.keys.curseforge_key = ENV.CURSEFORGE_TOKEN 82 | project.ext.keys.kosmx_maven = ENV.KOSMX_MAVEN_TOKEN 83 | project.ext.keys.kosmx_maven_user = ENV.KOSMX_MAVEN_USER 84 | } 85 | 86 | publish { 87 | if (upload) { 88 | finalizedBy(':minecraft:publishMod') 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /coreLib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'maven-publish' 4 | //id "io.freefair.lombok" version "6.5.0.2" This breaks javadoc (???) I'll use annotationProcessor 5 | } 6 | 7 | dependencies { 8 | 9 | implementation 'com.google.code.gson:gson:2.8.9' 10 | 11 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' 12 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' 13 | 14 | 15 | compileOnly 'org.projectlombok:lombok:1.18.32' 16 | annotationProcessor 'org.projectlombok:lombok:1.18.32' 17 | 18 | testCompileOnly 'org.projectlombok:lombok:1.18.32' 19 | testAnnotationProcessor 'org.projectlombok:lombok:1.18.32' 20 | compileOnly 'org.jetbrains:annotations:23.0.0' 21 | } 22 | 23 | compileJava { 24 | options.release.set 17 25 | } 26 | 27 | 28 | test { 29 | useJUnitPlatform() 30 | } 31 | 32 | java { 33 | withJavadocJar() 34 | withSourcesJar() 35 | } 36 | 37 | publishing { 38 | publications { 39 | mavenJava(MavenPublication) { 40 | artifactId = 'anim-core' 41 | 42 | from components.java 43 | 44 | pom{ 45 | name = "playerAnimatorCORE" 46 | description = "Minecraft animation api" 47 | url = 'https://github.com/KosmX/emotes' 48 | developers { 49 | developer { 50 | id = 'kosmx' 51 | name = 'KosmX' 52 | email = 'kosmx.mc@gmail.com' 53 | } 54 | } 55 | } 56 | } 57 | } 58 | repositories { 59 | 60 | if (project.keysExists) { 61 | maven { 62 | url = 'https://maven.kosmx.dev/' 63 | credentials { 64 | username = project.keys.kosmx_maven_user 65 | password = project.keys.kosmx_maven 66 | } 67 | } 68 | maven { 69 | name = "GitHubPackages" 70 | url = "https://maven.pkg.github.com/kosmx/minecraftPlayerAnimator" 71 | credentials { 72 | username = System.getenv("GITHUB_ACTOR") 73 | password = System.getenv("GITHUB_TOKEN") 74 | } 75 | } 76 | } else { 77 | mavenLocal() 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/AnimUtils.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api; 2 | 3 | import dev.kosmx.playerAnim.api.layered.AnimationStack; 4 | 5 | public abstract class AnimUtils { 6 | 7 | /** 8 | * Get the animation stack for a player entity on the client. 9 | *

10 | * Or you can use {@code ((IPlayer) player).getAnimationStack();} 11 | * 12 | * @param player The ClientPlayer object 13 | * @return The players' animation stack 14 | */ 15 | public static AnimationStack getPlayerAnimLayer(Object player) throws IllegalArgumentException { 16 | if (player instanceof IPlayer) { 17 | return ((IPlayer) player).getAnimationStack(); 18 | } else throw new IllegalArgumentException(player + " is not a player or library mixins failed"); 19 | } 20 | 21 | /** 22 | * Unused, use proper first person library instead 23 | * will be removed after next release (1.1) 24 | */ 25 | @Deprecated 26 | public static boolean disableFirstPersonAnim = true; 27 | } 28 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/IPlayable.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api; 2 | 3 | import dev.kosmx.playerAnim.api.layered.IActualAnimation; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.UUID; 7 | import java.util.function.Supplier; 8 | 9 | /** 10 | * Animation that can be stored in animation registry. 11 | * It has a function to create a player 12 | */ 13 | public interface IPlayable extends Supplier { 14 | 15 | @NotNull 16 | IActualAnimation playAnimation(); 17 | 18 | @NotNull String getName(); 19 | } 20 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/IPlayer.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api; 2 | 3 | import dev.kosmx.playerAnim.api.layered.AnimationStack; 4 | 5 | public interface IPlayer { 6 | AnimationStack getAnimationStack(); 7 | } 8 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/TransformType.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api; 2 | 3 | public enum TransformType { 4 | /** 5 | * The part is shifted in 3d space into the direction 6 | */ 7 | POSITION, 8 | /** 9 | * The part is rotated in 3D space using Euler angles 10 | */ 11 | ROTATION, 12 | /** 13 | * Bend the part, the vector should look like this: {bend planes rotation 0-2π, bend value, not defined} 14 | */ 15 | BEND, 16 | /** 17 | * The part is scaled in 3d space 18 | */ 19 | SCALE 20 | } 21 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/firstPerson/FirstPersonConfiguration.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.firstPerson; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.Accessors; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Accessors(chain = true) 12 | public class FirstPersonConfiguration { 13 | boolean showRightArm = false; 14 | boolean showLeftArm = false; 15 | boolean showRightItem = true; 16 | boolean showLeftItem = true; 17 | boolean showArmor = false; 18 | 19 | public FirstPersonConfiguration(boolean showRightArm, boolean showLeftArm, boolean showRightItem, boolean showLeftItem) { 20 | this.showRightArm = showRightArm; 21 | this.showLeftArm = showLeftArm; 22 | this.showRightItem = showRightItem; 23 | this.showLeftItem = showLeftItem; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/firstPerson/FirstPersonMode.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.firstPerson; 2 | 3 | import lombok.Getter; 4 | import org.jetbrains.annotations.ApiStatus; 5 | 6 | public enum FirstPersonMode { 7 | 8 | /** 9 | * The animation does not decide first person mode, this way, the animation will be transparent in first person mode. 10 | */ 11 | NONE(false), 12 | /** 13 | * Use the vanilla renderer, most of the time broken, if you use this, please check your animation 14 | */ 15 | VANILLA(true), 16 | 17 | /** 18 | * Use the 3rd person player model (only arms/items/shoulder armor) to render accurate first-person perspective. 19 | * Note that armor rendering is disabled in the default FirstPersonConfiguration. {@link FirstPersonConfiguration#showShoulder} 20 | */ 21 | THIRD_PERSON_MODEL(true), 22 | 23 | /** 24 | * First person animation is DISABLED, vanilla idle will be active. 25 | */ 26 | DISABLED(false), 27 | 28 | ; 29 | @Getter 30 | private final boolean enabled; 31 | 32 | 33 | FirstPersonMode(boolean enabled) { 34 | this.enabled = enabled; 35 | } 36 | 37 | 38 | 39 | private static final ThreadLocal firstPersonPass = ThreadLocal.withInitial(() -> false); 40 | 41 | 42 | /** 43 | * @return is the current render pass a first person pass 44 | */ 45 | public static boolean isFirstPersonPass() { 46 | return firstPersonPass.get(); 47 | } 48 | 49 | @ApiStatus.Internal 50 | public static void setFirstPersonPass(boolean newValue) { 51 | firstPersonPass.set(newValue); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/AnimationContainer.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered; 2 | 3 | import dev.kosmx.playerAnim.api.TransformType; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 5 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 6 | import dev.kosmx.playerAnim.core.util.Vec3f; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | /** 11 | * A container to make swapping animation object easier 12 | * It will clone the behaviour of the held animation 13 | *

14 | * you can put endless AnimationContainer into each other 15 | * @param Nullable animation 16 | */ 17 | public class AnimationContainer implements IAnimation { 18 | @Nullable 19 | protected T anim; 20 | 21 | public AnimationContainer(@Nullable T anim) { 22 | this.anim = anim; 23 | } 24 | 25 | public AnimationContainer() { 26 | this.anim = null; 27 | } 28 | 29 | public void setAnim(@Nullable T newAnim) { 30 | this.anim = newAnim; 31 | } 32 | 33 | public @Nullable T getAnim() { 34 | return this.anim; 35 | } 36 | 37 | @Override 38 | public boolean isActive() { 39 | return anim != null && anim.isActive(); 40 | } 41 | 42 | @Override 43 | public void tick() { 44 | if (anim != null) anim.tick(); 45 | } 46 | 47 | @Override 48 | public @NotNull Vec3f get3DTransform(@NotNull String modelName, @NotNull TransformType type, float tickDelta, @NotNull Vec3f value0) { 49 | return anim == null ? value0 : anim.get3DTransform(modelName, type, tickDelta, value0); 50 | } 51 | 52 | @Override 53 | public void setupAnim(float tickDelta) { 54 | if (this.anim != null) this.anim.setupAnim(tickDelta); 55 | } 56 | 57 | @Override 58 | public @NotNull FirstPersonMode getFirstPersonMode(float tickDelta) { 59 | return anim != null ? anim.getFirstPersonMode(tickDelta) : FirstPersonMode.NONE; 60 | } 61 | 62 | // Override candidate 63 | @Override 64 | public @NotNull FirstPersonConfiguration getFirstPersonConfiguration(float tickDelta) { 65 | return anim != null ? anim.getFirstPersonConfiguration(tickDelta) : IAnimation.super.getFirstPersonConfiguration(tickDelta); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/AnimationStack.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered; 2 | 3 | import dev.kosmx.playerAnim.api.TransformType; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 5 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 6 | import dev.kosmx.playerAnim.core.util.Pair; 7 | import dev.kosmx.playerAnim.core.util.Vec3f; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.ArrayList; 11 | 12 | /** 13 | * Player animation stack, can contain multiple active or passive layers, will always be evaluated from the lowest index. 14 | * Highest index = it can override everything else 15 | */ 16 | public class AnimationStack implements IAnimation { 17 | 18 | private final ArrayList> layers = new ArrayList<>(); 19 | 20 | /** 21 | * 22 | * @return true if exists level what is active. 23 | */ 24 | @Override 25 | public boolean isActive() { 26 | for (Pair layer : layers) { 27 | if (layer.getRight().isActive()) return true; 28 | } 29 | return false; 30 | } 31 | 32 | @Override 33 | public void tick() { 34 | for (Pair layer : layers) { 35 | if (layer.getRight().isActive()) { 36 | layer.getRight().tick(); 37 | } 38 | } 39 | } 40 | 41 | @Override 42 | public @NotNull Vec3f get3DTransform(@NotNull String modelName, @NotNull TransformType type, float tickDelta, @NotNull Vec3f value0) { 43 | for (Pair layer : layers) { 44 | if (layer.getRight().isActive() && (!FirstPersonMode.isFirstPersonPass() || layer.getRight().getFirstPersonMode(tickDelta).isEnabled())) { 45 | value0 = layer.getRight().get3DTransform(modelName, type, tickDelta, value0); 46 | } 47 | } 48 | return value0; 49 | } 50 | 51 | @Override 52 | public void setupAnim(float tickDelta) { 53 | for (Pair layer : layers) { 54 | layer.getRight().setupAnim(tickDelta); 55 | } 56 | } 57 | 58 | 59 | /** 60 | * Add an animation layer. 61 | * If there are multiple with the same priority, the one, added first will have larger priority 62 | * @param priority priority 63 | * @param layer animation layer 64 | * note: Same priority entries logic is subject to change 65 | */ 66 | public void addAnimLayer(int priority, IAnimation layer) { 67 | int search = 0; 68 | //Insert the layer into the correct slot 69 | while (layers.size() > search && layers.get(search).getLeft() < priority) { 70 | search++; 71 | } 72 | layers.add(search, new Pair<>(priority, layer)); 73 | } 74 | 75 | /** 76 | * Remove an animation layer 77 | * @param layer needle 78 | * @return true if any elements were removed. 79 | */ 80 | public boolean removeLayer(IAnimation layer) { 81 | return layers.removeIf(integerIAnimationPair -> integerIAnimationPair.getRight() == layer); 82 | } 83 | 84 | /** 85 | * Remove EVERY layer with priority 86 | * @param layerLevel search and destroy 87 | * @return true if any elements were removed. 88 | */ 89 | public boolean removeLayer(int layerLevel) { 90 | return layers.removeIf(integerIAnimationPair -> integerIAnimationPair.getLeft() == layerLevel); 91 | } 92 | 93 | @Override 94 | public @NotNull FirstPersonMode getFirstPersonMode(float tickDelta) { 95 | for (int i = layers.size(); i > 0;) { 96 | Pair layer = layers.get(--i); 97 | if (layer.getRight().isActive()) { // layer.right.requestFirstPersonMode(tickDelta).takeIf{ it != NONE }?.let{ return@requestFirstPersonMode it } 98 | FirstPersonMode mode = layer.getRight().getFirstPersonMode(tickDelta); 99 | if (mode != FirstPersonMode.NONE) return mode; 100 | } 101 | } 102 | return FirstPersonMode.NONE; 103 | } 104 | 105 | @Override 106 | public @NotNull FirstPersonConfiguration getFirstPersonConfiguration(float tickDelta) { 107 | for (int i = layers.size(); i > 0;) { 108 | Pair layer = layers.get(--i); 109 | if (layer.getRight().isActive()) { // layer.right.requestFirstPersonMode(tickDelta).takeIf{ it != NONE }?.let{ return@requestFirstPersonMode it } 110 | FirstPersonMode mode = layer.getRight().getFirstPersonMode(tickDelta); 111 | if (mode != FirstPersonMode.NONE) return layer.getRight().getFirstPersonConfiguration(tickDelta); 112 | } 113 | } 114 | return IAnimation.super.getFirstPersonConfiguration(tickDelta); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/IActualAnimation.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered; 2 | 3 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * interface for some setters on animation players 9 | */ 10 | public interface IActualAnimation> extends IAnimation { 11 | 12 | /** 13 | * @return this for chaining (fluent) 14 | */ 15 | @NotNull 16 | T setFirstPersonMode(@NotNull FirstPersonMode firstPersonMode); 17 | 18 | 19 | @NotNull 20 | T setFirstPersonConfiguration(@NotNull FirstPersonConfiguration firstPersonConfiguration); 21 | } 22 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/IAnimation.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered; 2 | 3 | 4 | import dev.kosmx.playerAnim.api.TransformType; 5 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 6 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 7 | import dev.kosmx.playerAnim.core.util.Vec3f; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | /** 11 | * An entry in {@link AnimationStack}, used to get the animated parts current transform 12 | */ 13 | public interface IAnimation { 14 | 15 | /** 16 | * Animation tick, on lag free client 20 [tick/sec] 17 | * You can get the animations time from other places, but it will be invoked when the animation is ACTIVE 18 | */ 19 | default void tick(){} 20 | 21 | /** 22 | * Is the animation currently active. 23 | * Tick will only be invoked when ACTIVE 24 | * @return active 25 | */ 26 | boolean isActive(); 27 | 28 | /** 29 | * Get the transformed value to a model part, transform type. 30 | * @param modelName The questionable model part 31 | * @param type Transform type 32 | * @param tickDelta Time since the last tick. 0-1 33 | * @param value0 The value before the transform. For identity transform return with it. 34 | * @return The new transform value 35 | */ 36 | @NotNull Vec3f get3DTransform(@NotNull String modelName, @NotNull TransformType type, float tickDelta, @NotNull Vec3f value0); 37 | 38 | /** 39 | * Called before rendering a character 40 | * @param tickDelta Time since the last tick. 0-1 41 | */ 42 | void setupAnim(float tickDelta); 43 | 44 | /** 45 | * Active animation can request first person render mode. 46 | * @param tickDelta current tickDelta 47 | * @return {@link FirstPersonMode} 48 | */ 49 | @NotNull 50 | default FirstPersonMode getFirstPersonMode(float tickDelta) { 51 | return FirstPersonMode.NONE; 52 | } 53 | 54 | /** 55 | * @param tickDelta 56 | * @return current first person configuration, only requested when playing this animation 57 | */ 58 | @NotNull 59 | default FirstPersonConfiguration getFirstPersonConfiguration(float tickDelta) { 60 | return new FirstPersonConfiguration(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/PlayerAnimationFrame.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered; 2 | 3 | import dev.kosmx.playerAnim.api.TransformType; 4 | import dev.kosmx.playerAnim.core.util.Pair; 5 | import dev.kosmx.playerAnim.core.util.Vec3f; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Mixin it into a player, add to its Animation stack, 13 | * and override its tick, 14 | *

15 | * It is a representation of your pose on the frame. 16 | * Override {@link IAnimation#setupAnim(float)} and set the pose there. 17 | */ 18 | public abstract class PlayerAnimationFrame implements IAnimation { 19 | 20 | protected PlayerPart head = new PlayerPart(); 21 | protected PlayerPart body = new PlayerPart(); 22 | protected PlayerPart rightArm = new PlayerPart(); 23 | protected PlayerPart leftArm = new PlayerPart(); 24 | protected PlayerPart rightLeg = new PlayerPart(); 25 | protected PlayerPart leftLeg = new PlayerPart(); 26 | protected PlayerPart rightItem = new PlayerPart(); 27 | protected PlayerPart leftItem = new PlayerPart(); 28 | 29 | HashMap parts = new HashMap<>(); 30 | 31 | public PlayerAnimationFrame() { 32 | parts.put("head", head); 33 | parts.put("body", body); 34 | parts.put("rightArm", rightArm); 35 | parts.put("leftArm", leftArm); 36 | parts.put("rightLeg", rightLeg); 37 | parts.put("leftLeg", leftLeg); 38 | parts.put("rightItem", rightItem); 39 | parts.put("leftItem", leftItem); 40 | } 41 | 42 | 43 | @Override 44 | public void tick() { 45 | IAnimation.super.tick(); 46 | } 47 | 48 | @Override 49 | public boolean isActive() { 50 | for (Map.Entry entry: parts.entrySet()) { 51 | PlayerPart part = entry.getValue(); 52 | if (part.bend != null || part.pos != null || part.rot != null || part.scale != null) return true; 53 | } 54 | return false; 55 | } 56 | 57 | /** 58 | * Reset every part, those parts won't influence the animation 59 | * Don't use it if you don't want to set every part in every frame 60 | */ 61 | public void resetPose() { 62 | for (Map.Entry entry: parts.entrySet()) { 63 | entry.getValue().setNull(); 64 | } 65 | } 66 | 67 | 68 | @Override 69 | public @NotNull Vec3f get3DTransform(@NotNull String modelName, @NotNull TransformType type, float tickDelta, @NotNull Vec3f value0) { 70 | PlayerPart part = parts.get(modelName); 71 | if (part == null) return value0; 72 | switch (type) { 73 | case POSITION: 74 | return part.pos == null ? value0 : part.pos; 75 | case ROTATION: 76 | return part.rot == null ? value0 : part.rot; 77 | case SCALE: 78 | return part.scale == null ? value0 : part.scale; 79 | case BEND: 80 | return part.bend == null ? value0 : new Vec3f(part.bend.getLeft(), part.bend.getRight(), 0f); 81 | default: 82 | return value0; 83 | } 84 | } 85 | 86 | public static class PlayerPart { 87 | public Vec3f pos; 88 | public Vec3f scale; 89 | public Vec3f rot; 90 | public Pair bend; 91 | 92 | protected void setNull() { 93 | pos = scale = rot = null; 94 | bend = null; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/modifier/AbstractFadeModifier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered.modifier; 2 | 3 | import dev.kosmx.playerAnim.api.TransformType; 4 | import dev.kosmx.playerAnim.api.layered.IAnimation; 5 | import dev.kosmx.playerAnim.core.util.Ease; 6 | import dev.kosmx.playerAnim.core.util.Vec3f; 7 | import lombok.Setter; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | /** 12 | * Use with ModifierLayer. 13 | * Set an animation as a fade from. 14 | * length in ticks 15 | */ 16 | public abstract class AbstractFadeModifier extends AbstractModifier { 17 | 18 | protected int time = 0; 19 | protected int length; 20 | 21 | /** 22 | * Animation to move from, null if transparent (easing in) 23 | * use setBeginAnimation(IAnimation); //generated by Lombok plugin 24 | */ 25 | @Nullable 26 | @Setter 27 | protected IAnimation beginAnimation; 28 | 29 | protected AbstractFadeModifier(int length) { 30 | this.length = length; 31 | } 32 | 33 | @Override 34 | public boolean isActive() { 35 | return super.isActive() || beginAnimation != null && beginAnimation.isActive(); 36 | } 37 | 38 | @Override 39 | public boolean canRemove() { 40 | return this.length <= time; 41 | } 42 | 43 | @Override 44 | public void setupAnim(float tickDelta) { 45 | super.setupAnim(tickDelta); 46 | if (beginAnimation != null) beginAnimation.setupAnim(tickDelta); 47 | } 48 | 49 | @Override 50 | public void tick() { 51 | super.tick(); 52 | if (beginAnimation != null) beginAnimation.tick(); 53 | this.time++; 54 | } 55 | 56 | @Override 57 | public @NotNull Vec3f get3DTransform(@NotNull String modelName, @NotNull TransformType type, float tickDelta, @NotNull Vec3f value0) { 58 | if (calculateProgress(tickDelta) > 1) { 59 | return super.get3DTransform(modelName, type, tickDelta, value0); 60 | } 61 | Vec3f animatedVec = super.get3DTransform(modelName, type, tickDelta, value0); 62 | float a = getAlpha(modelName, type, calculateProgress(tickDelta)); 63 | Vec3f source = beginAnimation != null && beginAnimation.isActive() ? beginAnimation.get3DTransform(modelName, type, tickDelta, value0) : value0; 64 | return animatedVec.scale(a).add(source.scale(1 - a)); //This would look much better in Kotlin... (operator overloading) 65 | } 66 | 67 | protected float calculateProgress(float f) { 68 | float actualTime = time + f; 69 | return actualTime / length; 70 | } 71 | 72 | /** 73 | * Get the alpha at the given progress 74 | * @param modelName modelName if you want to handle parts differently 75 | * @param type Transform type 76 | * @param progress animation progress, float between 0 and 1 77 | * @return alpha, float between 0 and 1, lower value means less visible from animation 78 | */ 79 | protected abstract float getAlpha(String modelName, TransformType type, float progress); 80 | 81 | /** 82 | * Creates a standard fade with some easing in it. 83 | * @param length ease length in ticks 84 | * @param ease ease function from {@link Ease} 85 | * @return fade modifier 86 | */ 87 | public static AbstractFadeModifier standardFadeIn(int length, Ease ease) { 88 | return new AbstractFadeModifier(length) { 89 | @Override 90 | protected float getAlpha(String modelName, TransformType type, float progress) { 91 | return ease.invoke(progress); 92 | } 93 | }; 94 | } 95 | 96 | /** 97 | * Functional constructor for functional folks 98 | * @param length ease length 99 | * @param function ease function 100 | * @return fade 101 | */ 102 | public static AbstractFadeModifier functionalFadeIn(int length, EasingFunction function) { 103 | return new AbstractFadeModifier(length) { 104 | @Override 105 | protected float getAlpha(String modelName, TransformType type, float progress) { 106 | return function.ease(modelName, type, progress); 107 | } 108 | }; 109 | } 110 | 111 | @FunctionalInterface 112 | public interface EasingFunction { 113 | float ease(String modelName, TransformType type, float value); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/modifier/AbstractModifier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered.modifier; 2 | 3 | import dev.kosmx.playerAnim.api.layered.AnimationContainer; 4 | import dev.kosmx.playerAnim.api.layered.IAnimation; 5 | import dev.kosmx.playerAnim.api.layered.ModifierLayer; 6 | import lombok.Setter; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | @SuppressWarnings("rawtypes") 10 | public abstract class AbstractModifier extends AnimationContainer { 11 | 12 | /** 13 | * ModifierLayer, if you want to do something really fancy! 14 | * Shouldn't be null when playing 15 | */ 16 | @Nullable 17 | @Setter 18 | protected ModifierLayer host; 19 | 20 | public AbstractModifier() { 21 | super(null); 22 | } 23 | 24 | /** 25 | * @return modifier can be removed. 26 | */ 27 | public boolean canRemove() { 28 | return false; 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/modifier/FirstPersonModifier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered.modifier; 2 | 3 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | /** 10 | * The {@code FirstPersonModifier} class is responsible for modifying 11 | * the first-person view configuration in a system. It allows enabling or disabling 12 | * specific arms and/or items in the first-person view using predefined configurations. 13 | * 14 | *

This class extends {@code AbstractModifier} and overrides the methods 15 | * {@code getFirstPersonConfiguration} and {@code getFirstPersonMode} to return the current 16 | * configuration and mode defined by the {@code FirstPersonConfigEnum} and {@code FirstPersonMode}, respectively.

17 | * 18 | *

The default configuration is {@code ENABLE_BOTH_ARMS}, which enables 19 | * both arms and items in the first-person view. The default mode is {@code DISABLED}.

20 | */ 21 | @Setter 22 | public class FirstPersonModifier extends AbstractModifier { 23 | /** 24 | * The currently active first-person configuration. This determines 25 | * which arms and items are visible in the first-person view. 26 | * 27 | *

Defaults to {@code ENABLE_BOTH_ARMS}, which displays both arms 28 | * and items in the first-person view.

29 | */ 30 | private FirstPersonConfigEnum currentFirstPersonConfig = FirstPersonConfigEnum.ENABLE_BOTH_ARMS; 31 | 32 | /** 33 | * The currently active first-person mode. This determines whether the 34 | * first-person view is enabled, disabled, or in another predefined mode. 35 | * 36 | *

Defaults to {@code DISABLED}.

37 | */ 38 | private FirstPersonMode currentFirstPersonMode = FirstPersonMode.DISABLED; 39 | 40 | /** 41 | * Retrieves the current first-person configuration. 42 | * 43 | *

This method returns the configuration defined by 44 | * the {@code currentFirstPersonConfig} field, which specifies 45 | * which arms and items are visible in the first-person view.

46 | * 47 | * @param tickDelta 48 | * @return The active {@link FirstPersonConfiguration} based on 49 | * {@code currentFirstPersonConfig}. 50 | */ 51 | @Override 52 | public @NotNull FirstPersonConfiguration getFirstPersonConfiguration(float tickDelta) { 53 | return currentFirstPersonConfig.getFirstPersonConfiguration(); 54 | } 55 | 56 | /** 57 | * Retrieves the current first-person mode. 58 | * 59 | *

This method returns the mode defined by the {@code currentFirstPersonMode} field, 60 | * which specifies whether the first-person view is enabled, disabled, or in another mode.

61 | * 62 | * @param tickDelta 63 | * @return The active {@link FirstPersonMode} based on {@code currentFirstPersonMode}. 64 | */ 65 | @Override 66 | public @NotNull FirstPersonMode getFirstPersonMode(float tickDelta) { 67 | return currentFirstPersonMode; 68 | } 69 | 70 | /** 71 | * Enumeration representing predefined first-person view configurations. 72 | * 73 | *

Each enum constant is associated with a {@link FirstPersonConfiguration} 74 | * object that specifies which arms and items are visible in the first-person view.

75 | */ 76 | @Getter 77 | public enum FirstPersonConfigEnum { 78 | /** 79 | * Enables both arms and both items in the first-person view. 80 | */ 81 | ENABLE_BOTH_ARMS(new FirstPersonConfiguration(true, true, true, true)), 82 | /** 83 | * Disables both arms and both items in the first-person view. 84 | */ 85 | DISABLE_BOTH_ARMS(new FirstPersonConfiguration(false, false, false, false)), 86 | /** 87 | * Enables only the right arm and its associated item in the first-person view. 88 | */ 89 | ONLY_RIGHT_ARM_AND_ITEM(new FirstPersonConfiguration(true, false, true, false)), 90 | /** 91 | * Enables only the left arm and its associated item in the first-person view. 92 | */ 93 | ONLY_LEFT_ARM_AND_ITEM(new FirstPersonConfiguration(false, true, false, true)), 94 | /** 95 | * Enables only the right arm without its associated item in the first-person view. 96 | */ 97 | ONLY_RIGHT_ARM(new FirstPersonConfiguration(true, false, false, false)), 98 | /** 99 | * Enables only the left arm without its associated item in the first-person view. 100 | */ 101 | ONLY_LEFT_ARM(new FirstPersonConfiguration(false, true, false, false)), 102 | /** 103 | * Enables only the right-hand item without showing the arm in the first-person view. 104 | */ 105 | ONLY_RIGHT_ITEM(new FirstPersonConfiguration(false, false, true, false)), 106 | /** 107 | * Enables only the left-hand item without showing the arm in the first-person view. 108 | */ 109 | ONLY_LEFT_ITEM(new FirstPersonConfiguration(false, false, false, true)); 110 | 111 | private final FirstPersonConfiguration firstPersonConfiguration; 112 | 113 | FirstPersonConfigEnum(@NotNull FirstPersonConfiguration firstPersonConfiguration) { 114 | this.firstPersonConfiguration = firstPersonConfiguration; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/modifier/MirrorModifier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered.modifier; 2 | 3 | import dev.kosmx.playerAnim.api.TransformType; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 5 | import dev.kosmx.playerAnim.core.util.Vec3f; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.Collections; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class MirrorModifier extends AbstractModifier { 19 | 20 | public static final Map mirrorMap; 21 | 22 | /** 23 | * Enable the modifier 24 | */ 25 | @Getter 26 | @Setter 27 | private boolean enabled = true; 28 | 29 | @Override 30 | public @NotNull Vec3f get3DTransform(@NotNull String modelName, @NotNull TransformType type, float tickDelta, @NotNull Vec3f value0) { 31 | if (!isEnabled()) return super.get3DTransform(modelName, type, tickDelta, value0); 32 | 33 | if (mirrorMap.containsKey(modelName)) modelName = mirrorMap.get(modelName); 34 | value0 = transformVector(value0, type); 35 | 36 | Vec3f vec3f = super.get3DTransform(modelName, type, tickDelta, value0); 37 | return transformVector(vec3f, type); 38 | } 39 | 40 | // Override candidate 41 | @Override 42 | public @NotNull FirstPersonConfiguration getFirstPersonConfiguration(float tickDelta) { 43 | FirstPersonConfiguration configuration = super.getFirstPersonConfiguration(tickDelta); 44 | if (isEnabled()) { 45 | return new FirstPersonConfiguration() 46 | .setShowLeftArm(configuration.isShowRightArm()) 47 | .setShowRightArm(configuration.isShowLeftArm()) 48 | .setShowLeftItem(configuration.isShowRightItem()) 49 | .setShowRightItem(configuration.isShowLeftItem()); 50 | } else return configuration; 51 | } 52 | 53 | protected Vec3f transformVector(Vec3f value0, TransformType type) { 54 | switch (type) { 55 | case POSITION: 56 | return new Vec3f(-value0.getX(), value0.getY(), value0.getZ()); 57 | case ROTATION: 58 | return new Vec3f(value0.getX(), -value0.getY(), -value0.getZ()); 59 | case BEND: 60 | return new Vec3f(value0.getX(), -value0.getY(), value0.getZ()); 61 | default: 62 | return value0; //why?! 63 | } 64 | } 65 | 66 | static { 67 | HashMap partMap = new HashMap<>(); 68 | partMap.put("leftArm", "rightArm"); 69 | partMap.put("leftLeg", "rightLeg"); 70 | partMap.put("leftItem", "rightItem"); 71 | partMap.put("rightArm", "leftArm"); 72 | partMap.put("rightLeg", "leftLeg"); 73 | partMap.put("rightItem", "leftItem"); 74 | mirrorMap = Collections.unmodifiableMap(partMap); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/layered/modifier/SpeedModifier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api.layered.modifier; 2 | 3 | import dev.kosmx.playerAnim.api.TransformType; 4 | import dev.kosmx.playerAnim.core.util.Vec3f; 5 | import lombok.NoArgsConstructor; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * Modifies the animation speed. 10 | * speed = 2 means twice the speed, the animation will take half as long 11 | * length = 1/speed 12 | */ 13 | @NoArgsConstructor 14 | public class SpeedModifier extends AbstractModifier { 15 | public float speed = 1; 16 | 17 | private float delta = 0; 18 | 19 | private float shiftedDelta = 0; 20 | 21 | 22 | public SpeedModifier(float speed) { 23 | if (!Float.isFinite(speed)) throw new IllegalArgumentException("Speed must be a finite number"); 24 | this.speed = speed; 25 | } 26 | 27 | @Override 28 | public void tick() { 29 | float delta = 1f - this.delta; 30 | this.delta = 0; 31 | step(delta); 32 | } 33 | 34 | @Override 35 | public void setupAnim(float tickDelta) { 36 | float delta = tickDelta - this.delta; //this should stay positive 37 | this.delta = tickDelta; 38 | step(delta); 39 | } 40 | 41 | protected void step(float delta) { 42 | delta *= speed; 43 | delta += shiftedDelta; 44 | while (delta > 1) { 45 | delta -= 1; 46 | super.tick(); 47 | } 48 | super.setupAnim(delta); 49 | this.shiftedDelta = delta; 50 | } 51 | 52 | @Override 53 | public @NotNull Vec3f get3DTransform(@NotNull String modelName, @NotNull TransformType type, float tickDelta, @NotNull Vec3f value0) { 54 | return super.get3DTransform(modelName, type, shiftedDelta, value0); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/api/package-info.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.api; 2 | /* 3 | * This will be moved to an independent library, 4 | * But classes here should stay close to what they already are. 5 | */ -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/AnimationFormat.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Where is the emote from 8 | * use dev.kosmx.playerAnim.minecraftApi.codec, AnimationCodecs class for deserializing instead. 9 | *

10 | * This package may be removed in the future 11 | */ 12 | @Deprecated 13 | public enum AnimationFormat { 14 | JSON_EMOTECRAFT("json"), 15 | JSON_MC_ANIM("json"), 16 | QUARK("emote"), 17 | BINARY("emotecraft"), 18 | SERVER(null), 19 | UNKNOWN(null); 20 | 21 | private static final Map FORMATS; 22 | 23 | static { 24 | AnimationFormat[] formatsValues = values(); 25 | 26 | FORMATS = new HashMap<>(formatsValues.length); 27 | 28 | for (AnimationFormat format : formatsValues) { 29 | if (format.extension != null) 30 | FORMATS.putIfAbsent(format.extension, format); 31 | } 32 | } 33 | 34 | @Deprecated(forRemoval = true) 35 | public static AnimationFormat byFileName(String fileName) { 36 | if (fileName == null || fileName.isEmpty()) 37 | return AnimationFormat.UNKNOWN; 38 | 39 | int i = fileName.lastIndexOf('.'); 40 | if (i > 0) { 41 | fileName = fileName.substring(i + 1); 42 | } 43 | 44 | return byExtension(fileName); 45 | } 46 | 47 | /** 48 | * use AnimationCodecs.deserialize() instead 49 | */ 50 | @Deprecated(forRemoval = true) 51 | public static AnimationFormat byExtension(String extension) { 52 | if (extension == null || extension.isEmpty()) 53 | return AnimationFormat.UNKNOWN; 54 | 55 | return FORMATS.getOrDefault(extension.toLowerCase(), AnimationFormat.UNKNOWN); 56 | } 57 | 58 | private final String extension; 59 | 60 | @Deprecated(forRemoval = true) 61 | AnimationFormat(String extension) { 62 | this.extension = extension; 63 | } 64 | 65 | @Deprecated(forRemoval = true) 66 | public String getExtension() { 67 | return extension; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/gson/AnimationSerializing.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.gson; 2 | 3 | import dev.kosmx.playerAnim.core.data.KeyframeAnimation; 4 | 5 | import java.io.*; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.List; 8 | 9 | /** 10 | * (De)Serialize {@link KeyframeAnimation} 11 | * Can load emotecraft and basic gecko-lib animations 12 | * always create emotecraft animation 13 | *

14 | * Use {@link AnimationSerializing#deserializeAnimation(Reader)} to deserialize
15 | * or {@link AnimationSerializing#serializeAnimation(KeyframeAnimation)} to serialize. 16 | * @deprecated use AnimationCodecs instead 17 | */ 18 | @Deprecated(forRemoval = true) 19 | public class AnimationSerializing { 20 | 21 | /** 22 | * Deserialize animations from Emotecraft or GeckoLib InputStreamReader 23 | * AnimatinCodecs#serialize() 24 | * @param stream inputStreamReader 25 | * @return List of animations 26 | */ 27 | @Deprecated(forRemoval = true) 28 | public static List deserializeAnimation(Reader stream) { 29 | return AnimationJson.GSON.fromJson(stream, AnimationJson.getListedTypeToken()); 30 | } 31 | 32 | /** 33 | * Deserialize animations from Emotecraft or GeckoLib InputStream 34 | * use AnimatinCodecs#serialize() 35 | * @param stream inputStream 36 | * @return List of animations 37 | */ 38 | @Deprecated(forRemoval = true) 39 | public static List deserializeAnimation(InputStream stream) throws IOException { 40 | try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) { 41 | return deserializeAnimation(reader); 42 | } 43 | } 44 | 45 | //Emotecraft binary is an emotecraft-specific thing. 46 | 47 | /** 48 | * Serialize animation into Emotecraft JSON format 49 | * @param animation animation 50 | * @return string 51 | */ 52 | public static String serializeAnimation(KeyframeAnimation animation) { 53 | return AnimationJson.GSON.toJson(animation, KeyframeAnimation.class); 54 | } 55 | 56 | /** 57 | * Write the animation to output stream 58 | * @param animation animation 59 | * @param writer writer 60 | * @return writer 61 | * @throws IOException writer errors 62 | */ 63 | public static Writer writeAnimation(KeyframeAnimation animation, Writer writer) throws IOException { 64 | writer.write(serializeAnimation(animation)); 65 | return writer; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/NBS.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs; 2 | 3 | import dev.kosmx.playerAnim.core.data.opennbs.format.CustomInstrument; 4 | import dev.kosmx.playerAnim.core.data.opennbs.format.Header; 5 | import dev.kosmx.playerAnim.core.data.opennbs.format.Layer; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class NBS { 11 | public final Header header; 12 | final ArrayList layers; 13 | int length; 14 | byte customInstrumentCount; 15 | final ArrayList customInstruments; 16 | 17 | 18 | 19 | public NBS(Header header, ArrayList layers, ArrayList customInstruments) { 20 | if(header.Layer_count != layers.size()){ 21 | if(layers.size() == 0){ 22 | for(int i = 0; i < header.Layer_count; i++){ 23 | layers.add(new Layer()); 24 | } 25 | } 26 | else throw new IllegalArgumentException("Layer count have to be same in the header with the layers size"); 27 | } 28 | this.header = header; 29 | this.layers = layers; 30 | this.customInstruments = customInstruments; 31 | } 32 | 33 | public ArrayList getLayers() { 34 | return layers; 35 | } 36 | 37 | List getNotesUntilTick(int tickFrom, int tickTo){ 38 | ArrayList notes = new ArrayList<>(); 39 | for(Layer layer:this.layers){ 40 | if(tickFrom > tickTo){ 41 | notes.addAll(layer.getNotesFrom(tickFrom, this.length)); 42 | notes.addAll(layer.getNotesFrom(header.Loop_on_off() ? header.Loop_start_tick -1 : -1, tickTo)); 43 | } 44 | else { 45 | notes.addAll(layer.getNotesFrom(tickFrom, tickTo)); 46 | } 47 | } 48 | return notes; 49 | } 50 | 51 | public int getLength() { 52 | return length; 53 | } 54 | 55 | public void setLength(int length) { 56 | this.length = (length/(int) (header.Time_signature) + 1)*header.Time_signature; 57 | } 58 | 59 | public static class Builder{ 60 | public Header header = new Header(); 61 | public ArrayList layers = new ArrayList<>(); 62 | public ArrayList customInstruments; 63 | 64 | public NBS build(){ 65 | return new NBS(header, layers, customInstruments); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/NBSFileUtils.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs; 2 | 3 | import dev.kosmx.playerAnim.core.data.opennbs.format.Header; 4 | import dev.kosmx.playerAnim.core.data.opennbs.format.Layer; 5 | 6 | import java.io.DataInputStream; 7 | import java.io.IOException; 8 | import java.nio.charset.StandardCharsets; 9 | 10 | /** 11 | * R/W nbs files 12 | */ 13 | 14 | public class NBSFileUtils { 15 | 16 | final static int maxWorkingVersion = 5; 17 | 18 | //some methods are from EmotecraftCommon. these have to be separated if I'll make a lib from this!!! 19 | public static NBS read(DataInputStream stream) throws IOException { 20 | if(readShort(stream) != 0){ 21 | throw new IOException("Can't read old NBS format."); 22 | } 23 | NBS.Builder songBuilder = new NBS.Builder(); 24 | Header header = songBuilder.header; 25 | header.NBS_version = stream.readByte(); 26 | int version = header.NBS_version; //just for faster coning 27 | if(version > maxWorkingVersion) throw new IOException("Can't read newer NBS format than " + maxWorkingVersion + "."); //I'll probably run into this 28 | header.Vanilla_instrument_count = stream.readByte(); 29 | if(version >= 3)header.Song_length = readShort(stream); 30 | header.Layer_count = readShort(stream); 31 | header.Song_name = readString(stream); 32 | header.Song_author = readString(stream); 33 | header.Song_original_author = readString(stream); 34 | header.Song_description = readString(stream); 35 | header.Song_tempo = readShort(stream); 36 | header.Auto_saving = stream.readByte(); 37 | header.Auto_saving_duration = stream.readByte(); 38 | header.Time_signature = stream.readByte(); 39 | header.Minutes_spent = readInt(stream); 40 | header.Left_clicks = readInt(stream); 41 | header.Right_clicks = readInt(stream); 42 | header.Note_blocks_added = readInt(stream); 43 | header.Note_blocks_removed = readInt(stream); 44 | header.MIDI_Schematic_file_name = readString(stream); 45 | if(version >= 4){ //looping 46 | header.Loop_on_off = stream.readByte(); 47 | header.Max_loop_count = stream.readByte(); 48 | header.Loop_start_tick = readShort(stream); 49 | } 50 | 51 | //Part 2 52 | 53 | for(int i = 0; i < header.Layer_count; i++){ 54 | songBuilder.layers.add(new Layer()); //Precreate layers for later use :) 55 | } 56 | 57 | int maxLength = 0; 58 | 59 | int tick = -1; 60 | for(short jumpToTheNextTick = readShort(stream); jumpToTheNextTick != 0; jumpToTheNextTick = readShort(stream)){ 61 | tick += jumpToTheNextTick; 62 | for(int layer = -1, jumpToTheNextLayer = readShort(stream); jumpToTheNextLayer != 0; jumpToTheNextLayer = readShort(stream)){ 63 | layer += jumpToTheNextLayer; 64 | Layer.Note note = songBuilder.layers.get(layer).addNote(tick); 65 | if(note == null){ 66 | throw new IOException("Creeper, Aww man"); //sry for putting this into an MC song stuff 67 | } 68 | note.instrument = stream.readByte(); 69 | note.key = stream.readByte(); 70 | if(version >= 4){ 71 | note.velocity = stream.readByte(); 72 | note.panning = stream.readByte(); 73 | note.pitch = readShort(stream); 74 | } 75 | maxLength = Math.max(maxLength, tick); 76 | } 77 | } 78 | //Part 3 : 79 | 80 | for (Layer layer: songBuilder.layers){ 81 | layer.name = readString(stream); 82 | if(version >= 4)layer.lock = stream.readByte(); 83 | layer.volume = stream.readByte(); 84 | layer.stereo = stream.readByte(); 85 | } 86 | if(stream.readByte() != 0){ 87 | throw new IOException("NBSUtils can not handle custom instruments (yet)"); 88 | } 89 | 90 | NBS song = songBuilder.build(); 91 | song.setLength(maxLength); 92 | return song; 93 | } 94 | 95 | static String readString(DataInputStream stream) throws IOException { 96 | int len = readInt(stream); 97 | if(len < 0){ 98 | throw new IOException("The string's length is less than zero. You wanna me to read it backwards???"); 99 | } 100 | byte[] bytes = new byte[len]; 101 | if(stream.read(bytes) != len){ 102 | throw new IOException("Invalid string"); 103 | } 104 | return new String(bytes, StandardCharsets.UTF_8); //:D 105 | } 106 | 107 | static int readInt(DataInputStream stream) throws IOException{ 108 | int i = 0; 109 | for(int n = 0; n<4; n++){ 110 | i |= (stream.read() << (8*n)); 111 | } 112 | return i; 113 | } 114 | static short readShort(DataInputStream stream) throws IOException{ 115 | short i = 0; 116 | for(int n = 0; n<2; n++){ 117 | i |= (stream.read() << (8*n)); 118 | } 119 | return i; 120 | } 121 | 122 | 123 | } 124 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/SoundPlayer.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs; 2 | 3 | import dev.kosmx.playerAnim.core.data.opennbs.format.Layer; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Plays {@link NBS} objects 11 | * It will need to be played on ClientSide 12 | */ 13 | public class SoundPlayer { 14 | final NBS song; 15 | final float songPerMCTick; 16 | int mcTick = 0; //MC tick (20 tps) not the song's custom 17 | int soundTick = -1; //MCTick * (song tickspeed/MCTickspeed) 18 | boolean isPlaying = true; //set false, when stopped. Newer set to true after stopped, instead create a new player. 19 | //To play the song in some interesting ways 20 | final Consumer playSound; 21 | 22 | public SoundPlayer(NBS song, Consumer soundPlayer, int tickToBegin) { 23 | this.song = song; 24 | this.songPerMCTick = ((float) song.header.Song_tempo) / 2000f; 25 | this.playSound = soundPlayer; 26 | this.mcTick = tickToBegin; 27 | } 28 | 29 | public void tick(){ 30 | int newSongTick = (int) (mcTick++ * songPerMCTick); 31 | if(newSongTick > song.header.Loop_start_tick && song.header.Loop_on_off()){ 32 | if(song.header.Max_loop_count != 0){ 33 | int loop = song.header.Max_loop_count & 0xFF; //turn it into an unsigned byte 34 | if((newSongTick - song.header.Loop_start_tick) / (song.getLength() - song.header.Loop_start_tick) > loop){ 35 | this.stop(); 36 | return; 37 | } 38 | } 39 | newSongTick = (newSongTick - song.header.Loop_start_tick) % (song.getLength() - song.header.Loop_start_tick) + song.header.Loop_start_tick; 40 | } 41 | else if(newSongTick > song.getLength()){ 42 | this.stop(); 43 | return; 44 | } 45 | if(newSongTick == this.soundTick){ 46 | return; //Nothing has happened, can continue; 47 | } 48 | List notesToPlay = this.song.getNotesUntilTick(this.soundTick, newSongTick); 49 | //MinecraftClient.getInstance().world.playSoundFromEntity(); 50 | notesToPlay.forEach(this.playSound); 51 | 52 | this.soundTick = newSongTick; 53 | 54 | } 55 | 56 | public void stop(){ 57 | this.isPlaying = false; 58 | } 59 | 60 | //My favorite one :D 61 | public static boolean isPlayingSong(@Nullable SoundPlayer player){ 62 | return player != null && player.isPlaying; 63 | } 64 | 65 | //TODO put it somewhere else where MC code is available and DELETE ME 66 | /* 67 | public static Instrument getInstrumentFromCode(byte code){ 68 | 69 | //That is more efficient than a switch case... 70 | Instrument[] instruments = {Instrument.HARP, Instrument.BASS, Instrument.BASEDRUM, Instrument.SNARE, Instrument.HAT, 71 | Instrument.GUITAR, Instrument.FLUTE, Instrument.BELL, Instrument.CHIME, Instrument.XYLOPHONE,Instrument.IRON_XYLOPHONE, 72 | Instrument.COW_BELL, Instrument.DIDGERIDOO, Instrument.BIT, Instrument.BANJO, Instrument.PLING}; 73 | 74 | if(code >= 0 && code < instruments.length){ 75 | return instruments[code]; 76 | } 77 | return Instrument.HARP; //I don't want to crash here 78 | } 79 | 80 | */ 81 | } 82 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/format/CustomInstrument.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs.format; 2 | 3 | /** 4 | * IDK what to do with it... 5 | */ 6 | public interface CustomInstrument { 7 | void setName(String name); 8 | void setToSongFile(String path); 9 | void setsetSoundPitch(byte pitch); 10 | void setPressKey(boolean pressKey); 11 | } 12 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/format/Header.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs.format; 2 | 3 | /** 4 | * Sound format header. contains Name, click amount, length, everything from header 5 | * field names and descriptions are form https://opennbs.org/nbs 6 | * 7 | * Deprecated fields are not used by OPEN NOTE BLOCK STUDIO 8 | */ 9 | public class Header { 10 | /** 11 | * The version of the new NBS format. 12 | */ 13 | public byte NBS_version; 14 | /** 15 | * Amount of default instruments when the song was saved. This is needed to determine at what index custom instruments start. 16 | */ 17 | public byte Vanilla_instrument_count; 18 | /** 19 | * The length of the song, measured in ticks. Divide this by the tempo to get the length of the song in seconds. Note Block Studio doesn't really care about this value, the song size is calculated in the second part. 20 | * (Note: this was re-added in NBS version 3) 21 | */ 22 | @Deprecated 23 | public short Song_length; 24 | /** 25 | * The last layer with at least one note block in it, or the last layer that has had its name, volume or stereo changed. 26 | */ 27 | public short Layer_count; 28 | /** 29 | * The name of the song. 30 | */ 31 | public String Song_name; 32 | /** 33 | * The author of the song. 34 | */ 35 | public String Song_author; 36 | /** 37 | * The original author of the song. 38 | */ 39 | public String Song_original_author; 40 | /** 41 | * The description of the song. 42 | */ 43 | public String Song_description; 44 | /** 45 | * The tempo of the song multiplied by 100 (for example, 1225 instead of 12.25). Measured in ticks per second. 46 | */ 47 | public short Song_tempo; 48 | /** 49 | * Whether auto-saving has been enabled (0 or 1). As of NBS version 4 this value is still saved to the file, but no longer used in the program. 50 | */ 51 | @Deprecated 52 | public byte Auto_saving; 53 | public boolean Auto_saving(){ 54 | return this.Auto_saving != 0; 55 | } 56 | /** 57 | * The amount of minutes between each auto-save (if it has been enabled) (1-60). As of NBS version 4 this value is still saved to the file, but no longer used in the program. 58 | */ 59 | @Deprecated 60 | public byte Auto_saving_duration; 61 | /** 62 | * The time signature of the song. If this is 3, then the signature is 3/4. Default is 4. This value ranges from 2-8. 63 | */ 64 | public byte Time_signature; 65 | /** 66 | * Amount of minutes spent on the project. 67 | */ 68 | public int Minutes_spent; 69 | /** 70 | * Amount of times the user has left-clicked. 71 | */ 72 | public int Left_clicks; 73 | /** 74 | * Amount of times the user has right-clicked. 75 | */ 76 | public int Right_clicks; 77 | /** 78 | * Amount of times the user has added a note block. 79 | */ 80 | public int Note_blocks_added; 81 | /** 82 | * The amount of times the user have removed a note block. 83 | */ 84 | public int Note_blocks_removed; 85 | /** 86 | * If the song has been imported from a .mid or .schematic file, that file name is stored here (only the name of the file, not the path). 87 | */ 88 | public String MIDI_Schematic_file_name; 89 | /** 90 | * Whether looping is on or off. (0 = off, 1 = on) 91 | */ 92 | public byte Loop_on_off; 93 | public boolean Loop_on_off(){ 94 | return this.Loop_on_off != 0; 95 | } 96 | /** 97 | * 0 = infinite. Other values mean the amount of times the song loops. 98 | */ 99 | public byte Max_loop_count; 100 | /** 101 | * Determines which part of the song (in ticks) it loops back to. 102 | */ 103 | public short Loop_start_tick; 104 | 105 | //This is the end of the header 106 | 107 | } 108 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/format/Layer.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs.format; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class Layer { 8 | /** 9 | * The name of the layer. 10 | */ 11 | public String name; 12 | /** 13 | * Whether or not this layer has been marked as locked. 1 = locked. 14 | */ 15 | public byte lock; 16 | public boolean getLock(){ 17 | return this.lock != 0; 18 | } 19 | public void setLock(boolean newValue){ 20 | this.lock = (byte) (newValue ? 1 : 0); 21 | } 22 | /** 23 | * The volume of the layer (percentage). Ranges from 0-100. 24 | */ 25 | public byte volume = 100; 26 | /** 27 | * How much this layer is panned to the left/right. 0 is 2 blocks right, 100 is center, 200 is 2 blocks left. 28 | */ 29 | public byte stereo = 100; 30 | 31 | private int lastUsedTickPos = 0; 32 | 33 | public final ArrayList notes = new ArrayList<>(); 34 | 35 | /** 36 | * search the position of the last keyframe 37 | * INTERNAL 38 | * @param tick when 39 | * @return pos in the array 40 | */ 41 | public int findAtTick(int tick){ 42 | int i = - 1; 43 | if(this.notes.size() > lastUsedTickPos + 1 && this.notes.get(lastUsedTickPos + 1).tick <= tick) i = lastUsedTickPos; 44 | while(this.notes.size() > i + 1 && this.notes.get(i + 1).tick <= tick){ 45 | i++; 46 | } 47 | lastUsedTickPos = i; 48 | return i; 49 | } 50 | 51 | @Nullable 52 | public Note addNote(int tick){ 53 | if(this.getLock()){ 54 | return null; 55 | } 56 | int i = findAtTick(tick); 57 | if(i > 0 && notes.get(i).tick == tick){ 58 | return null; 59 | } 60 | Note note = new Note(tick); 61 | notes.add(i + 1, note); 62 | return note; 63 | } 64 | 65 | public ArrayList getNotesFrom(int fromTick, int toTick) { 66 | ArrayList returnNotes = new ArrayList<>(); 67 | int posAtBegin = findAtTick(fromTick); 68 | if(notes.size() >= posAtBegin){ 69 | posAtBegin++; 70 | } 71 | int posAtEnd = findAtTick(toTick); 72 | if(notes.size()>= posAtEnd){ 73 | posAtEnd++; 74 | } 75 | if(posAtBegin < 0){ 76 | posAtBegin = 0; 77 | } 78 | for(; posAtBegin < posAtEnd; posAtBegin++){ 79 | returnNotes.add(this.notes.get(posAtBegin)); 80 | } 81 | return returnNotes; 82 | } 83 | 84 | public ArrayList getNotesFrom(int toTick) { 85 | return getNotesFrom(this.lastUsedTickPos, toTick); 86 | } 87 | 88 | public class Note { 89 | /** 90 | * The instrument of the note block. This is 0-15, or higher if the song uses custom instruments. 91 | * 0 = Piano (Air) 92 | * 1 = Double Bass (Wood) 93 | * 2 = Bass Drum (Stone) 94 | * 3 = Snare Drum (Sand) 95 | * 4 = Click (Glass) 96 | * 5 = Guitar (Wool) 97 | * 6 = Flute (Clay) 98 | * 7 = Bell (Block of Gold) 99 | * 8 = Chime (Packed Ice) 100 | * 9 = Xylophone (Bone Block) 101 | * 10 = Iron Xylophone (Iron Block) 102 | * 11 = Cow Bell (Soul Sand) 103 | * 12 = Didgeridoo (Pumpkin) 104 | * 13 = Bit (Block of Emerald) 105 | * 14 = Banjo (Hay) 106 | * 15 = Pling (Glowstone) 107 | */ 108 | public byte instrument; 109 | /** 110 | * The key of the note block, from 0-87, where 0 is A0 and 87 is C8. 33-57 is within the 2-octave limit. 111 | */ 112 | public byte key; 113 | /** 114 | * The velocity/volume of the note block, from 0% to 100%. 115 | */ 116 | public byte velocity = 100; 117 | /** 118 | * The stereo position of the note block, from 0-200. 100 is center panning. 119 | */ 120 | public byte panning = 100; 121 | /** 122 | * The fine pitch of the note block, from -32,768 to 32,767 cents (but the max in Note Block Studio is limited to -1200 and +1200). 0 is no fine-tuning. ±100 cents is a single semitone difference. After reading this, we go back to step 2. 123 | */ 124 | public short pitch = 0; 125 | 126 | /** 127 | * Where is that note exactly. to be able to search without replaying the whole binary. 128 | */ 129 | public final int tick; 130 | 131 | public Note(int tick) { 132 | this.tick = tick; 133 | } 134 | 135 | /** 136 | * @return sound's pitch as a float 137 | */ 138 | public float getPitch(){ 139 | return (float)Math.pow(2.0D, (double)(this.key + this.pitch/100 - 45) / 12.0D); //key 45 is F# 140 | } 141 | 142 | /** 143 | * @return sound value in percents (including the channels volume) 144 | */ 145 | public float getVolume(){ 146 | return this.velocity/10000f*volume; //there is why notes can't be static 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/format/package-info.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs.format; 2 | /* 3 | For everything common in formats. Like the header. 4 | */ -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/opennbs/package-info.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.opennbs; 2 | /* 3 | 4 | [OpenNBS](https://opennbs.org/nbs) format reader, writer and most important, Player 5 | May be separated into a different package. 6 | (It will use MC code once, in the player.) 7 | 8 | */ -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/InverseEase.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | import dev.kosmx.playerAnim.core.util.Ease; 4 | import dev.kosmx.playerAnim.core.util.Easing; 5 | 6 | public class InverseEase { 7 | public static Ease inverse(Ease ease){ 8 | String str = ease.toString(); 9 | if(str.substring(0, 2).equals("IN") && ! str.substring(0, 5).equals("INOUT")){ 10 | return (Easing.easeFromString("OUT" + str.substring(2))); 11 | }else if(str.substring(0, 3).equals("OUT")){ 12 | return (Easing.easeFromString("IN" + str.substring(3))); 13 | }else return ease; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/Move.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | 4 | import dev.kosmx.playerAnim.core.util.Ease; 5 | 6 | public class Move implements Playable { 7 | private final Ease ease; 8 | private final float value; 9 | private float valueBefore; 10 | private float valueAfter; 11 | private boolean isInitialized = false; 12 | private PartMap.PartValue part; 13 | private final int length; 14 | 15 | public Move(PartMap.PartValue part, float value, int length, Ease ease){ 16 | this.ease = ease; 17 | this.length = length; 18 | this.value = value; 19 | this.part = part; 20 | } 21 | 22 | @Override 23 | public int playForward(int time) throws QuarkParsingError{ 24 | if(! isInitialized){ 25 | this.isInitialized = true; 26 | this.valueBefore = part.getValue(); 27 | this.valueAfter = this.value; 28 | this.part.addValue(time, time + this.length, this.valueAfter, ease); 29 | }else{ 30 | this.part.hold(); 31 | this.part.addValue(time, valueBefore, ease); 32 | this.part.addValue(time + this.length, valueAfter, Ease.CONSTANT); 33 | //this.part.setValue(this.valueAfter); 34 | } 35 | return time + this.length; 36 | } 37 | 38 | @Override 39 | public int playBackward(int time) throws QuarkParsingError{ 40 | if(! isInitialized) throw new QuarkParsingError(); 41 | this.part.hold(); 42 | this.part.addValue(time, this.valueAfter, InverseEase.inverse(ease)); 43 | this.part.addValue(time + this.length, this.valueBefore, Ease.CONSTANT); 44 | //this.part.setValue(this.valueBefore); 45 | return time + this.length; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/PartMap.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | 4 | import dev.kosmx.playerAnim.core.data.KeyframeAnimation; 5 | import dev.kosmx.playerAnim.core.util.Ease; 6 | 7 | public class PartMap { 8 | public KeyframeAnimation.StateCollection part; 9 | public PartValue x; 10 | public PartValue y; 11 | public PartValue z; 12 | 13 | public PartMap(KeyframeAnimation.StateCollection part){ 14 | this.part = part; 15 | this.x = new PartValue(this.part.pitch); 16 | this.y = new PartValue(this.part.yaw); 17 | this.z = new PartValue(this.part.roll); 18 | } 19 | 20 | static class PartValue { 21 | private float value; 22 | private int lastTick; 23 | private final KeyframeAnimation.StateCollection.State timeline; 24 | 25 | 26 | private PartValue(KeyframeAnimation.StateCollection.State timeline){ 27 | this.timeline = timeline; 28 | } 29 | 30 | public void addValue(int tick, float value, Ease ease){ 31 | this.lastTick = tick; 32 | this.timeline.addKeyFrame(tick, value, ease); 33 | } 34 | 35 | public void addValue(int tickFrom, int tickTo, float value, Ease ease) throws QuarkParsingError{ 36 | if(tickFrom < this.lastTick){ 37 | throw new QuarkParsingError(); 38 | }else if(tickFrom == this.lastTick && timeline.getKeyFrames().size() != 0){ 39 | timeline.replaceEase(timeline.findAtTick(tickFrom), ease); 40 | //timeline.keyFrames.get(timeline.findAtTick(tickFrom)).ease =ease; 41 | }else{ 42 | timeline.addKeyFrame(tickFrom, this.value, ease); 43 | } 44 | this.value = value; 45 | this.lastTick = tickTo; 46 | this.timeline.addKeyFrame(tickTo, this.value, Ease.CONSTANT); 47 | } 48 | 49 | public float getValue(){ 50 | return value; 51 | } 52 | 53 | public void hold(){ 54 | //this.timeline.keyFrames.get(this.timeline.keyFrames.size() - 1).setEase(Ease.CONSTANT); 55 | this.timeline.replaceEase(this.timeline.length() -1, Ease.CONSTANT); 56 | } 57 | 58 | public void setValue(float valueAfter){ 59 | this.value = valueAfter; 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/Pause.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | public class Pause implements Playable { 4 | private final int len; 5 | 6 | public Pause(int len){ 7 | this.len = len; 8 | } 9 | 10 | @Override 11 | public int playForward(int time){ 12 | return time + this.len; 13 | } 14 | 15 | @Override 16 | public int playBackward(int time){ 17 | return time + this.len; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/Pauseable.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | public class Pauseable implements Playable { 4 | private final Playable playable; 5 | private final int len; 6 | 7 | 8 | public Pauseable(Playable playable, int len){ 9 | this.playable = playable; 10 | this.len = len; 11 | } 12 | 13 | 14 | @Override 15 | public int playForward(int time) throws QuarkParsingError{ 16 | return playable.playForward(time + this.len); 17 | } 18 | 19 | @Override 20 | public int playBackward(int time) throws QuarkParsingError{ 21 | return playable.playBackward(time) + this.len; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/Playable.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | public interface Playable { 4 | 5 | int playForward(int time) throws QuarkParsingError; 6 | 7 | int playBackward(int time) throws QuarkParsingError; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/QuarkParsingError.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | public class QuarkParsingError extends Exception { 4 | final String message; 5 | 6 | public QuarkParsingError(String message, int i){ 7 | this.message = "at line " + i + " " + message; 8 | } 9 | 10 | public QuarkParsingError(){ 11 | this.message = null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/Repeat.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | public class Repeat implements Playable { 4 | protected final Playable playable; 5 | protected final int delay; 6 | protected int count; 7 | 8 | 9 | public Repeat(Playable parent, int delay, int count) throws QuarkParsingError{ 10 | this.playable = parent; 11 | if(count < 0 || count > 128) throw new QuarkParsingError(); 12 | this.count = count; 13 | this.delay = delay; 14 | } 15 | 16 | public int playForward(int time) throws QuarkParsingError{ 17 | for(int i = 0; i <= count; i++){ 18 | time = this.playable.playForward(time); 19 | time += delay; 20 | } 21 | return time; 22 | } 23 | 24 | @Override 25 | public int playBackward(int time) throws QuarkParsingError{ 26 | throw new QuarkParsingError(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/Reset.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | import dev.kosmx.playerAnim.core.util.Ease; 4 | 5 | public class Reset implements Playable { 6 | private Playable[] parts; 7 | 8 | public Reset(QuarkReader reader, String all, int len) throws QuarkParsingError{ 9 | if(all.equals("all")){ 10 | parts = new Playable[18]; 11 | addParts(0, reader.head, len); 12 | addParts(3, reader.rightArm, len); 13 | addParts(6, reader.rightLeg, len); 14 | addParts(9, reader.leftArm, len); 15 | addParts(12, reader.leftLeg, len); 16 | addParts(15, reader.torso, len); 17 | }else{ 18 | parts = new Playable[3]; 19 | addParts(0, reader.getBPFromStr(all.split("_")), len); 20 | } 21 | } 22 | 23 | private void addParts(int i, PartMap part, int len){ 24 | parts[i] = new Move(part.x, 0, len, Ease.INOUTQUAD); 25 | parts[i + 1] = new Move(part.y, 0, len, Ease.INOUTQUAD); 26 | parts[i + 2] = new Move(part.z, 0, len, Ease.INOUTQUAD); 27 | } 28 | 29 | @Override 30 | public int playForward(int time) throws QuarkParsingError{ 31 | return 0; 32 | } 33 | 34 | @Override 35 | public int playBackward(int time) throws QuarkParsingError{ 36 | return 0; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/data/quarktool/Yoyo.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data.quarktool; 2 | 3 | public class Yoyo extends Repeat { 4 | public Yoyo(Playable parent, int delay, int count) throws QuarkParsingError{ 5 | super(parent, delay, count); 6 | } 7 | 8 | @Override 9 | public int playForward(int time) throws QuarkParsingError{ 10 | int i = 0; 11 | int t = time; 12 | while(true){ 13 | if(i++ > this.count) return t; 14 | if(i != 1) t += this.delay; 15 | t = this.playable.playForward(t); 16 | if(i++ > this.count) return t; 17 | t += this.delay; 18 | t = this.playable.playBackward(t); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/impl/AnimationProcessor.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.impl; 2 | 3 | 4 | import dev.kosmx.playerAnim.api.TransformType; 5 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 6 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 7 | import dev.kosmx.playerAnim.api.layered.IAnimation; 8 | import dev.kosmx.playerAnim.core.util.Pair; 9 | import dev.kosmx.playerAnim.core.util.Vec3f; 10 | import org.jetbrains.annotations.ApiStatus; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | /** 14 | * Tool to easily play animation to the player. 15 | * internal, do not use 16 | */ 17 | @ApiStatus.Internal 18 | public class AnimationProcessor { 19 | private final IAnimation animation; 20 | private float tickDelta = 0f; 21 | 22 | public AnimationProcessor(IAnimation animation) { 23 | this.animation = animation; 24 | } 25 | 26 | public void tick() { 27 | animation.tick(); 28 | } 29 | 30 | public boolean isActive() { 31 | return animation.isActive(); 32 | } 33 | 34 | public Vec3f get3DTransform(String modelName, TransformType type, Vec3f value0) { 35 | return animation.get3DTransform(modelName, type, this.tickDelta, value0); 36 | } 37 | 38 | public void setTickDelta(float tickDelta) { 39 | this.tickDelta = tickDelta; 40 | this.animation.setupAnim(tickDelta); 41 | } 42 | 43 | public boolean isFirstPersonAnimationDisabled() { 44 | return !animation.getFirstPersonMode(tickDelta).isEnabled(); 45 | } 46 | 47 | public @NotNull FirstPersonMode getFirstPersonMode() { 48 | return animation.getFirstPersonMode(tickDelta); 49 | } 50 | 51 | public @NotNull FirstPersonConfiguration getFirstPersonConfiguration() { 52 | return animation.getFirstPersonConfiguration(tickDelta); 53 | } 54 | 55 | public Pair getBend(String modelName) { 56 | Vec3f bendVec = this.get3DTransform(modelName, TransformType.BEND, Vec3f.ZERO); 57 | return new Pair<>(bendVec.getX(), bendVec.getY()); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/impl/event/Event.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.impl.event; 2 | 3 | import java.util.ArrayList; 4 | 5 | 6 | /** 7 | * To register a listener, use {@link Event#register(Object)}; 8 | * @param 9 | */ 10 | public class Event { 11 | final ArrayList listeners = new ArrayList<>(); 12 | final Invoker _invoker; 13 | 14 | public Event(Class clazz, Invoker invoker){ 15 | this._invoker = invoker; 16 | } 17 | 18 | /** 19 | * Do EVENT.invoker()./invoke(Objects...)/; 20 | * Only when firing the event. 21 | * @return the invoker 22 | * This shall only be used by the API 23 | */ 24 | public final T invoker(){ 25 | return _invoker.invoker(listeners); 26 | } 27 | 28 | /** 29 | * Register a new event listener; 30 | * See the actual event documentation for return type 31 | * @param listener the listener. 32 | */ 33 | public void register(T listener){ 34 | if(listener == null) throw new NullPointerException("listener can not be null"); 35 | listeners.add(listener); 36 | } 37 | 38 | /** 39 | * unregister the listener 40 | * @param listener listener to unregister, or a similar listener if it has equals() function. 41 | */ 42 | public void unregister(T listener){ 43 | listeners.remove(listener); 44 | } 45 | 46 | @FunctionalInterface 47 | public interface Invoker{ 48 | T invoker(Iterable listeners); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/impl/event/EventResult.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.impl.event; 2 | 3 | /** 4 | * Generic event results 5 | * See the actual event documentation for actual behaviour 6 | */ 7 | public enum EventResult { 8 | /** 9 | * Your listener did nothing, in won't change the result of the event 10 | */ 11 | PASS, 12 | 13 | /** 14 | * Cancel the event and success. see the event's documentation 15 | */ 16 | SUCCESS, 17 | 18 | /** 19 | * Event failed, cancel the further processing, see the event's documentation 20 | */ 21 | FAIL, 22 | 23 | /** 24 | * Cancel the event, then does nothing. sometimes the same as SUCCESS 25 | */ 26 | CONSUME 27 | } 28 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/Ease.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.function.Function; 6 | 7 | /** 8 | * Easings form easings.net
9 | * + constant + linear 10 | */ 11 | public enum Ease { 12 | LINEAR(0, arg -> easeIn(f -> f)), 13 | CONSTANT(1, arg -> easeIn(f -> 0f)), 14 | 15 | // Sine 16 | INSINE(6, arg -> easeIn(Easing::sine)), 17 | OUTSINE(7, arg -> easeOut(Easing::sine)), 18 | INOUTSINE(8, arg -> easeInOut(Easing::sine)), 19 | 20 | // Cubic 21 | INCUBIC(9, arg -> easeIn(Easing::cubic)), 22 | OUTCUBIC(10, arg -> easeOut(Easing::cubic)), 23 | INOUTCUBIC(11, arg -> easeInOut(Easing::cubic)), 24 | 25 | // Quadratic 26 | INQUAD(12, arg -> easeIn(Easing::quadratic)), 27 | OUTQUAD(13, arg -> easeOut(Easing::quadratic)), 28 | INOUTQUAD(14, arg -> easeInOut(Easing::quadratic)), 29 | 30 | // Quart 31 | INQUART(15, arg -> easeIn(Easing.pow(4))), 32 | OUTQUART(16, arg -> easeOut(Easing.pow(4))), 33 | INOUTQUART(17, arg -> easeInOut(Easing.pow(4))), 34 | 35 | // Quint 36 | INQUINT(18, arg -> easeIn(Easing.pow(5))), 37 | OUTQUINT(19, arg -> easeOut(Easing.pow(5))), 38 | INOUTQUINT(20, arg -> easeInOut(Easing.pow(5))), 39 | 40 | // Expo 41 | INEXPO(21, arg -> easeIn(Easing::exp)), 42 | OUTEXPO(22, arg -> easeOut(Easing::exp)), 43 | INOUTEXPO(23, arg -> easeInOut(Easing::exp)), 44 | 45 | // Cricle 46 | INCIRC(24, arg -> easeIn(Easing::circle)), 47 | OUTCIRC(25, arg -> easeOut(Easing::circle)), 48 | INOUTCIRC(26, arg -> easeInOut(Easing::circle)), 49 | 50 | // Back 51 | INBACK(27, arg -> easeIn(Easing.back(arg))), 52 | OUTBACK(28, arg -> easeOut(Easing.back(arg))), 53 | INOUTBACK(29, arg -> easeInOut(Easing.back(arg))), 54 | 55 | // Elastic 56 | INELASTIC(30, arg -> easeIn(Easing.elastic(arg))), 57 | OUTELASTIC(31, arg -> easeOut(Easing.elastic(arg))), 58 | INOUTELASTIC(32, arg -> easeInOut(Easing.elastic(arg))), 59 | 60 | // Bounce 61 | INBOUNCE(33, arg -> easeIn(Easing.bounce(arg))), 62 | OUTBOUNCE(34, arg -> easeOut(Easing.bounce(arg))), 63 | INOUTBOUNCE(35, arg -> easeInOut(Easing.bounce(arg))), 64 | 65 | CATMULLROM(36, arg -> easeInOut(Easing::catmullRom)), 66 | STEP(37, arg -> easeIn(Easing.step(arg))); 67 | 68 | @Getter 69 | final byte id; 70 | private final Function> impl; 71 | 72 | /** 73 | * @param id id 74 | * @param impl implementation 75 | */ 76 | Ease(byte id, Function> impl) { 77 | this.id = id; 78 | this.impl = impl; 79 | } 80 | 81 | /** 82 | * @param id id 83 | * @param impl implementation 84 | */ 85 | Ease(int id, Function> impl) { 86 | this((byte) id, impl); 87 | } 88 | 89 | /** 90 | * Run the easing 91 | * @param f float between 0 and 1 92 | * @return ease(f) 93 | */ 94 | public float invoke(float f) { 95 | return invoke(f, null); 96 | } 97 | 98 | /** 99 | * Run the easing 100 | * @param t float between 0 and 1 101 | * @param n float easing argument 102 | * @return ease(t, n) 103 | */ 104 | public float invoke(float t, Float n) { 105 | return this.impl.apply(n).apply(t); 106 | } 107 | 108 | //To be able to send these as bytes instead of String names. 109 | public static Ease getEase(byte b){ 110 | for(Ease ease:Ease.values()){ 111 | if(ease.id == b) return ease; 112 | } 113 | return LINEAR; 114 | } 115 | 116 | public static Function easeIn(Function function) { 117 | return function; 118 | } 119 | 120 | public static Function easeOut(Function function) { 121 | return time -> 1 - function.apply(1 - time); 122 | } 123 | 124 | public static Function easeInOut(Function function) { 125 | return time -> { 126 | if (time < 0.5F) { 127 | return function.apply(time * 2F) / 2F; 128 | } 129 | 130 | return 1 - function.apply((1 - time) * 2F) / 2F; 131 | }; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/Easing.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.function.Function; 7 | 8 | public class Easing { 9 | 10 | /** 11 | * Easing functions from easings.net 12 | * All function have a string codename 13 | * EasingFromString 14 | *

15 | * All function needs an input between 0 and 1 16 | * 17 | * @deprecated Just use {@link Ease#invoke(float)} 18 | */ 19 | @Deprecated 20 | public static float easingFromEnum(@Nullable Ease type, float f) { 21 | return type != null ? type.invoke(f) : f; 22 | } 23 | 24 | /** 25 | * @param string ease name 26 | * @return ease 27 | */ 28 | public static Ease easeFromString(String string){ 29 | try{ 30 | if(string.substring(0, 4).equalsIgnoreCase("EASE")){ 31 | string = string.substring(4); 32 | } 33 | return Ease.valueOf(string.toUpperCase()); 34 | } catch(Exception exception){ 35 | //Main.log(Level.ERROR, "Ease name unknown: \"" + string + "\" using linear", true); 36 | //Main.log(Level.WARN, exception.toString()); 37 | return Ease.LINEAR; 38 | } 39 | } 40 | 41 | public static float sine(float n) { 42 | return 1 - (float) Math.cos(n * Math.PI / 2F); 43 | } 44 | 45 | public static float cubic(float n) { 46 | return n * n * n; 47 | } 48 | 49 | public static float quadratic(float n) { 50 | return n * n; 51 | } 52 | 53 | public static Function pow(float n) { 54 | return t -> (float) Math.pow(t, n); 55 | } 56 | 57 | public static float exp(float n) { 58 | return (float) Math.pow(2, 10 * (n - 1)); 59 | } 60 | 61 | public static float circle(float n) { 62 | return 1 - (float) Math.sqrt(1 - n * n); 63 | } 64 | 65 | public static Function back(Float n) { 66 | final float n2 = n == null ? 1.70158F : n * 1.70158F; 67 | 68 | return t -> t * t * ((n2 + 1) * t - n2); 69 | } 70 | 71 | public static Function elastic(Float n) { 72 | float n2 = n == null ? 1 : n; 73 | 74 | return t -> (float) (1 - Math.pow(Math.cos(t * Math.PI / 2f), 3) * Math.cos(t * n2 * Math.PI)); 75 | } 76 | 77 | public static Function bounce(Float n) { 78 | final float n2 = n == null ? 0.5F : n; 79 | 80 | Function one = x -> 121f / 16f * x * x; 81 | Function two = x -> (float) (121f / 4f * n2 * Math.pow(x - 6f / 11f, 2) + 1 - n2); 82 | Function three = x -> (float) (121 * n2 * n2 * Math.pow(x - 9f / 11f, 2) + 1 - n2 * n2); 83 | Function four = x -> (float) (484 * n2 * n2 * n2 * Math.pow(x - 10.5f / 11f, 2) + 1 - n2 * n2 * n2); 84 | 85 | return t -> Math.min(Math.min(one.apply(t), two.apply(t)), Math.min(three.apply(t), four.apply(t))); 86 | } 87 | 88 | public static Function step(Float n) { 89 | float n2 = n == null ? 2 : n; 90 | 91 | if (n2 < 2) 92 | throw new IllegalArgumentException("Steps must be >= 2, got: " + n2); 93 | 94 | final int steps = (int)n2; 95 | 96 | return t -> { 97 | float result = 0; 98 | 99 | if (t < 0) 100 | return result; 101 | 102 | float stepLength = (1 / (float)steps); 103 | 104 | if (t > (result = (steps - 1) * stepLength)) 105 | return result; 106 | 107 | int testIndex; 108 | int leftBorderIndex = 0; 109 | int rightBorderIndex = steps - 1; 110 | 111 | while (rightBorderIndex - leftBorderIndex != 1) { 112 | testIndex = leftBorderIndex + (rightBorderIndex - leftBorderIndex) / 2; 113 | 114 | if (t >= testIndex * stepLength) { 115 | leftBorderIndex = testIndex; 116 | } 117 | else { 118 | rightBorderIndex = testIndex; 119 | } 120 | } 121 | 122 | return leftBorderIndex * stepLength; 123 | }; 124 | } 125 | 126 | public static float catmullRom(float n) { 127 | return (0.5f * (2.0f * (n + 1) + ((n + 2) - n) * 1 128 | + (2.0f * n - 5.0f * (n + 1) + 4.0f * (n + 2) - (n + 3)) * 1 129 | + (3.0f * (n + 1) - n - 3.0f * (n + 2) + (n + 3)) * 1)); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/MathHelper.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.Buffer; 6 | import java.nio.ByteBuffer; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | //like MC math helper but without MC 11 | public class MathHelper { 12 | 13 | public static float lerp(float delta, float start, float end) { 14 | return start + delta * (end - start); 15 | } 16 | 17 | public static double lerp(double delta, double start, double end) { 18 | return start + delta * (end - start); 19 | } 20 | 21 | public static int colorHelper(int r, int g, int b, int a){ 22 | return ((a & 255) << 24) | ((r & 255) << 16) | ((g & 255) << 8) | (b & 255); //Sometimes minecraft uses ints as color... 23 | } 24 | 25 | /** 26 | * Clamp f to -Pi until Pi range 27 | * @param f radians 28 | * @return radians 29 | */ 30 | public static float clampToRadian(float f){ 31 | final double a = Math.PI*2; 32 | double b = ((f + Math.PI)%a); 33 | if(b < 0){ 34 | b += a; 35 | } 36 | return (float) (b - Math.PI); 37 | } 38 | 39 | 40 | /** 41 | * similar? to Java 9+ {@link InputStream#readAllBytes()} 42 | * because of compatibility, I can not use that 43 | * @param stream read this stream 44 | * @return ByteBuffer from stream 45 | * @throws IOException ... 46 | */ 47 | public static ByteBuffer readFromIStream(InputStream stream) throws IOException { 48 | List> listOfBites = new LinkedList<>(); 49 | int totalSize = 0; 50 | while (true){ 51 | int estimatedSize = stream.available(); 52 | byte[] bytes = new byte[Math.max(1, estimatedSize)]; 53 | int i = stream.read(bytes); 54 | if(i < 1) break; 55 | totalSize += i; 56 | listOfBites.add(new Pair<>(i, bytes)); 57 | } 58 | ByteBuffer byteBuffer = ByteBuffer.allocate(totalSize); 59 | for(Pair i:listOfBites){ 60 | byteBuffer.put(i.getRight(), 0, i.getLeft()); 61 | } 62 | ((Buffer)byteBuffer).position(0); //set position to 0, we'll read it 63 | return byteBuffer; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/NetworkHelper.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | 4 | import java.io.IOException; 5 | import java.nio.ByteBuffer; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.UUID; 8 | 9 | /** 10 | * I can't use Minecraft's string and uuid byte reader in a bukkit plugin, I need to implement these. 11 | * This can still here, but it can be removed if unused 12 | */ 13 | public final class NetworkHelper { 14 | public static String readString(ByteBuffer buf) throws IOException { 15 | int len = buf.getInt(); 16 | if(len < 0){ 17 | throw new IOException("The received encoded string buffer length is less than zero! Weird string!"); 18 | } 19 | byte[] b = new byte[len]; 20 | buf.get(b); //that is safe to use. 21 | 22 | return new String(b, StandardCharsets.UTF_8); 23 | } 24 | 25 | public static void writeString(ByteBuffer buf, String str){ 26 | byte[] b = str.getBytes(StandardCharsets.UTF_8); 27 | buf.putInt(b.length); 28 | buf.put(b); 29 | } 30 | 31 | //copied from MC 32 | public static String readVarString(ByteBuffer buf) throws IOException { 33 | int j = readVarInt(buf); 34 | if (j < 0) { 35 | throw new IOException("The received encoded string buffer length is less than zero! Weird string!"); 36 | } else { 37 | byte[] bytes = new byte[j]; 38 | buf.get(bytes); 39 | return new String(bytes, StandardCharsets.UTF_8); 40 | } 41 | } 42 | public static void writeVarString(ByteBuffer buf, String str){ 43 | byte[] bytes = str.getBytes(StandardCharsets.UTF_8); 44 | writeVarInt(buf, bytes.length); 45 | buf.put(bytes); 46 | } 47 | 48 | public static UUID readUUID(ByteBuffer buf){ 49 | long a = buf.getLong(); 50 | long b = buf.getLong(); 51 | return new UUID(a, b); //The order is important 52 | } 53 | public static void writeUUID(ByteBuffer buf, UUID uuid){ 54 | buf.putLong(uuid.getMostSignificantBits()); 55 | buf.putLong(uuid.getLeastSignificantBits()); 56 | } 57 | 58 | //copied from minecraft 59 | public static int readVarInt(ByteBuffer buf) { 60 | int i = 0; 61 | int j = 0; 62 | 63 | byte b; 64 | do { 65 | b = buf.get(); 66 | i |= (b & 127) << j++ * 7; 67 | if (j > 5) { 68 | throw new RuntimeException("VarInt too big"); 69 | } 70 | } while((b & 128) == 128); 71 | 72 | return i; 73 | } 74 | 75 | //copied from minecraft 76 | public static void writeVarInt(ByteBuffer buf, int i){ 77 | while((i & - 128) != 0){ 78 | buf.put((byte) (i & 127 | 128)); 79 | i >>>= 7; 80 | } 81 | 82 | buf.put((byte) i); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/Pair.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | //I Didn't find any pair in Java common... so here is it 4 | 5 | import lombok.Getter; 6 | 7 | import javax.annotation.concurrent.Immutable; 8 | import java.util.Objects; 9 | 10 | /** 11 | * Pair, stores two objects. 12 | * @param Left object 13 | * @param Right object 14 | */ 15 | @Getter 16 | @Immutable 17 | public class Pair { 18 | final L left; 19 | final R right; 20 | 21 | /** 22 | * Creates a pair from two values 23 | * @param left left member 24 | * @param right right member 25 | */ 26 | public Pair(L left, R right){ 27 | this.left = left; 28 | this.right = right; 29 | } 30 | 31 | @Override 32 | @SuppressWarnings("rawtypes") 33 | public boolean equals(Object o){ 34 | if(o instanceof Pair){ 35 | Pair o2 = (Pair) o; 36 | return Objects.equals(this.left, o2.left) && Objects.equals(right, o2.right); 37 | } 38 | return false; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | int hash = 0; 44 | hash = hash * 31 + (left == null ? 0 : left.hashCode()); 45 | hash = hash * 31 + (right == null ? 0 : right.hashCode()); 46 | return hash; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "Pair{" + 52 | "left=" + left + 53 | ", right=" + right + 54 | '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/SetableSupplier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | import java.util.function.Supplier; 4 | 5 | /* 6 | * I'll use this... 7 | */ 8 | public class SetableSupplier implements Supplier { 9 | T object; 10 | 11 | /** 12 | * :D 13 | * @param object T 14 | */ 15 | public void set(T object) { 16 | this.object = object; 17 | } 18 | 19 | @Override 20 | public T get() { 21 | return this.object; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/UUIDMap.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.UUID; 7 | import java.util.function.Predicate; 8 | import java.util.function.Supplier; 9 | 10 | //HashMap but with making my life easier 11 | public class UUIDMap> extends HashMap implements Iterable { 12 | public T put(T v){ 13 | return this.put(v.get(), v); 14 | } 15 | 16 | public void addAll(Collection m) { 17 | for(T t : m){ 18 | this.put(t); 19 | } 20 | } 21 | 22 | 23 | @Override 24 | public Iterator iterator() { 25 | return this.values().iterator(); 26 | } 27 | 28 | public void add(T value) { 29 | this.put(value); 30 | } 31 | 32 | public boolean contains(T value) { 33 | return this.containsKey(value.get()); 34 | } 35 | 36 | public void removeIf(Predicate predicate){ 37 | this.values().removeIf(predicate); 38 | } 39 | } -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/Vec3d.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | /** 4 | * Three-dimensional double vector 5 | */ 6 | public class Vec3d extends Vector3 { 7 | 8 | public Vec3d(Double x, Double y, Double z) { 9 | super(x, y, z); 10 | } 11 | 12 | public double squaredDistanceTo(Vec3d vec3d){ 13 | double a = this.x - vec3d.x; 14 | double b = this.y - vec3d.y; 15 | double c = this.z - vec3d.z; 16 | return a*a + b*b + c*c; 17 | } 18 | 19 | /** 20 | * Scale the vector 21 | * @param scalar scalar 22 | * @return scaled vector 23 | */ 24 | public Vec3d scale(double scalar) { 25 | return new Vec3d(this.getX() * scalar, this.getY() * scalar, this.getZ() * scalar); 26 | } 27 | 28 | /** 29 | * Add two vectors 30 | * @param other other vector 31 | * @return sum vector 32 | */ 33 | public Vec3d add(Vec3d other) { 34 | return new Vec3d(this.getX() + other.getX(), this.getY() + other.getY(), this.getZ() + other.getZ()); 35 | } 36 | 37 | /** 38 | * Dot product with other vector 39 | * @param other rhs operand 40 | * @return v 41 | */ 42 | public double dotProduct(Vec3d other) { 43 | return this.getX() * other.getX() + this.getY() * other.getY() + this.getZ() * other.getZ(); 44 | } 45 | 46 | /** 47 | * Cross product 48 | * @param other rhs operand 49 | * @return v 50 | */ 51 | public Vec3d crossProduct(Vec3d other) { 52 | return new Vec3d( 53 | this.getY()*other.getZ() - this.getZ()*other.getY(), 54 | this.getZ()*other.getX() - this.getX()*other.getZ(), 55 | this.getX()*other.getY() - this.getY()*other.getX() 56 | ); 57 | } 58 | 59 | /** 60 | * Subtract a vector from this 61 | * @param rhs rhs operand 62 | * @return v 63 | */ 64 | public Vec3d subtract(Vec3d rhs) { 65 | //You could have guessed what will happen here. 66 | return add(rhs.scale(-1)); 67 | } 68 | 69 | public double distanceTo(Vec3d vec3d){ 70 | return Math.sqrt(squaredDistanceTo(vec3d)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/Vec3f.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | import javax.annotation.concurrent.Immutable; 4 | 5 | @Immutable 6 | public class Vec3f extends Vector3 { 7 | 8 | public static final Vec3f ZERO = new Vec3f(0f, 0f, 0f); 9 | 10 | public Vec3f(float x, float y, float z) { 11 | super(x, y, z); 12 | } 13 | 14 | public double squaredDistanceTo(Vec3d vec3d){ 15 | double a = this.x - vec3d.x; 16 | double b = this.y - vec3d.y; 17 | double c = this.z - vec3d.z; 18 | return a*a + b*b + c*c; 19 | } 20 | 21 | /** 22 | * Scale the vector 23 | * @param scalar scalar 24 | * @return scaled vector 25 | */ 26 | public Vec3f scale(float scalar) { 27 | return new Vec3f(this.getX() * scalar, this.getY() * scalar, this.getZ() * scalar); 28 | } 29 | 30 | /** 31 | * Add two vectors 32 | * @param other other vector 33 | * @return sum vector 34 | */ 35 | public Vec3f add(Vec3f other) { 36 | return new Vec3f(this.getX() + other.getX(), this.getY() + other.getY(), this.getZ() + other.getZ()); 37 | } 38 | 39 | /** 40 | * Dot product with other vector 41 | * @param other rhs operand 42 | * @return v 43 | */ 44 | public float dotProduct(Vec3f other) { 45 | return this.getX() * other.getX() + this.getY() * other.getY() + this.getZ() * other.getZ(); 46 | } 47 | 48 | /** 49 | * Cross product 50 | * @param other rhs operand 51 | * @return v 52 | */ 53 | public Vec3f crossProduct(Vec3f other) { 54 | return new Vec3f( 55 | this.getY()*other.getZ() - this.getZ()*other.getY(), 56 | this.getZ()*other.getX() - this.getX()*other.getZ(), 57 | this.getX()*other.getY() - this.getY()*other.getX() 58 | ); 59 | } 60 | 61 | /** 62 | * Subtract a vector from this 63 | * @param rhs rhs operand 64 | * @return v 65 | */ 66 | public Vec3f subtract(Vec3f rhs) { 67 | //You could have guessed what will happen here. 68 | return add(rhs.scale(-1)); 69 | } 70 | 71 | public double distanceTo(Vec3d vec3d){ 72 | return Math.sqrt(squaredDistanceTo(vec3d)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /coreLib/src/main/java/dev/kosmx/playerAnim/core/util/Vector3.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.util; 2 | 3 | import javax.annotation.concurrent.Immutable; 4 | import java.util.Objects; 5 | 6 | 7 | /** 8 | * 3 dimensional generic vector implementation 9 | * @param value type 10 | */ 11 | @Immutable 12 | public class Vector3 { 13 | N x, y, z; 14 | 15 | public Vector3(N x, N y, N z){ 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | 21 | public N getX() { 22 | return x; 23 | } 24 | 25 | public N getY() { 26 | return y; 27 | } 28 | 29 | public N getZ() { 30 | return z; 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (!(o instanceof Vector3)) return false; 37 | Vector3 vector3 = (Vector3) o; 38 | return Objects.equals(x, vector3.x) && Objects.equals(y, vector3.y) && Objects.equals(z, vector3.z); 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return Objects.hash(x, y, z); 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "Vec3f[" + this.getX() + "; " + this.getY() + "; " + this.getZ() + "]"; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /coreLib/src/test/java/dev/kosmx/playerAnim/core/data/KeyframeAnimationTest.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data; 2 | 3 | import dev.kosmx.playerAnim.core.util.Ease; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Random; 8 | 9 | public class KeyframeAnimationTest { 10 | @Test 11 | public void testKeyframeAnimation() { 12 | KeyframeAnimation.StateCollection.State state = new KeyframeAnimation.StateCollection(0).x; 13 | // Easy case 14 | state.addKeyFrame(1, 0, Ease.CONSTANT); 15 | state.addKeyFrame(5, 0, Ease.CONSTANT); 16 | state.addKeyFrame(10, 10, Ease.CONSTANT); 17 | state.addKeyFrame(10, 10, Ease.CONSTANT); 18 | 19 | state.addKeyFrame(15, 10, Ease.CONSTANT); 20 | 21 | verify(state); 22 | state.getKeyFrames().clear(); 23 | 24 | 25 | // random case 26 | Random random = new Random(); 27 | 28 | for (int i = 0; i < 10000; i += random.nextInt(100)) { 29 | state.addKeyFrame(i, i, Ease.CONSTANT); 30 | } 31 | 32 | verify(state); 33 | } 34 | 35 | public static void verify(KeyframeAnimation.StateCollection.State state) { 36 | 37 | for (int t = 0; t < state.getKeyFrames().size(); t++) { 38 | // Iterative, 100% works algorithm 39 | 40 | int i = -1; 41 | while (state.getKeyFrames().size() > i + 1 && state.getKeyFrames().get(i + 1).tick <= t) { 42 | i++; 43 | } 44 | 45 | Assertions.assertEquals(i, state.findAtTick(t), "KeyframeAnimationTest failed at tick " + t); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /coreLib/src/test/java/dev/kosmx/playerAnim/core/data/LayerTest.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data; 2 | 3 | import dev.kosmx.playerAnim.api.layered.AnimationStack; 4 | import dev.kosmx.playerAnim.api.layered.IAnimation; 5 | import dev.kosmx.playerAnim.api.layered.ModifierLayer; 6 | import dev.kosmx.playerAnim.core.util.Pair; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.lang.reflect.Field; 10 | import java.util.List; 11 | import java.util.Random; 12 | 13 | public class LayerTest { 14 | 15 | @Test 16 | public void testLayers() throws NoSuchFieldException, IllegalAccessException { 17 | AnimationStack stack = new AnimationStack(); 18 | Random random = new Random(); 19 | for (int i = 0; i < 128; i++) { 20 | stack.addAnimLayer(random.nextInt()%10000, new ModifierLayer<>()); 21 | } 22 | 23 | //This should not be accessible while using it as an API, but for the test, it is completely reasonable 24 | Field layersRef = AnimationStack.class.getDeclaredField("layers"); 25 | layersRef.setAccessible(true); 26 | List> layers = (List>)layersRef.get(stack); 27 | 28 | int i = Integer.MIN_VALUE; 29 | for (Pair layer : layers) { 30 | int n = layer.getLeft(); 31 | if (n < i) { 32 | System.out.println(layers); 33 | throw new AssertionError("Layers are not in order"); 34 | } 35 | i = n; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /coreLib/src/test/java/dev/kosmx/playerAnim/core/data/StringTest.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.core.data; 2 | 3 | import dev.kosmx.playerAnim.core.data.gson.GeckoLibSerializer; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | 8 | public class StringTest { 9 | 10 | @Test 11 | public void camelTest() { 12 | String str = "camel_case_string"; 13 | String converted = "camelCaseString"; 14 | Assertions.assertEquals(converted, GeckoLibSerializer.snake2Camel(str)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | 3 | minecraft_version=1.21.1 4 | 5 | archives_base_name=player-animation-lib 6 | #Major: API break, Minor: non-breaking but significant, Patch: minor bugfix/change + MC implementation fix 7 | mod_version=2.0.1 8 | maven_group=dev.kosmx.player-anim 9 | 10 | 11 | fabric_loader_version=0.16.9 12 | fabric_api_version=0.110.0+1.21.1 13 | 14 | forge_version=21.1.89 15 | 16 | # from localmaven TODO change 17 | bendy_lib=5.1 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/minecraftPlayerAnimator/93e08373367850a0e956550740adc684305d82ab/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/minecraftPlayerAnimator/93e08373367850a0e956550740adc684305d82ab/icon.png -------------------------------------------------------------------------------- /minecraft/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | subprojects { 10 | apply plugin: "dev.architectury.loom" 11 | 12 | loom { 13 | silentMojangMappingsLicense() 14 | } 15 | 16 | dependencies { 17 | minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" 18 | // The following line declares the mojmap mappings, you may use other mappings as well 19 | mappings loom.officialMojangMappings() 20 | // The following line declares the yarn mappings you may select this one as well. 21 | // mappings "net.fabricmc:yarn:1.19+build.2:v2" 22 | } 23 | } 24 | 25 | if (keysExists) { 26 | task publishMod { 27 | finalizedBy(":${project.name}:fabric:modrinth") 28 | finalizedBy(":${project.name}:forge:modrinth") 29 | finalizedBy(":${project.name}:fabric:curseforge") 30 | finalizedBy(":${project.name}:forge:curseforge") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /minecraft/common/build.gradle: -------------------------------------------------------------------------------- 1 | architectury { 2 | common("neoforge", "fabric") 3 | } 4 | 5 | loom { 6 | accessWidenerPath = file("src/main/resources/playerAnimator.accesswidener") 7 | } 8 | 9 | dependencies { 10 | // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies 11 | // Do NOT use other classes from fabric loader 12 | modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" 13 | implementation project(':coreLib') 14 | 15 | //modApi "dev.architectury:architectury:${rootProject.architectury_version}" 16 | 17 | modCompileOnly "io.github.kosmx.bendy-lib:bendy-lib:${project.bendy_lib}" 18 | //modCompileOnly "maven.modrinth:3dskinlayers:1.5.2-fabric-1.19" 19 | 20 | } 21 | 22 | publishing { 23 | publications { 24 | mavenCommon(MavenPublication) { 25 | artifactId = rootProject.archives_base_name 26 | from components.java 27 | 28 | /* 29 | pom.withXml { 30 | 31 | def depsNode = asNode().appendNode("dependencies") 32 | 33 | def apiNode = depsNode.appendNode("dependency") 34 | apiNode.appendNode("groupId", project.group) 35 | apiNode.appendNode("artifactId", "animCore") 36 | apiNode.appendNode("version", project.version) 37 | apiNode.appendNode("scope", "compile") 38 | }//*/ 39 | } 40 | } 41 | 42 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 43 | repositories { 44 | repositories { 45 | 46 | if (project.keysExists) { 47 | maven { 48 | url = 'https://maven.kosmx.dev/' 49 | credentials { 50 | username = project.keys.kosmx_maven_user 51 | password = project.keys.kosmx_maven 52 | } 53 | } 54 | maven { 55 | name = "GitHubPackages" 56 | url = "https://maven.pkg.github.com/kosmx/minecraftPlayerAnimator" 57 | credentials { 58 | username = System.getenv("GITHUB_ACTOR") 59 | password = System.getenv("GITHUB_TOKEN") 60 | } 61 | } 62 | } else { 63 | mavenLocal() 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/Helper.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl; 2 | 3 | import dev.architectury.injectables.annotations.ExpectPlatform; 4 | import org.jetbrains.annotations.ApiStatus; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | /** 10 | * Helper Utility class 11 | */ 12 | @ApiStatus.Internal 13 | public final class Helper { 14 | 15 | @Nullable 16 | private static AtomicBoolean isBendyLibLoaded = null; 17 | 18 | public static boolean isBendEnabled() { 19 | if (isBendyLibLoaded == null) isBendyLibLoaded = new AtomicBoolean(isBendyLibPresent()); 20 | return isBendyLibLoaded.get(); 21 | } 22 | 23 | @ExpectPlatform 24 | public static boolean isBendyLibPresent() { 25 | throw new AssertionError(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/IAnimatedPlayer.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl; 2 | 3 | import dev.kosmx.playerAnim.api.IPlayer; 4 | import dev.kosmx.playerAnim.api.layered.IAnimation; 5 | import dev.kosmx.playerAnim.impl.animation.AnimationApplier; 6 | import net.minecraft.resources.ResourceLocation; 7 | import org.jetbrains.annotations.ApiStatus; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | /** 12 | * Even if it is marked as internal API, this interface should not change 13 | */ 14 | 15 | @ApiStatus.Internal 16 | public interface IAnimatedPlayer extends IPlayer { 17 | 18 | /** 19 | * @deprecated potential name conflict on mixin interface 20 | * use {@code IAnimatedPlayer#playerAnimator_getAnimation} 21 | */ 22 | @Deprecated(forRemoval = true) 23 | default AnimationApplier getAnimation() { 24 | return playerAnimator_getAnimation(); 25 | } 26 | 27 | 28 | AnimationApplier playerAnimator_getAnimation(); 29 | 30 | /** 31 | * Get an animation associated with the player 32 | * @param id Animation identifier, please start with your modid to avoid collision 33 | * @return animation or null if not exists 34 | * @apiNote This function does not register the animation, just store it. 35 | */ 36 | @Nullable 37 | IAnimation playerAnimator_getAnimation(@NotNull ResourceLocation id); 38 | 39 | /** 40 | * Set an animation associated with the player 41 | * 42 | * @param id Animation identifier. Please don't override/remove other mod animations, always use your modid! 43 | * @param animation animation to store in the player, null to clear stored animation 44 | * @return The previously stored animation. 45 | */ 46 | @Nullable 47 | IAnimation playerAnimator_setAnimation(@NotNull ResourceLocation id, @Nullable IAnimation animation); 48 | } 49 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/IMutableModel.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl; 2 | 3 | import dev.kosmx.playerAnim.core.impl.AnimationProcessor; 4 | import dev.kosmx.playerAnim.core.util.SetableSupplier; 5 | import org.jetbrains.annotations.ApiStatus; 6 | 7 | @ApiStatus.Internal 8 | public interface IMutableModel { 9 | 10 | void setEmoteSupplier(SetableSupplier emoteSupplier); 11 | 12 | SetableSupplier getEmoteSupplier(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/IPlayerModel.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | 5 | @ApiStatus.Internal 6 | public interface IPlayerModel { 7 | void playerAnimator_prepForFirstPersonRender(); 8 | } 9 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/IUpperPartHelper.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | 5 | @ApiStatus.Internal 6 | public interface IUpperPartHelper { 7 | boolean isUpperPart(); 8 | 9 | void setUpperPart(boolean bl); 10 | } 11 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/animation/AnimationApplier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl.animation; 2 | 3 | 4 | import dev.kosmx.playerAnim.api.TransformType; 5 | import dev.kosmx.playerAnim.api.layered.IAnimation; 6 | import dev.kosmx.playerAnim.core.impl.AnimationProcessor; 7 | import dev.kosmx.playerAnim.core.util.MathHelper; 8 | import dev.kosmx.playerAnim.core.util.Pair; 9 | import dev.kosmx.playerAnim.core.util.Vec3f; 10 | import net.minecraft.client.model.geom.ModelPart; 11 | import org.jetbrains.annotations.ApiStatus; 12 | 13 | @ApiStatus.Internal 14 | public class AnimationApplier extends AnimationProcessor { 15 | public AnimationApplier(IAnimation animation) { 16 | super(animation); 17 | } 18 | 19 | public void updatePart(String partName, ModelPart part) { 20 | Vec3f pos = this.get3DTransform(partName, TransformType.POSITION, new Vec3f(part.x, part.y, part.z)); 21 | part.x = pos.getX(); 22 | part.y = pos.getY(); 23 | part.z = pos.getZ(); 24 | Vec3f rot = this.get3DTransform(partName, TransformType.ROTATION, new Vec3f( // clamp guards 25 | MathHelper.clampToRadian(part.xRot), 26 | MathHelper.clampToRadian(part.yRot), 27 | MathHelper.clampToRadian(part.zRot))); 28 | part.setRotation(rot.getX(), rot.getY(), rot.getZ()); 29 | Vec3f scale = this.get3DTransform(partName, TransformType.SCALE, 30 | new Vec3f(part.xScale, part.yScale, part.zScale) 31 | ); 32 | part.xScale = scale.getX(); 33 | part.yScale = scale.getY(); 34 | part.zScale = scale.getZ(); 35 | if (!partName.equals("head")) { 36 | if (partName.equals("torso")) { 37 | Pair torsoBend = getBend(partName); 38 | Pair bodyBend = getBend("body"); 39 | IBendHelper.INSTANCE.bend(part, new Pair<>(torsoBend.getLeft() + bodyBend.getLeft(), torsoBend.getRight() + bodyBend.getRight())); 40 | } else { 41 | IBendHelper.INSTANCE.bend(part, getBend(partName)); 42 | } 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/animation/BendHelper.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl.animation; 2 | 3 | import dev.kosmx.playerAnim.core.util.Pair; 4 | import io.github.kosmx.bendylib.ModelPartAccessor; 5 | import io.github.kosmx.bendylib.impl.BendableCuboid; 6 | import net.minecraft.client.model.geom.ModelPart; 7 | import net.minecraft.core.Direction; 8 | import org.jetbrains.annotations.ApiStatus; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | @ApiStatus.Internal 12 | public class BendHelper implements IBendHelper { 13 | @Override 14 | public void bend(ModelPart modelPart, float axis, float rotation){ 15 | // Don't enable bend until rotation is bigger than epsilon. This should avoid unnecessary heavy calculations. 16 | if (Math.abs(rotation) >= 0.0001f) { 17 | ModelPartAccessor.optionalGetCuboid(modelPart, 0).ifPresent(mutableCuboid -> ((BendableCuboid) mutableCuboid.getAndActivateMutator("bend")).applyBend(axis, rotation)); 18 | } else { 19 | ModelPartAccessor.optionalGetCuboid(modelPart, 0).ifPresent(mutableCuboid -> mutableCuboid.getAndActivateMutator(null)); 20 | } 21 | } 22 | 23 | @Override 24 | public void bend(ModelPart modelPart, @Nullable Pair pair){ 25 | if(pair != null) { 26 | this.bend(modelPart, pair.getLeft(), pair.getRight()); 27 | } 28 | else { 29 | //ModelPartAccessor.getCuboid(modelPart, 0).getAndActivateMutator(null); 30 | ModelPartAccessor.optionalGetCuboid(modelPart, 0).ifPresent(mutableCuboid -> mutableCuboid.getAndActivateMutator(null)); 31 | } 32 | } 33 | 34 | @Override 35 | public void initBend(ModelPart modelPart, Direction direction) { 36 | ModelPartAccessor.optionalGetCuboid(modelPart, 0).ifPresent(mutableModelPart -> mutableModelPart.registerMutator("bend", data -> new BendableCuboid.Builder().setDirection(direction).build(data))); 37 | } 38 | 39 | @Override 40 | public void initCapeBend(ModelPart modelPart) { 41 | ModelPartAccessor.optionalGetCuboid(modelPart, 0).ifPresent(mutableModelPart -> mutableModelPart.registerMutator("bend", data -> { 42 | data.pivot = 6; 43 | return new BendableCuboid.Builder().setDirection(Direction.UP).build(data); 44 | })); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/animation/IBendHelper.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl.animation; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import dev.kosmx.playerAnim.core.util.Pair; 5 | import dev.kosmx.playerAnim.impl.Helper; 6 | import net.minecraft.client.model.geom.ModelPart; 7 | import net.minecraft.core.Direction; 8 | import org.jetbrains.annotations.ApiStatus; 9 | import org.jetbrains.annotations.Nullable; 10 | import org.joml.Quaternionf; 11 | import org.joml.Vector3f; 12 | 13 | @ApiStatus.Internal 14 | public interface IBendHelper { 15 | 16 | IBendHelper INSTANCE = Helper.isBendEnabled() ? new BendHelper() : new DummyBendable(); 17 | static void rotateMatrixStack(PoseStack matrices, Pair pair){ 18 | float offset = 0.375f; 19 | matrices.translate(0, offset, 0); 20 | float bend = pair.getRight(); 21 | float axisf = - pair.getLeft(); 22 | Vector3f axis = new Vector3f((float) Math.cos(axisf), 0, (float) Math.sin(axisf)); 23 | matrices.mulPose(new Quaternionf().rotateAxis(bend, axis)); 24 | matrices.translate(0, - offset, 0); 25 | } 26 | 27 | void bend(ModelPart modelPart, float a, float b); 28 | 29 | void bend(ModelPart modelPart, @Nullable Pair pair); 30 | 31 | void initBend(ModelPart modelPart, Direction direction); 32 | 33 | void initCapeBend(ModelPart modelPart); 34 | 35 | class DummyBendable implements IBendHelper { 36 | 37 | @Override 38 | public void bend(ModelPart modelPart, float a, float b) { 39 | 40 | } 41 | 42 | @Override 43 | public void bend(ModelPart modelPart, @org.jetbrains.annotations.Nullable Pair pair) { 44 | 45 | } 46 | 47 | @Override 48 | public void initBend(ModelPart modelPart, Direction direction) { 49 | 50 | } 51 | 52 | @Override 53 | public void initCapeBend(ModelPart modelPart) { 54 | 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/compat/skinLayers/SkinLayersTransformer.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl.compat.skinLayers; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.slf4j.Logger; 5 | 6 | @ApiStatus.Internal 7 | public class SkinLayersTransformer { 8 | 9 | public static void init(Logger logger) { 10 | /* 11 | logger.info("Loading 3D skin compat"); 12 | 13 | LayerFeatureTransformerAPI.setLayerTransformer((player, matrixStack, modelPart) -> { 14 | if (((IUpperPartHelper)modelPart).isUpperPart()) { 15 | IBendHelper.rotateMatrixStack(matrixStack, ((IAnimatedPlayer) player).playerAnimator_getAnimation().getBend("body")); 16 | } 17 | }); 18 | 19 | */ 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/impl/mixin/MixinConfig.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl.mixin; 2 | 3 | import dev.kosmx.playerAnim.impl.Helper; 4 | import org.jetbrains.annotations.ApiStatus; 5 | import org.objectweb.asm.tree.ClassNode; 6 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 7 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 8 | 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | @ApiStatus.Internal 13 | public class MixinConfig implements IMixinConfigPlugin { 14 | 15 | @Override 16 | public void onLoad(String mixinPackage) { 17 | 18 | } 19 | 20 | @Override 21 | public String getRefMapperConfig() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 27 | if (mixinClassName.endsWith("bendOnly") && !Helper.isBendEnabled()) { 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | @Override 34 | public void acceptTargets(Set myTargets, Set otherTargets) { 35 | 36 | } 37 | 38 | @Override 39 | public List getMixins() { 40 | return null; 41 | } 42 | 43 | @Override 44 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 45 | 46 | } 47 | 48 | @Override 49 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/PlayerAnimationAccess.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi; 2 | 3 | import dev.kosmx.playerAnim.api.IPlayer; 4 | import dev.kosmx.playerAnim.api.layered.AnimationStack; 5 | import dev.kosmx.playerAnim.api.layered.IAnimation; 6 | import dev.kosmx.playerAnim.core.impl.event.Event; 7 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 8 | import net.fabricmc.api.EnvType; 9 | import net.fabricmc.api.Environment; 10 | import net.minecraft.client.player.AbstractClientPlayer; 11 | import net.minecraft.resources.ResourceLocation; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | @Environment(EnvType.CLIENT) 16 | public final class PlayerAnimationAccess { 17 | 18 | /** 19 | * Get the animation stack for a player entity on the client. 20 | *

21 | * Or you can use {@code ((IPlayer) player).getAnimationStack();} 22 | * 23 | * @param player The ClientPlayer object 24 | * @return The players' animation stack 25 | */ 26 | public static AnimationStack getPlayerAnimLayer(AbstractClientPlayer player) throws IllegalArgumentException { 27 | if (player instanceof IPlayer) { 28 | return ((IPlayer) player).getAnimationStack(); 29 | } else throw new IllegalArgumentException(player + " is not a player or library mixins failed"); 30 | } 31 | 32 | /** 33 | * Allows mods to store animation layers associated with player. 34 | * Stored data does not get automatically registered. 35 | * @param player player entity 36 | * @return data accessor type, you can use get() and set() on it (kotlin getter/setter compatible) 37 | * @throws IllegalArgumentException if the given argument is not a player, or api mixins have failed (normally never) 38 | * @implNote data is stored in the player object (using mixins), using it is more efficient than any objectMap as objectMap solution does not know when to delete the data. 39 | */ 40 | public static PlayerAssociatedAnimationData getPlayerAssociatedData(@NotNull AbstractClientPlayer player) { 41 | if (player instanceof IAnimatedPlayer animatedPlayer) { 42 | return new PlayerAssociatedAnimationData(animatedPlayer); 43 | } else throw new IllegalArgumentException(player + " is not a player or library mixins failed"); 44 | } 45 | 46 | /** 47 | * If you don't want to create your own mixin, you can use this event to add animation to players
48 | * The event will fire for every player and if the player reloads, it will fire again.
49 | *


50 | * NOTE: When the event fires, {@link IPlayer#getAnimationStack()} will be null, you'll have to use the given stack. 51 | */ 52 | public static final Event REGISTER_ANIMATION_EVENT = new Event<>(AnimationRegister.class, listeners -> (player, animationStack) -> { 53 | for (AnimationRegister listener : listeners) { 54 | listener.registerAnimation(player, animationStack); 55 | } 56 | }); 57 | 58 | @FunctionalInterface 59 | public interface AnimationRegister { 60 | /** 61 | * Player object is in construction, it will be invoked when you can register animation 62 | * It will be invoked for every player only ONCE (it isn't a tick function) 63 | * @param player Client player object, can be the main player or other player 64 | * @param animationStack the players AnimationStack, unique for every player 65 | */ 66 | void registerAnimation(@NotNull AbstractClientPlayer player, @NotNull AnimationStack animationStack); 67 | } 68 | 69 | public static class PlayerAssociatedAnimationData { 70 | @NotNull 71 | private final IAnimatedPlayer player; 72 | 73 | public PlayerAssociatedAnimationData(@NotNull IAnimatedPlayer player) { 74 | this.player = player; 75 | } 76 | 77 | /** 78 | * Get an animation associated with the player 79 | * @param id Animation identifier, please start with your modid to avoid collision 80 | * @return animation or null if not exists 81 | * @apiNote This function does not register the animation, just store it. 82 | */ 83 | @Nullable public IAnimation get(@NotNull ResourceLocation id) { 84 | return player.playerAnimator_getAnimation(id); 85 | } 86 | 87 | /** 88 | * Set an animation associated with the player 89 | * 90 | * @param id Animation identifier. Please don't override/remove other mod animations, always use your modid! 91 | * @param animation animation to store in the player, null to clear stored animation 92 | * @return The previously stored animation. 93 | */ 94 | @Nullable public IAnimation set(@NotNull ResourceLocation id, @Nullable IAnimation animation) { 95 | return player.playerAnimator_setAnimation(id, animation); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/PlayerAnimationFactory.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi; 2 | 3 | import dev.kosmx.playerAnim.api.layered.AnimationStack; 4 | import dev.kosmx.playerAnim.api.layered.IAnimation; 5 | import net.minecraft.client.player.AbstractClientPlayer; 6 | import net.minecraft.resources.ResourceLocation; 7 | import org.jetbrains.annotations.ApiStatus; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Optional; 15 | import java.util.function.Function; 16 | 17 | /** 18 | * Animation factory, the factory will be invoked whenever a client-player is constructed. 19 | * The returned animation will be automatically registered and added to playerAssociated data. 20 | *

21 | * {@link PlayerAnimationAccess#REGISTER_ANIMATION_EVENT} is invoked after factories are done. 22 | */ 23 | public interface PlayerAnimationFactory { 24 | 25 | FactoryHolder ANIMATION_DATA_FACTORY = new FactoryHolder(); 26 | 27 | @Nullable IAnimation invoke(@NotNull AbstractClientPlayer player); 28 | 29 | class FactoryHolder { 30 | private FactoryHolder() {} 31 | 32 | private static final List> factories = new ArrayList<>(); 33 | 34 | /** 35 | * Animation factory 36 | * @param id animation id or null if you don't want to add to playerAssociated data 37 | * @param priority animation priority 38 | * @param factory animation factory 39 | */ 40 | public void registerFactory(@Nullable ResourceLocation id, int priority, @NotNull PlayerAnimationFactory factory) { 41 | factories.add(player -> Optional.ofNullable(factory.invoke(player)).map(animation -> new DataHolder(id, priority, animation)).orElse(null)); 42 | } 43 | 44 | @ApiStatus.Internal 45 | private record DataHolder(@Nullable ResourceLocation id, int priority, @NotNull IAnimation animation) {} 46 | 47 | @ApiStatus.Internal 48 | public void prepareAnimations(AbstractClientPlayer player, AnimationStack playerStack, Map animationMap) { 49 | for (Function factory: factories) { 50 | DataHolder dataHolder = factory.apply(player); 51 | if (dataHolder != null) { 52 | playerStack.addAnimLayer(dataHolder.priority(), dataHolder.animation()); 53 | if (dataHolder.id() != null) { 54 | animationMap.put(dataHolder.id(), dataHolder.animation()); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/codec/AbstractGsonCodec.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.codec; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonParseException; 5 | import dev.kosmx.playerAnim.api.IPlayable; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | import java.lang.reflect.Type; 12 | import java.util.Collection; 13 | 14 | public abstract class AbstractGsonCodec implements AnimationCodec { 15 | 16 | protected abstract Gson getGson(); 17 | 18 | protected abstract Type getListedTypeToken(); 19 | 20 | @Override 21 | public @NotNull Collection decode(@NotNull InputStream buffer) throws IOException { 22 | var gson = getGson(); 23 | try { 24 | // type safety is off! 25 | return gson.fromJson(new InputStreamReader(buffer), getListedTypeToken()); 26 | } catch (JsonParseException e) { 27 | throw new IOException(e); 28 | } 29 | } 30 | 31 | @Override 32 | public @NotNull String getExtension() { 33 | return "json"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/codec/AnimationCodec.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.codec; 2 | 3 | import dev.kosmx.playerAnim.api.IPlayable; 4 | import net.minecraft.resources.ResourceLocation; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | 10 | public interface AnimationCodec extends AnimationEncoder, AnimationDecoder { 11 | 12 | /** 13 | * It is not required to have encode capabilities. 14 | * @param output output byte stream, may be backed by a file 15 | * @param location animation ID 16 | * @param animation animation 17 | * @throws IOException if something goes wrong. 18 | */ 19 | @Override 20 | default void encode(@NotNull OutputStream output, @NotNull ResourceLocation location, @NotNull T animation) throws IOException { 21 | throw new UnsupportedOperationException(); 22 | } 23 | 24 | 25 | @NotNull String getFormatName(); 26 | @NotNull String getExtension(); 27 | } 28 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/codec/AnimationCodecs.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.codec; 2 | 3 | import dev.kosmx.playerAnim.api.IPlayable; 4 | import org.jetbrains.annotations.ApiStatus; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.BufferedInputStream; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.util.*; 15 | import java.util.function.Supplier; 16 | 17 | public class AnimationCodecs { 18 | private static final Logger logger = LoggerFactory.getLogger(AnimationCodecs.class); 19 | public static final AnimationCodecs INSTANCE = new AnimationCodecs(); 20 | private AnimationCodecs() {} 21 | 22 | private final List> codecs = new ArrayList<>(); 23 | 24 | public void registerCodec(AnimationCodec codec) { 25 | codecs.add(codec); 26 | } 27 | 28 | /** 29 | * Return decoder(s) for the given file extension. 30 | * An extension might have more decoders, like .json might be emotecraft or bedrock 31 | * @param fileExtension extension without the dot (.) like json 32 | * @return stream, the stream might be empty. 33 | */ 34 | @NotNull 35 | public List> getCodec(@NotNull String fileExtension) { 36 | return codecs.stream().filter(it -> Objects.equals(it.getExtension(), fileExtension)).toList(); 37 | } 38 | 39 | /** 40 | * 41 | * @return list of all available codecs. Do not modify the list, use {@link AnimationCodecs#registerCodec(AnimationCodec)} to add stuff. 42 | */ 43 | @NotNull 44 | public List> getAllCodecs() { 45 | return codecs; 46 | } 47 | 48 | 49 | public static @Nullable String getExtension(@NotNull String filename) { 50 | if (filename.isEmpty()) return null; 51 | 52 | int i = filename.lastIndexOf('.'); 53 | if (i > 0) { 54 | filename = filename.substring(i + 1); 55 | } 56 | return filename; 57 | } 58 | 59 | @NotNull 60 | @ApiStatus.Experimental 61 | public static Collection deserialize(@Nullable String extension, @NotNull InputStream inputStream) throws IOException { 62 | var data = inputStream.readAllBytes(); // I might need to try multiple times, don't break the input stream 63 | try { 64 | inputStream.close(); 65 | } catch (Exception ignored) {} 66 | return deserialize(extension, () -> new ByteArrayInputStream(data)); 67 | } 68 | 69 | @NotNull 70 | public static Collection deserialize(@Nullable String extension, @NotNull Supplier inputStreamSupplier) { 71 | 72 | List animations = new LinkedList<>(); 73 | 74 | for (AnimationCodec deserializer: extension == null ? AnimationCodecs.INSTANCE.getAllCodecs() : AnimationCodecs.INSTANCE.getCodec(extension)) { 75 | try (var reader = inputStreamSupplier.get()) { 76 | final var result = deserializer.decode(new BufferedInputStream(reader)); 77 | if (result.isEmpty()) throw new RuntimeException("Decoder is not obeying API"); 78 | 79 | animations.addAll(result); 80 | break; 81 | 82 | } catch (IOException e) { 83 | // this is normal to happen 84 | logger.info(String.format("Failed to apply %s", deserializer.getFormatName()), e); 85 | } catch (Exception e) { 86 | logger.error(String.format("Unknown error when trying to apply %s", deserializer.getFormatName()), e); 87 | } 88 | } 89 | return animations; 90 | } 91 | 92 | static { 93 | INSTANCE.registerCodec(EmotecraftGsonCodec.INSTANCE); 94 | INSTANCE.registerCodec(LegacyGeckoJsonCodec.INSTANCE); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/codec/AnimationDecoder.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.codec; 2 | 3 | import dev.kosmx.playerAnim.api.IPlayable; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Collection; 9 | 10 | @FunctionalInterface 11 | public interface AnimationDecoder { 12 | 13 | /** 14 | * Decode animation from an input stream. 15 | * @param buffer input data, usually file, but can be other source 16 | * @return Decoded animation(s) 17 | *
18 | * Do not return an empty content. 19 | * This is done, so if a deserialization fails, another can try. Returning empty will always cut the deserialize chain. 20 | * 21 | * @throws IOException If the format can't be parsed. 22 | */ 23 | @NotNull 24 | Collection decode(@NotNull InputStream buffer) throws IOException; 25 | } 26 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/codec/AnimationEncoder.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.codec; 2 | 3 | import dev.kosmx.playerAnim.api.IPlayable; 4 | import net.minecraft.resources.ResourceLocation; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | 10 | @FunctionalInterface 11 | public interface AnimationEncoder { 12 | void encode(@NotNull OutputStream output, @NotNull ResourceLocation location, @NotNull T animation) throws IOException; 13 | } 14 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/codec/EmotecraftGsonCodec.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.codec; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonSyntaxException; 5 | import dev.kosmx.playerAnim.core.data.KeyframeAnimation; 6 | import dev.kosmx.playerAnim.core.data.gson.AnimationJson; 7 | import net.minecraft.resources.ResourceLocation; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.io.OutputStreamWriter; 13 | import java.lang.reflect.Type; 14 | 15 | public class EmotecraftGsonCodec extends AbstractGsonCodec { 16 | 17 | public static final EmotecraftGsonCodec INSTANCE = new EmotecraftGsonCodec(); 18 | 19 | @Override 20 | protected Gson getGson() { 21 | return AnimationJson.GSON; 22 | } 23 | 24 | @Override 25 | protected Type getListedTypeToken() { 26 | return AnimationJson.getListedTypeToken(); 27 | } 28 | 29 | @Override 30 | public @NotNull String getFormatName() { 31 | return "emotecraft"; 32 | } 33 | 34 | @Override 35 | public void encode(@NotNull OutputStream output, @NotNull ResourceLocation location, @NotNull KeyframeAnimation animation) throws IOException { 36 | try (var writer = new OutputStreamWriter(output)) { 37 | writer.write(getGson().toJson(animation)); 38 | } catch (JsonSyntaxException e) { 39 | throw new IOException(e); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/codec/LegacyGeckoJsonCodec.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.codec; 2 | 3 | import com.google.gson.Gson; 4 | import dev.kosmx.playerAnim.core.data.KeyframeAnimation; 5 | import dev.kosmx.playerAnim.core.data.gson.GeckoLibSerializer; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.lang.reflect.Type; 9 | 10 | /** 11 | * Codec for GeckoLib serializing. 12 | * TODO new serializer and format 13 | */ 14 | @Deprecated(forRemoval = false) 15 | public class LegacyGeckoJsonCodec extends AbstractGsonCodec { 16 | 17 | public static final LegacyGeckoJsonCodec INSTANCE = new LegacyGeckoJsonCodec(); 18 | 19 | @Override 20 | protected Gson getGson() { 21 | return GeckoLibSerializer.GSON; 22 | } 23 | 24 | @Override 25 | protected Type getListedTypeToken() { 26 | return GeckoLibSerializer.getListedTypeToken(); 27 | } 28 | 29 | @Override 30 | public @NotNull String getFormatName() { 31 | return "gecko_legacy"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/minecraftApi/layers/LeftHandedHelperModifier.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.minecraftApi.layers; 2 | 3 | import dev.kosmx.playerAnim.api.layered.modifier.MirrorModifier; 4 | import net.minecraft.world.entity.HumanoidArm; 5 | import net.minecraft.world.entity.player.Player; 6 | 7 | /** 8 | * Left-handedness helper 9 | * If enabled, automatically mirror all animation if player is left-handed 10 | */ 11 | public class LeftHandedHelperModifier extends MirrorModifier { 12 | private final Player player; 13 | 14 | public LeftHandedHelperModifier(Player player) { 15 | this.player = player; 16 | } 17 | 18 | @Override 19 | public boolean isEnabled() { 20 | return super.isEnabled() && player.getMainArm() == HumanoidArm.LEFT; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/ArmorFeatureRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 4 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 5 | import dev.kosmx.playerAnim.impl.IUpperPartHelper; 6 | import dev.kosmx.playerAnim.impl.animation.AnimationApplier; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.client.model.HumanoidModel; 9 | import net.minecraft.client.renderer.entity.RenderLayerParent; 10 | import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; 11 | import net.minecraft.client.renderer.entity.layers.RenderLayer; 12 | import net.minecraft.client.resources.model.ModelManager; 13 | import net.minecraft.world.entity.EquipmentSlot; 14 | import net.minecraft.world.entity.LivingEntity; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | import static net.minecraft.world.entity.EquipmentSlot.CHEST; 21 | 22 | @Mixin(HumanoidArmorLayer.class) 23 | public abstract class ArmorFeatureRendererMixin, A extends HumanoidModel> extends RenderLayer { 24 | 25 | protected ArmorFeatureRendererMixin(RenderLayerParent renderLayerParent) { 26 | super(renderLayerParent); 27 | } 28 | 29 | @Inject(method = "", at = @At("RETURN")) 30 | private void initInject(RenderLayerParent context, A leggingsModel, A bodyModel, ModelManager modelManager, CallbackInfo ci) { 31 | ((IUpperPartHelper) this).setUpperPart(false); 32 | } 33 | 34 | @Inject( 35 | method = "setPartVisibility", 36 | at = @At("HEAD"), 37 | cancellable = true 38 | ) 39 | private void modifyArmorVisibility(A humanoidModel, EquipmentSlot equipmentSlot, CallbackInfo ci) { 40 | AnimationApplier emote = ((IAnimatedPlayer) Minecraft.getInstance().player).playerAnimator_getAnimation(); 41 | if (emote.isActive() && emote.getFirstPersonMode() == FirstPersonMode.THIRD_PERSON_MODEL && emote.getFirstPersonConfiguration().isShowArmor() && FirstPersonMode.isFirstPersonPass()) { 42 | humanoidModel.setAllVisible(false); 43 | if (equipmentSlot == CHEST) { 44 | humanoidModel.rightArm.visible = emote.getFirstPersonConfiguration().isShowRightArm(); 45 | humanoidModel.leftArm.visible = emote.getFirstPersonConfiguration().isShowLeftArm(); 46 | humanoidModel.body.visible = false; 47 | } 48 | ci.cancel(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/BipedEntityModelMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.blaze3d.vertex.VertexConsumer; 5 | import dev.kosmx.playerAnim.core.impl.AnimationProcessor; 6 | import dev.kosmx.playerAnim.core.util.SetableSupplier; 7 | import dev.kosmx.playerAnim.impl.Helper; 8 | import dev.kosmx.playerAnim.impl.IMutableModel; 9 | import dev.kosmx.playerAnim.impl.IUpperPartHelper; 10 | import dev.kosmx.playerAnim.impl.animation.IBendHelper; 11 | import net.minecraft.client.model.AgeableListModel; 12 | import net.minecraft.client.model.HumanoidModel; 13 | import net.minecraft.client.model.geom.ModelPart; 14 | import net.minecraft.client.renderer.RenderType; 15 | import net.minecraft.core.Direction; 16 | import net.minecraft.resources.ResourceLocation; 17 | import net.minecraft.world.entity.LivingEntity; 18 | import org.spongepowered.asm.mixin.*; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 22 | 23 | import java.util.function.Function; 24 | 25 | @Mixin(HumanoidModel.class) 26 | public abstract class BipedEntityModelMixin extends AgeableListModel implements IMutableModel { 27 | @Final 28 | @Shadow 29 | public ModelPart rightArm; 30 | @Final 31 | @Shadow 32 | public ModelPart leftArm; 33 | @Unique 34 | private SetableSupplier animation = new SetableSupplier<>(); 35 | 36 | @Inject(method = "(Lnet/minecraft/client/model/geom/ModelPart;Ljava/util/function/Function;)V", at = @At("RETURN")) 37 | private void initBend(ModelPart modelPart, Function function, CallbackInfo ci){ 38 | IBendHelper.INSTANCE.initBend(modelPart.getChild("body"), Direction.DOWN); 39 | IBendHelper.INSTANCE.initBend(modelPart.getChild("right_arm"), Direction.UP); 40 | IBendHelper.INSTANCE.initBend(modelPart.getChild("left_arm"), Direction.UP); 41 | IBendHelper.INSTANCE.initBend(modelPart.getChild("right_leg"), Direction.UP); 42 | IBendHelper.INSTANCE.initBend(modelPart.getChild("left_leg"), Direction.UP); 43 | ((IUpperPartHelper)rightArm).setUpperPart(true); 44 | ((IUpperPartHelper)leftArm).setUpperPart(true); 45 | ((IUpperPartHelper)head).setUpperPart(true); 46 | ((IUpperPartHelper)hat).setUpperPart(true); 47 | } 48 | 49 | @Override 50 | public void setEmoteSupplier(SetableSupplier emoteSupplier){ 51 | this.animation = emoteSupplier; 52 | } 53 | 54 | @Inject(method = "copyPropertiesTo", at = @At("RETURN")) 55 | private void copyMutatedAttributes(HumanoidModel bipedEntityModel, CallbackInfo ci){ 56 | if(animation != null) { 57 | ((IMutableModel) bipedEntityModel).setEmoteSupplier(animation); 58 | } 59 | } 60 | 61 | @Intrinsic(displace = true) 62 | @Override 63 | public void renderToBuffer(PoseStack matrices, VertexConsumer vertices, int light, int overlay, int color){ 64 | if(Helper.isBendEnabled() && this.animation.get() != null && this.animation.get().isActive()){ 65 | this.headParts().forEach((part)->{ 66 | if(! ((IUpperPartHelper) part).isUpperPart()){ 67 | part.render(matrices, vertices, light, overlay, color); 68 | } 69 | }); 70 | this.bodyParts().forEach((part)->{ 71 | if(! ((IUpperPartHelper) part).isUpperPart()){ 72 | part.render(matrices, vertices, light, overlay, color); 73 | } 74 | }); 75 | 76 | SetableSupplier emoteSupplier = this.animation; 77 | matrices.pushPose(); 78 | IBendHelper.rotateMatrixStack(matrices, emoteSupplier.get().getBend("body")); 79 | this.headParts().forEach((part)->{ 80 | if(((IUpperPartHelper) part).isUpperPart()){ 81 | part.render(matrices, vertices, light, overlay, color); 82 | } 83 | }); 84 | this.bodyParts().forEach((part)->{ 85 | if(((IUpperPartHelper) part).isUpperPart()){ 86 | part.render(matrices, vertices, light, overlay, color); 87 | } 88 | }); 89 | matrices.popPose(); 90 | } else super.renderToBuffer(matrices, vertices, light, overlay, color); 91 | } 92 | 93 | @Final 94 | @Shadow public ModelPart body; 95 | 96 | @Shadow @Final public ModelPart head; 97 | 98 | @Shadow @Final public ModelPart hat; 99 | 100 | @Override 101 | public SetableSupplier getEmoteSupplier(){ 102 | return animation; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/CapeLayerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import com.llamalad7.mixinextras.sugar.Local; 5 | import com.mojang.blaze3d.vertex.PoseStack; 6 | import com.mojang.math.Axis; 7 | import dev.kosmx.playerAnim.core.util.Pair; 8 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 9 | import dev.kosmx.playerAnim.impl.animation.AnimationApplier; 10 | import dev.kosmx.playerAnim.impl.animation.IBendHelper; 11 | import net.minecraft.client.model.PlayerModel; 12 | import net.minecraft.client.model.geom.ModelPart; 13 | import net.minecraft.client.player.AbstractClientPlayer; 14 | import net.minecraft.client.renderer.MultiBufferSource; 15 | import net.minecraft.client.renderer.entity.RenderLayerParent; 16 | import net.minecraft.client.renderer.entity.layers.CapeLayer; 17 | import net.minecraft.client.renderer.entity.layers.RenderLayer; 18 | import org.joml.Quaternionf; 19 | import org.spongepowered.asm.mixin.Mixin; 20 | import org.spongepowered.asm.mixin.injection.At; 21 | import org.spongepowered.asm.mixin.injection.Inject; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | @Mixin(CapeLayer.class) 25 | public abstract class CapeLayerMixin extends RenderLayer> { 26 | public CapeLayerMixin(RenderLayerParent> renderLayerParent) { 27 | super(renderLayerParent); 28 | } 29 | 30 | @Inject(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/player/AbstractClientPlayer;FFFFFF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/PlayerModel;renderCloak(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;II)V")) 31 | private void render(PoseStack poseStack, MultiBufferSource multiBufferSource, int i, AbstractClientPlayer abstractClientPlayer, float f, float g, float h, float j, float k, float l, CallbackInfo ci) { 32 | AnimationApplier emote = ((IAnimatedPlayer) abstractClientPlayer).playerAnimator_getAnimation(); 33 | if (emote.isActive()) { 34 | ModelPart torso = this.getParentModel().body; 35 | Pair torsoBend = emote.getBend("torso"); 36 | Pair bodyBend = emote.getBend("body"); 37 | Pair bend = new Pair<>(torsoBend.getLeft() + bodyBend.getLeft(), torsoBend.getRight() + bodyBend.getRight()); 38 | 39 | poseStack.translate(torso.x / 16, torso.y / 16, torso.z / 16); 40 | poseStack.mulPose((new Quaternionf()).rotateXYZ(torso.xRot, torso.yRot, torso.zRot)); 41 | IBendHelper.rotateMatrixStack(poseStack, torsoBend); 42 | poseStack.translate(0.0F, 0.0F, 0.125F); 43 | poseStack.mulPose(Axis.YP.rotationDegrees(180)); 44 | 45 | ModelPart cape = ((PlayerModelAccessor)this.getParentModel()).getCloak(); 46 | cape.x = 0; 47 | cape.y = 0; 48 | cape.z = 0; 49 | cape.xRot = 0; 50 | cape.yRot = 0; 51 | cape.zRot = 0; 52 | 53 | IBendHelper.INSTANCE.bend(cape, bend); 54 | } 55 | else { 56 | IBendHelper.INSTANCE.bend(((PlayerModelAccessor)this.getParentModel()).getCloak(), null); 57 | } 58 | } 59 | 60 | @WrapWithCondition(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/player/AbstractClientPlayer;FFFFFF)V", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V")) 61 | private boolean mulPose(PoseStack instance, Quaternionf quaternionf, @Local(argsOnly = true) AbstractClientPlayer abstractClientPlayer) { 62 | AnimationApplier emote = ((IAnimatedPlayer) abstractClientPlayer).playerAnimator_getAnimation(); 63 | return !emote.isActive(); 64 | } 65 | 66 | @WrapWithCondition(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/player/AbstractClientPlayer;FFFFFF)V", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;translate(FFF)V")) 67 | private boolean translate(PoseStack instance, float f, float g, float h, @Local(argsOnly = true) AbstractClientPlayer abstractClientPlayer) { 68 | AnimationApplier emote = ((IAnimatedPlayer) abstractClientPlayer).playerAnimator_getAnimation(); 69 | return !emote.isActive(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/ElytraLayerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.math.Axis; 5 | import dev.kosmx.playerAnim.api.TransformType; 6 | import dev.kosmx.playerAnim.core.util.Vec3f; 7 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 8 | import dev.kosmx.playerAnim.impl.animation.AnimationApplier; 9 | import dev.kosmx.playerAnim.impl.animation.IBendHelper; 10 | import net.minecraft.client.model.EntityModel; 11 | import net.minecraft.client.player.AbstractClientPlayer; 12 | import net.minecraft.client.renderer.MultiBufferSource; 13 | import net.minecraft.client.renderer.entity.layers.ElytraLayer; 14 | import net.minecraft.world.entity.LivingEntity; 15 | import org.joml.Quaternionf; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Inject; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | @Mixin(ElytraLayer.class) 22 | public class ElytraLayerMixin> { 23 | @Inject(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/entity/LivingEntity;FFFFFF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ElytraModel;renderToBuffer(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;II)V")) 24 | private void inject(PoseStack poseStack, MultiBufferSource multiBufferSource, int i, T livingEntity, float f, float g, float h, float j, float k, float l, CallbackInfo ci) { 25 | if (livingEntity instanceof AbstractClientPlayer) { 26 | AnimationApplier emote = ((IAnimatedPlayer) livingEntity).playerAnimator_getAnimation(); 27 | if (emote.isActive()) { 28 | Vec3f translation = emote.get3DTransform("torso", TransformType.POSITION, Vec3f.ZERO); 29 | Vec3f rotation = emote.get3DTransform("torso", TransformType.ROTATION, Vec3f.ZERO); 30 | poseStack.translate(translation.getX() / 16, translation.getY() / 16, translation.getZ() / 16); 31 | poseStack.translate(0.0F, 0.0F, -0.125F); 32 | poseStack.mulPose((new Quaternionf()).rotateXYZ(rotation.getX(), rotation.getY(), rotation.getZ())); 33 | IBendHelper.rotateMatrixStack(poseStack, emote.getBend("torso")); 34 | poseStack.translate(0.0F, 0.0F, 0.125F); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/FeatureRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import dev.kosmx.playerAnim.impl.IUpperPartHelper; 4 | import net.minecraft.client.renderer.entity.RenderLayerParent; 5 | import net.minecraft.client.renderer.entity.layers.RenderLayer; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Unique; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(RenderLayer.class) 13 | public class FeatureRendererMixin implements IUpperPartHelper { 14 | @Unique 15 | private boolean isUpperPart = true; 16 | 17 | 18 | @Inject(method = "", at = @At("RETURN")) 19 | private void init(RenderLayerParent renderLayerParent, CallbackInfo ci) { 20 | if (this.getClass().getPackageName().contains("skinlayers") && !this.getClass().getSimpleName().toLowerCase().contains("head")) { 21 | isUpperPart = false; 22 | } 23 | } 24 | 25 | @Override 26 | public boolean isUpperPart() { 27 | return this.isUpperPart; 28 | } 29 | 30 | @Override 31 | public void setUpperPart(boolean bl) { 32 | this.isUpperPart = bl; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/HeldItemMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import dev.kosmx.playerAnim.api.TransformType; 5 | import dev.kosmx.playerAnim.core.impl.AnimationProcessor; 6 | import dev.kosmx.playerAnim.core.util.Pair; 7 | import dev.kosmx.playerAnim.core.util.Vec3f; 8 | import dev.kosmx.playerAnim.impl.Helper; 9 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 10 | import net.minecraft.client.renderer.MultiBufferSource; 11 | import net.minecraft.client.renderer.entity.layers.ItemInHandLayer; 12 | import net.minecraft.world.entity.HumanoidArm; 13 | import net.minecraft.world.entity.LivingEntity; 14 | import net.minecraft.world.item.ItemDisplayContext; 15 | import net.minecraft.world.item.ItemStack; 16 | import org.joml.Quaternionf; 17 | import org.joml.Vector3f; 18 | import org.spongepowered.asm.mixin.Mixin; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 22 | 23 | @Mixin(ItemInHandLayer.class) 24 | public class HeldItemMixin { 25 | 26 | @Inject(method = "renderArmWithItem", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 0)) 27 | private void renderMixin(LivingEntity livingEntity, ItemStack stack, ItemDisplayContext itemDisplayContext, HumanoidArm arm, PoseStack matrices, MultiBufferSource vertexConsumers, int light, CallbackInfo ci){ 28 | if(Helper.isBendEnabled() && livingEntity instanceof IAnimatedPlayer player){ 29 | if(player.playerAnimator_getAnimation().isActive()){ 30 | AnimationProcessor anim = player.playerAnimator_getAnimation(); 31 | 32 | Vec3f data = anim.get3DTransform(arm == HumanoidArm.LEFT ? "leftArm" : "rightArm", TransformType.BEND, new Vec3f(0f, 0f, 0f)); 33 | 34 | Pair pair = new Pair<>(data.getX(), data.getY()); 35 | 36 | float offset = 0.25f; 37 | matrices.translate(0, offset, 0); 38 | float bend = pair.getRight(); 39 | float axisf = - pair.getLeft(); 40 | Vector3f axis = new Vector3f((float) Math.cos(axisf), 0, (float) Math.sin(axisf)); 41 | //return this.setRotation(axis.getRadialQuaternion(bend)); 42 | matrices.mulPose(new Quaternionf().rotateAxis(bend, axis)); 43 | matrices.translate(0, - offset, 0); 44 | 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/LivingEntityRenderRedirect_bendOnly.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import dev.kosmx.playerAnim.impl.Helper; 5 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 6 | import dev.kosmx.playerAnim.impl.IUpperPartHelper; 7 | import dev.kosmx.playerAnim.impl.animation.IBendHelper; 8 | import net.minecraft.client.model.EntityModel; 9 | import net.minecraft.client.renderer.MultiBufferSource; 10 | import net.minecraft.client.renderer.entity.EntityRenderer; 11 | import net.minecraft.client.renderer.entity.EntityRendererProvider; 12 | import net.minecraft.client.renderer.entity.LivingEntityRenderer; 13 | import net.minecraft.client.renderer.entity.RenderLayerParent; 14 | import net.minecraft.client.renderer.entity.layers.RenderLayer; 15 | import net.minecraft.world.entity.Entity; 16 | import net.minecraft.world.entity.LivingEntity; 17 | import net.minecraft.world.entity.player.Player; 18 | import org.spongepowered.asm.mixin.Mixin; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.Redirect; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | import java.util.Iterator; 25 | import java.util.List; 26 | 27 | /** 28 | * Compatibility issue: can not redirect {@link RenderLayer#render(PoseStack, MultiBufferSource, int, Entity, float, float, float, float, float, float)} 29 | * I have to modify the matrixStack and do not forget to POP it! 30 | *

31 | * I can inject into the enhanced for 32 | * {@link List#iterator()} //initial push to keep in sync 33 | * {@link Iterator#hasNext()} //to pop the matrix stack 34 | * {@link Iterator#next()} //I can see the modelPart, decide if I need to manipulate it. But push always 35 | * 36 | * @param 37 | * @param 38 | */ 39 | @Mixin(LivingEntityRenderer.class) 40 | public abstract class LivingEntityRenderRedirect_bendOnly> extends EntityRenderer implements RenderLayerParent { 41 | 42 | protected LivingEntityRenderRedirect_bendOnly(EntityRendererProvider.Context context) { 43 | super(context); 44 | } 45 | 46 | @Inject(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", 47 | at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;")) 48 | private void initialPush(LivingEntity livingEntity, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci){ 49 | if (Helper.isBendEnabled()) poseStack.pushPose(); 50 | } 51 | 52 | @Inject(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", 53 | at = @At(value = "INVOKE", target = "Ljava/util/Iterator;hasNext()Z")) 54 | private void popMatrixStack(LivingEntity livingEntity, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci){ 55 | if (Helper.isBendEnabled()) poseStack.popPose(); 56 | } 57 | 58 | @Redirect(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", 59 | at = @At(value = "INVOKE", target = "Ljava/util/Iterator;next()Ljava/lang/Object;")) 60 | private Object transformMatrixStack(Iterator> instance, LivingEntity livingEntity, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i){ 61 | if (Helper.isBendEnabled()) { 62 | poseStack.pushPose(); 63 | RenderLayer layer = instance.next(); 64 | if (livingEntity instanceof Player && livingEntity instanceof IAnimatedPlayer && ((IAnimatedPlayer) livingEntity).playerAnimator_getAnimation().isActive() && ((IUpperPartHelper) layer).isUpperPart()) { 65 | IBendHelper.rotateMatrixStack(poseStack, ((IAnimatedPlayer) livingEntity).playerAnimator_getAnimation().getBend("body")); 66 | } 67 | return layer; 68 | } else { 69 | return instance.next(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/ModelPartMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import dev.kosmx.playerAnim.impl.IUpperPartHelper; 4 | import net.minecraft.client.model.geom.ModelPart; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | 7 | @Mixin(ModelPart.class) 8 | public class ModelPartMixin implements IUpperPartHelper { 9 | private boolean Emotecraft_upper = false; 10 | 11 | @Override 12 | public boolean isUpperPart() { 13 | return Emotecraft_upper; 14 | } 15 | 16 | @Override 17 | public void setUpperPart(boolean bl) { 18 | Emotecraft_upper = bl; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/PlayerEntityMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import dev.kosmx.playerAnim.api.layered.AnimationStack; 4 | import dev.kosmx.playerAnim.api.layered.IAnimation; 5 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 6 | import dev.kosmx.playerAnim.impl.animation.AnimationApplier; 7 | import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; 8 | import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory; 9 | import net.minecraft.client.player.AbstractClientPlayer; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraft.world.entity.player.Player; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | @Mixin(Player.class) 24 | public abstract class PlayerEntityMixin implements IAnimatedPlayer { 25 | 26 | //Unique params might be renamed 27 | @Unique 28 | private final Map modAnimationData = new HashMap<>(); 29 | @Unique 30 | private final AnimationStack animationStack = createAnimationStack(); 31 | @Unique 32 | private final AnimationApplier animationApplier = new AnimationApplier(animationStack); 33 | 34 | 35 | 36 | @SuppressWarnings("ConstantConditions") 37 | @Unique 38 | private AnimationStack createAnimationStack() { 39 | AnimationStack stack = new AnimationStack(); 40 | if (AbstractClientPlayer.class.isInstance(this)) { 41 | PlayerAnimationFactory.ANIMATION_DATA_FACTORY.prepareAnimations((AbstractClientPlayer)(Object) this, stack, modAnimationData); 42 | PlayerAnimationAccess.REGISTER_ANIMATION_EVENT.invoker().registerAnimation((AbstractClientPlayer)(Object) this, stack); 43 | } 44 | return stack; 45 | } 46 | 47 | @Override 48 | public AnimationStack getAnimationStack() { 49 | return animationStack; 50 | } 51 | 52 | @Override 53 | public AnimationApplier playerAnimator_getAnimation() { 54 | return animationApplier; 55 | } 56 | 57 | @Override 58 | public @Nullable IAnimation playerAnimator_getAnimation(@NotNull ResourceLocation id) { 59 | return modAnimationData.get(id); 60 | } 61 | 62 | @Override 63 | public @Nullable IAnimation playerAnimator_setAnimation(@NotNull ResourceLocation id, @Nullable IAnimation animation) { 64 | if (animation == null) { 65 | return modAnimationData.remove(id); 66 | } else { 67 | return modAnimationData.put(id, animation); 68 | } 69 | } 70 | 71 | @SuppressWarnings("ConstantConditions") // When injected into PlayerEntity, instance check can tell if a ClientPlayer or ServerPlayer 72 | @Inject(method = "tick", at = @At("HEAD")) 73 | private void tick(CallbackInfo ci) { 74 | if (AbstractClientPlayer.class.isInstance(this)) { 75 | animationStack.tick(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/PlayerModelAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin; 2 | 3 | import net.minecraft.client.model.PlayerModel; 4 | import net.minecraft.client.model.geom.ModelPart; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(PlayerModel.class) 9 | public interface PlayerModelAccessor { 10 | @Accessor 11 | ModelPart getCloak(); 12 | } 13 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/firstPerson/CameraAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin.firstPerson; 2 | 3 | import net.minecraft.client.Camera; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(Camera.class) 8 | public interface CameraAccessor { 9 | @Accessor 10 | public void setDetached(boolean value); 11 | } 12 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/firstPerson/EntityRenderDispatcherMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin.firstPerson; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 5 | import net.minecraft.client.renderer.MultiBufferSource; 6 | import net.minecraft.client.renderer.entity.EntityRenderDispatcher; 7 | import net.minecraft.world.entity.Entity; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.level.LevelReader; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(EntityRenderDispatcher.class) 16 | public class EntityRenderDispatcherMixin { 17 | @Inject(method = "renderShadow", at = @At("HEAD"), cancellable = true) 18 | private static void renderShadow_HEAD_PlayerAnimator(PoseStack matrices, MultiBufferSource vertexConsumers, Entity entity, float opacity, float tickDelta, LevelReader world, float radius, CallbackInfo ci) { 19 | if (entity instanceof Player && FirstPersonMode.isFirstPersonPass()) { 20 | // Shadow doesn't render in first person, 21 | // so we don't want to make it appear during first person animation 22 | ci.cancel(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/firstPerson/ItemInHandRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin.firstPerson; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.math.Axis; 5 | import dev.kosmx.playerAnim.api.TransformType; 6 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 7 | import dev.kosmx.playerAnim.core.impl.AnimationProcessor; 8 | import dev.kosmx.playerAnim.core.util.Vec3f; 9 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 10 | import net.minecraft.client.Minecraft; 11 | import net.minecraft.client.model.geom.ModelPart; 12 | import net.minecraft.client.player.LocalPlayer; 13 | import net.minecraft.client.renderer.ItemInHandRenderer; 14 | import net.minecraft.client.renderer.MultiBufferSource; 15 | import net.minecraft.world.entity.LivingEntity; 16 | import net.minecraft.world.item.ItemDisplayContext; 17 | import net.minecraft.world.item.ItemStack; 18 | import org.spongepowered.asm.mixin.Mixin; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 22 | 23 | @Mixin(ItemInHandRenderer.class) 24 | public class ItemInHandRendererMixin { 25 | @Inject(method = "renderHandsWithItems", at = @At("HEAD"), cancellable = true) 26 | private void disableDefaultItemIfNeeded(float f, PoseStack poseStack, MultiBufferSource.BufferSource bufferSource, LocalPlayer localPlayer, int i, CallbackInfo ci) { 27 | if (localPlayer instanceof IAnimatedPlayer player && (player.playerAnimator_getAnimation().getFirstPersonMode() == FirstPersonMode.THIRD_PERSON_MODEL)) { 28 | ci.cancel(); 29 | } 30 | 31 | } 32 | 33 | /* AW needed, I may do it later 34 | @Redirect(method = "renderHandsWithItems", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemInHandRenderer;evaluateWhichHandsToRender(Lnet/minecraft/client/player/LocalPlayer;)Lnet/minecraft/client/renderer/ItemInHandRenderer$HandRenderSelection;")) 35 | private ItemInHandRenderer.HandRenderSelection selectHandsToRender(LocalPlayer localPlayer) { 36 | 37 | return null; 38 | }*/ 39 | 40 | @Inject(method = "renderItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderStatic(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;ZLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;Lnet/minecraft/world/level/Level;III)V"), cancellable = true) 41 | private void cancelItemRender(LivingEntity entity, ItemStack itemStack, ItemDisplayContext transformType, boolean bl, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci) { 42 | if (entity != Minecraft.getInstance().getCameraEntity()) { 43 | return; 44 | } 45 | if (FirstPersonMode.isFirstPersonPass() && entity instanceof IAnimatedPlayer player) { 46 | var config = player.playerAnimator_getAnimation().getFirstPersonConfiguration(); 47 | if (transformType == ItemDisplayContext.FIRST_PERSON_RIGHT_HAND || transformType == ItemDisplayContext.THIRD_PERSON_RIGHT_HAND) { 48 | if (!config.isShowRightItem()) { 49 | ci.cancel(); 50 | } 51 | } else { 52 | if (!config.isShowLeftItem()) { 53 | ci.cancel(); 54 | } 55 | } 56 | } 57 | } 58 | 59 | @Inject(method = "renderItem", at = @At("HEAD")) 60 | void changeItemLocation( 61 | LivingEntity livingEntity, 62 | ItemStack itemStack, 63 | ItemDisplayContext itemDisplayContext, 64 | boolean bl, 65 | PoseStack poseStack, 66 | MultiBufferSource multiBufferSource, 67 | int i, 68 | CallbackInfo ci 69 | ) { 70 | if(livingEntity instanceof IAnimatedPlayer player) { 71 | if (player.playerAnimator_getAnimation().isActive()) { 72 | AnimationProcessor anim = player.playerAnimator_getAnimation(); 73 | 74 | Vec3f scale = anim.get3DTransform(bl ? "leftItem" : "rightItem", TransformType.SCALE, 75 | new Vec3f(ModelPart.DEFAULT_SCALE, ModelPart.DEFAULT_SCALE, ModelPart.DEFAULT_SCALE) 76 | ); 77 | Vec3f rot = anim.get3DTransform(bl ? "leftItem" : "rightItem", TransformType.ROTATION, Vec3f.ZERO); 78 | Vec3f pos = anim.get3DTransform(bl ? "leftItem" : "rightItem", TransformType.POSITION, Vec3f.ZERO).scale(1/16f); 79 | 80 | poseStack.scale(scale.getX(), scale.getY(), scale.getZ()); 81 | poseStack.translate(pos.getX(), pos.getY(), pos.getZ()); 82 | 83 | poseStack.mulPose(Axis.ZP.rotation(rot.getZ())); //roll 84 | poseStack.mulPose(Axis.YP.rotation(rot.getY())); //pitch 85 | poseStack.mulPose(Axis.XP.rotation(rot.getX())); //yaw 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/firstPerson/LevelRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin.firstPerson; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 5 | import dev.kosmx.playerAnim.impl.IAnimatedPlayer; 6 | import net.minecraft.client.Camera; 7 | import net.minecraft.client.DeltaTracker; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.renderer.GameRenderer; 10 | import net.minecraft.client.renderer.LevelRenderer; 11 | import net.minecraft.client.renderer.LightTexture; 12 | import net.minecraft.client.renderer.MultiBufferSource; 13 | import net.minecraft.world.entity.Entity; 14 | import net.minecraft.world.entity.LivingEntity; 15 | import org.joml.Matrix4f; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Unique; 18 | import org.spongepowered.asm.mixin.injection.At; 19 | import org.spongepowered.asm.mixin.injection.Inject; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | 22 | @Mixin(LevelRenderer.class) 23 | public class LevelRendererMixin { 24 | 25 | @Unique 26 | private boolean defaultCameraState = false; 27 | 28 | // @Redirect(at = @At(target = "Lnet/minecraft/client/Camera;isDetached()Z")) is forbidden 29 | 30 | @Inject(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;isDetached()Z")) 31 | private void fakeThirdPersonMode(DeltaTracker deltaTracker, boolean bl, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f matrix4f, Matrix4f matrix4f2, CallbackInfo ci) { 32 | // mods may need to redirect that method, I want to avoid compatibility issues as long as possible 33 | defaultCameraState = camera.isDetached(); 34 | if (camera.getEntity() instanceof IAnimatedPlayer player && (player.playerAnimator_getAnimation().getFirstPersonMode() == FirstPersonMode.THIRD_PERSON_MODEL)) { 35 | FirstPersonMode.setFirstPersonPass(!camera.isDetached() && (!(camera.getEntity() instanceof LivingEntity) || !((LivingEntity) camera.getEntity()).isSleeping())); // this will cause a lot of pain 36 | ((CameraAccessor) camera).setDetached(true); 37 | } 38 | } 39 | 40 | @Inject(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;isDetached()Z", shift = At.Shift.AFTER)) 41 | private void resetThirdPerson(DeltaTracker deltaTracker, boolean bl, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f matrix4f, Matrix4f matrix4f2, CallbackInfo ci) { 42 | ((CameraAccessor) camera).setDetached(defaultCameraState); 43 | } 44 | 45 | 46 | @Inject(method = "renderEntity", at = @At("TAIL")) 47 | private void dontRenderEntity_End(Entity entity, double cameraX, double cameraY, double cameraZ, 48 | float tickDelta, PoseStack matrices, MultiBufferSource vertexConsumers, CallbackInfo ci) { 49 | Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); 50 | if (entity == camera.getEntity()) { 51 | FirstPersonMode.setFirstPersonPass(false); // Unmark this render cycle 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /minecraft/common/src/main/java/dev/kosmx/playerAnim/mixin/firstPerson/LivingEntityRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.mixin.firstPerson; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 5 | import net.minecraft.client.player.LocalPlayer; 6 | import net.minecraft.client.renderer.MultiBufferSource; 7 | import net.minecraft.client.renderer.entity.LivingEntityRenderer; 8 | import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; 9 | import net.minecraft.client.renderer.entity.layers.PlayerItemInHandLayer; 10 | import net.minecraft.world.entity.LivingEntity; 11 | import org.objectweb.asm.Opcodes; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Redirect; 17 | 18 | import java.util.List; 19 | 20 | @Mixin(value = LivingEntityRenderer.class, priority = 2000) 21 | public class LivingEntityRendererMixin { 22 | @Shadow @Final protected List layers; 23 | 24 | @Redirect( 25 | method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", 26 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/LivingEntityRenderer;layers:Ljava/util/List;", opcode = Opcodes.GETFIELD)) 27 | private List filterLayers(LivingEntityRenderer instance, LivingEntity entity, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i) { 28 | if (entity instanceof LocalPlayer && FirstPersonMode.isFirstPersonPass()) { 29 | return layers.stream().filter(layer -> layer instanceof PlayerItemInHandLayer || layer instanceof HumanoidArmorLayer).toList(); 30 | } else return layers; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /minecraft/common/src/main/resources/architectury.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessWidener": "playerAnimator.accesswidener" 3 | } -------------------------------------------------------------------------------- /minecraft/common/src/main/resources/playerAnimator-common.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.kosmx.playerAnim.mixin", 5 | "compatibilityLevel": "JAVA_21", 6 | "plugin": "dev.kosmx.playerAnim.impl.mixin.MixinConfig", 7 | "client": [ 8 | "ArmorFeatureRendererMixin", 9 | "BipedEntityModelMixin", 10 | "CapeLayerMixin", 11 | "ElytraLayerMixin", 12 | "FeatureRendererMixin", 13 | "HeldItemMixin", 14 | "LivingEntityRenderRedirect_bendOnly", 15 | "ModelPartMixin", 16 | "PlayerEntityMixin", 17 | "PlayerModelAccessor", 18 | "PlayerModelMixin", 19 | "PlayerRendererMixin", 20 | "firstPerson.CameraAccessor", 21 | "firstPerson.EntityRenderDispatcherMixin", 22 | "firstPerson.ItemInHandRendererMixin", 23 | "firstPerson.LevelRendererMixin", 24 | "firstPerson.LivingEntityRendererMixin" 25 | ], 26 | "injectors": { 27 | "defaultRequire": 1 28 | } 29 | } -------------------------------------------------------------------------------- /minecraft/common/src/main/resources/playerAnimator.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v2 named 2 | 3 | #I need it to be mixinable and then castable 4 | extendable class net/minecraft/client/model/geom/ModelPart 5 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/dev/kosmx/playerAnim/fabric/client/FabricClientInitializer.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.fabric.client; 2 | 3 | import dev.kosmx.playerAnim.impl.Helper; 4 | import dev.kosmx.playerAnim.impl.compat.skinLayers.SkinLayersTransformer; 5 | import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; 6 | import net.fabricmc.api.ClientModInitializer; 7 | import net.fabricmc.fabric.api.resource.ResourceManagerHelper; 8 | import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; 9 | import net.fabricmc.loader.api.FabricLoader; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraft.server.packs.PackType; 12 | import net.minecraft.server.packs.resources.ResourceManager; 13 | import org.jetbrains.annotations.ApiStatus; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | @ApiStatus.Internal 19 | public class FabricClientInitializer implements ClientModInitializer { 20 | public static final Logger LOGGER = LoggerFactory.getLogger("player-animator"); 21 | 22 | @Override 23 | public void onInitializeClient() { 24 | 25 | if (Helper.isBendEnabled() && FabricLoader.getInstance().isModLoaded("skinlayers")) { 26 | try { 27 | SkinLayersTransformer.init(LOGGER); 28 | } catch(Error e) { 29 | LOGGER.error("Failed to initialize 3D Skin Layers module: " + e.getMessage()); 30 | } 31 | } 32 | 33 | ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() { 34 | @Override 35 | public ResourceLocation getFabricId() { 36 | return ResourceLocation.fromNamespaceAndPath("playeranimator", "animation"); 37 | } 38 | 39 | @Override 40 | public void onResourceManagerReload(@NotNull ResourceManager manager) { 41 | PlayerAnimationRegistry.resourceLoaderCallback(manager); 42 | } 43 | }); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/dev/kosmx/playerAnim/impl/fabric/HelperImpl.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl.fabric; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import org.jetbrains.annotations.ApiStatus; 5 | 6 | @ApiStatus.Internal 7 | public class HelperImpl { 8 | public static boolean isBendyLibPresent() { 9 | return FabricLoader.getInstance().isModLoaded("bendy-lib"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/resources/assets/player-animator/lang/tt_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "modmenu.descriptionTranslation.player-animator": "Уенчыны гади анимацияләү өчен җиңел (авырлык буенча) китапханә" 3 | } 4 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "playeranimator", 4 | "version": "${version}", 5 | "name": "Player Animator", 6 | "description": "A lightweight library to easily animate the player", 7 | "authors": [ 8 | "KosmX " 9 | ], 10 | "contact": { 11 | "homepage": "kosmx.dev", 12 | "sources": "https://github.com/KosmX/fabricPlayerAnimation" 13 | }, 14 | "provides": [ 15 | "player-animator" 16 | ], 17 | "license": "MIT", 18 | "environment": "*", 19 | "entrypoints": { 20 | "client": ["dev.kosmx.playerAnim.fabric.client.FabricClientInitializer"] 21 | }, 22 | "mixins": [ 23 | "playerAnimator-common.mixins.json" 24 | ], 25 | "depends": { 26 | "minecraft": ">=1.21 <1.21.2", 27 | "fabric-resource-loader-v0": "*" 28 | }, 29 | "breaks": { 30 | "bettercombat": "<1.6.0" 31 | }, 32 | "custom": { 33 | "modmenu": { 34 | "badges": ["library"] 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /minecraft/fabric/src/test.md: -------------------------------------------------------------------------------- 1 | # Never name your Fabric testmod sources `test`! 2 | 3 | If you do, IntelliJ won't be able to load those as testmod, and you won't understand why is it still not working. 4 | -------------------------------------------------------------------------------- /minecraft/fabric/src/testmod/java/dev/kosmx/animatorTestmod/PlayerAnimTestmod.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.animatorTestmod; 2 | 3 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonConfiguration; 4 | import dev.kosmx.playerAnim.api.firstPerson.FirstPersonMode; 5 | import dev.kosmx.playerAnim.api.layered.IAnimation; 6 | import dev.kosmx.playerAnim.api.layered.ModifierLayer; 7 | import dev.kosmx.playerAnim.api.layered.modifier.AbstractFadeModifier; 8 | import dev.kosmx.playerAnim.api.layered.modifier.MirrorModifier; 9 | import dev.kosmx.playerAnim.api.layered.modifier.SpeedModifier; 10 | import dev.kosmx.playerAnim.core.util.Ease; 11 | import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; 12 | import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory; 13 | import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; 14 | import net.fabricmc.api.ClientModInitializer; 15 | import net.minecraft.client.Minecraft; 16 | import net.minecraft.client.player.LocalPlayer; 17 | import net.minecraft.resources.ResourceLocation; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.util.Random; 22 | 23 | /** 24 | * Testmod for testing and demonstration purposes. 25 | *
26 | * 27 | * In this dev env I use mojmap (the project was remapped to it when I initially began supporting forge)
28 | * If you want to see what would it like with Yarn,
29 | * use gradlew migrateMappings --mappings "1.19+build.4" or with the latest mapping
30 | * More about migrateMappings on Fabric wiki 31 | * 32 | */ 33 | public class PlayerAnimTestmod implements ClientModInitializer { 34 | public static final Logger LOGGER = LoggerFactory.getLogger("testmod"); 35 | //public static final ModifierLayer testAnimation = new ModifierLayer<>(); //Create an animation container for the main player 36 | //You can create a map for every player or just mixin the data into the playerEntity. 37 | 38 | @Override 39 | public void onInitializeClient() { 40 | LOGGER.warn("Testmod is loading :D"); 41 | 42 | //You might use the EVENT to register new animations, or you can use Mixin. 43 | PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(ResourceLocation.fromNamespaceAndPath("testmod", "animation"), 42, (player) -> { 44 | if (player instanceof LocalPlayer) { 45 | //animationStack.addAnimLayer(42, testAnimation); //Add and save the animation container for later use. 46 | ModifierLayer testAnimation = new ModifierLayer<>(); 47 | 48 | testAnimation.addModifierBefore(new SpeedModifier(0.5f)); //This will be slow 49 | testAnimation.addModifierBefore(new MirrorModifier(true)); //Mirror the animation 50 | return testAnimation; 51 | } 52 | return null; 53 | }); 54 | 55 | PlayerAnimationAccess.REGISTER_ANIMATION_EVENT.register((player, animationStack) -> { 56 | ModifierLayer layer = new ModifierLayer<>(); 57 | animationStack.addAnimLayer(69, layer); 58 | PlayerAnimationAccess.getPlayerAssociatedData(player).set(ResourceLocation.fromNamespaceAndPath("testmod", "test"), layer); 59 | }); 60 | //You can add modifiers to the ModifierLayer. 61 | 62 | 63 | } 64 | 65 | public static void playTestAnimation() { 66 | //Use this for setting an animation without fade 67 | //PlayerAnimTestmod.testAnimation.setAnimation(new KeyframeAnimationPlayer(AnimationRegistry.animations.get("two_handed_vertical_right_right"))); 68 | 69 | ModifierLayer testAnimation; 70 | if (new Random().nextBoolean()) { 71 | testAnimation = (ModifierLayer) PlayerAnimationAccess.getPlayerAssociatedData(Minecraft.getInstance().player).get(ResourceLocation.fromNamespaceAndPath("testmod", "animation")); 72 | } else { 73 | testAnimation = (ModifierLayer) PlayerAnimationAccess.getPlayerAssociatedData(Minecraft.getInstance().player).get(ResourceLocation.fromNamespaceAndPath("testmod", "test")); 74 | } 75 | 76 | if (testAnimation.getAnimation() != null && new Random().nextBoolean()) { 77 | //It will fade out from the current animation, null as newAnimation means no animation. 78 | testAnimation.replaceAnimationWithFade(AbstractFadeModifier.standardFadeIn(20, Ease.LINEAR), null); 79 | } else { 80 | //Fade from current animation to a new one. 81 | //Will not fade if there is no animation currently. 82 | testAnimation.replaceAnimationWithFade(AbstractFadeModifier.functionalFadeIn(20, (modelName, type, value) -> value), 83 | PlayerAnimationRegistry.getAnimation(ResourceLocation.fromNamespaceAndPath("testmod", "two_handed_slash_vertical_right")).playAnimation() 84 | .setFirstPersonMode(FirstPersonMode.THIRD_PERSON_MODEL) 85 | .setFirstPersonConfiguration(new FirstPersonConfiguration().setShowRightArm(true).setShowLeftItem(false)) 86 | ); 87 | } 88 | 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /minecraft/fabric/src/testmod/java/dev/kosmx/animatorTestmod/mixin/ClientPlayerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.animatorTestmod.mixin; 2 | 3 | 4 | import dev.kosmx.animatorTestmod.PlayerAnimTestmod; 5 | import net.minecraft.client.player.LocalPlayer; 6 | import net.minecraft.world.InteractionHand; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | @Mixin(LocalPlayer.class) 11 | public class ClientPlayerMixin { 12 | 13 | 14 | private void playTestAnimation(InteractionHand interactionHand, CallbackInfo ci) { 15 | PlayerAnimTestmod.playTestAnimation(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /minecraft/fabric/src/testmod/java/dev/kosmx/animatorTestmod/mixin/MinecraftClientMixin.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.animatorTestmod.mixin; 2 | 3 | import dev.kosmx.animatorTestmod.PlayerAnimTestmod; 4 | import net.minecraft.client.Minecraft; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 9 | 10 | @Mixin(Minecraft.class) 11 | public abstract class MinecraftClientMixin { 12 | 13 | @Inject(method = "startAttack", at = @At("HEAD"), cancellable = true) 14 | private void ATTACK(CallbackInfoReturnable cir) { 15 | System.out.println("MinecraftClientMixin - startAttack"); 16 | PlayerAnimTestmod.playTestAnimation(); 17 | cir.setReturnValue(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /minecraft/fabric/src/testmod/resources/assets/testmod/player_animations/note.md: -------------------------------------------------------------------------------- 1 | These animations are from [BetterCombat](https://github.com/ZsoltMolnarrr/BetterCombat) 2 | -------------------------------------------------------------------------------- /minecraft/fabric/src/testmod/resources/assets/testmod/player_animations/player.animation.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "1.8.0", 3 | "animations": { 4 | "animation.model.rodHand": { 5 | "loop": true, 6 | "bones": { 7 | "right_arm": { 8 | "rotation": { 9 | "vector": [-70, 0, 0] 10 | }, 11 | "position": { 12 | "vector": [0, -2, -1] 13 | } 14 | } 15 | } 16 | } 17 | }, 18 | "geckolib_format_version": 2 19 | } -------------------------------------------------------------------------------- /minecraft/fabric/src/testmod/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "testmod", 4 | "version": "42", 5 | 6 | "name": "Test mod", 7 | "description": "This is an example description! Tell everyone what your mod is about!", 8 | "authors": [ 9 | "Me!" 10 | ], 11 | "contact": { 12 | "homepage": "https://fabricmc.net/", 13 | "sources": "https://github.com/FabricMC/fabric-example-mod" 14 | }, 15 | 16 | "license": "CC0-1.0", 17 | 18 | "environment": "*", 19 | "entrypoints": { 20 | "client": [ 21 | "dev.kosmx.animatorTestmod.PlayerAnimTestmod" 22 | ] 23 | }, 24 | "mixins": [ 25 | "testmod.mixins.json" 26 | ], 27 | 28 | "depends": { 29 | "fabricloader": ">=0.14.6", 30 | "minecraft": "*", 31 | "java": ">=17" 32 | }, 33 | "suggests": { 34 | "another-mod": "*" 35 | } 36 | } -------------------------------------------------------------------------------- /minecraft/fabric/src/testmod/resources/testmod.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "dev.kosmx.animatorTestmod.mixin", 4 | "compatibilityLevel": "JAVA_16", 5 | "client": [ 6 | "ClientPlayerMixin", 7 | "MinecraftClientMixin" 8 | ], 9 | "mixins": [ 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | } 14 | } -------------------------------------------------------------------------------- /minecraft/forge/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'com.github.johnrengelman.shadow' 3 | apply plugin: 'com.modrinth.minotaur' 4 | apply plugin: 'com.matthewprenger.cursegradle' 5 | 6 | architectury { 7 | platformSetupLoomIde() 8 | neoForge() 9 | } 10 | 11 | loom { 12 | accessWidenerPath = project(":${project.module_name}:common").loom.accessWidenerPath 13 | } 14 | 15 | configurations { 16 | common 17 | shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this. 18 | compileClasspath.extendsFrom common 19 | runtimeClasspath.extendsFrom common 20 | developmentNeoForge.extendsFrom common 21 | } 22 | 23 | dependencies { 24 | neoForge "net.neoforged:neoforge:${rootProject.forge_version}" 25 | 26 | //modApi "dev.architectury:architectury-forge:${rootProject.architectury_version}" 27 | 28 | common(project(path: ":${project.module_name}:common", configuration: "namedElements")) { transitive false } 29 | shadowCommon(project(path: ":${project.module_name}:common", configuration: "transformProductionNeoForge")) { transitive = false } 30 | 31 | common(shadowCommon(project(path: ":coreLib")) {transitive false}) {transitive false} //Why can I nest these? 32 | 33 | //Testing libraries 34 | /* 35 | modLocalRuntime "io.github.kosmx.bendy-lib:bendy-lib-forge:${project.bendy_lib}" 36 | modLocalRuntime "maven.modrinth:3dskinlayers:1.5.2-forge-1.19" 37 | modLocalRuntime "maven.modrinth:emotecraft:2.2.6-SNAPSHOT-build.44-MC1.19.2-forge" 38 | //*/ 39 | } 40 | 41 | project.archivesBaseName = rootProject.archives_base_name + "-" + project.name 42 | 43 | processResources { 44 | inputs.property "version", project.version 45 | 46 | filesMatching("META-INF/neoforge.mods.toml") { 47 | expand "version": project.version 48 | } 49 | } 50 | 51 | shadowJar { 52 | exclude "fabric.mod.json" 53 | exclude "architectury.common.json" 54 | 55 | configurations = [project.configurations.shadowCommon] 56 | archiveClassifier.set("dev-shadow") 57 | } 58 | 59 | remapJar { 60 | atAccessWideners.add(loom.accessWidenerPath.get().asFile.name) 61 | 62 | inputFile.set shadowJar.archiveFile 63 | dependsOn shadowJar 64 | archiveClassifier.set(null) 65 | } 66 | 67 | jar { 68 | archiveClassifier.set("dev") 69 | } 70 | 71 | sourcesJar { 72 | def commonSources = project(":${project.module_name}:common").sourcesJar 73 | dependsOn commonSources 74 | from commonSources.archiveFile.map { zipTree(it) } 75 | 76 | def apiSources = project(":coreLib").sourcesJar 77 | dependsOn apiSources 78 | from apiSources.archiveFile.map { zipTree(it) } 79 | } 80 | 81 | components.java { 82 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 83 | skip() 84 | } 85 | } 86 | 87 | publishing { 88 | publications { 89 | mavenNeoForge(MavenPublication) { 90 | artifactId = rootProject.archives_base_name + "-" + project.name 91 | from components.java 92 | } 93 | } 94 | 95 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 96 | repositories { 97 | repositories { 98 | 99 | if (project.keysExists) { 100 | maven { 101 | url = 'https://maven.kosmx.dev/' 102 | credentials { 103 | username = project.keys.kosmx_maven_user 104 | password = project.keys.kosmx_maven 105 | } 106 | } 107 | maven { 108 | name = "GitHubPackages" 109 | url = "https://maven.pkg.github.com/kosmx/minecraftPlayerAnimator" 110 | credentials { 111 | username = System.getenv("GITHUB_ACTOR") 112 | password = System.getenv("GITHUB_TOKEN") 113 | } 114 | } 115 | } else { 116 | mavenLocal() 117 | } 118 | } 119 | } 120 | } 121 | 122 | 123 | if(keysExists) { 124 | modrinth { 125 | versionType = project.cfType 126 | 127 | uploadFile = remapJar 128 | 129 | token = project.keys.modrinth_token 130 | 131 | projectId = "playeranimator" 132 | 133 | versionNumber = "${rootProject.mod_version}-forge" 134 | versionName = "${rootProject.mod_version}-forge" 135 | 136 | gameVersions = ["1.21", "1.21.1"] 137 | changelog = changes 138 | loaders = ["neoforge"] 139 | failSilently = false 140 | 141 | dependencies { 142 | optional.project "bendy-lib" 143 | } 144 | } 145 | 146 | curseforge { 147 | apiKey = project.keys.curseforge_key 148 | 149 | project { 150 | id = '658587' 151 | changelogType = "markdown" 152 | //changelog = '[See on Github](https://github.com/KosmX/emotes/commits/master)' 153 | changelog = changes 154 | releaseType = project.cfType 155 | addGameVersion "1.21" 156 | addGameVersion "1.21.1" 157 | addGameVersion "NeoForge" 158 | 159 | 160 | relations { 161 | optionalDependency 'bendy-lib' 162 | } 163 | 164 | options { 165 | forgeGradleIntegration = false 166 | javaVersionAutoDetect = false // defaults to true 167 | } 168 | 169 | 170 | mainArtifact(remapJar) 171 | }//*/ 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /minecraft/forge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=neoforge -------------------------------------------------------------------------------- /minecraft/forge/src/main/java/dev/kosmx/playerAnim/forge/ForgeClientEvent.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.forge; 2 | 3 | import dev.kosmx.playerAnim.impl.Helper; 4 | import dev.kosmx.playerAnim.impl.compat.skinLayers.SkinLayersTransformer; 5 | import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; 6 | import net.minecraft.server.packs.resources.ResourceManagerReloadListener; 7 | import net.neoforged.api.distmarker.Dist; 8 | import net.neoforged.bus.api.IEventBus; 9 | import net.neoforged.fml.ModList; 10 | import net.neoforged.fml.common.Mod; 11 | import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; 12 | import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | @Mod(value = "playeranimator", dist = Dist.CLIENT) 18 | public class ForgeClientEvent { 19 | public static final Logger LOGGER = LoggerFactory.getLogger("player-animator"); 20 | 21 | public ForgeClientEvent(IEventBus bus) { 22 | bus.addListener(this::resourceLoadingListener); 23 | bus.addListener(this::clientSetup); 24 | } 25 | 26 | public void clientSetup(FMLClientSetupEvent event) { 27 | if (Helper.isBendEnabled() && ModList.get().isLoaded("skinlayers3d")) { 28 | try { 29 | SkinLayersTransformer.init(ForgeClientEvent.LOGGER); 30 | } catch(Error e) { 31 | ForgeClientEvent.LOGGER.error("Failed to initialize 3D skin layers compat: " + e.getMessage()); 32 | } 33 | } 34 | } 35 | 36 | public void resourceLoadingListener(@NotNull RegisterClientReloadListenersEvent event) { 37 | event.registerReloadListener((ResourceManagerReloadListener) PlayerAnimationRegistry::resourceLoaderCallback); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /minecraft/forge/src/main/java/dev/kosmx/playerAnim/impl/neoforge/HelperImpl.java: -------------------------------------------------------------------------------- 1 | package dev.kosmx.playerAnim.impl.neoforge; 2 | 3 | import net.neoforged.fml.loading.LoadingModList; 4 | import org.jetbrains.annotations.ApiStatus; 5 | 6 | @ApiStatus.Internal 7 | public class HelperImpl { 8 | public static boolean isBendyLibPresent() { 9 | return LoadingModList.get().getModFileById("bendylib") != null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /minecraft/forge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "*" 3 | #issueTrackerURL = "" 4 | license = "MIT" 5 | 6 | [[mods]] 7 | modId = "playeranimator" 8 | version = "${version}" 9 | displayURL = "https://github.com/KosmX/minecraftPlayerAnimator" 10 | displayName = "Player Animator" 11 | authors = "KosmX" 12 | description = "A lightweight library to easily animate the player" 13 | #logoFile = "" 14 | 15 | [[mixins]] 16 | config = "playerAnimator-common.mixins.json" 17 | 18 | [[dependencies.playeranimator]] 19 | modId = "minecraft" 20 | type = "required" 21 | mandatory = true 22 | versionRange = "[1.21,1.21.2)" 23 | ordering = "NONE" 24 | side = "BOTH" 25 | -------------------------------------------------------------------------------- /minecraft/gradle.properties: -------------------------------------------------------------------------------- 1 | module_name = minecraft 2 | java_version = 21 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url "https://maven.fabricmc.net/" } 4 | maven { url "https://maven.architectury.dev/" } 5 | maven { url "https://maven.neoforged.net/releases" } 6 | gradlePluginPortal() 7 | } 8 | } 9 | plugins { 10 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' 11 | } 12 | 13 | rootProject.name = "playerAnimator" 14 | 15 | include 'coreLib' 16 | 17 | include 'minecraft' 18 | 19 | include "minecraft:common" 20 | include "minecraft:fabric" 21 | include "minecraft:forge" 22 | --------------------------------------------------------------------------------