diff --git a/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch b/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch index f4eaa0f86c..071f7fbb2f 100644 --- a/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch @@ -600,6 +600,14 @@ if (this.f_20935_.m_41781_()) { this.m_21329_(); } +@@ -2996,6 +_,7 @@ + } + + public void m_5810_() { ++ if (this.m_6117_() && !this.f_20935_.m_41619_()) this.f_20935_.onStopUsing(this, f_20936_); + if (!this.f_19853_.f_46443_) { + boolean flag = this.m_6117_(); + this.m_21155_(1, false); @@ -3011,7 +_,7 @@ public boolean m_21254_() { if (this.m_6117_() && !this.f_20935_.m_41619_()) { diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java index 0ccb3687da..c6444e7738 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java @@ -201,6 +201,28 @@ public interface IForgeItem { } + /** + * Called when an entity stops using an item for any reason, notably when selecting another item without releasing or finishing. + * This method is called in addition to any other hooks called when an item is finished using; when another hook is also called it will be called before this method. + * + * Note that if you break an item while using it (that is, it becomes empty without swapping the stack instance), this hook may not be called on the serverside as you are + * technically still using the empty item (thus this hook is called on air instead). It is necessary to call {@link LivingEntity#stopUsingItem()} as part of your + * {@link ItemStack#hurtAndBreak(int, LivingEntity, Consumer)} callback to prevent this issue. + * + * For most uses, you likely want one of the following: + * + * + * @param stack The Item being used + * @param entity The entity using the item, typically a player + * @param count The amount of time in tick the item has been used for continuously + */ + default void onStopUsing(ItemStack stack, LivingEntity entity, int count) + { + } + /** * Called when the player Left Clicks (attacks) an entity. Processed before * damage is done, if return value is true further processing is canceled and diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java b/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java index d934c30c11..f15a119bd5 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java @@ -255,6 +255,17 @@ public interface IForgeItemStack extends ICapabilitySerializable self().getItem().onUsingTick(self(), player, count); } + /** + * Called when an entity stops using an item item for any reason. + * + * @param entity The entity using the item, typically a player + * @param count The amount of time in tick the item has been used for continuously + */ + default void onStopUsing(LivingEntity entity, int count) + { + self().getItem().onStopUsing(self(), entity, count); + } + /** * Retrieves the normal 'lifespan' of this item when it is dropped on the ground * as a EntityItem. This is in ticks, standard result is 6000, or 5 mins. diff --git a/src/test/java/net/minecraftforge/debug/item/StopUsingItemTest.java b/src/test/java/net/minecraftforge/debug/item/StopUsingItemTest.java new file mode 100644 index 0000000000..a5ec8c4638 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/item/StopUsingItemTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.item; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.UseAnim; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.ComputeFovModifierEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.CreativeModeTabEvent; +import net.minecraftforge.event.VanillaGameEvent; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +/** + * This test mod provides two items for testing the Forge onStopUsing hook. Both items attempt to create an item that increases FOV and allows creative flight when used + * + */ +@Mod(StopUsingItemTest.MODID) +public class StopUsingItemTest +{ + protected static final String MODID = "stop_using_item"; + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); + + /** Current FOV change, consumed by the event. + * Good enough for a test mod as we only need one copy for the client player, in a real mod you probably want to reset this on world exit. */ + private static float fovChange = 1.0f; + + public StopUsingItemTest() + { + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + ITEMS.register(modEventBus); + modEventBus.addListener(this::addCreative); + MinecraftForge.EVENT_BUS.addListener(this::onVanillaEvent); + } + + /** Attempt at a "reverse scope" that also makes you fly without using the Forge method. Will not remove the speed if you scroll away or swap items */ + public static RegistryObject BAD = ITEMS.register("bad_scope", () -> new InvertedTelescope(new Item.Properties()) + { + @Override + public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity living) + { + removeFov(living); + return stack; + } + + @Override + public void releaseUsing(ItemStack stack, Level level, LivingEntity living, int count) + { + removeFov(living); + } + }); + + /** Successful "scope item" using the Forge method, all cases of stopping using the item will stop the FOV change */ + public static RegistryObject GOOD = ITEMS.register("good_scope", () -> new InvertedTelescope(new Item.Properties()) + { + @Override + public void onStopUsing(ItemStack stack, LivingEntity living, int count) + { + removeFov(living); + } + }); + + private void addCreative(CreativeModeTabEvent.BuildContents event) + { + if (event.getTab() == CreativeModeTabs.COMBAT) + { + event.accept(BAD); + event.accept(GOOD); + } + } + + private void onVanillaEvent(VanillaGameEvent event) + { + if (event.getVanillaEvent() == GameEvent.ITEM_INTERACT_FINISH && event.getCause() instanceof LivingEntity living && living.isUsingItem() && living.getUseItem().is(BAD.get())) + InvertedTelescope.removeFov(living); + } + + private static abstract class InvertedTelescope extends Item + { + public InvertedTelescope(Properties props) + { + super(props); + } + + @Override + public int getUseDuration(ItemStack stack) + { + return 72000; + } + + @Override + public UseAnim getUseAnimation(ItemStack stack) + { + return UseAnim.EAT; + } + + @Override + public InteractionResultHolder use(Level level, Player player, InteractionHand hand) + { + player.startUsingItem(hand); + player.getAbilities().mayfly = true; + if (player.level.isClientSide) + fovChange = 10f; + return InteractionResultHolder.consume(player.getItemInHand(hand)); + } + + public static void removeFov(LivingEntity living) + { + if (living.level.isClientSide) + fovChange = 1f; + if (living instanceof Player player) + { + player.getAbilities().mayfly = player.isCreative(); + if (!player.isCreative()) + player.getAbilities().flying = false; + } + } + } + + @EventBusSubscriber(modid = MODID, value = Dist.CLIENT, bus = Bus.FORGE) + public static class ClientEvents + { + @SubscribeEvent + static void computeFovModifier(ComputeFovModifierEvent event) + { + event.setNewFovModifier(event.getFovModifier() * fovChange); + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 1a4a1b240d..810a867b3d 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -117,6 +117,8 @@ modId="custom_elytra_test" [[mods]] modId="snow_boots_test" [[mods]] +modId="stop_using_item" +[[mods]] modId="finite_water_test" [[mods]] modId="scaffolding_test"