version 0.0.7

This commit is contained in:
LostInLinearPast 2025-10-30 23:39:21 +08:00
parent 0f5e9ee4ea
commit eb3cca667a
64 changed files with 3017 additions and 585 deletions

View File

@ -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
}

BIN
gif/1.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1009 KiB

BIN
gif/2.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

View File

@ -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.

View File

@ -0,0 +1,10 @@
[
{
"key": "sccore:normal_layers",
"priority": 42
},
{
"key": "sccore:ride_layers",
"priority": 43
}
]

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-10-30T21:02:30.6951333 Languages: zh_cn
feac50843a3ec6d5a3ad4c4e917d0f19a2b857cb assets/sccore/lang/zh_cn.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-10-30T21:02:30.6972105 Languages: en_us
165168d6118e3dde833169d2ab1bc0028d425d55 assets/sccore/lang/en_us.json

View File

@ -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. "
}

View File

@ -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。"
}

View File

@ -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)) {

View File

@ -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<IAnimation> modifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
.getPlayerAssociatedData(clientPlayer).get(layer);
.getPlayerAssociatedData(clientPlayer).get(clientPlayerLayer);
ModifierLayer<IAnimation> targetModifierLayer = (ModifierLayer<IAnimation>) 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<IAnimation> modifierLayer = (ModifierLayer<IAnimation>) 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)

View File

