├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── build-extensions.kt ├── fabric ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ ├── java │ └── com │ │ └── trainguy9512 │ │ └── locomotion │ │ ├── LocomotionDataGenerator.java │ │ ├── LocomotionFabric.java │ │ └── integration │ │ └── ModMenuIntegration.java │ └── resources │ ├── fabric.mod.json │ └── locomotion-fabric.mixins.json ├── forge ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ ├── java │ └── com │ │ └── trainguy9512 │ │ └── locomotion │ │ └── forge │ │ ├── LocomotionForge.java │ │ └── client │ │ └── LocomotionForgeClient.java │ └── resources │ ├── META-INF │ └── mods.toml │ ├── locomotion-forge.mixins.json │ └── pack.mcmeta ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── neoforge ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ ├── java │ └── com │ │ └── trainguy9512 │ │ └── locomotion │ │ └── neoforge │ │ └── LocomotionNeoForge.java │ └── resources │ ├── META-INF │ └── neoforge.mods.toml │ ├── locomotion-neoforge.mixins.json │ └── pack.mcmeta ├── settings.gradle.kts ├── src └── main │ ├── java │ └── com │ │ └── trainguy9512 │ │ └── locomotion │ │ ├── LocomotionMain.java │ │ ├── access │ │ ├── AlternateSingleBlockRenderer.java │ │ ├── FirstPersonPlayerRendererGetter.java │ │ ├── LivingEntityRenderStateAccess.java │ │ └── MatrixModelPart.java │ │ ├── animation │ │ ├── animator │ │ │ ├── JointAnimator.java │ │ │ ├── JointAnimatorDispatcher.java │ │ │ ├── JointAnimatorRegistry.java │ │ │ └── entity │ │ │ │ ├── EntityJointAnimator.java │ │ │ │ ├── LivingEntityJointAnimator.java │ │ │ │ ├── PlayerPartAnimatorOld.java │ │ │ │ └── firstperson │ │ │ │ ├── FirstPersonAdditiveMovement.java │ │ │ │ ├── FirstPersonAnimationSequences.java │ │ │ │ ├── FirstPersonDrivers.java │ │ │ │ ├── FirstPersonGenericItemPose.java │ │ │ │ ├── FirstPersonHandPose.java │ │ │ │ ├── FirstPersonJointAnimator.java │ │ │ │ ├── FirstPersonMining.java │ │ │ │ ├── FirstPersonMontages.java │ │ │ │ ├── FirstPersonShield.java │ │ │ │ ├── FirstPersonSword.java │ │ │ │ └── FirstPersonTwoHandedActions.java │ │ ├── data │ │ │ ├── AnimationDataContainer.java │ │ │ ├── OnTickDriverContainer.java │ │ │ └── PoseCalculationDataContainer.java │ │ ├── driver │ │ │ ├── Driver.java │ │ │ ├── DriverKey.java │ │ │ ├── SpringDriver.java │ │ │ ├── TimerDriver.java │ │ │ ├── TriggerDriver.java │ │ │ └── VariableDriver.java │ │ ├── joint │ │ │ ├── JointChannel.java │ │ │ ├── Transformer.java │ │ │ └── skeleton │ │ │ │ ├── BlendMask.java │ │ │ │ ├── BlendProfile.java │ │ │ │ ├── JointSkeleton.java │ │ │ │ └── SkeletonPropertyDefinition.java │ │ ├── pose │ │ │ ├── ComponentSpacePose.java │ │ │ ├── LocalSpacePose.java │ │ │ ├── Pose.java │ │ │ └── function │ │ │ │ ├── AnimationPlayer.java │ │ │ │ ├── ApplyAdditiveFunction.java │ │ │ │ ├── BlendPosesFunction.java │ │ │ │ ├── BlendedSequencePlayerFunction.java │ │ │ │ ├── JointTransformerFunction.java │ │ │ │ ├── MakeDynamicAdditiveFunction.java │ │ │ │ ├── MirrorFunction.java │ │ │ │ ├── PoseConversionFunction.java │ │ │ │ ├── PoseFunction.java │ │ │ │ ├── SequenceEvaluatorFunction.java │ │ │ │ ├── SequencePlayerFunction.java │ │ │ │ ├── SequenceReferencePoint.java │ │ │ │ ├── TimeBasedPoseFunction.java │ │ │ │ ├── cache │ │ │ │ ├── CachedPoseContainer.java │ │ │ │ └── CachedPoseFunction.java │ │ │ │ ├── montage │ │ │ │ ├── MontageConfiguration.java │ │ │ │ ├── MontageManager.java │ │ │ │ └── MontageSlotFunction.java │ │ │ │ └── statemachine │ │ │ │ ├── State.java │ │ │ │ ├── StateAlias.java │ │ │ │ ├── StateMachineFunction.java │ │ │ │ └── StateTransition.java │ │ └── sequence │ │ │ └── AnimationSequence.java │ │ ├── config │ │ ├── LocomotionConfig.java │ │ └── LocomotionConfigScreen.java │ │ ├── mixin │ │ ├── debug │ │ │ ├── MixinDebugScreenOverlay.java │ │ │ └── MixinDefaultPlayerSkin.java │ │ ├── game │ │ │ ├── MixinMinecraft.java │ │ │ └── MixinMultiPlayerGameMode.java │ │ ├── item │ │ │ ├── MixinBlocksAttacks.java │ │ │ ├── MixinItemInHandLayer.java │ │ │ ├── MixinItemTransform.java │ │ │ └── property │ │ │ │ ├── MixinCrossbowPull.java │ │ │ │ ├── MixinIsUsingItem.java │ │ │ │ └── MixinUseDuration.java │ │ └── render │ │ │ ├── MixinBlockRenderDispatcher.java │ │ │ ├── MixinEntityRenderDispatcher.java │ │ │ ├── MixinGameRenderer.java │ │ │ ├── MixinItemInHandRenderer.java │ │ │ ├── MixinLivingEntityRenderState.java │ │ │ ├── MixinLivingEntityRenderer.java │ │ │ ├── MixinModelPart.java │ │ │ └── layer │ │ │ ├── MixinCapeLayer.java │ │ │ └── MixinElytraLayer.java │ │ ├── render │ │ └── FirstPersonPlayerRenderer.java │ │ ├── resource │ │ ├── FormatVersion.java │ │ ├── LocomotionResources.java │ │ └── json │ │ │ ├── AnimationSequenceDeserializer.java │ │ │ ├── GsonConfiguration.java │ │ │ ├── JointChannelDeserializer.java │ │ │ ├── JointSkeletonDeserializer.java │ │ │ └── PartPoseDeserializer.java │ │ └── util │ │ ├── Easing.java │ │ ├── Interpolator.java │ │ ├── TimeSpan.java │ │ ├── Timeline.java │ │ └── Transition.java │ ├── python │ └── export_script.py │ └── resources │ ├── architectury.common.json │ ├── assets │ └── locomotion │ │ ├── icon.png │ │ ├── lang │ │ └── en_us.json │ │ ├── sequences │ │ └── entity │ │ │ └── player │ │ │ └── first_person │ │ │ ├── ground_movement │ │ │ ├── falling_down.json │ │ │ ├── falling_in_place.json │ │ │ ├── falling_up.json │ │ │ ├── idle.json │ │ │ ├── jump.json │ │ │ ├── land.json │ │ │ ├── pose.json │ │ │ ├── walk_to_stop.json │ │ │ └── walking.json │ │ │ └── hand │ │ │ ├── bow │ │ │ ├── pose.json │ │ │ ├── pull.json │ │ │ └── release.json │ │ │ ├── crossbow │ │ │ ├── fire.json │ │ │ ├── pose.json │ │ │ ├── raise.json │ │ │ ├── reload.json │ │ │ └── reload_finish.json │ │ │ ├── empty │ │ │ ├── lower.json │ │ │ ├── lowered.json │ │ │ ├── mine_finish.json │ │ │ ├── mine_swing.json │ │ │ ├── pose.json │ │ │ └── raise.json │ │ │ ├── generic_item │ │ │ ├── 2d_item_pose.json │ │ │ ├── arrow_pose.json │ │ │ ├── banner_pose.json │ │ │ ├── block_pose.json │ │ │ ├── door_block_pose.json │ │ │ ├── lower.json │ │ │ ├── raise.json │ │ │ ├── rod_pose.json │ │ │ └── small_block_pose.json │ │ │ ├── shield │ │ │ ├── block_in.json │ │ │ ├── block_out.json │ │ │ ├── disable_in.json │ │ │ ├── disable_out.json │ │ │ ├── impact.json │ │ │ └── pose.json │ │ │ └── tool │ │ │ ├── attack.json │ │ │ ├── lower.json │ │ │ ├── pickaxe │ │ │ ├── mine_finish.json │ │ │ └── mine_swing.json │ │ │ ├── pose.json │ │ │ ├── raise.json │ │ │ ├── sword │ │ │ ├── swing_left.json │ │ │ └── swing_right.json │ │ │ └── use.json │ │ └── skeletons │ │ └── entity │ │ └── player │ │ └── first_person.json │ ├── locomotion-common.mixins.json │ └── locomotion.accesswidener └── stonecutter.gradle.kts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain platforms or Java versions, and provides a first line of defence 4 | # against bad commits. 5 | 6 | name: build 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | java: [ 14 | 21, # Current Java LTS & minimum supported by Minecraft 15 | ] 16 | os: [ubuntu-latest, windows-latest] 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - name: checkout repository 20 | uses: actions/checkout@v4 21 | - name: validate gradle wrapper 22 | uses: gradle/wrapper-validation-action@v1 23 | - name: setup jdk ${{ matrix.java }} 24 | uses: actions/setup-java@v4 25 | with: 26 | java-version: ${{ matrix.java }} 27 | distribution: 'microsoft' 28 | - name: make gradle wrapper executable 29 | if: ${{ runner.os != 'Windows' }} 30 | run: chmod +x ./gradlew 31 | - name: build 32 | run: ./gradlew chiseledBuild 33 | # - name: merge 34 | # run: ./gradlew mergeJars 35 | - name: capture build artifacts 36 | if: ${{ runner.os == 'Linux' && matrix.java == '21' }} 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: Artifacts 40 | path: build/libs/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle ### 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | gradle/wrapper/gradle-wrapper.jar 8 | 9 | build.properties 10 | .architectury-transformer 11 | 12 | ### IntelliJ IDEA ### 13 | .idea/ 14 | .shelf/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | ### Minecraft ### 20 | .runs/ 21 | run/ 22 | 23 | # java 24 | 25 | hs_err_*.log 26 | replay_*.log 27 | *.hprof 28 | *.jfr -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | id("dev.architectury.loom") 5 | id("architectury-plugin") 6 | } 7 | 8 | val minecraft = stonecutter.current.version 9 | 10 | version = "${prop("mod.version")}+$minecraft-playtesting" 11 | base { 12 | archivesName.set("${prop("mod.id")}-common") 13 | } 14 | 15 | architectury.common(stonecutter.tree.branches.mapNotNull { 16 | if (stonecutter.current.project !in it) null 17 | else it.project.prop("loom.platform") 18 | }) 19 | 20 | loom { 21 | silentMojangMappingsLicense() 22 | accessWidenerPath = rootProject.file("src/main/resources/${prop("mod.id")}.accesswidener") 23 | 24 | decompilers { 25 | get("vineflower").apply { // Adds names to lambdas - useful for mixins 26 | options.put("mark-corresponding-synthetics", "1") 27 | } 28 | } 29 | } 30 | 31 | repositories { 32 | maven("https://maven.parchmentmc.org/") 33 | 34 | maven("https://maven.terraformersmc.com/") 35 | maven("https://maven.isxander.dev/releases") 36 | maven("https://api.modrinth.com/maven") 37 | } 38 | 39 | dependencies { 40 | minecraft("com.mojang:minecraft:$minecraft") 41 | mappings(loom.layered { 42 | officialMojangMappings() 43 | parchment("org.parchmentmc.data:parchment-${versionProp("parchment_minecraft_version")}:${versionProp("parchment_mappings_version")}@zip") 44 | }) 45 | modImplementation("net.fabricmc:fabric-loader:${versionProp("fabric_loader")}") 46 | 47 | // Mod implementations 48 | modCompileOnly("dev.isxander:yet-another-config-lib:${versionProp("yacl_version")}-fabric") 49 | } 50 | 51 | tasks.processResources { 52 | applyProperties(project, listOf("${prop("mod.id")}-common.mixin.json")) 53 | } 54 | 55 | java { 56 | withSourcesJar() 57 | val java = if (stonecutter.eval(minecraft, ">=1.20.5")) 58 | JavaVersion.VERSION_21 else JavaVersion.VERSION_17 59 | targetCompatibility = java 60 | sourceCompatibility = java 61 | } 62 | 63 | tasks.build { 64 | group = "versioned" 65 | description = "Must run through 'chiseledBuild'" 66 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | kotlin("jvm") version "2.0.20" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | 11 | dependencies { 12 | // Make sure the version here is the same as the plugin in settings.gradle.kts 13 | implementation("dev.kikugie.stonecutter:dev.kikugie.stonecutter.gradle.plugin:0.5.1") 14 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/build-extensions.kt: -------------------------------------------------------------------------------- 1 | import dev.kikugie.stonecutter.build.StonecutterBuild 2 | import org.gradle.api.Project 3 | import org.gradle.language.jvm.tasks.ProcessResources 4 | 5 | /** 6 | * Retrieves a property value from the project based on the provided key. 7 | * 8 | * @param key the property key to look up in the project's properties. 9 | * @return the value of the property as a string, or null if the property does not exist. 10 | */ 11 | fun Project.prop(key: String): String? = findProperty(key)?.toString() 12 | 13 | /** 14 | * Retrieves a version-specific or wildcard property key from the project's properties. 15 | * 16 | * @param key the specific key to identify the property related to the version or a wildcard. 17 | * @return the value of the property as a string if found, or throws an IllegalArgumentException if the property is missing. 18 | * @throws IllegalArgumentException if neither the version-specific key nor the wildcard key is found. 19 | */ 20 | fun Project.versionProp(key: String): String? { 21 | val specificKey = "version.${stonecutter(project).current.version.replace(".", "_")}.$key" 22 | val wildcardKey = "version.*.$key" 23 | 24 | return when { 25 | project.prop(specificKey) != null -> project.prop(specificKey)!! 26 | project.prop(wildcardKey) != null -> project.prop(wildcardKey)!! 27 | else -> throw IllegalArgumentException("Missing '$specificKey' or '$wildcardKey'") 28 | } 29 | } 30 | 31 | /** 32 | * Retrieves a version-specific or wildcard property key from the project's properties. 33 | * 34 | * @param key the specific key to identify the property related to the version or a wildcard. 35 | * @return the value of the property as a string if found, or null if the property is missing. 36 | */ 37 | fun Project.versionPropOrNull(key: String): String? { 38 | val specificKey = "version.${stonecutter(project).current.version.replace(".", "_")}.$key" 39 | val wildcardKey = "version.*.$key" 40 | 41 | return when { 42 | project.prop(specificKey) != null -> project.prop(specificKey)!! 43 | project.prop(wildcardKey) != null -> project.prop(wildcardKey)!! 44 | else -> null 45 | } 46 | } 47 | 48 | /** 49 | * Applies a set of properties to a `ProcessResources` task, based on the project's configuration 50 | * and additional resource files. 51 | * 52 | * @param project the Gradle project that contains the configuration and properties. 53 | * @param files an iterable collection of file paths to which the properties will be applied. 54 | */ 55 | fun ProcessResources.applyProperties(project: Project, files: Iterable) { 56 | val props = mutableMapOf( 57 | "mod_version" to project.prop("mod.version"), 58 | "mod_group" to project.prop("mod.group"), 59 | "mod_id" to project.prop("mod.id"), 60 | 61 | "mod_name" to project.prop("mod.name"), 62 | "mod_description" to project.prop("mod.description"), 63 | "mod_license" to project.prop("mod.license"), 64 | 65 | "minecraft_version" to stonecutter(project).current.version, 66 | ) 67 | fun addNullable(name: String, value: String?) { 68 | if (value != null) props[name] = value 69 | } 70 | 71 | addNullable("fabric_loader_version", project.versionPropOrNull("fabric_loader")) 72 | addNullable("forge_loader_version", project.versionPropOrNull("forge_loader")) 73 | addNullable("neoforge_loader_version", project.versionPropOrNull("neoforge_loader")) 74 | 75 | inputs.properties(props) 76 | filesMatching(files) { 77 | expand(props) 78 | } 79 | } 80 | 81 | fun stonecutter(project: Project): StonecutterBuild { 82 | return requireNotNull(project.extensions.findByType(StonecutterBuild::class.java)) { "Stonecutter build extension not found" } 83 | } -------------------------------------------------------------------------------- /fabric/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | id("dev.architectury.loom") 5 | id("architectury-plugin") 6 | id("com.github.johnrengelman.shadow") 7 | } 8 | 9 | val loader = prop("loom.platform")!! 10 | val minecraft: String = stonecutter.current.version 11 | val common: Project = requireNotNull(stonecutter.node.sibling("")) { 12 | "No common project for $project" 13 | }.project 14 | 15 | version = "${prop("mod.version")}+$minecraft-playtesting" 16 | base { 17 | archivesName.set("${prop("mod.id")}-$loader") 18 | } 19 | 20 | architectury { 21 | platformSetupLoomIde() 22 | fabric() 23 | } 24 | 25 | loom { 26 | silentMojangMappingsLicense() 27 | 28 | decompilers { 29 | get("vineflower").apply { // Adds names to lambdas - useful for mixins 30 | options.put("mark-corresponding-synthetics", "1") 31 | } 32 | } 33 | 34 | runs { 35 | val runDir = "../../../.runs" 36 | 37 | named("client") { 38 | client() 39 | configName = "Client" 40 | runDir("$runDir/client") 41 | source(sourceSets["main"]) 42 | programArgs("--username=Dev") 43 | } 44 | named("server") { 45 | server() 46 | configName = "Server" 47 | runDir("$runDir/server") 48 | source(sourceSets["main"]) 49 | } 50 | } 51 | 52 | sourceSets { 53 | main { 54 | resources { 55 | srcDir(project.file("versions/$minecraft/src/main/generated")) 56 | } 57 | } 58 | } 59 | 60 | runConfigs.all { 61 | isIdeConfigGenerated = true 62 | } 63 | } 64 | 65 | val commonBundle: Configuration by configurations.creating { 66 | isCanBeConsumed = false 67 | isCanBeResolved = true 68 | } 69 | 70 | val shadowBundle: Configuration by configurations.creating { 71 | isCanBeConsumed = false 72 | isCanBeResolved = true 73 | } 74 | 75 | configurations { 76 | compileClasspath.get().extendsFrom(commonBundle) 77 | runtimeClasspath.get().extendsFrom(commonBundle) 78 | get("developmentFabric").extendsFrom(commonBundle) 79 | } 80 | 81 | repositories { 82 | maven("https://maven.parchmentmc.org/") 83 | 84 | maven("https://maven.terraformersmc.com/") 85 | maven("https://maven.isxander.dev/releases") 86 | maven("https://api.modrinth.com/maven") 87 | } 88 | 89 | dependencies { 90 | minecraft("com.mojang:minecraft:$minecraft") 91 | mappings(loom.layered { 92 | officialMojangMappings() 93 | parchment("org.parchmentmc.data:parchment-${versionProp("parchment_minecraft_version")}:${versionProp("parchment_mappings_version")}@zip") 94 | }) 95 | modImplementation("net.fabricmc:fabric-loader:${versionProp("fabric_loader")}") 96 | 97 | commonBundle(project(common.path, "namedElements")) { isTransitive = false } 98 | shadowBundle(project(common.path, "transformProductionFabric")) { isTransitive = false } 99 | 100 | // Mod implementations 101 | modImplementation("net.fabricmc.fabric-api:fabric-api:${versionProp("fabric_api_version")}") 102 | modImplementation("dev.isxander:yet-another-config-lib:${versionProp("yacl_version")}-fabric") 103 | modImplementation("com.terraformersmc:modmenu:${versionProp("modmenu_version")}") 104 | modImplementation("maven.modrinth:sodium:${versionProp("sodium_version")}") 105 | modImplementation("maven.modrinth:iris:${versionProp("iris_version")}") 106 | 107 | // Iris dependencies 108 | runtimeOnly("org.antlr:antlr4-runtime:4.13.1") 109 | runtimeOnly("io.github.douira:glsl-transformer:2.0.1") 110 | runtimeOnly("org.anarres:jcpp:1.4.14") 111 | } 112 | 113 | tasks.processResources { 114 | applyProperties(project, listOf("fabric.mod.json", "${prop("mod.id")}-fabric.mixin.json")) 115 | } 116 | 117 | tasks.shadowJar { 118 | configurations = listOf(shadowBundle) 119 | archiveClassifier = "dev-shadow" 120 | } 121 | 122 | tasks.remapJar { 123 | injectAccessWidener = true 124 | input = tasks.shadowJar.get().archiveFile 125 | archiveClassifier = null 126 | dependsOn(tasks.shadowJar) 127 | } 128 | 129 | tasks.jar { 130 | archiveClassifier = "dev" 131 | } 132 | 133 | java { 134 | withSourcesJar() 135 | val java = if (stonecutter.eval(minecraft, ">=1.20.5")) 136 | JavaVersion.VERSION_21 else JavaVersion.VERSION_17 137 | targetCompatibility = java 138 | sourceCompatibility = java 139 | } 140 | 141 | tasks.build { 142 | group = "versioned" 143 | description = "Must run through 'chiseledBuild'" 144 | } 145 | 146 | tasks.register("buildAndCollect") { 147 | group = "versioned" 148 | description = "Must run through 'chiseledBuild'" 149 | from(tasks.remapJar.get().archiveFile, tasks.remapSourcesJar.get().archiveFile) 150 | into(rootProject.layout.buildDirectory.file("libs/${prop("mod.version")}/$loader")) 151 | dependsOn("build") 152 | } 153 | 154 | fabricApi { 155 | configureDataGeneration { 156 | client = true 157 | modId = "${prop("mod.id")}" 158 | } 159 | } 160 | 161 | stonecutter { 162 | // Constants should be given a key and a boolean value 163 | const("fabric", loader == "fabric") 164 | const("forge", loader == "forge") 165 | const("neoforge", loader == "neoforge") 166 | } -------------------------------------------------------------------------------- /fabric/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=fabric -------------------------------------------------------------------------------- /fabric/src/main/java/com/trainguy9512/locomotion/LocomotionDataGenerator.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion; 2 | 3 | import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; 4 | import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; 5 | public class LocomotionDataGenerator implements DataGeneratorEntrypoint { 6 | @Override 7 | public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/trainguy9512/locomotion/LocomotionFabric.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion; 2 | 3 | import com.trainguy9512.locomotion.resource.LocomotionResources; 4 | import net.fabricmc.api.ClientModInitializer; 5 | import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; 6 | import net.fabricmc.fabric.api.resource.ResourceManagerHelper; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.server.packs.PackType; 9 | import net.minecraft.server.packs.resources.ResourceManager; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.concurrent.Executor; 14 | 15 | public class LocomotionFabric implements ClientModInitializer { 16 | 17 | private void registerResourceReloader() { 18 | ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new IdentifiableResourceReloadListener() { 19 | @Override 20 | public @NotNull CompletableFuture reload(PreparationBarrier barrier, ResourceManager manager, Executor backgroundExecutor, Executor gameExecutor) { 21 | return LocomotionResources.reload(barrier, manager, backgroundExecutor, gameExecutor); 22 | } 23 | 24 | @Override 25 | public ResourceLocation getFabricId() { 26 | return LocomotionResources.RELOADER_IDENTIFIER; 27 | } 28 | }); 29 | } 30 | 31 | @Override 32 | public void onInitializeClient() { 33 | LocomotionMain.initialize(); 34 | this.registerResourceReloader(); 35 | } 36 | } -------------------------------------------------------------------------------- /fabric/src/main/java/com/trainguy9512/locomotion/integration/ModMenuIntegration.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.integration; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | import com.trainguy9512.locomotion.LocomotionMain; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | 8 | public class ModMenuIntegration implements ModMenuApi { 9 | @Override 10 | public ConfigScreenFactory getModConfigScreenFactory() { 11 | return LocomotionMain.CONFIG.getConfigScreen(FabricLoader.getInstance()::isModLoaded)::apply; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "${mod_id}", 4 | "version": "${mod_version}", 5 | 6 | "name": "${mod_name}", 7 | "description": "${mod_description}", 8 | "authors": [ 9 | "Trainguy9512" 10 | ], 11 | "contact": { 12 | "homepage": "https://github.com/Trainguy9512/trainguys-animation-overhaul/", 13 | "sources": "https://github.com/Trainguy9512/trainguys-animation-overhaul/" 14 | }, 15 | 16 | "license": "LGPLv3", 17 | "icon": "assets/locomotion/icon.png", 18 | 19 | "environment": "*", 20 | "entrypoints": { 21 | "client": [ 22 | "com.trainguy9512.locomotion.LocomotionFabric" 23 | ], 24 | "modmenu": [ 25 | "com.trainguy9512.locomotion.integration.ModMenuIntegration" 26 | ], 27 | "fabric-datagen": [ 28 | "com.trainguy9512.locomotion.LocomotionDataGenerator" 29 | ] 30 | }, 31 | "mixins": [ 32 | "${mod_id}-common.mixins.json", 33 | "${mod_id}-fabric.mixins.json" 34 | ], 35 | 36 | "depends": { 37 | "fabricloader": ">=${fabric_loader_version}", 38 | "fabric-api": "*", 39 | "minecraft": ">=${minecraft_version}" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /fabric/src/main/resources/locomotion-fabric.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "${mod_group}.${mod_id}.fabric.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | ], 7 | "injectors": { 8 | "defaultRequire": 1 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /forge/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | id("dev.architectury.loom") 5 | id("architectury-plugin") 6 | id("com.github.johnrengelman.shadow") 7 | } 8 | 9 | val loader = prop("loom.platform")!! 10 | val minecraft: String = stonecutter.current.version 11 | val common: Project = requireNotNull(stonecutter.node.sibling("")) { 12 | "No common project for $project" 13 | }.project 14 | 15 | version = "${prop("mod.version")}+$minecraft" 16 | base { 17 | archivesName.set("${prop("mod.id")}-$loader") 18 | } 19 | 20 | architectury { 21 | platformSetupLoomIde() 22 | forge() 23 | } 24 | 25 | loom { 26 | silentMojangMappingsLicense() 27 | 28 | decompilers { 29 | get("vineflower").apply { // Adds names to lambdas - useful for mixins 30 | options.put("mark-corresponding-synthetics", "1") 31 | } 32 | } 33 | 34 | runs { 35 | val runDir = "../../../.runs" 36 | 37 | named("client") { 38 | client() 39 | configName = "Client" 40 | runDir("$runDir/client") 41 | source(sourceSets["main"]) 42 | programArgs("--username=Dev") 43 | } 44 | named("server") { 45 | server() 46 | configName = "Server" 47 | runDir("$runDir/server") 48 | source(sourceSets["main"]) 49 | } 50 | } 51 | 52 | runConfigs.all { 53 | isIdeConfigGenerated = true 54 | } 55 | 56 | forge.convertAccessWideners = true 57 | forge.mixinConfigs( 58 | "locomotion-common.mixins.json", 59 | "locomotion-forge.mixins.json", 60 | ) 61 | } 62 | 63 | val commonBundle: Configuration by configurations.creating { 64 | isCanBeConsumed = false 65 | isCanBeResolved = true 66 | } 67 | 68 | val shadowBundle: Configuration by configurations.creating { 69 | isCanBeConsumed = false 70 | isCanBeResolved = true 71 | } 72 | 73 | configurations { 74 | compileClasspath.get().extendsFrom(commonBundle) 75 | runtimeClasspath.get().extendsFrom(commonBundle) 76 | get("developmentForge").extendsFrom(commonBundle) 77 | } 78 | 79 | repositories { 80 | maven("https://maven.parchmentmc.org/") 81 | maven("https://maven.minecraftforge.net") 82 | } 83 | 84 | dependencies { 85 | minecraft("com.mojang:minecraft:$minecraft") 86 | mappings(loom.layered { 87 | officialMojangMappings() 88 | parchment("org.parchmentmc.data:parchment-${versionProp("parchment_minecraft_version")}:${versionProp("parchment_mappings_version")}@zip") 89 | }) 90 | forge("net.minecraftforge:forge:$minecraft-${versionProp("forge_loader")}") 91 | 92 | commonBundle(project(common.path, "namedElements")) { isTransitive = false } 93 | shadowBundle(project(common.path, "transformProductionForge")) { isTransitive = false } 94 | } 95 | 96 | tasks.processResources { 97 | applyProperties(project, listOf("META-INF/mods.toml", "${prop("mod.id")}-forge.mixin.json", "pack.mcmeta")) 98 | } 99 | 100 | tasks.shadowJar { 101 | configurations = listOf(shadowBundle) 102 | archiveClassifier = "dev-shadow" 103 | } 104 | 105 | tasks.remapJar { 106 | injectAccessWidener = true 107 | input = tasks.shadowJar.get().archiveFile 108 | archiveClassifier = null 109 | dependsOn(tasks.shadowJar) 110 | } 111 | 112 | tasks.jar { 113 | archiveClassifier = "dev" 114 | } 115 | 116 | java { 117 | withSourcesJar() 118 | val java = if (stonecutter.eval(minecraft, ">=1.20.5")) 119 | JavaVersion.VERSION_21 else JavaVersion.VERSION_17 120 | targetCompatibility = java 121 | sourceCompatibility = java 122 | } 123 | 124 | tasks.build { 125 | group = "versioned" 126 | description = "Must run through 'chiseledBuild'" 127 | } 128 | 129 | tasks.register("buildAndCollect") { 130 | group = "versioned" 131 | description = "Must run through 'chiseledBuild'" 132 | from(tasks.remapJar.get().archiveFile, tasks.remapSourcesJar.get().archiveFile) 133 | into(rootProject.layout.buildDirectory.file("libs/${prop("mod.version")}}/$loader")) 134 | dependsOn("build") 135 | } 136 | 137 | stonecutter { 138 | // Constants should be given a key and a boolean value 139 | const("fabric", loader == "fabric") 140 | const("forge", loader == "forge") 141 | const("neoforge", loader == "neoforge") 142 | } -------------------------------------------------------------------------------- /forge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=forge -------------------------------------------------------------------------------- /forge/src/main/java/com/trainguy9512/locomotion/forge/LocomotionForge.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.forge; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.animation.data.AnimationSequenceDataLoader; 5 | import net.minecraftforge.api.distmarker.Dist; 6 | import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; 7 | import net.minecraftforge.eventbus.api.SubscribeEvent; 8 | import net.minecraftforge.fml.DistExecutor; 9 | import net.minecraftforge.fml.common.Mod; 10 | 11 | @Mod(LocomotionMain.MOD_ID) 12 | public class LocomotionForge { 13 | public LocomotionForge() { 14 | LocomotionMain.initialize(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /forge/src/main/java/com/trainguy9512/locomotion/forge/client/LocomotionForgeClient.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.forge.client; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.animation.data.AnimationSequenceDataLoader; 5 | import net.minecraftforge.api.distmarker.Dist; 6 | import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; 7 | import net.minecraftforge.eventbus.api.SubscribeEvent; 8 | import net.minecraftforge.fml.common.Mod; 9 | 10 | @Mod.EventBusSubscriber(value = Dist.CLIENT, modid = LocomotionMain.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) 11 | public class LocomotionForgeClient { 12 | 13 | @SubscribeEvent 14 | public static void registerReloadListeners(RegisterClientReloadListenersEvent event) { 15 | event.registerReloadListener(AnimationSequenceDataLoader::reload); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /forge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "*" 3 | license = "${mod_license}" 4 | 5 | [[mods]] 6 | modId = "${mod_id}" 7 | version = "${mod_version}" 8 | displayName = "${mod_name}" 9 | authors = "Me!" 10 | description = "${mod_description}" 11 | logoFile = "assets/${mod_id}/icon.png" 12 | logoBlur = false 13 | 14 | [[dependencies."${mod_id}"]] 15 | modId = "forge" 16 | mandatory = true 17 | versionRange = "[${forge_loader_version},)" 18 | ordering = "NONE" 19 | side = "BOTH" 20 | 21 | [[dependencies."${mod_id}"]] 22 | modId = "minecraft" 23 | mandatory = true 24 | versionRange = "[${minecraft_version},)" 25 | ordering = "NONE" 26 | side = "BOTH" -------------------------------------------------------------------------------- /forge/src/main/resources/locomotion-forge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "${mod_group}.${mod_id}.neoforge.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | ], 7 | "injectors": { 8 | "defaultRequire": 1 9 | } 10 | } -------------------------------------------------------------------------------- /forge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "${mod_name} resources", 4 | "forge:server_data_pack_format": 15, 5 | "pack_format": 15 6 | } 7 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs = -Xmx4G 3 | org.gradle.parallel = false 4 | 5 | # Mod Properties 6 | mod.version = 0.1.4 7 | mod.group = com.trainguy9512 8 | mod.id = locomotion 9 | 10 | mod.name = Locomotion 11 | mod.description = An animation overhaul mod 12 | mod.license = GPLv3 13 | 14 | # Fabric Loader 15 | version.*.fabric_loader = 0.16.10 16 | 17 | # Forge Loader 18 | version.1_21_5.forge_loader = 55.0.3 19 | version.1_21_4.forge_loader = 54.1.0 20 | 21 | # NeoForge Loader 22 | version.1_21_5.neoforge_loader = 21.5.0-beta 23 | version.1_21_4.neoforge_loader = 21.4.124 24 | 25 | # Parchment Minecraft versions 26 | version.1_21_5.parchment_minecraft_version = 1.21.4 27 | version.1_21_4.parchment_minecraft_version = 1.21.4 28 | 29 | # Parchment mapping versions 30 | version.1_21_5.parchment_mappings_version = 2025.03.23 31 | version.1_21_4.parchment_mappings_version = 2025.03.23 32 | 33 | 34 | 35 | # Fabric API 36 | version.1_21_5.fabric_api_version = 0.119.5+1.21.5 37 | version.1_21_4.fabric_api_version = 0.119.2+1.21.4 38 | 39 | # YACL 40 | version.1_21_5.yacl_version = 3.6.6+1.21.5 41 | version.1_21_4.yacl_version = 3.6.6+1.21.4 42 | 43 | # Mod Menu 44 | version.1_21_5.modmenu_version = 14.0.0-rc.1 45 | version.1_21_4.modmenu_version = 13.0.3 46 | 47 | # Sodium 48 | version.1_21_5.sodium_version = mc1.21.5-0.6.13-fabric 49 | version.1_21_4.sodium_version = mc1.21.4-0.6.13-fabric 50 | 51 | # Iris 52 | version.1_21_5.iris_version = 1.8.11+1.21.5-fabric 53 | version.1_21_4.iris_version = 1.8.8+1.21.4-fabric -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trainguy9512/locomotion/030661db62e477756a65be6163e556d4289bf3e4/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.12.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /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 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /neoforge/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | id("dev.architectury.loom") 5 | id("architectury-plugin") 6 | id("com.github.johnrengelman.shadow") 7 | } 8 | 9 | val loader = prop("loom.platform")!! 10 | val minecraft: String = stonecutter.current.version 11 | val common: Project = requireNotNull(stonecutter.node.sibling("")) { 12 | "No common project for $project" 13 | }.project 14 | 15 | version = "${prop("mod.version")}+$minecraft-playtesting" 16 | base { 17 | archivesName.set("${prop("mod.id")}-$loader") 18 | } 19 | 20 | architectury { 21 | platformSetupLoomIde() 22 | neoForge() 23 | } 24 | 25 | loom { 26 | silentMojangMappingsLicense() 27 | 28 | decompilers { 29 | get("vineflower").apply { // Adds names to lambdas - useful for mixins 30 | options.put("mark-corresponding-synthetics", "1") 31 | } 32 | } 33 | 34 | runs { 35 | val runDir = "../../../.runs" 36 | 37 | named("client") { 38 | client() 39 | configName = "Client" 40 | runDir("$runDir/client") 41 | source(sourceSets["main"]) 42 | programArgs("--username=Dev") 43 | } 44 | named("server") { 45 | server() 46 | configName = "Server" 47 | runDir("$runDir/server") 48 | source(sourceSets["main"]) 49 | } 50 | } 51 | 52 | runConfigs.all { 53 | isIdeConfigGenerated = true 54 | } 55 | } 56 | 57 | val commonBundle: Configuration by configurations.creating { 58 | isCanBeConsumed = false 59 | isCanBeResolved = true 60 | } 61 | 62 | val shadowBundle: Configuration by configurations.creating { 63 | isCanBeConsumed = false 64 | isCanBeResolved = true 65 | } 66 | 67 | configurations { 68 | compileClasspath.get().extendsFrom(commonBundle) 69 | runtimeClasspath.get().extendsFrom(commonBundle) 70 | get("developmentNeoForge").extendsFrom(commonBundle) 71 | } 72 | 73 | repositories { 74 | maven("https://maven.parchmentmc.org/") 75 | maven("https://maven.neoforged.net/releases/") 76 | 77 | maven("https://maven.isxander.dev/releases") 78 | maven("https://api.modrinth.com/maven") 79 | } 80 | 81 | dependencies { 82 | minecraft("com.mojang:minecraft:$minecraft") 83 | mappings(loom.layered { 84 | officialMojangMappings() 85 | parchment("org.parchmentmc.data:parchment-${versionProp("parchment_minecraft_version")}:${versionProp("parchment_mappings_version")}@zip") 86 | }) 87 | neoForge("net.neoforged:neoforge:${versionProp("neoforge_loader")}") 88 | 89 | commonBundle(project(common.path, "namedElements")) { isTransitive = false } 90 | shadowBundle(project(common.path, "transformProductionNeoForge")) { isTransitive = false } 91 | 92 | // Mod implementations 93 | //runtimeOnly("dev.isxander:yet-another-config-lib:${versionProp("yacl_version")}-neoforge") 94 | } 95 | 96 | tasks.processResources { 97 | applyProperties(project, listOf("META-INF/neoforge.mods.toml", "${prop("mod.id")}-neoforge.mixin.json", "pack.mcmeta")) 98 | } 99 | 100 | tasks.shadowJar { 101 | configurations = listOf(shadowBundle) 102 | archiveClassifier = "dev-shadow" 103 | } 104 | 105 | tasks.remapJar { 106 | injectAccessWidener = true 107 | input = tasks.shadowJar.get().archiveFile 108 | archiveClassifier = null 109 | dependsOn(tasks.shadowJar) 110 | } 111 | 112 | tasks.jar { 113 | archiveClassifier = "dev" 114 | } 115 | 116 | java { 117 | withSourcesJar() 118 | val java = if (stonecutter.eval(minecraft, ">=1.20.5")) 119 | JavaVersion.VERSION_21 else JavaVersion.VERSION_17 120 | targetCompatibility = java 121 | sourceCompatibility = java 122 | } 123 | 124 | tasks.build { 125 | group = "versioned" 126 | description = "Must run through 'chiseledBuild'" 127 | } 128 | 129 | tasks.register("buildAndCollect") { 130 | group = "versioned" 131 | description = "Must run through 'chiseledBuild'" 132 | from(tasks.remapJar.get().archiveFile, tasks.remapSourcesJar.get().archiveFile) 133 | into(rootProject.layout.buildDirectory.file("libs/${prop("mod.version")}/$loader")) 134 | dependsOn("build") 135 | } 136 | 137 | stonecutter { 138 | // Constants should be given a key and a boolean value 139 | const("fabric", loader == "fabric") 140 | const("forge", loader == "forge") 141 | const("neoforge", loader == "neoforge") 142 | } -------------------------------------------------------------------------------- /neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=neoforge -------------------------------------------------------------------------------- /neoforge/src/main/java/com/trainguy9512/locomotion/neoforge/LocomotionNeoForge.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.neoforge; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.resource.LocomotionResources; 5 | import net.neoforged.api.distmarker.Dist; 6 | import net.neoforged.bus.api.IEventBus; 7 | import net.neoforged.fml.ModContainer; 8 | import net.neoforged.fml.ModList; 9 | import net.neoforged.fml.common.Mod; 10 | import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; 11 | import net.neoforged.neoforge.client.event.AddClientReloadListenersEvent; 12 | import net.neoforged.neoforge.client.gui.IConfigScreenFactory; 13 | 14 | @Mod(value = LocomotionMain.MOD_ID, dist = Dist.CLIENT) 15 | public class LocomotionNeoForge { 16 | 17 | public LocomotionNeoForge(ModContainer modContainer, IEventBus modEventBus) { 18 | modEventBus.addListener(this::onClientInitialize); 19 | modEventBus.addListener(this::onResourceReload); 20 | 21 | modContainer.registerExtensionPoint(IConfigScreenFactory.class, (container, parent) -> LocomotionMain.CONFIG.getConfigScreen(ModList.get()::isLoaded).apply(parent)); 22 | } 23 | 24 | public void onClientInitialize(FMLClientSetupEvent event) { 25 | LocomotionMain.initialize(); 26 | 27 | } 28 | 29 | public void onResourceReload(AddClientReloadListenersEvent event) { 30 | event.addListener(LocomotionResources.RELOADER_IDENTIFIER, LocomotionResources::reload); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "*" 3 | license = "${mod_license}" 4 | 5 | [[mods]] 6 | modId = "${mod_id}" 7 | version = "${mod_version}" 8 | displayName = "${mod_name}" 9 | authors = "Me!" 10 | description = "${mod_description}" 11 | logoFile = "assets/${mod_id}/icon.png" 12 | logoBlur = false 13 | 14 | [[dependencies."${mod_id}"]] 15 | modId = "neoforge" 16 | type = "required" 17 | versionRange = "[${neoforge_loader_version},)" 18 | ordering = "NONE" 19 | side = "BOTH" 20 | 21 | [[mixins]] 22 | config = "${mod_id}-common.mixins.json" 23 | 24 | [[mixins]] 25 | config = "${mod_id}-neoforge.mixins.json" -------------------------------------------------------------------------------- /neoforge/src/main/resources/locomotion-neoforge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "${mod_group}.${mod_id}.neoforge.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | ], 7 | "injectors": { 8 | "defaultRequire": 1 9 | } 10 | } -------------------------------------------------------------------------------- /neoforge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "${mod_name} resources", 4 | "pack_format": 15 5 | } 6 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | maven("https://maven.fabricmc.net/") 6 | maven("https://maven.architectury.dev") 7 | maven("https://maven.minecraftforge.net") 8 | maven("https://maven.neoforged.net/releases/") 9 | // maven("https://maven.kikugie.dev/snapshots") 10 | } 11 | } 12 | 13 | plugins { 14 | // Make sure the version here is the same as the dependency in buildSrc/build.gradle.kts.kts 15 | id("dev.kikugie.stonecutter") version "0.5.1" 16 | } 17 | 18 | stonecutter { 19 | centralScript = "build.gradle.kts" 20 | kotlinController = true 21 | create(rootProject) { 22 | versions("1.21.5") 23 | vcsVersion = "1.21.5" 24 | branch("fabric") 25 | //branch("forge") { versions("1.21.5") }+ 26 | branch("neoforge") { versions("1.21.5") } 27 | } 28 | } 29 | 30 | rootProject.name = "Locomotion" -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/LocomotionMain.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion; 2 | 3 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorRegistry; 4 | import com.trainguy9512.locomotion.animation.animator.entity.firstperson.FirstPersonJointAnimator; 5 | import com.trainguy9512.locomotion.config.LocomotionConfig; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | public class LocomotionMain { 10 | 11 | public static final Logger DEBUG_LOGGER = LogManager.getLogger("Locomotion/Resources"); 12 | public static final String MOD_ID = "locomotion"; 13 | public static final LocomotionConfig CONFIG = new LocomotionConfig(); 14 | 15 | public static void initialize() { 16 | CONFIG.load(); 17 | registerEntityAnimators(); 18 | //registerBlockRenderers(); 19 | } 20 | 21 | /* 22 | public static void onClientPostInit(Platform.ModSetupContext ctx) { 23 | } 24 | 25 | public static void onCommonInit() { 26 | } 27 | 28 | public static void onCommonPostInit(P.ModSetupContext ctx) { 29 | } 30 | 31 | */ 32 | 33 | 34 | private static void registerEntityAnimators(){ 35 | JointAnimatorRegistry.registerFirstPersonPlayerJointAnimator(new FirstPersonJointAnimator()); 36 | } 37 | 38 | /* 39 | private static void registerBlockRenderers(){ 40 | registerBlockRenderer(new PressurePlateBlockRenderer(), PressurePlateBlockRenderer.PRESSURE_PLATES); 41 | registerBlockRenderer(new ButtonBlockRenderer(), ButtonBlockRenderer.BUTTONS); 42 | registerBlockRenderer(new TrapDoorBlockRenderer(), TrapDoorBlockRenderer.TRAPDOORS); 43 | registerBlockRenderer(new LeverBlockRenderer(), LeverBlockRenderer.LEVERS); 44 | registerBlockRenderer(new EndPortalFrameBlockRenderer(), EndPortalFrameBlockRenderer.END_PORTAL_BLOCKS); 45 | registerBlockRenderer(new ChainedBlockRenderer(), ChainedBlockRenderer.CHAINED_BLOCKS); 46 | //registerBlockRenderer(new FloatingPlantBlockRenderer(), FloatingPlantBlockRenderer.FLOATING_PLANTS); 47 | } 48 | */ 49 | 50 | 51 | /* 52 | private static void registerBlockRenderer(TickableBlockRenderer tickableBlockRenderer, Block[] blocks){ 53 | for(Block block : blocks){ 54 | BlockRendererRegistry.register(block, tickableBlockRenderer); 55 | } 56 | } 57 | */ 58 | 59 | } -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/access/AlternateSingleBlockRenderer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.access; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.client.renderer.MultiBufferSource; 5 | import net.minecraft.world.level.block.state.BlockState; 6 | 7 | public interface AlternateSingleBlockRenderer { 8 | void locomotion$renderSingleBlockWithEmission(BlockState blockState, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/access/FirstPersonPlayerRendererGetter.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.access; 2 | 3 | import com.trainguy9512.locomotion.render.FirstPersonPlayerRenderer; 4 | 5 | import java.util.Optional; 6 | 7 | public interface FirstPersonPlayerRendererGetter { 8 | Optional locomotion$getFirstPersonPlayerRenderer(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/access/LivingEntityRenderStateAccess.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.access; 2 | 3 | import com.trainguy9512.locomotion.animation.animator.entity.EntityJointAnimator; 4 | import com.trainguy9512.locomotion.animation.pose.Pose; 5 | 6 | import java.util.Optional; 7 | 8 | public interface LivingEntityRenderStateAccess { 9 | void animationOverhaul$setInterpolatedAnimationPose(Pose interpolatedPose); 10 | Optional animationOverhaul$getInterpolatedAnimationPose(); 11 | 12 | void animationOverhaul$setEntityJointAnimator(EntityJointAnimator livingEntityJointAnimator); 13 | Optional> animationOverhaul$getEntityJointAnimator(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/access/MatrixModelPart.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.access; 2 | 3 | import org.joml.Matrix4f; 4 | import org.spongepowered.asm.mixin.gen.Accessor; 5 | 6 | public interface MatrixModelPart { 7 | void locomotion$setMatrix(Matrix4f matrix4f); 8 | Matrix4f locomotion$getMatrix(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/animator/JointAnimator.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.animator; 2 | 3 | import com.trainguy9512.locomotion.animation.data.OnTickDriverContainer; 4 | import com.trainguy9512.locomotion.animation.joint.skeleton.JointSkeleton; 5 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 6 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 7 | import com.trainguy9512.locomotion.animation.pose.function.cache.CachedPoseContainer; 8 | import com.trainguy9512.locomotion.animation.pose.function.montage.MontageManager; 9 | import net.minecraft.resources.ResourceLocation; 10 | 11 | /** 12 | * Uses a data reference and a joint skeleton to calculate a pose once per tick. 13 | * @param Object used for data reference 14 | */ 15 | public interface JointAnimator { 16 | 17 | /** 18 | * Gets the resource location for the joint skeleton being used. 19 | * @return Joint skeleton resource location 20 | */ 21 | ResourceLocation getJointSkeleton(); 22 | 23 | /** 24 | * Uses an object for data reference and updates the animation data container. Called once per tick, prior to pose samplers updating and pose calculation. 25 | * @param dataReference Object used as reference for updating the animation data container 26 | * @param dataContainer Data container from the previous tick 27 | * @param montageManager Controls data used for getting and playing animation montages. 28 | */ 29 | void extractAnimationData(T dataReference, OnTickDriverContainer dataContainer, MontageManager montageManager); 30 | 31 | /** 32 | * Creates the pose function that will return an animation pose for the joint animator. 33 | * @param cachedPoseContainer Container for registering and retrieving saved cached poses. 34 | * @return Pose function that returns a pose in local space. 35 | */ 36 | PoseFunction constructPoseFunction(CachedPoseContainer cachedPoseContainer); 37 | 38 | default PoseCalculationFrequency getPoseCalulationFrequency(){ 39 | return PoseCalculationFrequency.CALCULATE_ONCE_PER_TICK; 40 | } 41 | 42 | public enum PoseCalculationFrequency { 43 | CALCULATE_EVERY_FRAME, 44 | CALCULATE_ONCE_PER_TICK 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/animator/JointAnimatorRegistry.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.animator; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.trainguy9512.locomotion.animation.animator.entity.EntityJointAnimator; 5 | import com.trainguy9512.locomotion.animation.animator.entity.LivingEntityJointAnimator; 6 | import net.minecraft.client.player.LocalPlayer; 7 | import net.minecraft.client.renderer.entity.state.PlayerRenderState; 8 | import net.minecraft.world.entity.Entity; 9 | import net.minecraft.world.entity.EntityType; 10 | 11 | import java.util.HashMap; 12 | import java.util.Optional; 13 | 14 | public class JointAnimatorRegistry { 15 | 16 | private static final HashMap, EntityJointAnimator> THIRD_PERSON_ENTITY_JOINT_ANIMATORS = Maps.newHashMap(); 17 | private static LivingEntityJointAnimator FIRST_PERSON_PLAYER_JOINT_ANIMATOR = null; 18 | 19 | private JointAnimatorRegistry(){ 20 | 21 | } 22 | 23 | /** 24 | * Registers a joint animator for use on third person living entities. 25 | * @param entityType Type of entity associated with the living entity 26 | * @param entityJointAnimator Newly constructed entity joint animator object 27 | */ 28 | public static void registerEntityJointAnimator(EntityType entityType, EntityJointAnimator entityJointAnimator){ 29 | THIRD_PERSON_ENTITY_JOINT_ANIMATORS.put(entityType, entityJointAnimator); 30 | } 31 | 32 | 33 | public static void registerFirstPersonPlayerJointAnimator(LivingEntityJointAnimator firstPersonPlayerJointAnimator){ 34 | FIRST_PERSON_PLAYER_JOINT_ANIMATOR = firstPersonPlayerJointAnimator; 35 | } 36 | 37 | /** 38 | * Returns the entity joint animator for the provided entity type. If the entity type does not have a joint animator registered, null is returned. 39 | * @param entity Entity 40 | * @return Entity joint animator 41 | */ 42 | @SuppressWarnings("unchecked") 43 | public static Optional> getThirdPersonJointAnimator(T entity){ 44 | return Optional.ofNullable((EntityJointAnimator) THIRD_PERSON_ENTITY_JOINT_ANIMATORS.get(entity.getType())); 45 | } 46 | 47 | /** 48 | * Returns the first person player joint animator, if it has been registered. If not, it returns null. 49 | */ 50 | public static Optional> getFirstPersonPlayerJointAnimator(){ 51 | return Optional.ofNullable(FIRST_PERSON_PLAYER_JOINT_ANIMATOR); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/animator/entity/EntityJointAnimator.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.animator.entity; 2 | 3 | import com.trainguy9512.locomotion.animation.animator.JointAnimator; 4 | import net.minecraft.client.model.EntityModel; 5 | import net.minecraft.client.renderer.entity.state.EntityRenderState; 6 | import net.minecraft.world.entity.Entity; 7 | 8 | public interface EntityJointAnimator extends JointAnimator { 9 | 10 | /** 11 | * Does some final operations on the entity model, similar to what would happen in the {@link EntityModel#setupAnim(EntityRenderState)} function. Called every frame after the interpolated pose is applied to the model 12 | * @param entityModel Entity model 13 | * @param entityRenderState Entity render state 14 | */ 15 | public void postProcessModelParts(EntityModel entityModel, S entityRenderState); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/animator/entity/LivingEntityJointAnimator.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.animator.entity; 2 | 3 | import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; 4 | import net.minecraft.world.entity.LivingEntity; 5 | 6 | public interface LivingEntityJointAnimator extends EntityJointAnimator { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/animator/entity/firstperson/FirstPersonMining.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.animator.entity.firstperson; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 4 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 5 | import com.trainguy9512.locomotion.animation.pose.function.cache.CachedPoseContainer; 6 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.State; 7 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.StateMachineFunction; 8 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.StateTransition; 9 | import com.trainguy9512.locomotion.util.Transition; 10 | 11 | public class FirstPersonMining { 12 | 13 | public static PoseFunction constructPoseFunction( 14 | CachedPoseContainer cachedPoseContainer, 15 | PoseFunction idlePoseFunction, 16 | PoseFunction swingPoseFunction, 17 | PoseFunction finishPoseFunction, 18 | Transition idleToMiningTiming 19 | ) { 20 | return StateMachineFunction.builder(evaluationState -> MiningStates.IDLE) 21 | .resetsUponRelevant(true) 22 | .defineState(State.builder(MiningStates.IDLE, idlePoseFunction) 23 | .addOutboundTransition(StateTransition.builder(MiningStates.SWING) 24 | .isTakenIfTrue(StateTransition.booleanDriverPredicate(FirstPersonDrivers.IS_MINING)) 25 | .setTiming(idleToMiningTiming) 26 | .build()) 27 | .build()) 28 | .defineState(State.builder(MiningStates.SWING, swingPoseFunction) 29 | .resetsPoseFunctionUponEntry(true) 30 | .addOutboundTransition(StateTransition.builder(MiningStates.FINISH) 31 | .isTakenIfTrue( 32 | StateTransition.MOST_RELEVANT_ANIMATION_PLAYER_IS_FINISHING.and(StateTransition.booleanDriverPredicate(FirstPersonDrivers.IS_MINING).negate()) 33 | ) 34 | .setTiming(Transition.SINGLE_TICK) 35 | .setPriority(50) 36 | .build()) 37 | .build()) 38 | .defineState(State.builder(MiningStates.FINISH, finishPoseFunction) 39 | .resetsPoseFunctionUponEntry(true) 40 | .addOutboundTransition(StateTransition.builder(MiningStates.IDLE) 41 | .isTakenIfMostRelevantAnimationPlayerFinishing(1) 42 | .setPriority(50) 43 | .setTiming(Transition.SINGLE_TICK) 44 | .build()) 45 | .addOutboundTransition(StateTransition.builder(MiningStates.SWING) 46 | .isTakenIfTrue(StateTransition.booleanDriverPredicate(FirstPersonDrivers.IS_MINING).and(StateTransition.CURRENT_TRANSITION_FINISHED)) 47 | .setPriority(60) 48 | .setTiming(idleToMiningTiming) 49 | .build()) 50 | .build()) 51 | .build(); 52 | } 53 | 54 | enum MiningStates { 55 | IDLE, 56 | SWING, 57 | FINISH 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/animator/entity/firstperson/FirstPersonMontages.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.animator.entity.firstperson; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.function.montage.MontageConfiguration; 4 | import com.trainguy9512.locomotion.util.Easing; 5 | import com.trainguy9512.locomotion.util.TimeSpan; 6 | import com.trainguy9512.locomotion.util.Transition; 7 | import net.minecraft.world.InteractionHand; 8 | 9 | 10 | public class FirstPersonMontages { 11 | 12 | public static final String MAIN_HAND_ATTACK_SLOT = "main_hand_attack"; 13 | public static final String OFF_HAND_ATTACK_SLOT = "off_hand_attack"; 14 | public static final String SHIELD_BLOCK_SLOT = "shield_block"; 15 | 16 | public static final MontageConfiguration HAND_TOOL_ATTACK_PICKAXE_MONTAGE = MontageConfiguration.builder("hand_tool_attack_pickaxe", FirstPersonAnimationSequences.HAND_TOOL_ATTACK) 17 | .playsInSlot(MAIN_HAND_ATTACK_SLOT) 18 | .setCooldownDuration(TimeSpan.of60FramesPerSecond(8)) 19 | .setTransitionIn(Transition.builder(TimeSpan.of60FramesPerSecond(1)).setEasement(Easing.SINE_OUT).build()) 20 | .setTransitionOut(Transition.builder(TimeSpan.of60FramesPerSecond(12)).setEasement(Easing.SINE_IN_OUT).build()) 21 | .makeAdditive(driverContainer -> { 22 | FirstPersonHandPose handPose = driverContainer.getDriverValue(FirstPersonDrivers.MAIN_HAND_POSE); 23 | if (handPose == FirstPersonHandPose.GENERIC_ITEM) { 24 | return driverContainer.getDriverValue(FirstPersonDrivers.MAIN_HAND_GENERIC_ITEM_POSE).basePoseLocation; 25 | } 26 | return handPose.basePoseLocation; 27 | }) 28 | .build(); 29 | 30 | 31 | public static final MontageConfiguration USE_MAIN_HAND_MONTAGE = MontageConfiguration.builder("hand_use_main_hand", FirstPersonAnimationSequences.HAND_TOOL_USE) 32 | .playsInSlot(MAIN_HAND_ATTACK_SLOT) 33 | .setCooldownDuration(TimeSpan.of60FramesPerSecond(5)) 34 | .setTransitionIn(Transition.builder(TimeSpan.of60FramesPerSecond(3)).setEasement(Easing.SINE_OUT).build()) 35 | .setTransitionOut(Transition.builder(TimeSpan.of60FramesPerSecond(16)).setEasement(Easing.SINE_IN_OUT).build()) 36 | .makeAdditive(driverContainer -> { 37 | FirstPersonHandPose handPose = driverContainer.getDriverValue(FirstPersonDrivers.MAIN_HAND_POSE); 38 | if (handPose == FirstPersonHandPose.GENERIC_ITEM) { 39 | return driverContainer.getDriverValue(FirstPersonDrivers.MAIN_HAND_GENERIC_ITEM_POSE).basePoseLocation; 40 | } 41 | return handPose.basePoseLocation; 42 | }) 43 | .build(); 44 | public static final MontageConfiguration USE_OFF_HAND_MONTAGE = MontageConfiguration.builder("hand_use_off_hand", FirstPersonAnimationSequences.HAND_TOOL_USE) 45 | .playsInSlot(OFF_HAND_ATTACK_SLOT) 46 | .setCooldownDuration(TimeSpan.of60FramesPerSecond(5)) 47 | .setTransitionIn(Transition.builder(TimeSpan.of60FramesPerSecond(3)).setEasement(Easing.SINE_OUT).build()) 48 | .setTransitionOut(Transition.builder(TimeSpan.of60FramesPerSecond(16)).setEasement(Easing.SINE_IN_OUT).build()) 49 | .makeAdditive(driverContainer -> { 50 | FirstPersonHandPose handPose = driverContainer.getDriverValue(FirstPersonDrivers.OFF_HAND_POSE); 51 | if (handPose == FirstPersonHandPose.GENERIC_ITEM) { 52 | return driverContainer.getDriverValue(FirstPersonDrivers.OFF_HAND_GENERIC_ITEM_POSE).basePoseLocation; 53 | } 54 | return handPose.basePoseLocation; 55 | }) 56 | .build(); 57 | public static final MontageConfiguration SHIELD_BLOCK_IMPACT_MONTAGE = MontageConfiguration.builder("shield_block_impact", FirstPersonAnimationSequences.HAND_SHIELD_IMPACT) 58 | .playsInSlot(SHIELD_BLOCK_SLOT) 59 | .setCooldownDuration(TimeSpan.of60FramesPerSecond(5)) 60 | .setTransitionIn(Transition.builder(TimeSpan.of60FramesPerSecond(2)).setEasement(Easing.SINE_IN_OUT).build()) 61 | .setTransitionOut(Transition.builder(TimeSpan.of60FramesPerSecond(8)).setEasement(Easing.SINE_IN_OUT).build()) 62 | .build(); 63 | 64 | 65 | public static final MontageConfiguration CROSSBOW_FIRE_MAIN_HAND_MONTAGE = MontageConfiguration.builder("crossbow_fire_main_hand", FirstPersonAnimationSequences.HAND_CROSSBOW_FIRE) 66 | .playsInSlot(MAIN_HAND_ATTACK_SLOT) 67 | .setTransitionIn(Transition.builder(TimeSpan.of60FramesPerSecond(3)).setEasement(Easing.SINE_OUT).build()) 68 | .setTransitionOut(Transition.builder(TimeSpan.of60FramesPerSecond(20)).setEasement(Easing.SINE_IN_OUT).build()) 69 | .build(); 70 | public static final MontageConfiguration CROSSBOW_FIRE_OFF_HAND_MONTAGE = MontageConfiguration.builder("crossbow_fire_off_hand", FirstPersonAnimationSequences.HAND_CROSSBOW_FIRE) 71 | .playsInSlot(OFF_HAND_ATTACK_SLOT) 72 | .setTransitionIn(Transition.builder(TimeSpan.of60FramesPerSecond(3)).setEasement(Easing.SINE_OUT).build()) 73 | .setTransitionOut(Transition.builder(TimeSpan.of60FramesPerSecond(20)).setEasement(Easing.SINE_IN_OUT).build()) 74 | .build(); 75 | public static MontageConfiguration getCrossbowFireMontage(InteractionHand interactionHand) { 76 | return switch (interactionHand) { 77 | case MAIN_HAND -> CROSSBOW_FIRE_MAIN_HAND_MONTAGE; 78 | case OFF_HAND -> CROSSBOW_FIRE_OFF_HAND_MONTAGE; 79 | }; 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/animator/entity/firstperson/FirstPersonSword.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.animator.entity.firstperson; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 4 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 5 | import com.trainguy9512.locomotion.animation.pose.function.SequencePlayerFunction; 6 | import com.trainguy9512.locomotion.animation.pose.function.cache.CachedPoseContainer; 7 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.State; 8 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.StateAlias; 9 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.StateMachineFunction; 10 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.StateTransition; 11 | import com.trainguy9512.locomotion.util.Easing; 12 | import com.trainguy9512.locomotion.util.TimeSpan; 13 | import com.trainguy9512.locomotion.util.Transition; 14 | import net.minecraft.world.InteractionHand; 15 | 16 | import java.util.Set; 17 | 18 | public class FirstPersonSword { 19 | 20 | public static PoseFunction handSwordPoseFunction(CachedPoseContainer cachedPoseContainer, InteractionHand interactionHand) { 21 | return switch (interactionHand) { 22 | case MAIN_HAND -> StateMachineFunction.builder(evaluationState -> SwordSwingStates.IDLE) 23 | .resetsUponRelevant(true) 24 | .defineState(State.builder(SwordSwingStates.IDLE, FirstPersonHandPose.SWORD.getMiningStateMachine(cachedPoseContainer, interactionHand)) 25 | .resetsPoseFunctionUponEntry(true) 26 | .addOutboundTransition(StateTransition.builder(SwordSwingStates.SWING_LEFT) 27 | .isTakenIfTrue(StateTransition.booleanDriverPredicate(FirstPersonDrivers.HAS_ATTACKED)) 28 | .setTiming(Transition.builder(TimeSpan.ofTicks(2)).build()) 29 | .build()) 30 | .build()) 31 | .defineState(State.builder(SwordSwingStates.SWING_LEFT, SequencePlayerFunction.builder(FirstPersonAnimationSequences.HAND_TOOL_SWORD_SWING_LEFT).build()) 32 | .resetsPoseFunctionUponEntry(true) 33 | .addOutboundTransition(StateTransition.builder(SwordSwingStates.SWING_RIGHT) 34 | .isTakenIfTrue(StateTransition.booleanDriverPredicate(FirstPersonDrivers.HAS_ATTACKED)) 35 | .setTiming(Transition.builder(TimeSpan.ofTicks(2)).build()) 36 | .build()) 37 | .build()) 38 | .defineState(State.builder(SwordSwingStates.SWING_RIGHT, SequencePlayerFunction.builder(FirstPersonAnimationSequences.HAND_TOOL_SWORD_SWING_RIGHT).build()) 39 | .resetsPoseFunctionUponEntry(true) 40 | .addOutboundTransition(StateTransition.builder(SwordSwingStates.SWING_LEFT) 41 | .isTakenIfTrue(StateTransition.booleanDriverPredicate(FirstPersonDrivers.HAS_ATTACKED)) 42 | .setTiming(Transition.builder(TimeSpan.ofTicks(2)).build()) 43 | .build()) 44 | .build()) 45 | .addStateAlias(StateAlias.builder( 46 | Set.of( 47 | SwordSwingStates.SWING_LEFT, 48 | SwordSwingStates.SWING_RIGHT 49 | )) 50 | .addOutboundTransition(StateTransition.builder(SwordSwingStates.IDLE) 51 | .isTakenIfMostRelevantAnimationPlayerFinishing(0) 52 | .setTiming(Transition.builder(TimeSpan.of60FramesPerSecond(10)).setEasement(Easing.SINE_IN_OUT).build()) 53 | .build()) 54 | .addOutboundTransition(StateTransition.builder(SwordSwingStates.IDLE) 55 | .isTakenIfTrue(StateTransition.booleanDriverPredicate(FirstPersonDrivers.IS_MINING)) 56 | .setTiming(Transition.builder(TimeSpan.of60FramesPerSecond(6)).setEasement(Easing.SINE_IN_OUT).build()) 57 | .build()) 58 | .build()) 59 | .build(); 60 | case OFF_HAND -> FirstPersonHandPose.SWORD.getMiningStateMachine(cachedPoseContainer, interactionHand); 61 | }; 62 | } 63 | 64 | enum SwordSwingStates { 65 | IDLE, 66 | SWING_LEFT, 67 | SWING_RIGHT 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/data/AnimationDataContainer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.data; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.trainguy9512.locomotion.animation.animator.JointAnimator; 5 | import com.trainguy9512.locomotion.animation.driver.Driver; 6 | import com.trainguy9512.locomotion.animation.driver.VariableDriver; 7 | import com.trainguy9512.locomotion.animation.driver.DriverKey; 8 | import com.trainguy9512.locomotion.animation.joint.skeleton.JointSkeleton; 9 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 10 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 11 | import com.trainguy9512.locomotion.animation.pose.function.cache.CachedPoseContainer; 12 | import com.trainguy9512.locomotion.animation.pose.function.montage.MontageManager; 13 | import com.trainguy9512.locomotion.resource.LocomotionResources; 14 | import com.trainguy9512.locomotion.util.Interpolator; 15 | import com.trainguy9512.locomotion.util.TimeSpan; 16 | 17 | import java.util.Map; 18 | 19 | public class AnimationDataContainer implements PoseCalculationDataContainer, OnTickDriverContainer { 20 | 21 | private final Map>, Driver> drivers; 22 | private final CachedPoseContainer savedCachedPoseContainer; 23 | private final PoseFunction poseFunction; 24 | private final MontageManager montageManager; 25 | 26 | private final JointSkeleton jointSkeleton; 27 | private final DriverKey> perTickCalculatedPoseDriverKey; 28 | private final DriverKey> gameTimeTicksDriverKey; 29 | 30 | private AnimationDataContainer(JointAnimator jointAnimator) { 31 | this.drivers = Maps.newHashMap(); 32 | this.savedCachedPoseContainer = CachedPoseContainer.of(); 33 | this.poseFunction = jointAnimator.constructPoseFunction(savedCachedPoseContainer).wrapUnique(); 34 | this.montageManager = MontageManager.of(); 35 | 36 | this.jointSkeleton = LocomotionResources.getOrThrowJointSkeleton(jointAnimator.getJointSkeleton()); 37 | this.perTickCalculatedPoseDriverKey = DriverKey.of("per_tick_calculated_pose", () -> VariableDriver.ofInterpolatable(() -> LocalSpacePose.of(jointSkeleton), Interpolator.LOCAL_SPACE_POSE)); 38 | this.gameTimeTicksDriverKey = DriverKey.of("game_time", () -> VariableDriver.ofConstant(() -> 0L)); 39 | this.tick(); 40 | } 41 | 42 | public static AnimationDataContainer of(JointAnimator jointAnimator) { 43 | return new AnimationDataContainer(jointAnimator); 44 | } 45 | 46 | public void preTick() { 47 | this.drivers.values().forEach(Driver::pushCurrentToPrevious); 48 | } 49 | 50 | public void tick() { 51 | this.montageManager.tick(); 52 | this.drivers.values().forEach(Driver::tick); 53 | this.getDriver(this.gameTimeTicksDriverKey).setValue(this.getDriver(this.gameTimeTicksDriverKey).getCurrentValue() + 1); 54 | this.poseFunction.tick(PoseFunction.FunctionEvaluationState.of( 55 | this, 56 | this.montageManager, 57 | false, 58 | this.getDriver(this.gameTimeTicksDriverKey).getCurrentValue() 59 | )); 60 | } 61 | 62 | public void postTick() { 63 | this.drivers.values().forEach(Driver::postTick); 64 | } 65 | 66 | public LocalSpacePose computePose(float partialTicks) { 67 | this.savedCachedPoseContainer.clearCaches(); 68 | return this.poseFunction.compute(PoseFunction.FunctionInterpolationContext.of( 69 | this, 70 | this.montageManager, 71 | partialTicks, 72 | TimeSpan.ofTicks(this.getInterpolatedDriverValue(gameTimeTicksDriverKey, 1) + partialTicks) 73 | )); 74 | } 75 | 76 | @Override 77 | public JointSkeleton getJointSkeleton() { 78 | return this.jointSkeleton; 79 | } 80 | 81 | public DriverKey> getPerTickCalculatedPoseDriverKey() { 82 | return this.perTickCalculatedPoseDriverKey; 83 | } 84 | 85 | public MontageManager getMontageManager() { 86 | return this.montageManager; 87 | } 88 | 89 | public Map>, Driver> getAllDrivers() { 90 | return this.drivers; 91 | } 92 | 93 | @Override 94 | public > D getInterpolatedDriverValue(DriverKey driverKey, float partialTicks) { 95 | return this.getDriver(driverKey).getValueInterpolated(partialTicks); 96 | } 97 | 98 | @Override 99 | public > D getDriverValue(DriverKey driverKey) { 100 | return this.getInterpolatedDriverValue(driverKey, 1); 101 | } 102 | 103 | @SuppressWarnings("unchecked") 104 | @Override 105 | public > R getDriver(DriverKey driverKey) { 106 | return (R) this.drivers.computeIfAbsent(driverKey, DriverKey::createInstance); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/data/OnTickDriverContainer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.data; 2 | 3 | import com.trainguy9512.locomotion.animation.driver.DriverKey; 4 | import com.trainguy9512.locomotion.animation.driver.Driver; 5 | 6 | public interface OnTickDriverContainer { 7 | /** 8 | * Returns the value of an animation driver at the current tick. 9 | * @param driverKey {@link DriverKey <>} of the driver to return a value for. 10 | * @return Interpolated value 11 | */ 12 | public > D getDriverValue(DriverKey driverKey); 13 | 14 | public > R getDriver(DriverKey driverKey); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/data/PoseCalculationDataContainer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.data; 2 | 3 | import com.trainguy9512.locomotion.animation.driver.DriverKey; 4 | import com.trainguy9512.locomotion.animation.driver.Driver; 5 | import com.trainguy9512.locomotion.animation.joint.skeleton.JointSkeleton; 6 | 7 | /** 8 | * Interface for retrieving the interpolated value of a driver during the sampling stage. 9 | */ 10 | public interface PoseCalculationDataContainer { 11 | 12 | /** 13 | * Returns the interpolated value of an animation driver. 14 | * @param driverKey {@link DriverKey <>} of the driver to return a value for. 15 | * @param partialTicks Percentage of a tick since the previous tick. 16 | * @return Interpolated value 17 | */ 18 | public > D getInterpolatedDriverValue(DriverKey driverKey, float partialTicks); 19 | 20 | /** 21 | * Returns the joint skeleton for the data container. 22 | */ 23 | public JointSkeleton getJointSkeleton(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/driver/Driver.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.driver; 2 | 3 | /** 4 | * Object for calculating and storing values derived from a data reference for use during animation pose calculation. 5 | * @param Data Type 6 | * @author James Pelter 7 | */ 8 | public interface Driver { 9 | 10 | /** 11 | * Updates the driver based on values set during data extraction, called once per tick after data extraction and prior to pose function tick. 12 | */ 13 | void tick(); 14 | 15 | /** 16 | * Returns the interpolated value between the previous tick and the current tick. 17 | * @param partialTicks Percentage of a tick since the previous tick. 18 | * @return Interpolated value. 19 | */ 20 | D getValueInterpolated(float partialTicks); 21 | 22 | /** 23 | * Prepares the driver for the next tick after it's finished being used in the current tick, which is usually setting the current value to the previous value 24 | */ 25 | void pushCurrentToPrevious(); 26 | 27 | /** 28 | * Called once per tick after data extraction and after pose function tick. 29 | */ 30 | void postTick(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/driver/DriverKey.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.driver; 2 | 3 | import com.trainguy9512.locomotion.animation.data.AnimationDataContainer; 4 | 5 | import java.util.function.Supplier; 6 | 7 | /** 8 | * Key for animation drivers that are stored once per data container. 9 | *

