diff --git a/build.gradle b/build.gradle index a45da86..044bb37 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,6 @@ minecraft { mixin { add sourceSets.main, "${mod_id}.refmap.json" config "${mod_id}.mixins.json" - } sourceSets.main.resources { srcDir 'src/generated/resources' } @@ -122,13 +121,13 @@ dependencies { 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") +// implementation fg.deobf("curse.maven:freecam-by-zergatul-618947:5402097") } tasks.named('processResources', ProcessResources).configure { @@ -163,7 +162,9 @@ tasks.named('jar', Jar).configure { "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")]) + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + "MixinConfigs" : "${mod_id}.mixins.json" + ]) } finalizedBy 'reobfJar' @@ -186,7 +187,9 @@ tasks.register('deobfJar', Jar) { "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")]) + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + "MixinConfigs" : "${mod_id}.mixins.json" + ]) } dependsOn classes } diff --git a/gif/1.gif b/gif/1.gif deleted file mode 100644 index ef0e215..0000000 Binary files a/gif/1.gif and /dev/null differ diff --git a/gif/2.gif b/gif/2.gif deleted file mode 100644 index dd383d0..0000000 Binary files a/gif/2.gif and /dev/null differ diff --git a/gradle.properties b/gradle.properties index 3310497..f45cb84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ 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.4 +mod_version=1.20.1-0.0.7 mod_group_id=com.linearpast mod_authors=LostInLinearPast mod_description=A lib about capability and player animator. diff --git a/sccore/animation/sccore/animation.layer.json b/sccore/animation/sccore/animation.layer.json new file mode 100644 index 0000000..7ee2ac9 --- /dev/null +++ b/sccore/animation/sccore/animation.layer.json @@ -0,0 +1,10 @@ +[ + { + "key": "sccore:normal_layers", + "priority": 42 + }, + { + "key": "sccore:ride_layers", + "priority": 43 + } +] \ No newline at end of file diff --git a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac new file mode 100644 index 0000000..56226a7 --- /dev/null +++ b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac @@ -0,0 +1,2 @@ +// 1.20.1 2025-10-30T21:02:30.6951333 Languages: zh_cn +feac50843a3ec6d5a3ad4c4e917d0f19a2b857cb assets/sccore/lang/zh_cn.json diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 new file mode 100644 index 0000000..4b8c14a --- /dev/null +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -0,0 +1,2 @@ +// 1.20.1 2025-10-30T21:02:30.6972105 Languages: en_us +165168d6118e3dde833169d2ab1bc0028d425d55 assets/sccore/lang/en_us.json diff --git a/src/generated/resources/assets/sccore/lang/en_us.json b/src/generated/resources/assets/sccore/lang/en_us.json new file mode 100644 index 0000000..2fe98bf --- /dev/null +++ b/src/generated/resources/assets/sccore/lang/en_us.json @@ -0,0 +1,40 @@ +{ + "translation.sccore.animation.command_cooldown": "You cannot execute this command, cooling down: %s seconds.", + "translation.sccore.animation.unknown_animation": "Unknown animation %s, please check if your resource packs is complete.", + "translation.sccore.animation.without_animation_ride_entity": "Command run fail, full or unsupported animations.", + "translation.sccore.command.animation.accept_apply_expired": "Application expired.(%s minute(s))", + "translation.sccore.command.animation.accept_apply_success": "%s has accepted the application of %s.", + "translation.sccore.command.animation.accept_apply_too_far": "You are too far apart. (%s block(s))", + "translation.sccore.command.animation.accept_invite_expired": "Invite expired.(%s minute(s))", + "translation.sccore.command.animation.accept_invite_success": "Invitation accepted.", + "translation.sccore.command.animation.accept_invite_too_far": "You are too far apart. (%s block(s))", + "translation.sccore.command.animation.accept_message_click": "Click here to accept.", + "translation.sccore.command.animation.accept_request_expired": "Request expired.(%s minute(s))", + "translation.sccore.command.animation.accept_request_success": "Request accepted.", + "translation.sccore.command.animation.animation_json_path": "%s", + "translation.sccore.command.animation.animation_layer_not_present": "Animation layer is not present.", + "translation.sccore.command.animation.animation_not_present": "Animation is not present.", + "translation.sccore.command.animation.animation_to_json": "The animation %s has been stored in the path on %s:", + "translation.sccore.command.animation.applied_join_message": "%S§b§l Apply for §r to join your animation. ", + "translation.sccore.command.animation.apply_expired": "%s has accepted your animation application but the application has expired. (%s minute(s))", + "translation.sccore.command.animation.apply_join_message": "Application sent.", + "translation.sccore.command.animation.apply_success": "%s has accepted your animation application.", + "translation.sccore.command.animation.apply_too_far": "%s has accepted your animation application but you are too far apart. (%s block(s))", + "translation.sccore.command.animation.clear_animations": "Animation cleared.", + "translation.sccore.command.animation.command_run_fail": "Command run fail.", + "translation.sccore.command.animation.command_run_success": "Command run success.", + "translation.sccore.command.animation.invite_expired": "%s has accepted your animation invitation but the invitation has expired. (%s minute(s))", + "translation.sccore.command.animation.invite_message": "Invitation sent.", + "translation.sccore.command.animation.invite_success": "%s has accepted your animation invitation.", + "translation.sccore.command.animation.invite_too_far": "%s has accepted your animation invitation but you are too far apart. (%s block(s))", + "translation.sccore.command.animation.invited_message": "%s§c§l invites§r you to animation: %s. ", + "translation.sccore.command.animation.play_animation_fail": "Fail to play animation with: %s", + "translation.sccore.command.animation.play_animation_success": "Successfully played animation on %s player(s).", + "translation.sccore.command.animation.refresh_animations": "Animation refreshed.", + "translation.sccore.command.animation.remove_animation_fail": "Fail to remove animation with: %s", + "translation.sccore.command.animation.remove_animation_success": "Successfully removed animation on %s player(s).", + "translation.sccore.command.animation.request_expired": "%s has accepted your animation request but the request has expired. (%s minute(s))", + "translation.sccore.command.animation.request_message": "Request sent.", + "translation.sccore.command.animation.request_success": "%s has accepted your animation request.", + "translation.sccore.command.animation.requested_message": "%s§d§l requests§r you to animation: %s. " +} \ No newline at end of file diff --git a/src/generated/resources/assets/sccore/lang/zh_cn.json b/src/generated/resources/assets/sccore/lang/zh_cn.json new file mode 100644 index 0000000..b1e830a --- /dev/null +++ b/src/generated/resources/assets/sccore/lang/zh_cn.json @@ -0,0 +1,40 @@ +{ + "translation.sccore.animation.command_cooldown": "你不能执行该指令,冷却中:%s 秒。", + "translation.sccore.animation.unknown_animation": "未知的动画%s,请检查你的资源包是否完整。", + "translation.sccore.animation.without_animation_ride_entity": "命令执行错误,已满人或不支持的动画。", + "translation.sccore.command.animation.accept_apply_expired": "申请已超时。(%s分钟)", + "translation.sccore.command.animation.accept_apply_success": "%s 接受了 %s 的申请。", + "translation.sccore.command.animation.accept_apply_too_far": "你们距离太远了。(%s格)", + "translation.sccore.command.animation.accept_invite_expired": "邀请已超时。(%s分钟)", + "translation.sccore.command.animation.accept_invite_success": "已接受邀请。", + "translation.sccore.command.animation.accept_invite_too_far": "你们距离太远了。(%s格)", + "translation.sccore.command.animation.accept_message_click": "单击此处同意。", + "translation.sccore.command.animation.accept_request_expired": "请求已超时。(%s分钟)", + "translation.sccore.command.animation.accept_request_success": "已接受请求。", + "translation.sccore.command.animation.animation_json_path": "%s", + "translation.sccore.command.animation.animation_layer_not_present": "动画层不存在。", + "translation.sccore.command.animation.animation_not_present": "动画不存在。", + "translation.sccore.command.animation.animation_to_json": "动画%s已经存储到%s路径:", + "translation.sccore.command.animation.applied_join_message": "%s§b§l 申请§r加入动画。", + "translation.sccore.command.animation.apply_expired": "%s 接受了你的动画申请,但是申请超时了。(%s分钟)", + "translation.sccore.command.animation.apply_join_message": "已发送申请。", + "translation.sccore.command.animation.apply_success": "%s 接受了你的动画申请。", + "translation.sccore.command.animation.apply_too_far": "%s 接受了你的动画申请,但你们距离太远了。(%s格)", + "translation.sccore.command.animation.clear_animations": "动画已清除。", + "translation.sccore.command.animation.command_run_fail": "命令执行失败。", + "translation.sccore.command.animation.command_run_success": "命令执行成功。", + "translation.sccore.command.animation.invite_expired": "%s 接受了你的动画邀请,但是邀请超时了。(%s分钟)", + "translation.sccore.command.animation.invite_message": "已发送邀请。", + "translation.sccore.command.animation.invite_success": "%s 接受了你的动画邀请。", + "translation.sccore.command.animation.invite_too_far": "%s 接受了你的动画邀请,但你们距离太远了。(%s格)", + "translation.sccore.command.animation.invited_message": "%s§c§l 邀请§r你进行动画:%s。", + "translation.sccore.command.animation.play_animation_fail": "在这些玩家上播放动画失败:%s", + "translation.sccore.command.animation.play_animation_success": "在%s个玩家上播放动画成功。", + "translation.sccore.command.animation.refresh_animations": "动画同步状态已刷新。", + "translation.sccore.command.animation.remove_animation_fail": "在这些玩家上移除动画失败:%s", + "translation.sccore.command.animation.remove_animation_success": "在%s个玩家上移除动画成功。", + "translation.sccore.command.animation.request_expired": "%s 接受了你的动画请求,但是请求超时了。(%s分钟)", + "translation.sccore.command.animation.request_message": "已发送请求。", + "translation.sccore.command.animation.request_success": "%s 接受了你的动画请求。", + "translation.sccore.command.animation.requested_message": "%s§d§l 请求§r你进行动画:%s。" +} \ No newline at end of file diff --git a/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java b/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java index a66e3f6..14bc973 100644 --- a/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java +++ b/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java @@ -1,11 +1,11 @@ package com.linearpast.sccore; -import com.linearpast.sccore.animation.registry.AnimationRegistry; +import com.linearpast.sccore.animation.AnimationUtils; import com.linearpast.sccore.capability.CapabilityUtils; import com.linearpast.sccore.core.ModChannel; import com.linearpast.sccore.core.ModCommands; -import com.linearpast.sccore.core.ModConfigs; +import com.linearpast.sccore.core.configs.ModConfigs; import com.linearpast.sccore.example.animation.ModAnimation; import com.linearpast.sccore.example.capability.ModCapability; import net.minecraftforge.common.MinecraftForge; @@ -27,15 +27,14 @@ public class SnowyCrescentCore { public SnowyCrescentCore() { ModLoadingContext modLoadingContext = ModLoadingContext.get(); - modLoadingContext.registerConfig(ModConfig.Type.COMMON, ModConfigs.Common.SPEC); + modLoadingContext.registerConfig(ModConfig.Type.SERVER, ModConfigs.Server.SPEC); IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); IEventBus forgeBus = MinecraftForge.EVENT_BUS; CapabilityUtils.registerHandler(forgeBus); ModChannel.register(); - AnimationRegistry.register(); - AnimationRegistry.addAnimationListener(forgeBus, modBus); + AnimationUtils.register(forgeBus, modBus); ModCommands.registerCommands(forgeBus, modBus); if(!FMLEnvironment.production || Boolean.getBoolean(ENABLE_EXAMPLES_PROPERTY_KEY)) { diff --git a/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java b/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java index c7f317f..9f17804 100644 --- a/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java +++ b/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java @@ -10,6 +10,7 @@ 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 com.linearpast.sccore.core.datagen.ModLang; import dev.kosmx.playerAnim.api.layered.IAnimation; import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; import dev.kosmx.playerAnim.api.layered.ModifierLayer; @@ -17,7 +18,11 @@ 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.ChatFormatting; +import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.api.distmarker.Dist; @@ -25,10 +30,14 @@ import net.minecraftforge.api.distmarker.OnlyIn; import org.jetbrains.annotations.Nullable; import java.util.Objects; +import java.util.UUID; public class AnimationPlayer { - public static void requestAnimationToServer(ResourceLocation layer, @Nullable ResourceLocation animation) { - ModChannel.sendToServer(new PlayAnimationRequestPacket(layer, animation)); + + public static void requestAnimationToServer(@Nullable AbstractClientPlayer player, ResourceLocation layer, @Nullable ResourceLocation animation) { + UUID uuid = null; + if(player != null) uuid = player.getUUID(); + ModChannel.sendToServer(new PlayAnimationRequestPacket(uuid, layer, animation)); } public static boolean serverPlayAnimation(ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { @@ -43,12 +52,10 @@ public class AnimationPlayer { 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(); + AnimationDataCapability.getCapability(serverPlayer).ifPresent(IAnimationCapability::removeRiderAnimation); return true; } } @@ -63,19 +70,26 @@ public class AnimationPlayer { 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); + public static void syncAnimation(ServerPlayer player, ServerPlayer target) { + ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID()), player); + ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID()), target); } @SuppressWarnings("unchecked") @OnlyIn(Dist.CLIENT) - public static void syncAnimation(AbstractClientPlayer clientPlayer, AbstractClientPlayer target, ResourceLocation layer) { + public static void syncAnimation(AbstractClientPlayer clientPlayer, AbstractClientPlayer target) { try { + IAnimationCapability clientPlayerData = AnimationDataCapability.getCapability(clientPlayer).orElse(null); + if(clientPlayerData == null) return; + IAnimationCapability targetData = AnimationDataCapability.getCapability(target).orElse(null); + if(targetData == null) return; + ResourceLocation clientPlayerLayer = clientPlayerData.getRiderAnimLayer(); + ResourceLocation targetLayer = targetData.getRiderAnimLayer(); + if(clientPlayerLayer == null || targetLayer == null) return; ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(clientPlayer).get(layer); + .getPlayerAssociatedData(clientPlayer).get(clientPlayerLayer); ModifierLayer targetModifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(target).get(layer); + .getPlayerAssociatedData(target).get(targetLayer); if(modifierLayer == null || targetModifierLayer == null) return; IMixinKeyframeAnimationPlayer animation = (IMixinKeyframeAnimationPlayer) modifierLayer.getAnimation(); KeyframeAnimationPlayer targetAnimation = (KeyframeAnimationPlayer) targetModifierLayer.getAnimation(); @@ -87,8 +101,11 @@ public class AnimationPlayer { @SuppressWarnings("unchecked") @OnlyIn(Dist.CLIENT) - public static void playAnimation(AbstractClientPlayer clientPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { + public static void playAnimation(@Nullable AbstractClientPlayer clientPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { try { + LocalPlayer localPlayer = Minecraft.getInstance().player; + if(clientPlayer == null) clientPlayer = localPlayer; + if(clientPlayer == null) return; ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess .getPlayerAssociatedData(clientPlayer).get(layer); if(animation == null) { @@ -104,7 +121,14 @@ public class AnimationPlayer { if(anim == null) return; if(modifierLayer == null) return; KeyframeAnimation keyframeAnimation = anim.getAnimation(); - if(keyframeAnimation == null) return; + if(keyframeAnimation == null) { + if(localPlayer == null) return; + localPlayer.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.UNKNOWN_ANIMATION.getKey(), + animation.toString() + ).withStyle(ChatFormatting.RED)); + return; + }; Objects.requireNonNull(modifierLayer).replaceAnimationWithFade( AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), new KeyframeAnimationPlayer(keyframeAnimation) diff --git a/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java b/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java index cc32e8b..3a15218 100644 --- a/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java +++ b/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java @@ -5,13 +5,15 @@ 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.PlayerTickEvent; import com.linearpast.sccore.animation.event.client.CameraAnglesModify; import com.linearpast.sccore.animation.event.client.ClientPlayerTick; +import com.linearpast.sccore.animation.event.client.EntityRendererRegisterEvent; 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.animation.register.AnimationCapabilities; +import com.linearpast.sccore.animation.register.AnimationChannels; +import com.linearpast.sccore.animation.register.AnimationEntities; +import com.linearpast.sccore.animation.register.AnimationRegistry; import com.linearpast.sccore.core.ModChannel; import com.linearpast.sccore.core.ModLazyRun; import dev.kosmx.playerAnim.api.layered.IAnimation; @@ -40,14 +42,15 @@ public class AnimationUtils { @Override public void addCommonListener(IEventBus forgeBus, IEventBus modBus) { AnimationEntities.register(modBus); - modBus.addListener(AnimationLayerRegistry::onCommonSetUp); + forgeBus.addListener(AnimationRegistry::onServerStarted); + forgeBus.addListener(AnimationRegistry::onPlayerLoggedIn); + forgeBus.addListener(PlayerTickEvent::onPlayerTickEvent); } @Override public void addClientListener(IEventBus forgeBus, IEventBus modBus) { forgeBus.addListener(CameraAnglesModify::changeCameraView); - modBus.addListener(AnimationLayerRegistry::onClientSetup); - modBus.addListener(EntityRendererRegistry::registerEntityRenderer); + modBus.addListener(EntityRendererRegisterEvent::registerEntityRenderer); forgeBus.addListener(ClientPlayerTick::onPlayerTick); forgeBus.addListener(ClientPlayerTick::delayRuns); } @@ -56,21 +59,21 @@ public class AnimationUtils { /** *
      * Play animation.
-     * If run in Dist.CLIENT, the serverPlayer can be null.
+     * If run in Dist.CLIENT, player can be null, it will play animation only client.
      * If animation be null, it will remove animation on layer.
      * 
- * @param serverPlayer Target player + * @param player Target player * @param layer Target layer * @param animation Animation * @return If success */ - public static boolean playAnimation(@Nullable ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { + public static boolean playAnimation(@Nullable Player player, ResourceLocation layer, @Nullable ResourceLocation animation) { return ANIMATION_RUNNER.testLoadedAndCall(() -> { if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) { - if(serverPlayer != null) { + if(player instanceof ServerPlayer serverPlayer) { return AnimationPlayer.serverPlayAnimation(serverPlayer, layer, animation); - }else { - AnimationPlayer.requestAnimationToServer(layer, animation); + }else if(player == null || player instanceof AbstractClientPlayer) { + AnimationPlayer.requestAnimationToServer((AbstractClientPlayer) player, layer, animation); return true; } } @@ -78,6 +81,24 @@ public class AnimationUtils { }); } + /** + * Client send request to server and run play animation.
+ * Only play animation with client self. + * @param layer Target layer + * @param animation Target animation + * @return If success + */ + @OnlyIn(Dist.CLIENT) + public static boolean requestAnimationClient(@Nullable AbstractClientPlayer player, ResourceLocation layer, @Nullable ResourceLocation animation) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) { + AnimationPlayer.requestAnimationToServer(player, layer, animation); + return true; + } + return false; + }); + } + /** *
      * Play animation with ride. Player will ride an entity, then play animation.
@@ -100,7 +121,8 @@ public class AnimationUtils {
                 if(serverPlayer != null) {
                     if(serverPlayer.getVehicle() != null && force) serverPlayer.unRide();
                     else if(serverPlayer.getVehicle() != null) return false;
-                    AnimationPlayer.playAnimationWithRide(serverPlayer, layer, animation, true);
+                    return AnimationPlayer.playAnimationWithRide(serverPlayer, layer, animation, true);
+
                 } else {
                     AnimationPlayer.requestAnimationRideToServer(layer, animation, force);
                     return true;
@@ -113,12 +135,12 @@ public class AnimationUtils {
     /**
      * Remove animation.
      * @see AnimationUtils#playAnimation
-     * @param serverPlayer Target player
+     * @param player Target player
      * @param layer Target layer
      * @return If success
      */
-    public static boolean removeAnimation(@Nullable ServerPlayer serverPlayer, ResourceLocation layer) {
-        return playAnimation(serverPlayer, layer, null);
+    public static boolean removeAnimation(@Nullable Player player, ResourceLocation layer) {
+        return playAnimation(player, layer, null);
     }
 
     /**
@@ -147,20 +169,20 @@ public class AnimationUtils {
     }
 
     /**
-     * Test if layer exist animation which is playing.
+     * Test if layer exist animation which is not stop.
      * 

* Only in dist client * @param player Target player * @param layer Target layer - * @return If layer exist animation which is playing + * @return True when the currentTick not larger than stopTick */ @OnlyIn(Dist.CLIENT) @SuppressWarnings("unchecked") - public static boolean isClientAnimationPlaying(AbstractClientPlayer player, @Nullable ResourceLocation layer) { + public static boolean isClientAnimationNotStop(AbstractClientPlayer player, @Nullable ResourceLocation layer) { return ANIMATION_RUNNER.testLoadedAndCall(() -> { try { Set resourceLocations = new HashSet<>(); - if(layer == null) resourceLocations = AnimationLayerRegistry.getAnimLayers().keySet(); + if(layer == null) resourceLocations = AnimationRegistry.getLayers().keySet(); else resourceLocations.add(layer); for (ResourceLocation location : resourceLocations) { ModifierLayer animationModifierLayer = (ModifierLayer) PlayerAnimationAccess @@ -177,25 +199,55 @@ public class AnimationUtils { }); } + /** + * Test if layer exist animation which is not end. + *

+ * Only in dist client + * @param player Target player + * @param layer Target layer + * @return True when animation is loop, or currentTick not larger than endTick + */ + @OnlyIn(Dist.CLIENT) + @SuppressWarnings("unchecked") + public static boolean isClientAnimationNotEnd(AbstractClientPlayer player, @Nullable ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + try { + Set resourceLocations = new HashSet<>(); + if(layer == null) resourceLocations = AnimationRegistry.getLayers().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(); + boolean isLoop = animation.getData().isInfinite; + int endTick = animation.getData().endTick; + return isLoop || currentTick <= endTick; + } + } 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)); + public static void syncAnimation(ServerPlayer player, ServerPlayer target) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target)); } /** * 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)); + public static void syncAnimation(AbstractClientPlayer player, AbstractClientPlayer target) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target)); } /** @@ -266,43 +318,34 @@ public class AnimationUtils { } /** - * Test if layer exist and has been register. + * Test if layer exist and has been invite. * @param layer Target layer - * @return If layer exist and has been register + * @return If layer exist and has been invite */ public static boolean isAnimationLayerPresent(ResourceLocation layer) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationLayerRegistry.isLayerPresent(layer)); + return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.getLayers().containsKey(layer)); } /** - * Test if animation exist and has been register. + * Test if animation exist and has been invite. * @param location Animation resource location - * @return If animation exist and has been register + * @return If animation exist and has been invited */ public static boolean isAnimationPresent(ResourceLocation location) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.isAnimationPresent(location)); + return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.getAnimations().containsKey(location)); } /** - * Register an animation through static function - * @param location Animation resource location - * @param animation Animation data + * The register handler + * @param forgeBus Forge event bus + * @param modBus Mod event bus */ - 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)); + public static void register(IEventBus forgeBus, IEventBus modBus){ + AnimationUtils.ANIMATION_RUNNER.testLoadedAndRun(() -> { + AnimationCapabilities.registerAnimationCapability(); + AnimationChannels.registerChannel(); + }); + AnimationUtils.ANIMATION_RUNNER.testLoadedAndAddListener(forgeBus, modBus); } /** @@ -314,7 +357,7 @@ public class AnimationUtils { */ @Nullable public static Animation getAnimation(ResourceLocation location) { - return AnimationRegistry.getAnimation(location); + return AnimationRegistry.getAnimations().getOrDefault(location, null); } /** @@ -377,11 +420,13 @@ public class AnimationUtils { 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)) { + boolean dirty = false; + for (ResourceLocation layer : Set.copyOf(oldLayers)) { + if (!isClientAnimationNotStop(clientPlayer, layer)) { oldLayers.remove(layer); + dirty = true; } - }); - ModChannel.sendToServer(new RefreshAnimationPacket(oldLayers)); + } + if(dirty) 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 index 474d237..083de9d 100644 --- a/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java +++ b/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java @@ -3,8 +3,8 @@ 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.animation.register.AnimationRegistry; import com.linearpast.sccore.capability.CapabilityUtils; import com.linearpast.sccore.capability.data.ICapabilitySync; import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync; @@ -12,6 +12,7 @@ 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.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; @@ -23,15 +24,20 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen public static final String AnimMap = "AnimMap"; public static final String RideAnimLayer = "RideAnimLayer"; + public static final String RideAnimation = "RideAnimation"; private final Map animMap = new HashMap<>(); private ResourceLocation rideAnimLayer; + private ResourceLocation rideAnimation; @Override public void mergeAnimations(Map animations) { animations.forEach((key, value) -> { - if (AnimationLayerRegistry.getAnimLayers().containsKey(key)) { + if (AnimationRegistry.getLayers().containsKey(key)) { if (AnimationUtils.isAnimationPresent(value)) { + if(this.rideAnimLayer.equals(key)) { + removeRiderAnimation(); + } this.animMap.put(key, value); setDirty(true); } @@ -41,8 +47,11 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen @Override public boolean mergeAnimation(ResourceLocation layer, ResourceLocation animation) { - if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) { + if (AnimationRegistry.getLayers().containsKey(layer)) { if (AnimationUtils.isAnimationPresent(animation)) { + if(this.rideAnimLayer.equals(layer)) { + removeRiderAnimation(); + } this.animMap.put(layer, animation); setDirty(true); return true; @@ -84,14 +93,34 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen } @Override - public void setRideAnimLayer(ResourceLocation rideAnimLayer) { - this.rideAnimLayer = rideAnimLayer; - setDirty(true); + public ResourceLocation getRiderAnimLayer() { + return rideAnimLayer; } @Override - public ResourceLocation getRideAnimLayer() { - return rideAnimLayer; + public ResourceLocation getRiderAnimation() { + return rideAnimation; + } + + @Override + public void setRiderAnimation(@NotNull ResourceLocation layer, @NotNull ResourceLocation animation) { + if(AnimationUtils.isAnimationLayerPresent(layer)) { + if(AnimationUtils.isAnimationPresent(animation)) { + this.rideAnimLayer = layer; + this.rideAnimation = animation; + if(animMap.get(layer) != null) { + animMap.remove(layer); + } + setDirty(true); + } + } + } + + @Override + public void removeRiderAnimation() { + this.rideAnimLayer = null; + this.rideAnimation = null; + setDirty(true); } @Override @@ -99,6 +128,8 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen IAnimationCapability data = (IAnimationCapability) oldData; this.animMap.clear(); this.animMap.putAll(data.getAnimations()); + this.rideAnimLayer = data.getRiderAnimLayer(); + this.rideAnimation = data.getRiderAnimation(); } @Override @@ -111,12 +142,15 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen tag.put(AnimMap, animMapTag); } if(rideAnimLayer != null) tag.putString(RideAnimLayer, rideAnimLayer.toString()); + if(rideAnimation != null) tag.putString(RideAnimation, rideAnimation.toString()); return tag; } @Override public void fromTag(CompoundTag tag) { this.animMap.clear(); + this.rideAnimLayer = null; + this.rideAnimation = null; if(tag.contains(AnimMap)) { CompoundTag animMapTag = tag.getCompound(AnimMap); animMapTag.getAllKeys().forEach(key -> this.animMap.put( @@ -125,6 +159,7 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen )); } if(tag.contains(RideAnimLayer)) this.rideAnimLayer = new ResourceLocation(tag.getString(RideAnimLayer)); + if(tag.contains(RideAnimation)) this.rideAnimation = new ResourceLocation(tag.getString(RideAnimation)); } @Override @@ -138,9 +173,10 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen 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; + if(rideAnimLayer != null && !AnimationUtils.isAnimationLayerPresent(rideAnimLayer)) { + removeRiderAnimation(); + } } public static Optional getCapability(Player player){ 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 index 454db91..ab07430 100644 --- a/src/main/java/com/linearpast/sccore/animation/capability/inter/IAnimationCapability.java +++ b/src/main/java/com/linearpast/sccore/animation/capability/inter/IAnimationCapability.java @@ -16,6 +16,8 @@ public interface IAnimationCapability extends ICapabilitySync { void clearAnimations(); boolean isAnimationPresent(ResourceLocation layer); - void setRideAnimLayer(ResourceLocation layer); - ResourceLocation getRideAnimLayer(); + ResourceLocation getRiderAnimLayer(); + ResourceLocation getRiderAnimation(); + void setRiderAnimation(ResourceLocation layer, ResourceLocation animation); + void removeRiderAnimation(); } diff --git a/src/main/java/com/linearpast/sccore/animation/command/ApplyJoinAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/ApplyJoinAnimCommand.java new file mode 100644 index 0000000..a0406bd --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/ApplyJoinAnimCommand.java @@ -0,0 +1,191 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.linearpast.sccore.core.configs.ModConfigs; +import com.linearpast.sccore.core.datagen.ModLang; +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.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; + +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 ApplyJoinAnimCommand { + private static final Map> applies = new HashMap<>(); + record ApplyRecord(long time, boolean isForce){} + private static final Map lastAppliedMap = new HashMap<>(); + public static void register(LiteralArgumentBuilder animCommand) { + animCommand + .then(literal("joinApply") + .then(argument("target", EntityArgument.player()) + .executes(ApplyJoinAnimCommand::tryJoinAnimation) + .then(argument("force", BoolArgumentType.bool()) + .executes(ApplyJoinAnimCommand::tryJoinAnimation) + ) + ) + .then(literal("accept") + .then(argument("player", EntityArgument.player()) + .executes(ApplyJoinAnimCommand::acceptJoinAnimation) + ) + ) + ); + } + + private static int tryJoinAnimation(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + boolean force = false; + try {force = BoolArgumentType.getBool(context, "force");} + catch (Exception ignored) {} + ServerPlayer target = EntityArgument.getPlayer(context, "target"); + ServerPlayer player; + try {player = EntityArgument.getPlayer(context, "player");} + catch (Exception e) { player = source.getPlayerOrException(); } + + Entity vehicle = target.getVehicle(); + if(!(vehicle instanceof AnimationRideEntity rideEntity) || !rideEntity.canAddPassenger(player)) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.WITHOUT_ANIMATION_RIDE_ENTITY.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + + //cooldown + Long lastApplied = lastAppliedMap.getOrDefault(player.getUUID(), null); + long now = System.currentTimeMillis(); + int applyCooldown = ModConfigs.Server.applyCooldown.get() * 1000; + if(!(lastApplied == null || now - lastApplied > applyCooldown)) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_COOLDOWN.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + lastAppliedMap.put(player.getUUID(), now); + + UUID rideEntityUUID = rideEntity.getUUID(); + Map applyRecordMap = applies.getOrDefault(rideEntityUUID, new HashMap<>()); + applyRecordMap.put(player.getUUID(), new ApplyRecord(now, force)); + applies.put(rideEntityUUID, applyRecordMap); + + //click event + Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim joinApply accept " + player.getName().getString()) + ).withUnderlined(true); + + //send message to all participants + for (Entity passenger : rideEntity.getPassengers()) { + passenger.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.APPLIED_JOIN_MESSAGE.getKey(), + player.getName().copy() + ).append(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() + ).setStyle(pStyle))); + } + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.APPLY_JOIN_MESSAGE.getKey() + ).withStyle(ChatFormatting.GREEN), true); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int acceptJoinAnimation(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer target = source.getPlayerOrException(); + ServerPlayer player = EntityArgument.getPlayer(context, "player"); + if(!(target.getVehicle() instanceof AnimationRideEntity rideEntity) || !rideEntity.canAddPassenger(player)) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.WITHOUT_ANIMATION_RIDE_ENTITY.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + + Map applyRecordMap = applies.getOrDefault(rideEntity.getUUID(), null); + if(applyRecordMap == null) throw new Exception(); + ApplyRecord applyRecord = applyRecordMap.getOrDefault(player.getUUID(), null); + + //test if expired + long now = System.currentTimeMillis(); + Integer applyDuration = ModConfigs.Server.applyDuration.get(); + if(now - applyRecord.time > applyDuration * 1000 || applyDuration == 0) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_APPLY_EXPIRED.getKey(), + applyDuration + ).withStyle(ChatFormatting.RED)); + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.APPLY_EXPIRED.getKey(), + target.getName().copy(), + applyDuration + ).withStyle(ChatFormatting.RED)); + return 0; + } + + //test if in range + Integer applyDistance = ModConfigs.Server.applyDistance.get(); + if(player.position().distanceToSqr(rideEntity.position()) > applyDistance * applyDistance || applyDistance == 0) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_APPLY_TOO_FAR.getKey(), + applyDistance + ).withStyle(ChatFormatting.RED)); + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.APPLY_TOO_FAR.getKey(), + target.getName().copy(), + applyDistance + ).withStyle(ChatFormatting.RED)); + return 0; + } + applyRecordMap.remove(player.getUUID()); + applies.put(player.getUUID(), applyRecordMap); + + //define message + MutableComponent successMessage = Component.translatable( + ModLang.TranslatableMessage.ACCEPT_APPLY_SUCCESS.getKey(), + target.getName().copy(), + player.getName().copy() + ).withStyle(ChatFormatting.GREEN); + + //play + AnimationUtils.joinAnimation(player, target, applyRecord.isForce); + + //send message + source.sendSuccess(() -> successMessage, true); + for (Entity passenger : rideEntity.getPassengers()) { + if(!passenger.getUUID().equals(target.getUUID()) && !passenger.getUUID().equals(player.getUUID())) { + passenger.sendSystemMessage(successMessage); + } + } + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.APPLY_SUCCESS.getKey(), + target.getName().copy() + ).withStyle(ChatFormatting.GREEN)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/CombineAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/CombineAnimCommand.java new file mode 100644 index 0000000..a0df00b --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/CombineAnimCommand.java @@ -0,0 +1,178 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +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.core.configs.ModConfigs; +import com.linearpast.sccore.core.datagen.ModLang; +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 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 CombineAnimCommand { + private static final Map lastInvitedMap = new HashMap<>(); + record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean isForce){} + private static final Map> invites = new HashMap<>(); + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("invite") + .then(argument("player", EntityArgument.player()) + .then(argument("layer", AnimationLayerArgument.layer()) + .then(argument("anim", AnimationArgument.animation()) + .executes(CombineAnimCommand::invite) + .then(argument("force", BoolArgumentType.bool()) + .executes(CombineAnimCommand::invite) + ) + ) + ) + ) + .then(literal("accept") + .then(argument("player", EntityArgument.player()) + .executes(CombineAnimCommand::acceptInvite) + ) + ) + ); + } + + private static int invite(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"); + + //cooldown + Long lastInvited = lastInvitedMap.getOrDefault(player.getUUID(), null); + long now = System.currentTimeMillis(); + int inviteCooldown = ModConfigs.Server.inviteCooldown.get() * 1000; + if(!(lastInvited == null || now - lastInvited > inviteCooldown)) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_COOLDOWN.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + lastInvitedMap.put(player.getUUID(), now); + + String layerString = AnimationLayerArgument.getLayer(context, "layer"); + String animString = AnimationArgument.getAnimation(context, "anim"); + ResourceLocation layer = new ResourceLocation(layerString); + ResourceLocation anim = new ResourceLocation(animString); + + //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, force)); + invites.put(player.getUUID(), inviteRecordMap); + + //click event + Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim invite accept " + player.getName().getString()) + ).withUnderlined(true); + //send message + target.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.INVITED_MESSAGE.getKey(), + player.getName().copy(), + anim.toString() + ).append(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() + ).setStyle(pStyle))); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.INVITE_MESSAGE.getKey() + ).withStyle(ChatFormatting.GREEN), true); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + 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(); + + //test if expired + long now = System.currentTimeMillis(); + Integer inviteDuration = ModConfigs.Server.inviteDuration.get(); + if(now - inviteRecord.time > inviteDuration * 1000 || inviteDuration == 0) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_INVITE_EXPIRED.getKey(), + inviteDuration + ).withStyle(ChatFormatting.RED)); + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.INVITE_EXPIRED.getKey(), + target.getName().copy(), + inviteDuration + ).withStyle(ChatFormatting.RED)); + return 0; + } + + //test if in range + Integer inviteDistance = ModConfigs.Server.inviteDistance.get(); + if(player.position().distanceToSqr(target.position()) > inviteDistance * inviteDistance || inviteDistance == 0) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_INVITE_TOO_FAR.getKey(), + inviteDistance + ).withStyle(ChatFormatting.RED)); + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.INVITE_TOO_FAR.getKey(), + target.getName().copy(), + inviteDistance + ).withStyle(ChatFormatting.RED)); + return 0; + } + inviteRecordMap.remove(target.getUUID()); + invites.put(player.getUUID(), inviteRecordMap); + + //play animation + AnimationUtils.startAnimationTogether(player, inviteRecord.layer, inviteRecord.animation, inviteRecord.isForce, target); + + //send message + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.ACCEPT_INVITE_SUCCESS.getKey() + ).withStyle(ChatFormatting.GREEN), true); + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.INVITE_SUCCESS.getKey(), + target.getName().copy() + ).withStyle(ChatFormatting.GREEN)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/GenerateJsonCommand.java b/src/main/java/com/linearpast/sccore/animation/command/GenerateJsonCommand.java new file mode 100644 index 0000000..8af7476 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/GenerateJsonCommand.java @@ -0,0 +1,168 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.util.AnimJson; +import com.linearpast.sccore.animation.data.util.AnimLayerJson; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.core.datagen.ModLang; +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.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.storage.LevelResource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class GenerateJsonCommand { + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("json").requires(cs -> cs.hasPermission(2)) + .then(literal("clearFile").executes(GenerateJsonCommand::clearJson)) + .then(literal("generate") + .then(literal("anim") + .then(literal("example").executes(GenerateJsonCommand::generateExample)) + .executes(context -> generateJson(context, false, false)) + .then(argument("reset", BoolArgumentType.bool()) + .executes(context -> + generateJson(context, false, BoolArgumentType.getBool(context, "reset")) + ) + ) + ) + .then(literal("layer") + .executes(context -> generateJson(context, true, false)) + .then(argument("reset", BoolArgumentType.bool()) + .executes(context -> + generateJson(context, true, BoolArgumentType.getBool(context, "reset")) + ) + ) + ) + ) + ); + } + + //clear path, remove file. + private static void clearPath(Path dir) throws IOException { + if (!Files.exists(dir)) return; + try (var pathStream = Files.walk(dir)) { + pathStream.sorted(Comparator.reverseOrder()).forEach(path -> { + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (RuntimeException ignored) {} + } + + private static int generateJson(CommandContext context, boolean isLayer, boolean isReset) { + CommandSourceStack source = context.getSource(); + try { + //get animation path + Path animationPath = getAnimationPath(source); + if (!Files.exists(animationPath)) { + try {Files.createDirectories(animationPath);} + catch (IOException e) { throw new RuntimeException(e); } + } + if(isReset) clearPath(animationPath); + + //generate json layer or animation + if(isLayer) { + //generate + Path path = AnimLayerJson.Writer.syntaxImmediately(animationPath); + //send message + MutableComponent component = Component.translatable( + ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), + "layer", "Server" + ).withStyle(ChatFormatting.GREEN); + component.append(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), + path.toString() + )); + source.sendSuccess(() -> component, true); + } else { + //generate + for (Animation value : AnimationRegistry.getAnimations().values()) { + AnimJson.Writer.stream(animationPath, value).syntax(); + } + //send message + MutableComponent component = Component.translatable( + ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), + "anim", "Server" + ).withStyle(ChatFormatting.GREEN); + component.append(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), + animationPath.toString() + )); + } + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int clearJson(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + //clear path + Path animationPath = getAnimationPath(source); + clearPath(animationPath); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int generateExample(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + //generate + Path animationPath = getAnimationPath(source); + Path path = AnimJson.Writer.syntaxExample(animationPath); + //send message + MutableComponent component = Component.translatable( + ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), + "anim example", "Server" + ).withStyle(ChatFormatting.GREEN); + component.append(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), + path.toString() + )); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + /** + * Get animation path + * @param source command source + * @return path + */ + private static Path getAnimationPath(CommandSourceStack source) { + MinecraftServer server = source.getServer(); + Path dataPackPath = server.getWorldPath(LevelResource.DATAPACK_DIR); + return dataPackPath.resolve("animation"); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/PlayAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/PlayAnimCommand.java new file mode 100644 index 0000000..3752911 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/PlayAnimCommand.java @@ -0,0 +1,258 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +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.linearpast.sccore.core.datagen.ModLang; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +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 PlayAnimCommand { + public static void register(LiteralArgumentBuilder animCommand){ + RequiredArgumentBuilder animCommandParam = argument("layer", AnimationLayerArgument.layer()) + .then(argument("animation", AnimationArgument.animation()) + .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("playSelf") + .then(animCommandParam)) + .then(literal("play") + .then(argument("players", EntityArgument.players()) + .requires(cs -> cs.hasPermission(2)) + .then(animCommandParam) + )) + .then(literal("remove") + .executes(PlayAnimCommand::clearAnimation) + .then(argument("players", EntityArgument.players()) + .requires(cs -> cs.hasPermission(2)) + .executes(PlayAnimCommand::clearAnimation) + .then(argument("layer", AnimationLayerArgument.layer()) + .executes(PlayAnimCommand::removeAnimation) + ) + ) + .then(argument("layer", AnimationLayerArgument.layer()) + .executes(PlayAnimCommand::removeAnimation) + ) + ); + } + + private static int playAnimation(CommandContext context, boolean withRide) { + CommandSourceStack source = context.getSource(); + try { + Collection players = null; + ServerPlayer player = null; + try {players = EntityArgument.getPlayers(context, "players");} + catch (Exception ignored) { player = source.getPlayerOrException(); } + String animation = AnimationArgument.getAnimation(context, "animation"); + String layer = AnimationLayerArgument.getLayer(context, "layer"); + 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.translatable( + ModLang.TranslatableMessage.ANIMATION_NOT_PRESENT.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + if(!layerPresent) { + source.sendFailure(Component.literal( + ModLang.TranslatableMessage.ANIMATION_LAYER_NOT_PRESENT.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + + //play with players + if(players != null) { + Set playerSet = Set.copyOf(players); + Collection finalPlayers = players; + playerSet.forEach(p -> { + if(withRide) { + boolean forced = false; + try { forced = BoolArgumentType.getBool(context, "forced");} + catch (Exception ignored) {} + if(AnimationUtils.playAnimationWithRide(p, layerLocation, animLocation, forced)) { + finalPlayers.remove(p); + } + } else { + if (AnimationUtils.playAnimation(p, layerLocation, animLocation)) { + finalPlayers.remove(p); + } + } + }); + + int successNum = playerSet.size() - players.size(); + if(successNum > 0) { + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.PLAY_ANIMATION_SUCCESS.getKey(), + successNum + ).withStyle(ChatFormatting.GREEN), true); + } + List list = players.stream().toList(); + if(!list.isEmpty()) { + MutableComponent failPlayers = Component.literal(""); + for (int i = 0; i < list.size(); i++) { + failPlayers.append(list.get(i).getName().copy()); + if (i < list.size() - 1) { + failPlayers.append(", "); + } + } + failPlayers.append("."); + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.PLAY_ANIMATION_FAIL.getKey(), + failPlayers + ).withStyle(ChatFormatting.RED)); + } + } + + //play with self + if(player != null) { + if(withRide) { + boolean forced = false; + try { forced = BoolArgumentType.getBool(context, "forced");} + catch (Exception ignored) {} + AnimationUtils.playAnimationWithRide(player, layerLocation, animLocation, forced); + } else { + AnimationUtils.playAnimation(player, layerLocation, animLocation); + } + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_SUCCESS.getKey() + ).withStyle(ChatFormatting.GREEN), true); + } + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int removeAnimation(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + Collection players = null; + ServerPlayer player = null; + try {players = EntityArgument.getPlayers(context, "players");} + catch (Exception ignored) { player = source.getPlayerOrException(); } + String layer = AnimationLayerArgument.getLayer(context, "layer"); + ResourceLocation layerLocation = new ResourceLocation(layer); + boolean layerPresent = AnimationUtils.isAnimationLayerPresent(layerLocation); + if(!layerPresent) { + source.sendFailure(Component.literal( + ModLang.TranslatableMessage.ANIMATION_LAYER_NOT_PRESENT.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + + //remove with players + if(players != null) { + Set playerSet = Set.copyOf(players); + Collection finalPlayers = players; + playerSet.forEach(p -> { + if(p.getVehicle() instanceof AnimationRideEntity rideEntity && rideEntity.getLayer().equals(layerLocation)) { + p.unRide(); + finalPlayers.remove(p); + } + if (AnimationUtils.removeAnimation(p, layerLocation)) { + finalPlayers.remove(p); + } + }); + int successNum = playerSet.size() - players.size(); + if(successNum > 0) { + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.REMOVE_ANIMATION_SUCCESS.getKey(), + successNum + ).withStyle(ChatFormatting.GREEN), true); + } + List list = players.stream().toList(); + if(!list.isEmpty()) { + MutableComponent failPlayers = Component.literal(""); + for (int i = 0; i < list.size(); i++) { + failPlayers.append(list.get(i).getName().copy()); + if (i < list.size() - 1) { + failPlayers.append(", "); + } + } + failPlayers.append("."); + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.REMOVE_ANIMATION_FAIL.getKey(), + failPlayers + ).withStyle(ChatFormatting.RED)); + } + } + + //remove with self + if(player != null) { + if(player.getVehicle() instanceof AnimationRideEntity rideEntity && rideEntity.getLayer().equals(layerLocation)) { + player.unRide(); + } + AnimationUtils.removeAnimation(player, layerLocation); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_SUCCESS.getKey() + ).withStyle(ChatFormatting.GREEN), true); + } + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int clearAnimation(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + Collection players; + try {players = EntityArgument.getPlayers(context, "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.translatable( + ModLang.TranslatableMessage.CLEAR_ANIMATIONS.getKey() + ).withStyle(ChatFormatting.GREEN), true); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/PlayAnimationCommand.java b/src/main/java/com/linearpast/sccore/animation/command/PlayAnimationCommand.java deleted file mode 100644 index c449b1b..0000000 --- a/src/main/java/com/linearpast/sccore/animation/command/PlayAnimationCommand.java +++ /dev/null @@ -1,186 +0,0 @@ -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/RequestAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/RequestAnimCommand.java new file mode 100644 index 0000000..f8b60db --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/RequestAnimCommand.java @@ -0,0 +1,181 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +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.core.configs.ModConfigs; +import com.linearpast.sccore.core.datagen.ModLang; +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 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; + +/** + * Request target player play animation. + */ +public class RequestAnimCommand { + private static final Map lastRequestedMap = new HashMap<>(); + record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean withRide, boolean isForce) {} + private static final Map> invites = new HashMap<>(); + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("request") + .then(argument("player", EntityArgument.player()).then( + argument("layer", AnimationLayerArgument.layer()) + .then(argument("animation", AnimationArgument.animation()) + .requires(cs -> cs.hasPermission(2)) + .executes(context -> invite(context, false)) + .then(argument("withRide", BoolArgumentType.bool()) + .executes(context -> invite( + context, BoolArgumentType.getBool(context, "withRide") + )) + .then(argument("forced", BoolArgumentType.bool()) + .executes(context -> invite( + context, BoolArgumentType.getBool(context, "withRide")) + ) + ) + ) + ) + )) + .then(literal("accept") + .then(argument("player", EntityArgument.player()) + .executes(RequestAnimCommand::accept) + ) + ) + ); + } + + private static int invite(CommandContext context, boolean withRide) { + 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"); + + //cooldown + Long lastRequested = lastRequestedMap.getOrDefault(player.getUUID(), null); + long now = System.currentTimeMillis(); + int requestCooldown = ModConfigs.Server.requestCooldown.get() * 1000; + if(!(lastRequested == null || now - lastRequested > requestCooldown)) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_COOLDOWN.getKey() + ).withStyle(ChatFormatting.RED)); + return 0; + } + lastRequestedMap.put(player.getUUID(), now); + + String layerString = AnimationLayerArgument.getLayer(context, "layer"); + String animString = AnimationArgument.getAnimation(context, "animation"); + ResourceLocation layer = new ResourceLocation(layerString); + ResourceLocation anim = new ResourceLocation(animString); + + //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, force, withRide)); + invites.put(player.getUUID(), inviteRecordMap); + + //click event + Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim request accept " + player.getName().getString()) + ).withUnderlined(true); + + //send message + target.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.REQUESTED_MESSAGE.getKey(), + player.getName().copy(), + anim.toString() + ).append(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() + ).setStyle(pStyle))); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.REQUEST_MESSAGE.getKey() + ).withStyle(ChatFormatting.GREEN), true); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int accept(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer target = source.getPlayerOrException(); + ServerPlayer player = EntityArgument.getPlayer(context, "player"); + + //get request record and test + 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(); + Integer requestDuration = ModConfigs.Server.requestDuration.get(); + + //test if expired + if(now - inviteRecord.time > requestDuration * 1000 || requestDuration == 0) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_REQUEST_EXPIRED.getKey(), + requestDuration + ).withStyle(ChatFormatting.RED)); + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.REQUEST_EXPIRED.getKey(), + target.getName().copy(), + requestDuration + ).withStyle(ChatFormatting.RED)); + return 0; + } + inviteRecordMap.remove(target.getUUID()); + invites.put(player.getUUID(), inviteRecordMap); + boolean withRide = inviteRecord.withRide; + + //play + if(withRide) { + AnimationUtils.playAnimationWithRide(target, inviteRecord.layer, inviteRecord.animation, inviteRecord.isForce); + }else { + AnimationUtils.playAnimation(target, inviteRecord.layer, inviteRecord.animation); + } + + //send message + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.ACCEPT_REQUEST_SUCCESS.getKey() + ).withStyle(ChatFormatting.GREEN), true); + player.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.REQUEST_SUCCESS.getKey(), + target.getName().copy() + ).withStyle(ChatFormatting.GREEN)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + 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 index 433df7b..6a69e34 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java +++ b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java @@ -1,6 +1,7 @@ package com.linearpast.sccore.animation.command.argument; -import com.linearpast.sccore.animation.registry.AnimationRegistry; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.register.AnimationRegistry; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -12,36 +13,61 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; 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 Supplier> EXAMPLES = AnimationArgument::getAnimationNames; private static final DynamicCommandExceptionType UNKNOWN_TYPE = new DynamicCommandExceptionType( animation -> Component.literal("Unknow animation : " + animation.toString()) ); - private final Set animationNames; + private final Supplier> animationNames; public AnimationArgument() { - this.animationNames = AnimationRegistry.getAnimations().keySet().stream() - .map(ResourceLocation::toString).collect(Collectors.toSet()); + this.animationNames = AnimationArgument::getAnimationNames; + } + + private static Set getAnimationNames(){ + HashSet set = new HashSet<>(); + AnimationRegistry.getAnimations().forEach((key, value) -> { + String name = value.getName(); + if(name != null && !set.contains(name)) { + set.add(name); + } else set.add(key.toString()); + }); + return set; } public static AnimationArgument animation() { return new AnimationArgument(); } + @Nullable + private static ResourceLocation getAnimationByName(String name) { + for (Animation animation : AnimationRegistry.getAnimations().values()) { + if (Objects.equals(animation.getName(), name)) { + return animation.getKey(); + } + } + return null; + } + public static String getAnimation(CommandContext context, String name) { - return context.getArgument(name, String.class); + String argument = context.getArgument(name, String.class); + if(argument.contains(":")) return argument; + ResourceLocation animationByName = getAnimationByName(argument); + if(animationByName == null) return argument; + else return animationByName.toString(); } public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - return SharedSuggestionProvider.suggest(animationNames, builder); + return SharedSuggestionProvider.suggest(animationNames.get(), builder); } public Collection getExamples() { @@ -54,7 +80,7 @@ public class AnimationArgument implements ArgumentType { reader.skip(); } String s = reader.getString().substring(start, reader.getCursor()); - if (!animationNames.contains(s)) { + if (!animationNames.get().contains(s)) { throw UNKNOWN_TYPE.create(s); } else { return s; 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 index 3963902..eb213bd 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationLayerArgument.java +++ b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationLayerArgument.java @@ -1,6 +1,6 @@ package com.linearpast.sccore.animation.command.argument; -import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import com.linearpast.sccore.animation.register.AnimationRegistry; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -20,15 +20,15 @@ import java.util.function.Supplier; import java.util.stream.Collectors; public class AnimationLayerArgument implements ArgumentType { - private static final Supplier> EXAMPLES = () -> AnimationLayerRegistry.getAnimLayers().keySet().stream() + private static final Supplier> EXAMPLES = () -> AnimationRegistry.getLayers().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; + private final Supplier> animationLayers; public AnimationLayerArgument() { - this.animationLayers = AnimationLayerRegistry.getAnimLayers().keySet().stream() + this.animationLayers = () -> AnimationRegistry.getLayers().keySet().stream() .map(ResourceLocation::toString).collect(Collectors.toSet()); } @@ -41,7 +41,7 @@ public class AnimationLayerArgument implements ArgumentType { } public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - return SharedSuggestionProvider.suggest(animationLayers, builder); + return SharedSuggestionProvider.suggest(animationLayers.get(), builder); } public Collection getExamples() { @@ -54,7 +54,7 @@ public class AnimationLayerArgument implements ArgumentType { reader.skip(); } String s = reader.getString().substring(start, reader.getCursor()); - if (!animationLayers.contains(s)) { + if (!animationLayers.get().contains(s)) { throw UNKNOWN_TYPE.create(s); } else { return s; diff --git a/src/main/java/com/linearpast/sccore/animation/command/client/GenerateJsonClientCommand.java b/src/main/java/com/linearpast/sccore/animation/command/client/GenerateJsonClientCommand.java new file mode 100644 index 0000000..e5ac488 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/client/GenerateJsonClientCommand.java @@ -0,0 +1,177 @@ +package com.linearpast.sccore.animation.command.client; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.util.AnimJson; +import com.linearpast.sccore.animation.data.util.AnimLayerJson; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.core.datagen.ModLang; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class GenerateJsonClientCommand { + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("jsonClient") + .then(argument("path", StringArgumentType.string()) + .suggests((context, builder) -> { + try { + File gameDirectory = Minecraft.getInstance().gameDirectory; + Path animation = gameDirectory.toPath().resolve(SnowyCrescentCore.MODID).resolve("animation"); + if(!animation.toFile().exists()) { + Files.createDirectories(animation); + } + String replace = animation.toString().replace("\\", "\\\\"); + builder.suggest("\"" + replace + "\""); + return builder.buildFuture(); + } catch (Exception e) { return builder.buildFuture(); } + }) + .then(literal("clearFile").executes(GenerateJsonClientCommand::clearJson)) + .then(literal("generate") + .then(literal("anim") + .then(literal("example").executes(GenerateJsonClientCommand::generateExample)) + .executes(context -> generateJson(context, false, false)) + .then(argument("reset", BoolArgumentType.bool()) + .executes(context -> + generateJson(context, false, BoolArgumentType.getBool(context, "reset")) + ) + ) + ) + .then(literal("layer") + .executes(context -> generateJson(context, true, false)) + .then(argument("reset", BoolArgumentType.bool()) + .executes(context -> + generateJson(context, true, BoolArgumentType.getBool(context, "reset")) + ) + ) + ) + ) + ) + ); + } + + private static void clearPath(Path dir) throws IOException { + if (!Files.exists(dir)) return; + try (var pathStream = Files.walk(dir)) { + pathStream.sorted(Comparator.reverseOrder()).forEach(path -> { + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (RuntimeException ignored) {} + } + + private static int generateJson(CommandContext context, boolean isLayer, boolean isReset) { + CommandSourceStack source = context.getSource(); + try { + String pathString = StringArgumentType.getString(context, "path"); + Path animationPath = Minecraft.getInstance().gameDirectory.toPath().resolve(pathString).resolve("animation"); + if (!Files.exists(animationPath)) { + try {Files.createDirectories(animationPath);} + catch (IOException e) { throw new RuntimeException(e); } + } + if(isReset) clearPath(animationPath); + if(isLayer) { + Path path = AnimLayerJson.Writer.syntaxImmediately(animationPath); + MutableComponent component = Component.translatable( + ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), + "layer", "Client" + ); + Style style = Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, animationPath.toString())) + .withColor(ChatFormatting.GREEN).withBold(true).withUnderlined(true); + component.append(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), + path.toString() + ).setStyle(style)); + source.sendSuccess(() -> component, true); + } else { + for (Animation value : AnimationRegistry.getAnimations().values()) { + AnimJson.Writer.stream(animationPath, value).syntax(); + } + MutableComponent component = Component.translatable( + ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), + "anim", "Client" + ); + Style style = Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, animationPath.toString())) + .withColor(ChatFormatting.GREEN).withBold(true).withUnderlined(true); + component.append(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), + animationPath.toString() + ).withStyle(style)); + source.sendSuccess(() -> component, true); + } + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int clearJson(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + String pathString = StringArgumentType.getString(context, "path"); + Path animationPath = Minecraft.getInstance().gameDirectory.toPath().resolve(pathString).resolve("animation"); + clearPath(animationPath); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_SUCCESS.getKey() + ), true); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } + + private static int generateExample(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + String pathString = StringArgumentType.getString(context, "path"); + Path animationPath = Minecraft.getInstance().gameDirectory.toPath().resolve(pathString); + Path path = AnimJson.Writer.syntaxExample(animationPath); + MutableComponent component = Component.translatable( + ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), + "anim example", "Client" + ); + Style style = Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, path.toString())) + .withColor(ChatFormatting.GREEN).withBold(true).withUnderlined(true); + component.append(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), + path.toString() + ).setStyle(style)); + source.sendSuccess(() -> component, true); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/client/AnimationRefreshCommand.java b/src/main/java/com/linearpast/sccore/animation/command/client/RefreshAnimCommand.java similarity index 64% rename from src/main/java/com/linearpast/sccore/animation/command/client/AnimationRefreshCommand.java rename to src/main/java/com/linearpast/sccore/animation/command/client/RefreshAnimCommand.java index ddf7fdf..dc69772 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/client/AnimationRefreshCommand.java +++ b/src/main/java/com/linearpast/sccore/animation/command/client/RefreshAnimCommand.java @@ -1,6 +1,8 @@ package com.linearpast.sccore.animation.command.client; +import com.linearpast.sccore.SnowyCrescentCore; import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.core.datagen.ModLang; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.minecraft.ChatFormatting; @@ -14,9 +16,9 @@ import net.minecraftforge.api.distmarker.OnlyIn; import static net.minecraft.commands.Commands.literal; @OnlyIn(Dist.CLIENT) -public class AnimationRefreshCommand { +public class RefreshAnimCommand { public static void register(LiteralArgumentBuilder animCommand){ - animCommand.then(literal("refresh").executes(AnimationRefreshCommand::refresh)); + animCommand.then(literal("refresh").executes(RefreshAnimCommand::refresh)); } private static int refresh(CommandContext ctx){ @@ -26,9 +28,14 @@ public class AnimationRefreshCommand { LocalPlayer player = instance.player; if(player == null) throw new RuntimeException(); AnimationUtils.refreshAnimation(player); - source.sendSuccess(() -> Component.literal("Animation refreshed.").withStyle(ChatFormatting.GREEN), true); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.REFRESH_ANIMATIONS.getKey() + ).withStyle(ChatFormatting.GREEN), true); } catch (Exception e) { - source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED)); + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); 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 index 59306ca..da590f7 100644 --- a/src/main/java/com/linearpast/sccore/animation/data/Animation.java +++ b/src/main/java/com/linearpast/sccore/animation/data/Animation.java @@ -5,8 +5,11 @@ import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; +import java.util.regex.Pattern; + public class Animation { - private final ResourceLocation name; + private @Nullable String name; + private final ResourceLocation key; private KeyframeAnimation animation; private float heightModifier = 1.0f; private float camYaw; @@ -16,15 +19,37 @@ public class Animation { private @Nullable Animation.LyingType lyingType; private @Nullable Ride ride; - public Animation(ResourceLocation name) { - this.name = name; + Animation(ResourceLocation key) { + this.key = key; + } + + public static Animation create(ResourceLocation name) { + return new Animation(name); } public enum LyingType { - RIGHT, - LEFT, - FRONT, - BACK + RIGHT("RIGHT"), + LEFT("LEFT"), + FRONT("FRONT"), + BACK("BACK"); + private final String name; + LyingType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Nullable + public static LyingType getLyingType(String name) { + for (LyingType type : LyingType.values()) { + if (type.name.equals(name)) { + return type; + } + } + return null; + } } public Animation withLyingType(@Nullable Animation.LyingType lyingType) { @@ -77,6 +102,18 @@ public class Animation { return this; } + public Animation withName(String name) { + String regex = "^[a-zA-Z0-9_-]+$"; + Pattern pattern = Pattern.compile(regex); + if (!pattern.matcher(name).matches()) { + throw new IllegalArgumentException( + "Invalid animation name: " + name + ", must match " + regex + ); + } + this.name = name; + return this; + } + public void setLyingType(@Nullable LyingType lyingType) { this.lyingType = lyingType; } @@ -95,7 +132,7 @@ public class Animation { @Nullable public KeyframeAnimation getAnimation() { - return PlayerAnimationRegistry.getAnimation(name); + return PlayerAnimationRegistry.getAnimation(key); } public @Nullable Animation.LyingType getLyingType() { @@ -114,7 +151,11 @@ public class Animation { return ride; } - public ResourceLocation getName() { + public ResourceLocation getKey() { + return key; + } + + public @Nullable String getName() { return name; } } diff --git a/src/main/java/com/linearpast/sccore/animation/data/util/AnimJson.java b/src/main/java/com/linearpast/sccore/animation/data/util/AnimJson.java new file mode 100644 index 0000000..8485605 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/data/util/AnimJson.java @@ -0,0 +1,195 @@ +package com.linearpast.sccore.animation.data.util; + +import com.google.gson.*; +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.Ride; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Path; + +public class AnimJson { + private static final String Key = "key"; + private static final String Name = "name"; + private static final String LyingType = "lyingType"; + private static final String HeightModifier = "heightModifier"; + private static final String CamY = "camY"; + private static final String CamPitch = "camPitch"; + private static final String CamRoll = "camRoll"; + private static final String CamYaw = "camYaw"; + private static final String WithRide = "withRide"; + private static final String Offset = "offset"; + private static final String XRot = "xRot"; + private static final String YRot = "yRot"; + private static final String ExistTick = "existTick"; + private static final String ComponentsAnimation = "componentsAnimation"; + + public static class Reader { + private final JsonElement originElement; + Reader(Path jsonFile) throws Exception { + File file = jsonFile.toFile(); + if (!file.exists()) { + throw new FileNotFoundException("File does not exist: " + file.getAbsolutePath()); + } + this.originElement = JsonParser.parseReader(new FileReader(file)); + } + + Reader(JsonElement originElement) { + this.originElement = originElement; + } + + public static Reader stream(Path path) throws Exception { + return new Reader(path); + } + + public static Reader stream(JsonElement jsonElement) { + return new Reader(jsonElement); + } + + public Animation parse() { + return fromJson(); + } + + public Animation fromJson() { + try { + JsonObject json = originElement.getAsJsonObject(); + Animation animation = Animation.create(new ResourceLocation(json.get(Key).getAsString())); + if(json.has(Name)) animation.withName(json.get(Name).getAsString()); + if(json.has(LyingType)) animation.withLyingType(Animation.LyingType.valueOf(json.get(LyingType).getAsString())); + animation.withHeightModifier(json.get(HeightModifier).getAsFloat()) + .withCamY(json.get(CamY).getAsFloat()) + .withCamPitch(json.get(CamPitch).getAsFloat()) + .withCamRoll(json.get(CamRoll).getAsFloat()) + .withCamYaw(json.get(CamYaw).getAsFloat()); + if(json.has(WithRide)){ + Ride ride = Ride.create(); + JsonObject withRide = json.get(WithRide).getAsJsonObject(); + JsonObject offsetJson = withRide.get(Offset).getAsJsonObject(); + if(withRide.has(ComponentsAnimation)){ + JsonArray elements = withRide.get(ComponentsAnimation).getAsJsonArray(); + for (JsonElement element : elements) { + String componentKeyString = element.getAsString(); + ResourceLocation componentKey = new ResourceLocation(componentKeyString); + ride.addComponentAnimation(componentKey); + } + } + Vec3 offset = new Vec3( + offsetJson.get("x").getAsDouble(), + offsetJson.get("y").getAsDouble(), + offsetJson.get("z").getAsDouble() + ); + ride.withOffset(offset).withExistTick(withRide.get(ExistTick).getAsInt()) + .withXRot(withRide.get(XRot).getAsFloat()) + .withYRot(withRide.get(YRot).getAsFloat()); + animation.withRide(ride); + } + return animation; + } catch (Exception e) { + throw new JsonParseException(e); + } + } + } + + public static class Writer { + private static final String example = "example"; + private final @Nullable Path file; + private final Animation animation; + Writer(@Nullable Path file, Animation animation) { + this.animation = animation; + this.file = file; + } + + public static Writer stream(Path path, Animation animation) { + return new Writer(path, animation); + } + + public static Writer stream(Animation animation) { + return new Writer(null, animation); + } + + public static Path syntaxExample(Path directory) throws Exception { + ResourceLocation exampleLocation = new ResourceLocation(SnowyCrescentCore.MODID, Writer.example); + Animation example = Animation.create(exampleLocation) + .withName(Writer.example) + .withLyingType(Animation.LyingType.RIGHT) + .withHeightModifier(0.3f) + .withCamY(-1.3f) + .withCamPitch(-90.0f) + .withCamRoll(90.0f) + .withCamYaw(90.0f) + .withRide(Ride.create() + .withOffset(new Vec3(0.0f, 1.0f, 0.0f)) + .withExistTick(200) + .withXRot(180) + .withYRot(0) + .addComponentAnimation(exampleLocation) + ); + Writer writer = stream(directory, example); + return writer.syntax(); + } + + public Path syntax() throws Exception { + if(file == null) throw new NullPointerException("file is null"); + Path modIdPath = file.resolve(animation.getKey().getNamespace()); + Path resultPath = modIdPath.resolve(animation.getKey().getPath() + ".anim.json"); + if(animation.getName() != null) { + resultPath = modIdPath.resolve(animation.getName() + ".anim.json"); + if(resultPath.toFile().exists()) { + resultPath = modIdPath.resolve(animation.getKey().getPath() + ".anim.json"); + } + } + if(resultPath.toFile().exists()) return resultPath; + if(!Files.exists(modIdPath)) Files.createDirectories(modIdPath); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + try (FileWriter writer = new FileWriter(resultPath.toFile())) { + gson.toJson(toJson(), writer); + return resultPath; + } + } + + + public JsonElement toJson() { + JsonObject json = new JsonObject(); + ResourceLocation key = animation.getKey(); + json.addProperty(Key, key.toString()); + if (animation.getName() != null) json.addProperty(Name, animation.getName()); + if (animation.getLyingType() != null) json.addProperty(LyingType, animation.getLyingType().getName()); + json.addProperty(HeightModifier, animation.getHeightModifier()); + json.addProperty(CamY, animation.getCamY()); + json.addProperty(CamPitch, animation.getCamPitch()); + json.addProperty(CamRoll, animation.getCamRoll()); + json.addProperty(CamYaw, animation.getCamYaw()); + Ride ride = animation.getRide(); + if(ride != null) { + JsonObject jsonRide = new JsonObject(); + JsonObject jsonOffset = new JsonObject(); + Vec3 offset = ride.getOffset(); + jsonOffset.addProperty("x", offset.x); + jsonOffset.addProperty("y", offset.y); + jsonOffset.addProperty("z", offset.z); + jsonRide.add(Offset, jsonOffset); + jsonRide.addProperty(XRot, ride.getXRot()); + jsonRide.addProperty(YRot, ride.getYRot()); + jsonRide.addProperty(ExistTick, ride.getExistTick()); + + if(!ride.getComponentAnimations().isEmpty()) { + JsonArray jsonComponents = new JsonArray(); + ride.getComponentAnimations().forEach(component -> + jsonComponents.add(component.toString()) + ); + jsonRide.add(ComponentsAnimation, jsonComponents); + } + json.add(WithRide, jsonRide); + } + return json; + } + + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/data/util/AnimLayerJson.java b/src/main/java/com/linearpast/sccore/animation/data/util/AnimLayerJson.java new file mode 100644 index 0000000..7123c8b --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/data/util/AnimLayerJson.java @@ -0,0 +1,129 @@ +package com.linearpast.sccore.animation.data.util; + +import com.google.gson.*; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import net.minecraft.resources.ResourceLocation; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +public class AnimLayerJson { + + private static final String Key = "key"; + private static final String Priority = "priority"; + + public static class Reader { + private final JsonElement originElement; + + Reader(Path jsonFile) throws Exception { + File file = jsonFile.toFile(); + if (!file.exists()) { + throw new FileNotFoundException("File does not exist: " + file.getAbsolutePath()); + } + this.originElement = JsonParser.parseReader(new FileReader(file)); + } + + Reader(JsonElement originElement) { + this.originElement = originElement; + } + + public static Reader stream(Path path) throws Exception { + return new Reader(path); + } + + public static Reader stream(JsonElement jsonElement) { + return new Reader(jsonElement); + } + + public Map parse() { + return fromJson(); + } + + private Map fromJson() { + try { + JsonArray jsonArray = originElement.getAsJsonArray(); + Map map = new HashMap<>(); + for (JsonElement element : jsonArray) { + JsonObject jsonObject = element.getAsJsonObject(); + ResourceLocation location = new ResourceLocation(jsonObject.get(Key).getAsString()); + int priority = jsonObject.get(Priority).getAsInt(); + map.put(location, priority); + } + return map; + } catch (Exception e) { + throw new JsonParseException(e); + } + } + } + + public static class Writer { + private final Path file; + private final Map layers = AnimationRegistry.getLayers(); + private final Map> layerNames = new HashMap<>(); + Writer(Path file) { + this.file = file; + for (ResourceLocation location : layers.keySet()) { + String namespace = location.getNamespace(); + Set locationSet = layerNames.getOrDefault(namespace, new HashSet<>()); + locationSet.add(location); + layerNames.put(namespace, locationSet); + } + } + + public static Writer stream(Path path) { + return new Writer(path); + } + + public static Path syntaxImmediately(Path directory) throws IOException { + Writer writer = stream(directory); + return writer.syntax(); + } + + public Map allToJson() { + Map map = new HashMap<>(); + for (String namespace : layerNames.keySet()) { + Set locationSet = layerNames.get(namespace); + JsonElement json = toJson(locationSet); + map.put(namespace, json); + } + return map; + } + + public Path syntax(String ... namespaces) throws IOException { + Set namespaceSet; + if(namespaces.length == 0) { + namespaceSet = layerNames.keySet(); + } else { + namespaceSet = Arrays.stream(namespaces).collect(Collectors.toSet()); + } + + for (String name : namespaceSet) { + Set locationSet = layerNames.get(name); + Path modIdPath = file.resolve(name); + Path resultPath = modIdPath.resolve("animation.layer.json"); + if(!Files.exists(modIdPath)) Files.createDirectories(modIdPath); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + try (FileWriter writer = new FileWriter(resultPath.toFile())) { + gson.toJson(toJson(locationSet), writer); + } + } + return file; + } + + private JsonElement toJson(Set locationSet) { + JsonArray jsonArray = new JsonArray(); + for (ResourceLocation location : locationSet) { + if(layers.containsKey(location)) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty(Key, location.toString()); + jsonObject.addProperty(Priority, layers.get(location)); + jsonArray.add(jsonObject); + } + } + return jsonArray; + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java b/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java index 72a075f..d673189 100644 --- a/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java +++ b/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java @@ -5,7 +5,7 @@ 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 com.linearpast.sccore.animation.register.AnimationEntities; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; @@ -20,9 +20,7 @@ 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; +import java.util.*; public class AnimationRideEntity extends Entity { public AnimationRideEntity(Level pLevel) { @@ -35,6 +33,7 @@ public class AnimationRideEntity extends Entity { private static final String Layer = "Layer"; private final Set players = new HashSet<>(); + private final Map animationPair = new HashMap<>(); private Animation animation; private ServerPlayer player; private ResourceLocation layer; @@ -43,6 +42,13 @@ public class AnimationRideEntity extends Entity { this.player = pPlayer; this.layer = layer; this.animation = animation; + Ride ride = animation.getRide(); + if(ride != null) { + List componentAnimations = ride.getComponentAnimations(); + for (ResourceLocation componentAnimation : componentAnimations) { + animationPair.put(componentAnimation, null); + } + } } public ResourceLocation getLayer() { @@ -53,6 +59,10 @@ public class AnimationRideEntity extends Entity { return players; } + public ServerPlayer getPlayer() { + return player; + } + public Animation getAnimation() { return animation; } @@ -88,7 +98,7 @@ public class AnimationRideEntity extends Entity { pCompound.putUUID(PlayerUUID, player.getUUID()); pCompound.putString(Layer, layer.toString()); if(animation != null) { - pCompound.putString(Animation, animation.getName().toString()); + pCompound.putString(Animation, animation.getKey().toString()); } } @@ -98,18 +108,6 @@ public class AnimationRideEntity extends Entity { 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); } } @@ -139,6 +137,9 @@ public class AnimationRideEntity extends Entity { Animation anim = AnimationUtils.getAnimation(animation); if(anim == null) return false; if(anim.getRide() == null) return false; + IAnimationCapability data = AnimationDataCapability.getCapability(pPlayer).orElse(null); + if(data == null) return false; + data.setRiderAnimation(layer, animation); AnimationRideEntity seat = new AnimationRideEntity(pPlayer, layer, anim); float xRot = anim.getRide().getXRot(); float yRot = anim.getRide().getYRot(); @@ -149,7 +150,7 @@ public class AnimationRideEntity extends Entity { seat.setPos(pos.x, pos.y + 0.35f, pos.z); pPlayer.level().addFreshEntity(seat); pPlayer.startRiding(seat, force); - return AnimationUtils.playAnimation(pPlayer, layer, animation); + return true; } @Override @@ -184,9 +185,17 @@ public class AnimationRideEntity extends Entity { 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); + ResourceLocation animLocation = null; + for (ResourceLocation location : animationPair.keySet()) { + if(animationPair.get(location) == null) + animLocation = location; + } + if(animLocation == null) return; + animationPair.put(animLocation, serverPlayer.getUUID()); + IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); + if(data == null) return; + data.setRiderAnimation(layer, animLocation); + AnimationUtils.syncAnimation(serverPlayer, player); players.add(serverPlayer); } } @@ -196,13 +205,20 @@ public class AnimationRideEntity extends Entity { super.removePassenger(entity); if(entity instanceof ServerPlayer serverPlayer) { AnimationUtils.removeAnimation(serverPlayer, layer); - AnimationDataCapability.getCapability(serverPlayer).ifPresent(data -> data.setRideAnimLayer(null)); players.remove(serverPlayer); + new HashMap<>(animationPair).forEach((key, value) -> { + if(Objects.equals(value, serverPlayer.getUUID())) { + animationPair.put(key, null); + } + }); + IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); + if(data == null) return; + data.removeRiderAnimation(); } } @Override - protected boolean canAddPassenger(@NotNull Entity pPassenger) { + public boolean canAddPassenger(@NotNull Entity pPassenger) { boolean isServerPlayer = pPassenger instanceof ServerPlayer; int size = players.size(); Ride ride = animation.getRide(); diff --git a/src/main/java/com/linearpast/sccore/animation/event/AnimationLayerRegistry.java b/src/main/java/com/linearpast/sccore/animation/event/AnimationLayerRegistry.java deleted file mode 100644 index f717c2a..0000000 --- a/src/main/java/com/linearpast/sccore/animation/event/AnimationLayerRegistry.java +++ /dev/null @@ -1,49 +0,0 @@ -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/PlayerTickEvent.java b/src/main/java/com/linearpast/sccore/animation/event/PlayerTickEvent.java new file mode 100644 index 0000000..9366912 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/PlayerTickEvent.java @@ -0,0 +1,24 @@ +package com.linearpast.sccore.animation.event; + +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.TickEvent; + +public class PlayerTickEvent { + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event) { + if (event.side.isServer() && event.phase == TickEvent.Phase.END) { + if(event.player.tickCount % 20 == 0) { + Player player = event.player; + if(!(player.getVehicle() instanceof AnimationRideEntity)){ + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) return; + if(data.getRiderAnimLayer() != null) { + data.removeRiderAnimation(); + } + } + } + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/EntityRendererRegistry.java b/src/main/java/com/linearpast/sccore/animation/event/client/EntityRendererRegisterEvent.java similarity index 67% rename from src/main/java/com/linearpast/sccore/animation/event/EntityRendererRegistry.java rename to src/main/java/com/linearpast/sccore/animation/event/client/EntityRendererRegisterEvent.java index 67b76e9..3638fec 100644 --- a/src/main/java/com/linearpast/sccore/animation/event/EntityRendererRegistry.java +++ b/src/main/java/com/linearpast/sccore/animation/event/client/EntityRendererRegisterEvent.java @@ -1,10 +1,10 @@ -package com.linearpast.sccore.animation.event; +package com.linearpast.sccore.animation.event.client; import com.linearpast.sccore.animation.entity.renderer.AnimationRideRenderer; -import com.linearpast.sccore.animation.registry.AnimationEntities; +import com.linearpast.sccore.animation.register.AnimationEntities; import net.minecraftforge.client.event.EntityRenderersEvent; -public class EntityRendererRegistry { +public class EntityRendererRegisterEvent { 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/create/AnimationLayerRegisterEvent.java b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java index 0d99f14..02b8e3a 100644 --- a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java +++ b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java @@ -2,28 +2,22 @@ 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 + * You can listen this event to invite an animation layer
+ * It is only useful in server */ -public class AnimationLayerRegisterEvent extends Event implements IModBusEvent { +public class AnimationLayerRegisterEvent extends Event { private final Map layers = new HashMap<>(); - public AnimationLayerRegisterEvent() { - - } - public Map getLayers() { return layers; } - public void putLayer(ResourceLocation key, Integer value) { + public void registerLayer(ResourceLocation key, Integer value) { layers.put(key, value); } } diff --git a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationRegisterEvent.java b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationRegisterEvent.java new file mode 100644 index 0000000..4fa15f3 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationRegisterEvent.java @@ -0,0 +1,24 @@ +package com.linearpast.sccore.animation.event.create; + +import com.linearpast.sccore.animation.data.Animation; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.Event; + +import java.util.HashMap; +import java.util.Map; + +/** + * You can listen this event to invite an animation
+ * It is only useful in server + */ +public class AnimationRegisterEvent extends Event { + private final Map animations = new HashMap<>(); + + public Map getAnimations() { + return new HashMap<>(animations); + } + + public void registerAnimation(ResourceLocation location, Animation animation) { + animations.put(location, animation); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/mixin/IMixinPlayerAnimationFactoryHolder.java b/src/main/java/com/linearpast/sccore/animation/mixin/IMixinPlayerAnimationFactoryHolder.java new file mode 100644 index 0000000..a62f1fc --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/mixin/IMixinPlayerAnimationFactoryHolder.java @@ -0,0 +1,12 @@ +package com.linearpast.sccore.animation.mixin; + +import dev.kosmx.playerAnim.api.layered.IAnimation; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface IMixinPlayerAnimationFactoryHolder { + record DataHolder(@Nullable ResourceLocation id, int priority, @NotNull IAnimation animation) {} + + void sccore$clearAnimations(); +} 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 index 67ce02f..77fe2f2 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java @@ -1,7 +1,6 @@ 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; @@ -50,14 +49,24 @@ public class AnimationCapabilityPacket extends SimpleCapabilityPacket { } private void testPlayAnimations(AbstractClientPlayer player, CompoundTag tag, IAnimationCapability data) { - Set oldLayerSet = new HashSet<>(); - if(data != null) oldLayerSet.addAll(data.getAnimations().keySet()); + if(data == null) return; + ResourceLocation oldRiderAnimLayer = data.getRiderAnimLayer(); + String riderAnimLayerString = tag.getString(AnimationDataCapability.RideAnimLayer); + ResourceLocation newRiderAnimLayer = riderAnimLayerString.isEmpty() ? null : new ResourceLocation(riderAnimLayerString); + if(!Objects.equals(oldRiderAnimLayer, newRiderAnimLayer)) { + String riderAnimationString = tag.getString(AnimationDataCapability.RideAnimation); + ResourceLocation newRiderAnimation = riderAnimationString.isEmpty() ? null : new ResourceLocation(riderAnimationString); + if(oldRiderAnimLayer != null) AnimationPlayer.playAnimation(player, oldRiderAnimLayer, null); + if(newRiderAnimLayer != null) AnimationPlayer.playAnimation(player, newRiderAnimLayer, newRiderAnimation); + } + + Set oldLayerSet = new HashSet<>(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); + ResourceLocation oldAnimLocation = data.getAnimation(newLayerLocation); if (!Objects.equals(newAnimLocation, oldAnimLocation)) { AnimationPlayer.playAnimation(player, newLayerLocation, newAnimLocation); } @@ -67,10 +76,7 @@ public class AnimationCapabilityPacket extends SimpleCapabilityPacket { 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/AnimationClientStatusPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationClientStatusPacket.java new file mode 100644 index 0000000..b2832d5 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationClientStatusPacket.java @@ -0,0 +1,47 @@ +package com.linearpast.sccore.animation.network.toclient; + +import com.linearpast.sccore.animation.register.AnimationRegistry; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +public record AnimationClientStatusPacket(int status) { + public enum Status { + ANIM_CACHE_CLEAR(0), + LAYER_CACHE_CLEAR(1), + ANIM_REGISTER(2), + LAYER_REGISTER(3),; + private final int value; + Status(final int value) { + this.value = value; + } + @Nullable + public static Status getStatus(final int value) { + for (Status status : values()) { + if (status.value == value) { + return status; + } + } + return null; + } + } + public AnimationClientStatusPacket(Status status) { + this(status.value); + } + public AnimationClientStatusPacket(FriendlyByteBuf buf) { + this(buf.readInt()); + } + public void encode(FriendlyByteBuf buf) { + buf.writeInt(status); + } + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + Status state = Status.getStatus(status); + if(state == null) return; + AnimationRegistry.ClientCache.animationStatusUpdate(state); + }); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationJsonPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationJsonPacket.java new file mode 100644 index 0000000..5759adb --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationJsonPacket.java @@ -0,0 +1,39 @@ +package com.linearpast.sccore.animation.network.toclient; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.util.AnimJson; +import com.linearpast.sccore.animation.data.util.AnimLayerJson; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkEvent; + +import java.util.Map; +import java.util.function.Supplier; + +public record AnimationJsonPacket(String json, boolean isLayer) { + public AnimationJsonPacket(FriendlyByteBuf buf) { + this(buf.readUtf(), buf.readBoolean()); + } + public void encode(FriendlyByteBuf buf) { + buf.writeUtf(json); + buf.writeBoolean(isLayer); + } + + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + JsonElement element = JsonParser.parseString(json); + if(isLayer) { + Map parse = AnimLayerJson.Reader.stream(element).parse(); + parse.forEach(AnimationRegistry.ClientCache::cacheAddAnimationLayer); + } else { + Animation animation = AnimJson.Reader.stream(element).parse(); + AnimationRegistry.ClientCache.cacheAddAnimation(animation.getKey(), animation); + } + }); + } +} 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 index 46f0cfa..5245a0d 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java @@ -6,7 +6,6 @@ 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; @@ -16,23 +15,19 @@ 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) { + public SyncAnimationPacket(UUID playerUUID, UUID targetUUID) { 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) { @@ -45,7 +40,7 @@ public class SyncAnimationPacket { AbstractClientPlayer player = (AbstractClientPlayer) level.getPlayerByUUID(playerUUID); AbstractClientPlayer target = (AbstractClientPlayer) level.getPlayerByUUID(targetUUID); ClientPlayerTick.runs.put( - () -> AnimationPlayer.syncAnimation(player, target, layer), + () -> AnimationPlayer.syncAnimation(player, target), 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 index 965456d..ad66db3 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java @@ -1,45 +1,52 @@ package com.linearpast.sccore.animation.network.toserver; import com.linearpast.sccore.animation.AnimationPlayer; -import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import com.linearpast.sccore.animation.register.AnimationRegistry; 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.UUID; import java.util.function.Supplier; public class PlayAnimationRequestPacket { private final ResourceLocation layer; private @Nullable ResourceLocation animation; - public PlayAnimationRequestPacket(ResourceLocation layer, @Nullable ResourceLocation animation) { + private @Nullable UUID uuid; + public PlayAnimationRequestPacket(@Nullable UUID uuid, ResourceLocation layer, @Nullable ResourceLocation animation) { this.layer = layer; this.animation = animation; + this.uuid = uuid; } public PlayAnimationRequestPacket(FriendlyByteBuf buf){ this.layer = buf.readResourceLocation(); try { this.animation = buf.readResourceLocation(); + this.uuid = buf.readUUID(); } catch (Exception e) { this.animation = null; + this.uuid = null; } } public void encode(FriendlyByteBuf buf){ buf.writeResourceLocation(layer); - if(animation != null){ - buf.writeResourceLocation(animation); - } + if(animation != null) buf.writeResourceLocation(animation); + if(uuid != null) buf.writeUUID(uuid); } public void handle(Supplier supplier){ NetworkEvent.Context context = supplier.get(); context.enqueueWork(() -> { context.setPacketHandled(true); - if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) { + if (AnimationRegistry.getLayers().containsKey(layer)) { + ServerPlayer target; ServerPlayer sender = context.getSender(); - if(sender == null) return; - AnimationPlayer.serverPlayAnimation(sender, layer, animation); + if(sender == null || sender.getServer() == null) return; + if(this.uuid == null) target = sender; + else target = sender.getServer().getPlayerList().getPlayer(this.uuid); + AnimationPlayer.serverPlayAnimation(target, 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 index c124f46..c3fd8c4 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java @@ -1,7 +1,7 @@ package com.linearpast.sccore.animation.network.toserver; import com.linearpast.sccore.animation.AnimationPlayer; -import com.linearpast.sccore.animation.event.AnimationLayerRegistry; +import com.linearpast.sccore.animation.register.AnimationRegistry; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; @@ -43,7 +43,7 @@ public class PlayAnimationRidePacket { NetworkEvent.Context context = supplier.get(); context.enqueueWork(() -> { context.setPacketHandled(true); - if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) { + if (AnimationRegistry.getLayers().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/register/AnimationCapabilities.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationCapabilities.java new file mode 100644 index 0000000..77ea2fb --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationCapabilities.java @@ -0,0 +1,31 @@ +package com.linearpast.sccore.animation.register; + +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.network.toclient.AnimationCapabilityPacket; +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.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; + +public class AnimationCapabilities { + public 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 + ); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/register/AnimationChannels.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationChannels.java new file mode 100644 index 0000000..4af2853 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationChannels.java @@ -0,0 +1,46 @@ +package com.linearpast.sccore.animation.register; + +import com.linearpast.sccore.animation.network.toclient.AnimationClientStatusPacket; +import com.linearpast.sccore.animation.network.toclient.AnimationJsonPacket; +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.core.ModChannel; +import net.minecraftforge.network.NetworkDirection; + +public class AnimationChannels { + public 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(AnimationJsonPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_CLIENT) + .decoder(AnimationJsonPacket::new) + .encoder(AnimationJsonPacket::encode) + .consumerMainThread(AnimationJsonPacket::handle) + .add(); + ModChannel.INSTANCE.messageBuilder(AnimationClientStatusPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_CLIENT) + .decoder(AnimationClientStatusPacket::new) + .encoder(AnimationClientStatusPacket::encode) + .consumerMainThread(AnimationClientStatusPacket::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(); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/AnimationCommands.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationCommands.java similarity index 75% rename from src/main/java/com/linearpast/sccore/animation/command/AnimationCommands.java rename to src/main/java/com/linearpast/sccore/animation/register/AnimationCommands.java index 5e3b8c1..d12e649 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/AnimationCommands.java +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationCommands.java @@ -1,9 +1,11 @@ -package com.linearpast.sccore.animation.command; +package com.linearpast.sccore.animation.register; import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.command.*; 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.linearpast.sccore.animation.command.client.GenerateJsonClientCommand; +import com.linearpast.sccore.animation.command.client.RefreshAnimCommand; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.synchronization.ArgumentTypeInfo; @@ -17,8 +19,11 @@ public class AnimationCommands { public static void commonCommandRegister(LiteralArgumentBuilder builder) { if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()){ LiteralArgumentBuilder anim = literal("anim"); - PlayAnimationCommand.register(anim); - + PlayAnimCommand.register(anim); + RequestAnimCommand.register(anim); + CombineAnimCommand.register(anim); + GenerateJsonCommand.register(anim); + ApplyJoinAnimCommand.register(anim); builder.then(anim); } } @@ -26,7 +31,8 @@ public class AnimationCommands { public static void clientCommandRegister(LiteralArgumentBuilder builder) { if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()) { LiteralArgumentBuilder anim = literal("anim"); - AnimationRefreshCommand.register(anim); + RefreshAnimCommand.register(anim); + GenerateJsonClientCommand.register(anim); builder.then(anim); } } diff --git a/src/main/java/com/linearpast/sccore/animation/registry/AnimationEntities.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationEntities.java similarity index 96% rename from src/main/java/com/linearpast/sccore/animation/registry/AnimationEntities.java rename to src/main/java/com/linearpast/sccore/animation/register/AnimationEntities.java index 553892d..fa574ca 100644 --- a/src/main/java/com/linearpast/sccore/animation/registry/AnimationEntities.java +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationEntities.java @@ -1,4 +1,4 @@ -package com.linearpast.sccore.animation.registry; +package com.linearpast.sccore.animation.register; import com.linearpast.sccore.SnowyCrescentCore; import com.linearpast.sccore.animation.entity.AnimationRideEntity; diff --git a/src/main/java/com/linearpast/sccore/animation/register/AnimationRegistry.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationRegistry.java new file mode 100644 index 0000000..c68d02f --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationRegistry.java @@ -0,0 +1,309 @@ +package com.linearpast.sccore.animation.register; + +import com.google.gson.JsonElement; +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.data.util.AnimJson; +import com.linearpast.sccore.animation.data.util.AnimLayerJson; +import com.linearpast.sccore.animation.event.create.AnimationLayerRegisterEvent; +import com.linearpast.sccore.animation.event.create.AnimationRegisterEvent; +import com.linearpast.sccore.animation.mixin.IMixinPlayerAnimationFactoryHolder; +import com.linearpast.sccore.animation.network.toclient.AnimationClientStatusPacket; +import com.linearpast.sccore.animation.network.toclient.AnimationJsonPacket; +import com.linearpast.sccore.core.ModChannel; +import dev.kosmx.playerAnim.api.layered.AnimationStack; +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.core.data.KeyframeAnimation; +import dev.kosmx.playerAnim.core.util.Pair; +import dev.kosmx.playerAnim.impl.animation.AnimationApplier; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.server.ServerStartedEvent; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class AnimationRegistry { + private static final Map animations = new HashMap<>(); + private static final Map layers = new HashMap<>(); + + public static Map getAnimations() { + return Map.copyOf(animations); + } + + public static Map getLayers() { + return Map.copyOf(layers); + } + + @OnlyIn(Dist.CLIENT) + public static void registerAnimations(Map animationMap) { + animations.clear(); + animations.putAll(animationMap); + } + + @OnlyIn(Dist.CLIENT) + public static void registerLayers(Map layerMap) { + layers.clear(); + layers.putAll(layerMap); + } + + public static void onServerStarted(ServerStartedEvent event) { + Path dataPackPath = event.getServer().getWorldPath(LevelResource.DATAPACK_DIR); + Path animationPath = dataPackPath.resolve("animation"); + if (!Files.exists(animationPath)) { + try { + Files.createDirectories(animationPath); + } catch (IOException e) { return; } + } + + safeUnzip(dataPackPath.resolve("animation.zip").toString(), animationPath.toAbsolutePath().toString()); + Set animZipPaths = getAllFile( + dataPackPath.resolve("animation"), + path -> path.toString().endsWith(".anim.zip") + ); + Set layerZipPaths = getAllFile( + dataPackPath.resolve("animation"), + path -> path.toString().endsWith(".layer.zip") + ); + for (Path zipPath : animZipPaths) { + safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString()); + } + for (Path zipPath : layerZipPaths) { + safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString()); + } + + Set animPaths = getAllFile( + dataPackPath.resolve("animation"), + path -> path.toString().endsWith(".anim.json") + ); + Set layerPaths = getAllFile( + dataPackPath.resolve("animation"), + path -> path.getFileName().toString().equals("animation.layer.json") + ); + Set animationsSet = new HashSet<>(); + Map layersMap = new HashMap<>(); + for (Path path : animPaths) { + try { + AnimJson.Reader reader = AnimJson.Reader.stream(path); + Animation anim = reader.parse(); + animationsSet.add(anim); + } catch (Exception ignored) { + SnowyCrescentCore.log.error("Failed to parse animation JSON: {}", path.toString()); + } + } + for (Path path : layerPaths) { + try { + AnimLayerJson.Reader reader = AnimLayerJson.Reader.stream(path); + Map parse = reader.parse(); + layersMap.putAll(parse); + } catch (Exception ignored) { + SnowyCrescentCore.log.error("Failed to parse layer JSON: {}", path.toString()); + } + } + + animations.clear(); + AnimationRegisterEvent animationRegisterEvent = new AnimationRegisterEvent(); + MinecraftForge.EVENT_BUS.post(animationRegisterEvent); + Map animationMap = animationRegisterEvent.getAnimations(); + animations.putAll(animationMap); + animations.putAll(animationsSet.stream().collect(Collectors.toMap(Animation::getKey, animation -> animation))); + + layers.clear(); + AnimationLayerRegisterEvent layerRegisterEvent = new AnimationLayerRegisterEvent(); + MinecraftForge.EVENT_BUS.post(layerRegisterEvent); + Map layerMap = layerRegisterEvent.getLayers(); + layers.putAll(layerMap); + layers.putAll(layersMap); + } + + public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + if (event.getEntity() instanceof ServerPlayer serverPlayer) { + MinecraftServer server = serverPlayer.getServer(); + if(server == null) return; + Path dataPackPath = server.getWorldPath(LevelResource.DATAPACK_DIR); + Path animationPath = dataPackPath.resolve("animation"); + if (!Files.exists(animationPath)) { + try {Files.createDirectories(animationPath);} + catch (IOException e) { return; } + } + ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.ANIM_CACHE_CLEAR), serverPlayer); + for (Animation value : animations.values()) { + JsonElement json = AnimJson.Writer.stream(value).toJson(); + String string = json.toString(); + ModChannel.sendToPlayer(new AnimationJsonPacket(string, false), serverPlayer); + } + ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.ANIM_REGISTER), serverPlayer); + ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.LAYER_CACHE_CLEAR), serverPlayer); + Map jsonElementMap = AnimLayerJson.Writer.stream(animationPath).allToJson(); + jsonElementMap.forEach((key, value) -> + ModChannel.sendToPlayer(new AnimationJsonPacket(value.toString(), true), serverPlayer) + ); + ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.LAYER_REGISTER), serverPlayer); + } + + } + + private static Set getAllFile(Path directory, Predicate filter) { + try (Stream walk = Files.walk(directory)) { + return walk.filter(Files::isRegularFile) + .filter(filter) + .collect(Collectors.toSet()); + } catch (Exception ignored) { + return Collections.emptySet(); + } + } + + private static void safeUnzip(String zipFile, String destDir) { + Path destPath = Paths.get(destDir).toAbsolutePath(); + + try (ZipFile zip = new ZipFile(zipFile)) { + Files.createDirectories(destPath); + Enumeration entries = zip.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + Path entryPath = destPath.resolve(entry.getName()).normalize(); + + if (entry.isDirectory()) { + Files.createDirectories(entryPath); + } else { + Files.createDirectories(entryPath.getParent()); + try (InputStream in = zip.getInputStream(entry); + OutputStream out = Files.newOutputStream(entryPath, StandardOpenOption.CREATE)) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + } + } + } catch (Exception ignored) {} + } + + + @OnlyIn(Dist.CLIENT) + public static class ClientCache { + private static final Map animationsCache = new HashMap<>(); + private static final Map layersCache = new HashMap<>(); + + public static void cacheAddAnimation(ResourceLocation location, Animation animation) { + animationsCache.put(location, animation); + } + + public static void cacheAddAnimationLayer(ResourceLocation location, Integer priority) { + layersCache.put(location, priority); + } + + @SuppressWarnings({"JavaReflectionMemberAccess", "UnstableApiUsage", "unchecked"}) + public static void animationStatusUpdate(AnimationClientStatusPacket.Status status) { + switch (status) { + case ANIM_CACHE_CLEAR -> animationsCache.clear(); + case LAYER_CACHE_CLEAR -> { + ((IMixinPlayerAnimationFactoryHolder)(PlayerAnimationFactory.ANIMATION_DATA_FACTORY)) + .sccore$clearAnimations(); + layersCache.clear(); + } + case ANIM_REGISTER -> registerAnimations(animationsCache); + case LAYER_REGISTER -> { + registerLayers(layersCache); + layersCache.forEach((key, value) -> + PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory( + key, value, ClientCache::registerPlayerAnimation + ) + ); + ClientLevel level = Minecraft.getInstance().level; + if(level == null) { + SnowyCrescentCore.log.error("{} : Level is null", ClientCache.class.getName()); + return; + } + for (AbstractClientPlayer player : level.players()) { + try { + if (player == null) throw new Exception("player is null"); + Class playerClass = Player.class; + Field animationStackField = playerClass.getDeclaredField("animationStack"); + animationStackField.setAccessible(true); + Method createAnimationStack = playerClass.getDeclaredMethod("createAnimationStack"); + createAnimationStack.setAccessible(true); + AnimationStack newAnimationStack = (AnimationStack) createAnimationStack.invoke(player); + AnimationStack oldAnimationStack = (AnimationStack) animationStackField.get(player); + Field layersField = AnimationStack.class.getDeclaredField("layers"); + layersField.setAccessible(true); + ArrayList> oldArrayList = (ArrayList>) layersField.get(oldAnimationStack); + ArrayList> newArrayList = (ArrayList>) layersField.get(newAnimationStack); + ArrayList> result = new ArrayList<>(); + ArrayList> newListCopy = new ArrayList<>(newArrayList); + for (Pair oldPair : oldArrayList) { + newListCopy.removeIf(pair -> { + if(pair.getLeft().equals(oldPair.getLeft())) { + result.add(pair); + return true; + } else { + result.add(oldPair); + return false; + } + }); + } + result.addAll(newListCopy); + layersField.set(newAnimationStack, result); + animationStackField.set(player, newAnimationStack); + Field animationApplierField = playerClass.getDeclaredField("animationApplier"); + animationApplierField.setAccessible(true); + animationApplierField.set(player, new AnimationApplier(newAnimationStack)); + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) return; + Map dataAnimations = data.getAnimations(); + ResourceLocation riderAnimLayer = data.getRiderAnimLayer(); + if(riderAnimLayer != null) { + dataAnimations.put(riderAnimLayer, data.getRiderAnimation()); + } + for (ResourceLocation location : dataAnimations.keySet()) { + ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(player).get(location); + if(modifierLayer == null) continue; + KeyframeAnimation animation = animations.get(dataAnimations.get(location)).getAnimation(); + if(animation == null) continue; + modifierLayer.setAnimation(new KeyframeAnimationPlayer(animation)); + } + }catch (Exception e){ + SnowyCrescentCore.log.error("Failed to register on {} animation layer: {}", player, e.getMessage(), e); + } + } + } + } + } + + private static IAnimation registerPlayerAnimation(AbstractClientPlayer player) { + return new ModifierLayer<>(); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/registry/AnimationRegistry.java b/src/main/java/com/linearpast/sccore/animation/registry/AnimationRegistry.java deleted file mode 100644 index 451056c..0000000 --- a/src/main/java/com/linearpast/sccore/animation/registry/AnimationRegistry.java +++ /dev/null @@ -1,98 +0,0 @@ -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 4dcd3d5..70d43cc 100644 --- a/src/main/java/com/linearpast/sccore/capability/CapabilityUtils.java +++ b/src/main/java/com/linearpast/sccore/capability/CapabilityUtils.java @@ -24,7 +24,7 @@ import java.util.function.Supplier; public class CapabilityUtils { /** - * Simultaneously register player capability and corresponding network packets + * Simultaneously invite 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} @@ -49,7 +49,7 @@ public class CapabilityUtils { } /** - * Simultaneously register entity capability and corresponding network packets + * Simultaneously invite 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} 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 bb05f05..1faa539 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 @@ -57,7 +57,7 @@ public class EntityCapabilityHandler { 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); + log.error("Failed to instantiate capability sync class {}. Your capability invite is wrong.", record.aClass(), e); } } })); 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 c38565d..54189ab 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 @@ -57,7 +57,7 @@ public class PlayerCapabilityHandler { ICapabilitySync capabilitySync = (ICapabilitySync) record.aClass().getDeclaredConstructor().newInstance(); event.addCapability(key, new PlayerCapabilityProvider<>(key, capabilitySync)); } catch (Exception e) { - log.error("Failed to instantiate capability sync class {}. Your capability register is wrong.", record.aClass(), e); + log.error("Failed to instantiate capability sync class {}. Your capability invite is wrong.", record.aClass(), e); } }); } 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 96dfc01..984ed3c 100644 --- a/src/main/java/com/linearpast/sccore/capability/network/CapabilityChannel.java +++ b/src/main/java/com/linearpast/sccore/capability/network/CapabilityChannel.java @@ -29,7 +29,7 @@ public class CapabilityChannel { } /** - * Add a network packet through this method and register + * Add a network packet through this method and invite * @param clazz Network packet class * @param cid index * @param decoder decoder diff --git a/src/main/java/com/linearpast/sccore/core/ModCommands.java b/src/main/java/com/linearpast/sccore/core/ModCommands.java index 23da601..6d832e8 100644 --- a/src/main/java/com/linearpast/sccore/core/ModCommands.java +++ b/src/main/java/com/linearpast/sccore/core/ModCommands.java @@ -1,7 +1,7 @@ package com.linearpast.sccore.core; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.command.AnimationCommands; +import com.linearpast.sccore.animation.register.AnimationCommands; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; diff --git a/src/main/java/com/linearpast/sccore/core/ModConfigs.java b/src/main/java/com/linearpast/sccore/core/ModConfigs.java deleted file mode 100644 index fd2b1e7..0000000 --- a/src/main/java/com/linearpast/sccore/core/ModConfigs.java +++ /dev/null @@ -1,20 +0,0 @@ -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/configs/ModConfigs.java b/src/main/java/com/linearpast/sccore/core/configs/ModConfigs.java new file mode 100644 index 0000000..fb49236 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/core/configs/ModConfigs.java @@ -0,0 +1,61 @@ +package com.linearpast.sccore.core.configs; + +import net.minecraftforge.common.ForgeConfigSpec; + +public class ModConfigs { + public enum ConfigName { + inviteDuration("inviteDuration"), + inviteDistance("inviteDistance"), + inviteCooldown("inviteCooldown"), + requestDuration("requestDuration"), + requestCooldown("requestCooldown"), + applyDistance("applyDistance"), + applyDuration("applyDuration"), + applyCooldown("applyCooldown"), + ; + + private final String name; + ConfigName(String name){ + this.name = name; + } + + public String getName() { + return name; + } + } + public static class Server { + public static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + public static final ForgeConfigSpec SPEC; + + public static final ForgeConfigSpec.ConfigValue inviteDuration; + public static final ForgeConfigSpec.ConfigValue inviteDistance; + public static final ForgeConfigSpec.ConfigValue inviteCooldown; + public static final ForgeConfigSpec.ConfigValue requestDuration; + public static final ForgeConfigSpec.ConfigValue requestCooldown; + public static final ForgeConfigSpec.ConfigValue applyDistance; + public static final ForgeConfigSpec.ConfigValue applyDuration; + public static final ForgeConfigSpec.ConfigValue applyCooldown; + + static { + BUILDER.push("Animation"); + inviteDuration = BUILDER.comment("Animation invite duration. Ignore when zero. (seconds)") + .defineInRange(ConfigName.inviteDuration.name, 120, 0, Integer.MAX_VALUE); + inviteDistance = BUILDER.comment("Animation invite max distance. Ignore when zero. (blocks)") + .defineInRange(ConfigName.inviteDistance.name, 6, 0, Integer.MAX_VALUE); + inviteCooldown = BUILDER.comment("Animation invite cooldown. (seconds)") + .defineInRange(ConfigName.inviteCooldown.name, 60, 0, Integer.MAX_VALUE); + requestDuration = BUILDER.comment("Animation request duration. Ignore when zero (seconds)") + .defineInRange(ConfigName.requestDuration.name, 120, 0, Integer.MAX_VALUE); + requestCooldown = BUILDER.comment("Animation request cooldown. (seconds)") + .defineInRange(ConfigName.requestCooldown.name, 60, 0, Integer.MAX_VALUE); + applyDuration = BUILDER.comment("Animation apply duration. Ignore when zero. (seconds)") + .defineInRange(ConfigName.applyDuration.name, 120, 0, Integer.MAX_VALUE); + applyDistance = BUILDER.comment("Animation apply max distance. Ignore when zero. (blocks)") + .defineInRange(ConfigName.applyDistance.name, 6, 0, Integer.MAX_VALUE); + applyCooldown = BUILDER.comment("Animation apply cooldown. (seconds)") + .defineInRange(ConfigName.applyCooldown.name, 60, 0, Integer.MAX_VALUE); + BUILDER.pop(); + SPEC = BUILDER.build(); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/core/datagen/DataGenEvent.java b/src/main/java/com/linearpast/sccore/core/datagen/DataGenEvent.java new file mode 100644 index 0000000..b123fc2 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/core/datagen/DataGenEvent.java @@ -0,0 +1,28 @@ +package com.linearpast.sccore.core.datagen; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.core.datagen.provider.ModLangProvider; +import net.minecraft.core.HolderLookup; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.PackOutput; +import net.minecraftforge.common.data.ExistingFileHelper; +import net.minecraftforge.data.event.GatherDataEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.concurrent.CompletableFuture; + +@Mod.EventBusSubscriber(modid = SnowyCrescentCore.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class DataGenEvent { + + @SubscribeEvent + public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); + PackOutput packOutput = generator.getPackOutput(); + CompletableFuture lookupProvider = event.getLookupProvider(); + ExistingFileHelper helper = event.getExistingFileHelper(); + + generator.addProvider(event.includeClient(), new ModLangProvider(packOutput, ModLangProvider.Lang.EN_US)); + generator.addProvider(event.includeClient(), new ModLangProvider(packOutput, ModLangProvider.Lang.ZH_CN)); + } +} diff --git a/src/main/java/com/linearpast/sccore/core/datagen/ModLang.java b/src/main/java/com/linearpast/sccore/core/datagen/ModLang.java new file mode 100644 index 0000000..0bd8207 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/core/datagen/ModLang.java @@ -0,0 +1,238 @@ +package com.linearpast.sccore.core.datagen; + +import com.linearpast.sccore.SnowyCrescentCore; +import net.minecraft.sounds.SoundEvent; + +import java.util.ArrayList; +import java.util.List; + +public class ModLang { + public record LangEntity(T key, String zhCn, String enUs) { } + public static final List> langList = new ArrayList<>(); + private final static String translationString = "translation." + SnowyCrescentCore.MODID; + private final static String command = ".command"; + private static final String animation = ".animation"; + public enum TranslatableMessage{ + COMMAND_RUN_FAIL(new LangEntity<>( + translationString + command + animation + ".command_run_fail", + "命令执行失败。", + "Command run fail." + )), + COMMAND_RUN_SUCCESS(new LangEntity<>( + translationString + command + animation + ".command_run_success", + "命令执行成功。", + "Command run success." + )), + ANIMATION_NOT_PRESENT(new LangEntity<>( + translationString + command + animation + ".animation_not_present", + "动画不存在。", + "Animation is not present." + )), + ANIMATION_LAYER_NOT_PRESENT(new LangEntity<>( + translationString + command + animation + ".animation_layer_not_present", + "动画层不存在。", + "Animation layer is not present." + )), + PLAY_ANIMATION_FAIL(new LangEntity<>( + translationString + command + animation + ".play_animation_fail", + "在这些玩家上播放动画失败:%s", + "Fail to play animation with: %s" + )), + PLAY_ANIMATION_SUCCESS(new LangEntity<>( + translationString + command + animation + ".play_animation_success", + "在%s个玩家上播放动画成功。", + "Successfully played animation on %s player(s)." + )), + REMOVE_ANIMATION_FAIL(new LangEntity<>( + translationString + command + animation + ".remove_animation_fail", + "在这些玩家上移除动画失败:%s", + "Fail to remove animation with: %s" + )), + REMOVE_ANIMATION_SUCCESS(new LangEntity<>( + translationString + command + animation + ".remove_animation_success", + "在%s个玩家上移除动画成功。", + "Successfully removed animation on %s player(s)." + )), + CLEAR_ANIMATIONS(new LangEntity<>( + translationString + command + animation + ".clear_animations", + "动画已清除。", + "Animation cleared." + )), + REFRESH_ANIMATIONS(new LangEntity<>( + translationString + command + animation + ".refresh_animations", + "动画同步状态已刷新。", + "Animation refreshed." + )), + ACCEPT_MESSAGE_CLICK(new LangEntity<>( + translationString + command + animation + ".accept_message_click", + "单击此处同意。", + "Click here to accept." + )), + INVITE_MESSAGE(new LangEntity<>( + translationString + command + animation + ".invite_message", + "已发送邀请。", + "Invitation sent." + )), + INVITED_MESSAGE(new LangEntity<>( + translationString + command + animation + ".invited_message", + "%s§c§l 邀请§r你进行动画:%s。", + "%s§c§l invites§r you to animation: %s. " + )), + ACCEPT_INVITE_EXPIRED(new LangEntity<>( + translationString + command + animation + ".accept_invite_expired", + "邀请已超时。(%s分钟)", + "Invite expired.(%s minute(s))" + )), + INVITE_EXPIRED(new LangEntity<>( + translationString + command + animation + ".invite_expired", + "%s 接受了你的动画邀请,但是邀请超时了。(%s分钟)", + "%s has accepted your animation invitation but the invitation has expired. (%s minute(s))" + )), + ACCEPT_INVITE_TOO_FAR(new LangEntity<>( + translationString + command + animation + ".accept_invite_too_far", + "你们距离太远了。(%s格)", + "You are too far apart. (%s block(s))" + )), + INVITE_TOO_FAR(new LangEntity<>( + translationString + command + animation + ".invite_too_far", + "%s 接受了你的动画邀请,但你们距离太远了。(%s格)", + "%s has accepted your animation invitation but you are too far apart. (%s block(s))" + )), + ACCEPT_INVITE_SUCCESS(new LangEntity<>( + translationString + command + animation + ".accept_invite_success", + "已接受邀请。", + "Invitation accepted." + )), + INVITE_SUCCESS(new LangEntity<>( + translationString + command + animation + ".invite_success", + "%s 接受了你的动画邀请。", + "%s has accepted your animation invitation." + )), + REQUEST_MESSAGE(new LangEntity<>( + translationString + command + animation + ".request_message", + "已发送请求。", + "Request sent." + )), + REQUESTED_MESSAGE(new LangEntity<>( + translationString + command + animation + ".requested_message", + "%s§d§l 请求§r你进行动画:%s。", + "%s§d§l requests§r you to animation: %s. " + )), + ACCEPT_REQUEST_EXPIRED(new LangEntity<>( + translationString + command + animation + ".accept_request_expired", + "请求已超时。(%s分钟)", + "Request expired.(%s minute(s))" + )), + REQUEST_EXPIRED(new LangEntity<>( + translationString + command + animation + ".request_expired", + "%s 接受了你的动画请求,但是请求超时了。(%s分钟)", + "%s has accepted your animation request but the request has expired. (%s minute(s))" + )), + ACCEPT_REQUEST_SUCCESS(new LangEntity<>( + translationString + command + animation + ".accept_request_success", + "已接受请求。", + "Request accepted." + )), + REQUEST_SUCCESS(new LangEntity<>( + translationString + command + animation + ".request_success", + "%s 接受了你的动画请求。", + "%s has accepted your animation request." + )), + APPLY_JOIN_MESSAGE(new LangEntity<>( + translationString + command + animation + ".apply_join_message", + "已发送申请。", + "Application sent." + )), + APPLIED_JOIN_MESSAGE(new LangEntity<>( + translationString + command + animation + ".applied_join_message", + "%s§b§l 申请§r加入动画。", + "%S§b§l Apply for §r to join your animation. " + )), + ACCEPT_APPLY_EXPIRED(new LangEntity<>( + translationString + command + animation + ".accept_apply_expired", + "申请已超时。(%s分钟)", + "Application expired.(%s minute(s))" + )), + APPLY_EXPIRED(new LangEntity<>( + translationString + command + animation + ".apply_expired", + "%s 接受了你的动画申请,但是申请超时了。(%s分钟)", + "%s has accepted your animation application but the application has expired. (%s minute(s))" + )), + ACCEPT_APPLY_TOO_FAR(new LangEntity<>( + translationString + command + animation + ".accept_apply_too_far", + "你们距离太远了。(%s格)", + "You are too far apart. (%s block(s))" + )), + APPLY_TOO_FAR(new LangEntity<>( + translationString + command + animation + ".apply_too_far", + "%s 接受了你的动画申请,但你们距离太远了。(%s格)", + "%s has accepted your animation application but you are too far apart. (%s block(s))" + )), + ACCEPT_APPLY_SUCCESS(new LangEntity<>( + translationString + command + animation + ".accept_apply_success", + "%s 接受了 %s 的申请。", + "%s has accepted the application of %s." + )), + APPLY_SUCCESS(new LangEntity<>( + translationString + command + animation + ".apply_success", + "%s 接受了你的动画申请。", + "%s has accepted your animation application." + )), + WITHOUT_ANIMATION_RIDE_ENTITY(new LangEntity<>( + translationString + animation + ".without_animation_ride_entity", + "命令执行错误,已满人或不支持的动画。", + "Command run fail, full or unsupported animations." + )), + COMMAND_COOLDOWN(new LangEntity<>( + translationString + animation + ".command_cooldown", + "你不能执行该指令,冷却中:%s 秒。", + "You cannot execute this command, cooling down: %s seconds." + )), + ANIMATION_TO_JSON(new LangEntity<>( + translationString + command + animation + ".animation_to_json", + "动画%s已经存储到%s路径:", + "The animation %s has been stored in the path on %s:" + )), + ANIMATION_JSON_PATH(new LangEntity<>( + translationString + command + animation + ".animation_json_path", + "%s", + "%s" + )), + UNKNOWN_ANIMATION(new LangEntity<>( + translationString + animation + ".unknown_animation", + "未知的动画%s,请检查你的资源包是否完整。", + "Unknown animation %s, please check if your resource packs is complete." + )), + UNSAFE_FILE_DIRECTORY(new LangEntity<>( + translationString + command + animation + ".unsafe_file_directory", + "你选择的文件路径并不安全", + "%s" + )), + ; + + private final LangEntity langEntity; + TranslatableMessage(LangEntity lang){ + this.langEntity = lang; + } + + public String getKey() { + return langEntity.key; + } + } + + public static void initLang() { + langList.clear(); + + initLangMessage(); + } + + private static void initLangMessage() { + for (TranslatableMessage value : TranslatableMessage.values()) { + langList.add(value.langEntity); + } + } + + public static String getSoundKey(SoundEvent soundEvent){ + return "subtitle." + SnowyCrescentCore.MODID + ".sound." + soundEvent.getLocation().getPath(); + } +} diff --git a/src/main/java/com/linearpast/sccore/core/datagen/provider/ModLangProvider.java b/src/main/java/com/linearpast/sccore/core/datagen/provider/ModLangProvider.java new file mode 100644 index 0000000..ba3735b --- /dev/null +++ b/src/main/java/com/linearpast/sccore/core/datagen/provider/ModLangProvider.java @@ -0,0 +1,73 @@ +package com.linearpast.sccore.core.datagen.provider; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.core.datagen.ModLang; +import net.minecraft.data.PackOutput; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.block.Block; +import net.minecraftforge.common.data.LanguageProvider; + + +public class ModLangProvider extends LanguageProvider { + + private final Lang lang; + + static { + ModLang.initLang(); + } + + public ModLangProvider(PackOutput output, Lang lang) { + super(output, SnowyCrescentCore.MODID, lang.getLangName()); + this.lang = lang; + } + + @Override + protected void addTranslations() { + switch (lang){ + case EN_US -> ModLang.langList.forEach(langEntity -> addTranslation(langEntity.key(), langEntity.enUs())); + case ZH_CN -> ModLang.langList.forEach(langEntity -> addTranslation(langEntity.key(), langEntity.zhCn())); + } + } + + private void addTranslation(T o, String string) { + if(o instanceof Item object){ + add(object,string); + }else if(o instanceof Block object){ + add(object,string); + }else if(o instanceof String object){ + add(object,string); + }else if(o instanceof ItemStack object){ + add(object,string); + }else if(o instanceof Enchantment object){ + add(object,string); + }else if(o instanceof MobEffect object){ + add(object,string); + }else if(o instanceof EntityType object) { + add(object,string); + }else if(o instanceof SoundEvent object) { + add(ModLang.getSoundKey(object),string); + }else { + throw new RuntimeException("Unknown object type: " + o.getClass()); + } + } + + public enum Lang{ + + ZH_CN("zh_cn"), + EN_US("en_us"), + ; + + private final String langName; + Lang(String langName) { + this.langName = langName; + } + public String getLangName() { + return langName; + } + } +} diff --git a/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java b/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java index 048ba36..79e36a2 100644 --- a/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java +++ b/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java @@ -5,6 +5,7 @@ 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.animation.event.create.AnimationRegisterEvent; import com.linearpast.sccore.example.animation.event.ExampleCommandEvent; import com.linearpast.sccore.example.animation.event.ExamplePlayerAttackEvent; import net.minecraft.resources.ResourceLocation; @@ -32,29 +33,48 @@ public class ModAnimation { 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) + /** + * You can register animation layer by event or json
+ * See wiki (If I'm done.) + * @param event event + */ + public static void onLayerRegister(AnimationLayerRegisterEvent event) { + event.registerLayer(normalLayers, 42); + } + + /** + * You can register animation by event or json
+ * See wiki (If I'm done.) + * @param event event + */ + public static void onAnimationRegister(AnimationRegisterEvent event) { + //You must define corresponding Animation to invite + Animation amLTRL = Animation.create(AmLyingToRightLying) .withLyingType(Animation.LyingType.RIGHT) - .withRide(Ride.create().addComponentAnimation(AmStandToLying)); - Animation amSTL = new Animation(AmStandToLying) + .withName("Lying-to-Right-Lying"); + Animation amSTL = Animation.create(AmStandToLying) + .withName("Stand-to-Lying") .withLyingType(Animation.LyingType.FRONT); - - Animation waltzGentleman = new Animation(WaltzGentleman) + Animation waltzGentleman = Animation.create(WaltzGentleman) + .withName("Waltz-Gentleman") .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); + Animation waltzLady = Animation.create(WaltzLady) + .withName("Waltz-Lady") + .withCamYaw(180) + .withRide(Ride.create().addComponentAnimation(WaltzGentleman)); + //You can use it to invite an Animation + event.registerAnimation(AmLyingToRightLying, amLTRL); + event.registerAnimation(AmStandToLying, amSTL); + event.registerAnimation(WaltzGentleman, waltzGentleman); + event.registerAnimation(WaltzLady, waltzLady); + } + public static void register(IEventBus forgeBus, IEventBus modBus) { //Register by event //Or use AnimationUtils.registerAnimationLayer(ResourceLocation layer, int priority); - modBus.addListener(ModAnimation::onLayerRegister); + forgeBus.addListener(ModAnimation::onLayerRegister); + forgeBus.addListener(ModAnimation::onAnimationRegister); //Try to play animation forgeBus.addListener(ExamplePlayerAttackEvent::onPlayerAttack); @@ -63,8 +83,4 @@ public class ModAnimation { 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 index 2297978..d408cb4 100644 --- a/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java +++ b/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java @@ -23,6 +23,7 @@ import java.util.UUID; import static net.minecraft.commands.Commands.argument; import static net.minecraft.commands.Commands.literal; +@Deprecated public class ExampleCommandEvent { record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean isForce){} private static final Map> invites = new HashMap<>(); 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 index fb5d7de..bcae68b 100644 --- a/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java +++ b/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java @@ -16,8 +16,7 @@ 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 + * when attack sheep, will play stand to lying animation * @param event event */ public static void onPlayerAttack(AttackEntityEvent event) { @@ -32,18 +31,7 @@ public class ExamplePlayerAttackEvent { AnimationUtils.playAnimation(player, ModAnimation.normalLayers, null); } } - if(target instanceof ServerPlayer serverPlayer) { - AnimationUtils.startAnimationTogether( - serverPlayer, - ModAnimation.normalLayers, - ModAnimation.AmLyingToRightLying, - true, - player - ); - - } } - } /** diff --git a/src/main/java/com/linearpast/sccore/example/capability/ModCapability.java b/src/main/java/com/linearpast/sccore/example/capability/ModCapability.java index d806d03..acbe0c2 100644 --- a/src/main/java/com/linearpast/sccore/example/capability/ModCapability.java +++ b/src/main/java/com/linearpast/sccore/example/capability/ModCapability.java @@ -23,11 +23,11 @@ public class ModCapability { * @see CapabilityUtils#registerPlayerCapabilityWithNetwork */ public static void register(){ - //If you want to register network packets in your own mod, to use : createChannel(INSTANCE) + //If you want to invite 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() + //If you want invite about player, please use CapabilityUtils.registerPlayerCapabilityWithNetwork() CapabilityUtils.registerEntityCapabilityWithNetwork( //A resourceLocation, named arbitrarily without repetition SheepDataCapability.key, 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 index ab217e6..b7e49bc 100644 --- a/src/main/java/com/linearpast/sccore/example/capability/data/ISheepData.java +++ b/src/main/java/com/linearpast/sccore/example/capability/data/ISheepData.java @@ -5,7 +5,7 @@ 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. + * Server methods for sharing caps that may be used. */ public interface ISheepData extends ICapabilitySync { Integer getValue(); diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinPlayerAnimationFactoryHolder.java b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinPlayerAnimationFactoryHolder.java new file mode 100644 index 0000000..eb200af --- /dev/null +++ b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinPlayerAnimationFactoryHolder.java @@ -0,0 +1,62 @@ +package com.linearpast.sccore.mixin.animation.client; + +import com.linearpast.sccore.animation.mixin.IMixinPlayerAnimationFactoryHolder; +import dev.kosmx.playerAnim.api.layered.AnimationStack; +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.resources.ResourceLocation; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +@Mixin(PlayerAnimationFactory.FactoryHolder.class) +public class MixinPlayerAnimationFactoryHolder implements IMixinPlayerAnimationFactoryHolder { + @Unique + @Final + private static List> sccore$factories = new ArrayList<>(); + + @Inject( + method = "prepareAnimations", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private void prepareAnimations(AbstractClientPlayer player, AnimationStack playerStack, Map animationMap, CallbackInfo ci) { + for (Function factory: sccore$factories) { + DataHolder dataHolder = factory.apply(player); + if (dataHolder != null) { + playerStack.addAnimLayer(dataHolder.priority(), dataHolder.animation()); + if (dataHolder.id() != null) { + animationMap.put(dataHolder.id(), dataHolder.animation()); + } + } + } + ci.cancel(); + } + + @Inject( + method = "registerFactory", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private void registerFactory(ResourceLocation id, int priority, PlayerAnimationFactory factory, CallbackInfo ci) { + sccore$factories.add(player -> Optional.ofNullable(factory.invoke(player)).map(animation -> new DataHolder(id, priority, animation)).orElse(null)); + ci.cancel(); + } + + @Unique + public void sccore$clearAnimations() { + sccore$factories.clear(); + } +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 9f2a89f..20ff457 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -32,4 +32,11 @@ modId = "playeranimator" mandatory = false versionRange = "[1.0.1,)" ordering = "AFTER" +side = "BOTH" + +[[dependencies."${mod_id}"]] +modId = "bendylib" +mandatory = false +versionRange = "[4.0.0,)" +ordering = "AFTER" side = "BOTH" \ No newline at end of file diff --git a/src/main/resources/sccore.mixins.json b/src/main/resources/sccore.mixins.json index 27f4ec7..b25d9ec 100644 --- a/src/main/resources/sccore.mixins.json +++ b/src/main/resources/sccore.mixins.json @@ -6,7 +6,8 @@ "refmap": "sccore.refmap.json", "plugin": "com.linearpast.sccore.mixin.SCCoreMixinPlugin", "mixins": [ - "animation.MixinEntity" + "animation.MixinEntity", + "animation.client.MixinPlayerAnimationFactoryHolder" ], "client": [ "animation.client.MixinEntity",