@ -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 {
/**
* <pre>
* 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.
* </pre>
* @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. <br>
* 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;
});
}
/**
* <pre>
* 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.
* <p>
* 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<ResourceLocation> 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<IAnimation> animationModifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
@ -177,25 +199,55 @@ public class AnimationUtils {
});
}
/**
* Test if layer exist animation which is not end.
* <p>
* 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<ResourceLocation> resourceLocations = new HashSet<>();
if(layer == null) resourceLocations = AnimationRegistry.getLayers().keySet();
else resourceLocations.add(layer);
for (ResourceLocation location : resourceLocations) {
ModifierLayer<IAnimation> animationModifierLayer = (ModifierLayer<IAnimation>) 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. <br>
* The number is bigger and the priority is higher. <br>
* It must run before these events : <br>
* {@link net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent} <br>
* {@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<ResourceLocation> 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));
}
}

View File

@ -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<ResourceLocation, ResourceLocation> animMap = new HashMap<>();
private ResourceLocation rideAnimLayer;
private ResourceLocation rideAnimation;
@Override
public void mergeAnimations(Map<ResourceLocation, ResourceLocation> 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<IAnimationCapability> getCapability(Player player){

View File

@ -16,6 +16,8 @@ public interface IAnimationCapability extends ICapabilitySync<Player> {
void clearAnimations();
boolean isAnimationPresent(ResourceLocation layer);
void setRideAnimLayer(ResourceLocation layer);
ResourceLocation getRideAnimLayer();
ResourceLocation getRiderAnimLayer();
ResourceLocation getRiderAnimation();
void setRiderAnimation(ResourceLocation layer, ResourceLocation animation);
void removeRiderAnimation();
}

View File

@ -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<UUID, Map<UUID, ApplyRecord>> applies = new HashMap<>();
record ApplyRecord(long time, boolean isForce){}
private static final Map<UUID, Long> lastAppliedMap = new HashMap<>();
public static void register(LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> 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<UUID, ApplyRecord> 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<CommandSourceStack> 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<UUID, ApplyRecord> 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;
}
}

View File

@ -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<UUID, Long> lastInvitedMap = new HashMap<>();
record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean isForce){}
private static final Map<UUID, Map<UUID, InviteRecord>> invites = new HashMap<>();
public static void register(LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> 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<UUID, InviteRecord> 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<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
try {
ServerPlayer target = source.getPlayerOrException();
ServerPlayer player = EntityArgument.getPlayer(context, "player");
Map<UUID, InviteRecord> 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;
}
}

View File

@ -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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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");
}
}

View File

@ -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<CommandSourceStack> animCommand){
RequiredArgumentBuilder<CommandSourceStack, String> 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<CommandSourceStack> context, boolean withRide) {
CommandSourceStack source = context.getSource();
try {
Collection<ServerPlayer> 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<ServerPlayer> playerSet = Set.copyOf(players);
Collection<ServerPlayer> 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<ServerPlayer> 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<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
try {
Collection<ServerPlayer> 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<ServerPlayer> playerSet = Set.copyOf(players);
Collection<ServerPlayer> 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<ServerPlayer> 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<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
try {
Collection<ServerPlayer> 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;
}
}

View File

@ -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<CommandSourceStack> 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<CommandSourceStack> ctx, boolean withRide) {
CommandSourceStack source = ctx.getSource();
try {
Collection<ServerPlayer> 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<ServerPlayer> 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<ServerPlayer> 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<CommandSourceStack> ctx) {
CommandSourceStack source = ctx.getSource();
try {
Collection<ServerPlayer> 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<ServerPlayer> 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<ServerPlayer> 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<CommandSourceStack> ctx) {
CommandSourceStack source = ctx.getSource();
try {
Collection<ServerPlayer> 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;
}
}

View File

@ -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<UUID, Long> lastRequestedMap = new HashMap<>();
record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean withRide, boolean isForce) {}
private static final Map<UUID, Map<UUID, InviteRecord>> invites = new HashMap<>();
public static void register(LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> 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<UUID, InviteRecord> 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<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
try {
ServerPlayer target = source.getPlayerOrException();
ServerPlayer player = EntityArgument.getPlayer(context, "player");
//get request record and test
Map<UUID, InviteRecord> 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;
}
}

View File

@ -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<String> {
private static final Supplier<Collection<String>> EXAMPLES = () -> AnimationRegistry.getAnimations().keySet().stream()
.map(ResourceLocation::toString).collect(Collectors.toSet());
private static final Supplier<Collection<String>> EXAMPLES = AnimationArgument::getAnimationNames;
private static final DynamicCommandExceptionType UNKNOWN_TYPE = new DynamicCommandExceptionType(
animation -> Component.literal("Unknow animation : " + animation.toString())
);
private final Set<String> animationNames;
private final Supplier<Set<String>> animationNames;
public AnimationArgument() {
this.animationNames = AnimationRegistry.getAnimations().keySet().stream()
.map(ResourceLocation::toString).collect(Collectors.toSet());
this.animationNames = AnimationArgument::getAnimationNames;
}
private static Set<String> getAnimationNames(){
HashSet<String> 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<CommandSourceStack> 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 <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(animationNames, builder);
return SharedSuggestionProvider.suggest(animationNames.get(), builder);
}
public Collection<String> getExamples() {
@ -54,7 +80,7 @@ public class AnimationArgument implements ArgumentType<String> {
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;

View File

@ -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<String> {
private static final Supplier<Collection<String>> EXAMPLES = () -> AnimationLayerRegistry.getAnimLayers().keySet().stream()
private static final Supplier<Collection<String>> 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<String> animationLayers;
private final Supplier<Set<String>> 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<String> {
}
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(animationLayers, builder);
return SharedSuggestionProvider.suggest(animationLayers.get(), builder);
}
public Collection<String> getExamples() {
@ -54,7 +54,7 @@ public class AnimationLayerArgument implements ArgumentType<String> {
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;

View File

@ -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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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;
}
}

View File

@ -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<CommandSourceStack> animCommand){
animCommand.then(literal("refresh").executes(AnimationRefreshCommand::refresh));
animCommand.then(literal("refresh").executes(RefreshAnimCommand::refresh));
}
private static int refresh(CommandContext<CommandSourceStack> 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;

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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<ResourceLocation, Integer> parse() {
return fromJson();
}
private Map<ResourceLocation, Integer> fromJson() {
try {
JsonArray jsonArray = originElement.getAsJsonArray();
Map<ResourceLocation, Integer> 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<ResourceLocation, Integer> layers = AnimationRegistry.getLayers();
private final Map<String, Set<ResourceLocation>> layerNames = new HashMap<>();
Writer(Path file) {
this.file = file;
for (ResourceLocation location : layers.keySet()) {
String namespace = location.getNamespace();
Set<ResourceLocation> 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<String, JsonElement> allToJson() {
Map<String, JsonElement> map = new HashMap<>();
for (String namespace : layerNames.keySet()) {
Set<ResourceLocation> locationSet = layerNames.get(namespace);
JsonElement json = toJson(locationSet);
map.put(namespace, json);
}
return map;
}
public Path syntax(String ... namespaces) throws IOException {
Set<String> namespaceSet;
if(namespaces.length == 0) {
namespaceSet = layerNames.keySet();
} else {
namespaceSet = Arrays.stream(namespaces).collect(Collectors.toSet());
}
for (String name : namespaceSet) {
Set<ResourceLocation> 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<ResourceLocation> 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;
}
}
}

View File

@ -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<ServerPlayer> players = new HashSet<>();
private final Map<ResourceLocation, UUID> 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<ResourceLocation> 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<ResourceLocation> 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();

View File

@ -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<ResourceLocation, Integer> 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<ResourceLocation, Integer> getAnimLayers() {
return animLayers;
}
public static boolean isLayerPresent(ResourceLocation layer) {
return animLayers.containsKey(layer);
}
private static IAnimation registerPlayerAnimation(AbstractClientPlayer player) {
return new ModifierLayer<>();
}
}

View File

@ -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();
}
}
}
}
}
}

View File

@ -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);
}

View File

@ -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 <br>
* Generally, the static function is better.
* @see com.linearpast.sccore.animation.AnimationUtils#registerAnimationLayer
* You can listen this event to invite an animation layer <br>
* It is only useful in server
*/
public class AnimationLayerRegisterEvent extends Event implements IModBusEvent {
public class AnimationLayerRegisterEvent extends Event {
private final Map<ResourceLocation, Integer> layers = new HashMap<>();
public AnimationLayerRegisterEvent() {
}
public Map<ResourceLocation, Integer> getLayers() {
return layers;
}
public void putLayer(ResourceLocation key, Integer value) {
public void registerLayer(ResourceLocation key, Integer value) {
layers.put(key, value);
}
}

View File

@ -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 <br>
* It is only useful in server
*/
public class AnimationRegisterEvent extends Event {
private final Map<ResourceLocation, Animation> animations = new HashMap<>();
public Map<ResourceLocation, Animation> getAnimations() {
return new HashMap<>(animations);
}
public void registerAnimation(ResourceLocation location, Animation animation) {
animations.put(location, animation);
}
}

View File

@ -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();
}

View File

@ -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<Player> {
}
private void testPlayAnimations(AbstractClientPlayer player, CompoundTag tag, IAnimationCapability data) {
Set<ResourceLocation> 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<ResourceLocation> 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<Player> {
AnimationPlayer.playAnimation(player, oldLayerLocation, null);
}
if(!tag.contains(AnimationDataCapability.RideAnimLayer)) {
if(data != null && data.getRideAnimLayer() != null) {
AnimationUtils.playAnimation(null, data.getRideAnimLayer(), null);
}
}
}
}

View File

@ -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<NetworkEvent.Context> supplier) {
NetworkEvent.Context context = supplier.get();
context.enqueueWork(() -> {
Status state = Status.getStatus(status);
if(state == null) return;
AnimationRegistry.ClientCache.animationStatusUpdate(state);
});
}
}