10 | * These keys are used for storing the default value of a driver object, to initialize every time a 11 | * driver is instanced from a new data container. 12 | *

13 | * 14 | * @see AnimationDataContainer 15 | * @author James Pelter 16 | */ 17 | public class DriverKey> { 18 | 19 | private final String identifier; 20 | private final Supplier defaultValue; 21 | 22 | protected DriverKey(String identifier, Supplier defaultValue){ 23 | this.identifier = identifier; 24 | this.defaultValue = defaultValue; 25 | } 26 | 27 | public static > DriverKey of(String identifier, Supplier defaultValue){ 28 | return new DriverKey<>(identifier, defaultValue); 29 | } 30 | 31 | /** 32 | * Returns the string identifier for this animation data key. 33 | */ 34 | public String getIdentifier(){ 35 | return this.identifier; 36 | } 37 | 38 | /** 39 | * Creates a new instance from the data key's default supplier. 40 | */ 41 | public R createInstance(){ 42 | return this.defaultValue.get(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/driver/SpringDriver.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.driver; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.util.Interpolator; 5 | import org.joml.Vector3d; 6 | import org.joml.Vector3f; 7 | 8 | import java.util.function.BiFunction; 9 | import java.util.function.BinaryOperator; 10 | import java.util.function.Function; 11 | import java.util.function.Supplier; 12 | 13 | public class SpringDriver extends VariableDriver { 14 | 15 | private final float stiffness; 16 | private final float damping; 17 | private final float mass; 18 | 19 | private final BinaryOperator addition; 20 | private final BiFunction multiplication; 21 | private final boolean returnsDelta; 22 | 23 | private D currentTargetValue; 24 | private D previousTargetValue; 25 | private D velocity; 26 | 27 | protected SpringDriver( 28 | float stiffness, 29 | float damping, 30 | float mass, 31 | Supplier initialValue, 32 | Interpolator interpolator, 33 | BinaryOperator addition, 34 | BiFunction multiplication, 35 | boolean returnsDelta 36 | ) { 37 | super(initialValue, interpolator); 38 | this.stiffness = stiffness; 39 | this.damping = damping; 40 | this.mass = Math.max(mass, 0.1f); 41 | 42 | this.addition = addition; 43 | this.multiplication = multiplication; 44 | this.returnsDelta = returnsDelta; 45 | 46 | this.currentTargetValue = initialValue.get(); 47 | this.previousTargetValue = initialValue.get(); 48 | this.velocity = multiplication.apply(initialValue.get(), 0f); 49 | } 50 | 51 | public static SpringDriver of(float stiffness, float damping, float mass, Supplier initialValue, Interpolator interpolator, BinaryOperator addition, BiFunction multiplication, boolean returnsDelta) { 52 | return new SpringDriver<>(stiffness, damping, mass, initialValue, interpolator, addition, multiplication, returnsDelta); 53 | } 54 | 55 | public static SpringDriver ofFloat(float stiffness, float damping, float mass, Supplier initialValue, boolean returnsDelta) { 56 | return SpringDriver.of(stiffness, damping, mass, initialValue, 57 | Interpolator.FLOAT, 58 | Float::sum, 59 | (a, b) -> a * b, 60 | returnsDelta 61 | ); 62 | } 63 | 64 | public static SpringDriver ofVector3f(float stiffness, float damping, float mass, Supplier initialValue, boolean returnsDelta) { 65 | return SpringDriver.of(stiffness, damping, mass, initialValue, 66 | Interpolator.VECTOR_FLOAT, 67 | (a, b) -> a.add(b, new Vector3f()), 68 | (a, b) -> a.mul(b, new Vector3f()), 69 | returnsDelta 70 | ); 71 | } 72 | 73 | @Override 74 | public void setValue(D value) { 75 | this.currentTargetValue = value; 76 | } 77 | 78 | @Override 79 | public void pushCurrentToPrevious() { 80 | super.pushCurrentToPrevious(); 81 | this.previousTargetValue = this.currentTargetValue; 82 | } 83 | 84 | @Override 85 | public void reset() { 86 | super.reset(); 87 | this.previousTargetValue = this.initialValue.get(); 88 | this.currentTargetValue = this.initialValue.get(); 89 | } 90 | 91 | @Override 92 | public D getValueInterpolated(float partialTicks) { 93 | D interpolatedValue = super.getValueInterpolated(partialTicks); 94 | if (this.returnsDelta) { 95 | D targetInterpolatedValue = this.interpolator.interpolate(this.previousTargetValue, this.currentTargetValue, partialTicks); 96 | return this.addition.apply(targetInterpolatedValue, this.multiplication.apply(interpolatedValue, -1f)); 97 | } else { 98 | return interpolatedValue; 99 | } 100 | } 101 | 102 | @Override 103 | public void tick() { 104 | super.tick(); 105 | 106 | D displacement = this.addition.apply(this.currentValue, this.multiplication.apply(this.currentTargetValue, -1f)); 107 | D springForce = this.multiplication.apply(displacement, this.stiffness * -1f); 108 | D dampingForce = this.multiplication.apply(this.velocity, this.damping * -1f); 109 | D acceleration = this.multiplication.apply(this.addition.apply(springForce, dampingForce), 1 / Math.max(this.mass, 0.01f)); 110 | 111 | this.velocity = this.addition.apply(this.velocity, acceleration); 112 | 113 | this.currentValue = this.addition.apply(this.currentValue, this.velocity); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/driver/TimerDriver.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.driver; 2 | 3 | import com.trainguy9512.locomotion.util.Easing; 4 | import com.trainguy9512.locomotion.util.Interpolator; 5 | import com.trainguy9512.locomotion.util.TimeSpan; 6 | 7 | import java.util.function.Supplier; 8 | 9 | public class TimerDriver extends VariableDriver { 10 | 11 | private float incrementPerTick; 12 | private Easing easing; 13 | private final float minValue; 14 | private final float maxValue; 15 | 16 | protected TimerDriver( 17 | Supplier initialValue, 18 | float incrementPerTick, 19 | Easing easing, 20 | float minValue, 21 | float maxValue 22 | ) { 23 | super(initialValue, Interpolator.FLOAT); 24 | this.incrementPerTick = incrementPerTick; 25 | this.easing = easing; 26 | this.minValue = minValue; 27 | this.maxValue = maxValue; 28 | } 29 | 30 | public static Builder builder(Supplier initialValue) { 31 | return new Builder(initialValue); 32 | } 33 | 34 | public void setIncrementPerTick(float incrementPerTick) { 35 | this.incrementPerTick = incrementPerTick; 36 | } 37 | 38 | public void setIncrementPerTick(TimeSpan timeToIncrementFully, float target) { 39 | this.incrementPerTick = 1 / target / timeToIncrementFully.inTicks(); 40 | } 41 | 42 | @Override 43 | public Float getValueInterpolated(float partialTicks) { 44 | return this.easing.ease(super.getValueInterpolated(partialTicks)); 45 | } 46 | 47 | @Override 48 | public void setValue(Float newValue) { 49 | super.setValue(Math.clamp(newValue, this.minValue, this.maxValue)); 50 | } 51 | 52 | @Override 53 | public void postTick() { 54 | this.setValue(this.currentValue + this.incrementPerTick); 55 | } 56 | 57 | public static class Builder { 58 | 59 | private final Supplier initialValue; 60 | private float incrementPerTick; 61 | private Easing easing; 62 | private float minValue; 63 | private float maxValue; 64 | 65 | private Builder(Supplier initialValue) { 66 | this.initialValue = initialValue; 67 | this.incrementPerTick = 1f; 68 | this.easing = Easing.LINEAR; 69 | this.minValue = -Float.MAX_VALUE; 70 | this.maxValue = Float.MAX_VALUE; 71 | } 72 | 73 | public Builder setInitialIncrement(float incrementPerTick) { 74 | this.incrementPerTick = incrementPerTick; 75 | return this; 76 | } 77 | 78 | public Builder setEasing(Easing easing) { 79 | this.easing = easing; 80 | return this; 81 | } 82 | 83 | public Builder setMinValue(float minValue) { 84 | this.minValue = minValue; 85 | return this; 86 | } 87 | 88 | public Builder setMaxValue(float maxValue) { 89 | this.maxValue = maxValue; 90 | return this; 91 | } 92 | 93 | public TimerDriver build() { 94 | return new TimerDriver( 95 | this.initialValue, 96 | this.incrementPerTick, 97 | this.easing, 98 | this.minValue, 99 | this.maxValue 100 | ); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/driver/TriggerDriver.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.driver; 2 | 3 | import com.trainguy9512.locomotion.animation.data.OnTickDriverContainer; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Boolean driver that can be triggered to get a one-tick "pulse". When triggered, it will automatically be un-triggered after pose function evaluation. 9 | */ 10 | public class TriggerDriver implements Driver { 11 | 12 | private final int triggerTickDuration; 13 | 14 | private int triggerCooldown; 15 | private boolean triggerConsumed; 16 | 17 | private TriggerDriver(int triggerTickDuration) { 18 | this.triggerTickDuration = triggerTickDuration; 19 | this.triggerCooldown = 0; 20 | } 21 | 22 | public static TriggerDriver of() { 23 | return new TriggerDriver(1); 24 | } 25 | 26 | public static TriggerDriver of(int triggerTickDuration) { 27 | return new TriggerDriver(Math.max(1, triggerTickDuration)); 28 | } 29 | 30 | public void trigger() { 31 | this.triggerCooldown = this.triggerTickDuration; 32 | this.triggerConsumed = false; 33 | } 34 | 35 | /** 36 | * Runs a function if the driver has been triggered, and then resets the driver after pose function evaluation. 37 | * @param runnable Function to run if triggered. 38 | */ 39 | public void runIfTriggered(Runnable runnable) { 40 | if (this.triggerCooldown > 0 && !this.triggerConsumed) { 41 | runnable.run(); 42 | this.triggerConsumed = true; 43 | } 44 | } 45 | 46 | public boolean hasBeenTriggered() { 47 | return this.triggerCooldown > 0; 48 | } 49 | 50 | @Override 51 | public void tick() { 52 | 53 | } 54 | 55 | @Override 56 | public Boolean getValueInterpolated(float partialTicks) { 57 | return triggerCooldown > 0; 58 | } 59 | 60 | @Override 61 | public void pushCurrentToPrevious() { 62 | 63 | } 64 | 65 | @Override 66 | public void postTick() { 67 | if (this.triggerConsumed) { 68 | this.triggerCooldown = Math.max(triggerCooldown - 1, 0); 69 | } 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return this.hasBeenTriggered() ? "Triggered!" : "Waiting..."; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/joint/Transformer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.joint; 2 | 3 | @FunctionalInterface 4 | public interface Transformer { 5 | void transform(JointChannel jointChannel, X value, JointChannel.TransformSpace transformSpace, JointChannel.TransformType transformType); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/joint/skeleton/BlendMask.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.joint.skeleton; 2 | 3 | import com.google.common.collect.Maps; 4 | import net.minecraft.util.Mth; 5 | 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | /** 10 | * Configuration that defines the weight influences for each joint, used when blending different poses together that shouldn't blend every joint. 11 | *

Default mask value for unassigned joints is 0.

12 | */ 13 | public class BlendMask extends SkeletonPropertyDefinition { 14 | 15 | private BlendMask(Map jointProperties, Map customAttributeProperties, boolean mirrored) { 16 | super(jointProperties, customAttributeProperties, mirrored, 0f); 17 | } 18 | 19 | @Override 20 | public SkeletonPropertyDefinition getMirrored() { 21 | return new BlendMask(this.jointProperties, this.customAttributeProperties, !this.isMirrored); 22 | } 23 | 24 | public static Builder builder() { 25 | return new Builder(); 26 | } 27 | 28 | public static class Builder { 29 | 30 | private final Map jointWeights; 31 | private final Map customAttributeWeights; 32 | 33 | public Builder() { 34 | this.jointWeights = Maps.newHashMap(); 35 | this.customAttributeWeights = Maps.newHashMap(); 36 | } 37 | 38 | /** 39 | * Defines a weight for the provided joint. 40 | * @param jointName Name of the joint 41 | * @param weight Weight value between 0 and 1. 42 | */ 43 | public Builder defineForJoint(String jointName, float weight) { 44 | this.jointWeights.put(jointName, Mth.clamp(weight, 0f, 1f)); 45 | return this; 46 | } 47 | 48 | /** 49 | * Defines a weight for multiple joints. 50 | * @param jointNames Set of joint names to assign the provided weight to. 51 | * @param weight Weight value between 0 and 1. 52 | */ 53 | public Builder defineForMultipleJoints(Set jointNames, float weight) { 54 | jointNames.forEach(jointName -> this.defineForJoint(jointName, weight)); 55 | return this; 56 | } 57 | 58 | /** 59 | * Defines a weight for the provided custom attribute. 60 | * @param customAttributeName Name of the custom attribute 61 | * @param weight Weight value between 0 and 1. 62 | */ 63 | public Builder defineForCustomAttribute(String customAttributeName, float weight) { 64 | this.jointWeights.put(customAttributeName, Mth.clamp(weight, 0f, 1f)); 65 | return this; 66 | } 67 | 68 | /** 69 | * Defines a weight for multiple custom attributes. 70 | * @param customAttributeNames Set of custom attribute names to assign the provided weight to. 71 | * @param weight Weight value between 0 and 1. 72 | */ 73 | public Builder defineForMultipleCustomAttributes(Set customAttributeNames, float weight) { 74 | customAttributeNames.forEach(jointName -> this.defineForJoint(jointName, weight)); 75 | return this; 76 | } 77 | 78 | public BlendMask build() { 79 | return new BlendMask(this.jointWeights, this.customAttributeWeights, false); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/joint/skeleton/BlendProfile.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.joint.skeleton; 2 | 3 | import com.google.common.collect.Maps; 4 | import net.minecraft.util.Mth; 5 | 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | /** 10 | * Configuration that allows individual joints to blend faster than others through duration multipliers. 11 | * @param jointDurationMultipliers Map of joints to duration multipliers. Duration multipliers can be anywhere between 0 and 1. 12 | * @param isMirrored Whether the blend profile will be mirrored or not, used by {@link BlendProfile#ofMirrored} 13 | */ 14 | public class BlendProfile extends SkeletonPropertyDefinition { 15 | 16 | private BlendProfile(Map jointProperties, Map customAttributeProperties, boolean mirrored) { 17 | super(jointProperties, customAttributeProperties, mirrored, 1f); 18 | } 19 | 20 | @Override 21 | public BlendProfile getMirrored() { 22 | return new BlendProfile(this.jointProperties, this.customAttributeProperties, !this.isMirrored); 23 | } 24 | 25 | public static Builder builder() { 26 | return new Builder(); 27 | } 28 | 29 | public static class Builder { 30 | private final Map jointDurationMultipliers; 31 | private final Map customAttributeDurationMultipliers; 32 | 33 | public Builder() { 34 | this.jointDurationMultipliers = Maps.newHashMap(); 35 | this.customAttributeDurationMultipliers = Maps.newHashMap(); 36 | } 37 | 38 | /** 39 | * Defines a duration multiplier for the provided joint. 40 | * @param jointName Name of the joint 41 | * @param durationMultiplier Float value between 0 and 1. 42 | */ 43 | public Builder defineForJoint(String jointName, float durationMultiplier) { 44 | this.jointDurationMultipliers.put(jointName, Mth.clamp(durationMultiplier, 0.001f, 1f)); 45 | return this; 46 | } 47 | 48 | /** 49 | * Defines a duration multiplier for multiple joints. 50 | * @param jointNames Set of joint names to assign the duration multiplier. 51 | * @param durationMultiplier Float value between 0 and 1. 52 | */ 53 | public Builder defineForMultipleJoints(Set jointNames, float durationMultiplier) { 54 | jointNames.forEach(jointName -> this.defineForJoint(jointName, durationMultiplier)); 55 | return this; 56 | } 57 | 58 | /** 59 | * Defines a weight for the provided custom attribute. 60 | * @param customAttributeName Name of the custom attribute 61 | * @param weight Weight value between 0 and 1. 62 | */ 63 | public Builder defineForCustomAttribute(String customAttributeName, float weight) { 64 | this.customAttributeDurationMultipliers.put(customAttributeName, Mth.clamp(weight, 0f, 1f)); 65 | return this; 66 | } 67 | 68 | /** 69 | * Defines a duration multiplier for multiple custom attributes. 70 | * @param customAttributeNames Set of custom attribute names to assign the provided weight to. 71 | * @param weight Weight value between 0 and 1. 72 | */ 73 | public Builder defineForMultipleCustomAttributes(Set customAttributeNames, float weight) { 74 | customAttributeNames.forEach(jointName -> this.defineForJoint(jointName, weight)); 75 | return this; 76 | } 77 | 78 | public BlendProfile build() { 79 | return new BlendProfile(this.jointDurationMultipliers, this.customAttributeDurationMultipliers, false); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/joint/skeleton/SkeletonPropertyDefinition.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.joint.skeleton; 2 | 3 | import java.util.Map; 4 | 5 | public abstract class SkeletonPropertyDefinition { 6 | 7 | protected final Map jointProperties; 8 | protected final Map customAttributeProperties; 9 | protected final boolean isMirrored; 10 | protected final D defaultValue; 11 | 12 | protected SkeletonPropertyDefinition(Map jointProperties, Map customAttributeProperties, boolean mirrored, D defaultValue) { 13 | this.jointProperties = jointProperties; 14 | this.customAttributeProperties = customAttributeProperties; 15 | this.isMirrored = mirrored; 16 | this.defaultValue = defaultValue; 17 | } 18 | 19 | public D getJointProperty(String jointName, JointSkeleton skeleton) { 20 | if (!skeleton.containsJoint(jointName)) { 21 | return this.defaultValue; 22 | } 23 | if (this.isMirrored) { 24 | jointName = skeleton.getJointConfiguration(jointName).mirrorJoint(); 25 | } 26 | return this.jointProperties.getOrDefault(jointName, this.defaultValue); 27 | } 28 | 29 | public D getCustomAttributeProperty(String customAttributeName, JointSkeleton skeleton) { 30 | if (!skeleton.containsCustomAttribute(customAttributeName)) { 31 | return this.defaultValue; 32 | } 33 | return this.customAttributeProperties.getOrDefault(customAttributeName, this.defaultValue); 34 | } 35 | 36 | public abstract SkeletonPropertyDefinition getMirrored(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/ComponentSpacePose.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose; 2 | 3 | import com.trainguy9512.locomotion.animation.joint.skeleton.JointSkeleton; 4 | import com.trainguy9512.locomotion.animation.joint.JointChannel; 5 | import org.joml.Matrix4f; 6 | 7 | public class ComponentSpacePose extends Pose { 8 | 9 | private ComponentSpacePose(JointSkeleton jointSkeleton) { 10 | super(jointSkeleton); 11 | } 12 | 13 | private ComponentSpacePose(Pose pose){ 14 | super(pose); 15 | } 16 | 17 | 18 | /** 19 | * Creates a blank animation pose using a joint skeleton as the template. 20 | * @param jointSkeleton Template joint skeleton 21 | * @return New animation pose 22 | */ 23 | public static ComponentSpacePose of(JointSkeleton jointSkeleton){ 24 | return new ComponentSpacePose(jointSkeleton); 25 | } 26 | 27 | 28 | public static ComponentSpacePose of(Pose pose){ 29 | return new ComponentSpacePose(pose); 30 | } 31 | 32 | /** 33 | * Retrieves a copy of the transform for the supplied joint. 34 | * @param joint Joint string identifier 35 | * @return Joint transform 36 | */ 37 | public JointChannel getComponentSpaceTransform(String joint){ 38 | return JointChannel.of(this.jointChannels.getOrDefault(joint, JointChannel.ZERO)); 39 | } 40 | 41 | /** 42 | * Creates a local space pose from this component space pose. 43 | */ 44 | public LocalSpacePose convertedToLocalSpace(){ 45 | LocalSpacePose pose = LocalSpacePose.of(this); 46 | pose.convertChildrenJointsToLocalSpace(this.getJointSkeleton().getRootJoint(), new Matrix4f()); 47 | return pose; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/Pose.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.mojang.blaze3d.vertex.PoseStack; 5 | import com.trainguy9512.locomotion.animation.joint.skeleton.JointSkeleton; 6 | import com.trainguy9512.locomotion.animation.joint.JointChannel; 7 | import org.joml.*; 8 | 9 | import java.util.*; 10 | 11 | public abstract class Pose { 12 | 13 | protected final JointSkeleton jointSkeleton; 14 | protected final Map jointChannels; 15 | protected final Map customAttributes; 16 | private final Map jointParentMatrices; 17 | 18 | protected Pose (JointSkeleton jointSkeleton) { 19 | this.jointSkeleton = jointSkeleton; 20 | this.jointChannels = Maps.newHashMap(); 21 | this.customAttributes = jointSkeleton.getCustomAttributeDefaults(); 22 | this.jointParentMatrices = Maps.newHashMap(); 23 | 24 | for(String joint : jointSkeleton.getJoints()){ 25 | this.setJointChannel(joint, JointChannel.ZERO); 26 | } 27 | } 28 | 29 | protected Pose (Pose pose) { 30 | this.jointSkeleton = pose.jointSkeleton; 31 | this.jointChannels = new HashMap<>(pose.jointChannels); 32 | this.customAttributes = Maps.newHashMap(pose.customAttributes); 33 | this.jointParentMatrices = new HashMap<>(pose.jointParentMatrices); 34 | } 35 | 36 | /** 37 | * Retrieves the animation pose's skeleton. 38 | * @return Joint skeleton 39 | */ 40 | public JointSkeleton getJointSkeleton(){ 41 | return this.jointSkeleton; 42 | } 43 | 44 | /** 45 | * Sets the transform for the supplied joint by its string identifier. 46 | * @param joint Joint string identifier 47 | * @param jointChannel Joint transform 48 | */ 49 | public void setJointChannel(String joint, JointChannel jointChannel){ 50 | if(this.jointSkeleton.containsJoint(joint)){ 51 | this.jointChannels.put(joint, jointChannel); 52 | } 53 | } 54 | 55 | /** 56 | * Retrieves a copy of the transform for the supplied joint. 57 | * @param joint Joint string identifier 58 | * @return Joint transform 59 | */ 60 | public JointChannel getJointChannel(String joint){ 61 | return JointChannel.of(this.jointChannels.getOrDefault(joint, JointChannel.ZERO)); 62 | } 63 | 64 | public void loadCustomAttributeValue(String customAttributeName, float value) { 65 | this.customAttributes.put(customAttributeName, value); 66 | } 67 | 68 | public float getCustomAttributeValue(String customAttributeName) { 69 | if (!this.customAttributes.containsKey(customAttributeName)) { 70 | throw new IllegalArgumentException("Custom attribute \"" + customAttributeName + "\" cannot be accessed from pose, not included in the following curves: " + this.customAttributes.keySet()); 71 | } 72 | return this.customAttributes.get(customAttributeName); 73 | } 74 | 75 | public boolean getCustomAttributeValueAsBoolean(String customAttributeName) { 76 | return this.getCustomAttributeValue(customAttributeName) > 0.5; 77 | } 78 | 79 | public

P copyCustomAttributesFrom(P other) { 80 | this.customAttributes.putAll(other.customAttributes); 81 | return (P) this; 82 | } 83 | 84 | protected void convertChildrenJointsToComponentSpace(String parent, PoseStack poseStack){ 85 | JointChannel localParentJointChannel = this.getJointChannel(parent); 86 | 87 | poseStack.pushPose(); 88 | poseStack.mulPose(localParentJointChannel.getTransform()); 89 | 90 | this.getJointSkeleton().getDirectChildrenOfJoint(parent).forEach(child -> this.convertChildrenJointsToComponentSpace(child, poseStack)); 91 | 92 | Matrix4f componentSpaceMatrix = new Matrix4f(poseStack.last().pose()); 93 | this.jointParentMatrices.put(parent, componentSpaceMatrix); 94 | this.setJointChannel(parent, JointChannel.of(componentSpaceMatrix, localParentJointChannel.getVisibility())); 95 | poseStack.popPose(); 96 | } 97 | 98 | protected void convertChildrenJointsToLocalSpace(String parent, Matrix4f parentMatrix){ 99 | 100 | this.getJointSkeleton().getDirectChildrenOfJoint(parent).forEach(child -> this.convertChildrenJointsToLocalSpace(child, this.jointParentMatrices.get(parent))); 101 | 102 | JointChannel parentJointChannel = this.getJointChannel(parent); 103 | parentJointChannel.multiply(parentMatrix.invert(new Matrix4f()), JointChannel.TransformSpace.LOCAL); 104 | this.setJointChannel(parent, parentJointChannel); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/AnimationPlayer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.function.statemachine.StateMachineFunction; 4 | import com.trainguy9512.locomotion.util.TimeSpan; 5 | import net.minecraft.util.Tuple; 6 | 7 | public interface AnimationPlayer { 8 | 9 | /** 10 | * Returns the remaining time in the sequence player at the previous tick and the current tick. 11 | * Meant to be called in contexts just prior to this pose function updating 12 | * @implNote Tuple should be (remainingTime - playRate, remainingTime) 13 | */ 14 | Tuple getRemainingTime(); 15 | 16 | /** 17 | * Returns the length of the animation currently being played. 18 | */ 19 | TimeSpan getAnimationLength(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/ApplyAdditiveFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.joint.JointChannel; 4 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Optional; 8 | import java.util.function.Function; 9 | 10 | /** 11 | * Pose function that adds an additive pose to a base animation pose, based on an alpha value. 12 | */ 13 | public class ApplyAdditiveFunction implements PoseFunction { 14 | 15 | private final PoseFunction basePoseInput; 16 | private final PoseFunction additivePoseInput; 17 | 18 | private final Function alphaFunction; 19 | 20 | public ApplyAdditiveFunction(PoseFunction basePoseInput, PoseFunction additivePoseInput, Function alphaFunction) { 21 | this.basePoseInput = basePoseInput; 22 | this.additivePoseInput = additivePoseInput; 23 | this.alphaFunction = alphaFunction; 24 | } 25 | 26 | public static ApplyAdditiveFunction of(PoseFunction basePoseInput, PoseFunction additivePoseInput, Function weightFunction) { 27 | return new ApplyAdditiveFunction(basePoseInput, additivePoseInput, weightFunction); 28 | } 29 | 30 | public static ApplyAdditiveFunction of(PoseFunction basePoseInput, PoseFunction additivePoseInput) { 31 | return new ApplyAdditiveFunction(basePoseInput, additivePoseInput, context -> 1f); 32 | } 33 | 34 | @Override 35 | public @NotNull LocalSpacePose compute(FunctionInterpolationContext context) { 36 | LocalSpacePose basePose = this.basePoseInput.compute(context); 37 | 38 | LocalSpacePose additivePose = this.additivePoseInput.compute(context); 39 | additivePose.multiply(basePose, JointChannel.TransformSpace.COMPONENT); 40 | additivePose.copyCustomAttributesFrom(basePose); 41 | 42 | float weight = this.alphaFunction.apply(context); 43 | if (weight == 1f) { 44 | return additivePose; 45 | } else if (weight == 0f) { 46 | return basePose; 47 | } else { 48 | return basePose.interpolated(additivePose, weight); 49 | } 50 | } 51 | 52 | @Override 53 | public void tick(FunctionEvaluationState evaluationState) { 54 | this.basePoseInput.tick(evaluationState); 55 | this.additivePoseInput.tick(evaluationState); 56 | } 57 | 58 | @Override 59 | public PoseFunction wrapUnique() { 60 | return new ApplyAdditiveFunction(this.basePoseInput.wrapUnique(), this.additivePoseInput.wrapUnique(), this.alphaFunction); 61 | } 62 | 63 | @Override 64 | public Optional testForMostRelevantAnimationPlayer() { 65 | // Test the base pose input first. If it does not have a relevant animation player, then test the additive pose input. 66 | Optional test = this.basePoseInput.testForMostRelevantAnimationPlayer(); 67 | if (test.isPresent()) { 68 | return test; 69 | } 70 | return this.additivePoseInput.testForMostRelevantAnimationPlayer(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/BlendPosesFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.trainguy9512.locomotion.animation.driver.VariableDriver; 5 | import com.trainguy9512.locomotion.animation.joint.skeleton.BlendMask; 6 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.*; 11 | import java.util.function.Function; 12 | 13 | public class BlendPosesFunction implements PoseFunction { 14 | 15 | private final PoseFunction baseFunction; 16 | private final Map> inputs; 17 | 18 | public BlendPosesFunction(PoseFunction baseFunction, Map> inputs){ 19 | this.baseFunction = baseFunction; 20 | this.inputs = inputs; 21 | } 22 | 23 | @Override 24 | public @NotNull LocalSpacePose compute(FunctionInterpolationContext context) { 25 | LocalSpacePose pose = this.baseFunction.compute(context); 26 | for(BlendInput blendInput : this.inputs.keySet()) { 27 | float weight = this.inputs.get(blendInput).getValueInterpolated(context.partialTicks()); 28 | if(weight != 0f){ 29 | pose = pose.interpolated(blendInput.inputFunction.compute(context), weight, blendInput.blendMask); 30 | } 31 | } 32 | return pose; 33 | } 34 | 35 | @Override 36 | public void tick(FunctionEvaluationState evaluationState) { 37 | this.baseFunction.tick(evaluationState); 38 | this.inputs.forEach((blendInput, weightDriver) -> { 39 | weightDriver.pushCurrentToPrevious(); 40 | float weight = blendInput.weightFunction.apply(evaluationState); 41 | weightDriver.setValue(weight); 42 | 43 | if(weight != 0f) { 44 | blendInput.inputFunction.tick(evaluationState); 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | public PoseFunction wrapUnique() { 51 | Builder builder = BlendPosesFunction.builder(this.baseFunction.wrapUnique()); 52 | for(BlendInput blendInput : this.inputs.keySet()){ 53 | builder.addBlendInput(blendInput.inputFunction.wrapUnique(), blendInput.weightFunction, blendInput.blendMask); 54 | } 55 | return builder.build(); 56 | } 57 | 58 | @Override 59 | public Optional testForMostRelevantAnimationPlayer() { 60 | List> blendAnimationPlayers = new ArrayList<>(); 61 | this.inputs.forEach(((blendInput, weightDriver) -> { 62 | if (weightDriver.getCurrentValue() >= 0.5f) { 63 | blendAnimationPlayers.add(blendInput.inputFunction.testForMostRelevantAnimationPlayer()); 64 | } 65 | })); 66 | if (!blendAnimationPlayers.isEmpty()) { 67 | return blendAnimationPlayers.getLast().isPresent() ? 68 | blendAnimationPlayers.getLast() : 69 | this.baseFunction.testForMostRelevantAnimationPlayer(); 70 | } else { 71 | return this.baseFunction.testForMostRelevantAnimationPlayer(); 72 | } 73 | } 74 | 75 | 76 | public static Builder builder(PoseFunction base){ 77 | return new Builder(base); 78 | } 79 | 80 | public static class Builder { 81 | 82 | private final PoseFunction baseFunction; 83 | private final Map> inputs; 84 | 85 | private Builder(PoseFunction baseFunction){ 86 | this.baseFunction = baseFunction; 87 | this.inputs = Maps.newHashMap(); 88 | } 89 | 90 | public Builder addBlendInput(PoseFunction inputFunction, Function weightFunction, @Nullable BlendMask blendMask){ 91 | this.inputs.put(new BlendInput(inputFunction, weightFunction, blendMask), VariableDriver.ofFloat(() -> 0f)); 92 | return this; 93 | } 94 | 95 | public Builder addBlendInput(PoseFunction inputFunction, Function weightFunction){ 96 | return this.addBlendInput(inputFunction, weightFunction, null); 97 | } 98 | 99 | public BlendPosesFunction build(){ 100 | return new BlendPosesFunction(this.baseFunction, this.inputs); 101 | } 102 | } 103 | 104 | public record BlendInput( 105 | PoseFunction inputFunction, 106 | Function weightFunction, 107 | @Nullable BlendMask blendMask 108 | ) { 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/MakeDynamicAdditiveFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.joint.JointChannel; 4 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * Pose function that creates an additive animation pose by subtracting a base pose from the desired additive pose. 11 | */ 12 | public class MakeDynamicAdditiveFunction implements PoseFunction { 13 | 14 | private final PoseFunction additivePoseInput; 15 | private final PoseFunction basePoseInput; 16 | 17 | public MakeDynamicAdditiveFunction(PoseFunction additivePoseInput, PoseFunction basePoseInput) { 18 | this.additivePoseInput = additivePoseInput; 19 | this.basePoseInput = basePoseInput; 20 | } 21 | 22 | public static MakeDynamicAdditiveFunction of(PoseFunction additivePoseInput, PoseFunction basePoseInput) { 23 | return new MakeDynamicAdditiveFunction(additivePoseInput, basePoseInput); 24 | } 25 | 26 | @Override 27 | public @NotNull LocalSpacePose compute(FunctionInterpolationContext context) { 28 | LocalSpacePose additivePose = this.additivePoseInput.compute(context); 29 | LocalSpacePose additivePoseReference = this.basePoseInput.compute(context); 30 | 31 | additivePoseReference.invert(); 32 | additivePose.multiply(additivePoseReference, JointChannel.TransformSpace.COMPONENT); 33 | 34 | return additivePose; 35 | } 36 | 37 | @Override 38 | public void tick(FunctionEvaluationState evaluationState) { 39 | this.additivePoseInput.tick(evaluationState); 40 | this.basePoseInput.tick(evaluationState); 41 | } 42 | 43 | @Override 44 | public PoseFunction wrapUnique() { 45 | return new MakeDynamicAdditiveFunction(this.additivePoseInput.wrapUnique(), this.basePoseInput.wrapUnique()); 46 | } 47 | 48 | @Override 49 | public Optional testForMostRelevantAnimationPlayer() { 50 | // Test the additive pose input first. If it does not have a relevant animation player, then test the base pose input. 51 | Optional test = this.additivePoseInput.testForMostRelevantAnimationPlayer(); 52 | if (test.isPresent()) { 53 | return test; 54 | } 55 | return this.basePoseInput.testForMostRelevantAnimationPlayer(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/MirrorFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Optional; 7 | import java.util.function.Function; 8 | 9 | /** 10 | * Takes an input pose and mirrors it. 11 | */ 12 | public class MirrorFunction implements PoseFunction { 13 | 14 | private final PoseFunction input; 15 | private final Function enabledFunction; 16 | 17 | public MirrorFunction(PoseFunction input, Function enabledFunction) { 18 | this.input = input; 19 | this.enabledFunction = enabledFunction; 20 | } 21 | 22 | public static MirrorFunction of(PoseFunction input, Function mirrorFunction) { 23 | return new MirrorFunction(input, mirrorFunction); 24 | } 25 | 26 | public static MirrorFunction of(PoseFunction input) { 27 | return new MirrorFunction(input, context -> true); 28 | } 29 | 30 | @Override 31 | public @NotNull LocalSpacePose compute(FunctionInterpolationContext context) { 32 | if (this.enabledFunction.apply(context)) { 33 | return input.compute(context).mirrored(); 34 | } else { 35 | return input.compute(context); 36 | } 37 | } 38 | 39 | @Override 40 | public void tick(FunctionEvaluationState evaluationState) { 41 | this.input.tick(evaluationState); 42 | } 43 | 44 | @Override 45 | public PoseFunction wrapUnique() { 46 | return MirrorFunction.of(this.input.wrapUnique(), this.enabledFunction); 47 | } 48 | 49 | @Override 50 | public Optional testForMostRelevantAnimationPlayer() { 51 | return this.input.testForMostRelevantAnimationPlayer(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/PoseConversionFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.ComponentSpacePose; 4 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 5 | import com.trainguy9512.locomotion.animation.pose.Pose; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Optional; 9 | import java.util.function.Function; 10 | 11 | /** 12 | * Converts a pose from component space to local space or local space to component space 13 | * @param input Input pose function 14 | * @param converter Conversion function (handled by static factory) 15 | * @param Input pose type 16 | * @param Output pose type 17 | */ 18 | public record PoseConversionFunction(PoseFunction input, Function converter) implements PoseFunction { 19 | @Override 20 | public @NotNull O compute(FunctionInterpolationContext context) { 21 | return this.converter.apply(this.input.compute(context)); 22 | } 23 | 24 | @Override 25 | public void tick(FunctionEvaluationState evaluationState) { 26 | this.input.tick(evaluationState); 27 | } 28 | 29 | @Override 30 | public PoseFunction wrapUnique() { 31 | return new PoseConversionFunction<>(this.input.wrapUnique(), this.converter); 32 | } 33 | 34 | @Override 35 | public Optional testForMostRelevantAnimationPlayer() { 36 | return this.input.testForMostRelevantAnimationPlayer(); 37 | } 38 | 39 | /** 40 | * Creates a pose conversion function that converts the local space pose to a component space pose. 41 | * @param input Local space pose function 42 | */ 43 | public static PoseConversionFunction localToComponentOf(PoseFunction input){ 44 | return new PoseConversionFunction<>(input, LocalSpacePose::convertedToComponentSpace); 45 | } 46 | 47 | /** 48 | * Creates a pose conversion function that converts the component space pose to a local space pose. 49 | * @param input Component space pose function 50 | */ 51 | public static PoseConversionFunction componentToLocalOf(PoseFunction input){ 52 | return new PoseConversionFunction<>(input, ComponentSpacePose::convertedToLocalSpace); 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/PoseFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.data.OnTickDriverContainer; 4 | import com.trainguy9512.locomotion.animation.data.PoseCalculationDataContainer; 5 | import com.trainguy9512.locomotion.animation.pose.Pose; 6 | import com.trainguy9512.locomotion.animation.pose.function.montage.MontageManager; 7 | import com.trainguy9512.locomotion.util.TimeSpan; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Optional; 11 | 12 | public interface PoseFunction

{ 13 | 14 | /** 15 | * Computes and returns an animation pose using its inputs. 16 | * @param context Interpolation context, containing the driver container, partial ticks float, and elapsed game time for calculating values every frame. 17 | * @implNote Called every frame for joint animators that compute a new pose every frame, or once per tick. 18 | */ 19 | @NotNull P compute(PoseFunction.FunctionInterpolationContext context); 20 | 21 | /** 22 | * Updates the function and then updates the function's inputs. 23 | * @param evaluationState Current state of the evaluation, with the data container as well as 24 | * @implNote If an input is deemed irrelevant, or not necessary during pose calculation, the input does not need to be ticked. 25 | * @implNote Called once per tick, with the assumption that per-frame values can be interpolated. 26 | */ 27 | void tick(FunctionEvaluationState evaluationState); 28 | 29 | /** 30 | * Recursive method that creates and returns a new copy of the function with its inputs also copied. 31 | * This ensures that no pose function is referenced twice, besides cached pose functions. 32 | * If a pose were referenced as an input twice, then it would tick and compute twice, which can lead to undesirable results. 33 | * @implNote Called after the joint animator's pose function is constructed. 34 | * @return Clean copy of the pose function with its inputs being clean copies 35 | */ 36 | PoseFunction

wrapUnique(); 37 | 38 | /** 39 | * Recursive method that goes down the chain of pose functions returns the most relevant {@link AnimationPlayer}. 40 | *

41 | * If this pose function is not an {@link AnimationPlayer}, or it is set to be ignored for relevancy tests, 42 | * then call this method for all inputs in order of most to least relevant. 43 | * If this pose function is the end of a chain and is not an animation player, then return null. 44 | * @return Most relevant animation player, if it exists in this part of the chain. 45 | */ 46 | Optional testForMostRelevantAnimationPlayer(); 47 | 48 | record FunctionEvaluationState(OnTickDriverContainer driverContainer, MontageManager montageManager, boolean resetting, long currentTick) { 49 | 50 | public static FunctionEvaluationState of(OnTickDriverContainer driverContainer, MontageManager montageManager, boolean resetting, long currentTick) { 51 | return new FunctionEvaluationState(driverContainer, montageManager, resetting, currentTick); 52 | } 53 | 54 | /** 55 | * Creates a copy of the evaluation state that is marked for a hard reset. 56 | * 57 | *

A hard reset is an animation reset that immediately resets with no blending.

58 | */ 59 | public FunctionEvaluationState markedForReset() { 60 | return FunctionEvaluationState.of(this.driverContainer, this.montageManager, true, this.currentTick); 61 | } 62 | 63 | public FunctionEvaluationState cleared() { 64 | return FunctionEvaluationState.of(this.driverContainer, this.montageManager, false, this.currentTick); 65 | } 66 | 67 | /** 68 | * Runs the provided function if this evaluation state is marked for hard reset. 69 | */ 70 | public void ifMarkedForReset(Runnable runnable) { 71 | if (this.resetting) { 72 | runnable.run(); 73 | } 74 | } 75 | } 76 | 77 | record FunctionInterpolationContext(PoseCalculationDataContainer driverContainer, MontageManager montageManager, float partialTicks, TimeSpan gameTime) { 78 | public static FunctionInterpolationContext of(PoseCalculationDataContainer dataContainer, MontageManager montageManager, float partialTicks, TimeSpan gameTime){ 79 | return new FunctionInterpolationContext(dataContainer, montageManager, partialTicks, gameTime); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/SequenceEvaluatorFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 4 | import com.trainguy9512.locomotion.animation.sequence.AnimationSequence; 5 | import com.trainguy9512.locomotion.util.TimeSpan; 6 | import net.minecraft.resources.ResourceLocation; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.Optional; 10 | import java.util.function.Function; 11 | 12 | public class SequenceEvaluatorFunction implements PoseFunction { 13 | 14 | private final Function animationSequenceFunction; 15 | private final Function sequenceTimeFunction; 16 | 17 | private SequenceEvaluatorFunction(Function animationSequenceFunction, Function sequenceTimeFunction) { 18 | this.animationSequenceFunction = animationSequenceFunction; 19 | this.sequenceTimeFunction = sequenceTimeFunction; 20 | } 21 | 22 | public static Builder builder(Function animationSequenceFunction) { 23 | return new Builder(animationSequenceFunction); 24 | } 25 | 26 | public static Builder builder(ResourceLocation animationSequence) { 27 | return builder(context -> animationSequence); 28 | } 29 | 30 | @Override 31 | public @NotNull LocalSpacePose compute(FunctionInterpolationContext context) { 32 | TimeSpan time = this.sequenceTimeFunction.apply(context); 33 | return AnimationSequence.samplePose( 34 | context.driverContainer().getJointSkeleton(), 35 | this.animationSequenceFunction.apply(context), 36 | time, 37 | false 38 | ); 39 | } 40 | 41 | @Override 42 | public void tick(FunctionEvaluationState evaluationState) { 43 | 44 | } 45 | 46 | @Override 47 | public PoseFunction wrapUnique() { 48 | return new SequenceEvaluatorFunction(this.animationSequenceFunction, this.sequenceTimeFunction); 49 | } 50 | 51 | @Override 52 | public Optional testForMostRelevantAnimationPlayer() { 53 | return Optional.empty(); 54 | } 55 | 56 | public static class Builder { 57 | private final Function animationSequenceFunction; 58 | private Function sequenceTimeFunction; 59 | 60 | public Builder(Function animationSequenceFunction) { 61 | this.animationSequenceFunction = animationSequenceFunction; 62 | this.sequenceTimeFunction = context -> TimeSpan.ZERO; 63 | } 64 | 65 | public Builder evaluatesPoseAt(Function sequenceTimeFunction) { 66 | this.sequenceTimeFunction = sequenceTimeFunction; 67 | return this; 68 | } 69 | 70 | public Builder evaluatesPoseAt(TimeSpan sequenceTime) { 71 | this.sequenceTimeFunction = context -> sequenceTime; 72 | return this; 73 | } 74 | 75 | public SequenceEvaluatorFunction build() { 76 | return new SequenceEvaluatorFunction(this.animationSequenceFunction, this.sequenceTimeFunction); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/SequenceReferencePoint.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | public enum SequenceReferencePoint { 4 | BEGINNING, 5 | END 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/TimeBasedPoseFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function; 2 | 3 | import com.trainguy9512.locomotion.animation.driver.VariableDriver; 4 | import com.trainguy9512.locomotion.animation.pose.Pose; 5 | import com.trainguy9512.locomotion.util.TimeSpan; 6 | 7 | import java.util.function.Function; 8 | 9 | public abstract class TimeBasedPoseFunction

implements PoseFunction

{ 10 | 11 | protected final Function isPlayingFunction; 12 | protected final Function playRateFunction; 13 | protected final TimeSpan resetStartTimeOffset; 14 | 15 | protected final VariableDriver ticksElapsed; 16 | protected float playRate; 17 | protected boolean isPlaying; 18 | 19 | protected TimeBasedPoseFunction(Function isPlayingFunction, Function playRateFunction, TimeSpan resetStartTimeOffset){ 20 | this.isPlayingFunction = isPlayingFunction; 21 | this.playRateFunction = playRateFunction; 22 | this.resetStartTimeOffset = resetStartTimeOffset; 23 | 24 | this.ticksElapsed = VariableDriver.ofFloat(this.resetStartTimeOffset::inTicks); 25 | } 26 | 27 | @Override 28 | public void tick(FunctionEvaluationState evaluationState) { 29 | this.isPlaying = isPlayingFunction.apply(evaluationState); 30 | this.playRate = this.isPlaying ? playRateFunction.apply(evaluationState) : 0; 31 | 32 | this.updateTime(evaluationState); 33 | } 34 | 35 | protected void updateTime(FunctionEvaluationState evaluationState) { 36 | this.ticksElapsed.pushCurrentToPrevious(); 37 | evaluationState.ifMarkedForReset(this::resetTime); 38 | if (this.isPlaying) { 39 | this.ticksElapsed.modifyValue(currentValue -> currentValue + this.playRate); 40 | } 41 | } 42 | 43 | protected void resetTime() { 44 | this.ticksElapsed.hardReset(); 45 | } 46 | 47 | protected TimeSpan getInterpolatedTimeElapsed(FunctionInterpolationContext context){ 48 | return TimeSpan.ofTicks(this.ticksElapsed.getValueInterpolated(context.partialTicks())); 49 | } 50 | 51 | public static class Builder { 52 | 53 | protected Function playRateFunction; 54 | protected Function isPlayingFunction; 55 | protected TimeSpan resetStartTimeOffsetTicks; 56 | 57 | protected Builder() { 58 | this.playRateFunction = (interpolationContext) -> 1f; 59 | this.isPlayingFunction = (interpolationContext) -> true; 60 | this.resetStartTimeOffsetTicks = TimeSpan.ZERO; 61 | } 62 | 63 | /** 64 | * Sets the function that is used to update the function's play rate each tick. 65 | *

66 | * The play rate is used as a multiplier when incrementing time, so 1 is normal time, 0.5 is half as fast, and 2 is twice as fast. 67 | */ 68 | @SuppressWarnings("unchecked") 69 | public B setPlayRate(Function playRateFunction) { 70 | this.playRateFunction = playRateFunction; 71 | return (B) this; 72 | } 73 | 74 | /** 75 | * Sets a constant play rate. 76 | *

77 | * The play rate is used as a multiplier when incrementing time, so 1 is normal time, 0.5 is half as fast, and 2 is twice as fast. 78 | */ 79 | @SuppressWarnings("unchecked") 80 | public B setPlayRate(float playRate) { 81 | this.playRateFunction = evaluationState -> playRate; 82 | return (B) this; 83 | } 84 | 85 | /** 86 | * Sets the function that is used to update whether the function is playing or not each tick. 87 | */ 88 | @SuppressWarnings("unchecked") 89 | public B setIsPlaying(Function isPlayingFunction) { 90 | this.isPlayingFunction = isPlayingFunction; 91 | return (B) this; 92 | } 93 | 94 | /** 95 | * Sets the reset start time offset, which is used to offset the start time when the function is reset. Default is 0. 96 | * @param startTimeOffset Offset time. 97 | */ 98 | @SuppressWarnings("unchecked") 99 | public B setResetStartTimeOffset(TimeSpan startTimeOffset) { 100 | this.resetStartTimeOffsetTicks = startTimeOffset; 101 | return (B) this; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/cache/CachedPoseContainer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function.cache; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 5 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | public class CachedPoseContainer { 11 | 12 | private final Map cachedPoseFunctions; 13 | 14 | private CachedPoseContainer() { 15 | this.cachedPoseFunctions = Maps.newHashMap(); 16 | } 17 | 18 | public static CachedPoseContainer of() { 19 | return new CachedPoseContainer(); 20 | } 21 | 22 | public void register(String identifier, PoseFunction poseFunction, boolean resetsUponRelevant) { 23 | if(this.cachedPoseFunctions.containsKey(identifier)){ 24 | throw new IllegalArgumentException("Failed to register saved cached pose for identifier " + identifier + " due to it being already registered."); 25 | } else { 26 | this.cachedPoseFunctions.put(identifier, CachedPoseFunction.of(poseFunction, resetsUponRelevant)); 27 | } 28 | } 29 | 30 | public PoseFunction getOrThrow(String identifier) { 31 | return Optional.ofNullable(this.cachedPoseFunctions.get(identifier)).orElseThrow(() -> new IllegalStateException("Missing saved cached pose for identifier " + identifier + ". Maybe it's being accessed before it has been defined?")); 32 | } 33 | 34 | public void clearCaches() { 35 | this.cachedPoseFunctions.values().forEach(CachedPoseFunction::clearCache); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/cache/CachedPoseFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function.cache; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 4 | import com.trainguy9512.locomotion.animation.pose.function.AnimationPlayer; 5 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Optional; 9 | 10 | public class CachedPoseFunction implements PoseFunction { 11 | 12 | private PoseFunction input; 13 | private final boolean resetsUponRelevant; 14 | 15 | LocalSpacePose poseCache; 16 | boolean hasTickedAlready; 17 | private long lastUpdateTick; 18 | 19 | private CachedPoseFunction(PoseFunction input, boolean resetsUponRelevant) { 20 | this.input = input; 21 | this.resetsUponRelevant = resetsUponRelevant; 22 | this.poseCache = null; 23 | this.hasTickedAlready = false; 24 | } 25 | 26 | protected static CachedPoseFunction of(PoseFunction input, boolean resetsUponRelevant) { 27 | return new CachedPoseFunction(input, resetsUponRelevant); 28 | } 29 | 30 | @Override 31 | public @NotNull LocalSpacePose compute(FunctionInterpolationContext context) { 32 | if (this.poseCache == null) { 33 | this.poseCache = this.input.compute(context); 34 | } 35 | return LocalSpacePose.of(this.poseCache); 36 | } 37 | 38 | @Override 39 | public void tick(FunctionEvaluationState evaluationState) { 40 | if (!this.hasTickedAlready) { 41 | if (evaluationState.currentTick() - 1 > this.lastUpdateTick && this.resetsUponRelevant) { 42 | this.input.tick(evaluationState.cleared().markedForReset()); 43 | } else { 44 | this.input.tick(evaluationState.cleared()); 45 | } 46 | this.lastUpdateTick = evaluationState.currentTick(); 47 | this.hasTickedAlready = true; 48 | } 49 | } 50 | 51 | @Override 52 | public PoseFunction wrapUnique() { 53 | this.input = input.wrapUnique(); 54 | return this; 55 | } 56 | 57 | @Override 58 | public Optional testForMostRelevantAnimationPlayer() { 59 | return Optional.empty(); 60 | } 61 | 62 | public void clearCache() { 63 | this.poseCache = null; 64 | this.hasTickedAlready = false; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/montage/MontageSlotFunction.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function.montage; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 4 | import com.trainguy9512.locomotion.animation.pose.function.AnimationPlayer; 5 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Optional; 9 | 10 | public record MontageSlotFunction(PoseFunction inputPose, String slot) implements PoseFunction { 11 | 12 | public static MontageSlotFunction of(PoseFunction inputPose, String slot) { 13 | return new MontageSlotFunction(inputPose, slot); 14 | } 15 | 16 | @Override 17 | public @NotNull LocalSpacePose compute(FunctionInterpolationContext context) { 18 | return context.montageManager().getLayeredSlotPose(this.inputPose.compute(context), this.slot, context.driverContainer().getJointSkeleton(), context.partialTicks()); 19 | } 20 | 21 | @Override 22 | public void tick(FunctionEvaluationState evaluationState) { 23 | if (!evaluationState.montageManager().areAnyMontagesInSlotFullyOverriding(this.slot)) { 24 | this.inputPose.tick(evaluationState); 25 | } 26 | } 27 | 28 | @Override 29 | public PoseFunction wrapUnique() { 30 | return MontageSlotFunction.of(this.inputPose, this.slot); 31 | } 32 | 33 | @Override 34 | public Optional testForMostRelevantAnimationPlayer() { 35 | return this.inputPose.testForMostRelevantAnimationPlayer(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/statemachine/State.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function.statemachine; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 5 | import com.trainguy9512.locomotion.animation.pose.function.PoseFunction; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class State> { 13 | 14 | private static final Logger LOGGER = LogManager.getLogger("Locomotion/State"); 15 | 16 | protected final S identifier; 17 | protected final PoseFunction inputFunction; 18 | protected final List> outboundTransitions; 19 | protected final boolean resetUponEntry; 20 | 21 | protected State(S identifier, PoseFunction inputFunction, List> outboundTransitions, boolean resetUponEntry) { 22 | this.identifier = identifier; 23 | this.inputFunction = inputFunction; 24 | this.outboundTransitions = outboundTransitions; 25 | this.resetUponEntry = resetUponEntry; 26 | 27 | if (!resetUponEntry) { 28 | for (StateTransition transition : outboundTransitions) { 29 | if (transition.isAutomaticTransition()) { 30 | LOGGER.warn("State transition to state {} in a state machine is set to be automatic based on the input sequence player, but the origin state is not set to reset upon entry. Automatic transitions are intended to be used with reset-upon-entry states, beware of unexpected behavior!", transition.target()); 31 | } 32 | } 33 | } 34 | } 35 | 36 | /** 37 | * Creates a new state builder. 38 | * 39 | * @param identifier Enum identifier that is associated with this state. Used for identifying transition targets. 40 | * @param inputFunction Pose function used for this state when it's active. 41 | */ 42 | public static > Builder builder(S identifier, PoseFunction inputFunction) { 43 | return new Builder<>(identifier, inputFunction); 44 | } 45 | 46 | /** 47 | * Creates a new state builder with the properties of the provided state. 48 | * 49 | * @param state Identifier for the new state. 50 | */ 51 | protected static > Builder builder(State state) { 52 | return new Builder<>(state); 53 | } 54 | 55 | public static class Builder> { 56 | 57 | private final S identifier; 58 | private PoseFunction inputFunction; 59 | private final List> outboundTransitions; 60 | private boolean resetUponEntry; 61 | 62 | private Builder(S identifier, PoseFunction inputFunction) { 63 | this.identifier = identifier; 64 | this.inputFunction = inputFunction; 65 | this.outboundTransitions = new ArrayList<>(); 66 | this.resetUponEntry = false; 67 | } 68 | 69 | private Builder(State state) { 70 | this.identifier = state.identifier; 71 | this.inputFunction = state.inputFunction; 72 | this.outboundTransitions = state.outboundTransitions; 73 | this.resetUponEntry = state.resetUponEntry; 74 | } 75 | 76 | /** 77 | * If true, this state will reset its pose function every time it is entered. 78 | */ 79 | public Builder resetsPoseFunctionUponEntry(boolean resetUponEntry) { 80 | this.resetUponEntry = resetUponEntry; 81 | return this; 82 | } 83 | 84 | /** 85 | * Assigns a set of potential outbound transitions to this state. 86 | * 87 | * @param transitions Set of individual transitions. 88 | */ 89 | protected Builder addOutboundTransitions(List> transitions) { 90 | transitions.forEach(this::addOutboundTransition); 91 | return this; 92 | } 93 | 94 | /** 95 | * Assigns a potential outbound transition to this state. 96 | * 97 | * @param transition Outbound transition. 98 | */ 99 | public final Builder addOutboundTransition(StateTransition transition) { 100 | this.outboundTransitions.add(transition); 101 | if (transition.target() == this.identifier) { 102 | LOGGER.warn("Cannot add state transition to state {} from the same state {}", transition.target(), this.identifier); 103 | } 104 | return this; 105 | } 106 | 107 | protected Builder wrapUniquePoseFunction() { 108 | this.inputFunction = inputFunction.wrapUnique(); 109 | return this; 110 | } 111 | 112 | public State build() { 113 | return new State<>(this.identifier, this.inputFunction, this.outboundTransitions, this.resetUponEntry); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/animation/pose/function/statemachine/StateAlias.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.animation.pose.function.statemachine; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | public record StateAlias>(Set originStates, List> outboundTransitions) { 13 | 14 | private static final Logger LOGGER = LogManager.getLogger("Locomotion/StateAlias"); 15 | 16 | /** 17 | * Creates a new state alias builder. 18 | * 19 | * @param originStates States that the alias' transitions can originate from. 20 | */ 21 | public static > Builder builder(Set originStates) { 22 | return new Builder<>(originStates); 23 | } 24 | 25 | public static class Builder> { 26 | 27 | private final Set originStates; 28 | private final List> outboundTransitions; 29 | 30 | private Builder(Set originStates) { 31 | this.originStates = new HashSet<>(originStates); 32 | this.outboundTransitions = new ArrayList<>(); 33 | } 34 | 35 | /** 36 | * Adds a set of states that the alias' transitions can originate from. 37 | * @param states State identifiers 38 | */ 39 | public final Builder addOriginatingStates(Set states) { 40 | this.originStates.addAll(states); 41 | return this; 42 | } 43 | 44 | /** 45 | * Adds a state that the alias' transitions can originate from. 46 | * @param state State identifier 47 | */ 48 | public final Builder addOriginatingState(S state) { 49 | this.originStates.add(state); 50 | return this; 51 | } 52 | 53 | /** 54 | * Assigns a potential outbound transitions to this state alias. 55 | * 56 | * @param transition Outbound transitions. 57 | */ 58 | public Builder addOutboundTransition(StateTransition transition) { 59 | if (this.originStates.contains(transition.target())) { 60 | LOGGER.warn("Cannot add state transition to state {} from state alias that contains it already: {}", transition.target(), this.originStates); 61 | } 62 | this.outboundTransitions.add(transition); 63 | return this; 64 | } 65 | 66 | public StateAlias build() { 67 | return new StateAlias<>(this.originStates, this.outboundTransitions); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/config/LocomotionConfig.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.config; 2 | 3 | import com.google.gson.FieldNamingPolicy; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import com.trainguy9512.locomotion.LocomotionMain; 7 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.gui.screens.AlertScreen; 10 | import net.minecraft.client.gui.screens.Screen; 11 | import net.minecraft.network.chat.Component; 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | 15 | import java.io.BufferedWriter; 16 | import java.io.FileReader; 17 | import java.io.IOException; 18 | import java.lang.reflect.Modifier; 19 | import java.nio.file.Files; 20 | import java.nio.file.Path; 21 | import java.util.function.Function; 22 | import java.util.function.Predicate; 23 | 24 | public class LocomotionConfig { 25 | 26 | private static final Logger LOGGER = LogManager.getLogger("Locomotion/Config"); 27 | 28 | //private static final Path CONFIG_FILE_PATH = FabricLoader.getInstance().getConfigDir().resolve(LocomotionMain.MOD_ID + ".json"); 29 | private static final Path CONFIG_FILE_PATH = Path.of("config").resolve( LocomotionMain.MOD_ID + ".json"); 30 | 31 | private static final Gson GSON = new GsonBuilder() 32 | .setPrettyPrinting() 33 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 34 | .excludeFieldsWithModifiers(Modifier.PRIVATE) 35 | .create(); 36 | 37 | private Data configData; 38 | 39 | public LocomotionConfig() { 40 | this.configData = new Data(); 41 | } 42 | 43 | public Data data() { 44 | return configData; 45 | } 46 | 47 | public void load() { 48 | if (Files.exists(CONFIG_FILE_PATH)) { 49 | try (FileReader reader = new FileReader(CONFIG_FILE_PATH.toFile())) { 50 | this.configData = GSON.fromJson(reader, LocomotionConfig.Data.class); 51 | } catch (IOException e) { 52 | throw new RuntimeException("Failed to load locomotion config file", e); 53 | } 54 | } else { 55 | configData = new LocomotionConfig.Data(); 56 | } 57 | save(); 58 | 59 | } 60 | 61 | public void save() { 62 | try (BufferedWriter writer = Files.newBufferedWriter(CONFIG_FILE_PATH)) { 63 | writer.write(GSON.toJson(this.configData)); 64 | // LocomotionMain.LOGGER.info("Saved config to path {}", CONFIG_FILE_PATH); 65 | } catch (Exception e) { 66 | LOGGER.error("Failed to write config to path {}", CONFIG_FILE_PATH.toAbsolutePath()); 67 | } 68 | JointAnimatorDispatcher.getInstance().reInitializeData(); 69 | } 70 | 71 | 72 | public static class Data { 73 | 74 | public final FirstPersonPlayer firstPersonPlayer = new FirstPersonPlayer(); 75 | 76 | public static class FirstPersonPlayer { 77 | public boolean enableRenderer = true; 78 | public boolean enableCameraRotationDamping = true; 79 | public float cameraRotationStiffnessFactor = 0.3f; 80 | public float cameraRotationDampingFactor = 0.65f; 81 | public float miningAnimationSpeedMultiplier = 1f; 82 | } 83 | } 84 | 85 | public Function getConfigScreen(Predicate ifModLoaded) { 86 | 87 | if (ifModLoaded.test("yet_another_config_lib_v3")) { 88 | return LocomotionConfigScreen::createConfigScreen; 89 | } else { 90 | return parent -> new AlertScreen( 91 | () -> Minecraft.getInstance().setScreen(parent), 92 | Component.translatable("locomotion.config.yacl_not_found.header"), 93 | Component.translatable("locomotion.config.yacl_not_found.description"), 94 | Component.translatable("locomotion.config.yacl_not_found.close"), 95 | true 96 | ); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/debug/MixinDefaultPlayerSkin.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.debug; 2 | 3 | import net.minecraft.client.resources.DefaultPlayerSkin; 4 | import net.minecraft.client.resources.PlayerSkin; 5 | import org.spongepowered.asm.mixin.Final; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | 12 | import java.util.UUID; 13 | 14 | @Mixin(DefaultPlayerSkin.class) 15 | public abstract class MixinDefaultPlayerSkin { 16 | 17 | 18 | @Shadow @Final private static PlayerSkin[] DEFAULT_SKINS; 19 | 20 | @Inject( 21 | method = "get(Ljava/util/UUID;)Lnet/minecraft/client/resources/PlayerSkin;", 22 | at = @At("HEAD"), 23 | cancellable = true 24 | ) 25 | private static void makeSteveDefault(UUID uuid, CallbackInfoReturnable cir) { 26 | cir.setReturnValue(DEFAULT_SKINS[11]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/game/MixinMinecraft.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.game; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 5 | import com.trainguy9512.locomotion.animation.animator.entity.firstperson.FirstPersonDrivers; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.multiplayer.ClientLevel; 8 | import net.minecraft.client.multiplayer.MultiPlayerGameMode; 9 | import net.minecraft.client.player.LocalPlayer; 10 | import net.minecraft.world.InteractionHand; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 18 | 19 | import java.util.concurrent.CompletableFuture; 20 | 21 | @Mixin(Minecraft.class) 22 | public abstract class MixinMinecraft { 23 | 24 | 25 | @Shadow @Nullable public ClientLevel level; 26 | 27 | @Shadow private volatile boolean pause; 28 | 29 | @Shadow protected abstract boolean isLevelRunningNormally(); 30 | 31 | @Shadow public abstract CompletableFuture delayTextureReload(); 32 | 33 | @Shadow @Nullable public LocalPlayer player; 34 | 35 | @Shadow @Nullable public MultiPlayerGameMode gameMode; 36 | 37 | @Inject( 38 | method = "handleKeybinds", 39 | at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;swing(Lnet/minecraft/world/InteractionHand;)V") 40 | ) 41 | public void playItemDropAnimation(CallbackInfo ci) { 42 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> { 43 | dataContainer.getDriver(FirstPersonDrivers.HAS_DROPPED_ITEM).trigger(); 44 | }); 45 | } 46 | 47 | @Inject( 48 | method = "startAttack", 49 | at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/MultiPlayerGameMode;attack(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/entity/Entity;)V")) 50 | public void injectStartAttackHitEntity(CallbackInfoReturnable cir) { 51 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> { 52 | dataContainer.getDriver(FirstPersonDrivers.HAS_ATTACKED).trigger(); 53 | }); 54 | } 55 | 56 | @Inject( 57 | method = "startAttack", 58 | at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;resetAttackStrengthTicker()V")) 59 | public void injectStartAttackMiss(CallbackInfoReturnable cir) { 60 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> { 61 | dataContainer.getDriver(FirstPersonDrivers.HAS_ATTACKED).trigger(); 62 | }); 63 | } 64 | 65 | @Inject( 66 | method = "startUseItem", 67 | at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;swing(Lnet/minecraft/world/InteractionHand;)V") 68 | ) 69 | public void injectOnSwingPlayerHandWhenBeginningUse(CallbackInfo ci, @Local InteractionHand interactionHand) { 70 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> { 71 | switch (interactionHand) { 72 | case MAIN_HAND -> dataContainer.getDriver(FirstPersonDrivers.HAS_USED_MAIN_HAND_ITEM).trigger(); 73 | case OFF_HAND -> dataContainer.getDriver(FirstPersonDrivers.HAS_USED_OFF_HAND_ITEM).trigger(); 74 | } 75 | }); 76 | } 77 | 78 | @Inject( 79 | method = "tick", 80 | at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;animateTick(III)V") 81 | ) 82 | private void locomotionTick(CallbackInfo ci) { 83 | assert this.level != null; 84 | JointAnimatorDispatcher jointAnimatorDispatcher = JointAnimatorDispatcher.getInstance(); 85 | jointAnimatorDispatcher.tickEntityJointAnimators(this.level.entitiesForRendering()); 86 | jointAnimatorDispatcher.tickFirstPersonPlayerJointAnimator(); 87 | } 88 | 89 | /** 90 | * Play the block cracking particles only if the mining animation has entered its impact state. 91 | * Play the cracking particles as normal if the first person renderer config is disabled. 92 | */ 93 | // TODO: If this is going to be re-implemented, NeoForge needs an alternative implementation. 94 | // @Redirect( 95 | // method = "continueAttack", 96 | // at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleEngine;crack(Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;)V")) 97 | // public void onlySpawnBreakParticlesOnPickaxeImpact(ParticleEngine instance, BlockPos pos, Direction side) { 98 | // JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(driverContainer -> { 99 | // if (driverContainer.getDriverValue(FirstPersonPlayerJointAnimator.IS_MINING_IMPACTING) || !LocomotionMain.CONFIG.data().firstPersonPlayer.enableRenderer) { 100 | // for (float i = 0; i < 8; i++) { 101 | // instance.crack(pos, side); 102 | // } 103 | // } 104 | // }); 105 | // } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/game/MixinMultiPlayerGameMode.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.game; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 5 | import com.trainguy9512.locomotion.animation.animator.entity.firstperson.FirstPersonDrivers; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.multiplayer.MultiPlayerGameMode; 8 | import net.minecraft.core.BlockPos; 9 | import net.minecraft.core.Direction; 10 | import net.minecraft.network.protocol.Packet; 11 | import net.minecraft.world.InteractionHand; 12 | import net.minecraft.world.InteractionResult; 13 | import net.minecraft.world.entity.player.Player; 14 | import org.apache.commons.lang3.mutable.MutableObject; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Shadow; 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 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 22 | 23 | @Mixin(MultiPlayerGameMode.class) 24 | public class MixinMultiPlayerGameMode { 25 | 26 | @Shadow @Final private Minecraft minecraft; 27 | 28 | @Inject( 29 | method = "stopDestroyBlock", 30 | at = @At("HEAD") 31 | ) 32 | public void disableMiningAnimationOnNoLongerMining(CallbackInfo ci) { 33 | assert this.minecraft.player != null; 34 | if (!this.minecraft.player.getAbilities().instabuild) { 35 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> { 36 | // if (driverContainer.getDriver(FirstPersonPlayerJointAnimator.IS_MINING).getCurrentValue() && !driverContainer.getDriver(FirstPersonPlayerJointAnimator.IS_MINING).getPreviousValue()) { 37 | // driverContainer.getDriver(FirstPersonPlayerJointAnimator.HAS_ATTACKED).trigger(); 38 | // } 39 | if (dataContainer.getDriver(FirstPersonDrivers.IS_MINING).getPreviousValue()) { 40 | dataContainer.getDriver(FirstPersonDrivers.IS_MINING).setValue(false); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | @Inject( 47 | method = "method_41929(Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/entity/player/Player;Lorg/apache/commons/lang3/mutable/MutableObject;I)Lnet/minecraft/network/protocol/Packet;", 48 | at = @At("TAIL") 49 | ) 50 | public void triggerHasInteractedWithDriver(InteractionHand interactionHand, Player player, MutableObject mutableObject, int i, CallbackInfoReturnable cir) { 51 | if (mutableObject.getValue() instanceof InteractionResult.Success) { 52 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> { 53 | dataContainer.getDriver(FirstPersonDrivers.getHasInteractedWithDriver(interactionHand)).trigger(); 54 | }); 55 | } 56 | } 57 | 58 | @Inject( 59 | method = "startDestroyBlock", 60 | at = @At("HEAD") 61 | ) 62 | public void enableMiningAnimationOnBeginMining(BlockPos loc, Direction face, CallbackInfoReturnable cir) { 63 | assert this.minecraft.player != null; 64 | if (!this.minecraft.player.getAbilities().instabuild) { 65 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> dataContainer.getDriver(FirstPersonDrivers.IS_MINING).setValue(true)); 66 | } 67 | } 68 | 69 | @Inject( 70 | method = "continueDestroyBlock", 71 | at = @At("HEAD") 72 | ) 73 | public void enableMiningAnimationOnContinueMining(BlockPos loc, Direction face, CallbackInfoReturnable cir) { 74 | assert this.minecraft.player != null; 75 | if (!this.minecraft.player.getAbilities().instabuild) { 76 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> dataContainer.getDriver(FirstPersonDrivers.IS_MINING).setValue(true)); 77 | } 78 | } 79 | 80 | @Inject( 81 | method = "destroyBlock", 82 | at = @At("RETURN") 83 | ) 84 | public void destroyBlockInCreativeInstantly(BlockPos pos, CallbackInfoReturnable cir) { 85 | assert this.minecraft.player != null; 86 | if (cir.getReturnValue() && this.minecraft.player.getAbilities().instabuild) { 87 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> dataContainer.getDriver(FirstPersonDrivers.HAS_ATTACKED).trigger()); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/item/MixinBlocksAttacks.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.item; 2 | 3 | //? if >= 1.21.5 { 4 | 5 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 6 | import com.trainguy9512.locomotion.animation.animator.entity.firstperson.FirstPersonDrivers; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.server.level.ServerLevel; 9 | import net.minecraft.world.entity.LivingEntity; 10 | import net.minecraft.world.item.component.BlocksAttacks; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | @Mixin(BlocksAttacks.class) 17 | public class MixinBlocksAttacks { 18 | 19 | @Inject( 20 | method = "onBlocked", 21 | at = @At("HEAD") 22 | ) 23 | public void playShieldImpactMontageOnShieldBlocked(ServerLevel serverLevel, LivingEntity livingEntity, CallbackInfo ci) { 24 | if (Minecraft.getInstance().isLocalPlayer(livingEntity.getUUID())) { 25 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> dataContainer.getDriver(FirstPersonDrivers.HAS_BLOCKED_ATTACK).trigger()); 26 | } 27 | } 28 | } 29 | 30 | //?} else { 31 | 32 | /*import net.minecraft.world.item.ShieldItem; 33 | import org.spongepowered.asm.mixin.Mixin; 34 | 35 | @Mixin(ShieldItem.class) 36 | public class MixinBlocksAttacks { 37 | 38 | } 39 | 40 | *///?} -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/item/MixinItemInHandLayer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.item; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import net.minecraft.client.model.EntityModel; 7 | import net.minecraft.client.renderer.MultiBufferSource; 8 | import net.minecraft.client.renderer.entity.RenderLayerParent; 9 | import net.minecraft.client.renderer.entity.layers.ItemInHandLayer; 10 | import net.minecraft.client.renderer.entity.layers.RenderLayer; 11 | import net.minecraft.client.renderer.entity.state.ArmedEntityRenderState; 12 | import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; 13 | import net.minecraft.client.renderer.item.ItemStackRenderState; 14 | import net.minecraft.world.entity.HumanoidArm; 15 | import net.minecraft.world.entity.LivingEntity; 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 | @Environment(EnvType.CLIENT) 22 | @Mixin(ItemInHandLayer.class) 23 | public abstract class MixinItemInHandLayer> extends RenderLayer { 24 | 25 | public MixinItemInHandLayer(RenderLayerParent renderLayerParent) { 26 | super(renderLayerParent); 27 | } 28 | 29 | @Inject(method = "renderArmWithItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/item/ItemStackRenderState;render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;II)V")) 30 | private void transformItemInHandLayer(S armedEntityRenderState, ItemStackRenderState itemStackRenderState, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci){ 31 | if(shouldTransformItemInHand(armedEntityRenderState)){ 32 | 33 | 34 | //TODO: Redo how hand stuff works, add override functions to living entity animators. (2025 update: what does this mean?) 35 | //update: gosh darnit 1.20 broke something else with this mixin 36 | /* 37 | poseStack.popPose(); 38 | poseStack.pushPose(); 39 | ((ArmedModel)this.getParentModel()).translateToHand(humanoidArm, poseStack); 40 | poseStack.translate((humanoidArm == HumanoidArm.LEFT ? 1 : -1) /16F, 8/16F, 0); 41 | 42 | Enum<> locatorIdentifier = humanoidArm == HumanoidArm.LEFT ? PlayerPartAnimator.ModelPartLocators.leftHand : PlayerPartAnimator.ModelPartLocators.rightHand; 43 | //AnimatorDispatcher.INSTANCE.getBakedPose(livingEntity.getUUID()).getLocator(locatorIdentifier, Minecraft.getInstance().getFrameTime()).translateAndRotatePoseStack(poseStack); 44 | 45 | BakedAnimationPose bakedAnimationPose = AnimatorDispatcher.INSTANCE.getBakedPose(livingEntity.getUUID()); 46 | bakedAnimationPose.getBlendedPose(Minecraft.getInstance().getFrameTime()).getLocatorPose(locatorIdentifier).translateAndRotatePoseStack(poseStack); 47 | 48 | poseStack.mulPose(Axis.XP.rotationDegrees(-90)); 49 | poseStack.mulPose(Axis.YP.rotationDegrees(180)); 50 | 51 | //poseStack.mulPose(Vector3f.XP.rotationDegrees(Util.getMillis() / 10F)); 52 | poseStack.translate(0, 2/16F, -2/16F); 53 | 54 | */ 55 | } 56 | } 57 | private boolean shouldTransformItemInHand(LivingEntityRenderState livingEntityRenderState){ 58 | return false; 59 | /* 60 | BakedAnimationPose bakedPose = AnimatorDispatcher.INSTANCE.getBakedPose(livingEntity.getUUID()); 61 | if(bakedPose != null){ 62 | if(bakedPose.containsLocator("leftHand") && bakedPose.containsLocator("rightHand")){ 63 | return true; 64 | } 65 | } 66 | return false; 67 | 68 | */ 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/item/MixinItemTransform.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.item; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.math.Axis; 5 | import com.trainguy9512.locomotion.render.FirstPersonPlayerRenderer; 6 | import net.minecraft.client.renderer.block.model.ItemTransform; 7 | import net.minecraft.util.Mth; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(ItemTransform.class) 14 | public class MixinItemTransform { 15 | 16 | @Inject( 17 | method = "apply", 18 | at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack$Pose;scale(FFF)V") 19 | ) 20 | public void flipItemModel(boolean bl, PoseStack.Pose pose, CallbackInfo ci) { 21 | if (FirstPersonPlayerRenderer.SHOULD_FLIP_ITEM_TRANSFORM && FirstPersonPlayerRenderer.IS_RENDERING_LOCOMOTION_FIRST_PERSON) { 22 | pose.rotate(Axis.YP.rotation(Mth.PI)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/item/property/MixinCrossbowPull.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.item.property; 2 | 3 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 4 | import com.trainguy9512.locomotion.animation.animator.entity.firstperson.FirstPersonDrivers; 5 | import com.trainguy9512.locomotion.render.FirstPersonPlayerRenderer; 6 | import net.minecraft.client.multiplayer.ClientLevel; 7 | import net.minecraft.client.renderer.item.properties.numeric.CrossbowPull; 8 | import net.minecraft.world.InteractionHand; 9 | import net.minecraft.world.entity.LivingEntity; 10 | import net.minecraft.world.item.ItemStack; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 15 | 16 | @Mixin(CrossbowPull.class) 17 | public class MixinCrossbowPull { 18 | 19 | /** 20 | * Modifies the "Crossbow Pull" item model property to sync up with Locomotion's first person animations rather than how it's calculated in vanilla. 21 | */ 22 | @Inject( 23 | method = "get", 24 | at = @At("HEAD"), 25 | cancellable = true 26 | ) 27 | public void injectLocomotionCrossbowPull(ItemStack stack, ClientLevel level, LivingEntity entity, int seed, CallbackInfoReturnable cir) { 28 | if (FirstPersonPlayerRenderer.IS_RENDERING_LOCOMOTION_FIRST_PERSON) { 29 | JointAnimatorDispatcher.getInstance().getInterpolatedFirstPersonPlayerPose().ifPresent(pose -> { 30 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(driverContainer -> { 31 | InteractionHand interactionHand = FirstPersonPlayerRenderer.CURRENT_ITEM_INTERACTION_HAND; 32 | InteractionHand currentUsingInteractionHand = driverContainer.getDriver(FirstPersonDrivers.LAST_USED_HAND).getCurrentValue(); 33 | cir.setReturnValue(interactionHand == currentUsingInteractionHand ? pose.getCustomAttributeValue("crossbow_pull_property") : 0f); 34 | }); 35 | }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/item/property/MixinIsUsingItem.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.item.property; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 5 | import com.trainguy9512.locomotion.animation.animator.entity.firstperson.FirstPersonDrivers; 6 | import com.trainguy9512.locomotion.render.FirstPersonPlayerRenderer; 7 | import net.minecraft.client.multiplayer.ClientLevel; 8 | import net.minecraft.client.renderer.item.properties.conditional.IsUsingItem; 9 | import net.minecraft.world.InteractionHand; 10 | import net.minecraft.world.entity.LivingEntity; 11 | import net.minecraft.world.item.ItemDisplayContext; 12 | import net.minecraft.world.item.ItemStack; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | 18 | @Mixin(IsUsingItem.class) 19 | public class MixinIsUsingItem { 20 | 21 | @Inject( 22 | method = "get", 23 | at = @At("HEAD"), 24 | cancellable = true 25 | ) 26 | public void injectLocomotionIsUsingItem(ItemStack itemStack, ClientLevel clientLevel, LivingEntity livingEntity, int i, ItemDisplayContext itemDisplayContext, CallbackInfoReturnable cir) { 27 | if (FirstPersonPlayerRenderer.IS_RENDERING_LOCOMOTION_FIRST_PERSON) { 28 | JointAnimatorDispatcher.getInstance().getInterpolatedFirstPersonPlayerPose().ifPresent(pose -> { 29 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(driverContainer -> { 30 | InteractionHand interactionHand = FirstPersonPlayerRenderer.CURRENT_ITEM_INTERACTION_HAND; 31 | InteractionHand currentUsingInteractionHand = driverContainer.getDriver(FirstPersonDrivers.LAST_USED_HAND).getCurrentValue(); 32 | cir.setReturnValue(interactionHand == currentUsingInteractionHand && pose.getCustomAttributeValueAsBoolean("is_using_property")); 33 | }); 34 | }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/item/property/MixinUseDuration.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.item.property; 2 | 3 | import com.trainguy9512.locomotion.LocomotionMain; 4 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 5 | import com.trainguy9512.locomotion.animation.animator.entity.firstperson.FirstPersonDrivers; 6 | import com.trainguy9512.locomotion.render.FirstPersonPlayerRenderer; 7 | import net.minecraft.client.multiplayer.ClientLevel; 8 | import net.minecraft.client.renderer.item.properties.numeric.UseDuration; 9 | import net.minecraft.world.InteractionHand; 10 | import net.minecraft.world.entity.LivingEntity; 11 | import net.minecraft.world.item.ItemStack; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 16 | 17 | @Mixin(UseDuration.class) 18 | public class MixinUseDuration { 19 | @Inject( 20 | method = "get", 21 | at = @At("RETURN"), 22 | cancellable = true 23 | ) 24 | public void injectLocomotionUseDuration(ItemStack stack, ClientLevel level, LivingEntity entity, int seed, CallbackInfoReturnable cir) { 25 | if (FirstPersonPlayerRenderer.IS_RENDERING_LOCOMOTION_FIRST_PERSON) { 26 | JointAnimatorDispatcher.getInstance().getInterpolatedFirstPersonPlayerPose().ifPresent(pose -> { 27 | JointAnimatorDispatcher.getInstance().getFirstPersonPlayerDataContainer().ifPresent(driverContainer -> { 28 | InteractionHand interactionHand = FirstPersonPlayerRenderer.CURRENT_ITEM_INTERACTION_HAND; 29 | InteractionHand currentUsingInteractionHand = driverContainer.getDriver(FirstPersonDrivers.LAST_USED_HAND).getCurrentValue(); 30 | cir.setReturnValue(interactionHand == currentUsingInteractionHand ? pose.getCustomAttributeValue("use_duration_property") : 0f); 31 | }); 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/MixinBlockRenderDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.blaze3d.vertex.VertexConsumer; 5 | import com.trainguy9512.locomotion.access.AlternateSingleBlockRenderer; 6 | import net.minecraft.client.color.block.BlockColors; 7 | import net.minecraft.client.renderer.ItemBlockRenderTypes; 8 | import net.minecraft.client.renderer.LightTexture; 9 | import net.minecraft.client.renderer.MultiBufferSource; 10 | import net.minecraft.client.renderer.SpecialBlockModelRenderer; 11 | import net.minecraft.client.renderer.block.BlockRenderDispatcher; 12 | import net.minecraft.client.renderer.block.model.BakedQuad; 13 | import net.minecraft.client.renderer.block.model.BlockModelPart; 14 | import net.minecraft.client.renderer.block.model.BlockStateModel; 15 | import net.minecraft.client.renderer.texture.OverlayTexture; 16 | import net.minecraft.core.Direction; 17 | import net.minecraft.util.Mth; 18 | import net.minecraft.util.RandomSource; 19 | import net.minecraft.world.item.ItemDisplayContext; 20 | import net.minecraft.world.level.block.RenderShape; 21 | import net.minecraft.world.level.block.state.BlockState; 22 | import org.spongepowered.asm.mixin.Final; 23 | import org.spongepowered.asm.mixin.Mixin; 24 | import org.spongepowered.asm.mixin.Shadow; 25 | import org.spongepowered.asm.mixin.Unique; 26 | 27 | import java.util.function.Supplier; 28 | 29 | 30 | @Mixin(BlockRenderDispatcher.class) 31 | public abstract class MixinBlockRenderDispatcher implements AlternateSingleBlockRenderer { 32 | 33 | @Shadow public abstract BlockStateModel getBlockModel(BlockState arg); 34 | 35 | @Shadow @Final private BlockColors blockColors; 36 | 37 | @Shadow @Final private Supplier specialBlockModelRenderer; 38 | 39 | @Unique 40 | public void locomotion$renderSingleBlockWithEmission(BlockState blockState, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight) { 41 | RenderShape renderShape = blockState.getRenderShape(); 42 | // Don't do anything more if the render shape is nothing. 43 | if (renderShape == RenderShape.INVISIBLE) { 44 | return; 45 | } 46 | // Set the combined light integer to use the block's light emission if it is brighter than the current light level. 47 | combinedLight = LightTexture.lightCoordsWithEmission(combinedLight, blockState.getLightEmission()); 48 | BlockStateModel blockStateModel = this.getBlockModel(blockState); 49 | int tint = this.blockColors.getColor(blockState, null, null, 0); 50 | float r = (float)(tint >> 16 & 0xFF) / 255.0f; 51 | float g = (float)(tint >> 8 & 0xFF) / 255.0f; 52 | float b = (float)(tint & 0xFF) / 255.0f; 53 | // Render each part of the block state model 54 | for (BlockModelPart blockModelPart : blockStateModel.collectParts(RandomSource.create(42L))) { 55 | for (Direction direction : Direction.values()) { 56 | for (BakedQuad bakedQuad : blockModelPart.getQuads(direction)) { 57 | this.locomotion$renderBakedQuad(bakedQuad, poseStack, bufferSource, r, g, b, combinedLight, blockState); 58 | } 59 | } 60 | for (BakedQuad bakedQuad : blockModelPart.getQuads(null)) { 61 | this.locomotion$renderBakedQuad(bakedQuad, poseStack, bufferSource, r, g, b, combinedLight, blockState); 62 | } 63 | } 64 | // Render the block through the special block renderer if it has one (skulls, beds, banners) 65 | this.specialBlockModelRenderer.get().renderByBlock(blockState.getBlock(), ItemDisplayContext.NONE, poseStack, bufferSource, combinedLight, OverlayTexture.NO_OVERLAY); 66 | } 67 | 68 | @Unique 69 | private void locomotion$renderBakedQuad(BakedQuad bakedQuad, PoseStack poseStack, MultiBufferSource bufferSource, float r, float g, float b, int combinedLight, BlockState blockState) { 70 | if (bakedQuad.isTinted()) { 71 | r = Mth.clamp(r, 0.0f, 1.0f); 72 | g = Mth.clamp(g, 0.0f, 1.0f); 73 | b = Mth.clamp(b, 0.0f, 1.0f); 74 | } else { 75 | r = 1.0f; 76 | g = 1.0f; 77 | b = 1.0f; 78 | } 79 | // Use the chunk render type vertex consumer for baked quad parts that don't get shaded. 80 | VertexConsumer vertexConsumer; 81 | if (bakedQuad.shade()) { 82 | vertexConsumer = bufferSource.getBuffer(ItemBlockRenderTypes.getRenderType(blockState)); 83 | } else { 84 | vertexConsumer = bufferSource.getBuffer(ItemBlockRenderTypes.getChunkRenderType(blockState)); 85 | } 86 | 87 | vertexConsumer.putBulkData( 88 | poseStack.last(), 89 | bakedQuad, 90 | new float[]{1, 1, 1, 1}, 91 | r, 92 | g, 93 | b, 94 | 1.0f, 95 | new int[]{combinedLight, combinedLight, combinedLight, combinedLight}, 96 | OverlayTexture.NO_OVERLAY, 97 | true 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/MixinEntityRenderDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import com.trainguy9512.locomotion.access.FirstPersonPlayerRendererGetter; 5 | import com.trainguy9512.locomotion.render.FirstPersonPlayerRenderer; 6 | import net.minecraft.client.renderer.entity.EntityRenderDispatcher; 7 | import net.minecraft.client.renderer.entity.EntityRendererProvider; 8 | import net.minecraft.server.packs.resources.ResourceManager; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Unique; 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 | import java.util.Optional; 16 | 17 | @Mixin(EntityRenderDispatcher.class) 18 | public class MixinEntityRenderDispatcher implements FirstPersonPlayerRendererGetter { 19 | 20 | @Unique 21 | private FirstPersonPlayerRenderer locomotion$firstPersonPlayerRenderer; 22 | 23 | @Inject( 24 | method = "onResourceManagerReload", 25 | at = @At("TAIL") 26 | ) 27 | private void constructLocomotionFirstPersonPlayerRenderer(ResourceManager resourceManager, CallbackInfo ci, @Local EntityRendererProvider.Context context){ 28 | this.locomotion$firstPersonPlayerRenderer = new FirstPersonPlayerRenderer(context); 29 | } 30 | 31 | @Override 32 | public Optional locomotion$getFirstPersonPlayerRenderer() { 33 | return Optional.ofNullable(this.locomotion$firstPersonPlayerRenderer); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/MixinGameRenderer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.trainguy9512.locomotion.LocomotionMain; 5 | import com.trainguy9512.locomotion.access.FirstPersonPlayerRendererGetter; 6 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorDispatcher; 7 | import com.trainguy9512.locomotion.animation.animator.JointAnimatorRegistry; 8 | import net.minecraft.client.DeltaTracker; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.renderer.GameRenderer; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(GameRenderer.class) 19 | public abstract class MixinGameRenderer { 20 | 21 | @Shadow @Final private Minecraft minecraft; 22 | 23 | /** 24 | * Computes and saves the interpolated animation pose prior to rendering. 25 | */ 26 | @Inject( 27 | method = "renderLevel", 28 | at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setup(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/world/entity/Entity;ZZF)V") 29 | ) 30 | private void computePosePriorToRendering(DeltaTracker deltaTracker, CallbackInfo ci){ 31 | if (LocomotionMain.CONFIG.data().firstPersonPlayer.enableRenderer) { 32 | JointAnimatorDispatcher jointAnimatorDispatcher = JointAnimatorDispatcher.getInstance(); 33 | jointAnimatorDispatcher.getFirstPersonPlayerDataContainer().ifPresent(dataContainer -> 34 | JointAnimatorRegistry.getFirstPersonPlayerJointAnimator().ifPresent( 35 | jointAnimator -> jointAnimatorDispatcher.calculateInterpolatedFirstPersonPlayerPose(jointAnimator, dataContainer, deltaTracker.getGameTimeDeltaPartialTick(true)) 36 | 37 | )); 38 | } 39 | } 40 | 41 | /** 42 | * Transform the camera pose stack based on the first person player's camera joint, prior to bobHurt and bobView. 43 | */ 44 | @Inject( 45 | method = "bobHurt", 46 | at = @At("HEAD") 47 | ) 48 | private void addCameraRotation(PoseStack poseStack, float partialTicks, CallbackInfo ci){ 49 | if (LocomotionMain.CONFIG.data().firstPersonPlayer.enableRenderer) { 50 | ((FirstPersonPlayerRendererGetter)this.minecraft.getEntityRenderDispatcher()).locomotion$getFirstPersonPlayerRenderer().ifPresent(firstPersonPlayerRenderer -> firstPersonPlayerRenderer.transformCamera(poseStack)); 51 | } 52 | 53 | } 54 | 55 | /** 56 | * Remove the view bobbing animation, as the animation pose provides its own. 57 | * When config is added to enable/disable custom first person rendering, this should be revisited! 58 | */ 59 | @Inject( 60 | method = "bobView", 61 | at = @At(value = "HEAD"), 62 | cancellable = true 63 | ) 64 | private void removeViewBobbing(PoseStack poseStack, float partialTicks, CallbackInfo ci){ 65 | if (LocomotionMain.CONFIG.data().firstPersonPlayer.enableRenderer) { 66 | ci.cancel(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/MixinItemInHandRenderer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.trainguy9512.locomotion.LocomotionMain; 5 | import com.trainguy9512.locomotion.access.FirstPersonPlayerRendererGetter; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.player.LocalPlayer; 8 | import net.minecraft.client.renderer.ItemInHandRenderer; 9 | import net.minecraft.client.renderer.MultiBufferSource; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | @Mixin(ItemInHandRenderer.class) 18 | public class MixinItemInHandRenderer { 19 | 20 | @Shadow @Final private Minecraft minecraft; 21 | 22 | @Inject( 23 | method = "renderHandsWithItems", 24 | at = @At("HEAD"), 25 | cancellable = true 26 | ) 27 | public void overrideFirstPersonRendering( 28 | float partialTicks, 29 | PoseStack poseStack, 30 | MultiBufferSource.BufferSource buffer, 31 | LocalPlayer playerEntity, 32 | int combinedLight, 33 | CallbackInfo ci 34 | ) { 35 | if (LocomotionMain.CONFIG.data().firstPersonPlayer.enableRenderer) { 36 | ((FirstPersonPlayerRendererGetter) this.minecraft.getEntityRenderDispatcher()).locomotion$getFirstPersonPlayerRenderer().ifPresent(firstPersonPlayerRenderer -> firstPersonPlayerRenderer.render(partialTicks, poseStack, buffer, playerEntity, combinedLight)); 37 | ci.cancel(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/MixinLivingEntityRenderState.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render; 2 | 3 | import com.trainguy9512.locomotion.access.LivingEntityRenderStateAccess; 4 | import com.trainguy9512.locomotion.animation.animator.entity.EntityJointAnimator; 5 | import com.trainguy9512.locomotion.animation.pose.Pose; 6 | import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | 10 | import java.util.Optional; 11 | 12 | @Mixin(LivingEntityRenderState.class) 13 | public class MixinLivingEntityRenderState implements LivingEntityRenderStateAccess { 14 | 15 | @Unique 16 | private Pose interpolatedPose; 17 | 18 | @Unique 19 | private EntityJointAnimator entityJointAnimator; 20 | 21 | @Unique 22 | @Override 23 | public void animationOverhaul$setInterpolatedAnimationPose(Pose interpolatedPose) { 24 | this.interpolatedPose = interpolatedPose; 25 | } 26 | 27 | @Override 28 | public Optional animationOverhaul$getInterpolatedAnimationPose() { 29 | return Optional.ofNullable(this.interpolatedPose); 30 | } 31 | 32 | @Override 33 | public void animationOverhaul$setEntityJointAnimator(EntityJointAnimator entityJointAnimator) { 34 | this.entityJointAnimator = entityJointAnimator; 35 | } 36 | 37 | @Override 38 | public Optional> animationOverhaul$getEntityJointAnimator() { 39 | return Optional.ofNullable(this.entityJointAnimator); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/MixinModelPart.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.trainguy9512.locomotion.access.MatrixModelPart; 5 | import net.minecraft.client.model.geom.ModelPart; 6 | import org.joml.Matrix4f; 7 | import org.joml.Vector3f; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(ModelPart.class) 15 | public class MixinModelPart implements MatrixModelPart { 16 | 17 | @Unique 18 | Matrix4f locomotion$matrix4f = null; 19 | 20 | @Unique 21 | @Override 22 | public void locomotion$setMatrix(Matrix4f matrix4f) { 23 | this.locomotion$matrix4f = matrix4f; 24 | } 25 | 26 | @Unique 27 | @Override 28 | public Matrix4f locomotion$getMatrix() { 29 | return this.locomotion$matrix4f; 30 | } 31 | 32 | @Inject(method = "resetPose", at = @At("HEAD")) 33 | public void resetMatrix(CallbackInfo ci){ 34 | this.locomotion$matrix4f = null; 35 | } 36 | 37 | @Inject(method = "translateAndRotate", at = @At("HEAD"), cancellable = true) 38 | public void multiplyPoseStackWithMatrix(PoseStack poseStack, CallbackInfo ci){ 39 | if(this.locomotion$matrix4f != null){ 40 | poseStack.mulPose(this.locomotion$matrix4f.setTranslation(this.locomotion$matrix4f.getTranslation(new Vector3f()).div(16f))); 41 | ci.cancel(); 42 | } 43 | } 44 | 45 | @Inject(method = "copyFrom", at = @At("HEAD")) 46 | public void copyMatrixFrom(ModelPart modelPart, CallbackInfo ci){ 47 | this.locomotion$matrix4f = ((MatrixModelPart)(Object)modelPart).locomotion$getMatrix(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/layer/MixinCapeLayer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render.layer; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.client.renderer.entity.layers.CapeLayer; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Redirect; 8 | 9 | @Mixin(CapeLayer.class) 10 | public class MixinCapeLayer { 11 | 12 | @Redirect(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/renderer/entity/state/PlayerRenderState;FF)V", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;translate(FFF)V")) 13 | private void removeCapeTranslate(PoseStack instance, float f, float g, float h){ 14 | instance.translate(0, 0, 0); 15 | } 16 | 17 | 18 | /* 19 | @Redirect(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/renderer/MultiBufferSource;getBuffer(Lnet/minecraft/client/renderer/RenderType;)Lcom/mojang/blaze3d/vertex/VertexConsumer;")) 20 | private VertexConsumer setCapeDebugTexture(MultiBufferSource instance, RenderType renderType){ 21 | return instance.getBuffer(RenderType.entitySolid(debugCapeLocation)); 22 | } 23 | 24 | */ 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/mixin/render/layer/MixinElytraLayer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.mixin.render.layer; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.trainguy9512.locomotion.access.LivingEntityRenderStateAccess; 5 | import net.minecraft.client.model.EntityModel; 6 | import net.minecraft.client.model.HumanoidModel; 7 | import net.minecraft.client.model.geom.ModelPart; 8 | import net.minecraft.client.renderer.MultiBufferSource; 9 | import net.minecraft.client.renderer.entity.RenderLayerParent; 10 | import net.minecraft.client.renderer.entity.layers.RenderLayer; 11 | import net.minecraft.client.renderer.entity.layers.WingsLayer; 12 | import net.minecraft.client.renderer.entity.state.HumanoidRenderState; 13 | import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; 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 | @Mixin(WingsLayer.class) 21 | public abstract class MixinElytraLayer> extends RenderLayer { 22 | public MixinElytraLayer(RenderLayerParent renderLayerParent) { 23 | super(renderLayerParent); 24 | } 25 | 26 | @Inject(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/renderer/entity/state/HumanoidRenderState;FF)V", at = @At("HEAD")) 27 | private void transformElytra(PoseStack poseStack, MultiBufferSource multiBufferSource, int i, S humanoidRenderState, float f, float g, CallbackInfo ci){ 28 | if(this.getParentModel() instanceof HumanoidModel && isValidForElytraTransformation(humanoidRenderState)){ 29 | poseStack.pushPose(); 30 | ModelPart body = ((HumanoidModel) this.getParentModel()).body; 31 | body.translateAndRotate(poseStack); 32 | } 33 | } 34 | 35 | @Inject(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/renderer/entity/state/HumanoidRenderState;FF)V", at = @At("RETURN")) 36 | private void transformElytraFinalized(PoseStack poseStack, MultiBufferSource multiBufferSource, int i, S humanoidRenderState, float f, float g, CallbackInfo ci){ 37 | if(this.getParentModel() instanceof HumanoidModel && isValidForElytraTransformation(humanoidRenderState)){ 38 | poseStack.popPose(); 39 | } 40 | } 41 | 42 | private boolean isValidForElytraTransformation(LivingEntityRenderState livingEntityRenderState){ 43 | return ((LivingEntityRenderStateAccess)livingEntityRenderState).animationOverhaul$getInterpolatedAnimationPose() != null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/resource/FormatVersion.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.resource; 2 | 3 | import com.google.gson.JsonDeserializer; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParseException; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | public record FormatVersion(int version) { 10 | 11 | private static final Logger LOGGER = LogManager.getLogger("Locomotion/Resources/FormatVersion"); 12 | private static final String FORMAT_VERSION_KEY = "format_version"; 13 | 14 | public static FormatVersion of(int version) { 15 | return new FormatVersion(version); 16 | } 17 | 18 | public static FormatVersion ofDefault() { 19 | return FormatVersion.of(1); 20 | } 21 | 22 | public boolean isIncompatible() { 23 | return this.version < 5; 24 | } 25 | 26 | public static JsonDeserializer getDeserializer() { 27 | return (jsonElement, type, context) -> FormatVersion.of(jsonElement.getAsInt()); 28 | } 29 | 30 | public static FormatVersion ofAssetJsonObject(JsonObject assetJson) { 31 | if (assetJson.has(FORMAT_VERSION_KEY)) { 32 | return FormatVersion.of(assetJson.get(FORMAT_VERSION_KEY).getAsInt()); 33 | } else { 34 | throw new JsonParseException("Asset does not contain valid format version field in JSON data."); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/resource/json/GsonConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.resource.json; 2 | 3 | import com.google.gson.*; 4 | import com.trainguy9512.locomotion.animation.joint.JointChannel; 5 | import com.trainguy9512.locomotion.animation.joint.skeleton.JointSkeleton; 6 | import com.trainguy9512.locomotion.animation.sequence.AnimationSequence; 7 | import com.trainguy9512.locomotion.resource.FormatVersion; 8 | import net.minecraft.client.model.geom.PartPose; 9 | import net.minecraft.util.Mth; 10 | import org.joml.Quaternionf; 11 | import org.joml.Vector3f; 12 | 13 | import java.lang.reflect.Type; 14 | 15 | public class GsonConfiguration { 16 | 17 | private static Gson GSON = createInternal(); 18 | 19 | private static Gson createInternal() { 20 | return new GsonBuilder() 21 | .setStrictness(Strictness.STRICT) 22 | .registerTypeAdapter(Vector3f.class, vector3fDeserializer()) 23 | .registerTypeAdapter(Quaternionf.class, quaternionDeserializer()) 24 | .registerTypeAdapter(AnimationSequence.class, new AnimationSequenceDeserializer()) 25 | .registerTypeAdapter(JointSkeleton.class, new JointSkeletonDeserializer()) 26 | .registerTypeAdapter(FormatVersion.class, FormatVersion.getDeserializer()) 27 | .registerTypeAdapter(JointChannel.class, new JointChannelDeserializer()) 28 | .registerTypeAdapter(PartPose.class, new PartPoseDeserializer()) 29 | .create(); 30 | } 31 | 32 | public static Gson getInstance() { 33 | return GSON; 34 | } 35 | 36 | public static D deserializeWithFallback(JsonDeserializationContext context, JsonObject json, String key, Class type, D fallback) { 37 | if (!json.has(key)) { 38 | return fallback; 39 | } 40 | if (json.get(key).isJsonNull()) { 41 | return null; 42 | } 43 | return context.deserialize(json.get(key), type); 44 | } 45 | 46 | private static JsonDeserializer vector3fDeserializer() { 47 | return (jsonElement, type, context) -> { 48 | JsonArray components = jsonElement.getAsJsonArray(); 49 | return new Vector3f( 50 | components.get(0).getAsFloat(), 51 | components.get(1).getAsFloat(), 52 | components.get(2).getAsFloat() 53 | ); 54 | }; 55 | } 56 | 57 | private static JsonDeserializer quaternionDeserializer() { 58 | return (jsonElement, type, context) -> { 59 | JsonArray components = jsonElement.getAsJsonArray(); 60 | return new Quaternionf().rotationZYX( 61 | components.get(2).getAsFloat() * Mth.DEG_TO_RAD, 62 | components.get(1).getAsFloat() * Mth.DEG_TO_RAD, 63 | components.get(0).getAsFloat() * Mth.DEG_TO_RAD 64 | ); 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/resource/json/JointChannelDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.resource.json; 2 | 3 | import com.google.gson.*; 4 | import com.mojang.math.Axis; 5 | import com.trainguy9512.locomotion.animation.joint.JointChannel; 6 | import net.minecraft.util.GsonHelper; 7 | import org.joml.Quaternionf; 8 | import org.joml.Vector3f; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | public class JointChannelDeserializer implements JsonDeserializer { 13 | 14 | private static final String TRANSLATION_KEY = "translation"; 15 | private static final String ROTATION_KEY = "rotation"; 16 | private static final String SCALE_KEY = "scale"; 17 | private static final String VISIBILITY_KEY = "visibility"; 18 | 19 | private static final Vector3f DEFAULT_TRANSLATION = new Vector3f(0, 0, 0); 20 | private static final Quaternionf DEFAULT_ROTATION = Axis.XP.rotation(0); 21 | private static final Vector3f DEFAULT_SCALE = new Vector3f(1, 1, 1); 22 | private static final boolean DEFAULT_VISIBILITY = true; 23 | 24 | @Override 25 | public JointChannel deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { 26 | JsonObject jointChannelJson = json.getAsJsonObject(); 27 | return JointChannel.ofTranslationRotationScaleQuaternion( 28 | GsonConfiguration.deserializeWithFallback( 29 | context, 30 | jointChannelJson, 31 | TRANSLATION_KEY, 32 | Vector3f.class, 33 | DEFAULT_TRANSLATION 34 | ), 35 | GsonConfiguration.deserializeWithFallback( 36 | context, 37 | jointChannelJson, 38 | ROTATION_KEY, 39 | Quaternionf.class, 40 | DEFAULT_ROTATION 41 | ), 42 | GsonConfiguration.deserializeWithFallback( 43 | context, 44 | jointChannelJson, 45 | SCALE_KEY, 46 | Vector3f.class, 47 | DEFAULT_SCALE 48 | ), 49 | Boolean.TRUE.equals(GsonConfiguration.deserializeWithFallback( 50 | context, 51 | jointChannelJson, 52 | VISIBILITY_KEY, 53 | Boolean.class, 54 | DEFAULT_VISIBILITY 55 | )) 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/resource/json/PartPoseDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.resource.json; 2 | 3 | import com.google.gson.*; 4 | import com.mojang.math.Axis; 5 | import net.minecraft.client.model.geom.PartPose; 6 | import org.joml.Quaternionf; 7 | import org.joml.Vector3f; 8 | 9 | import java.lang.reflect.Type; 10 | 11 | public class PartPoseDeserializer implements JsonDeserializer { 12 | 13 | private static final String TRANSLATION_KEY = "translation"; 14 | private static final String ROTATION_KEY = "rotation"; 15 | private static final String SCALE_KEY = "scale"; 16 | 17 | private static final Vector3f DEFAULT_TRANSLATION = new Vector3f(0, 0, 0); 18 | private static final Vector3f DEFAULT_ROTATION = new Vector3f(0, 0, 0); 19 | private static final Vector3f DEFAULT_SCALE = new Vector3f(1, 1, 1); 20 | 21 | @Override 22 | public PartPose deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { 23 | JsonObject partPoseJson = json.getAsJsonObject(); 24 | Vector3f translation = GsonConfiguration.deserializeWithFallback( 25 | context, 26 | partPoseJson, 27 | TRANSLATION_KEY, 28 | Vector3f.class, 29 | DEFAULT_TRANSLATION 30 | ); 31 | Vector3f rotation = GsonConfiguration.deserializeWithFallback( 32 | context, 33 | partPoseJson, 34 | ROTATION_KEY, 35 | Vector3f.class, 36 | DEFAULT_ROTATION 37 | ); 38 | Vector3f scale = GsonConfiguration.deserializeWithFallback( 39 | context, 40 | partPoseJson, 41 | SCALE_KEY, 42 | Vector3f.class, 43 | DEFAULT_SCALE 44 | ); 45 | return new PartPose( 46 | translation.x(), 47 | translation.y(), 48 | translation.z(), 49 | rotation.x(), 50 | rotation.y(), 51 | rotation.z(), 52 | scale.x(), 53 | scale.y(), 54 | scale.z() 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/util/Interpolator.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.util; 2 | 3 | import com.trainguy9512.locomotion.animation.pose.LocalSpacePose; 4 | import org.joml.Quaternionf; 5 | import org.joml.Vector3f; 6 | 7 | /** 8 | * @author Marvin Schürz 9 | */ 10 | @FunctionalInterface 11 | public interface Interpolator { 12 | T interpolate(T a, T b, float time); 13 | 14 | static Interpolator constantKeyframe(){ 15 | return (a, b, time) -> b; 16 | } 17 | 18 | static Interpolator constantBlend(){ 19 | return (a, b, time) -> b; 20 | } 21 | 22 | Interpolator FLOAT = (a, b, time) -> a + (b - a) * time; 23 | Interpolator BOOLEAN_KEYFRAME = Interpolator.constantKeyframe(); 24 | Interpolator BOOLEAN_BLEND = Interpolator.constantBlend(); 25 | Interpolator LOCAL_SPACE_POSE = (a, b, time) -> a.interpolated(b, time, LocalSpacePose.of(a)); 26 | 27 | Interpolator VECTOR_FLOAT = (a, b, time) -> { 28 | if (time == 0) { 29 | return new Vector3f(a); 30 | } 31 | if (time == 1) { 32 | return new Vector3f(b); 33 | } 34 | if (a.equals(b)) { 35 | return new Vector3f(a); 36 | } 37 | return a.lerp(b, time, new Vector3f()); 38 | }; 39 | 40 | Interpolator QUATERNION = (a, b, time) -> { 41 | if (time == 0) { 42 | return new Quaternionf(a); 43 | } 44 | if (time == 1) { 45 | return new Quaternionf(b); 46 | } 47 | if (a.equals(b)) { 48 | return new Quaternionf(a); 49 | } 50 | return a.slerp(b, time, new Quaternionf()); 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/util/TimeSpan.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * Utility class for representing time in different formats such as ticks, seconds, or various frames of different frame rates. 7 | * @author James Pelter 8 | */ 9 | public class TimeSpan implements Comparable { 10 | 11 | public static final TimeSpan ZERO = TimeSpan.ofTicks(0); 12 | 13 | private final float timeInTicks; 14 | 15 | private TimeSpan(float timeInTicks){ 16 | this.timeInTicks = timeInTicks; 17 | } 18 | 19 | /** 20 | * Creates a timespan from a time measured in ticks. 21 | *

22 | * Conversion is 20 ticks to 1 second. 23 | * @return Timespan 24 | */ 25 | public static TimeSpan ofTicks(float timeInTicks){ 26 | return new TimeSpan(timeInTicks); 27 | } 28 | 29 | /** 30 | * Creates a timespan from a time measured in seconds. 31 | * @return Timespan 32 | */ 33 | public static TimeSpan ofSeconds(float timeInSeconds){ 34 | return new TimeSpan(timeInSeconds * 20f); 35 | } 36 | 37 | /** 38 | * Creates a timespan from a time measured in frames, at 60 frames per second. 39 | * @return Timespan 40 | */ 41 | public static TimeSpan of60FramesPerSecond(float timeIn60FramesPerSecond){ 42 | return TimeSpan.ofSeconds(timeIn60FramesPerSecond / 60f); 43 | } 44 | 45 | /** 46 | * Creates a timespan from a time measured in frames, at 30 frames per second. 47 | * @return Timespan 48 | */ 49 | public static TimeSpan of30FramesPerSecond(float timeIn30FramesPerSecond){ 50 | return TimeSpan.ofSeconds(timeIn30FramesPerSecond / 30f); 51 | } 52 | 53 | /** 54 | * Creates a timespan from a time measured in frames, at 24 frames per second. 55 | * @return Timespan 56 | */ 57 | public static TimeSpan of24FramesPerSecond(float timeIn24FramesPerSecond){ 58 | return TimeSpan.ofSeconds(timeIn24FramesPerSecond / 24f); 59 | } 60 | 61 | /** 62 | * Retrieves the value of this timespan in ticks. 63 | * @return Float time measured in ticks. 64 | */ 65 | public float inTicks(){ 66 | return this.timeInTicks; 67 | } 68 | 69 | /** 70 | * Retrieves the value of this timespan in seconds. 71 | * @return Float time measured in seconds. 72 | */ 73 | public float inSeconds(){ 74 | return this.timeInTicks / 20f; 75 | } 76 | 77 | @Override 78 | public int compareTo(@NotNull TimeSpan timeSpan) { 79 | if (this.timeInTicks == timeSpan.timeInTicks){ 80 | return 0; 81 | } else { 82 | return this.timeInTicks > timeSpan.timeInTicks ? 1 : -1; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/util/Timeline.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.util; 2 | 3 | import com.trainguy9512.locomotion.animation.joint.JointChannel; 4 | 5 | import java.util.TreeMap; 6 | 7 | /** 8 | * @author Marvin Schürz 9 | */ 10 | public class Timeline { 11 | 12 | private final TreeMap> keyframes; 13 | private final Interpolator interpolator; 14 | private final float length; 15 | 16 | private Timeline(Interpolator interpolator, float length) { 17 | this.keyframes = new TreeMap<>(); 18 | this.interpolator = interpolator; 19 | this.length = length; 20 | } 21 | 22 | public static Timeline of(Interpolator interpolator, float length){ 23 | return new Timeline<>(interpolator, length); 24 | } 25 | 26 | /** 27 | * Returns the value at the given time, looped or not. 28 | * @param time Time in seconds. 29 | * @param looping Whether the time should be looped or not. 30 | */ 31 | public T getValueAtTime(float time, boolean looping){ 32 | return looping ? this.getValueAtTimeLooped(time) : this.getValueAtTime(time); 33 | } 34 | 35 | /** 36 | * Returns the value at the given time. 37 | * @param time Time in seconds. 38 | */ 39 | public T getValueAtTime(float time) { 40 | var firstKeyframe = keyframes.floorEntry(time); 41 | var secondKeyframe = keyframes.ceilingEntry(time); 42 | 43 | if (firstKeyframe == null) 44 | return secondKeyframe.getValue().getValue(); 45 | if (secondKeyframe == null) 46 | return firstKeyframe.getValue().getValue(); 47 | 48 | //same frame 49 | if (firstKeyframe.getKey().equals(secondKeyframe.getKey())) 50 | return firstKeyframe.getValue().getValue(); 51 | 52 | 53 | 54 | float relativeTime = (time - firstKeyframe.getKey()) / (secondKeyframe.getKey() - firstKeyframe.getKey()); 55 | 56 | 57 | return this.interpolator.interpolate( 58 | firstKeyframe.getValue().getValue(), 59 | secondKeyframe.getValue().getValue(), 60 | secondKeyframe.getValue().getEasing().ease(relativeTime) 61 | ); 62 | } 63 | 64 | /** 65 | * Returns the value at the looped given time. 66 | * @param time Time in ticks. 67 | */ 68 | public T getValueAtTimeLooped(float time) { 69 | return this.getValueAtTime(time % this.length); 70 | } 71 | 72 | public Timeline addKeyframe(float time, T value) { 73 | return addKeyframe(time, value, Easing.LINEAR); 74 | } 75 | 76 | public Timeline addKeyframe(float time, T value, Easing easing) { 77 | keyframes.put(time, new Keyframe(value, easing)); 78 | return this; 79 | } 80 | 81 | public static class Keyframe { 82 | private final T value; 83 | Easing easing; 84 | 85 | public Keyframe(T value, Easing easing) { 86 | this.value = value; 87 | this.easing = easing; 88 | } 89 | 90 | public T getValue() { 91 | return value; 92 | } 93 | 94 | public Easing getEasing() { 95 | return easing; 96 | } 97 | 98 | } 99 | 100 | public static Timeline ofFloat(float length) { 101 | return Timeline.of(Interpolator.FLOAT, length); 102 | } 103 | 104 | public static Timeline ofJointTransform(float length) { 105 | return Timeline.of(JointChannel::interpolate, length); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/trainguy9512/locomotion/util/Transition.java: -------------------------------------------------------------------------------- 1 | package com.trainguy9512.locomotion.util; 2 | 3 | import com.trainguy9512.locomotion.animation.joint.skeleton.BlendProfile; 4 | 5 | /** 6 | * Represents the properties of a transition, including the duration and easing function used to 7 | * blend between animation poses. 8 | * 9 | *

In most implementations of transitions, the underlying transition itself is a linear gradient from 0 to 1 10 | * with the easing function applied on top.

11 | * 12 | *

The transition duration specifies how long the transition takes to complete, as a {@link TimeSpan}. 13 | * The transition easement defines the type of {@link Easing} function used to modify the interpolation 14 | * of the transition, allowing for more interesting or stylized transitions.

15 | * 16 | * @param duration The duration of the transition. 17 | * @param easement The type of {@link Easing} function to apply to the transition. 18 | * @param blendProfile Blend profile for the adjusting how quickly certain joints transition. 19 | */ 20 | public record Transition(TimeSpan duration, Easing easement, BlendProfile blendProfile) { 21 | 22 | public static final Transition INSTANT = Transition.builder(TimeSpan.ofTicks(1)).setEasement(Easing.CONSTANT).build(); 23 | public static final Transition SINGLE_TICK = Transition.builder(TimeSpan.ofTicks(1)).setEasement(Easing.LINEAR).build(); 24 | 25 | /** 26 | * Creates a new {@link Transition.Builder} with the provided duration. 27 | * @param duration The duration of the transition 28 | */ 29 | public static Builder builder(TimeSpan duration) { 30 | return new Builder(duration); 31 | } 32 | 33 | public Transition withInverseEasing() { 34 | return new Transition(this.duration, Easing.inverse(this.easement), this.blendProfile); 35 | } 36 | 37 | public static class Builder { 38 | 39 | private final TimeSpan duration; 40 | private Easing easement; 41 | private BlendProfile blendProfile; 42 | 43 | private Builder(TimeSpan duration) { 44 | this.duration = duration; 45 | this.easement = Easing.LINEAR; 46 | this.blendProfile = null; 47 | } 48 | 49 | public Builder setEasement(Easing easement) { 50 | this.easement = easement; 51 | return this; 52 | } 53 | 54 | public Builder setBlendProfile(BlendProfile blendProfile) { 55 | this.blendProfile = blendProfile; 56 | return this; 57 | } 58 | 59 | public Transition build() { 60 | return new Transition( 61 | this.duration, 62 | this.easement, 63 | this.blendProfile 64 | ); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/resources/architectury.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessWidener": "locomotion.accesswidener" 3 | } -------------------------------------------------------------------------------- /src/main/resources/assets/locomotion/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trainguy9512/locomotion/030661db62e477756a65be6163e556d4289bf3e4/src/main/resources/assets/locomotion/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/locomotion/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "locomotion.config.yacl_not_found.header": "Configuration API not installed", 3 | "locomotion.config.yacl_not_found.description": "Locomotion uses the configuration library \"YetAnotherConfigLib\" to generate the in-game configuration screen. It is not a required dependency, but it must be installed if you would like to adjust the configuration while in-game. Otherwise you may modify the configuration manually in .minecraft/config/locomotion.json", 4 | "locomotion.config.yacl_not_found.close": "Close", 5 | 6 | "locomotion.config.title": "Locomotion Configuration", 7 | "locomotion.config.category.general.name": "General", 8 | "locomotion.config.category.general.tooltip": "General settings for all animations", 9 | "locomotion.config.category.first_person_player.name": "First Person Player", 10 | "locomotion.config.category.first_person_player.tooltip": "Settings for first person player animations", 11 | "locomotion.config.option.enable_first_person_renderer.name": "First Person Animations", 12 | "locomotion.config.option.enable_first_person_renderer.description": "Whether Locomotion's first person player animations should be used or not. Unlike most other animations provided, this one has its own separate renderer that completely overwrites Vanilla's first person renderer. Disable this to show Vanilla's first person player renderer, or for compatibility with mods that integrate into Vanilla's first person renderer.", 13 | "locomotion.config.group.first_person_arm_camera_damping.name": "Camera Rotation Arm Damping", 14 | "locomotion.config.group.first_person_arm_camera_damping.description": "Settings for adjusting how the arms are rotated when the camera is moved around.", 15 | "locomotion.config.option.enable_first_person_arm_camera_damping.name": "Enabled", 16 | "locomotion.config.option.enable_first_person_arm_camera_damping.description": "Whether the first person arm dampening is enabled or not. Disabling this makes the arms not move at all when moving the camera.", 17 | "locomotion.config.option.first_person_arm_camera_damping_factor.name": "Damping Factor", 18 | "locomotion.config.option.first_person_arm_camera_damping_factor.description": "Controls how much friction the spring animation has. The higher the damping, the less it bounces back and forth", 19 | "locomotion.config.option.first_person_arm_camera_stiffness_factor.name": "Stiffness Factor", 20 | "locomotion.config.option.first_person_arm_camera_stiffness_factor.description": "Controls how quickly the arms rotate to match up with the camera's rotation", 21 | "locomotion.config.option.first_person_mining_speed.name": "Mining Animation Speed Multiplier", 22 | "locomotion.config.option.first_person_mining_speed.description": "Adjusts how fast the mining animations play. 1 is normal speed, 0.5 is half as fast, and 2 is twice as fast" 23 | } -------------------------------------------------------------------------------- /src/main/resources/locomotion-common.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "com.trainguy9512.locomotion.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | ], 8 | "client": [ 9 | "debug.MixinDebugScreenOverlay", 10 | "debug.MixinDefaultPlayerSkin", 11 | "game.MixinMinecraft", 12 | "game.MixinMultiPlayerGameMode", 13 | "item.MixinBlocksAttacks", 14 | "item.MixinItemInHandLayer", 15 | "item.MixinItemTransform", 16 | "item.property.MixinCrossbowPull", 17 | "item.property.MixinIsUsingItem", 18 | "item.property.MixinUseDuration", 19 | "render.MixinBlockRenderDispatcher", 20 | "render.MixinEntityRenderDispatcher", 21 | "render.MixinGameRenderer", 22 | "render.MixinItemInHandRenderer", 23 | "render.MixinLivingEntityRenderer", 24 | "render.MixinLivingEntityRenderState", 25 | "render.MixinModelPart", 26 | "render.layer.MixinCapeLayer", 27 | "render.layer.MixinElytraLayer" 28 | ], 29 | "injectors": { 30 | "defaultRequire": 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/locomotion.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v2 named -------------------------------------------------------------------------------- /stonecutter.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dev.kikugie.stonecutter") 3 | id("dev.architectury.loom") version "1.10-SNAPSHOT" apply false 4 | id("architectury-plugin") version "3.4-SNAPSHOT" apply false 5 | id("com.github.johnrengelman.shadow") version "8.1.1" apply false 6 | } 7 | stonecutter active "1.21.5" /* [SC] DO NOT EDIT */ 8 | 9 | // Builds every version into `build/libs/{mod.version}/{loader}` 10 | stonecutter registerChiseled tasks.register("chiseledBuild", stonecutter.chiseled) { 11 | group = "project" 12 | ofTask("buildAndCollect") 13 | } 14 | 15 | stonecutter registerChiseled tasks.register("chiseledRunDatagen", stonecutter.chiseled) { 16 | group = "project" 17 | ofTask("runDatagen") 18 | } 19 | 20 | // Builds loader-specific versions into `build/libs/{mod.version}/{loader}` 21 | for (it in stonecutter.tree.branches) { 22 | if (it.id.isEmpty()) continue 23 | val loader = it.id.replaceFirstChar { it.uppercaseChar() } 24 | stonecutter registerChiseled tasks.register("chiseledBuild$loader", stonecutter.chiseled) { 25 | group = "project" 26 | versions { branch, _ -> branch == it.id } 27 | ofTask("buildAndCollect") 28 | } 29 | } 30 | 31 | // Runs active versions for each loader 32 | for (it in stonecutter.tree.nodes) { 33 | if (it.metadata != stonecutter.current || it.branch.id.isEmpty()) continue 34 | val types = listOf("Client", "Server") 35 | val loader = it.branch.id.replaceFirstChar { it.uppercaseChar() } 36 | for (type in types) it.project.tasks.register("runActive$type$loader") { 37 | group = "project" 38 | dependsOn("run$type") 39 | } 40 | } 41 | --------------------------------------------------------------------------------