diff --git a/common/src/main/java/com/mrbysco/armorposer/Reference.java b/common/src/main/java/com/mrbysco/armorposer/Reference.java index 2d14a17..aefe920 100644 --- a/common/src/main/java/com/mrbysco/armorposer/Reference.java +++ b/common/src/main/java/com/mrbysco/armorposer/Reference.java @@ -19,11 +19,13 @@ public class Reference { public static final String MOD_NAME = "Armor Poser"; public static final Logger LOGGER = LogUtils.getLogger(); + public static final int ANIMATION_SEARCH_RADIUS = 32; public static final ResourceLocation SYNC_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "sync_packet"); public static final ResourceLocation SWAP_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "swap_packet"); public static final ResourceLocation RENAME_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "rename_packet"); public static final ResourceLocation SCREEN_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "screen_packet"); + public static final ResourceLocation COPY_TO_BOOK_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "copy_to_book"); public static final Map defaultPoseMap = initializePoseMap(); @@ -83,4 +85,9 @@ public static boolean canResize(Player player) { } return true; } + + public static boolean animationEnabled = false; + public static void setAnimationEnabled(boolean value) { + animationEnabled = value; + } } \ No newline at end of file diff --git a/common/src/main/java/com/mrbysco/armorposer/animation/AnimationHandler.java b/common/src/main/java/com/mrbysco/armorposer/animation/AnimationHandler.java new file mode 100644 index 0000000..9826bc4 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/animation/AnimationHandler.java @@ -0,0 +1,88 @@ +package com.mrbysco.armorposer.animation; + +import com.mrbysco.armorposer.Reference; +import com.mrbysco.armorposer.data.BookCopyData; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.ai.targeting.TargetingConditions; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.decoration.ItemFrame; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.item.component.WrittenBookContent; +import net.minecraft.world.phys.AABB; + +import java.util.HashSet; +import java.util.Set; + +public class AnimationHandler { + // Cache of item frames positions that are currently being animated + private static final Set cachedFrames = new HashSet<>(); + + /** + * Handles the animation for the armor stand when an item frame is powered while holding a compatible armor poser book + * + * @param frame The item frame that is being checked + */ + public static void onFrameUpdate(ItemFrame frame) { + if (frame.level() instanceof ServerLevel serverLevel && frame.getDirection() == Direction.UP) { + BlockPos pos = frame.blockPosition(); + ItemStack frameStack = frame.getItem(); + // Check if the item frame is holding a valid armor poser book + if (isValidArmorPoserBook(frameStack)) { + // Check if the item frame is powered + if (serverLevel.getSignal(pos, Direction.UP) >= 1) { + if (!cachedFrames.contains(pos)) { + cachedFrames.add(pos); + WrittenBookContent bookContent = frameStack.getOrDefault(DataComponents.WRITTEN_BOOK_CONTENT, WrittenBookContent.EMPTY); + // The name to match the armor stand with (empty string if no match required) + String match = ""; + if (!bookContent.pages().isEmpty()) { + var firstPage = bookContent.pages().getFirst(); + if (!firstPage.raw().getString().isEmpty()) + match = firstPage.raw().getString(); + } + // Targeting conditions for the armor stand + TargetingConditions conditions = TargetingConditions.forNonCombat(); + if (!match.isEmpty()) { + String finalMatch = match; + conditions.selector((livingEntity, level) -> + livingEntity.getName().getString().equals(finalMatch)); + } + // Search for the nearest armor stand within the search radius + var nearestStand = serverLevel.getNearestEntity(ArmorStand.class, conditions, null, pos.getX(), pos.getY(), pos.getZ(), + AABB.ofSize(frame.position(), Reference.ANIMATION_SEARCH_RADIUS, Reference.ANIMATION_SEARCH_RADIUS, Reference.ANIMATION_SEARCH_RADIUS)); + if (nearestStand != null) { + CompoundTag customTag = frameStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag(); + // Check if the book contains a saved pose + if (customTag.contains("SavedPose")) { + CompoundTag poseTag = customTag.getCompound("SavedPose"); + BookCopyData bookCopyData = new BookCopyData(nearestStand.getUUID(), poseTag); + bookCopyData.handleFrame(nearestStand); + } + } + } + } else { + cachedFrames.remove(pos); + } + } + } + } + + /** + * Checks if the item frame is holding a valid armor poser book + * + * @param stack The item stack that is being checked + * @return True if the item stack is a valid armor poser book + */ + private static boolean isValidArmorPoserBook(ItemStack stack) { + return stack.is(Items.WRITTEN_BOOK) + && stack.getCustomName() != null + && stack.getCustomName().getString().equals("Armor Poser") + && stack.has(DataComponents.CUSTOM_DATA); + } +} diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java index 9d666dd..414025c 100644 --- a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java @@ -217,9 +217,13 @@ public void init() { .tooltip(Tooltip.create(Component.translatable("armorposer.gui.tooltip.poses"))).build()); this.addRenderableWidget(Button.builder(Component.translatable("armorposer.gui.label.copy"), (button) -> { CompoundTag compound = this.writeFieldsToNBT(); - String clipboardData = compound.toString(); - if (this.minecraft != null) { - this.minecraft.keyboardHandler.setClipboard(clipboardData); + if (hasShiftDown()) { + Services.PLATFORM.copyArmorStandPose(this.entityArmorStand, compound); + } else { + String clipboardData = compound.toString(); + if (this.minecraft != null) { + this.minecraft.keyboardHandler.setClipboard(clipboardData); + } } }).bounds(offsetX, offsetY + 22, 42, 20).tooltip(Tooltip.create(Component.translatable("armorposer.gui.tooltip.copy"))).build()); this.addRenderableWidget(Button.builder(Component.translatable("armorposer.gui.label.paste"), (button) -> { diff --git a/common/src/main/java/com/mrbysco/armorposer/data/BookCopyData.java b/common/src/main/java/com/mrbysco/armorposer/data/BookCopyData.java new file mode 100644 index 0000000..97a8f50 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/data/BookCopyData.java @@ -0,0 +1,82 @@ +package com.mrbysco.armorposer.data; + +import net.minecraft.core.UUIDUtil; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.CustomData; + +import java.util.UUID; + +public record BookCopyData(UUID entityUUID, CompoundTag tag) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + BookCopyData::entityUUID, + ByteBufCodecs.COMPOUND_TAG, + BookCopyData::tag, + BookCopyData::new); + + /** + * Handles applying the pose data to the player's offhand book if it's an armor poser book + * + * @param player The player to apply the data to + */ + public void handleData(Player player) { + if (!tag.isEmpty()) { + ItemStack offStack = player.getOffhandItem(); + // Check if the player is holding an armor poser book + if (offStack.is(Items.WRITTEN_BOOK) && offStack.getCustomName() != null && offStack.getCustomName().getString().equals("Armor Poser")) { + CustomData data = offStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + CompoundTag tagCopy = data.copyTag(); + // Set the datapack to ArmorStatuesV2 to make it compatible with the Armor Statues Datapack + tagCopy.putString("datapack", "ArmorStatuesV2"); + // Set the pose data to the book + tagCopy.put("SavedPose", tag); + offStack.set(DataComponents.CUSTOM_DATA, CustomData.of(tagCopy)); + } + } + } + + /** + * Handles the pose data for the armor stand + * + * @param armorStand The armor stand to apply the pose to + */ + public void handleFrame(ArmorStand armorStand) { + CompoundTag entityTag = armorStand.saveWithoutId(new CompoundTag()); + CompoundTag entityTagCopy = entityTag.copy(); + + if (!tag.isEmpty()) { + entityTagCopy.merge(tag); + armorStand.load(entityTagCopy); + armorStand.setUUID(entityUUID); + + ListTag tagList = tag.getList("Move", Tag.TAG_DOUBLE); + double xOffset = tagList.getDouble(0); + double yOffset = tagList.getDouble(1); + double zOffset = tagList.getDouble(2); + if (xOffset != 0 || yOffset != 0 || zOffset != 0) + armorStand.setPosRaw(armorStand.getX() + xOffset, + armorStand.getY() + yOffset, + armorStand.getZ() + zOffset); + + double scale = tag.getDouble("Scale"); + if (scale > 0) { + AttributeInstance attributeInstance = armorStand.getAttributes().getInstance(Attributes.SCALE); + if (attributeInstance != null) { + attributeInstance.setBaseValue(scale); + } + } + } + } +} diff --git a/common/src/main/java/com/mrbysco/armorposer/packets/ArmorStandCopyToBookPayload.java b/common/src/main/java/com/mrbysco/armorposer/packets/ArmorStandCopyToBookPayload.java new file mode 100644 index 0000000..8fd3af1 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/packets/ArmorStandCopyToBookPayload.java @@ -0,0 +1,27 @@ +package com.mrbysco.armorposer.packets; + +import com.mrbysco.armorposer.Reference; +import com.mrbysco.armorposer.data.BookCopyData; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +public record ArmorStandCopyToBookPayload(BookCopyData data) implements CustomPacketPayload { + public static final StreamCodec CODEC = CustomPacketPayload.codec( + ArmorStandCopyToBookPayload::write, + ArmorStandCopyToBookPayload::new); + public static final Type ID = new Type<>(Reference.COPY_TO_BOOK_PACKET_ID); + + public ArmorStandCopyToBookPayload(final FriendlyByteBuf packetBuffer) { + this(BookCopyData.STREAM_CODEC.decode(packetBuffer)); + } + + public void write(FriendlyByteBuf buf) { + BookCopyData.STREAM_CODEC.encode(buf, data()); + } + + @Override + public Type type() { + return ID; + } +} diff --git a/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java b/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java index aa20c92..f2e7088 100644 --- a/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java +++ b/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java @@ -23,6 +23,11 @@ public interface IPlatformHelper { */ void renameArmorStand(ArmorStand armorStand, String newName); + /** + * Copy Armor Stand pose to book + */ + void copyArmorStandPose(ArmorStand armorStand, CompoundTag compound); + /** * Allow scrolling to increase/decrease the angle of text fields */ diff --git a/common/src/main/resources/assets/armorposer/lang/en_us.json b/common/src/main/resources/assets/armorposer/lang/en_us.json index fa540cf..45b7646 100644 --- a/common/src/main/resources/assets/armorposer/lang/en_us.json +++ b/common/src/main/resources/assets/armorposer/lang/en_us.json @@ -10,6 +10,8 @@ "armorposer.config.resizeWhitelist.tooltip": "List of players that are allowed to resize the Armor Stand when restrictResizeToOP is enabled", "armorposer.config.restrictResizeToOP": "Restrict Resize To OP", "armorposer.config.restrictResizeToOP.tooltip": "Restrict the ability to resize the Armor Stand to server operators", + "armorposer.config.enableAnimation": "Enable Animation", + "armorposer.config.enableAnimation.tooltip": "Restrict the ability to resize the Armor Stand to server operators", "armorposer.configuration.title": "Armor Poser", "armorposer.gui.armor_list.list": "Armor Stands", "armorposer.gui.armor_list.locate": "Locate", @@ -120,5 +122,7 @@ "text.autoconfig.armorposer.option.general.resizeWhitelist.@Tooltip": "List of players that are allowed to resize the Armor Stand when restrictResizeToOP is enabled", "text.autoconfig.armorposer.option.general.restrictResizeToOP": "Restrict Resize To OP", "text.autoconfig.armorposer.option.general.restrictResizeToOP.@Tooltip": "Restrict the ability to resize the Armor Stand to server operators", + "text.autoconfig.armorposer.option.general.enableAnimation": "Enable Animation", + "text.autoconfig.armorposer.option.general.enableAnimation.@Tooltip": "Restrict the ability to resize the Armor Stand to server operators", "text.autoconfig.armorposer.title": "Armor Poser" } \ No newline at end of file diff --git a/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java b/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java index 54b5e48..65b7804 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java @@ -1,10 +1,12 @@ package com.mrbysco.armorposer; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.data.BookCopyData; import com.mrbysco.armorposer.data.RenameData; import com.mrbysco.armorposer.data.SwapData; import com.mrbysco.armorposer.data.SyncData; import com.mrbysco.armorposer.handlers.EventHandler; +import com.mrbysco.armorposer.packets.ArmorStandCopyToBookPayload; import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; @@ -13,10 +15,12 @@ import me.shedaniel.autoconfig.ConfigHolder; import me.shedaniel.autoconfig.serializer.Toml4jConfigSerializer; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.decoration.ArmorStand; @@ -26,6 +30,18 @@ public class ArmorPoser implements ModInitializer { @Override public void onInitialize() { config = AutoConfig.register(PoserConfig.class, Toml4jConfigSerializer::new); + config.registerLoadListener((holder, config) -> { + Reference.setAnimationEnabled(config.general.enableAnimation); + return InteractionResult.PASS; + }); + config.registerSaveListener((holder, config) -> { + Reference.setAnimationEnabled(config.general.enableAnimation); + return InteractionResult.PASS; + }); + + ServerLifecycleEvents.SERVER_STARTING.register((server) -> { + Reference.setAnimationEnabled(config.get().general.enableAnimation); + }); UseItemCallback.EVENT.register((player, world, hand) -> EventHandler.onPlayerRightClickItem(player, hand)); @@ -67,5 +83,12 @@ public void onInitialize() { } }); }); + PayloadTypeRegistry.playC2S().register(ArmorStandCopyToBookPayload.ID, ArmorStandCopyToBookPayload.CODEC); + ServerPlayNetworking.registerGlobalReceiver(ArmorStandCopyToBookPayload.ID, (payload, context) -> { + BookCopyData bookData = payload.data(); + context.player().server.execute(() -> { + bookData.handleData(context.player()); + }); + }); } } diff --git a/fabric/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java b/fabric/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java index a57f844..55bfa8f 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java @@ -24,6 +24,8 @@ public static class General { @ConfigEntry.Gui.Tooltip @Comment("Allow scrolling to add / decrease an angle value in the posing screen") public boolean allowScrolling = true; + @Comment("Enable Armor Poser's animation system for the Armor Stand") + public boolean enableAnimation = false; @ConfigEntry.Gui.Tooltip @Comment("Restrict the ability to resize the Armor Stand to server operators") public boolean restrictResizeToOP = false; diff --git a/fabric/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java b/fabric/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java index 9910f90..00e060a 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java @@ -1,6 +1,8 @@ package com.mrbysco.armorposer.handlers; import com.mrbysco.armorposer.ArmorPoser; +import com.mrbysco.armorposer.Reference; +import com.mrbysco.armorposer.animation.AnimationHandler; import com.mrbysco.armorposer.config.PoserConfig; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; @@ -10,6 +12,7 @@ import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.decoration.ItemFrame; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; @@ -51,4 +54,10 @@ public static InteractionResult onPlayerRightClickItem(Player player, Interactio return InteractionResult.PASS; } + public static void onFrameUpdate(Entity entity) { + if (!Reference.animationEnabled) return; + if (entity instanceof ItemFrame frame) { + AnimationHandler.onFrameUpdate(frame); + } + } } diff --git a/fabric/src/main/java/com/mrbysco/armorposer/mixin/BlockAttachedEntityMixin.java b/fabric/src/main/java/com/mrbysco/armorposer/mixin/BlockAttachedEntityMixin.java new file mode 100644 index 0000000..9943da9 --- /dev/null +++ b/fabric/src/main/java/com/mrbysco/armorposer/mixin/BlockAttachedEntityMixin.java @@ -0,0 +1,19 @@ +package com.mrbysco.armorposer.mixin; + +import com.mrbysco.armorposer.handlers.EventHandler; +import net.minecraft.world.entity.decoration.BlockAttachedEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BlockAttachedEntity.class) +public class BlockAttachedEntityMixin { + + @Inject(method = "tick()V", at = @At( + value = "TAIL") + ) + public void armorposer$tick(CallbackInfo ci) { + EventHandler.onFrameUpdate(((BlockAttachedEntity) (Object) this)); + } +} diff --git a/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java b/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java index fb1e642..9e5e45c 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java @@ -2,9 +2,11 @@ import com.mrbysco.armorposer.Reference; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.data.BookCopyData; import com.mrbysco.armorposer.data.RenameData; import com.mrbysco.armorposer.data.SwapData; import com.mrbysco.armorposer.data.SyncData; +import com.mrbysco.armorposer.packets.ArmorStandCopyToBookPayload; import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; @@ -41,6 +43,12 @@ public void renameArmorStand(ArmorStand armorStand, String newName) { ClientPlayNetworking.send(new ArmorStandRenamePayload(data)); } + @Override + public void copyArmorStandPose(ArmorStand armorStand, CompoundTag compound) { + BookCopyData data = new BookCopyData(armorStand.getUUID(), compound); + ClientPlayNetworking.send(new ArmorStandCopyToBookPayload(data)); + } + @Override public boolean allowScrolling() { PoserConfig config = AutoConfig.getConfigHolder(PoserConfig.class).getConfig(); diff --git a/fabric/src/main/resources/armorposer.fabric.mixins.json b/fabric/src/main/resources/armorposer.fabric.mixins.json index b2422f7..94c5fa3 100644 --- a/fabric/src/main/resources/armorposer.fabric.mixins.json +++ b/fabric/src/main/resources/armorposer.fabric.mixins.json @@ -5,8 +5,9 @@ "refmap": "armorposer.refmap.json", "compatibilityLevel": "JAVA_21", "mixins": [ + "ArmorStandAccessor", "ArmorStandMixin", - "ArmorStandAccessor" + "BlockAttachedEntityMixin" ], "client": [ "MinecraftMixin" diff --git a/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java b/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java index b26ccf0..bc2b7fd 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java +++ b/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java @@ -1,6 +1,7 @@ package com.mrbysco.armorposer; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.packets.ArmorStandCopyToBookPayload; import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; @@ -37,5 +38,6 @@ private void setupPackets(final RegisterPayloadHandlersEvent event) { registrar.playToServer(ArmorStandSwapPayload.ID, ArmorStandSwapPayload.CODEC, ServerPayloadHandler.getInstance()::handleSwapData); registrar.playToServer(ArmorStandSyncPayload.ID, ArmorStandSyncPayload.CODEC, ServerPayloadHandler.getInstance()::handleSyncData); registrar.playToServer(ArmorStandRenamePayload.ID, ArmorStandRenamePayload.CODEC, ServerPayloadHandler.getInstance()::handleRenameData); + registrar.playToServer(ArmorStandCopyToBookPayload.ID, ArmorStandCopyToBookPayload.CODEC, ServerPayloadHandler.getInstance()::handleCopyToBook); } } \ No newline at end of file diff --git a/forge/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java b/forge/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java index 3fb5971..ac3ea07 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java +++ b/forge/src/main/java/com/mrbysco/armorposer/config/PoserConfig.java @@ -16,6 +16,7 @@ public static class Common { public final BooleanValue enableConfigGui; public final BooleanValue enableNameTags; public final BooleanValue allowScrolling; + public final BooleanValue enableAnimation; public final BooleanValue restrictResizeToOP; public final ModConfigSpec.ConfigValue> resizeWhitelist; @@ -39,6 +40,11 @@ public static class Common { .translation("armorposer.config.allowScrolling") .define("allowScrolling", true); + enableAnimation = builder + .comment("Enable Armor Poser's animation system for the Armor Stand") + .translation("armorposer.config.enableAnimation") + .define("enableAnimation", false); + restrictResizeToOP = builder .comment("Restrict the ability to resize the Armor Stand to server operators") .translation("armorposer.config.restrictResizeToOP") @@ -68,6 +74,11 @@ public static void onLoad(final ModConfigEvent.Loading configEvent) { Reference.LOGGER.debug("Loaded {}'s config file {}", Reference.MOD_ID, configEvent.getConfig().getFileName()); } + @SubscribeEvent + public static void onConfigEvent(final ModConfigEvent configEvent) { + Reference.setAnimationEnabled(PoserConfig.COMMON.enableAnimation.get()); + } + @SubscribeEvent public static void onFileChange(final ModConfigEvent.Reloading configEvent) { Reference.LOGGER.debug("{}'s config just got changed on the file system!", Reference.MOD_ID); diff --git a/forge/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java b/forge/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java index 567e596..1bf7601 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java +++ b/forge/src/main/java/com/mrbysco/armorposer/handlers/EventHandler.java @@ -1,12 +1,14 @@ package com.mrbysco.armorposer.handlers; import com.mrbysco.armorposer.Reference; +import com.mrbysco.armorposer.animation.AnimationHandler; import com.mrbysco.armorposer.config.PoserConfig; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import net.minecraft.core.component.DataComponents; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.decoration.ItemFrame; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; @@ -14,6 +16,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; +import net.neoforged.neoforge.event.tick.EntityTickEvent; @EventBusSubscriber(modid = Reference.MOD_ID, bus = EventBusSubscriber.Bus.GAME) public class EventHandler { @@ -27,8 +30,8 @@ public static void onPlayerEntityInteractSpecific(PlayerInteractEvent.EntityInte if (PoserConfig.COMMON.enableConfigGui.get() && player.isShiftKeyDown()) { if (event.getHand() == InteractionHand.MAIN_HAND && !level.isClientSide) { ((ServerPlayer) player).connection.send(new ArmorStandScreenPayload(armorstand.getId())); + event.setCanceled(true); } - event.setCanceled(true); return; } @@ -53,4 +56,12 @@ public static void onPlayerRightClickItem(PlayerInteractEvent.RightClickItem eve event.setCanceled(true); } } + + @SubscribeEvent + public static void onFrameUpdate(EntityTickEvent.Pre event) { + if (!Reference.animationEnabled) return; + if (event.getEntity() instanceof ItemFrame frame) { + AnimationHandler.onFrameUpdate(frame); + } + } } diff --git a/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java b/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java index 730c640..cfcb85d 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java +++ b/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java @@ -1,5 +1,6 @@ package com.mrbysco.armorposer.packets.handler; +import com.mrbysco.armorposer.packets.ArmorStandCopyToBookPayload; import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; @@ -66,4 +67,18 @@ public void handleRenameData(final ArmorStandRenamePayload renameData, final IPa return null; }); } + + public void handleCopyToBook(final ArmorStandCopyToBookPayload copyData, final IPayloadContext context) { + // Do something with the pose, on the main thread + context.enqueueWork(() -> { + if (context.player() != null && context.player().level() instanceof ServerLevel serverLevel) { + copyData.data().handleData(context.player()); + } + }) + .exceptionally(e -> { + // Handle exception + context.disconnect(Component.translatable("armorposer.networking.copy_to_book.failed", e.getMessage())); + return null; + }); + } } diff --git a/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java b/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java index 2863aa2..c8674ba 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java +++ b/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java @@ -2,9 +2,11 @@ import com.mrbysco.armorposer.Reference; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.data.BookCopyData; import com.mrbysco.armorposer.data.RenameData; import com.mrbysco.armorposer.data.SwapData; import com.mrbysco.armorposer.data.SyncData; +import com.mrbysco.armorposer.packets.ArmorStandCopyToBookPayload; import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; @@ -38,6 +40,11 @@ public void renameArmorStand(ArmorStand armorStand, String newName) { PacketDistributor.sendToServer(new ArmorStandRenamePayload(new RenameData(armorStand.getUUID(), newName))); } + @Override + public void copyArmorStandPose(ArmorStand armorStand, CompoundTag compound) { + PacketDistributor.sendToServer(new ArmorStandCopyToBookPayload(new BookCopyData(armorStand.getUUID(), compound))); + } + @Override public boolean allowScrolling() { return PoserConfig.COMMON.allowScrolling.get();