From 16995e5f9d796434f2a668b3095fcc31ce8788e8 Mon Sep 17 00:00:00 2001 From: LostInLinearPast <1283411677@qq.com> Date: Sun, 26 Oct 2025 22:09:49 +0800 Subject: [PATCH] version 0.0.4 --- .gitignore | 122 +- build.gradle | 86 +- gradle.properties | 6 +- .../linearpast/sccore/SnowyCrescentCore.java | 37 +- .../sccore/animation/AnimationPlayer.java | 116 ++ .../sccore/animation/AnimationUtils.java | 387 ++++ .../capability/AnimationDataCapability.java | 151 ++ .../inter/IAnimationCapability.java | 21 + .../animation/command/AnimationCommands.java | 48 + .../command/PlayAnimationCommand.java | 186 ++ .../command/argument/AnimationArgument.java | 68 + .../argument/AnimationLayerArgument.java | 69 + .../client/AnimationRefreshCommand.java | 36 + .../sccore/animation/data/Animation.java | 120 ++ .../sccore/animation/data/Ride.java | 87 + .../animation/entity/AnimationRideEntity.java | 213 +++ .../renderer/AnimationRideRenderer.java | 33 + .../event/AnimationLayerRegistry.java | 49 + .../event/EntityRendererRegistry.java | 11 + .../event/client/CameraAnglesModify.java | 68 + .../event/client/ClientPlayerTick.java | 37 + .../create/AnimationLayerRegisterEvent.java | 29 + .../mixin/IMixinKeyframeAnimationPlayer.java | 9 + .../toclient/AnimationCapabilityPacket.java | 76 + .../network/toclient/SyncAnimationPacket.java | 54 + .../toserver/PlayAnimationRequestPacket.java | 46 + .../toserver/PlayAnimationRidePacket.java | 54 + .../toserver/RefreshAnimationPacket.java | 45 + .../animation/registry/AnimationEntities.java | 33 + .../animation/registry/AnimationRegistry.java | 98 ++ .../sccore/capability/CapabilityUtils.java | 107 +- .../capability/data/ICapabilitySync.java | 48 +- .../data/entity/EntityCapabilityHandler.java | 37 +- .../data/entity/EntityCapabilityProvider.java | 10 +- .../data/entity/EntityCapabilityRegistry.java | 35 +- .../entity/EntityCapabilityRemainder.java | 20 +- .../entity/SimpleEntityCapabilitySync.java | 56 +- .../data/player/PlayerCapabilityHandler.java | 16 +- .../data/player/PlayerCapabilityProvider.java | 10 + .../data/player/PlayerCapabilityRegistry.java | 21 +- .../player/PlayerCapabilityRemainder.java | 47 +- .../player/SimplePlayerCapabilitySync.java | 42 +- .../capability/network/CapabilityChannel.java | 23 +- .../capability/network/ICapabilityPacket.java | 24 +- .../network/SimpleCapabilityPacket.java | 18 +- .../Channel.java => core/ModChannel.java} | 14 +- .../linearpast/sccore/core/ModCommands.java | 64 + .../linearpast/sccore/core/ModConfigs.java | 20 + .../linearpast/sccore/core/ModLazyRun.java | 52 + .../linearpast/sccore/example/ModCaps.java | 63 - .../example/animation/ModAnimation.java | 70 + .../animation/event/ExampleCommandEvent.java | 122 ++ .../event/ExamplePlayerAttackEvent.java | 65 + .../sccore/example/cap/ISheepData.java | 13 - .../example/capability/ModCapability.java | 64 + .../example/capability/data/ISheepData.java | 13 + .../data}/SheepDataCapability.java | 64 +- .../event/PlayerAttackEvent.java | 7 +- .../sccore/mixin/SCCoreMixinPlugin.java | 54 + .../sccore/mixin/animation/MixinEntity.java | 97 ++ .../mixin/animation/client/MixinEntity.java | 60 + .../animation/client/MixinHumanoidModel.java | 46 + .../client/MixinKeyframeAnimationPlayer.java | 20 + src/main/resources/META-INF/mods.toml | 7 + .../am_lying_to_right_lying.json | 112 ++ .../player_animation/am_stand_to_lying.json | 64 + .../player_animation/waltz_gentleman.json | 1536 ++++++++++++++++ .../sccore/player_animation/waltz_lady.json | 1552 +++++++++++++++++ src/main/resources/logo.png | Bin 0 -> 5712 bytes src/main/resources/sccore.mixins.json | 5 + 70 files changed, 6632 insertions(+), 461 deletions(-) create mode 100644 src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java create mode 100644 src/main/java/com/linearpast/sccore/animation/AnimationUtils.java create mode 100644 src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java create mode 100644 src/main/java/com/linearpast/sccore/animation/capability/inter/IAnimationCapability.java create mode 100644 src/main/java/com/linearpast/sccore/animation/command/AnimationCommands.java create mode 100644 src/main/java/com/linearpast/sccore/animation/command/PlayAnimationCommand.java create mode 100644 src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java create mode 100644 src/main/java/com/linearpast/sccore/animation/command/argument/AnimationLayerArgument.java create mode 100644 src/main/java/com/linearpast/sccore/animation/command/client/AnimationRefreshCommand.java create mode 100644 src/main/java/com/linearpast/sccore/animation/data/Animation.java create mode 100644 src/main/java/com/linearpast/sccore/animation/data/Ride.java create mode 100644 src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java create mode 100644 src/main/java/com/linearpast/sccore/animation/entity/renderer/AnimationRideRenderer.java create mode 100644 src/main/java/com/linearpast/sccore/animation/event/AnimationLayerRegistry.java create mode 100644 src/main/java/com/linearpast/sccore/animation/event/EntityRendererRegistry.java create mode 100644 src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java create mode 100644 src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerTick.java create mode 100644 src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java create mode 100644 src/main/java/com/linearpast/sccore/animation/mixin/IMixinKeyframeAnimationPlayer.java create mode 100644 src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java create mode 100644 src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java create mode 100644 src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java create mode 100644 src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java create mode 100644 src/main/java/com/linearpast/sccore/animation/network/toserver/RefreshAnimationPacket.java create mode 100644 src/main/java/com/linearpast/sccore/animation/registry/AnimationEntities.java create mode 100644 src/main/java/com/linearpast/sccore/animation/registry/AnimationRegistry.java rename src/main/java/com/linearpast/sccore/{network/Channel.java => core/ModChannel.java} (85%) create mode 100644 src/main/java/com/linearpast/sccore/core/ModCommands.java create mode 100644 src/main/java/com/linearpast/sccore/core/ModConfigs.java create mode 100644 src/main/java/com/linearpast/sccore/core/ModLazyRun.java delete mode 100644 src/main/java/com/linearpast/sccore/example/ModCaps.java create mode 100644 src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java create mode 100644 src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java create mode 100644 src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java delete mode 100644 src/main/java/com/linearpast/sccore/example/cap/ISheepData.java create mode 100644 src/main/java/com/linearpast/sccore/example/capability/ModCapability.java create mode 100644 src/main/java/com/linearpast/sccore/example/capability/data/ISheepData.java rename src/main/java/com/linearpast/sccore/example/{cap => capability/data}/SheepDataCapability.java (59%) rename src/main/java/com/linearpast/sccore/example/{ => capability}/event/PlayerAttackEvent.java (81%) create mode 100644 src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java create mode 100644 src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java create mode 100644 src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java create mode 100644 src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java create mode 100644 src/main/java/com/linearpast/sccore/mixin/animation/client/MixinKeyframeAnimationPlayer.java create mode 100644 src/main/resources/assets/sccore/player_animation/am_lying_to_right_lying.json create mode 100644 src/main/resources/assets/sccore/player_animation/am_stand_to_lying.json create mode 100644 src/main/resources/assets/sccore/player_animation/waltz_gentleman.json create mode 100644 src/main/resources/assets/sccore/player_animation/waltz_lady.json create mode 100644 src/main/resources/logo.png diff --git a/.gitignore b/.gitignore index d5f737e..60f5c25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,119 +1,5 @@ -# User-specific stuff -.idea/ - -*.iml -*.ipr -*.iws - -# IntelliJ -out/ -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - +.idea .gradle -build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Cache of project -.gradletasknamecache - -**/build/ - -# Common working directory -run/ -runs/ - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar +build +run +run-data \ No newline at end of file diff --git a/build.gradle b/build.gradle index 62259cc..a45da86 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ buildscript { plugins { id 'eclipse' id 'idea' + id 'maven-publish' id 'net.minecraftforge.gradle' version '[6.0.16,6.2)' id 'org.parchmentmc.librarian.forgegradle' version '1.+' } @@ -25,14 +26,14 @@ base { } java { + withSourcesJar() + withJavadocJar() toolchain.languageVersion = JavaLanguageVersion.of(17) } minecraft { mappings channel: mapping_channel, version: mapping_version - accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') - copyIdeResources = true runs { @@ -52,6 +53,8 @@ minecraft { var client = client { property 'forge.enabledGameTestNamespaces', mod_id + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${project.projectDir}/build/createSrgToMcp/output.srg" } client_0 { @@ -66,6 +69,8 @@ minecraft { server { property 'forge.enabledGameTestNamespaces', mod_id + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${project.projectDir}/build/createSrgToMcp/output.srg" args '--nogui' } @@ -116,6 +121,14 @@ dependencies { annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" compileOnly 'org.spongepowered:mixin:0.8.5' + implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")) + + implementation(jarJar("io.github.llamalad7:mixinextras-forge:0.4.1") { + jarJar.ranged(it, "[0.4.1,)") + }) + implementation fg.deobf("dev.kosmx.player-anim:player-animation-lib-forge:1.0.2-rc1+1.20") + implementation fg.deobf("io.github.kosmx.bendy-lib:bendy-lib-forge:4.0.0") + implementation fg.deobf("curse.maven:freecam-by-zergatul-618947:5402097") } tasks.named('processResources', ProcessResources).configure { @@ -156,6 +169,75 @@ tasks.named('jar', Jar).configure { finalizedBy 'reobfJar' } +tasks.withType(Javadoc).configureEach { + options.addStringOption('Xdoclint:none', '-quiet') + options.addStringOption('Xdoclint:-missing', '-quiet') + options.encoding = 'UTF-8' + failOnError = false +} + +tasks.register('deobfJar', Jar) { + from(sourceSets.main.output) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { + attributes(["Specification-Title" : mod_id, + "Specification-Vendor" : mod_authors, + "Specification-Version" : "1", + "Implementation-Title" : project.name, + "Implementation-Version" : project.jar.archiveVersion, + "Implementation-Vendor" : mod_authors, + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")]) + } + dependsOn classes +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifact deobfJar + artifact javadocJar + artifact sourcesJar + + groupId = project.group + artifactId = mod_id + version = project.version + pom { + name = mod_id + description = mod_description + url = mod_url + licenses { + license { + name = mod_license + } + } + developers { + developer { + id = "{mod_id}" + name = mod_authors + } + } + } + } + } + repositories { + maven { + name = 'LTDNexus' + url = 'https://nexus.bot.leisuretimedock.top/repository/maven-releases/' + credentials { + username = System.getenv('LTDNexusUsername') ?: '' + password = System.getenv('LTDNexusPassword') ?: '' + } + } + } +} + +gradle.buildFinished { + def javadocJar = file("build/libs/${mod_id}-${mod_version}-javadoc.jar") + def sourcesJar = file("build/libs/${mod_id}-${mod_version}-sources.jar") + if (javadocJar.exists()) ant.delete(file: javadocJar) + if (sourcesJar.exists()) ant.delete(file: sourcesJar) +} + tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } diff --git a/gradle.properties b/gradle.properties index 2d6005c..3310497 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,9 +12,9 @@ mapping_version=2023.09.03-1.20.1 mod_id=sccore mod_name=SnowyCrescentCore mod_license=GNU AGPL 3.0 -mod_version=1.20.1-0.0.2 +mod_version=1.20.1-0.0.4 mod_group_id=com.linearpast mod_authors=LostInLinearPast -mod_description=A lib. +mod_description=A lib about capability and player animator. mod_credits= -mod_url=https://qm.qq.com/q/k0NVzvUdlC +mod_url=https://github.com/Linearpast/SnowyCrescentCore diff --git a/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java b/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java index 0bad530..a66e3f6 100644 --- a/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java +++ b/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java @@ -1,28 +1,47 @@ package com.linearpast.sccore; +import com.linearpast.sccore.animation.registry.AnimationRegistry; import com.linearpast.sccore.capability.CapabilityUtils; -import com.linearpast.sccore.example.ModCaps; -import com.linearpast.sccore.network.Channel; +import com.linearpast.sccore.core.ModChannel; +import com.linearpast.sccore.core.ModCommands; +import com.linearpast.sccore.core.ModConfigs; +import com.linearpast.sccore.example.animation.ModAnimation; +import com.linearpast.sccore.example.capability.ModCapability; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Mod(SnowyCrescentCore.MODID) public class SnowyCrescentCore { - + public static final Logger log = LoggerFactory.getLogger(SnowyCrescentCore.class); public static final String MODID = "sccore"; + public static final String ENABLE_EXAMPLES_PROPERTY_KEY = "sccore.enable_examples"; public SnowyCrescentCore() { - IEventBus forgeBus = MinecraftForge.EVENT_BUS; - CapabilityUtils.registerHandler(forgeBus); - Channel.register(); + ModLoadingContext modLoadingContext = ModLoadingContext.get(); + modLoadingContext.registerConfig(ModConfig.Type.COMMON, ModConfigs.Common.SPEC); - if(!FMLEnvironment.production){ - ModCaps.register(); - ModCaps.addListenerToEvent(forgeBus); + IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); + IEventBus forgeBus = MinecraftForge.EVENT_BUS; + + CapabilityUtils.registerHandler(forgeBus); + ModChannel.register(); + AnimationRegistry.register(); + AnimationRegistry.addAnimationListener(forgeBus, modBus); + ModCommands.registerCommands(forgeBus, modBus); + + if(!FMLEnvironment.production || Boolean.getBoolean(ENABLE_EXAMPLES_PROPERTY_KEY)) { + ModCapability.register(); + ModCapability.addListenerToEvent(forgeBus); + ModAnimation.register(forgeBus, modBus); } } } diff --git a/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java b/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java new file mode 100644 index 0000000..c7f317f --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java @@ -0,0 +1,116 @@ +package com.linearpast.sccore.animation; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.linearpast.sccore.animation.mixin.IMixinKeyframeAnimationPlayer; +import com.linearpast.sccore.animation.network.toclient.SyncAnimationPacket; +import com.linearpast.sccore.animation.network.toserver.PlayAnimationRequestPacket; +import com.linearpast.sccore.animation.network.toserver.PlayAnimationRidePacket; +import com.linearpast.sccore.core.ModChannel; +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; +import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.api.layered.modifier.AbstractFadeModifier; +import dev.kosmx.playerAnim.core.data.KeyframeAnimation; +import dev.kosmx.playerAnim.core.util.Ease; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class AnimationPlayer { + public static void requestAnimationToServer(ResourceLocation layer, @Nullable ResourceLocation animation) { + ModChannel.sendToServer(new PlayAnimationRequestPacket(layer, animation)); + } + + public static boolean serverPlayAnimation(ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { + IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); + if(data == null) return false; + if(animation != null) { + return data.mergeAnimation(layer, animation); + } else { + return data.removeAnimation(layer); + } + } + + public static boolean playAnimationWithRide(ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation, boolean force){ + if(animation != null) { + IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); + if(data == null) return false; + data.setRideAnimLayer(layer); + return AnimationRideEntity.create(serverPlayer, layer, animation, force); + } else { + serverPlayer.unRide(); + return true; + } + } + + public static void requestAnimationRideToServer(ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) { + ModChannel.sendToServer(new PlayAnimationRidePacket(layer, animation, force)); + } + + public static void clearAnimation(ServerPlayer serverPlayer) { + IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); + if(data == null) return; + data.clearAnimations(); + } + + public static void syncAnimation(ServerPlayer player, ServerPlayer target, ResourceLocation layer) { + ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID(), layer), player); + ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID(), layer), target); + } + + @SuppressWarnings("unchecked") + @OnlyIn(Dist.CLIENT) + public static void syncAnimation(AbstractClientPlayer clientPlayer, AbstractClientPlayer target, ResourceLocation layer) { + try { + ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(clientPlayer).get(layer); + ModifierLayer targetModifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(target).get(layer); + if(modifierLayer == null || targetModifierLayer == null) return; + IMixinKeyframeAnimationPlayer animation = (IMixinKeyframeAnimationPlayer) modifierLayer.getAnimation(); + KeyframeAnimationPlayer targetAnimation = (KeyframeAnimationPlayer) targetModifierLayer.getAnimation(); + if(animation == null || targetAnimation == null) return; + int currentTick = targetAnimation.getCurrentTick(); + animation.sccore$setCurrentTick(currentTick); + } catch (Exception ignored) {} + } + + @SuppressWarnings("unchecked") + @OnlyIn(Dist.CLIENT) + public static void playAnimation(AbstractClientPlayer clientPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { + try { + ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(clientPlayer).get(layer); + if(animation == null) { + if(modifierLayer != null) { + modifierLayer.replaceAnimationWithFade( + AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), + null + ); + } + return; + } + Animation anim = AnimationUtils.getAnimation(animation); + if(anim == null) return; + if(modifierLayer == null) return; + KeyframeAnimation keyframeAnimation = anim.getAnimation(); + if(keyframeAnimation == null) return; + Objects.requireNonNull(modifierLayer).replaceAnimationWithFade( + AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), + new KeyframeAnimationPlayer(keyframeAnimation) + ); + }catch (Exception e) { + SnowyCrescentCore.log.error("Failed to play animation : {}", animation, e); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java b/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java new file mode 100644 index 0000000..cc32e8b --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java @@ -0,0 +1,387 @@ +package com.linearpast.sccore.animation; + +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.Ride; +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import com.linearpast.sccore.animation.event.EntityRendererRegistry; +import com.linearpast.sccore.animation.event.client.CameraAnglesModify; +import com.linearpast.sccore.animation.event.client.ClientPlayerTick; +import com.linearpast.sccore.animation.network.toserver.RefreshAnimationPacket; +import com.linearpast.sccore.animation.registry.AnimationEntities; +import com.linearpast.sccore.animation.registry.AnimationRegistry; +import com.linearpast.sccore.core.ModChannel; +import com.linearpast.sccore.core.ModLazyRun; +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; +import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.eventbus.api.IEventBus; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; + +/** + * Animation Util. May be you can call it Api. + */ +public class AnimationUtils { + public static final String AnimModId = "playeranimator"; + public static final ModLazyRun ANIMATION_RUNNER = new ModLazyRun(AnimModId) { + @Override + public void addCommonListener(IEventBus forgeBus, IEventBus modBus) { + AnimationEntities.register(modBus); + modBus.addListener(AnimationLayerRegistry::onCommonSetUp); + } + + @Override + public void addClientListener(IEventBus forgeBus, IEventBus modBus) { + forgeBus.addListener(CameraAnglesModify::changeCameraView); + modBus.addListener(AnimationLayerRegistry::onClientSetup); + modBus.addListener(EntityRendererRegistry::registerEntityRenderer); + forgeBus.addListener(ClientPlayerTick::onPlayerTick); + forgeBus.addListener(ClientPlayerTick::delayRuns); + } + }; + + /** + *
+     * Play animation.
+     * If run in Dist.CLIENT, the serverPlayer can be null.
+     * If animation be null, it will remove animation on layer.
+     * 
+ * @param serverPlayer Target player + * @param layer Target layer + * @param animation Animation + * @return If success + */ + public static boolean playAnimation(@Nullable ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) { + if(serverPlayer != null) { + return AnimationPlayer.serverPlayAnimation(serverPlayer, layer, animation); + }else { + AnimationPlayer.requestAnimationToServer(layer, animation); + return true; + } + } + return false; + }); + } + + /** + *
+     * Play animation with ride. Player will ride an entity, then play animation.
+     * When player unride, animation will be remove.
+     * If run in Dist.CLIENT, the serverPlayer can be null.
+     * If animation be null, it will call function: {@link ServerPlayer#unRide()}
+     * If player is riding and the "force" is false, it will return false
+     * 
+ * @param serverPlayer Target player + * @param layer Target layer + * @param animation Animation + * @param force If force to ride and play animation + * @return If success + */ + public static boolean playAnimationWithRide(@Nullable ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) { + Animation anim = AnimationUtils.getAnimation(animation); + if(anim != null && anim.getRide() == null) return false; + if(serverPlayer != null) { + if(serverPlayer.getVehicle() != null && force) serverPlayer.unRide(); + else if(serverPlayer.getVehicle() != null) return false; + AnimationPlayer.playAnimationWithRide(serverPlayer, layer, animation, true); + } else { + AnimationPlayer.requestAnimationRideToServer(layer, animation, force); + return true; + } + } + return false; + }); + } + + /** + * Remove animation. + * @see AnimationUtils#playAnimation + * @param serverPlayer Target player + * @param layer Target layer + * @return If success + */ + public static boolean removeAnimation(@Nullable ServerPlayer serverPlayer, ResourceLocation layer) { + return playAnimation(serverPlayer, layer, null); + } + + /** + * Get animation which is playing now on player.
+ * If layer is null, it will return the first playing animation which can be found. + * @param player Target player + * @param layer Target layer + * @return Playing animation resource location + */ + @Nullable + public static ResourceLocation getAnimationPlaying(Player player, @Nullable ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) return null; + if(layer == null){ + for (ResourceLocation value : data.getAnimations().values()) { + if(value != null) return value; + } + } else if (isAnimationLayerPresent(layer)) { + if(data.isAnimationPresent(layer)){ + return data.getAnimation(layer); + } + } + return null; + }); + } + + /** + * Test if layer exist animation which is playing. + *

+ * Only in dist client + * @param player Target player + * @param layer Target layer + * @return If layer exist animation which is playing + */ + @OnlyIn(Dist.CLIENT) + @SuppressWarnings("unchecked") + public static boolean isClientAnimationPlaying(AbstractClientPlayer player, @Nullable ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + try { + Set resourceLocations = new HashSet<>(); + if(layer == null) resourceLocations = AnimationLayerRegistry.getAnimLayers().keySet(); + else resourceLocations.add(layer); + for (ResourceLocation location : resourceLocations) { + ModifierLayer animationModifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(player).get(location); + if(animationModifierLayer == null) continue; + KeyframeAnimationPlayer animation = (KeyframeAnimationPlayer) animationModifierLayer.getAnimation(); + if(animation == null) return false; + int currentTick = animation.getCurrentTick(); + int stopTick = animation.getStopTick(); + return currentTick <= stopTick; + } + } catch (Exception ignored) {} + return false; + }); + } + + /** + * Sync animation tick to client + * @param player Player + * @param target Target player + * @param layer Target layer + */ + public static void syncAnimation(ServerPlayer player, ServerPlayer target, ResourceLocation layer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target, layer)); + } + + /** + * Sync animation tick on client + * @param player Player + * @param target Target player + * @param layer Target layer + */ + @OnlyIn(Dist.CLIENT) + public static void syncAnimation(AbstractClientPlayer player, AbstractClientPlayer target, ResourceLocation layer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target, layer)); + } + + /** + * Join animation. + * @param player Will join player + * @param target Joined player + * @param force If force + * @return If success + */ + public static boolean joinAnimation(ServerPlayer player, ServerPlayer target, boolean force) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + Entity vehicle = target.getVehicle(); + if(!(vehicle instanceof AnimationRideEntity rideEntity)) return false; + int playerCount = rideEntity.getPlayers().size(); + Animation animation = rideEntity.getAnimation(); + if(animation == null) return false; + Ride ride = animation.getRide(); + if(ride == null) return false; + int maxCount = ride.getComponentAnimations().size(); + if(playerCount >= maxCount) return false; + return player.startRiding(vehicle, force); + }); + } + + /** + *

+     * Start animation together...
+     * The max participants' count is depend on your animation.
+     * Max count = Size of {@link Ride#getComponentAnimations()}
+     * 
+ * @param player Leader + * @param layer Target layer + * @param animation Animation location + * @param force If force start leader + * @param participants Participants + */ + public static void startAnimationTogether( + ServerPlayer player, + ResourceLocation layer, + ResourceLocation animation, + boolean force, + ServerPlayer ... participants + ) { + AnimationUtils.playAnimationWithRide(player, layer, animation, force); + for (ServerPlayer participant : participants) { + AnimationUtils.joinAnimation(participant, player, force); + } + } + + /** + * Detach animation + * @param player Player + */ + public static void detachAnimation(ServerPlayer player) { + ANIMATION_RUNNER.testLoadedAndRun(() -> { + if(player.getVehicle() instanceof AnimationRideEntity) { + player.unRide(); + } + }); + } + + /** + * Clear Player animations. + * @param serverPlayer Target player + */ + public static void clearAnimation(ServerPlayer serverPlayer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.clearAnimation(serverPlayer)); + } + + /** + * Test if layer exist and has been register. + * @param layer Target layer + * @return If layer exist and has been register + */ + public static boolean isAnimationLayerPresent(ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationLayerRegistry.isLayerPresent(layer)); + } + + /** + * Test if animation exist and has been register. + * @param location Animation resource location + * @return If animation exist and has been register + */ + public static boolean isAnimationPresent(ResourceLocation location) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.isAnimationPresent(location)); + } + + /** + * Register an animation through static function + * @param location Animation resource location + * @param animation Animation data + */ + public static void registerAnimation(ResourceLocation location, Animation animation) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationRegistry.registerAnimation(location, animation)); + } + + /** + * Register an animation layer through static function.
+ * The number is bigger and the priority is higher.
+ * It must run before these events :
+ * {@link net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent}
+ * {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent} + * @param location Layer location key + * @param priority Layer priority, + */ + public static void registerAnimationLayer(ResourceLocation location, int priority) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationLayerRegistry.registerPlayerAnimation(location, priority)); + } + + /** + * Get animation data from animation resource location.
+ * You will get null if you use it too early.
+ * Suggest only use it in game has loaded. + * @param location Animation resource location + * @return Animation data + */ + @Nullable + public static Animation getAnimation(ResourceLocation location) { + return AnimationRegistry.getAnimation(location); + } + + /** + * Get the LyingType when there are animations which playing on player.
+ * And It will return the first which be found. + * @param player Target player + * @return The first LyingType it find. + */ + @Nullable + public static Animation.LyingType getSideView(Player player) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) return null; + Animation.LyingType lyingType = null; + for (ResourceLocation value : data.getAnimations().values()) { + Animation animation = getAnimation(value); + if(animation == null) return null; + Animation.LyingType type = animation.getLyingType(); + if(type == null) continue; + switch (type) { + case FRONT,BACK -> {} + case LEFT,RIGHT -> lyingType = animation.getLyingType(); + } + } + return lyingType; + }); + } + + /** + * Get the HeightModifier when there are animations which playing on player.
+ * And It will return the first which be found. + * @param player Target player + * @return The first HeightModifier it find. + */ + public static float getHeightModifier(Player player) { + Float result = ANIMATION_RUNNER.testLoadedAndCall(() -> { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if (data == null) return 1.0f; + float heightModifier = 1.0f; + for (ResourceLocation value : data.getAnimations().values()) { + Animation animation = getAnimation(value); + if (animation == null) continue; + float animationHeightModifier = animation.getHeightModifier(); + heightModifier = Math.min(heightModifier, animationHeightModifier); + } + return heightModifier; + }); + return result == null ? 1.0f : result; + } + + /** + * Test if animation is playing
+ * if not, it will remove the animation resource location on client + *

+ * Only in dist client + * @param clientPlayer Target player + */ + @OnlyIn(Dist.CLIENT) + public static void refreshAnimation(AbstractClientPlayer clientPlayer) { + IAnimationCapability data = AnimationDataCapability.getCapability(clientPlayer).orElse(null); + if(data == null) return; + Set oldLayers = new HashSet<>(data.getAnimations().keySet()); + oldLayers.forEach(layer -> { + if (isClientAnimationPlaying(clientPlayer, layer)) { + oldLayers.remove(layer); + } + }); + ModChannel.sendToServer(new RefreshAnimationPacket(oldLayers)); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java b/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java new file mode 100644 index 0000000..474d237 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java @@ -0,0 +1,151 @@ +package com.linearpast.sccore.animation.capability; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import com.linearpast.sccore.animation.network.toclient.AnimationCapabilityPacket; +import com.linearpast.sccore.capability.CapabilityUtils; +import com.linearpast.sccore.capability.data.ICapabilitySync; +import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync; +import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class AnimationDataCapability extends SimplePlayerCapabilitySync implements IAnimationCapability { + public static final ResourceLocation key = new ResourceLocation(SnowyCrescentCore.MODID, "animation_data"); + + public static final String AnimMap = "AnimMap"; + public static final String RideAnimLayer = "RideAnimLayer"; + + private final Map animMap = new HashMap<>(); + private ResourceLocation rideAnimLayer; + + @Override + public void mergeAnimations(Map animations) { + animations.forEach((key, value) -> { + if (AnimationLayerRegistry.getAnimLayers().containsKey(key)) { + if (AnimationUtils.isAnimationPresent(value)) { + this.animMap.put(key, value); + setDirty(true); + } + } + }); + } + + @Override + public boolean mergeAnimation(ResourceLocation layer, ResourceLocation animation) { + if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) { + if (AnimationUtils.isAnimationPresent(animation)) { + this.animMap.put(layer, animation); + setDirty(true); + return true; + } + } + return false; + } + + @Override + public boolean removeAnimation(ResourceLocation layer) { + ResourceLocation remove = this.animMap.remove(layer); + if(remove != null) { + setDirty(true); + return true; + } + return false; + } + + @Nullable + @Override + public ResourceLocation getAnimation(ResourceLocation layer) { + return animMap.get(layer); + } + + @Override + public Map getAnimations() { + return animMap; + } + + @Override + public void clearAnimations() { + this.animMap.clear(); + setDirty(true); + } + + @Override + public boolean isAnimationPresent(ResourceLocation layer) { + return animMap.containsKey(layer); + } + + @Override + public void setRideAnimLayer(ResourceLocation rideAnimLayer) { + this.rideAnimLayer = rideAnimLayer; + setDirty(true); + } + + @Override + public ResourceLocation getRideAnimLayer() { + return rideAnimLayer; + } + + @Override + public void copyFrom(ICapabilitySync oldData) { + IAnimationCapability data = (IAnimationCapability) oldData; + this.animMap.clear(); + this.animMap.putAll(data.getAnimations()); + } + + @Override + public CompoundTag toTag(CompoundTag tag) { + if(!animMap.isEmpty()) { + CompoundTag animMapTag = new CompoundTag(); + animMap.forEach((string, animation) -> + animMapTag.putString(string.toString(), animation.toString()) + ); + tag.put(AnimMap, animMapTag); + } + if(rideAnimLayer != null) tag.putString(RideAnimLayer, rideAnimLayer.toString()); + return tag; + } + + @Override + public void fromTag(CompoundTag tag) { + this.animMap.clear(); + if(tag.contains(AnimMap)) { + CompoundTag animMapTag = tag.getCompound(AnimMap); + animMapTag.getAllKeys().forEach(key -> this.animMap.put( + new ResourceLocation(key), + new ResourceLocation(animMapTag.getString(key)) + )); + } + if(tag.contains(RideAnimLayer)) this.rideAnimLayer = new ResourceLocation(tag.getString(RideAnimLayer)); + } + + @Override + public SimpleCapabilityPacket getDefaultPacket() { + return new AnimationCapabilityPacket(serializeNBT()); + } + + @Override + public void attachInit(Player player) { + Map map = new HashMap<>(this.animMap); + map.forEach((key, value) -> { + if(!AnimationUtils.isAnimationLayerPresent(key)) this.animMap.remove(key); + if(!AnimationUtils.isAnimationPresent(value)) this.animMap.remove(key); + if(key.equals(rideAnimLayer)) this.animMap.remove(key); + }); + this.rideAnimLayer = null; + } + + public static Optional getCapability(Player player){ + return Optional.ofNullable(CapabilityUtils.getPlayerCapability( + player, AnimationDataCapability.key, IAnimationCapability.class + )); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/capability/inter/IAnimationCapability.java b/src/main/java/com/linearpast/sccore/animation/capability/inter/IAnimationCapability.java new file mode 100644 index 0000000..454db91 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/capability/inter/IAnimationCapability.java @@ -0,0 +1,21 @@ +package com.linearpast.sccore.animation.capability.inter; + +import com.linearpast.sccore.capability.data.ICapabilitySync; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public interface IAnimationCapability extends ICapabilitySync { + void mergeAnimations(Map animations); + boolean mergeAnimation(ResourceLocation layer, ResourceLocation animation); + boolean removeAnimation(ResourceLocation layer); + @Nullable ResourceLocation getAnimation(ResourceLocation layer); + Map getAnimations(); + void clearAnimations(); + boolean isAnimationPresent(ResourceLocation layer); + + void setRideAnimLayer(ResourceLocation layer); + ResourceLocation getRideAnimLayer(); +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/AnimationCommands.java b/src/main/java/com/linearpast/sccore/animation/command/AnimationCommands.java new file mode 100644 index 0000000..5e3b8c1 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/AnimationCommands.java @@ -0,0 +1,48 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.command.argument.AnimationArgument; +import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; +import com.linearpast.sccore.animation.command.client.AnimationRefreshCommand; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.synchronization.ArgumentTypeInfo; +import net.minecraft.commands.synchronization.ArgumentTypeInfos; +import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import net.minecraftforge.registries.DeferredRegister; + +import static net.minecraft.commands.Commands.literal; + +public class AnimationCommands { + public static void commonCommandRegister(LiteralArgumentBuilder builder) { + if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()){ + LiteralArgumentBuilder anim = literal("anim"); + PlayAnimationCommand.register(anim); + + builder.then(anim); + } + } + + public static void clientCommandRegister(LiteralArgumentBuilder builder) { + if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()) { + LiteralArgumentBuilder anim = literal("anim"); + AnimationRefreshCommand.register(anim); + builder.then(anim); + } + } + + public static void registerArguments(DeferredRegister> register) { + register.register("animation", + () -> ArgumentTypeInfos.registerByClass( + AnimationArgument.class, + SingletonArgumentInfo.contextFree(AnimationArgument::animation) + ) + ); + register.register("layer", + () -> ArgumentTypeInfos.registerByClass( + AnimationLayerArgument.class, + SingletonArgumentInfo.contextFree(AnimationLayerArgument::layer) + ) + ); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/PlayAnimationCommand.java b/src/main/java/com/linearpast/sccore/animation/command/PlayAnimationCommand.java new file mode 100644 index 0000000..c449b1b --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/PlayAnimationCommand.java @@ -0,0 +1,186 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.command.argument.AnimationArgument; +import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class PlayAnimationCommand { + public static void register(LiteralArgumentBuilder animCommand){ + animCommand.then(literal("play").then(argument("players", EntityArgument.players()).then( + argument("layer", AnimationLayerArgument.layer()) + .then(argument("animation", AnimationArgument.animation()) + .requires(cs -> cs.hasPermission(2)) + .executes(context -> playAnimation(context, false)) + .then(argument("withRide", BoolArgumentType.bool()) + .executes(context -> playAnimation( + context, BoolArgumentType.getBool(context, "withRide") + )) + .then(argument("forced", BoolArgumentType.bool()) + .executes(context -> playAnimation( + context, BoolArgumentType.getBool(context, "withRide")) + ) + ) + ) + ) + ))); + animCommand.then(literal("remove") + .requires(cs -> cs.hasPermission(2)) + .executes(PlayAnimationCommand::clearAnimation) + .then(argument("players", EntityArgument.players()) + .requires(cs -> cs.hasPermission(2)) + .executes(PlayAnimationCommand::clearAnimation) + .then(argument("layer", AnimationLayerArgument.layer()) + .executes(PlayAnimationCommand::removeAnimation) + ) + ) + ); + } + + private static int playAnimation(CommandContext ctx, boolean withRide) { + CommandSourceStack source = ctx.getSource(); + try { + Collection players = EntityArgument.getPlayers(ctx, "players"); + String animation = AnimationArgument.getAnimation(ctx, "animation").replace("$", ":"); + String layer = AnimationLayerArgument.getLayer(ctx, "layer").replace("$", ":"); + ResourceLocation layerLocation = new ResourceLocation(layer); + ResourceLocation animLocation = new ResourceLocation(animation); + boolean animationPresent = AnimationUtils.isAnimationPresent(animLocation); + boolean layerPresent = AnimationUtils.isAnimationLayerPresent(layerLocation); + if(!animationPresent) { + source.sendFailure(Component.literal("Animation is not present.").withStyle(ChatFormatting.RED)); + return 0; + } + if(!layerPresent) { + source.sendFailure(Component.literal("Layer is not present.").withStyle(ChatFormatting.RED)); + return 0; + } + Set playerSet = Set.copyOf(players); + playerSet.forEach(player -> { + if(withRide) { + boolean forced = false; + try { forced = BoolArgumentType.getBool(ctx, "forced");} + catch (Exception ignored) {} + if(AnimationUtils.playAnimationWithRide(player, layerLocation, animLocation, forced)) { + players.remove(player); + } + } else { + if (AnimationUtils.playAnimation(player, layerLocation, animLocation)) { + players.remove(player); + } + } + }); + MutableComponent fail = Component.literal("Fail to play animation with: "); + int successNum = playerSet.size() - players.size(); + if(successNum > 0) { + MutableComponent success = Component.literal("Success to play animation with ") + .append(successNum + "") + .append(" player"); + if(successNum > 1) success.append("s."); + else success.append("."); + source.sendSuccess(() -> success.withStyle(ChatFormatting.GREEN), true); + } + List list = players.stream().toList(); + if(!list.isEmpty()) { + for (int i = 0; i < list.size(); i++) { + fail.append(list.get(i).getName()); + if (i < list.size() - 1) { + fail.append(", "); + } + } + fail.append("."); + source.sendFailure(fail.withStyle(ChatFormatting.RED)); + } + } catch (Exception e) { + source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED)); + return 0; + } + return 1; + } + + private static int removeAnimation(CommandContext ctx) { + CommandSourceStack source = ctx.getSource(); + try { + Collection players = EntityArgument.getPlayers(ctx, "players"); + String layer = AnimationLayerArgument.getLayer(ctx, "layer").replace("$", ":"); + ResourceLocation layerLocation = new ResourceLocation(layer); + boolean layerPresent = AnimationUtils.isAnimationLayerPresent(layerLocation); + if(!layerPresent) { + source.sendFailure(Component.literal("Layer is not present.").withStyle(ChatFormatting.RED)); + return 0; + } + Set playerSet = Set.copyOf(players); + playerSet.forEach(player -> { + if(player.getVehicle() instanceof AnimationRideEntity rideEntity && rideEntity.getLayer().equals(layerLocation)) { + player.unRide(); + players.remove(player); + } + if (AnimationUtils.removeAnimation(player, layerLocation)) { + players.remove(player); + } + }); + MutableComponent fail = Component.literal("Fail to remove animation with: "); + int successNum = playerSet.size() - players.size(); + if(successNum > 0) { + MutableComponent success = Component.literal("Success to remove animation with ") + .append(successNum + "") + .append(" player"); + if(successNum > 1) success.append("s."); + else success.append("."); + source.sendSuccess(() -> success.withStyle(ChatFormatting.GREEN), true); + } + List list = players.stream().toList(); + if(!list.isEmpty()) { + for (int i = 0; i < list.size(); i++) { + fail.append(list.get(i).getName()); + if (i < list.size() - 1) { + fail.append(", "); + } + } + fail.append("."); + source.sendFailure(fail.withStyle(ChatFormatting.RED)); + } + } catch (Exception e) { + source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED)); + return 0; + } + return 1; + } + + private static int clearAnimation(CommandContext ctx) { + CommandSourceStack source = ctx.getSource(); + try { + Collection players; + try {players = EntityArgument.getPlayers(ctx, "players");} + catch (Exception ignored) { players = Set.of(source.getPlayerOrException()); } + Set.copyOf(players).forEach(player -> { + if(player.getVehicle() instanceof AnimationRideEntity) { + player.unRide(); + } + AnimationUtils.clearAnimation(player); + }); + source.sendSuccess(() -> Component.literal("Animation cleared.").withStyle(ChatFormatting.GREEN), true); + } catch (Exception e) { + source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED)); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java new file mode 100644 index 0000000..433df7b --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java @@ -0,0 +1,68 @@ +package com.linearpast.sccore.animation.command.argument; + +import com.linearpast.sccore.animation.registry.AnimationRegistry; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class AnimationArgument implements ArgumentType { + private static final Supplier> EXAMPLES = () -> AnimationRegistry.getAnimations().keySet().stream() + .map(ResourceLocation::toString).collect(Collectors.toSet()); + private static final DynamicCommandExceptionType UNKNOWN_TYPE = new DynamicCommandExceptionType( + animation -> Component.literal("Unknow animation : " + animation.toString()) + ); + + private final Set animationNames; + public AnimationArgument() { + this.animationNames = AnimationRegistry.getAnimations().keySet().stream() + .map(ResourceLocation::toString).collect(Collectors.toSet()); + } + + public static AnimationArgument animation() { + return new AnimationArgument(); + } + + public static String getAnimation(CommandContext context, String name) { + return context.getArgument(name, String.class); + } + + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggest(animationNames, builder); + } + + public Collection getExamples() { + return EXAMPLES.get(); + } + + public String parse(StringReader reader) throws CommandSyntaxException { + int start = reader.getCursor(); + while (reader.canRead() && canRead(reader.peek())) { + reader.skip(); + } + String s = reader.getString().substring(start, reader.getCursor()); + if (!animationNames.contains(s)) { + throw UNKNOWN_TYPE.create(s); + } else { + return s; + } + } + + private static boolean canRead(char peek) { + boolean origin = StringReader.isAllowedInUnquotedString(peek); + return origin || peek == ':'; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationLayerArgument.java b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationLayerArgument.java new file mode 100644 index 0000000..3963902 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationLayerArgument.java @@ -0,0 +1,69 @@ +package com.linearpast.sccore.animation.command.argument; + +import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class AnimationLayerArgument implements ArgumentType { + private static final Supplier> EXAMPLES = () -> AnimationLayerRegistry.getAnimLayers().keySet().stream() + .map(ResourceLocation::toString).collect(Collectors.toSet()); + private static final DynamicCommandExceptionType UNKNOWN_TYPE = new DynamicCommandExceptionType( + layer -> Component.literal("Unknow layer : " + layer.toString()) + ); + + private final Set animationLayers; + public AnimationLayerArgument() { + this.animationLayers = AnimationLayerRegistry.getAnimLayers().keySet().stream() + .map(ResourceLocation::toString).collect(Collectors.toSet()); + } + + public static AnimationLayerArgument layer() { + return new AnimationLayerArgument(); + } + + public static String getLayer(CommandContext context, String name) { + return context.getArgument(name, String.class); + } + + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggest(animationLayers, builder); + } + + public Collection getExamples() { + return EXAMPLES.get(); + } + + public String parse(StringReader reader) throws CommandSyntaxException { + int start = reader.getCursor(); + while (reader.canRead() && canRead(reader.peek())) { + reader.skip(); + } + String s = reader.getString().substring(start, reader.getCursor()); + if (!animationLayers.contains(s)) { + throw UNKNOWN_TYPE.create(s); + } else { + return s; + } + } + + private static boolean canRead(char peek) { + boolean origin = StringReader.isAllowedInUnquotedString(peek); + return origin || peek == ':'; + } + +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/client/AnimationRefreshCommand.java b/src/main/java/com/linearpast/sccore/animation/command/client/AnimationRefreshCommand.java new file mode 100644 index 0000000..ddf7fdf --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/client/AnimationRefreshCommand.java @@ -0,0 +1,36 @@ +package com.linearpast.sccore.animation.command.client; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import static net.minecraft.commands.Commands.literal; + +@OnlyIn(Dist.CLIENT) +public class AnimationRefreshCommand { + public static void register(LiteralArgumentBuilder animCommand){ + animCommand.then(literal("refresh").executes(AnimationRefreshCommand::refresh)); + } + + private static int refresh(CommandContext ctx){ + CommandSourceStack source = ctx.getSource(); + try { + Minecraft instance = Minecraft.getInstance(); + LocalPlayer player = instance.player; + if(player == null) throw new RuntimeException(); + AnimationUtils.refreshAnimation(player); + source.sendSuccess(() -> Component.literal("Animation refreshed.").withStyle(ChatFormatting.GREEN), true); + } catch (Exception e) { + source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED)); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/data/Animation.java b/src/main/java/com/linearpast/sccore/animation/data/Animation.java new file mode 100644 index 0000000..59306ca --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/data/Animation.java @@ -0,0 +1,120 @@ +package com.linearpast.sccore.animation.data; + +import dev.kosmx.playerAnim.core.data.KeyframeAnimation; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +public class Animation { + private final ResourceLocation name; + private KeyframeAnimation animation; + private float heightModifier = 1.0f; + private float camYaw; + private float camPitch; + private float camRoll; + private float camY; + private @Nullable Animation.LyingType lyingType; + private @Nullable Ride ride; + + public Animation(ResourceLocation name) { + this.name = name; + } + + public enum LyingType { + RIGHT, + LEFT, + FRONT, + BACK + } + + public Animation withLyingType(@Nullable Animation.LyingType lyingType) { + this.lyingType = lyingType; + if(lyingType == null) return this; + this.camY = -1.3f; + this.camPitch = -90.0f; + this.heightModifier = 0.3f; + switch (lyingType) { + case RIGHT -> { + this.camRoll = 90.0f; + this.camYaw = 90.0f; + } + case LEFT -> { + this.camRoll = -90.0f; + this.camYaw = -90.0f; + } + case BACK -> this.camPitch = 90.0f; + } + return this; + } + + public Animation withHeightModifier(float heightModifier) { + this.heightModifier = heightModifier; + return this; + } + + public Animation withCamYaw(float camYaw) { + this.camYaw = camYaw; + return this; + } + + public Animation withCamPitch(float camPitch) { + this.camPitch = camPitch; + return this; + } + + public Animation withCamRoll(float camRoll) { + this.camRoll = camRoll; + return this; + } + + public Animation withCamY(float camY) { + this.camY = camY; + return this; + } + + public Animation withRide(Ride ride) { + this.ride = ride; + return this; + } + + public void setLyingType(@Nullable LyingType lyingType) { + this.lyingType = lyingType; + } + + public float getCamRoll() { + return camRoll; + } + + public float getCamPitch() { + return camPitch; + } + + public float getCamYaw() { + return camYaw; + } + + @Nullable + public KeyframeAnimation getAnimation() { + return PlayerAnimationRegistry.getAnimation(name); + } + + public @Nullable Animation.LyingType getLyingType() { + return lyingType; + } + + public float getCamY() { + return camY; + } + + public float getHeightModifier() { + return heightModifier; + } + + public @Nullable Ride getRide() { + return ride; + } + + public ResourceLocation getName() { + return name; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/data/Ride.java b/src/main/java/com/linearpast/sccore/animation/data/Ride.java new file mode 100644 index 0000000..7ff963a --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/data/Ride.java @@ -0,0 +1,87 @@ +package com.linearpast.sccore.animation.data; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.Vec3; + +import java.util.ArrayList; +import java.util.List; + +public class Ride { + private final List componentAnimations = new ArrayList<>(); + private Vec3 offset = Vec3.ZERO; + private int existTick; + private float xRot; + private float yRot; + + + public static Ride create() { + return new Ride(); + } + + public Ride withOffset(Vec3 offset) { + this.offset = offset; + return this; + } + + public Ride withExistTick(int existTick) { + this.existTick = existTick; + return this; + } + + public Ride withXRot(float xRot) { + this.xRot = xRot; + return this; + } + + public Ride withYRot(float yRot) { + this.yRot = yRot; + return this; + } + + public Ride setComponentAnimations(List animations) { + this.componentAnimations.clear(); + this.componentAnimations.addAll(animations); + return this; + } + + public Ride addComponentAnimation(ResourceLocation animation) { + this.componentAnimations.add(animation); + return this; + } + + public float getXRot() { + return xRot; + } + + public void setXRot(float xRot) { + this.xRot = xRot; + } + + public float getYRot() { + return yRot; + } + + public void setYRot(float yRot) { + this.yRot = yRot; + } + + public Vec3 getOffset() { + return offset; + } + + public void setOffset(Vec3 offset) { + this.offset = offset; + } + + public int getExistTick() { + return existTick; + } + + public void setExistTick(int existTick) { + this.existTick = existTick; + } + + public List getComponentAnimations() { + return componentAnimations; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java b/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java new file mode 100644 index 0000000..72a075f --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java @@ -0,0 +1,213 @@ +package com.linearpast.sccore.animation.entity; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.Ride; +import com.linearpast.sccore.animation.registry.AnimationEntities; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.network.NetworkHooks; +import net.minecraftforge.server.ServerLifecycleHooks; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class AnimationRideEntity extends Entity { + public AnimationRideEntity(Level pLevel) { + super(AnimationEntities.RIDE.get(), pLevel); + this.noPhysics = true; + } + + private static final String Animation = "Animation"; + private static final String PlayerUUID = "PlayerUUID"; + private static final String Layer = "Layer"; + + private final Set players = new HashSet<>(); + private Animation animation; + private ServerPlayer player; + private ResourceLocation layer; + public AnimationRideEntity(ServerPlayer pPlayer, ResourceLocation layer, Animation animation) { + this(pPlayer.level()); + this.player = pPlayer; + this.layer = layer; + this.animation = animation; + } + + public ResourceLocation getLayer() { + return layer; + } + + public Set getPlayers() { + return players; + } + + public Animation getAnimation() { + return animation; + } + + @Override + protected void defineSynchedData() {} + + @Override + protected void readAdditionalSaveData(@NotNull CompoundTag pCompound) { + String string = pCompound.getString(Animation); + if(!string.isEmpty()) { + ResourceLocation rl = new ResourceLocation(string); + Animation anim = AnimationUtils.getAnimation(rl); + if(anim != null) { + this.animation = anim; + } + } + + if(pCompound.contains(PlayerUUID)) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if(server != null) { + this.player = server.getPlayerList().getPlayer(pCompound.getUUID(PlayerUUID)); + } + } + + if(pCompound.contains(Layer)) { + this.layer = new ResourceLocation(pCompound.getString(Layer)); + } + } + + @Override + protected void addAdditionalSaveData(@NotNull CompoundTag pCompound) { + pCompound.putUUID(PlayerUUID, player.getUUID()); + pCompound.putString(Layer, layer.toString()); + if(animation != null) { + pCompound.putString(Animation, animation.getName().toString()); + } + } + + @Override + public void tick() { + super.tick(); + if(!this.level().isClientSide) { + Ride ride = animation == null ? null : animation.getRide(); + if(!this.getPassengers().contains(player) || (ride != null && ride.getExistTick() > 0 && this.tickCount >= ride.getExistTick())) { + if(player != null) { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data != null) { + AnimationUtils.removeAnimation(player, layer); + data.setRideAnimLayer(null); + } + } + if(ride != null) players.forEach(player -> { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + AnimationUtils.removeAnimation(player, layer); + if(data != null) data.setRideAnimLayer(null); + }); + this.remove(RemovalReason.DISCARDED); + } + } + } + + @Override + public double getPassengersRidingOffset() { + return 0.0; + } + + @Override + protected boolean canRide(@NotNull Entity entity) { + return true; + } + + @Override + public boolean shouldRiderSit() { + return false; + } + + @Override + public @NotNull Packet getAddEntityPacket(){ + return NetworkHooks.getEntitySpawningPacket(this); + } + + public static boolean create(ServerPlayer pPlayer, ResourceLocation layer, ResourceLocation animation, boolean force) { + Animation anim = AnimationUtils.getAnimation(animation); + if(anim == null) return false; + if(anim.getRide() == null) return false; + AnimationRideEntity seat = new AnimationRideEntity(pPlayer, layer, anim); + float xRot = anim.getRide().getXRot(); + float yRot = anim.getRide().getYRot(); + if(xRot == 0 && yRot == 0) seat.setRot(pPlayer.getXRot(), pPlayer.getYRot()); + else seat.setRot(yRot, xRot); + Vec3 pos = pPlayer.position(); + pos.add(anim.getRide().getOffset()); + seat.setPos(pos.x, pos.y + 0.35f, pos.z); + pPlayer.level().addFreshEntity(seat); + pPlayer.startRiding(seat, force); + return AnimationUtils.playAnimation(pPlayer, layer, animation); + } + + @Override + protected void positionRider(@NotNull Entity pPassenger, @NotNull MoveFunction pCallback) { + super.positionRider(pPassenger, pCallback); + pPassenger.setYBodyRot(this.getYRot()); + } + + @Override + public void onPassengerTurned(@NotNull Entity pEntityToUpdate) { + pEntityToUpdate.setYBodyRot(this.getYRot()); + } + + @Override + public @NotNull Vec3 getDismountLocationForPassenger(@NotNull LivingEntity entity) { + Ride ride = animation.getRide(); + if(ride != null) { + Vec3 position = entity.position(); + return position.subtract(ride.getOffset()); + } + return entity.position(); + } + + @Override + protected void addPassenger(@NotNull Entity entity) { + int passengerNum = getPassengers().size(); + super.addPassenger(entity); + if(passengerNum == 0) return; + if(entity instanceof ServerPlayer serverPlayer) { + Ride ride = animation.getRide(); + if(ride == null) return; + List componentAnimations = ride.getComponentAnimations(); + if(componentAnimations.isEmpty()) return; + if(passengerNum > componentAnimations.size()) return; + ResourceLocation location = componentAnimations.get(passengerNum - 1); + AnimationUtils.playAnimation(serverPlayer, layer, location); + AnimationUtils.syncAnimation(serverPlayer, player, layer); + players.add(serverPlayer); + } + } + + @Override + protected void removePassenger(@NotNull Entity entity) { + super.removePassenger(entity); + if(entity instanceof ServerPlayer serverPlayer) { + AnimationUtils.removeAnimation(serverPlayer, layer); + AnimationDataCapability.getCapability(serverPlayer).ifPresent(data -> data.setRideAnimLayer(null)); + players.remove(serverPlayer); + } + } + + @Override + protected boolean canAddPassenger(@NotNull Entity pPassenger) { + boolean isServerPlayer = pPassenger instanceof ServerPlayer; + int size = players.size(); + Ride ride = animation.getRide(); + if(ride == null) return false; + int maxSize = ride.getComponentAnimations().size(); + return isServerPlayer && size < maxSize; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/entity/renderer/AnimationRideRenderer.java b/src/main/java/com/linearpast/sccore/animation/entity/renderer/AnimationRideRenderer.java new file mode 100644 index 0000000..7355404 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/entity/renderer/AnimationRideRenderer.java @@ -0,0 +1,33 @@ +package com.linearpast.sccore.animation.entity.renderer; + +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +public class AnimationRideRenderer extends EntityRenderer { + public AnimationRideRenderer(EntityRendererProvider.Context pContext) { + super(pContext); + } + + @Override + public @NotNull ResourceLocation getTextureLocation(@NotNull AnimationRideEntity pEntity) { + return new ResourceLocation(""); + } + + @Override + public boolean shouldRender(@NotNull AnimationRideEntity pLivingEntity, @NotNull Frustum pCamera, double pCamX, double pCamY, double pCamZ) { + return false; + } + + @Override + public void render(@NotNull AnimationRideEntity pEntity, float pEntityYaw, float pPartialTick, @NotNull PoseStack pPoseStack, @NotNull MultiBufferSource pBuffer, int pPackedLight) {} + + @Override + protected void renderNameTag(@NotNull AnimationRideEntity pEntity, @NotNull Component pDisplayName, @NotNull PoseStack pPoseStack, @NotNull MultiBufferSource pBuffer, int pPackedLight) {} +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/AnimationLayerRegistry.java b/src/main/java/com/linearpast/sccore/animation/event/AnimationLayerRegistry.java new file mode 100644 index 0000000..f717c2a --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/AnimationLayerRegistry.java @@ -0,0 +1,49 @@ +package com.linearpast.sccore.animation.event; + +import com.linearpast.sccore.animation.event.create.AnimationLayerRegisterEvent; +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; + +import java.util.HashMap; +import java.util.Map; + +public class AnimationLayerRegistry { + private static final Map animLayers = new HashMap<>(); + + public static void onClientSetup(FMLClientSetupEvent event) { + onCommonSetUp(null); + event.enqueueWork(() -> animLayers.forEach((location, integer) -> + PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(location, integer, + AnimationLayerRegistry::registerPlayerAnimation + ) + )); + } + + public static void onCommonSetUp(FMLCommonSetupEvent event) { + AnimationLayerRegisterEvent layerEvent = new AnimationLayerRegisterEvent(); + ModLoader.get().postEvent(layerEvent); + animLayers.putAll(layerEvent.getLayers()); + } + + public static void registerPlayerAnimation(ResourceLocation location, int priority) { + animLayers.put(location, priority); + } + + public static Map getAnimLayers() { + return animLayers; + } + + public static boolean isLayerPresent(ResourceLocation layer) { + return animLayers.containsKey(layer); + } + + private static IAnimation registerPlayerAnimation(AbstractClientPlayer player) { + return new ModifierLayer<>(); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/EntityRendererRegistry.java b/src/main/java/com/linearpast/sccore/animation/event/EntityRendererRegistry.java new file mode 100644 index 0000000..67b76e9 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/EntityRendererRegistry.java @@ -0,0 +1,11 @@ +package com.linearpast.sccore.animation.event; + +import com.linearpast.sccore.animation.entity.renderer.AnimationRideRenderer; +import com.linearpast.sccore.animation.registry.AnimationEntities; +import net.minecraftforge.client.event.EntityRenderersEvent; + +public class EntityRendererRegistry { + public static void registerEntityRenderer(EntityRenderersEvent.RegisterRenderers event) { + event.registerEntityRenderer(AnimationEntities.RIDE.get(), AnimationRideRenderer::new); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java b/src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java new file mode 100644 index 0000000..38c4996 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java @@ -0,0 +1,68 @@ +package com.linearpast.sccore.animation.event.client; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.data.Animation; +import dev.kosmx.playerAnim.core.util.MathHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.event.ViewportEvent; + +import java.util.Comparator; + +@OnlyIn(Dist.CLIENT) +public class CameraAnglesModify { + private static float targetYaw = 0.0F; + private static float targetPitch = 0.0F; + private static float targetRoll = 0.0F; + private static float currentYaw = 0.0F; + private static float currentPitch = 0.0F; + private static float currentRoll = 0.0F; + + public static void changeCameraView(ViewportEvent.ComputeCameraAngles event){ + Minecraft minecraft = Minecraft.getInstance(); + + if (minecraft.options.getCameraType().isFirstPerson()) { + LocalPlayer player = minecraft.player; + if (player != null) { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) return; + Animation animation = null; + try { + animation = data.getAnimations().values().stream() + .map(AnimationUtils::getAnimation) + .min(Comparator.comparingDouble(anim -> { + if (anim == null) return 1.0f; + return anim.getHeightModifier(); + })).orElse(null); + }catch (Exception ignored){} + + + if(animation != null) { + targetPitch = animation.getCamPitch(); + targetYaw = animation.getCamYaw(); + targetRoll = animation.getCamRoll(); + } else { + targetYaw = 0.0F; + targetPitch = 0.0F; + targetRoll = 0.0F; + } + } + float var3 = Minecraft.getInstance().getDeltaFrameTime(); + float var4 = var3 / 5.0F; + if (var4 == 0.0F) { + var4 = 0.0022857143F; + } + + currentPitch = MathHelper.lerp(var4, currentPitch, targetPitch); + currentYaw = MathHelper.lerp(var4, currentYaw, targetYaw); + currentRoll = MathHelper.lerp(var4, currentRoll, targetRoll); + event.setPitch(event.getPitch() + currentPitch); + event.setYaw(event.getYaw() + currentYaw); + event.setRoll(event.getRoll() + currentRoll); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerTick.java b/src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerTick.java new file mode 100644 index 0000000..2f7c906 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerTick.java @@ -0,0 +1,37 @@ +package com.linearpast.sccore.animation.event.client; + +import com.linearpast.sccore.animation.AnimationUtils; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.event.TickEvent; + +import java.util.HashMap; +import java.util.Map; + +@OnlyIn(Dist.CLIENT) +public class ClientPlayerTick { + public static void onPlayerTick(TickEvent.PlayerTickEvent event) { + if (event.side.isClient() && event.phase == TickEvent.Phase.START) { + Player player = event.player; + if(player.tickCount % 10 != 0) return; + if (!(player instanceof AbstractClientPlayer clientPlayer)) return; + AnimationUtils.refreshAnimation(clientPlayer); + } + } + + public static Map> runs = new HashMap<>(); + public static void delayRuns(TickEvent.PlayerTickEvent event) { + if (event.side.isClient() && event.phase == TickEvent.Phase.END) { + Map.copyOf(runs).forEach((runnable, countMap) -> { + if(countMap.getValue() >= countMap.getKey()) { + runnable.run(); + runs.remove(runnable); + } else { + countMap.setValue(countMap.getValue() + 1); + } + }); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java new file mode 100644 index 0000000..0d99f14 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java @@ -0,0 +1,29 @@ +package com.linearpast.sccore.animation.event.create; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.event.IModBusEvent; + +import java.util.HashMap; +import java.util.Map; + +/** + * You can listen this event to register an animation layer
+ * Generally, the static function is better. + * @see com.linearpast.sccore.animation.AnimationUtils#registerAnimationLayer + */ +public class AnimationLayerRegisterEvent extends Event implements IModBusEvent { + private final Map layers = new HashMap<>(); + + public AnimationLayerRegisterEvent() { + + } + + public Map getLayers() { + return layers; + } + + public void putLayer(ResourceLocation key, Integer value) { + layers.put(key, value); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/mixin/IMixinKeyframeAnimationPlayer.java b/src/main/java/com/linearpast/sccore/animation/mixin/IMixinKeyframeAnimationPlayer.java new file mode 100644 index 0000000..6facf88 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/mixin/IMixinKeyframeAnimationPlayer.java @@ -0,0 +1,9 @@ +package com.linearpast.sccore.animation.mixin; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public interface IMixinKeyframeAnimationPlayer { + void sccore$setCurrentTick(int tick); +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java new file mode 100644 index 0000000..67ce02f --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java @@ -0,0 +1,76 @@ +package com.linearpast.sccore.animation.network.toclient; + +import com.linearpast.sccore.animation.AnimationPlayer; +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync; +import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class AnimationCapabilityPacket extends SimpleCapabilityPacket { + public AnimationCapabilityPacket(CompoundTag data) { + super(data); + } + + public AnimationCapabilityPacket(FriendlyByteBuf buf) { + super(buf); + } + + @Override + public void handler(NetworkEvent.Context context) { + context.setPacketHandled(true); + Minecraft instance = Minecraft.getInstance(); + ClientLevel level = instance.level; + if (level == null) return; + CompoundTag nbt = getData(); + Player player = level.getPlayerByUUID(nbt.getUUID(SimplePlayerCapabilitySync.OwnerUUID)); + try { + IAnimationCapability data = getCapability(player); + testPlayAnimations((AbstractClientPlayer) player, nbt, data); + syncData(nbt, data); + }catch (Exception ignored) {} + } + + @Override + public @Nullable IAnimationCapability getCapability(Player player) { + return AnimationDataCapability.getCapability(player).orElse(null); + } + + private void testPlayAnimations(AbstractClientPlayer player, CompoundTag tag, IAnimationCapability data) { + Set oldLayerSet = new HashSet<>(); + if(data != null) oldLayerSet.addAll(data.getAnimations().keySet()); + CompoundTag animMap = tag.getCompound(AnimationDataCapability.AnimMap); + for (String newLayerString : animMap.getAllKeys()) { + ResourceLocation newLayerLocation = new ResourceLocation(newLayerString); + String newAnimString = animMap.getString(newLayerString); + ResourceLocation newAnimLocation = newAnimString.isEmpty() ? null : new ResourceLocation(newAnimString); + ResourceLocation oldAnimLocation = data == null ? null : data.getAnimation(newLayerLocation); + if (!Objects.equals(newAnimLocation, oldAnimLocation)) { + AnimationPlayer.playAnimation(player, newLayerLocation, newAnimLocation); + } + oldLayerSet.remove(newLayerLocation); + } + for (ResourceLocation oldLayerLocation : oldLayerSet) { + AnimationPlayer.playAnimation(player, oldLayerLocation, null); + } + + if(!tag.contains(AnimationDataCapability.RideAnimLayer)) { + if(data != null && data.getRideAnimLayer() != null) { + AnimationUtils.playAnimation(null, data.getRideAnimLayer(), null); + } + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java new file mode 100644 index 0000000..46f0cfa --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java @@ -0,0 +1,54 @@ +package com.linearpast.sccore.animation.network.toclient; + +import com.linearpast.sccore.animation.AnimationPlayer; +import com.linearpast.sccore.animation.event.client.ClientPlayerTick; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkEvent; + +import java.util.AbstractMap; +import java.util.UUID; +import java.util.function.Supplier; + +public class SyncAnimationPacket { + private final UUID playerUUID; + private final UUID targetUUID; + private final ResourceLocation layer; + public SyncAnimationPacket(UUID playerUUID, UUID targetUUID, ResourceLocation layer) { + this.playerUUID = playerUUID; + this.targetUUID = targetUUID; + this.layer = layer; + } + + public SyncAnimationPacket(FriendlyByteBuf buf) { + this.playerUUID = buf.readUUID(); + this.targetUUID = buf.readUUID(); + this.layer = buf.readResourceLocation(); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeUUID(this.playerUUID); + buf.writeUUID(this.targetUUID); + buf.writeResourceLocation(this.layer); + } + + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + Minecraft instance = Minecraft.getInstance(); + ClientLevel level = instance.level; + if(level == null) return; + AbstractClientPlayer player = (AbstractClientPlayer) level.getPlayerByUUID(playerUUID); + AbstractClientPlayer target = (AbstractClientPlayer) level.getPlayerByUUID(targetUUID); + ClientPlayerTick.runs.put( + () -> AnimationPlayer.syncAnimation(player, target, layer), + new AbstractMap.SimpleEntry<>(5, 0) + ); + + }); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java new file mode 100644 index 0000000..965456d --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java @@ -0,0 +1,46 @@ +package com.linearpast.sccore.animation.network.toserver; + +import com.linearpast.sccore.animation.AnimationPlayer; +import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +public class PlayAnimationRequestPacket { + private final ResourceLocation layer; + private @Nullable ResourceLocation animation; + public PlayAnimationRequestPacket(ResourceLocation layer, @Nullable ResourceLocation animation) { + this.layer = layer; + this.animation = animation; + } + public PlayAnimationRequestPacket(FriendlyByteBuf buf){ + this.layer = buf.readResourceLocation(); + try { + this.animation = buf.readResourceLocation(); + } catch (Exception e) { + this.animation = null; + } + } + public void encode(FriendlyByteBuf buf){ + buf.writeResourceLocation(layer); + if(animation != null){ + buf.writeResourceLocation(animation); + } + } + + public void handle(Supplier supplier){ + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) { + ServerPlayer sender = context.getSender(); + if(sender == null) return; + AnimationPlayer.serverPlayAnimation(sender, layer, animation); + } + }); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java new file mode 100644 index 0000000..c124f46 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java @@ -0,0 +1,54 @@ +package com.linearpast.sccore.animation.network.toserver; + +import com.linearpast.sccore.animation.AnimationPlayer; +import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +public class PlayAnimationRidePacket { + private final ResourceLocation layer; + private @Nullable ResourceLocation animation; + private final boolean force; + public PlayAnimationRidePacket(ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) { + this.layer = layer; + this.animation = animation; + this.force = force; + } + + public PlayAnimationRidePacket(FriendlyByteBuf buf) { + this.layer = buf.readResourceLocation(); + this.force = buf.readBoolean(); + try { + this.animation = buf.readResourceLocation(); + } catch (Exception e) { + this.animation = null; + } + + } + + public void encode(FriendlyByteBuf buf) { + buf.writeResourceLocation(layer); + buf.writeBoolean(force); + if(animation != null) { + buf.writeResourceLocation(animation); + } + } + + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) { + ServerPlayer sender = context.getSender(); + if(sender == null) return; + AnimationPlayer.playAnimationWithRide(sender, layer, animation, force); + } + }); + } + +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/RefreshAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/RefreshAnimationPacket.java new file mode 100644 index 0000000..ca7811d --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/RefreshAnimationPacket.java @@ -0,0 +1,45 @@ +package com.linearpast.sccore.animation.network.toserver; + +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +public class RefreshAnimationPacket { + private final Set needRemoves; + public RefreshAnimationPacket(Set needRemoves) { + this.needRemoves = needRemoves; + } + public RefreshAnimationPacket(FriendlyByteBuf buf) { + int i = buf.readInt(); + needRemoves = new HashSet<>(); + for (int j = 0; j < i; ++j) { + needRemoves.add(buf.readResourceLocation()); + } + } + + public void encode(FriendlyByteBuf buf) { + buf.writeInt(needRemoves.size()); + for (ResourceLocation needRemove : needRemoves) { + buf.writeResourceLocation(needRemove); + } + } + + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + ServerPlayer sender = context.getSender(); + if(sender == null) return; + IAnimationCapability data = AnimationDataCapability.getCapability(sender).orElse(null); + if(data == null) return; + needRemoves.forEach(data::removeAnimation); + }); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/registry/AnimationEntities.java b/src/main/java/com/linearpast/sccore/animation/registry/AnimationEntities.java new file mode 100644 index 0000000..553892d --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/registry/AnimationEntities.java @@ -0,0 +1,33 @@ +package com.linearpast.sccore.animation.registry; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + + + +public class AnimationEntities { + public static final DeferredRegister> REGISTER = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, SnowyCrescentCore.MODID); + + public static final RegistryObject> RIDE = register( + "animation_ride_entity", EntityType.Builder.of((type, world) -> new AnimationRideEntity(world), MobCategory.MISC) + .sized(0.0F, 0.0F) + .setCustomClientFactory((spawnEntity, world) -> + new AnimationRideEntity(world) + ) + ); + + private static RegistryObject> register(String name, EntityType.Builder builder) { + return REGISTER.register(name, () -> builder.build(name)); + } + + public static void register(IEventBus modBus){ + REGISTER.register(modBus); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/registry/AnimationRegistry.java b/src/main/java/com/linearpast/sccore/animation/registry/AnimationRegistry.java new file mode 100644 index 0000000..451056c --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/registry/AnimationRegistry.java @@ -0,0 +1,98 @@ +package com.linearpast.sccore.animation.registry; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.network.toclient.AnimationCapabilityPacket; +import com.linearpast.sccore.animation.network.toclient.SyncAnimationPacket; +import com.linearpast.sccore.animation.network.toserver.PlayAnimationRequestPacket; +import com.linearpast.sccore.animation.network.toserver.PlayAnimationRidePacket; +import com.linearpast.sccore.animation.network.toserver.RefreshAnimationPacket; +import com.linearpast.sccore.capability.CapabilityUtils; +import com.linearpast.sccore.capability.data.player.PlayerCapabilityRegistry; +import com.linearpast.sccore.capability.network.CapabilityChannel; +import com.linearpast.sccore.core.ModChannel; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.network.NetworkDirection; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class AnimationRegistry { + private static final Map animations = new HashMap<>(); + + public static Map getAnimations() { + return animations; + } + + @Nullable + public static Animation getAnimation(ResourceLocation location) { + return animations.get(location); + } + + public static void registerAnimation(ResourceLocation location, Animation animation) { + animations.put(location, animation); + } + + public static boolean isAnimationPresent(ResourceLocation location) { + return animations.containsKey(location); + } + + public static void addAnimationListener(IEventBus forgeBus, IEventBus modBus) { + AnimationUtils.ANIMATION_RUNNER.testLoadedAndAddListener(forgeBus, modBus); + } + + private static void registerAnimationCapability() { + CapabilityChannel channel = CapabilityUtils.createChannel(); + CapabilityUtils.registerPlayerCapabilityWithNetwork( + AnimationDataCapability.key, + new PlayerCapabilityRegistry.CapabilityRecord<>( + AnimationDataCapability.class, + CapabilityManager.get(new CapabilityToken<>() {}), + IAnimationCapability.class + ), + channel, + ModChannel.getAndAddCid(), + AnimationCapabilityPacket.class, + AnimationCapabilityPacket::new, + AnimationCapabilityPacket::encode, + AnimationCapabilityPacket::handle + ); + } + + private static void registerChannel() { + ModChannel.INSTANCE.messageBuilder(SyncAnimationPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_CLIENT) + .decoder(SyncAnimationPacket::new) + .encoder(SyncAnimationPacket::encode) + .consumerMainThread(SyncAnimationPacket::handle) + .add(); + + ModChannel.INSTANCE.messageBuilder(PlayAnimationRequestPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER) + .decoder(PlayAnimationRequestPacket::new) + .encoder(PlayAnimationRequestPacket::encode) + .consumerMainThread(PlayAnimationRequestPacket::handle) + .add(); + ModChannel.INSTANCE.messageBuilder(PlayAnimationRidePacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER) + .decoder(PlayAnimationRidePacket::new) + .encoder(PlayAnimationRidePacket::encode) + .consumerMainThread(PlayAnimationRidePacket::handle) + .add(); + ModChannel.INSTANCE.messageBuilder(RefreshAnimationPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER) + .decoder(RefreshAnimationPacket::new) + .encoder(RefreshAnimationPacket::encode) + .consumerMainThread(RefreshAnimationPacket::handle) + .add(); + } + + public static void register(){ + AnimationUtils.ANIMATION_RUNNER.testLoadedAndRun(() -> { + registerAnimationCapability(); + registerChannel(); + }); + } +} diff --git a/src/main/java/com/linearpast/sccore/capability/CapabilityUtils.java b/src/main/java/com/linearpast/sccore/capability/CapabilityUtils.java index d5251e7..4dcd3d5 100644 --- a/src/main/java/com/linearpast/sccore/capability/CapabilityUtils.java +++ b/src/main/java/com/linearpast/sccore/capability/CapabilityUtils.java @@ -7,7 +7,7 @@ import com.linearpast.sccore.capability.data.player.PlayerCapabilityHandler; import com.linearpast.sccore.capability.data.player.PlayerCapabilityRegistry; import com.linearpast.sccore.capability.network.CapabilityChannel; import com.linearpast.sccore.capability.network.ICapabilityPacket; -import com.linearpast.sccore.network.Channel; +import com.linearpast.sccore.core.ModChannel; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; @@ -24,15 +24,16 @@ import java.util.function.Supplier; public class CapabilityUtils { /** - * 同时注册玩家capability和对应的网络包 - * @param key capability的唯一name - * @param capabilityRecord capability的注册数据 - * @param channelRegister 应该提前创建好实例传入,参阅{@link CapabilityUtils#createChannel} - * @param cid 网络频道索引 - * @param clazz 网络包的类 - * @param decoder 网络包的decode - * @param encoder 网络包的encode - * @param handler 网络包的handle + * Simultaneously register player capability and corresponding network packets + * @param key The unique name of capability + * @param capabilityRecord Registration data for capability + * @param channelRegister You should create an instance in advance to pass in, see: {@link CapabilityUtils#createChannel} + * @param cid Network Channel Index + * @param clazz Class of network packets + * @param decoder Decoding of network packets + * @param encoder Encoding of network packets + * @param handler Handle of network packets + * @param extend {@code ICapabilityPacket} */ public static > void registerPlayerCapabilityWithNetwork( ResourceLocation key, PlayerCapabilityRegistry.CapabilityRecord> capabilityRecord, @@ -48,15 +49,16 @@ public class CapabilityUtils { } /** - * 同时注册实体capability和对应的网络包 - * @param key capability的唯一name - * @param capabilityRecord capability的注册数据 - * @param channelRegister 应该提前创建好实例传入,参阅{@link CapabilityUtils#createChannel} - * @param cid 网络频道索引 - * @param clazz 网络包的类 - * @param decoder 网络包的decode - * @param encoder 网络包的encode - * @param handler 网络包的handle + * Simultaneously register entity capability and corresponding network packets + * @param key The unique name of capability + * @param capabilityRecord Registration data for capability + * @param channelRegister You should create an instance in advance to pass in, see: {@link CapabilityUtils#createChannel} + * @param cid Network Channel Index + * @param clazz Class of network packets + * @param decoder Decoding of network packets + * @param encoder Encoding of network packets + * @param handler Handle of network packets + * @param {@code ICapabilityPacket} */ public static > void registerEntityCapabilityWithNetwork( ResourceLocation key, EntityCapabilityRegistry.CapabilityRecord> capabilityRecord, @@ -72,46 +74,47 @@ public class CapabilityUtils { } /** - * 通过此方法注册玩家capability,仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent} - * 事件结束之前有效 - * @param key capability的唯一name - * @param capabilityRecord 使用record存储了应该注册的capability的各项数据,参阅:{@link PlayerCapabilityRegistry.CapabilityRecord} + * See {@link PlayerCapabilityRegistry#registerCapability(ResourceLocation, PlayerCapabilityRegistry.CapabilityRecord)} + * @param key The unique name of capability. + * @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link PlayerCapabilityRegistry.CapabilityRecord} + * @param extends {@code ICapabilitySync} */ public static > void registerPlayerCapability(ResourceLocation key, PlayerCapabilityRegistry.CapabilityRecord capabilityRecord){ PlayerCapabilityRegistry.registerCapability(key, capabilityRecord); } /** - * 通过此方法注册实体capability,仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent} - * 事件结束之前有效 - * @param key capability的唯一name - * @param capabilityRecord 使用record存储了应该注册的capability的各项数据,参阅:{@link PlayerCapabilityRegistry.CapabilityRecord} + * See {@link EntityCapabilityRegistry#registerCapability(ResourceLocation, EntityCapabilityRegistry.CapabilityRecord)} + * @param key The unique name of capability. + * @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link EntityCapabilityRegistry.CapabilityRecord} + * @param extends {@code ICapabilitySync} */ public static > void registerEntityCapability(ResourceLocation key, EntityCapabilityRegistry.CapabilityRecord capabilityRecord){ EntityCapabilityRegistry.registerCapability(key, capabilityRecord); } /** - * 通过这个方法返回一个新的PlayerCapabilityChannel实例,一般只有注册不同channel的网络包才会使用 - * @param channel 你自己模组的channel - * @return 新的实例 + * Return a new PlayerCapabilityChannel instance through this method
+ * Generally, only network packets registered on different channels will be used + * @param channel Your own mod channel + * @return newInstances */ public static CapabilityChannel createChannel(SimpleChannel channel) { return new CapabilityChannel(channel); } /** - * 通过这个方法返回本模组的Channel的PlayerCapabilityChannel实例 - * @return 新的实例 + * Return the PlayerCapabilityChannel instance of the Channel in SCCore through this method + * @return newInstances */ public static CapabilityChannel createChannel() { - return new CapabilityChannel(Channel.INSTANCE); + return new CapabilityChannel(ModChannel.INSTANCE); } /** - * 通过此方法监听capability事件,从而启用所有功能
- * 重复调用不会发生任何事 - * @param forgeBus forge事件总线 + * By using this method to listen for capability events, all functions will be enabled
+ * Repeated calls will not cause anything + * @param forgeBus Forge event bus */ public static void registerHandler(IEventBus forgeBus){ PlayerCapabilityHandler.register(forgeBus); @@ -119,11 +122,13 @@ public class CapabilityUtils { } /** - * 请通过该方法获取capability - * @param entity 目标实体,类型 {@code } - * @param key capability的唯一名 - * @param clazz 应返回的capability类型,若为null则会返回 {@code ICapabilitySync} - * @return 返回对应的capability + * Please obtain capability through this method + * @param entity Target entity, type: {@code } + * @param key The unique name of capability + * @param clazz The capability type that should be returned. If it is null, will return: {@code ICapabilitySync} + * @param extend {@code Entity} + * @param extend {@code ICapabilitySync} + * @return Return the corresponding capability */ @SuppressWarnings("unchecked") @Nullable @@ -142,11 +147,13 @@ public class CapabilityUtils { } /** - * 请通过该方法获取capability - * @param entity 目标实体,类型 {@code } - * @param key capability的唯一名 - * @param clazz 应返回的capability类型,若为null则会返回 {@code ICapabilitySync} - * @return 返回对应的capability + * Please obtain capability through this method + * @param entity Target entity, type: {@code } + * @param key The unique name of capability + * @param clazz The capability type that should be returned. If it is null, will return: {@code ICapabilitySync} + * @param extend {@code Player} + * @param extend {@code ICapabilitySync} + * @return Return the corresponding capability */ @SuppressWarnings("unchecked") @Nullable @@ -165,10 +172,10 @@ public class CapabilityUtils { } /** - * 获取一个未转换类型的Cap - * @param entity 目标 - * @param key cap的唯一名 - * @return 未转换类型的cap + * Get a capability of an unconverted type + * @param entity Target + * @param key The unique name of capability + * @return An unconverted capability */ @Nullable public static ICapabilitySync getCapability(Entity entity, ResourceLocation key) { diff --git a/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java b/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java index 7c03d9c..e822143 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java +++ b/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java @@ -1,23 +1,25 @@ package com.linearpast.sccore.capability.data; import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; -import com.linearpast.sccore.network.Channel; +import com.linearpast.sccore.core.ModChannel; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraftforge.common.util.INBTSerializable; public interface ICapabilitySync extends INBTSerializable { + /** + * You should call it to set it to true in the setter of each property to trigger automatic synchronization + * @param dirty dirty + */ void setDirty(boolean dirty); + boolean isDirty(); - CompoundTag toTag(CompoundTag tag); - void fromTag(CompoundTag tag); - /** - * 该方法重写时应该在最后调用super方法 - * @param oldData 旧数据 - * @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()} + * When this method is overridden, the super method should be called at the end + * @param oldData old data + * @param listenDone Whether to execute the completion method at the end: {@link ICapabilitySync#onCopyDone()} */ default void copyFrom(ICapabilitySync oldData, boolean listenDone) { this.setDirty(oldData.isDirty()); @@ -25,40 +27,40 @@ public interface ICapabilitySync extends INBTSerializable - * 多用于玩家 跨越维度/死亡 时重置数据 + * After the copy is completed, if certain values need to be redefined, you should override this method
+ * Commonly used for resetting data when players cross dimensions or die */ default void onCopyDone(){} /** - * 一般情况下建议重写,否则会以sccore的Channel实例发送
- * 服务端给全体玩家发送客户端同步数据 + * In general, it is recommended to rewrite it, otherwise it will be sent as a Channel instance of SCCore
+ * The server sends client synchronized data to all players */ default void sendToClient(){ - Channel.sendAllPlayer(getDefaultPacket()); + ModChannel.sendAllPlayer(getDefaultPacket()); } /** - * 一般情况下建议重写,否则会以sccore的Channel实例发送
- * 服务端给单个玩家发送客户端同步数据 - * @param player 发送给的目标玩家 + * In general, it is recommended to rewrite it, otherwise it will be sent as a Channel instance of SCCore
+ * The server sends client synchronized data to a single player + * @param player Target player */ default void sendToClient(ServerPlayer player){ - Channel.sendToPlayer(getDefaultPacket(), player); + ModChannel.sendToPlayer(getDefaultPacket(), player); } /** - * 重写该方法为你的Capability设定一个网络包类,目前仅有客户端
- * 当调用sendToClient方法时会从这里获取网络包直接发送
- * 一般情况下,你应该extends SimpleCapabilityPacket然后重写该方法返回你的子类 - * @return 网络包类SimpleCapabilityPacket + * Rewrite this method to set a network packet class for your Capability
+ * When calling the sendToClient method, network packets will be obtained from here and sent directly
+ * In general, you should extend SimpleCapacityPackage and then override the method to return your subclass + * @return SimpleCapacityPacket, a network packet class */ SimpleCapabilityPacket getDefaultPacket(); /** - * 当玩家登录 / 实体加入世界时的cao初始化时会调用
- * 必须实现,但可为空方法 - * @param entity 目标 + * When players log in or entity join the world, the capability initialization will be called
+ * Must be implemented, but can be an empty method + * @param entity Target */ void attachInit(T entity); } diff --git a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityHandler.java b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityHandler.java index f45ca5f..bb05f05 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityHandler.java +++ b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityHandler.java @@ -18,8 +18,10 @@ public class EntityCapabilityHandler { private static boolean isRegistered = false; /** - * 应在Forge主线中调用以监听capability注册
- * 建议在Mod构造方法里调用 + * It should be called in the Forge mainline to listen to the capability registration
+ * Suggest calling it in the Mod constructor method
+ * Normally SCCore will call it, so you should not call it + * @param forgeBus forge event bus */ public static void register(IEventBus forgeBus) { if (isRegistered) return; @@ -30,7 +32,10 @@ public class EntityCapabilityHandler { isRegistered = true; } - //注册 capability + /** + * Register capability + * @param event event + */ @SubscribeEvent(priority = EventPriority.HIGHEST) public static void registerCapability(RegisterCapabilitiesEvent event) { EntityCapabilityRegistry.getCapabilityMap().values().forEach(record -> @@ -38,20 +43,24 @@ public class EntityCapabilityHandler { ); } - //附加 capability + /** + * Attach capability to entity + * @param event event + */ @SubscribeEvent(priority = EventPriority.HIGHEST) public static void attachCapability(AttachCapabilitiesEvent event) { if(event.getObject() instanceof Entity entity) { - EntityCapabilityRegistry.getCapabilityMap().forEach((key, record) -> { - if(record.target().isInstance(entity)) { - try { - ICapabilitySync capabilitySync = (ICapabilitySync) record.aClass().getDeclaredConstructor().newInstance(); - event.addCapability(key, new EntityCapabilityProvider<>(key, capabilitySync)); - } catch (Exception e) { - log.error("Failed to instantiate capability sync class {}. Your capability register is wrong.", record.aClass(), e); - } - } - }); + EntityCapabilityRegistry.getCapabilityMap().forEach((key, record) -> + record.targets().forEach(target -> { + if(target.isInstance(entity)) { + try { + ICapabilitySync capabilitySync = (ICapabilitySync) record.aClass().getDeclaredConstructor().newInstance(); + event.addCapability(key, new EntityCapabilityProvider<>(key, capabilitySync)); + } catch (Exception e) { + log.error("Failed to instantiate capability sync class {}. Your capability register is wrong.", record.aClass(), e); + } + } + })); } } } diff --git a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityProvider.java b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityProvider.java index c1c69e3..4d27acc 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityProvider.java +++ b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityProvider.java @@ -13,13 +13,19 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * cap的最终 序列化、反序列化、获取方法 - * @param 继承 {@link ICapabilitySync} + * The final serialization, deserialization, and retrieval methods of capability + * @param extends {@link ICapabilitySync} */ @AutoRegisterCapability public class EntityCapabilityProvider> implements ICapabilitySerializable { private final C instance; private final ResourceLocation resourceLocation; + + /** + * Constructor + * @param resourceLocation key + * @param instance instance + */ public EntityCapabilityProvider(ResourceLocation resourceLocation, C instance) { this.resourceLocation = resourceLocation; this.instance = instance; diff --git a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRegistry.java b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRegistry.java index 547a6ec..960536a 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRegistry.java +++ b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRegistry.java @@ -7,46 +7,47 @@ import net.minecraftforge.common.capabilities.Capability; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class EntityCapabilityRegistry { public static final EntityCapabilityRegistry CAPABILITIES = new EntityCapabilityRegistry(); private final Map> capabilityRecordMap = new HashMap<>(); /** - * 通过此方法注册capability,仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent} - * 事件结束之前有效 - * @param key capability的唯一name - * @param capabilityRecord 使用record存储了应该注册的capability的各项数据,参阅:{@link CapabilityRecord} + * Registering entity capabilities through this method only applies to {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
+ * @param key The unique name of capability. + * @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link EntityCapabilityRegistry.CapabilityRecord} */ public static void registerCapability(ResourceLocation key, CapabilityRecord capabilityRecord) { CAPABILITIES.capabilityRecordMap.put(key, capabilityRecord); } /** - * 通过此方法获取对应的capability数据 - * @param key 根据key获取 - * @return capability 数据 + * Obtain corresponding capability data through this method + * @param key Obtain based on key + * @return capability */ public static CapabilityRecord getCapabilityRecord(ResourceLocation key){ return CAPABILITIES.capabilityRecordMap.get(key); } - /** - * 获取所有key对应的cap数据集 - * @return map - */ public static Map> getCapabilityMap(){ return CAPABILITIES.capabilityRecordMap; } /** - * 记录capability的注册数据 - * @param aClass 最终会附加给实体的实例的类,应该是实现了clazz的类 - * @param capability 注册时一般默认{@code CapabilityManager.get(new CapabilityToken<>(){})}即可 - * @param interfaceClass instance类对应的实例对应的接口类,比如ICapabilitySync.class - * @param target capability附加的目标类型 + * Record the registration data of capability + * @param aClass The instance that will ultimately be attached to the entity. Should be an instance of ICapabilitySync + * @param capability In general, it is not necessary to initialize it, default: {@code CapabilityManager.get(new CapabilityToken<>(){})} + * @param interfaceClass The interface class corresponding to the instance, such as: ICapabilitySync.class. + * @param targets Targets types attached to capability */ - public record CapabilityRecord>(Class aClass, Capability capability, Class interfaceClass, Class target) { + public record CapabilityRecord>( + Class aClass, + Capability capability, + Class interfaceClass, + Set> targets + ) { } } diff --git a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRemainder.java b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRemainder.java index 9936631..d46944c 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRemainder.java +++ b/src/main/java/com/linearpast/sccore/capability/data/entity/EntityCapabilityRemainder.java @@ -12,10 +12,10 @@ import net.minecraftforge.event.entity.player.PlayerEvent; public class EntityCapabilityRemainder { /** - * 玩家追踪实体事件
- * 当有其他实体被加载时,客户端需要对方的capability,该事件可以主动发送
- * 会调用{@link ICapabilitySync#sendToClient(ServerPlayer)} - * @param event 追踪事件实例 + * Player start tracking an entity event
+ * When other entities are loaded, the client requires the capabilities of the other party, and this event can be actively sent
+ * Will call{@link ICapabilitySync#sendToClient(ServerPlayer)} + * @param event event */ public static void onEntityBeTracked(PlayerEvent.StartTracking event) { if (event.getEntity() instanceof ServerPlayer attacker) { @@ -28,10 +28,10 @@ public class EntityCapabilityRemainder { } /** - * 实体Tick事件
- * 如果capability是dirty的,就会调用{@link ICapabilitySync#sendToClient()}
- * 为了性能,每秒才触发一次同步 - * @param event 事件实例 + * Entity Tick Event
+ * If the capability is dirty, it will call {@link ICapabilitySync#sendToClient()}
+ * For performance reasons, synchronization is only triggered once per second + * @param event event */ public static void capabilitySync(LivingEvent.LivingTickEvent event) { LivingEntity entity = event.getEntity(); @@ -50,8 +50,8 @@ public class EntityCapabilityRemainder { } /** - * 实体加入level的事件,初始化 - * @param event 实体加入事件 + * Event of entity joining level, initialization + * @param event event */ public static void onEntityJoin(EntityJoinLevelEvent event) { Entity entity = event.getEntity(); diff --git a/src/main/java/com/linearpast/sccore/capability/data/entity/SimpleEntityCapabilitySync.java b/src/main/java/com/linearpast/sccore/capability/data/entity/SimpleEntityCapabilitySync.java index 1eb0dd5..1061ecb 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/entity/SimpleEntityCapabilitySync.java +++ b/src/main/java/com/linearpast/sccore/capability/data/entity/SimpleEntityCapabilitySync.java @@ -5,9 +5,9 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.Entity; /** - * 实现时建议手动添加:
- * key —— 作为cap的唯一标识
- * getCapability(Entity entity) —— 获取cap的简化方法
+ * It is recommended to manually add it during implementation:
+ * {@code key} ---- As the unique identifier of capability.
+ * {@code getCapability(Entity entity)} ---- Simplified method for obtaining capability
* 例: *

  * {@code
@@ -23,6 +23,9 @@ import net.minecraft.world.entity.Entity;
  *
  */
 public abstract class SimpleEntityCapabilitySync implements ICapabilitySync {
+    /**
+     * Id
+     */
     public static final String Id = "Id";
 
     private boolean dirty;
@@ -34,28 +37,36 @@ public abstract class SimpleEntityCapabilitySync implements IC
     }
 
     /**
-     * 你应该在每个属性的setter里调用它设置为true,以触发自动同步
-     * @param dirty 是否应该同步(是否为脏)
+     * You should call it to set it to true in the setter of each property to trigger automatic synchronization
+     * @param dirty dirty
      */
     @Override
     public void setDirty(boolean dirty) {
         this.dirty = dirty;
     }
 
+    /**
+     * get id
+     * @return Id
+     */
     public Integer getId() {
         return id;
     }
 
+    /**
+     * set id
+     * @param id id
+     */
     public void setId(Integer id) {
         this.id = id;
         setDirty(true);
     }
 
     /**
-     * 从参数实例中复制数据到当前实例 
- * 你不应该重写它,你应该实现 {@link SimpleEntityCapabilitySync#copyFrom(ICapabilitySync)} - * @param oldData 旧数据 - * @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()} + * Copy data from parameter instance to current instance
+ *You shouldn't rewrite it, you should implement: {@link SimpleEntityCapabilitySync#copyFrom(ICapabilitySync)} + * @param oldData old data + * @param listenDone Whether to execute the completion method at the end: {@link ICapabilitySync#onCopyDone()} */ @Override public void copyFrom(ICapabilitySync oldData, boolean listenDone) { @@ -66,14 +77,14 @@ public abstract class SimpleEntityCapabilitySync implements IC } /** - * 触发数据复制时会执行的方法 - * @param oldData 从这个数据中复制到当前实例 + * The method that will be executed when triggering data replication + * @param oldData Copy from this data to the current instance */ public abstract void copyFrom(ICapabilitySync oldData); /** - * 序列化为tag
- * 你不应该重写它,你应该实现{@link ICapabilitySync#toTag(CompoundTag)} + * Serialize to tag
+ * You shouldn't rewrite it, you should implement: {@link SimpleEntityCapabilitySync#toTag(CompoundTag)} * @return tag */ @Override @@ -85,8 +96,8 @@ public abstract class SimpleEntityCapabilitySync implements IC } /** - * 反序列化为实例对象
- * 你应该不需要重写它,你应该实现{@link ICapabilitySync#fromTag(CompoundTag)} + * Deserialize to instance object
+ * You don't need to rewrite it, you should implement: {@link SimpleEntityCapabilitySync#fromTag(CompoundTag)} * @param nbt nbt */ @Override @@ -95,4 +106,19 @@ public abstract class SimpleEntityCapabilitySync implements IC if(nbt.contains(Id)) this.id = nbt.getInt(Id); fromTag(nbt); } + + /** + * In the serializeNBT method of SimpleElementCapability Sync, it will be called
+ * Actually equivalent to serializeNBT() + * @param tag data tag + * @return tag + */ + public abstract CompoundTag toTag(CompoundTag tag); + + /** + * In the deserializeNBT method of SimpleElementCapability Sync, it will be called
+ * Actually equivalent to deserializeNBT() + * @param tag data tag + */ + public abstract void fromTag(CompoundTag tag); } diff --git a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityHandler.java b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityHandler.java index 5f58d25..c38565d 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityHandler.java +++ b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityHandler.java @@ -18,8 +18,10 @@ public class PlayerCapabilityHandler { private static boolean isRegistered = false; /** - * 应在Forge主线中调用以监听capability注册
- * 建议在Mod构造方法里调用 + * It should be called in the Forge mainline to listen to the capability registration
+ * Suggest calling it in the Mod constructor method
+ * Normally SCCore will call it, so you should not call it + * @param forgeBus forge event bus */ public static void register(IEventBus forgeBus) { if (isRegistered) return; @@ -32,7 +34,10 @@ public class PlayerCapabilityHandler { isRegistered = true; } - //注册 capability + /** + * Register capability + * @param event event + */ @SubscribeEvent(priority = EventPriority.HIGHEST) public static void registerCapability(RegisterCapabilitiesEvent event) { PlayerCapabilityRegistry.getCapabilityMap().values().forEach(record -> @@ -40,7 +45,10 @@ public class PlayerCapabilityHandler { ); } - //附加 capability + /** + * Attach capability to entity + * @param event event + */ @SubscribeEvent(priority = EventPriority.HIGHEST) public static void attachCapability(AttachCapabilitiesEvent event) { if(event.getObject() instanceof Player) { diff --git a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityProvider.java b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityProvider.java index 607f40d..5e3b2e7 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityProvider.java +++ b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityProvider.java @@ -11,10 +11,20 @@ import net.minecraftforge.common.util.LazyOptional; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +/** + * The final serialization, deserialization, and retrieval methods of capability + * @param extends {@link ICapabilitySync} + */ @AutoRegisterCapability public class PlayerCapabilityProvider> implements ICapabilitySerializable { private final C instance; private final ResourceLocation resourceLocation; + + /** + * Constructor + * @param resourceLocation key + * @param instance instance + */ public PlayerCapabilityProvider(ResourceLocation resourceLocation, C instance) { this.resourceLocation = resourceLocation; this.instance = instance; diff --git a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRegistry.java b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRegistry.java index 8a5c85a..a8837ef 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRegistry.java +++ b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRegistry.java @@ -13,19 +13,18 @@ public class PlayerCapabilityRegistry { private final Map> capabilityRecordMap = new HashMap<>(); /** - * 通过此方法注册capability,仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent} - * 事件结束之前有效 - * @param key capability的唯一name - * @param capabilityRecord 使用record存储了应该注册的capability的各项数据,参阅:{@link CapabilityRecord} + * Registering player capabilities through this method only applies to {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
+ * @param key The unique name of capability. + * @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link PlayerCapabilityRegistry.CapabilityRecord} */ public static void registerCapability(ResourceLocation key, CapabilityRecord capabilityRecord) { CAPABILITIES.capabilityRecordMap.put(key, capabilityRecord); } /** - * 通过此方法获取对应的capability数据 - * @param key 根据key获取 - * @return capability 数据 + * Obtain corresponding capability data through this method + * @param key Obtain based on key + * @return capability */ public static CapabilityRecord getCapabilityRecord(ResourceLocation key){ return CAPABILITIES.capabilityRecordMap.get(key); @@ -36,10 +35,10 @@ public class PlayerCapabilityRegistry { } /** - * 记录capability的注册数据 - * @param aClass 最终会附加给玩家的实例,应该是ICapabilitySync的实例 - * @param capability 一般情况下不需要初始化它,默认:CapabilityManager.get(new CapabilityToken<>(){}) - * @param interfaceClass instance实例对应的接口类,比如ICapabilitySync.class + * Record the registration data of capability + * @param aClass The instance that will ultimately be attached to the player. Should be an instance of ICapabilitySync + * @param capability In general, it is not necessary to initialize it, default: {@code CapabilityManager.get(new CapabilityToken<>(){})} + * @param interfaceClass The interface class corresponding to the instance, such as: ICapabilitySync.class. */ public record CapabilityRecord>(Class aClass, Capability capability, Class interfaceClass) { } } diff --git a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRemainder.java b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRemainder.java index d1dad4a..4304f5f 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRemainder.java +++ b/src/main/java/com/linearpast/sccore/capability/data/player/PlayerCapabilityRemainder.java @@ -2,22 +2,15 @@ package com.linearpast.sccore.capability.data.player; import com.linearpast.sccore.capability.CapabilityUtils; import com.linearpast.sccore.capability.data.ICapabilitySync; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.players.PlayerList; import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; -import java.util.Optional; - -/** - * 用于维护数据同步 - */ public class PlayerCapabilityRemainder { /** - * 玩家跨越维度/死亡时应该转移数据到新身体上 - * @param event Clone事件 + * Players should transfer data to a new body when crossing dimensions or dying + * @param event event */ public static void onPlayerClone(PlayerEvent.Clone event) { Player entity = event.getEntity(); @@ -37,8 +30,8 @@ public class PlayerCapabilityRemainder { } /** - * 玩家重生时应该更新自己的capability - * @param event 重生事件实例 + * Players should update their capabilities when they are reborn + * @param event event */ public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) { if(event.getEntity() instanceof ServerPlayer newPlayer){ @@ -51,10 +44,10 @@ public class PlayerCapabilityRemainder { } /** - * 玩家追踪实体事件
- * 当有其他玩家被加载时,客户端需要对方的capability,该事件可以主动发送
- * 会调用{@link ICapabilitySync#sendToClient(ServerPlayer)} - * @param event 追踪事件实例 + * Player start tracking an player event
+ * When other entities are loaded, the client requires the capabilities of the other party, and this event can be actively sent
+ * Will call{@link ICapabilitySync#sendToClient(ServerPlayer)} + * @param event event */ public static void onEntityBeTracked(PlayerEvent.StartTracking event) { if (event.getTarget() instanceof Player target && event.getEntity() instanceof ServerPlayer attacker) { @@ -67,9 +60,9 @@ public class PlayerCapabilityRemainder { } /** - * 玩家Tick事件
- * 如果capability是dirty的,就会调用{@link ICapabilitySync#sendToClient()} - * @param event 事件实例 + * Player Tick Event
+ * If the capability is dirty, it will call {@link ICapabilitySync#sendToClient()}
+ * @param event event */ public static void capabilitySync(TickEvent.PlayerTickEvent event) { if(!event.player.level().isClientSide){ @@ -85,10 +78,9 @@ public class PlayerCapabilityRemainder { } /** - * 玩家登录事件
- * 重初始化登录玩家的cap
- * 将服务端所有玩家的cap发送给该玩家以初始化该玩家的客户端侧的RemotePlayer数据
- * 上一行的这个行为可能会导致卡顿,它的必要性还未知,可以发pr或issue提议删除它 + * Player login event
+ * Reinitialize the login player's capability
+ * @param event event */ public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { Player player = event.getEntity(); @@ -103,16 +95,5 @@ public class PlayerCapabilityRemainder { data.setDirty(false); data.sendToClient(); }); - Optional.ofNullable(serverPlayer.getServer()).map(MinecraftServer::getPlayerList).map(PlayerList::getPlayers).ifPresent( - serverPlayers -> serverPlayers.forEach(p -> { - if(!p.getUUID().equals(serverPlayer.getUUID())) { - PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> { - ICapabilitySync data = CapabilityUtils.getCapability(player, key); - if(data == null) return; - data.sendToClient(serverPlayer); - }); - } - }) - ); } } diff --git a/src/main/java/com/linearpast/sccore/capability/data/player/SimplePlayerCapabilitySync.java b/src/main/java/com/linearpast/sccore/capability/data/player/SimplePlayerCapabilitySync.java index f47e826..e99424f 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/player/SimplePlayerCapabilitySync.java +++ b/src/main/java/com/linearpast/sccore/capability/data/player/SimplePlayerCapabilitySync.java @@ -1,15 +1,16 @@ package com.linearpast.sccore.capability.data.player; import com.linearpast.sccore.capability.data.ICapabilitySync; +import com.linearpast.sccore.capability.data.entity.SimpleEntityCapabilitySync; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.player.Player; import java.util.UUID; /** - * 实现时建议手动添加:
- * key —— 作为cap的唯一标识
- * getCapability(Player player) —— 获取cap的简化方法
+ * It is recommended to manually add it during implementation:
+ * {@code key} ---- As the unique identifier of capability.
+ * {@code getCapability(Player player)} ---- Simplified method for obtaining capability
* 例: *
  * {@code
@@ -50,10 +51,10 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync
-     * 你不应该重写它,你应该实现 {@link SimplePlayerCapabilitySync#copyFrom(ICapabilitySync)}
-     * @param oldData 旧数据
-     * @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()}
+     * Copy data from parameter instance to current instance 
+ *You shouldn't rewrite it, you should implement: {@link SimpleEntityCapabilitySync#copyFrom(ICapabilitySync)} + * @param oldData old data + * @param listenDone Whether to execute the completion method at the end: {@link ICapabilitySync#onCopyDone()} */ @Override public void copyFrom(ICapabilitySync oldData, boolean listenDone) { @@ -64,14 +65,14 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync oldData); /** - * 序列化为tag
- * 你不应该重写它,你应该实现{@link ICapabilitySync#toTag(CompoundTag)} + * Serialize to tag
+ * You shouldn't rewrite it, you should implement: {@link SimplePlayerCapabilitySync#toTag(CompoundTag)} * @return tag */ @Override @@ -83,8 +84,8 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync - * 你不应该重写它,你应该实现{@link ICapabilitySync#fromTag(CompoundTag)} )} + * Deserialize to instance object
+ * You don't need to rewrite it, you should implement: {@link SimplePlayerCapabilitySync#fromTag(CompoundTag)} * @param nbt nbt */ @Override @@ -93,4 +94,19 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync + * Actually equivalent to serializeNBT() + * @param tag data tag + * @return tag + */ + public abstract CompoundTag toTag(CompoundTag tag); + + /** + * In the deserializeNBT method of SimpleElementCapability Sync, it will be called
+ * Actually equivalent to deserializeNBT() + * @param tag data tag + */ + public abstract void fromTag(CompoundTag tag); } diff --git a/src/main/java/com/linearpast/sccore/capability/network/CapabilityChannel.java b/src/main/java/com/linearpast/sccore/capability/network/CapabilityChannel.java index 0a01934..96dfc01 100644 --- a/src/main/java/com/linearpast/sccore/capability/network/CapabilityChannel.java +++ b/src/main/java/com/linearpast/sccore/capability/network/CapabilityChannel.java @@ -10,31 +10,32 @@ import java.util.function.Function; import java.util.function.Supplier; /** - * 在Mod主类构造方法逻辑中调用createChannel,有两种:
+ * Call createChannel in the Mod main class construction method logic. There are two ways:
*
  * 1. {@link com.linearpast.sccore.capability.CapabilityUtils#createChannel(SimpleChannel)}
- * 若如此做,则必须重写Cap实体类中的所有sendToPlayer方法,并在重写中调用使用你的Channel
+ * If you do this, you must override all the sendToPlayer methods in the Capability class, and call your Channel in the override
  * 
*
  * 2. {@link com.linearpast.sccore.capability.CapabilityUtils#createChannel()}
- * 若如此做,则网络包会以SnowyCrescentCore的Channel注册
+ * If this is done, the network package will be registered with SCCore's Channel
  * 
- * 所添加的网络包必须实现ICapabilityPacket接口 + * The added network packet must implement the ICapabilityPacket interface */ public class CapabilityChannel { private final SimpleChannel channel; + public CapabilityChannel(SimpleChannel channel) { this.channel = channel; } /** - * 通过此方法预设添加一个网络包,等待实例register - * @param clazz 网络包类 - * @param cid 索引 - * @param decoder 解码器 - * @param encoder 编码器 - * @param handler 句柄 - * @param 网络包接口 + * Add a network packet through this method and register + * @param clazz Network packet class + * @param cid index + * @param decoder decoder + * @param encoder encoder + * @param handler handler + * @param extend {@code ICapabilityPacket} */ public > void register( Class clazz, diff --git a/src/main/java/com/linearpast/sccore/capability/network/ICapabilityPacket.java b/src/main/java/com/linearpast/sccore/capability/network/ICapabilityPacket.java index 1a4f3f2..7699041 100644 --- a/src/main/java/com/linearpast/sccore/capability/network/ICapabilityPacket.java +++ b/src/main/java/com/linearpast/sccore/capability/network/ICapabilityPacket.java @@ -11,13 +11,13 @@ import java.util.function.Supplier; public interface ICapabilityPacket { /** - * 解码网络包 + * Decoding network packets * @param buf FriendlyByteBuf */ void encode(FriendlyByteBuf buf); /** - * 网络包处理事件,一般情况下不需要重写它,默认的行为足够使用 + * Network packet processing events generally do not need to be rewritten, and the default behavior is sufficient for use * @param supplier supplier */ default void handle(Supplier supplier){ @@ -26,30 +26,30 @@ public interface ICapabilityPacket { } /** - * 网络包处理事件应该在这重写 + * Network packet processing events should be rewritten here * @param context NetworkEvent.Context */ void handler(NetworkEvent.Context context); /** - * 在网络包中获取对应的capability,一般在 {@link ICapabilityPacket#syncData}后执行 - * @param entity 目标实体 - * @return 返回Capability + * Retrieve the corresponding capability from the network packet, usually executed after {@link ICapabilityPacket#syncData} + * @param entity Target + * @return capability */ - @Nullable ICapabilitySync getCapability(T entity); + @Nullable ICapabilitySync getCapability(T entity); /** - * 获取Tag - * @return Tag + * Get tag + * @return tag */ CompoundTag getData(); /** - * 网络包中将tag转换为capability data,默认直接反序列化 + * Convert tags to capability data in network packets and deserialize them directly by default * @param dataTag tag - * @param data 应被写入数据的data + * @param data The data that should be written into the data */ - default void syncData(CompoundTag dataTag, ICapabilitySync data){ + default void syncData(CompoundTag dataTag, ICapabilitySync data){ if(data == null) return; data.deserializeNBT(dataTag); } diff --git a/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java b/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java index 67ab9d2..435915c 100644 --- a/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java +++ b/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java @@ -13,19 +13,35 @@ import net.minecraftforge.network.NetworkEvent; public abstract class SimpleCapabilityPacket implements ICapabilityPacket { private final CompoundTag data; + /** + * Constructor + * @param data data tag + */ public SimpleCapabilityPacket(CompoundTag data) { this.data = data; } + /** + * decoder + * @param buf buf + */ public SimpleCapabilityPacket(FriendlyByteBuf buf) { this.data = buf.readNbt(); } + /** + * encoder + * @param buf buf + */ @Override public void encode(FriendlyByteBuf buf) { buf.writeNbt(data); } + /** + * Default network packet handle, generally sufficient for use + * @param context NetworkEvent.Context + */ @SuppressWarnings("unchecked") @Override public void handler(NetworkEvent.Context context) { @@ -43,7 +59,7 @@ public abstract class SimpleCapabilityPacket implements ICapab } if(entity == null) return; try { - ICapabilitySync data = getCapability((T) entity); + ICapabilitySync data = getCapability((T) entity); syncData(nbt, data); }catch (Exception ignored) {} } diff --git a/src/main/java/com/linearpast/sccore/network/Channel.java b/src/main/java/com/linearpast/sccore/core/ModChannel.java similarity index 85% rename from src/main/java/com/linearpast/sccore/network/Channel.java rename to src/main/java/com/linearpast/sccore/core/ModChannel.java index 39b0dd3..da9ef80 100644 --- a/src/main/java/com/linearpast/sccore/network/Channel.java +++ b/src/main/java/com/linearpast/sccore/core/ModChannel.java @@ -1,4 +1,4 @@ -package com.linearpast.sccore.network; +package com.linearpast.sccore.core; import com.linearpast.sccore.SnowyCrescentCore; import net.minecraft.resources.ResourceLocation; @@ -8,8 +8,8 @@ import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.simple.SimpleChannel; -public class Channel { - public static int cid = 0; +public class ModChannel { + private static int cid = 0; private static final String PROTOCOL_VERSION = ModList.get() .getModContainerById(SnowyCrescentCore.MODID) .map(c -> c.getModInfo().getVersion().toString()) @@ -25,12 +25,8 @@ public class Channel { } - public static int getCid() { - return cid; - } - - public static void setCid(int cid) { - Channel.cid = cid; + public static int getAndAddCid() { + return cid++; } public static void sendAllPlayer(MSG message){ diff --git a/src/main/java/com/linearpast/sccore/core/ModCommands.java b/src/main/java/com/linearpast/sccore/core/ModCommands.java new file mode 100644 index 0000000..23da601 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/core/ModCommands.java @@ -0,0 +1,64 @@ +package com.linearpast.sccore.core; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.command.AnimationCommands; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.synchronization.ArgumentTypeInfo; +import net.minecraftforge.client.event.RegisterClientCommandsEvent; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; + +import java.util.HashSet; +import java.util.Set; + +import static net.minecraft.commands.Commands.literal; + +public class ModCommands { + static final Set animationCommand = new HashSet<>(Set.of(SnowyCrescentCore.MODID, "sc", "scc")); + public static void addCommandAlias(String alias) { + animationCommand.add(alias); + } + public static Set getAnimationCommand() { + return animationCommand; + } + + public static void registerCommands(IEventBus forgeBus, IEventBus modBus) { + forgeBus.addListener(ModCommands::commonCommandRegister); + forgeBus.addListener(ModCommands::clientCommandRegister); + Arguments.register(modBus); + } + + + public static void commonCommandRegister(RegisterCommandsEvent event) { + animationCommand.forEach(string -> { + LiteralArgumentBuilder builder = literal(string); + CommandBuildContext buildContext = event.getBuildContext(); + AnimationCommands.commonCommandRegister(builder); + + event.getDispatcher().register(builder); + }); + } + + public static void clientCommandRegister(RegisterClientCommandsEvent event) { + animationCommand.forEach(string -> { + LiteralArgumentBuilder builder = literal(string); + AnimationCommands.clientCommandRegister(builder); + + event.getDispatcher().register(builder); + }); + } + + public static class Arguments { + public static final DeferredRegister> REGISTRY = DeferredRegister.create( + ForgeRegistries.Keys.COMMAND_ARGUMENT_TYPES, SnowyCrescentCore.MODID + ); + public static void register(IEventBus eventBus) { + AnimationCommands.registerArguments(REGISTRY); + REGISTRY.register(eventBus); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/core/ModConfigs.java b/src/main/java/com/linearpast/sccore/core/ModConfigs.java new file mode 100644 index 0000000..fd2b1e7 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/core/ModConfigs.java @@ -0,0 +1,20 @@ +package com.linearpast.sccore.core; + +import net.minecraftforge.common.ForgeConfigSpec; + +public class ModConfigs { + public static class Common { + public static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + public static final ForgeConfigSpec SPEC; + + public static final ForgeConfigSpec.ConfigValue enableExample; + + static { + BUILDER.push("Development"); + enableExample = BUILDER.comment("Enable some example for lib.") + .define("enableExample", false); + BUILDER.pop(); + SPEC = BUILDER.build(); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/core/ModLazyRun.java b/src/main/java/com/linearpast/sccore/core/ModLazyRun.java new file mode 100644 index 0000000..8522125 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/core/ModLazyRun.java @@ -0,0 +1,52 @@ +package com.linearpast.sccore.core; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; + +import java.util.concurrent.Callable; + +public abstract class ModLazyRun { + private final String modId; + public ModLazyRun(String modId) { + this.modId = modId; + } + + public boolean testLoadedAndRun(Runnable runnable){ + if(isModLoaded()) runnable.run(); + else return false; + return true; + } + + public T testLoadedAndCall(Callable callable) { + try { + if(isModLoaded()) return callable.call(); + } catch (Exception ignored) {} + return null; + } + + public T testLoadedAndCall(Callable callable, Callable elseCall) { + try { + if(isModLoaded()) return callable.call(); + else return elseCall.call(); + }catch(Exception e) { + return null; + } + } + + public void addCommonListener(IEventBus forgeBus, IEventBus modBus){} + public void addClientListener(IEventBus forgeBus, IEventBus modBus){} + public void testLoadedAndAddListener(IEventBus forgeBus, IEventBus modBus) { + if(isModLoaded()){ + addCommonListener(forgeBus, modBus); + if(FMLLoader.getDist() == Dist.CLIENT){ + addClientListener(forgeBus, modBus); + } + } + } + + public boolean isModLoaded() { + return ModList.get().isLoaded(modId); + }; +} diff --git a/src/main/java/com/linearpast/sccore/example/ModCaps.java b/src/main/java/com/linearpast/sccore/example/ModCaps.java deleted file mode 100644 index 174622e..0000000 --- a/src/main/java/com/linearpast/sccore/example/ModCaps.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.linearpast.sccore.example; - -import com.linearpast.sccore.capability.CapabilityUtils; -import com.linearpast.sccore.capability.data.entity.EntityCapabilityRegistry; -import com.linearpast.sccore.capability.network.CapabilityChannel; -import com.linearpast.sccore.example.cap.ISheepData; -import com.linearpast.sccore.example.cap.SheepDataCapability; -import com.linearpast.sccore.example.event.PlayerAttackEvent; -import com.linearpast.sccore.network.Channel; -import net.minecraft.world.entity.animal.Sheep; -import net.minecraftforge.common.capabilities.CapabilityManager; -import net.minecraftforge.common.capabilities.CapabilityToken; -import net.minecraftforge.eventbus.api.IEventBus; - -public class ModCaps { - /** - * 注册实体capability的示例
- * 请参阅 {@link CapabilityUtils} - */ - public static void register(){ - //如果你想将网络包注册到你自己的mod中,createChannel(INSTANCE) - //然后别忘记在capability类里面重写所有的sendToClient方法 - CapabilityChannel channel = CapabilityUtils.createChannel(); - //不可与其他网络包重复的任意整数 - int cid = Channel.getCid(); - //注册实体cap和它的网络包 - //若注册玩家的请用registerPlayerCapabilityWithNetwork - CapabilityUtils.registerEntityCapabilityWithNetwork( - //一个resourceLocation,任意命名,不重复即可 - SheepDataCapability.key, - //需要注册cap的数据 - new EntityCapabilityRegistry.CapabilityRecord<>( - //registry将会 new 一个此类的实例,你可以在该类中重写无参构造以让它初始化 - SheepDataCapability.class, - //固定写法,一般情况你无需修改它 - CapabilityManager.get(new CapabilityToken<>() {}), - //第一个参数类的接口,可以为抽象类或不用接口 - //你可以用它自己: SheepDataCapability.class - ISheepData.class, - //注册的cap应附加在什么实体上 - Sheep.class - ), - channel, - //索引使用后+1,防止后续网络频道冲突 - cid++, - //网络包的class - SheepDataCapability.SheepCapabilityPacket.class, - //网络包的decode方法 - SheepDataCapability.SheepCapabilityPacket::new, - //网络包的encode方法 - SheepDataCapability.SheepCapabilityPacket::encode, - //网络包的handle方法 - SheepDataCapability.SheepCapabilityPacket::handle - ); - //这是为了还给Channel一个增加后的cid,以防止后续网络包索引重复 - Channel.setCid(cid); - } - - //测试cap是否成功添加的监听事件 - public static void addListenerToEvent(IEventBus forgeBus){ - forgeBus.addListener(PlayerAttackEvent::onPlayerAttack); - } -} diff --git a/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java b/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java new file mode 100644 index 0000000..048ba36 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java @@ -0,0 +1,70 @@ +package com.linearpast.sccore.example.animation; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.Ride; +import com.linearpast.sccore.animation.event.create.AnimationLayerRegisterEvent; +import com.linearpast.sccore.example.animation.event.ExampleCommandEvent; +import com.linearpast.sccore.example.animation.event.ExamplePlayerAttackEvent; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.loading.FMLEnvironment; + +/** + * @see AnimationUtils + */ +public class ModAnimation { + /** + * This is an animation layer + */ + public static final ResourceLocation normalLayers = new ResourceLocation(SnowyCrescentCore.MODID, "normal_layers"); + + /** + *
+     * They are animations
+     * {@code new ResourceLocation(modid, name)}
+     * Resource from "assets/{modid}/player_animation/{name}.json"
+ */ + public static final ResourceLocation AmLyingToRightLying = new ResourceLocation(SnowyCrescentCore.MODID, "am_lying_to_right_lying"); + public static final ResourceLocation AmStandToLying = new ResourceLocation(SnowyCrescentCore.MODID, "am_stand_to_lying"); + public static final ResourceLocation WaltzGentleman = new ResourceLocation(SnowyCrescentCore.MODID, "waltz_gentleman"); + public static final ResourceLocation WaltzLady = new ResourceLocation(SnowyCrescentCore.MODID, "waltz_lady"); + + public static void register(IEventBus forgeBus, IEventBus modBus) { + //You must define corresponding Animation to register + Animation amLTRL = new Animation(AmLyingToRightLying) + .withLyingType(Animation.LyingType.RIGHT) + .withRide(Ride.create().addComponentAnimation(AmStandToLying)); + Animation amSTL = new Animation(AmStandToLying) + .withLyingType(Animation.LyingType.FRONT); + + Animation waltzGentleman = new Animation(WaltzGentleman) + .withRide(Ride.create().addComponentAnimation(WaltzLady)); + Animation waltzLady = new Animation(WaltzLady) + .withCamYaw(180); + + //You can use it to register an Animation + AnimationUtils.registerAnimation(AmLyingToRightLying, amLTRL); + AnimationUtils.registerAnimation(AmStandToLying, amSTL); + AnimationUtils.registerAnimation(WaltzGentleman, waltzGentleman); + AnimationUtils.registerAnimation(WaltzLady, waltzLady); + + + //Register by event + //Or use AnimationUtils.registerAnimationLayer(ResourceLocation layer, int priority); + modBus.addListener(ModAnimation::onLayerRegister); + + //Try to play animation + forgeBus.addListener(ExamplePlayerAttackEvent::onPlayerAttack); + forgeBus.addListener(ExampleCommandEvent::inviteDance); + if(FMLEnvironment.dist == Dist.CLIENT){ + forgeBus.addListener(ExamplePlayerAttackEvent::onInputEvent); + } + } + + public static void onLayerRegister(AnimationLayerRegisterEvent event) { + event.putLayer(normalLayers, 42); + } +} diff --git a/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java b/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java new file mode 100644 index 0000000..2297978 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java @@ -0,0 +1,122 @@ +package com.linearpast.sccore.example.animation.event; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.command.argument.AnimationArgument; +import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.event.RegisterCommandsEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class ExampleCommandEvent { + record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean isForce){} + private static final Map> invites = new HashMap<>(); + public static void inviteDance(RegisterCommandsEvent event) { + LiteralArgumentBuilder builder = literal("dance").then(literal("invite") + .then(argument("player", EntityArgument.player()) + .then(argument("layer", AnimationLayerArgument.layer()) + .then(argument("anim", AnimationArgument.animation()) + .executes(ExampleCommandEvent::inviteDance) + .then(argument("force", BoolArgumentType.bool()) + .executes(ExampleCommandEvent::inviteDance) + ) + ) + ) + ) + .then(literal("accept") + .then(argument("player", EntityArgument.player()) + .executes(ExampleCommandEvent::acceptInvite) + ) + ) + ); + event.getDispatcher().register(builder); + } + + private static int inviteDance(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + //get info + boolean force = false; + try { + force = BoolArgumentType.getBool(context, "force"); + } catch (Exception ignored) {} + ServerPlayer player = source.getPlayerOrException(); + ServerPlayer target = EntityArgument.getPlayer(context, "player"); + String layerString = AnimationLayerArgument.getLayer(context, "layer"); + String animString = AnimationArgument.getAnimation(context, "anim"); + ResourceLocation layer = new ResourceLocation(layerString); + ResourceLocation anim = new ResourceLocation(animString); + boolean finalForce = force; + + //test info present + boolean animationPresent = AnimationUtils.isAnimationPresent(anim); + boolean animationLayerPresent = AnimationUtils.isAnimationLayerPresent(layer); + if(!animationLayerPresent || !animationPresent) throw new Exception(); + + //update static cache + Map inviteRecordMap = invites.getOrDefault(player.getUUID(), new HashMap<>()); + inviteRecordMap.put(target.getUUID(), new InviteRecord(System.currentTimeMillis(), layer, anim, finalForce)); + invites.put(player.getUUID(), inviteRecordMap); + + //send message + Component name = player.getName(); + Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/dance invite accept " + player.getName().getString()) + ); + target.sendSystemMessage(name.copy().append("邀请你跳一支舞。").append(Component.literal("单击此处同意.").setStyle(pStyle))); + source.sendSuccess(() -> Component.literal("命令执行成功. 已发送邀请").withStyle(ChatFormatting.GREEN), true); + } catch (Exception e) { + source.sendFailure(Component.literal("Command run fail.").withStyle(ChatFormatting.RED)); + return 0; + } + return 1; + } + + private static int acceptInvite(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer target = source.getPlayerOrException(); + ServerPlayer player = EntityArgument.getPlayer(context, "player"); + + Map inviteRecordMap = invites.getOrDefault(player.getUUID(), null); + if(inviteRecordMap == null) throw new Exception(); + InviteRecord inviteRecord = inviteRecordMap.getOrDefault(target.getUUID(), null); + if(inviteRecord == null) throw new Exception(); + long now = System.currentTimeMillis(); + if(now - inviteRecord.time > 120000) { + source.sendFailure(Component.literal("邀请已超时(2分钟).").withStyle(ChatFormatting.RED)); + player.sendSystemMessage(target.getName().copy().append("接受了你的舞蹈邀请. 但是邀请超时了(2分钟).").withStyle(ChatFormatting.RED)); + return 0; + } + if(player.position().distanceToSqr(target.position()) > 64) { + source.sendFailure(Component.literal("你们距离太远了(8格).").withStyle(ChatFormatting.RED)); + player.sendSystemMessage(target.getName().copy().append("接受了你的舞蹈邀请. 但你们距离太远了(8格).").withStyle(ChatFormatting.RED)); + return 0; + } + inviteRecordMap.remove(target.getUUID()); + invites.put(player.getUUID(), inviteRecordMap); + AnimationUtils.startAnimationTogether(player, inviteRecord.layer, inviteRecord.animation, inviteRecord.isForce, target); + source.sendSuccess(() -> Component.literal("已接受邀请.").withStyle(ChatFormatting.GREEN), true); + player.sendSystemMessage(target.getName().copy().append("已接受你的舞蹈邀请.").withStyle(ChatFormatting.GREEN)); + } catch (Exception e) { + source.sendFailure(Component.literal("Command run fail.").withStyle(ChatFormatting.RED)); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java b/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java new file mode 100644 index 0000000..fb5d7de --- /dev/null +++ b/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java @@ -0,0 +1,65 @@ +package com.linearpast.sccore.example.animation.event; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.example.animation.ModAnimation; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.animal.Sheep; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.event.InputEvent; +import net.minecraftforge.event.entity.player.AttackEntityEvent; + +public class ExamplePlayerAttackEvent { + /** + * when attack sheep, will play stand to lying animation
+ * when attack other player, will play animation together + * @param event event + */ + public static void onPlayerAttack(AttackEntityEvent event) { + Entity target = event.getTarget(); + Player entity = event.getEntity(); + if(entity instanceof ServerPlayer player) { + if(target instanceof Sheep){ + ResourceLocation playing = AnimationUtils.getAnimationPlaying(player, ModAnimation.normalLayers); + if(playing == null) { + AnimationUtils.playAnimation(player, ModAnimation.normalLayers, ModAnimation.AmStandToLying); + } else { + AnimationUtils.playAnimation(player, ModAnimation.normalLayers, null); + } + } + if(target instanceof ServerPlayer serverPlayer) { + AnimationUtils.startAnimationTogether( + serverPlayer, + ModAnimation.normalLayers, + ModAnimation.AmLyingToRightLying, + true, + player + ); + + } + } + + } + + /** + * when press "/", this will run + * @param event event + */ + @OnlyIn(Dist.CLIENT) + public static void onInputEvent(InputEvent.Key event) { + Minecraft instance = Minecraft.getInstance(); + LocalPlayer player = instance.player; + if (player == null) return; + if(instance.options.keyCommand.isDown()) { + ResourceLocation playing = AnimationUtils.getAnimationPlaying(player, ModAnimation.normalLayers); + if(playing == null) { + AnimationUtils.playAnimationWithRide(null, ModAnimation.normalLayers, ModAnimation.AmLyingToRightLying, true); + } + } + } +} diff --git a/src/main/java/com/linearpast/sccore/example/cap/ISheepData.java b/src/main/java/com/linearpast/sccore/example/cap/ISheepData.java deleted file mode 100644 index b2e1846..0000000 --- a/src/main/java/com/linearpast/sccore/example/cap/ISheepData.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.linearpast.sccore.example.cap; - -import com.linearpast.sccore.capability.data.ICapabilitySync; -import net.minecraft.world.entity.animal.Sheep; - -/** - * 接口继承ICapabilitySync是必需的,但是接口是非必需的(你可以在注册时直接使用cap类本身)
- * 用于共享一些可能会用到的cap的公共方法 - */ -public interface ISheepData extends ICapabilitySync { - Integer getValue(); - void setValue(Integer value); -} diff --git a/src/main/java/com/linearpast/sccore/example/capability/ModCapability.java b/src/main/java/com/linearpast/sccore/example/capability/ModCapability.java new file mode 100644 index 0000000..d806d03 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/example/capability/ModCapability.java @@ -0,0 +1,64 @@ +package com.linearpast.sccore.example.capability; + +import com.linearpast.sccore.capability.CapabilityUtils; +import com.linearpast.sccore.capability.data.entity.EntityCapabilityRegistry; +import com.linearpast.sccore.capability.network.CapabilityChannel; +import com.linearpast.sccore.core.ModChannel; +import com.linearpast.sccore.example.capability.data.ISheepData; +import com.linearpast.sccore.example.capability.data.SheepDataCapability; +import com.linearpast.sccore.example.capability.event.PlayerAttackEvent; +import net.minecraft.world.entity.animal.Sheep; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; +import net.minecraftforge.eventbus.api.IEventBus; + +import java.util.Set; + +public class ModCapability { + /** + * Example of Registered Entity Capability
+ * @see CapabilityUtils#registerEntityCapability + * @see CapabilityUtils#registerPlayerCapability + * @see CapabilityUtils#registerEntityCapabilityWithNetwork + * @see CapabilityUtils#registerPlayerCapabilityWithNetwork + */ + public static void register(){ + //If you want to register network packets in your own mod, to use : createChannel(INSTANCE) + //And don't forget to rewrite all the sendToClient methods in the capability class + CapabilityChannel channel = CapabilityUtils.createChannel(); + //Register the entity capability and its network packet + //If you want register about player, please use CapabilityUtils.registerPlayerCapabilityWithNetwork() + CapabilityUtils.registerEntityCapabilityWithNetwork( + //A resourceLocation, named arbitrarily without repetition + SheepDataCapability.key, + //Data that needs to be registered for capability + new EntityCapabilityRegistry.CapabilityRecord<>( + //Registry will create a new instance of this class + //And you can override the parameterless construct in this class to initialize it + SheepDataCapability.class, + //Fixed writing style, generally you don't need to modify it + CapabilityManager.get(new CapabilityToken<>() {}), + //The interface of the first parameter class can be an abstract class or not require an interface + //You can use it yourself: SheepDataCapability.class + ISheepData.class, + //What entities should the registered capability be attached to + Set.of(Sheep.class) + ), + channel, + //Index+1 after use to prevent subsequent network channel conflicts + ModChannel.getAndAddCid(), + //Class of network packet + SheepDataCapability.SheepCapabilityPacket.class, + //Decoder method for network packet + SheepDataCapability.SheepCapabilityPacket::new, + //Encoder method for network packet + SheepDataCapability.SheepCapabilityPacket::encode, + //Handler method for network packet + SheepDataCapability.SheepCapabilityPacket::handle + ); + } + + public static void addListenerToEvent(IEventBus forgeBus){ + forgeBus.addListener(PlayerAttackEvent::onPlayerAttack); + } +} diff --git a/src/main/java/com/linearpast/sccore/example/capability/data/ISheepData.java b/src/main/java/com/linearpast/sccore/example/capability/data/ISheepData.java new file mode 100644 index 0000000..ab217e6 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/example/capability/data/ISheepData.java @@ -0,0 +1,13 @@ +package com.linearpast.sccore.example.capability.data; + +import com.linearpast.sccore.capability.data.ICapabilitySync; +import net.minecraft.world.entity.animal.Sheep; + +/** + * The interface inheritance ICapabilitySync is required, but the interface is not necessary (you can directly use the cap class itself during registration)
+ * Common methods for sharing caps that may be used. + */ +public interface ISheepData extends ICapabilitySync { + Integer getValue(); + void setValue(Integer value); +} diff --git a/src/main/java/com/linearpast/sccore/example/cap/SheepDataCapability.java b/src/main/java/com/linearpast/sccore/example/capability/data/SheepDataCapability.java similarity index 59% rename from src/main/java/com/linearpast/sccore/example/cap/SheepDataCapability.java rename to src/main/java/com/linearpast/sccore/example/capability/data/SheepDataCapability.java index d71f435..92535fc 100644 --- a/src/main/java/com/linearpast/sccore/example/cap/SheepDataCapability.java +++ b/src/main/java/com/linearpast/sccore/example/capability/data/SheepDataCapability.java @@ -1,4 +1,4 @@ -package com.linearpast.sccore.example.cap; +package com.linearpast.sccore.example.capability.data; import com.linearpast.sccore.SnowyCrescentCore; import com.linearpast.sccore.capability.CapabilityUtils; @@ -8,56 +8,61 @@ import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.animal.Sheep; import org.jetbrains.annotations.Nullable; import java.util.Optional; /** - * cap的实体类
- * 继承SimpleEntityCapabilitySync意味着自动托管一个id的同步
- * 实现的IsheepData仅含有属性value的getter和setter
+ * The entity class of cap
+ * Inheriting SimpleElementCapability Sync means automatically hosting synchronization of an ID
+ * The IsheepData implemented only contains the property 'value' as a getter and setter
+ * @see SimpleEntityCapabilitySync */ public class SheepDataCapability extends SimpleEntityCapabilitySync implements ISheepData { - //代表cap的key,注册、获取时都需要它 public static final ResourceLocation key = new ResourceLocation(SnowyCrescentCore.MODID, "sheep_data"); - //只是为了统一管理(反)序列化时的keyName public static final String Value = "Value"; - //最后附加到实体实例变量 private Integer value; - //getter @Override public Integer getValue() { return value; } - //setter @Override public void setValue(Integer value) { this.value = value; setDirty(true); } - //在SimpleEntityCapabilitySync的serializeNBT方法中会调用 - //实际上相当于serializeNBT + /** + * @param tag data tag + * @return tag + * @see SimpleEntityCapabilitySync#toTag(CompoundTag) + */ @Override public CompoundTag toTag(CompoundTag tag) { if(value != null) tag.putInt(Value, value); return tag; } - //在SimpleEntityCapabilitySync的deserializeNBT方法中会调用 - //实际上相当于deserializeNBT + /** + * @see SimpleEntityCapabilitySync#fromTag(CompoundTag) + * @param tag data tag + */ @Override public void fromTag(CompoundTag tag) { this.value = null; if(tag.contains(Value)) this.value = tag.getInt(Value); } - //从旧实例中复制数据到新实例的方法 + /** + * @see SimpleEntityCapabilitySync#copyFrom(ICapabilitySync) + * @param oldData Copy from this data to the current instance + */ @Override public void copyFrom(ICapabilitySync oldData) { SheepDataCapability data = (SheepDataCapability) oldData; @@ -65,43 +70,54 @@ public class SheepDataCapability extends SimpleEntityCapabilitySync imple } /** - * 网络包,你可以在里面重写任意方法,关于方法的作用请参阅
+ * Network packet, you can rewrite any method inside. For the function of methods, please refer to
* {@link com.linearpast.sccore.capability.network.ICapabilityPacket}
- * 可以不写在内部类中,作者是觉得它内容太少,写里面显得更紧凑美观 + * It is not necessary to include it in the internal class. I feel that the content is too limited and writing it inside makes it more compact and beautiful + * @see SimpleCapabilityPacket */ public static class SheepCapabilityPacket extends SimpleCapabilityPacket { - //网络包构造方法 public SheepCapabilityPacket(CompoundTag data) { super(data); } - //这实际上是decoder public SheepCapabilityPacket(FriendlyByteBuf buf) { super(buf); } - //仅用在网络包内部的getCap @Override public @Nullable SheepDataCapability getCapability(Sheep entity) { return SheepDataCapability.getCapability(entity).orElse(null); } } - //获取默认网络包,会在sendToClient的时候调用以发送 + /** + * Get the default network packet, which will be called when sendToClient sends it + * @return network packet + * @see ICapabilitySync#getDefaultPacket() + */ @Override public SimpleCapabilityPacket getDefaultPacket() { return new SheepCapabilityPacket(serializeNBT()); } - //该方法会在cap初始化时调用,比如玩家登录 - //该例中,当羊加入level时会调用该方法以初始化cap + /** + * This method will be called during cap initialization, such as player login
+ * In this example, when the sheep joins the level, this method will be called to initialize the capability + * @param entity Target + * @see ICapabilitySync#attachInit(Entity) + */ @Override public void attachInit(Sheep entity) { } - //在其他地方需要用到cap的时候调用这个 - //目的是为了简化cap utils的方法 + /** + * It is not necessary.
+ * Call this when capability is needed in other places
+ * The purpose is to simplify the method of capability get + * @param sheep Target + * @return Optional capability + */ public static Optional getCapability(Sheep sheep){ return Optional.ofNullable(CapabilityUtils.getEntityCapability( sheep, SheepDataCapability.key, SheepDataCapability.class diff --git a/src/main/java/com/linearpast/sccore/example/event/PlayerAttackEvent.java b/src/main/java/com/linearpast/sccore/example/capability/event/PlayerAttackEvent.java similarity index 81% rename from src/main/java/com/linearpast/sccore/example/event/PlayerAttackEvent.java rename to src/main/java/com/linearpast/sccore/example/capability/event/PlayerAttackEvent.java index 8f4cd02..6925994 100644 --- a/src/main/java/com/linearpast/sccore/example/event/PlayerAttackEvent.java +++ b/src/main/java/com/linearpast/sccore/example/capability/event/PlayerAttackEvent.java @@ -1,6 +1,6 @@ -package com.linearpast.sccore.example.event; +package com.linearpast.sccore.example.capability.event; -import com.linearpast.sccore.example.cap.SheepDataCapability; +import com.linearpast.sccore.example.capability.data.SheepDataCapability; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; @@ -9,7 +9,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.entity.player.AttackEntityEvent; public class PlayerAttackEvent { - //简单的测试一下cap是否生效 public static void onPlayerAttack(AttackEntityEvent event) { Entity target = event.getTarget(); Player entity = event.getEntity(); @@ -22,7 +21,7 @@ public class PlayerAttackEvent { data.setValue(value); Integer id = data.getId(); player.sendSystemMessage(Component.literal( - "第" + value + "攻击了id为\"" + id + "\"的羊" + value + "th attack on sheep with ID " + id )); }); } diff --git a/src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java b/src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java new file mode 100644 index 0000000..88443d1 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java @@ -0,0 +1,54 @@ +package com.linearpast.sccore.mixin; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.AnimationUtils; +import net.minecraftforge.fml.ModList; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +public class SCCoreMixinPlugin implements IMixinConfigPlugin { + @Override + public void onLoad(String s) { + + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + if (targetClassName.startsWith("runData\\.")) { + return "runData".equals(System.getProperty("gradle.task")); + } + if (mixinClassName.startsWith("com\\.linearpast\\." + SnowyCrescentCore.MODID + "\\.mixin\\.animation")) { + return ModList.get().isLoaded(AnimationUtils.AnimModId); + } + return true; + } + + @Override + public void acceptTargets(Set set, Set set1) { + + } + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) { + + } + + @Override + public void postApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) { + + } +} diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java b/src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java new file mode 100644 index 0000000..bbd8353 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java @@ -0,0 +1,97 @@ +package com.linearpast.sccore.mixin.animation; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.data.Animation; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.AABB; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Entity.class) +public abstract class MixinEntity { + @Shadow + private float eyeHeight; + + @Shadow public abstract float getEyeHeight(Pose pPose); + + @Shadow private AABB bb; + + @Shadow public abstract void setPose(Pose pPose); + + @Inject( + method = "getEyeHeight()F", + at = @At(value = "HEAD"), + cancellable = true + ) + private void redefinedEyeHeight(CallbackInfoReturnable cir){ + Entity self = Entity.class.cast(this); + if(self instanceof Player player){ + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) return; + float camYModifier = 0.0f; + for (ResourceLocation value : data.getAnimations().values()) { + Animation animation = AnimationUtils.getAnimation(value); + if(animation == null) continue; + float animationCamY = animation.getCamY(); + camYModifier = Math.min(camYModifier, animationCamY); + } + this.eyeHeight = this.getEyeHeight(Pose.STANDING) + camYModifier; + cir.setReturnValue(this.eyeHeight); + } + } + + @Inject( + method = "getBoundingBox", + at = @At(value = "RETURN"), + cancellable = true + ) + private void redefinedBoundingBox(CallbackInfoReturnable cir){ + Entity self = Entity.class.cast(this); + if(self instanceof Player player){ + float heightModifier = AnimationUtils.getHeightModifier(player); + if(heightModifier == 1.0f) return; + double modifyHeight = 1.8f * heightModifier; + cir.setReturnValue(this.bb.setMaxY(modifyHeight + this.bb.minY)); + } + } + + @ModifyReturnValue( + method = "getBbHeight", + at = @At(value = "RETURN") + ) + private float redefinedBbHeight(float original){ + Entity self = Entity.class.cast(this); + if(self instanceof Player player){ + float heightModifier = AnimationUtils.getHeightModifier(player); + if(heightModifier == 1.0f) return original; + return original * heightModifier; + } + return original; + } + + + + @Inject( + method = "getPose", + at = @At(value = "HEAD"), + cancellable = true + ) + private void redefinedPose(CallbackInfoReturnable cir){ + Entity self = Entity.class.cast(this); + if(self instanceof Player player){ + float heightModifier = AnimationUtils.getHeightModifier(player); + if(heightModifier == 1.0f) return; + setPose(Pose.STANDING); + cir.setReturnValue(Pose.STANDING); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java new file mode 100644 index 0000000..5db9656 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java @@ -0,0 +1,60 @@ +package com.linearpast.sccore.mixin.animation.client; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.data.Animation; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Entity.class) +public abstract class MixinEntity { + @Shadow public abstract void setXRot(float pXRot); + + @Shadow private float xRot; + + @Shadow public abstract void setYRot(float pYRot); + + @Shadow private float yRot; + + @Shadow public abstract float getXRot(); + + @Shadow public float xRotO; + + @Shadow public float yRotO; + + @Inject( + method = "turn", + at = {@At(value = "HEAD")}, + cancellable = true + ) + private void turnPosePlayer(double pYRot, double pXRot, CallbackInfo ci) { + Entity self = Entity.class.cast(this); + if(self instanceof Player player){ + Animation.LyingType lyingType = AnimationUtils.getSideView(player); + if(lyingType != null && Minecraft.getInstance().options.getCameraType().isFirstPerson()) { + float f = (float)pXRot * 0.15F; + float f1 = (float)pYRot * 0.15F; + switch (lyingType) { + case LEFT -> { + this.setXRot(this.xRot + f1 * -1.0f); + this.setYRot(this.yRot + f); + } + case RIGHT -> { + this.setXRot(this.xRot + f1); + this.setYRot(this.yRot + f * -1.0f); + } + } + this.setXRot(Mth.clamp(this.getXRot(), 0.0f, 90.0f)); + this.xRotO = this.xRot; + this.yRotO = this.yRot; + ci.cancel(); + } + } + } +} diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java new file mode 100644 index 0000000..e95d084 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java @@ -0,0 +1,46 @@ +package com.linearpast.sccore.mixin.animation.client; + +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.data.Animation; +import net.minecraft.client.model.AgeableListModel; +import net.minecraft.client.model.ArmedModel; +import net.minecraft.client.model.HeadedModel; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(HumanoidModel.class) +public abstract class MixinHumanoidModel extends AgeableListModel implements ArmedModel, HeadedModel { + @Shadow @Final public ModelPart head; + + @Inject( + method = "setupAnim(Lnet/minecraft/world/entity/LivingEntity;FFFFF)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/geom/ModelPart;copyFrom(Lnet/minecraft/client/model/geom/ModelPart;)V") + ) + private void modifyHeadRot(T pEntity, float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch, CallbackInfo ci){ + if(pEntity instanceof Player player){ + Animation.LyingType lyingType = AnimationUtils.getSideView(player); + if(lyingType != null) { + float pitch = pHeadPitch - 90.0f; + float yaw = pNetHeadYaw * -1.0f; + switch (lyingType) { + case LEFT: { + pitch *= -1.0f; + yaw *= -1.0f; + } + case RIGHT: { + this.head.yRot = pitch * 0.017453292F; + this.head.xRot = yaw * 0.017453292F; + } + } + } + } + } +} diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinKeyframeAnimationPlayer.java b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinKeyframeAnimationPlayer.java new file mode 100644 index 0000000..8e8df05 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinKeyframeAnimationPlayer.java @@ -0,0 +1,20 @@ +package com.linearpast.sccore.mixin.animation.client; + +import com.linearpast.sccore.animation.mixin.IMixinKeyframeAnimationPlayer; +import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(KeyframeAnimationPlayer.class) +public class MixinKeyframeAnimationPlayer implements IMixinKeyframeAnimationPlayer { + + @Shadow(remap = false) + private int currentTick; + + @Override + @Unique + public void sccore$setCurrentTick(int tick) { + this.currentTick = tick; + } +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 4f415fb..9f2a89f 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -25,4 +25,11 @@ modId = "minecraft" mandatory = true versionRange = "${minecraft_version_range}" ordering = "NONE" +side = "BOTH" + +[[dependencies."${mod_id}"]] +modId = "playeranimator" +mandatory = false +versionRange = "[1.0.1,)" +ordering = "AFTER" side = "BOTH" \ No newline at end of file diff --git a/src/main/resources/assets/sccore/player_animation/am_lying_to_right_lying.json b/src/main/resources/assets/sccore/player_animation/am_lying_to_right_lying.json new file mode 100644 index 0000000..b3562b3 --- /dev/null +++ b/src/main/resources/assets/sccore/player_animation/am_lying_to_right_lying.json @@ -0,0 +1,112 @@ +{ + "name": "am_lying_to_right_lying", + "author": "LostInLinearPast", + "description": "fix in 1.20.1 from CreatorGalaxy", + "emote":{ + "isLoop": "false", + "returnTick": 2, + "beginTick":0, + "endTick":6, + "stopTick":2147483647, + "degrees":false, + "moves":[ + + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":-0.08066412806510925 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":-0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":-1.568853497505188 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":1.5707963705062866 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":1.5704461336135864 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.623153030872345 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.4366978108882904 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.0 + } + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/sccore/player_animation/am_stand_to_lying.json b/src/main/resources/assets/sccore/player_animation/am_stand_to_lying.json new file mode 100644 index 0000000..5c753ec --- /dev/null +++ b/src/main/resources/assets/sccore/player_animation/am_stand_to_lying.json @@ -0,0 +1,64 @@ +{ + "name": "am_stand_to_lying", + "author": "LostInLinearPast", + "description": "fix in 1.20.1 from CreatorGalaxy", + "emote":{ + "isLoop": "false", + "returnTick": 2, + "beginTick":0, + "endTick":5, + "stopTick":2147483647, + "degrees":false, + "moves":[ + + { + "tick":5, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":0.0 + } + }, + { + "tick":5, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":-0.0 + } + }, + { + "tick":5, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":1.5707963705062866 + } + }, + { + "tick":5, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.623153030872345 + } + }, + { + "tick":5, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":5, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.0 + } + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/sccore/player_animation/waltz_gentleman.json b/src/main/resources/assets/sccore/player_animation/waltz_gentleman.json new file mode 100644 index 0000000..2ae3908 --- /dev/null +++ b/src/main/resources/assets/sccore/player_animation/waltz_gentleman.json @@ -0,0 +1,1536 @@ +{ + "name": "waltz_gentleman", + "author": "LostInLinearPast", + "description": "waltz gentleman", + "emote":{ + "isLoop": "true", + "returnTick": 2, + "beginTick":0, + "endTick":81, + "stopTick":2147483647, + "degrees":false, + "moves":[ + + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "y":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "y":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "z":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "z":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "x":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "x":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "yaw":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "yaw":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "roll":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "roll":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "pitch":-0.1944645792245865 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "pitch":-0.1944645792245865 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":-0.06981316953897476 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.03490658476948738 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":-0.03490658476948738 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.0625 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.027499999850988388 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.009999999776482582 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.02500000037252903 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.0625 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.027499999850988388 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.009999999776482582 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.02500000037252903 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.012500000186264515 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.05000000074505806 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.13750000298023224 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.3125 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.3125 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.30000001192092896 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.26249998807907104 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.17499999701976776 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.4124999940395355 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.6875 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.7950000166893005 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.925000011920929 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.925000011920929 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.512499988079071 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.23749999701976776 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.12999999523162842 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "yaw":-0.30647698044776917 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "yaw":-0.30647698044776917 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "roll":1.3770651817321777 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "roll":1.3770651817321777 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "y":3.5131139755249023 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "y":3.5131139755249023 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "z":-0.9560732841491699 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "z":-0.9560732841491699 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "x":-5.65712833404541 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "x":-5.65712833404541 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "pitch":-1.2955334186553955 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "pitch":-1.2955334186553955 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "bend":-0.46759316325187683 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "bend":-0.46759316325187683 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "roll":-0.5759586691856384 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "roll":-0.5759586691856384 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "yaw":-1.3089969158172607 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "yaw":-1.3089969158172607 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "pitch":-1.0646508932113647 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "pitch":-1.0646508932113647 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "y":2.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "z":-1.2999999523162842 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "z":-1.2999999523162842 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "x":5.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.09413255006074905 + } + }, + { + "tick":32, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.05712477117776871 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":0.006151077337563038 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":0.010275216773152351 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.032113973051309586 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.10115855187177658 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.23402659595012665 + } + }, + { + "tick":32, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.42213836312294006 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.0634104534983635 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.1390521377325058 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.2702104151248932 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.2626937925815582 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.7774391174316406 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.2324778437614441 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":0.2255288064479828 + } + }, + { + "tick":32, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":0.1099814623594284 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":44, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.19448231160640717 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.6989269852638245 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.176431804895401 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":0.11157336086034775 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":4.2785497367248126e-11 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":4.2785497367248126e-11 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.140909433364868 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":-0.8861581921577454 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.33404916524887085 + } + }, + { + "tick":12, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.2565633952617645 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.20696492493152618 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.042693521827459335 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.10096975415945053 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.07553856074810028 + } + }, + { + "tick":10, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.1206885427236557 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0923948809504509 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.10970524698495865 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.010996125638484955 + } + }, + { + "tick":72, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.04013187810778618 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.16261258721351624 + } + }, + { + "tick":10, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.23268601298332214 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.15703840553760529 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.246794655919075 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.14305073022842407 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.2745340168476105 + } + }, + { + "tick":72, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.4361734688282013 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":6, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.5139611959457397 + } + }, + { + "tick":10, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.22671326994895935 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.08939548581838608 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.13704152405261993 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.4427417814731598 + } + }, + { + "tick":50, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.28025344014167786 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.20064905285835266 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.04128774628043175 + } + }, + { + "tick":72, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.10648827999830246 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":50, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.10896075647324324 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":0.2722991406917572 + } + }, + { + "tick":18, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":0.15728268027305603 + } + }, + { + "tick":26, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":0.17915907502174377 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/sccore/player_animation/waltz_lady.json b/src/main/resources/assets/sccore/player_animation/waltz_lady.json new file mode 100644 index 0000000..1003640 --- /dev/null +++ b/src/main/resources/assets/sccore/player_animation/waltz_lady.json @@ -0,0 +1,1552 @@ +{ + "name": "waltz_lady", + "author": "LostInLinearPast", + "description": "waltz lady", + "emote":{ + "isLoop": "true", + "returnTick": 2, + "beginTick":0, + "endTick":81, + "stopTick":2147483647, + "degrees":false, + "moves":[ + + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "y":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "z":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "x":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "yaw":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "roll":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "pitch":0.1185886561870575 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "head":{ + "pitch":0.1185886561870575 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":3.1415927410125732 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":3.1415927410125732 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "yaw":3.1415927410125732 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "roll":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.03490658476948738 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":-0.03490658476948738 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "pitch":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.027499999850988388 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.009999999776482582 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.02500000037252903 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.0625 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.027499999850988388 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.009999999776482582 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":-0.02500000037252903 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "y":0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.012500000186264515 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.05000000074505806 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.13750000298023224 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.3125 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.3125 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.30000001192092896 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.26249998807907104 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":-0.17499999701976776 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "x":0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.5 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.9125000238418579 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-1.1875 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-1.2949999570846558 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-1.4249999523162842 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-1.4249999523162842 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-1.0125000476837158 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.737500011920929 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.6299999952316284 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.5 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.5 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "torso":{ + "z":-0.5 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "yaw":1.3089969158172607 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "yaw":1.3089969158172607 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "roll":0.5759586691856384 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "roll":0.5759586691856384 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "y":2.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "z":-1.2999999523162842 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "z":-1.2999999523162842 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "x":-5.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "pitch":-1.0646508932113647 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightArm":{ + "pitch":-1.0646508932113647 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "roll":1.510741949081421 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "roll":1.510741949081421 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "yaw":0.24142970144748688 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "yaw":0.24142970144748688 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "pitch":-1.8961212635040283 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "pitch":-1.8961212635040283 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "y":1.3259220123291016 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "y":1.3259220123291016 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "z":-0.8563777208328247 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "z":-0.8563777208328247 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "x":5.7146406173706055 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "x":5.7146406173706055 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "bend":0.37502986192703247 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftArm":{ + "bend":0.37502986192703247 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.006303687114268541 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.007730656303465366 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":0.0030700149945914745 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.030118120834231377 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "yaw":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.21962174773216248 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.2617029547691345 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.08802766352891922 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.2535526156425476 + } + }, + { + "tick":72, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":0.40923434495925903 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "roll":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":2, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.2701720595359802 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.7349715828895569 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0006949927774257958 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.00104520411696285 + } + }, + { + "tick":35, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.6743449568748474 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.15383221209049225 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":0.12758493423461914 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "pitch":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":2, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.342857241630554 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.75428593158722 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.473965883255005 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "y":12.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":2, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":-0.17428531646728515 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":-0.6542854547500611 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":-0.25547447204589846 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "z":0.1 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":2, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.20577150583267212 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.5501528382301331 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.2617993950843811 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":0.35444632172584534 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "rightLeg":{ + "bend":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":0.008380268700420856 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":0.020489023998379707 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":0.03528726473450661 + } + }, + { + "tick":32, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":0.09650745987892151 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.06075865775346756 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.03179699555039406 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":0.01990862935781479 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "yaw":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":0.0 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.04884415119886398 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.10059066116809845 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.26200801134109497 + } + }, + { + "tick":32, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.4258441925048828 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.028214696794748306 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.20640932023525238 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.21954356133937836 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.24775296449661255 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "roll":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.4414220154285431 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.18329083919525146 + } + }, + { + "tick":20, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.052891556173563004 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.08107911050319672 + } + }, + { + "tick":32, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.06880011409521103 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.636621356010437 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.05537297576665878 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.16388186812400818 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":0.03859478235244751 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "pitch":-0.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.0 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.425536721944809 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.0 + } + }, + { + "tick":60, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.0 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":11.753142833709717 + } + }, + { + "tick":75, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "y":12.0 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":25, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.49681914448738096 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "z":-0.1 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "x":1.9 + } + }, + { + "tick":1, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":8, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":0.20332171022891998 + } + }, + { + "tick":15, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":41, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":48, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":0.2600111663341522 + } + }, + { + "tick":55, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":0.13962633907794952 + } + }, + { + "tick":65, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + }, + { + "tick":81, + "easing": "EASEINOUTQUAD", + "turn": 0, + "leftLeg":{ + "bend":-0.0 + } + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2ddf3e34a99cd71082c679ad86851a3fc97ae8b6 GIT binary patch literal 5712 zcmV-W7O&}vP)Atad;}w z%)RIAy}Oq`bobu9_c@{5Gv}Uj_OsVo-}POO?jtqVDS!Y<-UK1jfx9TqpQ+u+w{F;YAFHV8kE- zh>6#zg2wZ`Dx#n|e#TVb;ajPUcqEN^wyA(;8ikUCgmA#}vC<GNP*s8tc<*VNj<)qQ%Z9e;*xz3;-p0b_@58(H%F4=F9e+L& zzq>5F7KgI%T4;K#7U@3^u#AE_=a_G7GM~@b+}z;c!2=w>{1|7hJI%?{&*rk@$JpLJ zz-(hqT~#0uywWrc7ccJd`#*3aylabVUMHn+AobaY1xT`@K~Ri=$|*w2 z7yk9jeC&_@nzIi-Mziej-WMY^YXqIXm*iQv!i?)x%N=X1OtEjOgu1Rdbodb0-*7EA zzT+)iedY|#)dU0)i>it6(*lP1ZW0hWjPanPa~-N?I4*(825PGWTK@6#U*u0e@;7|% zyHCo*9^K_N9zd!8z5aKG19=ntue*J5>>6W*$b?`8%HVTgE zg$@Bi0MJN&eMD4@c#L#_qG|~0P%Hx;BSM4@F$&so;ruQi{=i@IAGhDhh21?=wGeI` zZp$bUkyRoBtmCrV1|}aaVnti!h4Ge0j~(Tvcm4!#zvXSrHx2<6kOhda3?3Fs&;DM= zavAVlh)*zrh=72#DTXMhlyEKxSK|3123J?S;KgTfR=DpQ579PV8gBKYfp^ zjkR2M+n8Pk4VKAANkW3*!1gvj^X|9t)|-A3WFuNm2f_2!c7eryplt(P7ihac*DB5m z#>AJkijfeXr4of5xn;6oat$kjt1GU1-YJe8ImlgKy^pT*BSDAp4+KyRsG z3^7U>FoJcKQ&(Ndks}BA+JD{G6J!2Ya>6qaVp70*5-jzV&$;TVlf2>0ujidVdlRag zbYZ>l5JN6N@X`w&(JY97wStI(fwonaOT`CbViXij?1Tt8YZL^JAZjo`pp84PV5$8Re|9z%WzkAiUO^mBN{t6$2k@4XqZ2kC+xg|(6O)4M@1 z1~e$a4~nTOgLCN|F|@7HG)mWL1VT_W1VXQ{Jn*rqRg~FA&6zV-a`#>L^W@{Zqp|m> z7Q@&*M-DG5!sq=%stKpBI>p=H`L-mySrI;4xrcoh2pTX}n9mFw8^U~U+1RjDm7oDM z2*F2dseJ^-N@e2N%ap}}(O@?-qkZ@i27#(ae0o)5Gnb<^W} zgn)ISF8_VOi++HcZ@r0Uoj5&;)!E!ZqtHzgUu_d;+NcB#igkjkEY2FL%Jgnvj9{x| z01bGJNb#QNGDJ5q2COmBWe9^cbLA(2qel*4oba{({NG;GaSkw!Y=x6Zu{)nKq%Ehe zI>D=6^GePbcNNcc>2&BC*{=h?f^v3Id$vDdM;-?D$PW3lg9ECXGaiWQCF%o>Xr z10j-~F$R;X!T_BoMvqqMT91Yf2#5xtOSrW7UiDUfUc(hv9!+*JNjc+is09a< zS;I&XWmGtN^ip2=LoWwv!1iY=(Mrpu=i+{h^-a^^yFlv$p;KC~G;Ksg+r&^9e4oDM z@*P32hR7F%ayt)A7ic@B^B%>c1T=U;$Vw1YEz1sOn+Li1JwJ*V)0Jb7W z+NZo?1mj|O6a$i6tLq{Xf=>xkasx&2+9d@TytlL-Uj5qVbNu*`Q9~rU3ZP>H_byPT z!&yjIRcs$R#4BF)GLUK%?m~z}wOn|XZATXr?*seGz}~{MT!xhX#yhHNWOfaJkLj)W zlnO^hSOd-pbtP0r5F3-8C}i}o%nWrwHmvDo zH6`T7)u1q|XIyst7^j|lI$1vnui!hSX*_KkP_J}eSu6v~MTp@n#qz1Xl~r#Sm&munw$^4;N8vZ2i!MK-c*cW&@o-@RrxT;iYVC&HMae zeXGutVQ~BoFyEMS{S9ZR>p3B7FU9E2N4D>L%<$SSLEneemWt2Lk2D6XQ;b!N(Zb<0 z+EoLXfVGNqF~O;v!MT_%IwKf_00bZ1IH=&g(gh`Ghx(4DiD)@`*%7Y2;S_aUu{JMA zBQOK($HH4%QO{;P=k$rlnu$1K3ri!RwE+zQG=6WpoZ^D349*fmm{qKe7IY4bI@G)80gH@=w7&3Wua7J`?d-f1Cp zdC+^$Y*usWk;51aexU0}b|DZ{6H`YkKumh7%23yl%v`c)d?jz3B#+uVHhp9;R&WT2 zV!ePBFfj&&5M3?=r3-=vMZ6L)7*VuRW|iTZ>z>1b?M-%f_J`VHVrVl&$ElXU8q4O^ zMxmfISXDIS3N7XVstRIoMwrbE&I*+STM4d`9?E8Y=G@;qA|bklwNN{unhA9+R0hPx z(fBSVOI;W6OK6%{L$p3}Q~-`%dWa*J9>Q8ntT6L7kf9rP4E^CO@Zn1jGn>zoj}}cZ zllpcR#OKk25uvJux;E4<3hgSWDzIi^c^R(D!ix-Okc zPpDAE)fL;@Te!-_M5^%p{CGF)7&@vM>j$>Bs4CZEBsV6k6{?xRW6=<>qEs$guC5Hu z8cg>2^qOQ`1DZ&R(e+9|1e3mZs`wy;5YQ0&i!p6hd{9J$#S%7BL&+M0brx$aLxnhY zcLz*%4kqflmRap+{IJgmV1&9#DO&(PSAs-osvNq~D_f8W^g|%^2j~fMP<>WVoFoww zAx{dwT>!kq)LLVv*|mYXHsA~)C?Sz?Y{40XDg0zWzySTB3J_u?7SNT3rZIw422Iuq z8e_96smnuO!W(7UBgwyzD9 zlQ{aIDLh8_QV8{o0U-4OR0peN1!*WwQvt73E@p-%6;C#WURMpF+F9tjR40T?EU7Rm zxWJT|AW9P`a zm@jN?7`6{uHfE8?t&2!lELz&u6M`1WP##4LgQg@0WhfY8hep_WdN&m?Yer3kqz3Kb zACkZ!Womu&l(8PO_4?bDrf0lgLrk?7OJ%u;y~9PTy~|iDkg`NWbPrWn?6)+_7N64d zu?bRdXaKy74rk3jdiL>N3RQ}hSIowm&d0*1XnK;mSCNfZ1I*=-jM}eU5mAWu6cK!u>p|; z%q8&NqGPewu~>SRZRi`G`F6bvOt(VAxi*NcvEi6#&M;xIh_Bc&PCW)6iPL)!qcPN) z6i!p(g?b)6`!u^ddwpwdoC-(8yRrxxN=8XSC{KLvDHeNs)bpCI6ZZEznq^2KC8edr ziZBm6rSwq=9yGCc4s$v6&nU=YGpEVfm6{foOK96Da0rZ;zVoSQs-bIRy=I&sF3>Dm z&OY=Q`+Ly^@>ugahAeflLS)L-Lhyk{9)6Uw4?oJutGDSop;^XRo{UvhXnl;yk$qzd zoaX~WR*GoROdFm#;sPDsy^n%7T}*!RjFK@>r9vx0U!uiLRD5L=XM~;edwl1C$GLc6 zKSh#qarFCQTVP?mQ7%P&$G85*x!$(Z;qErB2*XI-&p|y4}9x;Jo3;JG)+h8-qY6KC=BMI z<n%t<`2^N9zVK-&;issJe{IEcZW|C_dIuOHL+rTh?p$QK zXn6jMu3o5*59* z<*+uA=X?&cN|?gvq=w>)sp=nyq zKJpj`wq{&&?Fp>0{cH|sEQM1cQddGf%bi_=Nr*wBaG4u?m#4sDy6gQ=6Xa6XIf?hN z!mN&h*L8${?UWE2{``+`=U@K*ZXWyIZr}f#3SZK1b_-i`>b3#)(`;$Xml^_1)AFtR zALf!vwm5O}D9*vGj*U@Iz*v~g3>zDUx{kBJ)txNxCaBm8QHJEWY+1=cS>99CN?w)Yty0?o4J?mO>i zbHj1XwO8UKu=I)*nAgJAR*V#;iqOxN&k2qXv6}NN-@52Q0Mm= ztnvKYKYX2!edu4gu(KenEcYrGMHTNd7}mOkfU<2fM#2;sf)C2FX}J4q_w(3yALoTH zyP5-=h65YIY$H!&$Mr%vZ9jZ*@(gQ`~Q3eG*f!|(pi=eXz2huFKY zpqnH+{j_-AQ%vj(GA4v<@LHYbWCJ3m@L9>-(gR^ObG-NcKhB%q`YN28f!Rz8s7&n0 z5D1~gcTwWp%F2aNsesG@7;q+boGTYl0-yh@ukh)Q|2sS9_GucQdKK%qD_eW`K2Gv_ z1iVg5>-U7$pI8dLih^=8M!4bmr@8svKgJ7Ray=?DRO@7sKnPt-k^77=DNu{X2^yQJ z#A)-o4s>nH|9t%+KKrRJ@zpPX8|_zL6^u0zXEB#8)D zo;b?!XI;vVzUh@b|3%kw$&rJo#)d)c8@M>xs#yWrlVp_4Bf$9!yL{uGNBGk1-{h$$ zFL2ja9-ee|%aB$Yu{2Olc8BMcijqxUuS^7;E+~Fx1&kjx*f28q$y1lJu{r0GBip>> zrGS8rn?Lx>gO zNc`zqM;%a8SU#WOR+5F@SEiO8v_4kGGoxe(M8h%FbqXu58MFBauA`A=7tlh%tBqw% zKjFz}QqkfEA(Nh9GS5Uf3hkTz%>4Jfes=W0ZMg}6UHyw<-_8B!UHxSL`Jmi_j1m)A zx0-V`r(ca&Q}-PyBNPp~aN+dt{Neu8az7n$6K_x!{m98bgBI?pb*2o9k%ua;M&Fa6x$;JJAldL?-} zX$t3qawF&UGyN6Y^3(nC63U023{DY!4rNEU|ZhahoD{k6x;HS zUP1Xa3}Sq3-gfk(N$7ZNM?b