View File

@ -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<NetworkEvent.Context> supplier) {
NetworkEvent.Context context = supplier.get();
context.enqueueWork(() -> {
context.setPacketHandled(true);
JsonElement element = JsonParser.parseString(json);
if(isLayer) {
Map<ResourceLocation, Integer> 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);
}
});
}
}

View File

@ -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<NetworkEvent.Context> 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)
);

View File

@ -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<NetworkEvent.Context> 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);
}
});
}

View File

@ -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);

View File

@ -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
);
}
}

View File

@ -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();
}
}

View File

@ -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<CommandSourceStack> builder) {
if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()){
LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> builder) {
if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()) {
LiteralArgumentBuilder<CommandSourceStack> anim = literal("anim");
AnimationRefreshCommand.register(anim);
RefreshAnimCommand.register(anim);
GenerateJsonClientCommand.register(anim);
builder.then(anim);
}
}

View File

@ -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;

View File

@ -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<ResourceLocation, Animation> animations = new HashMap<>();
private static final Map<ResourceLocation, Integer> layers = new HashMap<>();
public static Map<ResourceLocation, Animation> getAnimations() {
return Map.copyOf(animations);
}
public static Map<ResourceLocation, Integer> getLayers() {
return Map.copyOf(layers);
}
@OnlyIn(Dist.CLIENT)
public static void registerAnimations(Map<ResourceLocation, Animation> animationMap) {
animations.clear();
animations.putAll(animationMap);
}
@OnlyIn(Dist.CLIENT)
public static void registerLayers(Map<ResourceLocation, Integer> 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<Path> animZipPaths = getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".anim.zip")
);
Set<Path> 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<Path> animPaths = getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".anim.json")
);
Set<Path> layerPaths = getAllFile(
dataPackPath.resolve("animation"),
path -> path.getFileName().toString().equals("animation.layer.json")
);
Set<Animation> animationsSet = new HashSet<>();
Map<ResourceLocation, Integer> 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<ResourceLocation, Integer> 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<ResourceLocation, Animation> 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<ResourceLocation, Integer> 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<String, JsonElement> 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<Path> getAllFile(Path directory, Predicate<Path> filter) {
try (Stream<Path> 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<? extends ZipEntry> 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<ResourceLocation, Animation> animationsCache = new HashMap<>();
private static final Map<ResourceLocation, Integer> 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<Pair<Integer, IAnimation>> oldArrayList = (ArrayList<Pair<Integer, IAnimation>>) layersField.get(oldAnimationStack);
ArrayList<Pair<Integer, IAnimation>> newArrayList = (ArrayList<Pair<Integer, IAnimation>>) layersField.get(newAnimationStack);
ArrayList<Pair<Integer, IAnimation>> result = new ArrayList<>();
ArrayList<Pair<Integer, IAnimation>> newListCopy = new ArrayList<>(newArrayList);
for (Pair<Integer, IAnimation> 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<ResourceLocation, ResourceLocation> dataAnimations = data.getAnimations();
ResourceLocation riderAnimLayer = data.getRiderAnimLayer();
if(riderAnimLayer != null) {
dataAnimations.put(riderAnimLayer, data.getRiderAnimation());
}
for (ResourceLocation location : dataAnimations.keySet()) {
ModifierLayer<IAnimation> modifierLayer = (ModifierLayer<IAnimation>) 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<>();
}
}
}

View File

@ -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<ResourceLocation, Animation> animations = new HashMap<>();
public static Map<ResourceLocation, Animation> 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();
});
}
}

