diff --git a/patches/minecraft/net/minecraft/client/model/HumanoidModel.java.patch b/patches/minecraft/net/minecraft/client/model/HumanoidModel.java.patch new file mode 100644 index 0000000000..2edd054f32 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/model/HumanoidModel.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/client/model/HumanoidModel.java ++++ b/net/minecraft/client/model/HumanoidModel.java +@@ -264,6 +_,8 @@ + case TOOT_HORN: + this.f_102811_.f_104203_ = Mth.m_14036_(this.f_102808_.f_104203_, -1.2F, 1.2F) - 1.4835298F; + this.f_102811_.f_104204_ = this.f_102808_.f_104204_ - ((float)Math.PI / 6F); ++ default: ++ this.f_102816_.applyTransform(this, p_102876_, net.minecraft.world.entity.HumanoidArm.RIGHT); + } + + } +@@ -304,6 +_,8 @@ + case TOOT_HORN: + this.f_102812_.f_104203_ = Mth.m_14036_(this.f_102808_.f_104203_, -1.2F, 1.2F) - 1.4835298F; + this.f_102812_.f_104204_ = this.f_102808_.f_104204_ + ((float)Math.PI / 6F); ++ default: ++ this.f_102815_.applyTransform(this, p_102879_, net.minecraft.world.entity.HumanoidArm.LEFT); + } + + } +@@ -396,7 +_,7 @@ + } + + @OnlyIn(Dist.CLIENT) +- public static enum ArmPose { ++ public static enum ArmPose implements net.minecraftforge.common.IExtensibleEnum { + EMPTY(false), + ITEM(false), + BLOCK(false), +@@ -411,10 +_,30 @@ + + private ArmPose(boolean p_102896_) { + this.f_102890_ = p_102896_; ++ this.forgeArmPose = null; + } + + public boolean m_102897_() { + return this.f_102890_; + } ++ // FORGE START ++ @javax.annotation.Nullable ++ private final net.minecraftforge.client.IArmPoseTransformer forgeArmPose; ++ ++ private ArmPose(boolean twoHanded, @javax.annotation.Nonnull net.minecraftforge.client.IArmPoseTransformer forgeArmPose) { ++ this.f_102890_ = twoHanded; ++ com.google.common.base.Preconditions.checkNotNull(forgeArmPose, "Cannot create new ArmPose with null transformer!"); ++ this.forgeArmPose = forgeArmPose; ++ } ++ ++ public static ArmPose create(String name, boolean twoHanded, @javax.annotation.Nonnull net.minecraftforge.client.IArmPoseTransformer forgeArmPose) { ++ throw new IllegalStateException("Enum not extended"); ++ } ++ ++ public void applyTransform(HumanoidModel model, T entity, net.minecraft.world.entity.HumanoidArm arm) { ++ com.google.common.base.Preconditions.checkNotNull(this.forgeArmPose, "applyTransform() must not be called on a vanilla ArmPose!"); ++ this.forgeArmPose.applyTransform(model, entity, arm); ++ } ++ // FORGE END + } + } diff --git a/patches/minecraft/net/minecraft/client/renderer/ItemInHandRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/ItemInHandRenderer.java.patch index deb2c7f20b..93347e8f51 100644 --- a/patches/minecraft/net/minecraft/client/renderer/ItemInHandRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/ItemInHandRenderer.java.patch @@ -24,6 +24,14 @@ boolean flag1 = CrossbowItem.m_40932_(p_109377_); boolean flag2 = humanoidarm == HumanoidArm.RIGHT; int i = flag2 ? 1 : -1; +@@ -384,6 +_,7 @@ + this.m_109322_(p_109372_, p_109377_, flag2 ? ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND : ItemTransforms.TransformType.FIRST_PERSON_LEFT_HAND, !flag2, p_109379_, p_109380_, p_109381_); + } else { + boolean flag3 = humanoidarm == HumanoidArm.RIGHT; ++ if (!net.minecraftforge.client.extensions.common.IClientItemExtensions.of(p_109377_).applyForgeHandTransform(p_109379_, f_109299_.f_91074_, humanoidarm, p_109377_, p_109373_, p_109378_, p_109376_)) // FORGE: Allow items to define custom arm animation + if (p_109372_.m_6117_() && p_109372_.m_21212_() > 0 && p_109372_.m_7655_() == p_109375_) { + int k = flag3 ? 1 : -1; + switch (p_109377_.m_41780_()) { @@ -487,8 +_,16 @@ this.f_109304_ = Mth.m_14036_(this.f_109304_ - 0.4F, 0.0F, 1.0F); } else { diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch index 3d4bbd15fc..ffb7b6fd8e 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch @@ -10,7 +10,7 @@ } public Vec3 m_7860_(AbstractClientPlayer p_117785_, float p_117786_) { -@@ -126,7 +_,7 @@ +@@ -126,10 +_,13 @@ if (useanim == UseAnim.TOOT_HORN) { return HumanoidModel.ArmPose.TOOT_HORN; } @@ -19,6 +19,12 @@ return HumanoidModel.ArmPose.CROSSBOW_HOLD; } ++ HumanoidModel.ArmPose forgeArmPose = net.minecraftforge.client.extensions.common.IClientItemExtensions.of(itemstack).getArmPose(p_117795_, p_117796_, itemstack); ++ if (forgeArmPose != null) return forgeArmPose; ++ + return HumanoidModel.ArmPose.ITEM; + } + } @@ -161,10 +_,12 @@ } diff --git a/patches/minecraft/net/minecraft/world/item/UseAnim.java.patch b/patches/minecraft/net/minecraft/world/item/UseAnim.java.patch new file mode 100644 index 0000000000..de3eec1480 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/item/UseAnim.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/item/UseAnim.java ++++ b/net/minecraft/world/item/UseAnim.java +@@ -9,5 +_,10 @@ + SPEAR, + CROSSBOW, + SPYGLASS, +- TOOT_HORN; ++ TOOT_HORN, ++ /** ++ * Items with custom arm animation should return this in `Item#getUseAnimation` ++ * to prevent vanilla from also trying to animate same item ++ */ ++ CUSTOM + } diff --git a/src/main/java/net/minecraftforge/client/IArmPoseTransformer.java b/src/main/java/net/minecraftforge/client/IArmPoseTransformer.java new file mode 100644 index 0000000000..8dd32506a0 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/IArmPoseTransformer.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.client; + +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.client.extensions.common.IClientItemExtensions; + +/** + * An ArmPose that can be defined by the user. + * Register one by creating a custom {@link net.minecraft.client.model.HumanoidModel.ArmPose} + * and returning it in {@link IClientItemExtensions#getArmPose(LivingEntity, InteractionHand, ItemStack)}. + */ +@FunctionalInterface +public interface IArmPoseTransformer +{ + + /** + * This method should be used to apply all wanted transformations to the player when the ArmPose is active. + * You can use {@link LivingEntity#getTicksUsingItem()} and {@link LivingEntity#getUseItemRemainingTicks()} for moving animations. + * + * @param model The humanoid model + * @param entity The humanoid entity + * @param arm Arm to pose + */ + void applyTransform(HumanoidModel model, LivingEntity entity, HumanoidArm arm); + +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/extensions/common/IClientItemExtensions.java b/src/main/java/net/minecraftforge/client/extensions/common/IClientItemExtensions.java index 0c84c8f0cd..5e28a83e12 100644 --- a/src/main/java/net/minecraftforge/client/extensions/common/IClientItemExtensions.java +++ b/src/main/java/net/minecraftforge/client/extensions/common/IClientItemExtensions.java @@ -5,19 +5,24 @@ package net.minecraftforge.client.extensions.common; +import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.Model; +import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.HumanoidArm; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.IArmPoseTransformer; import net.minecraftforge.fml.LogicalSide; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -57,6 +62,37 @@ public interface IClientItemExtensions return null; } + /** + * This method returns an ArmPose that can be defined using the {@link net.minecraft.client.model.HumanoidModel.ArmPose#create(String, boolean, IArmPoseTransformer)} method. + * This allows for creating custom item use animations. + * + * @param entityLiving The entity holding the item + * @param hand The hand the ArmPose will be applied to + * @param itemStack The stack being held + * @return A custom ArmPose that can be used to define movement of the arm + */ + @Nullable + default HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack itemStack) + { + return null; + } + + /** + * Called right before when client applies transformations to item in hand and render it. + * + * @param poseStack The pose stack + * @param player The player holding the item, it's always main client player + * @param arm The arm holding the item + * @param itemInHand The held item + * @param partialTick Partial tick time, useful for interpolation + * @param equipProcess Equip process time, Ranging from 0.0 to 1.0. 0.0 when it's done equipping + * @param swingProcess Swing process time, Ranging from 0.0 to 1.0. 0.0 when it's done swinging + * @return true if it should skip applying other transforms and go straight to rendering + */ + default boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) { + return false; + } + /** * Queries the humanoid armor model for this item when it's equipped. * diff --git a/src/test/java/net/minecraftforge/debug/entity/player/ItemUseAnimationTest.java b/src/test/java/net/minecraftforge/debug/entity/player/ItemUseAnimationTest.java new file mode 100644 index 0000000000..d37f0f4f80 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/entity/player/ItemUseAnimationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.entity.player; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.UseAnim; +import net.minecraftforge.client.extensions.common.IClientItemExtensions; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +import java.util.function.Consumer; + +/** + * Tests if item usage animation system works as intended. `item_use_animation_test:thing` is edible item with custom usage animation made with this system. + * In game, use `/give @s item_use_animation_test:thing 1` to obtain test item + * When you try to eat it, your arm in 3d person should start swinging really fast. + * And item in your hand will go down little. + */ +@Mod(ItemUseAnimationTest.MOD_ID) +public class ItemUseAnimationTest +{ + + public static final String MOD_ID = "item_use_animation_test"; + + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID); + + private static final RegistryObject THING = ITEMS.register("thing", () -> new ThingItem(new Item.Properties().tab(CreativeModeTab.TAB_TOOLS).food(new FoodProperties.Builder().nutrition(4).alwaysEat().build()))); + + public ItemUseAnimationTest() + { + ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus()); + } + + private static class ThingItem extends Item + { + + public ThingItem(Item.Properties props) + { + super(props); + } + + @Override + public UseAnim getUseAnimation(ItemStack stack) + { + return UseAnim.CUSTOM; + } + + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IClientItemExtensions() + { + + private static final HumanoidModel.ArmPose SWING_POSE = HumanoidModel.ArmPose.create("SWING", false, (model, entity, arm) -> { + if (arm == HumanoidArm.RIGHT) + { + model.rightArm.xRot = (float) (Math.random() * Math.PI * 2); + } else + { + model.leftArm.xRot = (float) (Math.random() * Math.PI * 2); + } + }); + + @Override + public HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack itemStack) + { + if (!itemStack.isEmpty()) + { + if (entityLiving.getUsedItemHand() == hand && entityLiving.getUseItemRemainingTicks() > 0) + { + return SWING_POSE; + } + } + return HumanoidModel.ArmPose.EMPTY; + } + + @Override + public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) { + applyItemArmTransform(poseStack, arm); + if (player.isUsingItem()) { + poseStack.translate(0.0, -0.05, 0.0); + } + return true; + } + + private void applyItemArmTransform(PoseStack poseStack, HumanoidArm arm) { + int i = arm == HumanoidArm.RIGHT ? 1 : -1; + poseStack.translate(i * 0.56F, -0.52F, -0.72F); + } + }); + } + } + +} \ No newline at end of file diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 9d9ec51a00..7932bf81bf 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -245,6 +245,8 @@ modId="emissive_elements_test" [[mods]] modId="anvil_update_event_fix" [[mods]] +modId="item_use_animation_test" +[[mods]] modId="custom_color_resolver_test"