From 8953ff467e752911e9bcd52b49d02c2fa854402d Mon Sep 17 00:00:00 2001 From: mworzala Date: Tue, 3 Dec 2024 00:08:25 -0500 Subject: [PATCH] feat: simplify item usage behavior (fixes #2475) --- .../java/net/minestom/demo/PlayerInit.java | 76 +++++++----- .../minestom/server/entity/LivingEntity.java | 3 +- .../net/minestom/server/entity/Player.java | 42 +++---- .../event/item/ItemUpdateStateEvent.java | 68 ----------- .../event/item/PlayerBeginItemUseEvent.java | 85 ++++++++++++++ .../event/item/PlayerCancelItemUseEvent.java | 50 ++++++++ ...ent.java => PlayerFinishItemUseEvent.java} | 31 +++-- .../server/event/player/PlayerEatEvent.java | 52 --------- .../player/PlayerItemAnimationEvent.java | 71 ----------- .../event/player/PlayerUseItemEvent.java | 3 +- .../event/player/UpdateTagListEvent.java | 20 ---- .../minestom/server/item/ItemAnimation.java | 21 ++++ .../server/item/component/Consumable.java | 23 +--- .../listener/PlayerDiggingListener.java | 17 +-- .../server/listener/PlayerHeldListener.java | 3 +- .../server/listener/UseItemListener.java | 110 ++++++++++-------- 16 files changed, 317 insertions(+), 358 deletions(-) delete mode 100644 src/main/java/net/minestom/server/event/item/ItemUpdateStateEvent.java create mode 100644 src/main/java/net/minestom/server/event/item/PlayerBeginItemUseEvent.java create mode 100644 src/main/java/net/minestom/server/event/item/PlayerCancelItemUseEvent.java rename src/main/java/net/minestom/server/event/item/{ItemUsageCompleteEvent.java => PlayerFinishItemUseEvent.java} (50%) delete mode 100644 src/main/java/net/minestom/server/event/player/PlayerEatEvent.java delete mode 100644 src/main/java/net/minestom/server/event/player/PlayerItemAnimationEvent.java delete mode 100644 src/main/java/net/minestom/server/event/player/UpdateTagListEvent.java create mode 100644 src/main/java/net/minestom/server/item/ItemAnimation.java diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index afaa4aada..04dd8f89e 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -15,8 +15,7 @@ import net.minestom.server.entity.damage.Damage; import net.minestom.server.event.Event; import net.minestom.server.event.EventNode; import net.minestom.server.event.entity.EntityAttackEvent; -import net.minestom.server.event.item.ItemDropEvent; -import net.minestom.server.event.item.PickupItemEvent; +import net.minestom.server.event.item.*; import net.minestom.server.event.player.*; import net.minestom.server.event.server.ServerTickMonitorEvent; import net.minestom.server.instance.Instance; @@ -28,25 +27,23 @@ import net.minestom.server.instance.block.predicate.BlockPredicate; import net.minestom.server.instance.block.predicate.BlockTypeFilter; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.item.ItemAnimation; import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.item.component.BlockPredicates; -import net.minestom.server.item.component.EnchantmentList; -import net.minestom.server.item.component.LodestoneTracker; -import net.minestom.server.item.component.PotionContents; -import net.minestom.server.item.enchant.Enchantment; +import net.minestom.server.item.component.Consumable; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.TickMonitor; import net.minestom.server.network.packet.server.common.CustomReportDetailsPacket; import net.minestom.server.network.packet.server.common.ServerLinksPacket; -import net.minestom.server.potion.CustomPotionEffect; -import net.minestom.server.potion.PotionEffect; import net.minestom.server.sound.SoundEvent; import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.time.TimeUnit; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; @@ -138,29 +135,11 @@ public class PlayerInit { .build(); player.getInventory().addItemStack(bundle); - player.getInventory().addItemStack(ItemStack.builder(Material.COMPASS) - .set(ItemComponent.LODESTONE_TRACKER, new LodestoneTracker(player.getInstance().getDimensionName(), new Vec(10, 10, 10), true)) - .build()); - - player.getInventory().addItemStack(ItemStack.builder(Material.STONE_SWORD) - .set(ItemComponent.ENCHANTMENTS, new EnchantmentList(Map.of( - Enchantment.SHARPNESS, 10 - ))) - .build()); -// - player.getInventory().addItemStack(ItemStack.builder(Material.STONE_SWORD) - .build()); - - player.getInventory().addItemStack(ItemStack.builder(Material.BLACK_BANNER) - .build()); - - player.getInventory().addItemStack(ItemStack.builder(Material.POTION) - .set(ItemComponent.POTION_CONTENTS, new PotionContents(null, null, List.of( - new CustomPotionEffect(PotionEffect.JUMP_BOOST, new CustomPotionEffect.Settings((byte) 4, - 45 * 20, false, true, true, null)) - ))) - .customName(Component.text("Sharpness 10 Sword").append(Component.space()).append(Component.text("§c§l[LEGENDARY]"))) - .build()); + player.setGameMode(GameMode.SURVIVAL); + PlayerInventory inventory = event.getPlayer().getInventory(); + inventory.addItemStack(getFoodItem(20)); + inventory.addItemStack(getFoodItem(10000)); + inventory.addItemStack(getFoodItem(Integer.MAX_VALUE)); if (event.isFirstSpawn()) { @@ -195,6 +174,29 @@ public class PlayerInit { event.getInstance().setBlock(event.getPosition(), block); }) + .addListener(PlayerBeginItemUseEvent.class, event -> { + final ItemStack itemStack = event.getItemStack(); + final boolean hasProjectile = !itemStack.get(ItemComponent.CHARGED_PROJECTILES, List.of()).isEmpty(); + if (itemStack.material() == Material.CROSSBOW && hasProjectile) { + // "shoot" the arrow + event.setItemStack(itemStack.without(ItemComponent.CHARGED_PROJECTILES)); + event.getPlayer().sendMessage("pew pew!"); + event.setItemUseDuration(0); // Do not start using the item + return; + } + }) + .addListener(PlayerFinishItemUseEvent.class, event -> { + if (event.getItemStack().material() == Material.APPLE) { + event.getPlayer().sendMessage("yummy yummy apple"); + } + }) + .addListener(PlayerCancelItemUseEvent.class, event -> { + final ItemStack itemStack = event.getItemStack(); + if (itemStack.material() == Material.CROSSBOW && event.getUseDuration() > 25) { + event.setItemStack(itemStack.with(ItemComponent.CHARGED_PROJECTILES, List.of(ItemStack.of(Material.ARROW)))); + return; + } + }) .addListener(PlayerBlockInteractEvent.class, event -> { var block = event.getBlock(); var rawOpenProp = block.getProperty("open"); @@ -256,4 +258,16 @@ public class PlayerInit { Audiences.players().sendPlayerListHeaderAndFooter(header, footer); }).repeat(10, TimeUnit.SERVER_TICK).schedule(); } + + public static ItemStack getFoodItem(int consumeTicks) { + return ItemStack.builder(Material.IRON_NUGGET) + .amount(64) + .set(ItemComponent.CONSUMABLE, new Consumable( + (float) consumeTicks / 20, + ItemAnimation.EAT, + SoundEvent.BLOCK_CHAIN_STEP, + true, + new ArrayList<>())) + .build(); + } } diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 57cdac9ab..44ca3f04c 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -600,9 +600,10 @@ public class LivingEntity extends Entity implements EquipmentHandler { meta.setHandActive(isHandActive); meta.setActiveHand(offHand ? PlayerHand.OFF : PlayerHand.MAIN); meta.setInRiptideSpinAttack(riptideSpinAttack); - meta.setNotifyAboutChanges(true); updatePose(); // Riptide spin attack has a pose + + meta.setNotifyAboutChanges(true); } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 7964ff451..2a285cb8b 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -38,9 +38,8 @@ import net.minestom.server.entity.vehicle.PlayerInputs; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.inventory.InventoryOpenEvent; import net.minestom.server.event.item.ItemDropEvent; -import net.minestom.server.event.item.ItemUpdateStateEvent; -import net.minestom.server.event.item.ItemUsageCompleteEvent; import net.minestom.server.event.item.PickupExperienceEvent; +import net.minestom.server.event.item.PlayerFinishItemUseEvent; import net.minestom.server.event.player.*; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.EntityTracker; @@ -405,24 +404,22 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou // Eating animation if (isUsingItem()) { if (itemUseTime > 0 && getCurrentItemUseTime() >= itemUseTime) { + PlayerFinishItemUseEvent finishUseEvent = new PlayerFinishItemUseEvent(this, itemUseHand, getItemInHand(itemUseHand), itemUseTime); + EventDispatcher.call(finishUseEvent); + + // Reset client state triggerStatus((byte) EntityStatuses.Player.MARK_ITEM_FINISHED); - ItemUpdateStateEvent itemUpdateStateEvent = callItemUpdateStateEvent(itemUseHand); - // Refresh hand - final boolean isOffHand = itemUpdateStateEvent.getHand() == PlayerHand.OFF; + // Reset server state + final boolean isOffHand = itemUseHand == PlayerHand.OFF; refreshActiveHand(false, isOffHand, false); - - final ItemStack item = itemUpdateStateEvent.getItemStack(); - final boolean isFood = item.has(ItemComponent.FOOD); - if (isFood || item.material() == Material.POTION) { - PlayerEatEvent playerEatEvent = new PlayerEatEvent(this, item, itemUseHand); - EventDispatcher.call(playerEatEvent); - } - - var itemUsageCompleteEvent = new ItemUsageCompleteEvent(this, itemUseHand, item); - EventDispatcher.call(itemUsageCompleteEvent); - clearItemUse(); + + // Update item from event, sending a slot refresh no matter what + final int slot = isOffHand ? PlayerInventoryUtils.OFFHAND_SLOT : getHeldSlot(); + final ItemStack itemStack = finishUseEvent.getItemStack(); + inventory.setItemStack(slot, itemStack, false); + inventory.sendSlotRefresh(slot, itemStack, itemStack); } } @@ -2157,19 +2154,6 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou refreshItemUse(null, 0); } - /** - * Used to call {@link ItemUpdateStateEvent} with the proper item - * It does check which hand to get the item to update. - * - * @return the called {@link ItemUpdateStateEvent}, - */ - public @NotNull ItemUpdateStateEvent callItemUpdateStateEvent(@NotNull PlayerHand hand) { - ItemUpdateStateEvent itemUpdateStateEvent = new ItemUpdateStateEvent(this, hand, getItemInHand(hand)); - EventDispatcher.call(itemUpdateStateEvent); - - return itemUpdateStateEvent; - } - public void refreshInput(boolean forward, boolean backward, boolean left, boolean right, boolean jump, boolean shift, boolean sprint) { this.inputs.refresh(forward, backward, left, right, jump, shift, sprint); } diff --git a/src/main/java/net/minestom/server/event/item/ItemUpdateStateEvent.java b/src/main/java/net/minestom/server/event/item/ItemUpdateStateEvent.java deleted file mode 100644 index 0a974b3a3..000000000 --- a/src/main/java/net/minestom/server/event/item/ItemUpdateStateEvent.java +++ /dev/null @@ -1,68 +0,0 @@ -package net.minestom.server.event.item; - -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.event.trait.ItemEvent; -import net.minestom.server.event.trait.PlayerInstanceEvent; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -/** - * Event when a player updates an item state, meaning when they stop using the item. - */ -public class ItemUpdateStateEvent implements PlayerInstanceEvent, ItemEvent { - - private final Player player; - private final PlayerHand hand; - private final ItemStack itemStack; - - private boolean handAnimation; - private boolean riptideSpinAttack; - - public ItemUpdateStateEvent(@NotNull Player player, @NotNull PlayerHand hand, @NotNull ItemStack itemStack) { - this.player = player; - this.hand = hand; - this.itemStack = itemStack; - } - - @NotNull - public PlayerHand getHand() { - return hand; - } - - /** - * Sets whether the player should have a hand animation. - * - * @param handAnimation whether the player should have a hand animation - */ - public void setHandAnimation(boolean handAnimation) { - this.handAnimation = handAnimation; - } - - public boolean hasHandAnimation() { - return handAnimation; - } - - /** - * Sets whether the player should have a riptide spin attack animation. - * - * @param riptideSpinAttack whether the player should have a riptide spin attack animation - */ - public void setRiptideSpinAttack(boolean riptideSpinAttack) { - this.riptideSpinAttack = riptideSpinAttack; - } - - public boolean isRiptideSpinAttack() { - return riptideSpinAttack; - } - - @Override - public @NotNull ItemStack getItemStack() { - return itemStack; - } - - @Override - public @NotNull Player getPlayer() { - return player; - } -} diff --git a/src/main/java/net/minestom/server/event/item/PlayerBeginItemUseEvent.java b/src/main/java/net/minestom/server/event/item/PlayerBeginItemUseEvent.java new file mode 100644 index 000000000..fdcf76dec --- /dev/null +++ b/src/main/java/net/minestom/server/event/item/PlayerBeginItemUseEvent.java @@ -0,0 +1,85 @@ +package net.minestom.server.event.item; + +import net.minestom.server.entity.Player; +import net.minestom.server.entity.PlayerHand; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.ItemEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.item.ItemAnimation; +import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player begins using an item with the item, animation, and duration. + * + *

Setting the use duration to zero or cancelling the event will prevent consumption.

+ */ +public class PlayerBeginItemUseEvent implements PlayerInstanceEvent, ItemEvent, CancellableEvent { + private final Player player; + private final PlayerHand hand; + private ItemStack itemStack; + private ItemAnimation animation; + private long itemUseDuration; + + private boolean cancelled = false; + + public PlayerBeginItemUseEvent(@NotNull Player player, @NotNull PlayerHand hand, + @NotNull ItemStack itemStack, @NotNull ItemAnimation animation, + long itemUseDuration) { + this.player = player; + this.hand = hand; + this.itemStack = itemStack; + this.animation = animation; + this.itemUseDuration = itemUseDuration; + } + + @Override + public @NotNull Player getPlayer() { + return player; + } + + public @NotNull PlayerHand getHand() { + return hand; + } + + @Override + public @NotNull ItemStack getItemStack() { + return itemStack; + } + + public void setItemStack(@NotNull ItemStack itemStack) { + this.itemStack = itemStack; + } + + public @NotNull ItemAnimation getAnimation() { + return animation; + } + + /** + * Returns the item use duration, in ticks. A duration of zero will prevent consumption (same effect as cancellation). + * + * @return the current item use duration + */ + public long getItemUseDuration() { + return itemUseDuration; + } + + /** + * Sets the item use duration, in ticks. + */ + public void setItemUseDuration(long itemUseDuration) { + Check.argCondition(itemUseDuration < 0, "Item use duration cannot be negative"); + this.itemUseDuration = itemUseDuration; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/src/main/java/net/minestom/server/event/item/PlayerCancelItemUseEvent.java b/src/main/java/net/minestom/server/event/item/PlayerCancelItemUseEvent.java new file mode 100644 index 000000000..bc9dc6285 --- /dev/null +++ b/src/main/java/net/minestom/server/event/item/PlayerCancelItemUseEvent.java @@ -0,0 +1,50 @@ +package net.minestom.server.event.item; + +import net.minestom.server.entity.Player; +import net.minestom.server.entity.PlayerHand; +import net.minestom.server.event.trait.ItemEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player stops using an item before the item has completed its usage, including the amount of + * time the item was used before cancellation. + * + *

This includes cases like half eating a food, but also includes shooting a bow.

+ */ +public class PlayerCancelItemUseEvent implements PlayerInstanceEvent, ItemEvent { + private final Player player; + private final PlayerHand hand; + private ItemStack itemStack; + private final long useDuration; + + public PlayerCancelItemUseEvent(@NotNull Player player, @NotNull PlayerHand hand, @NotNull ItemStack itemStack, long useDuration) { + this.player = player; + this.hand = hand; + this.itemStack = itemStack; + this.useDuration = useDuration; + } + + @Override + public @NotNull Player getPlayer() { + return player; + } + + public @NotNull PlayerHand getHand() { + return hand; + } + + @Override + public @NotNull ItemStack getItemStack() { + return itemStack; + } + + public void setItemStack(@NotNull ItemStack itemStack) { + this.itemStack = itemStack; + } + + public long getUseDuration() { + return useDuration; + } +} diff --git a/src/main/java/net/minestom/server/event/item/ItemUsageCompleteEvent.java b/src/main/java/net/minestom/server/event/item/PlayerFinishItemUseEvent.java similarity index 50% rename from src/main/java/net/minestom/server/event/item/ItemUsageCompleteEvent.java rename to src/main/java/net/minestom/server/event/item/PlayerFinishItemUseEvent.java index e24f9974e..d7a99eb74 100644 --- a/src/main/java/net/minestom/server/event/item/ItemUsageCompleteEvent.java +++ b/src/main/java/net/minestom/server/event/item/PlayerFinishItemUseEvent.java @@ -8,23 +8,29 @@ import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; /** - * Event when the item usage duration has passed for a player, meaning when the item has completed its usage. + * Called when a player completely finishes using an item. + * + *

{@link #getUseDuration()} represents the total time spent using the item.

*/ -public class ItemUsageCompleteEvent implements PlayerInstanceEvent, ItemEvent { - +public class PlayerFinishItemUseEvent implements PlayerInstanceEvent, ItemEvent { private final Player player; private final PlayerHand hand; - private final ItemStack itemStack; + private ItemStack itemStack; + private final long useDuration; - public ItemUsageCompleteEvent(@NotNull Player player, @NotNull PlayerHand hand, - @NotNull ItemStack itemStack) { + public PlayerFinishItemUseEvent(@NotNull Player player, @NotNull PlayerHand hand, @NotNull ItemStack itemStack, long useDuration) { this.player = player; this.hand = hand; this.itemStack = itemStack; + this.useDuration = useDuration; } - @NotNull - public PlayerHand getHand() { + @Override + public @NotNull Player getPlayer() { + return player; + } + + public @NotNull PlayerHand getHand() { return hand; } @@ -33,8 +39,11 @@ public class ItemUsageCompleteEvent implements PlayerInstanceEvent, ItemEvent { return itemStack; } - @Override - public @NotNull Player getPlayer() { - return player; + public void setItemStack(@NotNull ItemStack itemStack) { + this.itemStack = itemStack; + } + + public long getUseDuration() { + return useDuration; } } diff --git a/src/main/java/net/minestom/server/event/player/PlayerEatEvent.java b/src/main/java/net/minestom/server/event/player/PlayerEatEvent.java deleted file mode 100644 index 1d2c0ba8f..000000000 --- a/src/main/java/net/minestom/server/event/player/PlayerEatEvent.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.minestom.server.event.player; - -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.event.trait.ItemEvent; -import net.minestom.server.event.trait.PlayerInstanceEvent; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -/** - * Called when a player is finished eating. - */ -public class PlayerEatEvent implements ItemEvent, PlayerInstanceEvent { - - private final Player player; - private final ItemStack foodItem; - private final PlayerHand hand; - - public PlayerEatEvent(@NotNull Player player, @NotNull ItemStack foodItem, @NotNull PlayerHand hand) { - this.player = player; - this.foodItem = foodItem; - this.hand = hand; - } - - /** - * Gets the food item that has been eaten. - * - * @return the food item - * @deprecated use getItemStack() for the eaten item - */ - @Deprecated - public @NotNull ItemStack getFoodItem() { - return foodItem; - } - - public @NotNull PlayerHand getHand() { - return hand; - } - - @Override - public @NotNull Player getPlayer() { - return player; - } - - /** - * Gets the food item that has been eaten. - * - * @return the food item - */ - @Override - public @NotNull ItemStack getItemStack() { return foodItem; } -} diff --git a/src/main/java/net/minestom/server/event/player/PlayerItemAnimationEvent.java b/src/main/java/net/minestom/server/event/player/PlayerItemAnimationEvent.java deleted file mode 100644 index 3841b8ce0..000000000 --- a/src/main/java/net/minestom/server/event/player/PlayerItemAnimationEvent.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.minestom.server.event.player; - -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.event.trait.CancellableEvent; -import net.minestom.server.event.trait.PlayerInstanceEvent; -import org.jetbrains.annotations.NotNull; - -/** - * Used when a {@link Player} starts the animation of an item. - * - * @see ItemAnimationType - */ -public class PlayerItemAnimationEvent implements PlayerInstanceEvent, CancellableEvent { - - private final Player player; - private final ItemAnimationType itemAnimationType; - private final PlayerHand hand; - private boolean cancelled; - - public PlayerItemAnimationEvent(@NotNull Player player, @NotNull ItemAnimationType itemAnimationType, @NotNull PlayerHand hand) { - this.player = player; - this.itemAnimationType = itemAnimationType; - this.hand = hand; - } - - /** - * Gets the animation. - * - * @return the animation - */ - public @NotNull ItemAnimationType getItemAnimationType() { - return itemAnimationType; - } - - /** - * Gets the hand that was used. - * - * @return the hand - */ - public @NotNull PlayerHand getHand() { - return hand; - } - - @Override - public @NotNull Player getPlayer() { - return player; - } - - public enum ItemAnimationType { - BOW, - CROSSBOW, - TRIDENT, - SHIELD, - SPYGLASS, - HORN, - BRUSH, - EAT, - OTHER - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } -} diff --git a/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java b/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java index eadd9f03c..0ee6713ef 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java @@ -2,7 +2,6 @@ package net.minestom.server.event.player; import net.minestom.server.entity.Player; import net.minestom.server.entity.PlayerHand; -import net.minestom.server.event.item.ItemUpdateStateEvent; import net.minestom.server.event.trait.CancellableEvent; import net.minestom.server.event.trait.ItemEvent; import net.minestom.server.event.trait.PlayerInstanceEvent; @@ -49,7 +48,7 @@ public class PlayerUseItemEvent implements PlayerInstanceEvent, ItemEvent, Cance /** * Gets the item usage duration. After this amount of milliseconds, - * the animation will stop automatically and {@link ItemUpdateStateEvent} is called. + * the animation will stop automatically and {@link net.minestom.server.event.item.PlayerFinishItemUseEvent} is called. * * @return the item use time */ diff --git a/src/main/java/net/minestom/server/event/player/UpdateTagListEvent.java b/src/main/java/net/minestom/server/event/player/UpdateTagListEvent.java deleted file mode 100644 index 4d7a2afec..000000000 --- a/src/main/java/net/minestom/server/event/player/UpdateTagListEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.minestom.server.event.player; - -import net.minestom.server.event.Event; -import net.minestom.server.network.packet.server.common.TagsPacket; -import org.jetbrains.annotations.NotNull; - -@Deprecated -public class UpdateTagListEvent implements Event { - - private TagsPacket packet; - - public UpdateTagListEvent(@NotNull TagsPacket packet) { - this.packet = packet; - } - - @NotNull - public TagsPacket getTags() { - return packet; - } -} diff --git a/src/main/java/net/minestom/server/item/ItemAnimation.java b/src/main/java/net/minestom/server/item/ItemAnimation.java new file mode 100644 index 000000000..4a3ef2b66 --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemAnimation.java @@ -0,0 +1,21 @@ +package net.minestom.server.item; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; + +public enum ItemAnimation { + NONE, + EAT, + DRINK, + BLOCK, + BOW, + SPEAR, + CROSSBOW, + SPYGLASS, + TOOT_HORN, + BRUSH, + BUNDLE; + + public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.Enum(ItemAnimation.class); + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.fromEnumStringable(ItemAnimation.class); +} diff --git a/src/main/java/net/minestom/server/item/component/Consumable.java b/src/main/java/net/minestom/server/item/component/Consumable.java index 404c97b90..00c812013 100644 --- a/src/main/java/net/minestom/server/item/component/Consumable.java +++ b/src/main/java/net/minestom/server/item/component/Consumable.java @@ -1,6 +1,7 @@ package net.minestom.server.item.component; import net.minestom.server.ServerFlag; +import net.minestom.server.item.ItemAnimation; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.NetworkBufferTemplate; import net.minestom.server.sound.SoundEvent; @@ -12,39 +13,23 @@ import java.util.List; public record Consumable( float consumeSeconds, - @NotNull Animation animation, + @NotNull ItemAnimation animation, @NotNull SoundEvent sound, boolean hasConsumeParticles, @NotNull List effects ) { public static final float DEFAULT_CONSUME_SECONDS = 1.6f; - public enum Animation { - NONE, - EAT, - DRINK, - BLOCK, - BOW, - SPEAR, - CROSSBOW, - SPYGLASS, - TOOT_HORN, - BRUSH; - - public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.Enum(Animation.class); - public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.fromEnumStringable(Animation.class); - } - public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBufferTemplate.template( NetworkBuffer.FLOAT, Consumable::consumeSeconds, - Animation.NETWORK_TYPE, Consumable::animation, + ItemAnimation.NETWORK_TYPE, Consumable::animation, SoundEvent.NETWORK_TYPE, Consumable::sound, NetworkBuffer.BOOLEAN, Consumable::hasConsumeParticles, ConsumeEffect.NETWORK_TYPE.list(Short.MAX_VALUE), Consumable::effects, Consumable::new); public static final BinaryTagSerializer NBT_TYPE = BinaryTagTemplate.object( "consume_seconds", BinaryTagSerializer.FLOAT.optional(DEFAULT_CONSUME_SECONDS), Consumable::consumeSeconds, - "animation", Animation.NBT_TYPE.optional(Animation.EAT), Consumable::animation, + "animation", ItemAnimation.NBT_TYPE.optional(ItemAnimation.EAT), Consumable::animation, "sound", SoundEvent.NBT_TYPE.optional(SoundEvent.ENTITY_GENERIC_EAT), Consumable::sound, "has_consume_particles", BinaryTagSerializer.BOOLEAN.optional(true), Consumable::hasConsumeParticles, "on_consume_effects", ConsumeEffect.NBT_TYPE.list().optional(List.of()), Consumable::effects, diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index a818aa75a..9b9830e24 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -10,7 +10,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.entity.PlayerHand; import net.minestom.server.entity.metadata.LivingEntityMeta; import net.minestom.server.event.EventDispatcher; -import net.minestom.server.event.item.ItemUpdateStateEvent; +import net.minestom.server.event.item.PlayerCancelItemUseEvent; import net.minestom.server.event.player.PlayerCancelDiggingEvent; import net.minestom.server.event.player.PlayerFinishDiggingEvent; import net.minestom.server.event.player.PlayerStartDiggingEvent; @@ -155,16 +155,19 @@ public final class PlayerDiggingListener { private static void updateItemState(Player player) { LivingEntityMeta meta = player.getLivingEntityMeta(); if (meta == null || !meta.isHandActive()) return; - PlayerHand hand = meta.getActiveHand(); + final PlayerHand hand = meta.getActiveHand(); - ItemUpdateStateEvent itemUpdateStateEvent = player.callItemUpdateStateEvent(hand); + PlayerCancelItemUseEvent cancelUseEvent = new PlayerCancelItemUseEvent(player, hand, player.getItemInHand(hand), player.getCurrentItemUseTime()); + EventDispatcher.call(cancelUseEvent); + player.setItemInHand(hand, cancelUseEvent.getItemStack()); - player.clearItemUse(); + // Reset client state player.triggerStatus((byte) EntityStatuses.Player.MARK_ITEM_FINISHED); - final boolean isOffHand = itemUpdateStateEvent.getHand() == PlayerHand.OFF; - player.refreshActiveHand(itemUpdateStateEvent.hasHandAnimation(), - isOffHand, itemUpdateStateEvent.isRiptideSpinAttack()); + // Reset server state + final boolean isOffHand = hand == PlayerHand.OFF; + player.refreshActiveHand(false, isOffHand, false); + player.clearItemUse(); } private static void swapItemHand(Player player) { diff --git a/src/main/java/net/minestom/server/listener/PlayerHeldListener.java b/src/main/java/net/minestom/server/listener/PlayerHeldListener.java index 9f48c57ba..51990f2e4 100644 --- a/src/main/java/net/minestom/server/listener/PlayerHeldListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerHeldListener.java @@ -1,6 +1,7 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; +import net.minestom.server.entity.PlayerHand; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.player.PlayerChangeHeldSlotEvent; import net.minestom.server.network.packet.client.play.ClientHeldItemChangePacket; @@ -37,7 +38,7 @@ public class PlayerHeldListener { } // Player is not using offhand, reset item use - if (player.getItemUseHand() != Player.Hand.OFF) { + if (player.getItemUseHand() != PlayerHand.OFF) { player.refreshActiveHand(false, false, false); player.clearItemUse(); } diff --git a/src/main/java/net/minestom/server/listener/UseItemListener.java b/src/main/java/net/minestom/server/listener/UseItemListener.java index 66df8f4fc..fce3fcd26 100644 --- a/src/main/java/net/minestom/server/listener/UseItemListener.java +++ b/src/main/java/net/minestom/server/listener/UseItemListener.java @@ -3,10 +3,10 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; import net.minestom.server.entity.PlayerHand; import net.minestom.server.event.EventDispatcher; -import net.minestom.server.event.player.PlayerItemAnimationEvent; -import net.minestom.server.event.player.PlayerPreEatEvent; +import net.minestom.server.event.item.PlayerBeginItemUseEvent; import net.minestom.server.event.player.PlayerUseItemEvent; import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.item.ItemAnimation; import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; @@ -22,9 +22,48 @@ public class UseItemListener { final PlayerHand hand = packet.hand(); final ItemStack itemStack = player.getItemInHand(hand); final Material material = itemStack.material(); + final Consumable consumable = itemStack.get(ItemComponent.CONSUMABLE); - boolean usingMainHand = player.getItemUseHand() == Player.Hand.MAIN && hand == Player.Hand.OFF; - PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(player, hand, itemStack, usingMainHand ? 0 : defaultUseItemTime(itemStack)); + // The following item animations and use item times come from vanilla. + // These items do not yet use components, but hopefully they will in the future + // and this behavior can be removed. + long useItemTime = 0; + ItemAnimation useAnimation = ItemAnimation.NONE; + if (material == Material.BOW) { + useItemTime = 72000; + useAnimation = ItemAnimation.BOW; + } else if (material == Material.CROSSBOW) { + // The crossbow has a min charge time dependent on quick charge, but to the + // client they can hold it forever + useItemTime = 7200; + useAnimation = ItemAnimation.CROSSBOW; + } else if (material == Material.SHIELD) { + useItemTime = 72000; + useAnimation = ItemAnimation.BLOCK; + } else if (material == Material.TRIDENT) { + useItemTime = 72000; + useAnimation = ItemAnimation.SPEAR; + } else if (material == Material.SPYGLASS) { + useItemTime = 1200; + useAnimation = ItemAnimation.SPYGLASS; + } else if (material == Material.GOAT_HORN) { + useItemTime = getInstrumentTime(itemStack); + useAnimation = ItemAnimation.TOOT_HORN; + } else if (material == Material.BRUSH) { + useItemTime = 200; + useAnimation = ItemAnimation.BRUSH; + } else if (material.name().contains("bundle")) { + // Why is a bundle usable??? + useItemTime = 200; + useAnimation = ItemAnimation.BUNDLE; + } else if (consumable != null) { + useItemTime = consumable.consumeTicks(); + useAnimation = consumable.animation(); + } + + boolean usingMainHand = player.getItemUseHand() == PlayerHand.MAIN && hand == PlayerHand.OFF; + PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(player, hand, itemStack, + usingMainHand ? 0 : useItemTime); EventDispatcher.call(useItemEvent); player.sendPacket(new AcknowledgeBlockChangePacket(packet.sequence())); @@ -34,55 +73,34 @@ public class UseItemListener { return; } - // Equip armor with right click + useItemTime = useItemEvent.getItemUseTime(); + if (useItemTime != 0) { + final PlayerBeginItemUseEvent beginUseEvent = new PlayerBeginItemUseEvent(player, hand, itemStack, useAnimation, useItemTime); + EventDispatcher.callCancellable(beginUseEvent, () -> { + player.setItemInHand(hand, beginUseEvent.getItemStack()); + if (beginUseEvent.getItemUseDuration() <= 0) return; + + player.refreshItemUse(hand, beginUseEvent.getItemUseDuration()); + player.refreshActiveHand(true, hand == PlayerHand.OFF, false); + }); + + return; // Do not also swap after use + } + + // If the item was not usable, we can try to do an equipment swap with it. final Equippable equippable = itemStack.get(ItemComponent.EQUIPPABLE); if (equippable != null && equippable.swappable()) { final ItemStack currentlyEquipped = player.getEquipment(equippable.slot()); player.setEquipment(equippable.slot(), itemStack); player.setItemInHand(hand, currentlyEquipped); } - - long itemUseTime = useItemEvent.getItemUseTime(); - PlayerItemAnimationEvent.ItemAnimationType itemAnimationType; - - if (material == Material.BOW) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.BOW; - } else if (material == Material.CROSSBOW) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.CROSSBOW; - } else if (material == Material.SHIELD) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.SHIELD; - } else if (material == Material.TRIDENT) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.TRIDENT; - } else if (material == Material.SPYGLASS) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.SPYGLASS; - } else if (material == Material.GOAT_HORN) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.HORN; - } else if (material == Material.BRUSH) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.BRUSH; - } else if (itemStack.has(ItemComponent.FOOD) || itemStack.material() == Material.POTION) { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.EAT; - - PlayerPreEatEvent playerPreEatEvent = new PlayerPreEatEvent(player, itemStack, hand, itemUseTime); - EventDispatcher.call(playerPreEatEvent); - if (playerPreEatEvent.isCancelled()) return; - itemUseTime = playerPreEatEvent.getEatingTime(); - } else { - itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.OTHER; - } - - if (itemUseTime != 0) { - player.refreshItemUse(hand, itemUseTime); - - PlayerItemAnimationEvent playerItemAnimationEvent = new PlayerItemAnimationEvent(player, itemAnimationType, hand); - EventDispatcher.callCancellable(playerItemAnimationEvent, () -> { - player.refreshActiveHand(true, hand == PlayerHand.OFF, false); - player.sendPacketToViewers(player.getMetadataPacket()); - }); - } } - private static int defaultUseItemTime(@NotNull ItemStack itemStack) { - final Consumable consumable = itemStack.get(ItemComponent.CONSUMABLE); - return consumable != null ? consumable.consumeTicks() : 0; + private static int getInstrumentTime(@NotNull ItemStack itemStack) { + final String instrumentName = itemStack.get(ItemComponent.INSTRUMENT); + if (instrumentName == null) return 0; + + // TODO(1.21.2): Load instrument registry + return 0; } }