mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-11 01:41:47 +01:00
feat: simplify item usage behavior (fixes #2475)
This commit is contained in:
parent
1a63e48894
commit
8953ff467e
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
* <p>Setting the use duration to zero or cancelling the event will prevent consumption.</p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
* <p>This includes cases like half eating a food, but also includes shooting a bow.</p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
* <p>{@link #getUseDuration()} represents the total time spent using the item.</p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
21
src/main/java/net/minestom/server/item/ItemAnimation.java
Normal file
21
src/main/java/net/minestom/server/item/ItemAnimation.java
Normal file
@ -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<ItemAnimation> NETWORK_TYPE = NetworkBuffer.Enum(ItemAnimation.class);
|
||||
public static final BinaryTagSerializer<ItemAnimation> NBT_TYPE = BinaryTagSerializer.fromEnumStringable(ItemAnimation.class);
|
||||
}
|
@ -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<ConsumeEffect> 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<Animation> NETWORK_TYPE = NetworkBuffer.Enum(Animation.class);
|
||||
public static final BinaryTagSerializer<Animation> NBT_TYPE = BinaryTagSerializer.fromEnumStringable(Animation.class);
|
||||
}
|
||||
|
||||
public static final NetworkBuffer.Type<Consumable> 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<Consumable> 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,
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user