View File

@ -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}

View File

@ -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);
}
}
}));

View File

@ -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);
}
});
}

View File

@ -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

View File

@ -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;

View File

@ -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<Boolean> enableExample;
static {
BUILDER.push("Development");
enableExample = BUILDER.comment("Enable some example for lib.")
.define("enableExample", false);
BUILDER.pop();
SPEC = BUILDER.build();
}
}
}

View File

@ -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<Integer> inviteDuration;
public static final ForgeConfigSpec.ConfigValue<Integer> inviteDistance;
public static final ForgeConfigSpec.ConfigValue<Integer> inviteCooldown;
public static final ForgeConfigSpec.ConfigValue<Integer> requestDuration;
public static final ForgeConfigSpec.ConfigValue<Integer> requestCooldown;
public static final ForgeConfigSpec.ConfigValue<Integer> applyDistance;
public static final ForgeConfigSpec.ConfigValue<Integer> applyDuration;
public static final ForgeConfigSpec.ConfigValue<Integer> 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();
}
}
}

View File

@ -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<HolderLookup.Provider> 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));
}
}

View File

@ -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>(T key, String zhCn, String enUs) { }
public static final List<LangEntity<?>> 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<String> langEntity;
TranslatableMessage(LangEntity<String> 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();
}
}

View File

@ -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 <T> 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;
}
}
}

View File

@ -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 <br>
* 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 <br>
* 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);
}
}

View File

@ -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<UUID, Map<UUID, InviteRecord>> invites = new HashMap<>();

View File

@ -16,8 +16,7 @@ import net.minecraftforge.event.entity.player.AttackEntityEvent;
public class ExamplePlayerAttackEvent {
/**
* when attack sheep, will play stand to lying animation <br>
* 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
);
}
}
}
/**

View File

@ -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,

View File

@ -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) <br>
* Common methods for sharing caps that may be used.
* Server methods for sharing caps that may be used.
*/
public interface ISheepData extends ICapabilitySync<Sheep> {
Integer getValue();

View File

@ -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<Function<AbstractClientPlayer, DataHolder>> sccore$factories = new ArrayList<>();
@Inject(
method = "prepareAnimations",
at = @At("HEAD"),
cancellable = true,
remap = false
)
private void prepareAnimations(AbstractClientPlayer player, AnimationStack playerStack, Map<ResourceLocation, IAnimation> animationMap, CallbackInfo ci) {
for (Function<AbstractClientPlayer, DataHolder> 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();
}
}

View File

@ -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"

View File

@ -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",