diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java index 2e7c3b2b7..e0f4be51d 100644 --- a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java +++ b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java @@ -68,8 +68,9 @@ public class Demo { private static void createFrame(Instance instance, int id, int x, int y, int z) { EntityItemFrame itemFrame = new EntityItemFrame(new Position(x, y, z), EntityItemFrame.ItemFrameOrientation.NORTH); itemFrame.getPosition().setYaw(180f); - ItemStack map = new ItemStack(Material.FILLED_MAP, (byte) 1); - map.setItemMeta(new MapMeta(id)); + ItemStack map = ItemStack.builder(Material.FILLED_MAP) + .meta(new MapMeta.Builder().mapId(id).build()) + .build(); itemFrame.setItemStack(map); itemFrame.setInstance(instance); itemFrame.setCustomNameVisible(true); diff --git a/src/main/java/net/minestom/server/advancements/Advancement.java b/src/main/java/net/minestom/server/advancements/Advancement.java index d16c1a449..6b9310e70 100644 --- a/src/main/java/net/minestom/server/advancements/Advancement.java +++ b/src/main/java/net/minestom/server/advancements/Advancement.java @@ -60,7 +60,7 @@ public class Advancement { public Advancement(@NotNull JsonMessage title, @NotNull JsonMessage description, @NotNull Material icon, @NotNull FrameType frameType, float x, float y) { - this(title, description, new ItemStack(icon, (byte) 1), frameType, x, y); + this(title, description, ItemStack.of(icon), frameType, x, y); } public Advancement(@NotNull Component title, Component description, @@ -77,7 +77,7 @@ public class Advancement { public Advancement(@NotNull Component title, @NotNull Component description, @NotNull Material icon, @NotNull FrameType frameType, float x, float y) { - this(title, description, new ItemStack(icon, (byte) 1), frameType, x, y); + this(title, description, ItemStack.of(icon), frameType, x, y); } /** diff --git a/src/main/java/net/minestom/server/advancements/notifications/Notification.java b/src/main/java/net/minestom/server/advancements/notifications/Notification.java index 456028ad0..201499a39 100644 --- a/src/main/java/net/minestom/server/advancements/notifications/Notification.java +++ b/src/main/java/net/minestom/server/advancements/notifications/Notification.java @@ -33,7 +33,7 @@ public class Notification { } public Notification(@NotNull Component title, @NotNull FrameType frameType, @NotNull Material icon) { - this(title, frameType, new ItemStack(icon, (byte) 1)); + this(title, frameType, ItemStack.of(icon)); } public Notification(@NotNull Component title, @NotNull FrameType frameType, @NotNull ItemStack icon) { @@ -46,7 +46,6 @@ public class Notification { * Gets the title of the notification. * * @return the notification title - * * @deprecated Use {@link #getTitle()} */ @NotNull diff --git a/src/main/java/net/minestom/server/chat/ChatHoverEvent.java b/src/main/java/net/minestom/server/chat/ChatHoverEvent.java index 4d499e391..a288b5814 100644 --- a/src/main/java/net/minestom/server/chat/ChatHoverEvent.java +++ b/src/main/java/net/minestom/server/chat/ChatHoverEvent.java @@ -10,12 +10,12 @@ import net.minestom.server.entity.EntityType; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.UUID; /** * Represents a hover event for a specific portion of the message. + * * @deprecated Use {@link HoverEvent} */ @Deprecated @@ -93,11 +93,8 @@ public class ChatHoverEvent { JsonObject obj = GsonComponentSerializer.gson().serializer().toJsonTree(Component.empty().hoverEvent(event)).getAsJsonObject(); obj = obj.get("hoverEvent").getAsJsonObject().get("contents").getAsJsonObject(); - if (itemStack.getItemMeta() != null) { - NBTCompound compound = new NBTCompound(); - itemStack.getItemMeta().write(compound); - obj.add("tag", new JsonPrimitive(compound.toSNBT())); - } + final String snbt = itemStack.getMeta().toSNBT(); + obj.add("tag", new JsonPrimitive(snbt)); return new ChatHoverEvent("show_item", obj); } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java index 0060a6624..84453f644 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java @@ -42,13 +42,11 @@ public class ArgumentItemStack extends Argument { if (nbtIndex == -1) { // Only item name final Material material = Registries.getMaterial(input); - return new ItemStack(material, (byte) 1); + return ItemStack.of(material); } else { final String materialName = input.substring(0, nbtIndex); final Material material = Registries.getMaterial(materialName); - ItemStack itemStack = new ItemStack(material, (byte) 1); - final String sNBT = input.substring(nbtIndex).replace("\\\"", "\""); NBTCompound compound; @@ -58,9 +56,7 @@ public class ArgumentItemStack extends Argument { throw new ArgumentSyntaxException("Item NBT is invalid", input, INVALID_NBT); } - NBTUtils.loadDataIntoItem(itemStack, compound); - - return itemStack; + return NBTUtils.loadItem(material, 1, compound); } } diff --git a/src/main/java/net/minestom/server/data/type/InventoryData.java b/src/main/java/net/minestom/server/data/type/InventoryData.java index f8df92439..c5a9a212c 100644 --- a/src/main/java/net/minestom/server/data/type/InventoryData.java +++ b/src/main/java/net/minestom/server/data/type/InventoryData.java @@ -1,5 +1,6 @@ package net.minestom.server.data.type; +import net.kyori.adventure.text.Component; import net.minestom.server.data.DataType; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.InventoryType; @@ -15,7 +16,7 @@ public class InventoryData extends DataType { final int size = inventoryType.getSize(); // Inventory title & type - writer.writeSizedString(value.getTitle()); + writer.writeComponent(value.getTitle()); writer.writeSizedString(inventoryType.name()); // Write all item stacks @@ -27,7 +28,7 @@ public class InventoryData extends DataType { @NotNull @Override public Inventory decode(@NotNull BinaryReader reader) { - final String title = reader.readSizedString(Integer.MAX_VALUE); + final Component title = reader.readComponent(Integer.MAX_VALUE); final InventoryType inventoryType = InventoryType.valueOf(reader.readSizedString(Integer.MAX_VALUE)); final int size = inventoryType.getSize(); diff --git a/src/main/java/net/minestom/server/entity/ItemEntity.java b/src/main/java/net/minestom/server/entity/ItemEntity.java index 9e0b14b05..8ee4b7ea6 100644 --- a/src/main/java/net/minestom/server/entity/ItemEntity.java +++ b/src/main/java/net/minestom/server/entity/ItemEntity.java @@ -109,7 +109,7 @@ public class ItemEntity extends ObjectEntity { if (!canApply) continue; - final ItemStack result = stackingRule.apply(itemStack.clone(), totalAmount); + final ItemStack result = stackingRule.apply(itemStack, totalAmount); EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity, result); callCancellableEvent(EntityItemMergeEvent.class, entityItemMergeEvent, () -> { diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 081fee3a7..1bbdfc619 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -37,7 +37,7 @@ import java.util.concurrent.ConcurrentHashMap; //TODO: Default attributes registration (and limitation ?) public class LivingEntity extends Entity implements EquipmentHandler { - // Item pickup + // ItemStack pickup protected boolean canPickupItem; protected Cooldown itemPickupCooldown = new Cooldown(new UpdateOption(5, TimeUnit.TICK)); @@ -111,13 +111,13 @@ public class LivingEntity extends Entity implements EquipmentHandler { } private void initEquipments() { - this.mainHandItem = ItemStack.getAirItem(); - this.offHandItem = ItemStack.getAirItem(); + this.mainHandItem = ItemStack.AIR; + this.offHandItem = ItemStack.AIR; - this.helmet = ItemStack.getAirItem(); - this.chestplate = ItemStack.getAirItem(); - this.leggings = ItemStack.getAirItem(); - this.boots = ItemStack.getAirItem(); + this.helmet = ItemStack.AIR; + this.chestplate = ItemStack.AIR; + this.leggings = ItemStack.AIR; + this.boots = ItemStack.AIR; } @NotNull diff --git a/src/main/java/net/minestom/server/entity/Metadata.java b/src/main/java/net/minestom/server/entity/Metadata.java index f0591c2bb..29124ea3c 100644 --- a/src/main/java/net/minestom/server/entity/Metadata.java +++ b/src/main/java/net/minestom/server/entity/Metadata.java @@ -343,7 +343,7 @@ public class Metadata { case TYPE_OPTCHAT: return (Value) OptChat((Component) null); case TYPE_SLOT: - return (Value) Slot(ItemStack.getAirItem()); + return (Value) Slot(ItemStack.AIR); case TYPE_BOOLEAN: return (Value) Boolean(false); case TYPE_ROTATION: diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index cdfd50c35..e56f4949a 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -68,6 +68,7 @@ import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.instance.InstanceUtils; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; import net.minestom.server.utils.time.Cooldown; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.UpdateOption; @@ -589,14 +590,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable, } } - // Item ownership cache - { - ItemStack[] itemStacks = inventory.getItemStacks(); - for (ItemStack itemStack : itemStacks) { - ItemStack.DATA_OWNERSHIP.clearCache(itemStack.getIdentifier()); - } - } - // Clear all viewable entities this.viewableEntities.forEach(entity -> entity.removeViewer(this)); // Clear all viewable chunks @@ -1096,42 +1089,16 @@ public class Player extends LivingEntity implements CommandSender, Localizable, MinecraftServer.getBossBarManager().removeBossBar(this, bar); } - /** - * Opens a book ui for the player with the given book metadata. - * - * @param bookMeta The metadata of the book to open - * @deprecated Use {@link #openBook(Book)} - */ - @Deprecated - public void openBook(@NotNull WrittenBookMeta bookMeta) { - // Set book in offhand - final ItemStack writtenBook = new ItemStack(Material.WRITTEN_BOOK, (byte) 1); - writtenBook.setItemMeta(bookMeta); - final SetSlotPacket setSlotPacket = new SetSlotPacket(); - setSlotPacket.windowId = 0; - setSlotPacket.slot = 45; - setSlotPacket.itemStack = writtenBook; - this.playerConnection.sendPacket(setSlotPacket); - - // Open the book - final OpenBookPacket openBookPacket = new OpenBookPacket(); - openBookPacket.hand = Hand.OFF; - this.playerConnection.sendPacket(openBookPacket); - - // Update inventory to remove book (which the actual inventory does not have) - this.inventory.update(); - } - @Override public void openBook(@NotNull Book book) { - // make the book - ItemStack writtenBook = new ItemStack(Material.WRITTEN_BOOK, (byte) 1); - writtenBook.setItemMeta(WrittenBookMeta.fromAdventure(book, this)); + ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK) + .meta(WrittenBookMeta.fromAdventure(book, this)) + .build(); // Set book in offhand SetSlotPacket setBookPacket = new SetSlotPacket(); setBookPacket.windowId = 0; - setBookPacket.slot = 45; + setBookPacket.slot = PlayerInventoryUtils.OFFHAND_SLOT; setBookPacket.itemStack = writtenBook; playerConnection.sendPacket(setBookPacket); @@ -1143,7 +1110,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // Restore the item in offhand SetSlotPacket restoreItemPacket = new SetSlotPacket(); restoreItemPacket.windowId = 0; - restoreItemPacket.slot = 45; + restoreItemPacket.slot = PlayerInventoryUtils.OFFHAND_SLOT; restoreItemPacket.itemStack = getItemInOffHand(); playerConnection.sendPacket(restoreItemPacket); } @@ -1996,10 +1963,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable, ItemStack cursorItem; if (openInventory == null) { cursorItem = getInventory().getCursorItem(); - getInventory().setCursorItem(ItemStack.getAirItem()); + getInventory().setCursorItem(ItemStack.AIR); } else { cursorItem = openInventory.getCursorItem(this); - openInventory.setCursorItem(this, ItemStack.getAirItem()); + openInventory.setCursorItem(this, ItemStack.AIR); } if (!cursorItem.isAir()) { // Add item to inventory if he hasn't been able to drop it diff --git a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayerController.java b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayerController.java index 08444fa19..6a9e8eeb2 100644 --- a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayerController.java +++ b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayerController.java @@ -4,7 +4,7 @@ import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; import net.minestom.server.instance.block.BlockFace; import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryModifier; +import net.minestom.server.inventory.AbstractInventory; import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.ClientPlayPacket; @@ -48,12 +48,12 @@ public class FakePlayerController { */ public void clickWindow(boolean playerInventory, short slot, byte button, short action, int mode) { Inventory inventory = playerInventory ? null : fakePlayer.getOpenInventory(); - InventoryModifier inventoryModifier = inventory == null ? fakePlayer.getInventory() : inventory; - playerInventory = inventoryModifier instanceof PlayerInventory; + AbstractInventory abstractInventory = inventory == null ? fakePlayer.getInventory() : inventory; + playerInventory = abstractInventory instanceof PlayerInventory; slot = playerInventory ? (short) PlayerInventoryUtils.convertToPacketSlot(slot) : slot; - ItemStack itemStack = inventoryModifier.getItemStack(slot); + ItemStack itemStack = abstractInventory.getItemStack(slot); ClientClickWindowPacket clickWindowPacket = new ClientClickWindowPacket(); clickWindowPacket.windowId = playerInventory ? 0 : inventory.getWindowId(); diff --git a/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java b/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java index 18e2512b7..9fbb9c0a7 100644 --- a/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java @@ -13,7 +13,7 @@ class ItemContainingMeta extends EntityMeta { protected ItemContainingMeta(@NotNull Entity entity, @NotNull Metadata metadata, @NotNull Material defaultItemMaterial) { super(entity, metadata); - this.defaultItem = new ItemStack(defaultItemMaterial, (byte) 1); + this.defaultItem = ItemStack.of(defaultItemMaterial); } @NotNull diff --git a/src/main/java/net/minestom/server/entity/metadata/other/FireworkRocketMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/FireworkRocketMeta.java index c3155617d..c019323de 100644 --- a/src/main/java/net/minestom/server/entity/metadata/other/FireworkRocketMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/other/FireworkRocketMeta.java @@ -18,7 +18,7 @@ public class FireworkRocketMeta extends EntityMeta implements ProjectileMeta { @NotNull public ItemStack getFireworkInfo() { - return super.metadata.getIndex((byte) 7, ItemStack.getAirItem()); + return super.metadata.getIndex((byte) 7, ItemStack.AIR); } public void setFireworkInfo(@NotNull ItemStack value) { diff --git a/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java index b4d0e2f68..080694213 100644 --- a/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java @@ -19,7 +19,7 @@ public class ItemFrameMeta extends EntityMeta implements ObjectDataProvider { @NotNull public ItemStack getItem() { - return super.metadata.getIndex((byte) 7, ItemStack.getAirItem()); + return super.metadata.getIndex((byte) 7, ItemStack.AIR); } public void setItem(@NotNull ItemStack value) { diff --git a/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java b/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java index d95df9cee..429b2bcdf 100644 --- a/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java +++ b/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java @@ -41,13 +41,13 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { setLeftLegRotation(new Vector(-1f, 0, -1f)); setRightLegRotation(new Vector(1, 0, 1)); - this.mainHandItem = ItemStack.getAirItem(); - this.offHandItem = ItemStack.getAirItem(); + this.mainHandItem = ItemStack.AIR; + this.offHandItem = ItemStack.AIR; - this.helmet = ItemStack.getAirItem(); - this.chestplate = ItemStack.getAirItem(); - this.leggings = ItemStack.getAirItem(); - this.boots = ItemStack.getAirItem(); + this.helmet = ItemStack.AIR; + this.chestplate = ItemStack.AIR; + this.leggings = ItemStack.AIR; + this.boots = ItemStack.AIR; } diff --git a/src/main/java/net/minestom/server/entity/type/decoration/EntityItemFrame.java b/src/main/java/net/minestom/server/entity/type/decoration/EntityItemFrame.java index 7683a76ce..f3fd2ffe5 100644 --- a/src/main/java/net/minestom/server/entity/type/decoration/EntityItemFrame.java +++ b/src/main/java/net/minestom/server/entity/type/decoration/EntityItemFrame.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull; // FIXME: https://wiki.vg/Object_Data#Item_Frame_.28id_71.29 // "You have to set both Orientation and Yaw/Pitch accordingly, otherwise it will not work." + /** * @deprecated Use {@link net.minestom.server.entity.metadata.other.ItemFrameMeta} instead. */ @@ -37,7 +38,7 @@ public class EntityItemFrame extends ObjectEntity { */ @NotNull public ItemStack getItemStack() { - return metadata.getIndex((byte) 7, ItemStack.getAirItem()); + return metadata.getIndex((byte) 7, ItemStack.AIR); } /** diff --git a/src/main/java/net/minestom/server/entity/type/projectile/EntityEyeOfEnder.java b/src/main/java/net/minestom/server/entity/type/projectile/EntityEyeOfEnder.java index ae9f28c5c..4ebfa357d 100644 --- a/src/main/java/net/minestom/server/entity/type/projectile/EntityEyeOfEnder.java +++ b/src/main/java/net/minestom/server/entity/type/projectile/EntityEyeOfEnder.java @@ -26,7 +26,7 @@ public class EntityEyeOfEnder extends Entity { * @return the item */ public ItemStack getItemStack() { - return metadata.getIndex((byte) 7, ItemStack.getAirItem()); + return metadata.getIndex((byte) 7, ItemStack.AIR); } /** diff --git a/src/main/java/net/minestom/server/entity/type/projectile/EntityPotion.java b/src/main/java/net/minestom/server/entity/type/projectile/EntityPotion.java index c42ec9b97..f0c1e1c76 100644 --- a/src/main/java/net/minestom/server/entity/type/projectile/EntityPotion.java +++ b/src/main/java/net/minestom/server/entity/type/projectile/EntityPotion.java @@ -22,7 +22,7 @@ public class EntityPotion extends Entity { @NotNull public ItemStack getPotion() { - return metadata.getIndex((byte) 7, ItemStack.getAirItem()); + return metadata.getIndex((byte) 7, ItemStack.AIR); } public void setPotion(@NotNull ItemStack potion) { diff --git a/src/main/java/net/minestom/server/event/player/PlayerAddItemStackEvent.java b/src/main/java/net/minestom/server/event/player/PlayerAddItemStackEvent.java deleted file mode 100644 index 3adbb1e6f..000000000 --- a/src/main/java/net/minestom/server/event/player/PlayerAddItemStackEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.minestom.server.event.player; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.CancellableEvent; -import net.minestom.server.event.PlayerEvent; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -/** - * Called as a result of {@link net.minestom.server.inventory.PlayerInventory#addItemStack(ItemStack)}. - */ -public class PlayerAddItemStackEvent extends PlayerEvent implements CancellableEvent { - - private ItemStack itemStack; - - private boolean cancelled; - - public PlayerAddItemStackEvent(@NotNull Player player, @NotNull ItemStack itemStack) { - super(player); - this.itemStack = itemStack; - } - - /** - * Gets the item stack which will be added. - * - * @return the item stack - */ - @NotNull - public ItemStack getItemStack() { - return itemStack; - } - - /** - * Changes the item stack which will be added. - * - * @param itemStack the new item stack - */ - public void setItemStack(@NotNull ItemStack itemStack) { - this.itemStack = itemStack; - } - - @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/PlayerSetItemStackEvent.java b/src/main/java/net/minestom/server/event/player/PlayerSetItemStackEvent.java deleted file mode 100644 index f0e9e0574..000000000 --- a/src/main/java/net/minestom/server/event/player/PlayerSetItemStackEvent.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.minestom.server.event.player; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.CancellableEvent; -import net.minestom.server.event.PlayerEvent; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -/** - * Called as a result of {@link net.minestom.server.inventory.PlayerInventory#setItemStack(int, ItemStack)} - * and player click in his inventory. - */ -public class PlayerSetItemStackEvent extends PlayerEvent implements CancellableEvent { - - private int slot; - private ItemStack itemStack; - - private boolean cancelled; - - public PlayerSetItemStackEvent(@NotNull Player player, int slot, @NotNull ItemStack itemStack) { - super(player); - this.slot = slot; - this.itemStack = itemStack; - } - - /** - * Gets the slot where the item will be set. - * - * @return the slot - */ - public int getSlot() { - return slot; - } - - /** - * Changes the slot where the item will be set. - * - * @param slot the new slot - */ - public void setSlot(int slot) { - this.slot = slot; - } - - /** - * Gets the item stack which will be set. - * - * @return the item stack - */ - @NotNull - public ItemStack getItemStack() { - return itemStack; - } - - /** - * Changes the item stack which will be set. - * - * @param itemStack the new item stack - */ - public void setItemStack(@NotNull ItemStack itemStack) { - this.itemStack = itemStack; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } -} diff --git a/src/main/java/net/minestom/server/gamedata/loottables/ConditionedFunctionWrapper.java b/src/main/java/net/minestom/server/gamedata/loottables/ConditionedFunctionWrapper.java index 93c6f0c00..4b4b9d0f6 100644 --- a/src/main/java/net/minestom/server/gamedata/loottables/ConditionedFunctionWrapper.java +++ b/src/main/java/net/minestom/server/gamedata/loottables/ConditionedFunctionWrapper.java @@ -22,7 +22,7 @@ public class ConditionedFunctionWrapper implements LootTableFunction { @Override public ItemStack apply(ItemStack stack, Data data) { for (Condition c : conditions) { - if(!c.test(data)) + if (!c.test(data)) return stack; } return baseFunction.apply(stack, data); diff --git a/src/main/java/net/minestom/server/gamedata/loottables/LootTable.java b/src/main/java/net/minestom/server/gamedata/loottables/LootTable.java index 2e9bbf0e5..db39e47fc 100644 --- a/src/main/java/net/minestom/server/gamedata/loottables/LootTable.java +++ b/src/main/java/net/minestom/server/gamedata/loottables/LootTable.java @@ -31,10 +31,10 @@ public class LootTable { } public List generate(Data arguments) { - if(arguments == null) + if (arguments == null) arguments = Data.EMPTY; List output = new LinkedList<>(); - for(Pool p : pools) { + for (Pool p : pools) { p.generate(output, arguments); } return output; @@ -74,18 +74,18 @@ public class LootTable { } public void generate(List output, Data arguments) { - for(Condition c : conditions) { - if(!c.test(arguments)) + for (Condition c : conditions) { + if (!c.test(arguments)) return; } Random rng = new Random(); int luck = arguments.getOrDefault(LUCK_KEY, 0); - int rollCount = rng.nextInt(maxRollCount - minRollCount +1 /*inclusive*/) + minRollCount; - int bonusRollCount = rng.nextInt(bonusMaxRollCount - bonusMinRollCount +1 /*inclusive*/) + bonusMinRollCount; + int rollCount = rng.nextInt(maxRollCount - minRollCount + 1 /*inclusive*/) + minRollCount; + int bonusRollCount = rng.nextInt(bonusMaxRollCount - bonusMinRollCount + 1 /*inclusive*/) + bonusMinRollCount; bonusRollCount *= luck; // TODO: implement luck (quality/weight) weight=floor( weight + (quality * generic.luck)) WeightedRandom weightedRandom = new WeightedRandom<>(entries); - for (int i = 0; i < rollCount+bonusRollCount; i++) { + for (int i = 0; i < rollCount + bonusRollCount; i++) { Entry entry = weightedRandom.get(rng); entry.generateStacks(output, arguments); } @@ -122,8 +122,8 @@ public class LootTable { } public final void generateStacks(List output, Data arguments) { - for(Condition c : conditions) { - if(!c.test(arguments)) + for (Condition c : conditions) { + if (!c.test(arguments)) return; } generate(output, arguments); diff --git a/src/main/java/net/minestom/server/gamedata/loottables/entries/AlternativesEntry.java b/src/main/java/net/minestom/server/gamedata/loottables/entries/AlternativesEntry.java index 47ba1fe79..6ede7cea8 100644 --- a/src/main/java/net/minestom/server/gamedata/loottables/entries/AlternativesEntry.java +++ b/src/main/java/net/minestom/server/gamedata/loottables/entries/AlternativesEntry.java @@ -17,11 +17,11 @@ public class AlternativesEntry extends LootTable.Entry { @Override public void generate(List output, Data arguments) { - for(LootTable.Entry c : children) { + for (LootTable.Entry c : children) { int previousSize = output.size(); c.generateStacks(output, arguments); int newSize = output.size(); - if(newSize != previousSize) { // an entry managed to generate, stop here + if (newSize != previousSize) { // an entry managed to generate, stop here return; } } diff --git a/src/main/java/net/minestom/server/gamedata/loottables/entries/ItemEntry.java b/src/main/java/net/minestom/server/gamedata/loottables/entries/ItemEntry.java index c49eb11bf..9d5b257bc 100644 --- a/src/main/java/net/minestom/server/gamedata/loottables/entries/ItemEntry.java +++ b/src/main/java/net/minestom/server/gamedata/loottables/entries/ItemEntry.java @@ -23,11 +23,11 @@ public class ItemEntry extends LootTable.Entry { @Override public void generate(List output, Data arguments) { - ItemStack stack = new ItemStack(item, (byte)1); + ItemStack stack = ItemStack.of(item); for (LootTableFunction function : functions) { stack = function.apply(stack, arguments); } - if(!stack.isAir()) { + if (!stack.isAir()) { output.add(stack); } } diff --git a/src/main/java/net/minestom/server/gamedata/loottables/entries/SequenceEntry.java b/src/main/java/net/minestom/server/gamedata/loottables/entries/SequenceEntry.java index 48beb921c..f1245e8e3 100644 --- a/src/main/java/net/minestom/server/gamedata/loottables/entries/SequenceEntry.java +++ b/src/main/java/net/minestom/server/gamedata/loottables/entries/SequenceEntry.java @@ -17,11 +17,11 @@ public class SequenceEntry extends LootTable.Entry { @Override public void generate(List output, Data arguments) { - for(LootTable.Entry c : children) { + for (LootTable.Entry c : children) { int previousSize = output.size(); c.generateStacks(output, arguments); int newSize = output.size(); - if(newSize == previousSize) { // an entry failed to generate, stop here + if (newSize == previousSize) { // an entry failed to generate, stop here return; } } diff --git a/src/main/java/net/minestom/server/gamedata/loottables/entries/TagEntry.java b/src/main/java/net/minestom/server/gamedata/loottables/entries/TagEntry.java index 296ea1b86..9dfa7a3ea 100644 --- a/src/main/java/net/minestom/server/gamedata/loottables/entries/TagEntry.java +++ b/src/main/java/net/minestom/server/gamedata/loottables/entries/TagEntry.java @@ -27,19 +27,19 @@ public class TagEntry extends LootTable.Entry { @Override public void generate(List output, Data arguments) { Set values = tag.getValues(); - if(values.isEmpty()) + if (values.isEmpty()) return; Material[] asArrayOfItems = new Material[values.size()]; int ptr = 0; for (NamespaceID id : values) { asArrayOfItems[ptr++] = Registries.getMaterial(id); } - if(expand) { - Material selectedItem = asArrayOfItems[rng.nextInt(asArrayOfItems.length)]; - output.add(new ItemStack(selectedItem, (byte) 1)); + if (expand) { + Material selectedMaterial = asArrayOfItems[rng.nextInt(asArrayOfItems.length)]; + output.add(ItemStack.of(selectedMaterial)); } else { - for(Material item : asArrayOfItems) { - output.add(new ItemStack(item, (byte) 1)); + for (Material material : asArrayOfItems) { + output.add(ItemStack.of(material)); } } } diff --git a/src/main/java/net/minestom/server/inventory/AbstractInventory.java b/src/main/java/net/minestom/server/inventory/AbstractInventory.java new file mode 100644 index 000000000..a95114d5b --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/AbstractInventory.java @@ -0,0 +1,223 @@ +package net.minestom.server.inventory; + +import net.minestom.server.data.Data; +import net.minestom.server.data.DataContainer; +import net.minestom.server.inventory.click.InventoryClickProcessor; +import net.minestom.server.inventory.condition.InventoryCondition; +import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.UnaryOperator; + +/** + * Represents an inventory where items can be modified/retrieved. + */ +public abstract class AbstractInventory implements InventoryClickHandler, DataContainer { + + private final int size; + protected final ItemStack[] itemStacks; + + // list of conditions/callbacks assigned to this inventory + protected final List inventoryConditions = new CopyOnWriteArrayList<>(); + // the click processor which process all the clicks in the inventory + protected final InventoryClickProcessor clickProcessor = new InventoryClickProcessor(); + + private Data data; + + protected AbstractInventory(int size) { + this.size = size; + this.itemStacks = new ItemStack[getSize()]; + + Arrays.fill(itemStacks, ItemStack.AIR); + } + + /** + * Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s). + * + * @param slot the slot to set the item + * @param itemStack the item to set + */ + public synchronized void setItemStack(int slot, @NotNull ItemStack itemStack) { + Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()), + "Inventory does not have the slot " + slot); + safeItemInsert(slot, itemStack); + } + + protected abstract void safeItemInsert(int slot, @NotNull ItemStack itemStack); + + public synchronized @NotNull T processItemStack(@NotNull ItemStack itemStack, + @NotNull TransactionType type, + @NotNull TransactionOption option) { + var pair = type.process(this, itemStack); + return option.fill(this, pair.left(), pair.right()); + } + + public synchronized @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, + @NotNull TransactionType type, + @NotNull TransactionOption option) { + List result = new ArrayList<>(itemStacks.size()); + itemStacks.forEach(itemStack -> { + T transactionResult = processItemStack(itemStack, type, option); + result.add(transactionResult); + }); + return result; + } + + /** + * Adds an {@link ItemStack} to the inventory and sends relevant update to the viewer(s). + * + * @param itemStack the item to add + * @param option the transaction option + * @return true if the item has been successfully added, false otherwise + */ + public @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { + return processItemStack(itemStack, TransactionType.ADD, option); + } + + public boolean addItemStack(@NotNull ItemStack itemStack) { + return addItemStack(itemStack, TransactionOption.ALL_OR_NOTHING); + } + + /** + * Adds {@link ItemStack}s to the inventory and sends relevant updates to the viewer(s). + * + * @param itemStacks items to add + * @param option the transaction option + * @return the operation results + */ + public @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, + @NotNull TransactionOption option) { + return processItemStacks(itemStacks, TransactionType.ADD, option); + } + + /** + * Takes an {@link ItemStack} from the inventory and sends relevant update to the viewer(s). + * + * @param itemStack the item to take + * @return true if the item has been successfully fully taken, false otherwise + */ + public @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { + return processItemStack(itemStack, TransactionType.TAKE, option); + } + + /** + * Takes {@link ItemStack}s from the inventory and sends relevant updates to the viewer(s). + * + * @param itemStacks items to take + * @return the operation results + */ + public @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, + @NotNull TransactionOption option) { + return processItemStacks(itemStacks, TransactionType.TAKE, option); + } + + public synchronized void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) { + var currentItem = getItemStack(slot); + setItemStack(slot, operator.apply(currentItem)); + } + + /** + * Clears the inventory and send relevant update to the viewer(s). + */ + public synchronized void clear() { + // Clear the item array + Arrays.fill(itemStacks, ItemStack.AIR); + // Send the cleared inventory to viewers + update(); + } + + public abstract void update(); + + /** + * Gets the {@link ItemStack} at the specified slot. + * + * @param slot the slot to check + * @return the item in the slot {@code slot} + */ + public @NotNull ItemStack getItemStack(int slot) { + return itemStacks[slot]; + } + + /** + * Gets all the {@link ItemStack} in the inventory. + *

+ * Be aware that the returned array does not need to be the original one, + * meaning that modifying it directly may not work. + * + * @return an array containing all the inventory's items + */ + public @NotNull ItemStack[] getItemStacks() { + return itemStacks.clone(); + } + + /** + * Gets the size of the inventory. + * + * @return the inventory's size + */ + public int getSize() { + return size; + } + + /** + * Gets the size of the "inner inventory" (which includes only "usable" slots). + * + * @return inner inventory's size + */ + public int getInnerSize() { + return getSize(); + } + + /** + * Gets all the {@link InventoryCondition} of this inventory. + * + * @return a modifiable {@link List} containing all the inventory conditions + */ + public @NotNull List<@NotNull InventoryCondition> getInventoryConditions() { + return inventoryConditions; + } + + /** + * Adds a new {@link InventoryCondition} to this inventory. + * + * @param inventoryCondition the inventory condition to add + */ + public void addInventoryCondition(@NotNull InventoryCondition inventoryCondition) { + this.inventoryConditions.add(inventoryCondition); + } + + /** + * Places all the items of {@code itemStacks} into the internal array. + * + * @param itemStacks the array to copy the content from + * @throws IllegalArgumentException if the size of the array is not equal to {@link #getSize()} + * @throws NullPointerException if {@code itemStacks} contains one null element or more + */ + public void copyContents(@NotNull ItemStack[] itemStacks) { + Check.argCondition(itemStacks.length != getSize(), + "The size of the array has to be of the same size as the inventory: " + getSize()); + + for (int i = 0; i < itemStacks.length; i++) { + final ItemStack itemStack = itemStacks[i]; + Check.notNull(itemStack, "The item array cannot contain any null element!"); + setItemStack(i, itemStack); + } + } + + @Override + public @Nullable Data getData() { + return data; + } + + @Override + public void setData(@Nullable Data data) { + this.data = data; + } +} diff --git a/src/main/java/net/minestom/server/inventory/Inventory.java b/src/main/java/net/minestom/server/inventory/Inventory.java index edc6e5eec..61e783412 100644 --- a/src/main/java/net/minestom/server/inventory/Inventory.java +++ b/src/main/java/net/minestom/server/inventory/Inventory.java @@ -1,33 +1,23 @@ package net.minestom.server.inventory; +import net.kyori.adventure.text.Component; import net.minestom.server.Viewable; -import net.minestom.server.data.Data; -import net.minestom.server.data.DataContainer; import net.minestom.server.entity.Player; import net.minestom.server.inventory.click.ClickType; import net.minestom.server.inventory.click.InventoryClickLoopHandler; -import net.minestom.server.inventory.click.InventoryClickProcessor; import net.minestom.server.inventory.click.InventoryClickResult; -import net.minestom.server.inventory.condition.InventoryCondition; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.StackingRule; import net.minestom.server.network.packet.server.play.OpenWindowPacket; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; import net.minestom.server.network.packet.server.play.WindowPropertyPacket; import net.minestom.server.network.player.PlayerConnection; -import net.minestom.server.utils.ArrayUtils; -import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.inventory.PlayerInventoryUtils; -import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collections; -import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; @@ -37,7 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger; * You can create one with {@link Inventory#Inventory(InventoryType, String)} or by making your own subclass. * It can then be opened using {@link Player#openInventory(Inventory)}. */ -public class Inventory implements InventoryModifier, InventoryClickHandler, Viewable, DataContainer { +public class Inventory extends AbstractInventory implements Viewable { // incremented each time an inventory is created (used in the window packets) private static final AtomicInteger LAST_INVENTORY_ID = new AtomicInteger(); @@ -46,41 +36,32 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View private final byte id; // the type of this inventory private final InventoryType inventoryType; - // the title of this inventory) - private String title; - - // the size based on the inventory type - private final int size; + // the title of this inventory + private Component title; private final int offset; - // the items in this inventory - private final ItemStack[] itemStacks; // the players currently viewing this inventory private final Set viewers = new CopyOnWriteArraySet<>(); private final Set unmodifiableViewers = Collections.unmodifiableSet(viewers); // (player -> cursor item) map, used by the click listeners private final ConcurrentHashMap cursorPlayersItem = new ConcurrentHashMap<>(); - // list of conditions/callbacks assigned to this inventory - private final List inventoryConditions = new CopyOnWriteArrayList<>(); - // the click processor which process all the clicks in the inventory - private final InventoryClickProcessor clickProcessor = new InventoryClickProcessor(); - - private Data data; - - public Inventory(@NotNull InventoryType inventoryType, @NotNull String title) { + public Inventory(@NotNull InventoryType inventoryType, @NotNull Component title) { + super(inventoryType.getSize()); this.id = generateId(); this.inventoryType = inventoryType; this.title = title; - this.size = inventoryType.getSize(); + this.offset = getSize(); + } - this.offset = size; - - this.itemStacks = new ItemStack[size]; - - ArrayUtils.fill(itemStacks, ItemStack::getAirItem); + /** + * @deprecated use {@link Inventory#Inventory(InventoryType, Component)} + */ + @Deprecated + public Inventory(@NotNull InventoryType inventoryType, @NotNull String title) { + this(inventoryType, Component.text(title)); } private static byte generateId() { @@ -106,7 +87,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View * @return the inventory title */ @NotNull - public String getTitle() { + public Component getTitle() { return title; } @@ -115,7 +96,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View * * @param title the new inventory title */ - public void setTitle(@NotNull String title) { + public void setTitle(@NotNull Component title) { this.title = title; OpenWindowPacket packet = new OpenWindowPacket(title); @@ -141,85 +122,17 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View } @Override - public void setItemStack(int slot, @NotNull ItemStack itemStack) { - Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()), - inventoryType.toString() + " does not have slot " + slot); - - safeItemInsert(slot, itemStack); - } - - @Override - public synchronized boolean addItemStack(@NotNull ItemStack itemStack) { - final StackingRule stackingRule = itemStack.getStackingRule(); - for (int i = 0; i < getSize(); i++) { - ItemStack item = getItemStack(i); - final StackingRule itemStackingRule = item.getStackingRule(); - if (itemStackingRule.canBeStacked(itemStack, item)) { - final int itemAmount = itemStackingRule.getAmount(item); - if (itemAmount == stackingRule.getMaxSize()) - continue; - final int itemStackAmount = itemStackingRule.getAmount(itemStack); - final int totalAmount = itemStackAmount + itemAmount; - if (!stackingRule.canApply(itemStack, totalAmount)) { - item = itemStackingRule.apply(item, itemStackingRule.getMaxSize()); - - sendSlotRefresh((short) i, item); - itemStack = stackingRule.apply(itemStack, totalAmount - stackingRule.getMaxSize()); - } else { - item.setAmount((byte) totalAmount); - sendSlotRefresh((short) i, item); - return true; - } - } else if (item.isAir()) { - setItemStack(i, itemStack); - return true; - } - } - return false; - } - - @Override - public void clear() { - // Clear the item array - for (int i = 0; i < getSize(); i++) { - setItemStackInternal(i, ItemStack.getAirItem()); - } - // Send the cleared inventory to viewers - update(); - } - - - @NotNull - @Override - public ItemStack getItemStack(int slot) { - return itemStacks[slot]; - } - - @NotNull - @Override - public ItemStack[] getItemStacks() { - return itemStacks.clone(); - } - - @Override - public int getSize() { - return size; - } - - @NotNull - @Override - public List getInventoryConditions() { - return inventoryConditions; - } - - @Override - public void addInventoryCondition(@NotNull InventoryCondition inventoryCondition) { - this.inventoryConditions.add(inventoryCondition); + public synchronized void clear() { + super.clear(); + // Clear cursor + getViewers().forEach(player -> + setCursorItem(player, ItemStack.AIR)); } /** * Refreshes the inventory for all viewers. */ + @Override public void update() { sendPacketToViewers(createNewWindowItemsPacket()); } @@ -239,15 +152,6 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View playerConnection.sendPacket(createNewWindowItemsPacket()); } - /** - * Refreshes only a specific slot with the updated item stack data. - * - * @param slot the slot to refresh - */ - public void refreshSlot(short slot) { - sendSlotRefresh(slot, getItemStack(slot)); - } - @NotNull @Override public Set getViewers() { @@ -289,7 +193,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View */ @NotNull public ItemStack getCursorItem(@NotNull Player player) { - return cursorPlayersItem.getOrDefault(player, ItemStack.getAirItem()); + return cursorPlayersItem.getOrDefault(player, ItemStack.AIR); } /** @@ -323,8 +227,9 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View * @param slot the internal slot id * @param itemStack the item to insert */ - private synchronized void safeItemInsert(int slot, @NotNull ItemStack itemStack) { - setItemStackInternal(slot, itemStack); + @Override + protected synchronized void safeItemInsert(int slot, @NotNull ItemStack itemStack) { + this.itemStacks[slot] = itemStack; SetSlotPacket setSlotPacket = new SetSlotPacket(); setSlotPacket.windowId = getWindowId(); setSlotPacket.slot = (short) slot; @@ -332,19 +237,6 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View sendPacketToViewers(setSlotPacket); } - /** - * Inserts an item into the inventory without notifying viewers. - *

- * This will also warn the inventory that the cached window items packet is - * not up-to-date. - * - * @param slot the internal slot - * @param itemStack the item to insert - */ - protected void setItemStackInternal(int slot, @NotNull ItemStack itemStack) { - itemStacks[slot] = itemStack; - } - /** * Creates a complete new {@link WindowItemsPacket}. * @@ -381,7 +273,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View * @param player the player to change the cursor item * @param itemStack the cursor item */ - private void setCursorPlayerItem(@NotNull Player player, @NotNull ItemStack itemStack) { + private void refreshPlayerCursorItem(@NotNull Player player, @NotNull ItemStack itemStack) { this.cursorPlayersItem.put(player, itemStack); } @@ -408,7 +300,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View } else { playerInventory.setItemStack(clickSlot, clickResult.getClicked()); } - setCursorPlayerItem(player, clickResult.getCursor()); + refreshPlayerCursorItem(player, clickResult.getCursor()); if (!clickResult.isCancel()) callClickEvent(player, isInWindow ? this : null, slot, ClickType.LEFT_CLICK, clicked, cursor); @@ -435,7 +327,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View } else { playerInventory.setItemStack(clickSlot, clickResult.getClicked()); } - setCursorPlayerItem(player, clickResult.getCursor()); + refreshPlayerCursorItem(player, clickResult.getCursor()); if (!clickResult.isCancel()) callClickEvent(player, isInWindow ? this : null, slot, ClickType.RIGHT_CLICK, clicked, cursor); @@ -493,7 +385,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View updateFromClick(clickResult, player); } - setCursorPlayerItem(player, clickResult.getCursor()); + refreshPlayerCursorItem(player, clickResult.getCursor()); playerInventory.update(); update(); @@ -543,7 +435,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View final boolean outsideDrop = slot == -999; final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); final ItemStack clicked = outsideDrop ? - ItemStack.getAirItem() : (isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)); + ItemStack.AIR : (isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)); final ItemStack cursor = getCursorItem(player); final InventoryClickResult clickResult = clickProcessor.drop(isInWindow ? this : null, player, @@ -562,7 +454,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View } } - setCursorPlayerItem(player, clickResult.getCursor()); + refreshPlayerCursorItem(player, clickResult.getCursor()); return !clickResult.isCancel(); } @@ -574,7 +466,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); final ItemStack clicked = slot != -999 ? (isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)) : - ItemStack.getAirItem(); + ItemStack.AIR; final ItemStack cursor = getCursorItem(player); final InventoryClickResult clickResult = clickProcessor.dragging(isInWindow ? this : null, player, @@ -600,7 +492,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View updateFromClick(clickResult, player); } - setCursorPlayerItem(player, clickResult.getCursor()); + refreshPlayerCursorItem(player, clickResult.getCursor()); return !clickResult.isCancel(); } @@ -629,27 +521,11 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View if (clickResult.doRefresh()) updateFromClick(clickResult, player); - setCursorPlayerItem(player, clickResult.getCursor()); + refreshPlayerCursorItem(player, clickResult.getCursor()); return !clickResult.isCancel(); } - /** - * Refresh a slot for all viewers - *

- * WARNING: this does not update the items in the inventory, this is only visual - * - * @param slot the packet slot - * @param itemStack the item stack to set at the slot - */ - private void sendSlotRefresh(short slot, ItemStack itemStack) { - SetSlotPacket setSlotPacket = new SetSlotPacket(); - setSlotPacket.windowId = getWindowId(); - setSlotPacket.slot = slot; - setSlotPacket.itemStack = itemStack; - sendPacketToViewers(setSlotPacket); - } - /** * Used to update the inventory for a specific player in order to fix his cancelled actions * @@ -663,15 +539,4 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View update(player); } } - - @Nullable - @Override - public Data getData() { - return data; - } - - @Override - public void setData(@Nullable Data data) { - this.data = data; - } } diff --git a/src/main/java/net/minestom/server/inventory/InventoryModifier.java b/src/main/java/net/minestom/server/inventory/InventoryModifier.java deleted file mode 100644 index f2d9035b9..000000000 --- a/src/main/java/net/minestom/server/inventory/InventoryModifier.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.minestom.server.inventory; - -import net.minestom.server.inventory.condition.InventoryCondition; -import net.minestom.server.item.ItemStack; -import net.minestom.server.utils.validate.Check; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * Represents an inventory where items can be modified/retrieved. - */ -public interface InventoryModifier { - - /** - * Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s). - * - * @param slot the slot to set the item - * @param itemStack the item to set - */ - void setItemStack(int slot, @NotNull ItemStack itemStack); - - /** - * Adds an {@link ItemStack} to the inventory and send relevant update to the viewer(s). - *

- * Even the item cannot be fully added, the amount of {@code itemStack} will be updated. - * - * @param itemStack the item to add - * @return true if the item has been successfully fully added, false otherwise - */ - boolean addItemStack(@NotNull ItemStack itemStack); - - /** - * Clears the inventory and send relevant update to the viewer(s). - */ - void clear(); - - /** - * Gets the {@link ItemStack} at the specified slot. - * - * @param slot the slot to check - * @return the item in the slot {@code slot} - */ - @NotNull - ItemStack getItemStack(int slot); - - /** - * Gets all the {@link ItemStack} in the inventory. - *

- * Be aware that the returned array does not need to be the original one, - * meaning that modifying it directly may not work. - * - * @return an array containing all the inventory's items - */ - @NotNull - ItemStack[] getItemStacks(); - - /** - * Gets the size of the inventory. - * - * @return the inventory's size - */ - int getSize(); - - /** - * Gets all the {@link InventoryCondition} of this inventory. - * - * @return a modifiable {@link List} containing all the inventory conditions - */ - @NotNull - List getInventoryConditions(); - - /** - * Adds a new {@link InventoryCondition} to this inventory. - * - * @param inventoryCondition the inventory condition to add - */ - void addInventoryCondition(@NotNull InventoryCondition inventoryCondition); - - /** - * Places all the items of {@code itemStacks} into the internal array. - * - * @param itemStacks the array to copy the content from - * @throws IllegalArgumentException if the size of the array is not equal to {@link #getSize()} - * @throws NullPointerException if {@code itemStacks} contains one null element or more - */ - default void copyContents(@NotNull ItemStack[] itemStacks) { - Check.argCondition(itemStacks.length != getSize(), - "The size of the array has to be of the same size as the inventory: " + getSize()); - - for (int i = 0; i < itemStacks.length; i++) { - final ItemStack itemStack = itemStacks[i]; - Check.notNull(itemStack, "The item array cannot contain any null element!"); - setItemStack(i, itemStack); - } - } -} diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index 1f3e90fb6..a1eb77b30 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -1,147 +1,60 @@ package net.minestom.server.inventory; -import net.minestom.server.data.Data; -import net.minestom.server.data.DataContainer; import net.minestom.server.entity.Player; import net.minestom.server.event.item.ArmorEquipEvent; -import net.minestom.server.event.player.PlayerAddItemStackEvent; -import net.minestom.server.event.player.PlayerSetItemStackEvent; import net.minestom.server.inventory.click.ClickType; import net.minestom.server.inventory.click.InventoryClickLoopHandler; -import net.minestom.server.inventory.click.InventoryClickProcessor; import net.minestom.server.inventory.click.InventoryClickResult; import net.minestom.server.inventory.condition.InventoryCondition; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.StackingRule; import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; -import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; /** * Represents the inventory of a {@link Player}, retrieved with {@link Player#getInventory()}. */ -public class PlayerInventory implements InventoryModifier, InventoryClickHandler, EquipmentHandler, DataContainer { +public class PlayerInventory extends AbstractInventory implements EquipmentHandler { public static final int INVENTORY_SIZE = 46; + public static final int INNER_INVENTORY_SIZE = 36; protected final Player player; - protected final ItemStack[] items = new ItemStack[INVENTORY_SIZE]; - private ItemStack cursorItem = ItemStack.getAirItem(); - - private final List inventoryConditions = new CopyOnWriteArrayList<>(); - private final InventoryClickProcessor clickProcessor = new InventoryClickProcessor(); - - private Data data; + private ItemStack cursorItem = ItemStack.AIR; public PlayerInventory(@NotNull Player player) { + super(INVENTORY_SIZE); this.player = player; - - ArrayUtils.fill(items, ItemStack::getAirItem); - } - - @NotNull - @Override - public ItemStack getItemStack(int slot) { - return this.items[slot]; - } - - @NotNull - @Override - public ItemStack[] getItemStacks() { - return items.clone(); - } - - @NotNull - @Override - public List getInventoryConditions() { - return inventoryConditions; } @Override public void addInventoryCondition(@NotNull InventoryCondition inventoryCondition) { + // fix packet slot to inventory slot conversion InventoryCondition condition = (p, slot, clickType, inventoryConditionResult) -> { final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); inventoryCondition.accept(p, convertedSlot, clickType, inventoryConditionResult); }; - this.inventoryConditions.add(condition); + super.addInventoryCondition(condition); } @Override - public void setItemStack(int slot, @NotNull ItemStack itemStack) { - PlayerSetItemStackEvent setItemStackEvent = new PlayerSetItemStackEvent(player, slot, itemStack); - player.callEvent(PlayerSetItemStackEvent.class, setItemStackEvent); - if (setItemStackEvent.isCancelled()) - return; - slot = setItemStackEvent.getSlot(); - itemStack = setItemStackEvent.getItemStack(); - - safeItemInsert(slot, itemStack); - } - - @Override - public synchronized boolean addItemStack(@NotNull ItemStack itemStack) { - PlayerAddItemStackEvent addItemStackEvent = new PlayerAddItemStackEvent(player, itemStack); - player.callEvent(PlayerAddItemStackEvent.class, addItemStackEvent); - if (addItemStackEvent.isCancelled()) - return false; - - itemStack = addItemStackEvent.getItemStack(); - - final StackingRule stackingRule = itemStack.getStackingRule(); - for (int i = 0; i < items.length - 10; i++) { - ItemStack item = items[i]; - final StackingRule itemStackingRule = item.getStackingRule(); - if (itemStackingRule.canBeStacked(itemStack, item)) { - final int itemAmount = itemStackingRule.getAmount(item); - if (itemAmount == stackingRule.getMaxSize()) - continue; - final int itemStackAmount = itemStackingRule.getAmount(itemStack); - final int totalAmount = itemStackAmount + itemAmount; - if (!stackingRule.canApply(itemStack, totalAmount)) { - item = itemStackingRule.apply(item, itemStackingRule.getMaxSize()); - - sendSlotRefresh((short) convertToPacketSlot(i), item); - itemStack = stackingRule.apply(itemStack, totalAmount - stackingRule.getMaxSize()); - } else { - item.setAmount((byte) totalAmount); - sendSlotRefresh((short) convertToPacketSlot(i), item); - return true; - } - } else if (item.isAir()) { - safeItemInsert(i, itemStack); - return true; - } - } - return false; - } - - @Override - public void clear() { - // Clear the item array - for (int i = 0; i < getSize(); i++) { - setItemStackInternal(i, ItemStack.getAirItem()); - } - // Send the cleared inventory to the inventory's owner - update(); - + public synchronized void clear() { + super.clear(); + // Reset cursor + setCursorItem(ItemStack.AIR); // Update equipments this.player.sendPacketToViewersAndSelf(player.getEquipmentsPacket()); } @Override - public int getSize() { - return INVENTORY_SIZE; + public int getInnerSize() { + return INNER_INVENTORY_SIZE; } @NotNull @@ -214,20 +127,11 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler * Refreshes the player inventory by sending a {@link WindowItemsPacket} containing all. * the inventory items */ + @Override public void update() { player.getPlayerConnection().sendPacket(createWindowItemsPacket()); } - /** - * Refreshes only a specific slot with the updated item stack data. - * - * @param slot the slot to refresh - */ - public void refreshSlot(short slot) { - final int packetSlot = convertToPacketSlot(slot); - sendSlotRefresh((short) packetSlot, getItemStack(slot)); - } - /** * Gets the item in player cursor. * @@ -261,6 +165,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler * @throws IllegalArgumentException if the slot {@code slot} does not exist * @throws NullPointerException if {@code itemStack} is null */ + @Override protected synchronized void safeItemInsert(int slot, @NotNull ItemStack itemStack) { Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()), "The slot " + slot + " does not exist for player"); @@ -295,7 +200,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler } } - this.items[slot] = itemStack; + this.itemStacks[slot] = itemStack; // Sync equipment if (equipmentSlot != null) { @@ -308,10 +213,6 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler //refreshSlot((short) slot); } - protected void setItemStackInternal(int slot, ItemStack itemStack) { - items[slot] = itemStack; - } - /** * Sets an item from a packet slot. * @@ -319,7 +220,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler * @param offset offset (generally 9 to ignore armor and craft slots) * @param itemStack the item stack to set */ - protected void setItemStack(int slot, int offset, ItemStack itemStack) { + protected void setItemStack(int slot, int offset, @NotNull ItemStack itemStack) { final int convertedSlot = convertPlayerInventorySlot(slot, offset); setItemStack(convertedSlot, itemStack); } @@ -333,7 +234,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler */ protected ItemStack getItemStack(int slot, int offset) { final int convertedSlot = convertPlayerInventorySlot(slot, offset); - return this.items[convertedSlot]; + return this.itemStacks[convertedSlot]; } /** @@ -359,9 +260,9 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler private WindowItemsPacket createWindowItemsPacket() { ItemStack[] convertedSlots = new ItemStack[INVENTORY_SIZE]; - for (int i = 0; i < items.length; i++) { + for (int i = 0; i < itemStacks.length; i++) { final int slot = convertToPacketSlot(i); - convertedSlots[slot] = items[i]; + convertedSlots[slot] = itemStacks[i]; } WindowItemsPacket windowItemsPacket = new WindowItemsPacket(); @@ -418,7 +319,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler public boolean drop(@NotNull Player player, int mode, int slot, int button) { final ItemStack cursor = getCursorItem(); final boolean outsideDrop = slot == -999; - final ItemStack clicked = outsideDrop ? ItemStack.getAirItem() : getItemStack(slot, OFFSET); + final ItemStack clicked = outsideDrop ? ItemStack.AIR : getItemStack(slot, OFFSET); final InventoryClickResult clickResult = clickProcessor.drop(null, player, mode, slot, button, clicked, cursor); @@ -441,7 +342,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler final boolean hotBarClick = convertToPacketSlot(slot) < 9; final InventoryClickResult clickResult = clickProcessor.shiftClick(null, player, slot, clicked, cursor, - new InventoryClickLoopHandler(0, items.length, 1, + new InventoryClickLoopHandler(0, itemStacks.length, 1, i -> { if (hotBarClick) { return i < 9 ? i + 9 : i - 9; @@ -492,7 +393,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler @Override public boolean dragging(@NotNull Player player, int slot, int button) { final ItemStack cursor = getCursorItem(); - final ItemStack clicked = slot != -999 ? getItemStack(slot, OFFSET) : ItemStack.getAirItem(); + final ItemStack clicked = slot != -999 ? getItemStack(slot, OFFSET) : ItemStack.AIR; final InventoryClickResult clickResult = clickProcessor.dragging(null, player, slot, button, @@ -516,9 +417,9 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler final ItemStack cursor = getCursorItem(); final InventoryClickResult clickResult = clickProcessor.doubleClick(null, player, slot, cursor, - new InventoryClickLoopHandler(0, items.length, 1, + new InventoryClickLoopHandler(0, itemStacks.length, 1, i -> i < 9 ? i + 9 : i - 9, - index -> items[index], + index -> itemStacks[index], this::setItemStack)); if (clickResult == null) @@ -531,15 +432,4 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler return !clickResult.isCancel(); } - - @Nullable - @Override - public Data getData() { - return data; - } - - @Override - public void setData(@Nullable Data data) { - this.data = data; - } } diff --git a/src/main/java/net/minestom/server/inventory/TransactionOption.java b/src/main/java/net/minestom/server/inventory/TransactionOption.java new file mode 100644 index 000000000..0fa871d87 --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/TransactionOption.java @@ -0,0 +1,47 @@ +package net.minestom.server.inventory; + +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +@FunctionalInterface +public interface TransactionOption { + + /** + * Place as much as the item as possible. + *

+ * The remaining, can be air. + */ + TransactionOption ALL = (inventory, result, itemChangesMap) -> { + itemChangesMap.forEach(inventory::safeItemInsert); + return result; + }; + + /** + * Only place the item if can be fully added. + *

+ * Returns true if the item has been added, false if nothing changed. + */ + TransactionOption ALL_OR_NOTHING = (inventory, result, itemChangesMap) -> { + if (result.isAir()) { + // Item can be fully placed inside the inventory, do so + itemChangesMap.forEach(inventory::safeItemInsert); + return true; + } else { + // Inventory cannot accept the item fully + return false; + } + }; + + /** + * Loop through the inventory items without changing anything. + *

+ * Returns true if the item can be fully added, false otherwise. + */ + TransactionOption DRY_RUN = (inventory, result, itemChangesMap) -> result.isAir(); + + @NotNull T fill(@NotNull AbstractInventory inventory, + @NotNull ItemStack result, + @NotNull Map<@NotNull Integer, @NotNull ItemStack> itemChangesMap); +} diff --git a/src/main/java/net/minestom/server/inventory/TransactionType.java b/src/main/java/net/minestom/server/inventory/TransactionType.java new file mode 100644 index 000000000..fa0bafc67 --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/TransactionType.java @@ -0,0 +1,102 @@ +package net.minestom.server.inventory; + +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.StackingRule; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * Represents a type of transaction that you can apply to an {@link AbstractInventory}. + */ +@FunctionalInterface +public interface TransactionType { + + /** + * Adds an item to the inventory. + * Can either take an air slot or be stacked. + */ + TransactionType ADD = (inventory, itemStack) -> { + Int2ObjectMap itemChangesMap = new Int2ObjectOpenHashMap<>(); + + final StackingRule stackingRule = itemStack.getStackingRule(); + + // Check filled slot (not air) + for (int i = 0; i < inventory.getInnerSize(); i++) { + ItemStack inventoryItem = inventory.getItemStack(i); + if (inventoryItem.isAir()) { + continue; + } + if (stackingRule.canBeStacked(itemStack, inventoryItem)) { + final int itemAmount = stackingRule.getAmount(inventoryItem); + if (itemAmount == stackingRule.getMaxSize()) + continue; + final int itemStackAmount = stackingRule.getAmount(itemStack); + final int totalAmount = itemStackAmount + itemAmount; + if (!stackingRule.canApply(itemStack, totalAmount)) { + // Slot cannot accept the whole item, reduce amount to 'itemStack' + itemChangesMap.put(i, stackingRule.apply(inventoryItem, stackingRule.getMaxSize())); + itemStack = stackingRule.apply(itemStack, totalAmount - stackingRule.getMaxSize()); + } else { + // Slot can accept the whole item + itemChangesMap.put(i, stackingRule.apply(inventoryItem, totalAmount)); + itemStack = stackingRule.apply(itemStack, 0); + break; + } + } + } + + // Check air slot to fill + for (int i = 0; i < inventory.getInnerSize(); i++) { + ItemStack inventoryItem = inventory.getItemStack(i); + if (!inventoryItem.isAir()) { + continue; + } + // Fill the slot + itemChangesMap.put(i, itemStack); + itemStack = stackingRule.apply(itemStack, 0); + break; + } + + return Pair.of(itemStack, itemChangesMap); + }; + + /** + * Takes an item from the inventory. + * Can either transform items to air or reduce their amount. + */ + TransactionType TAKE = (inventory, itemStack) -> { + Int2ObjectMap itemChangesMap = new Int2ObjectOpenHashMap<>(); + final StackingRule stackingRule = itemStack.getStackingRule(); + for (int i = 0; i < inventory.getInnerSize(); i++) { + ItemStack inventoryItem = inventory.getItemStack(i); + if (inventoryItem.isAir()) { + continue; + } + if (stackingRule.canBeStacked(itemStack, inventoryItem)) { + final int itemAmount = stackingRule.getAmount(inventoryItem); + final int itemStackAmount = stackingRule.getAmount(itemStack); + if (itemStackAmount < itemAmount) { + itemChangesMap.put(i, stackingRule.apply(inventoryItem, itemAmount - itemStackAmount)); + itemStack = stackingRule.apply(itemStack, 0); + break; + } + itemChangesMap.put(i, stackingRule.apply(inventoryItem, 0)); + itemStack = stackingRule.apply(itemStack, itemStackAmount - itemAmount); + if (stackingRule.getAmount(itemStack) == 0) { + itemStack = stackingRule.apply(itemStack, 0); + break; + } + } + } + + return Pair.of(itemStack, itemChangesMap); + }; + + @NotNull Pair> process(@NotNull AbstractInventory inventory, + @NotNull ItemStack itemStack); + +} diff --git a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java index 949029f78..d18b2e7af 100644 --- a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java +++ b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java @@ -91,29 +91,22 @@ public class InventoryClickProcessor { ItemStack resultClicked; if (clickedRule.canBeStacked(clicked, cursor)) { - final int amount = clicked.getAmount() + 1; + final int amount = clickedRule.getAmount(clicked) + 1; if (!clickedRule.canApply(clicked, amount)) { return clickResult; } else { - resultCursor = cursorRule.apply(cursor, cursorRule.getAmount(cursor) - 1); + resultCursor = cursorRule.apply(cursor, operand -> operand - 1); resultClicked = clickedRule.apply(clicked, amount); } } else { if (cursor.isAir()) { - final int amount = (int) Math.ceil((double) clicked.getAmount() / 2d); - resultCursor = clicked.clone(); - resultCursor = cursorRule.apply(resultCursor, amount); - - resultClicked = clicked.clone(); - resultClicked = clickedRule.apply(resultClicked, clicked.getAmount() / 2); + final int amount = (int) Math.ceil((double) clickedRule.getAmount(clicked) / 2d); + resultCursor = cursorRule.apply(clicked, amount); + resultClicked = clickedRule.apply(clicked, operand -> operand / 2); } else { if (clicked.isAir()) { - final int amount = cursor.getAmount(); - resultCursor = cursor.clone(); - resultCursor = cursorRule.apply(resultCursor, amount - 1); - - resultClicked = cursor.clone(); - resultClicked = clickedRule.apply(resultClicked, 1); + resultCursor = cursorRule.apply(cursor, operand -> operand - 1); + resultClicked = clickedRule.apply(cursor, 1); } else { resultCursor = clicked; resultClicked = cursor; @@ -155,11 +148,11 @@ public class InventoryClickProcessor { if (clicked.isAir()) { // Set held item [key] to slot resultClicked = cursor; - resultHeld = ItemStack.getAirItem(); + resultHeld = ItemStack.AIR; } else { if (cursor.isAir()) { // if held item [key] is air then set clicked to held - resultClicked = ItemStack.getAirItem(); + resultClicked = ItemStack.AIR; } else { // Otherwise replace held item and held resultClicked = cursor; @@ -189,7 +182,7 @@ public class InventoryClickProcessor { final StackingRule clickedRule = clicked.getStackingRule(); boolean filled = false; - ItemStack resultClicked = clicked.clone(); + ItemStack resultClicked = clicked; for (InventoryClickLoopHandler loopHandler : loopHandlers) { final Int2IntFunction indexModifier = loopHandler.getIndexModifier(); @@ -242,7 +235,7 @@ public class InventoryClickProcessor { // Switch itemSetter.accept(index, resultClicked); - itemSetter.accept(slot, ItemStack.getAirItem()); + itemSetter.accept(slot, ItemStack.AIR); filled = true; break; } @@ -287,16 +280,16 @@ public class InventoryClickProcessor { int finalCursorAmount = cursorAmount; for (int s : slots) { - final ItemStack draggedItem = cursor.clone(); ItemStack slotItem = itemGetter.apply(s); clickResult = startCondition(inventory, player, s, ClickType.DRAGGING, slotItem, cursor); if (clickResult.isCancel()) break; + StackingRule slotItemRule = slotItem.getStackingRule(); final int maxSize = stackingRule.getMaxSize(); - if (stackingRule.canBeStacked(draggedItem, slotItem)) { - final int amount = slotItem.getAmount() + slotSize; + if (stackingRule.canBeStacked(cursor, slotItem)) { + final int amount = slotItemRule.getAmount(slotItem) + slotSize; if (stackingRule.canApply(slotItem, amount)) { slotItem = stackingRule.apply(slotItem, amount); finalCursorAmount -= slotSize; @@ -306,7 +299,7 @@ public class InventoryClickProcessor { finalCursorAmount -= removedAmount; } } else if (slotItem.isAir()) { - slotItem = stackingRule.apply(draggedItem, slotSize); + slotItem = stackingRule.apply(cursor, slotSize); finalCursorAmount -= slotSize; } itemSetter.accept(s, slotItem); @@ -327,15 +320,16 @@ public class InventoryClickProcessor { if (size > cursorAmount) return null; for (int s : slots) { - ItemStack draggedItem = cursor.clone(); + ItemStack draggedItem = cursor; ItemStack slotItem = itemGetter.apply(s); clickResult = startCondition(inventory, player, s, ClickType.DRAGGING, slotItem, cursor); if (clickResult.isCancel()) break; + StackingRule slotItemRule = slotItem.getStackingRule(); if (stackingRule.canBeStacked(draggedItem, slotItem)) { - final int amount = slotItem.getAmount() + 1; + final int amount = slotItemRule.getAmount(slotItem) + 1; if (stackingRule.canApply(slotItem, amount)) { slotItem = stackingRule.apply(slotItem, amount); itemSetter.accept(s, slotItem); @@ -377,7 +371,7 @@ public class InventoryClickProcessor { @Nullable public InventoryClickResult doubleClick(@Nullable Inventory inventory, @NotNull Player player, int slot, @NotNull ItemStack cursor, @NotNull InventoryClickLoopHandler... loopHandlers) { - InventoryClickResult clickResult = startCondition(inventory, player, slot, ClickType.START_DOUBLE_CLICK, ItemStack.getAirItem(), cursor); + InventoryClickResult clickResult = startCondition(inventory, player, slot, ClickType.START_DOUBLE_CLICK, ItemStack.AIR, cursor); if (clickResult.isCancel()) { return clickResult; @@ -446,8 +440,8 @@ public class InventoryClickProcessor { final StackingRule clickedRule = clicked.getStackingRule(); final StackingRule cursorRule = cursor.getStackingRule(); - ItemStack resultClicked = clicked.clone(); - ItemStack resultCursor = cursor.clone(); + ItemStack resultClicked = clicked; + ItemStack resultCursor = cursor; if (slot == -999) { @@ -455,7 +449,7 @@ public class InventoryClickProcessor { if (button == 0) { // Left (drop all) final int amount = cursorRule.getAmount(resultCursor); - final ItemStack dropItem = cursorRule.apply(resultCursor.clone(), amount); + final ItemStack dropItem = cursorRule.apply(resultCursor, amount); final boolean dropResult = player.dropItem(dropItem); clickResult.setCancel(!dropResult); if (dropResult) { @@ -463,7 +457,7 @@ public class InventoryClickProcessor { } } else if (button == 1) { // Right (drop 1) - final ItemStack dropItem = cursorRule.apply(resultCursor.clone(), 1); + final ItemStack dropItem = cursorRule.apply(resultCursor, 1); final boolean dropResult = player.dropItem(dropItem); clickResult.setCancel(!dropResult); if (dropResult) { @@ -476,7 +470,7 @@ public class InventoryClickProcessor { } else if (mode == 4) { if (button == 0) { // Drop key Q (drop 1) - final ItemStack dropItem = cursorRule.apply(resultClicked.clone(), 1); + final ItemStack dropItem = cursorRule.apply(resultClicked, 1); final boolean dropResult = player.dropItem(dropItem); clickResult.setCancel(!dropResult); if (dropResult) { @@ -487,7 +481,7 @@ public class InventoryClickProcessor { } else if (button == 1) { // Ctrl + Drop key Q (drop all) final int amount = cursorRule.getAmount(resultClicked); - final ItemStack dropItem = clickedRule.apply(resultClicked.clone(), amount); + final ItemStack dropItem = clickedRule.apply(resultClicked, amount); final boolean dropResult = player.dropItem(dropItem); clickResult.setCancel(!dropResult); if (dropResult) { @@ -517,7 +511,7 @@ public class InventoryClickProcessor { // Call ItemStack#onInventoryClick { - clickResult.getClicked().onInventoryClick(player, clickType, slot, isPlayerInventory); + //clickResult.getClicked().onInventoryClick(player, clickType, slot, isPlayerInventory); } // Reset the didCloseInventory field @@ -578,6 +572,7 @@ public class InventoryClickProcessor { @NotNull ClickType clickType, @NotNull ItemStack clicked, @NotNull ItemStack cursor) { InventoryClickEvent inventoryClickEvent = new InventoryClickEvent(inventory, player, slot, clickType, clicked, cursor); player.callEvent(InventoryClickEvent.class, inventoryClickEvent); + System.out.println("click"); } public void clearCache(@NotNull Player player) { diff --git a/src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java b/src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java index 5a5ace268..121adade1 100644 --- a/src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java +++ b/src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java @@ -1,11 +1,11 @@ package net.minestom.server.inventory.condition; import net.minestom.server.entity.Player; -import net.minestom.server.inventory.InventoryModifier; +import net.minestom.server.inventory.AbstractInventory; import net.minestom.server.inventory.click.ClickType; /** - * Can be added to any {@link InventoryModifier} + * Can be added to any {@link AbstractInventory} * using {@link net.minestom.server.inventory.Inventory#addInventoryCondition(InventoryCondition)} * or {@link net.minestom.server.inventory.PlayerInventory#addInventoryCondition(InventoryCondition)} * in order to listen to any issued clicks. diff --git a/src/main/java/net/minestom/server/item/ItemDisplay.java b/src/main/java/net/minestom/server/item/ItemDisplay.java deleted file mode 100644 index c927059a9..000000000 --- a/src/main/java/net/minestom/server/item/ItemDisplay.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.minestom.server.item; - -import net.kyori.adventure.text.Component; -import net.minestom.server.chat.JsonMessage; - -public class ItemDisplay { - - private Component displayName; - private Component[] lore; - - /** - * @deprecated Use {@link #ItemDisplay(Component, Component[])} - */ - @Deprecated - public ItemDisplay(JsonMessage displayName, JsonMessage[] lore) { - this.displayName = displayName.asComponent(); - this.lore = new Component[lore.length]; - - for (int i = 0; i < lore.length; i++) { - this.lore[i] = lore[i].asComponent(); - } - } - - public ItemDisplay(Component displayName, Component[] lore) { - this.displayName = displayName; - this.lore = lore; - } - - /** - * Gets the item display name. - * - * @return the item display name - * @deprecated Use {@link #getDisplayName()} - */ - @Deprecated - public JsonMessage getDisplayNameJson() { - return JsonMessage.fromComponent(displayName); - } - - /** - * Gets the item lore. - * - * @return the item lore - * @deprecated Use {@link #getLore()} - */ - @Deprecated - public JsonMessage[] getLoreJson() { - JsonMessage[] loreOld = new JsonMessage[lore.length]; - for (int i = 0; i < lore.length; i++) { - loreOld[i] = JsonMessage.fromComponent(lore[i]); - } - return loreOld; - } - - /** - * Gets the item display name. - * - * @return the item display name - */ - public Component getDisplayName() { - return displayName; - } - - /** - * Gets the item lore. - * - * @return the item lore - */ - public Component[] getLore() { - return lore; - } -} diff --git a/src/main/java/net/minestom/server/item/ItemFlag.java b/src/main/java/net/minestom/server/item/ItemHideFlag.java similarity index 66% rename from src/main/java/net/minestom/server/item/ItemFlag.java rename to src/main/java/net/minestom/server/item/ItemHideFlag.java index c4a3d55de..5d05cbb28 100644 --- a/src/main/java/net/minestom/server/item/ItemFlag.java +++ b/src/main/java/net/minestom/server/item/ItemHideFlag.java @@ -1,9 +1,9 @@ package net.minestom.server.item; /** - * Represents a flag which can be applied to an {@link ItemStack} using {@link ItemStack#addItemFlags(ItemFlag...)}. + * Represents a hide flag which can be applied to an {@link ItemStack} using {@link ItemMetaBuilder#hideFlag(int)}. */ -public enum ItemFlag { +public enum ItemHideFlag { HIDE_ENCHANTS(1), HIDE_ATTRIBUTES(2), HIDE_UNBREAKABLE(4), @@ -13,7 +13,7 @@ public enum ItemFlag { private final int bitFieldPart; - ItemFlag(int bit) { + ItemHideFlag(int bit) { this.bitFieldPart = bit; } diff --git a/src/main/java/net/minestom/server/item/ItemMeta.java b/src/main/java/net/minestom/server/item/ItemMeta.java new file mode 100644 index 000000000..6eb11fffd --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemMeta.java @@ -0,0 +1,165 @@ +package net.minestom.server.item; + +import net.kyori.adventure.text.Component; +import net.minestom.server.instance.block.Block; +import net.minestom.server.item.attribute.ItemAttribute; +import net.minestom.server.utils.binary.BinaryWriter; +import net.minestom.server.utils.binary.Writeable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +import java.util.*; +import java.util.function.Consumer; + +public class ItemMeta implements Writeable { + + private final int damage; + private final boolean unbreakable; + private final int hideFlag; + private final Component displayName; + private final List lore; + + private final Map enchantmentMap; + private final List attributes; + + private final int customModelData; + + private final Set canDestroy; + private final Set canPlaceOn; + + private final NBTCompound nbt; + private final ItemMetaBuilder emptyBuilder; + + private String cachedSNBT; + private BinaryWriter cachedBuffer; + + protected ItemMeta(@NotNull ItemMetaBuilder metaBuilder) { + this.damage = metaBuilder.damage; + this.unbreakable = metaBuilder.unbreakable; + this.hideFlag = metaBuilder.hideFlag; + this.displayName = metaBuilder.displayName; + this.lore = new ArrayList<>(metaBuilder.lore); + this.enchantmentMap = new HashMap<>(metaBuilder.enchantmentMap); + this.attributes = new ArrayList<>(metaBuilder.attributes); + this.customModelData = metaBuilder.customModelData; + this.canDestroy = new HashSet<>(metaBuilder.canDestroy); + this.canPlaceOn = new HashSet<>(metaBuilder.canPlaceOn); + + this.nbt = metaBuilder.nbt; + this.emptyBuilder = metaBuilder.getSupplier().get(); + } + + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemMeta with(@NotNull Consumer<@NotNull ItemMetaBuilder> builderConsumer) { + var builder = builder(); + builderConsumer.accept(builder); + return builder.build(); + } + + @Contract(pure = true) + public int getDamage() { + return damage; + } + + @Contract(pure = true) + public boolean isUnbreakable() { + return unbreakable; + } + + @Contract(pure = true) + public int getHideFlag() { + return hideFlag; + } + + @Contract(pure = true) + public @Nullable Component getDisplayName() { + return displayName; + } + + @Contract(pure = true) + public @NotNull List<@NotNull Component> getLore() { + return Collections.unmodifiableList(lore); + } + + @Contract(pure = true) + public @NotNull Map getEnchantmentMap() { + return Collections.unmodifiableMap(enchantmentMap); + } + + @Contract(pure = true) + public @NotNull List<@NotNull ItemAttribute> getAttributes() { + return Collections.unmodifiableList(attributes); + } + + @Contract(pure = true) + public int getCustomModelData() { + return customModelData; + } + + @Contract(pure = true) + public @NotNull Set<@NotNull Block> getCanDestroy() { + return Collections.unmodifiableSet(canDestroy); + } + + @Contract(pure = true) + public @NotNull Set<@NotNull Block> getCanPlaceOn() { + return Collections.unmodifiableSet(canPlaceOn); + } + + @Contract(pure = true) + public T getOrDefault(@NotNull ItemTag tag, @Nullable T defaultValue) { + var key = tag.getKey(); + if (nbt.containsKey(key)) { + return tag.read(toNBT()); + } else { + return defaultValue; + } + } + + public @Nullable T get(@NotNull ItemTag tag) { + return tag.read(toNBT()); + } + + public @NotNull NBTCompound toNBT() { + return nbt.deepClone(); + } + + public @NotNull String toSNBT() { + if (cachedSNBT == null) { + this.cachedSNBT = nbt.toSNBT(); + } + return cachedSNBT; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ItemMeta itemMeta = (ItemMeta) o; + return nbt.equals(itemMeta.nbt); + } + + @Override + public int hashCode() { + return nbt.hashCode(); + } + + @Contract(value = "-> new", pure = true) + protected @NotNull ItemMetaBuilder builder() { + return ItemMetaBuilder.fromNBT(emptyBuilder, nbt); + } + + @Override + public synchronized void write(@NotNull BinaryWriter writer) { + if (cachedBuffer == null) { + BinaryWriter w = new BinaryWriter(); + w.writeNBT("", nbt); + this.cachedBuffer = w; + } + writer.write(cachedBuffer); + cachedBuffer.getBuffer().resetReaderIndex(); + } +} diff --git a/src/main/java/net/minestom/server/item/ItemMetaBuilder.java b/src/main/java/net/minestom/server/item/ItemMetaBuilder.java new file mode 100644 index 000000000..d7c871b39 --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemMetaBuilder.java @@ -0,0 +1,271 @@ +package net.minestom.server.item; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minestom.server.adventure.AdventureSerializer; +import net.minestom.server.instance.block.Block; +import net.minestom.server.item.attribute.ItemAttribute; +import net.minestom.server.utils.NBTUtils; +import net.minestom.server.utils.Utils; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.*; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class ItemMetaBuilder { + + protected NBTCompound nbt = new NBTCompound(); + + protected int damage; + protected boolean unbreakable; + protected int hideFlag; + protected Component displayName; + protected List lore = new ArrayList<>(); + protected Map enchantmentMap = new HashMap<>(); + protected List attributes = new ArrayList<>(); + protected int customModelData; + protected Set canDestroy = new HashSet<>(); + protected Set canPlaceOn = new HashSet<>(); + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder damage(int damage) { + this.damage = damage; + this.nbt.setInt("Damage", damage); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder unbreakable(boolean unbreakable) { + this.unbreakable = unbreakable; + this.nbt.setByte("Unbreakable", (byte) (unbreakable ? 1 : 0)); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder hideFlag(int hideFlag) { + this.hideFlag = hideFlag; + this.nbt.setInt("HideFlags", hideFlag); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder hideFlag(@NotNull ItemHideFlag... hideFlags) { + int result = 0; + for (ItemHideFlag hideFlag : hideFlags) { + result |= hideFlag.getBitFieldPart(); + } + return hideFlag(result); + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder displayName(@Nullable Component displayName) { + this.displayName = displayName; + handleCompound("display", nbtCompound -> { + if (displayName != null) { + final String name = AdventureSerializer.serialize(displayName); + nbtCompound.setString("Name", name); + } else { + nbtCompound.removeTag("Name"); + } + }); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder lore(@NotNull List<@NotNull Component> lore) { + this.lore = lore; + handleCompound("display", nbtCompound -> { + final NBTList loreNBT = new NBTList<>(NBTTypes.TAG_String); + for (Component line : lore) { + loreNBT.add(new NBTString(GsonComponentSerializer.gson().serialize(line))); + } + nbtCompound.set("Lore", loreNBT); + }); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder lore(Component... lore) { + lore(Arrays.asList(lore)); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder enchantments(@NotNull Map enchantments) { + this.enchantmentMap = enchantments; + handleMap(enchantmentMap, "Enchantments", nbt, () -> { + NBTUtils.writeEnchant(nbt, "Enchantments", enchantmentMap); + return nbt.get("Enchantments"); + }); + return this; + } + + @Contract("_, _ -> this") + public @NotNull ItemMetaBuilder enchantment(@NotNull Enchantment enchantment, short level) { + this.enchantmentMap.put(enchantment, level); + enchantments(enchantmentMap); + return this; + } + + @Contract("-> this") + public @NotNull ItemMetaBuilder clearEnchantment() { + this.enchantmentMap.clear(); + enchantments(enchantmentMap); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder attributes(@NotNull List<@NotNull ItemAttribute> attributes) { + this.attributes = attributes; + + handleCollection(attributes, "AttributeModifiers", nbt, () -> { + NBTList attributesNBT = new NBTList<>(NBTTypes.TAG_Compound); + for (ItemAttribute itemAttribute : attributes) { + final UUID uuid = itemAttribute.getUuid(); + attributesNBT.add( + new NBTCompound() + .setIntArray("UUID", Utils.uuidToIntArray(uuid)) + .setDouble("Amount", itemAttribute.getValue()) + .setString("Slot", itemAttribute.getSlot().name().toLowerCase()) + .setString("AttributeName", itemAttribute.getAttribute().getKey()) + .setInt("Operation", itemAttribute.getOperation().getId()) + .setString("Name", itemAttribute.getInternalName()) + ); + } + return attributesNBT; + }); + + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder customModelData(int customModelData) { + this.customModelData = customModelData; + this.nbt.setInt("CustomModelData", customModelData); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder canPlaceOn(@NotNull Set<@NotNull Block> blocks) { + this.canPlaceOn = blocks; + handleCollection(canPlaceOn, "CanPlaceOn", nbt, () -> { + NBTList list = new NBTList<>(NBTTypes.TAG_String); + canPlaceOn.forEach(block -> list.add(new NBTString(block.getName()))); + nbt.set("CanPlaceOn", list); + return list; + }); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder canPlaceOn(@NotNull Block... blocks) { + return canPlaceOn(Set.of(blocks)); + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder canDestroy(@NotNull Set<@NotNull Block> blocks) { + this.canDestroy = blocks; + handleCollection(canDestroy, "CanDestroy", nbt, () -> { + NBTList list = new NBTList<>(NBTTypes.TAG_String); + canDestroy.forEach(block -> list.add(new NBTString(block.getName()))); + nbt.set("CanDestroy", list); + return list; + }); + return this; + } + + @Contract("_ -> this") + public @NotNull ItemMetaBuilder canDestroy(@NotNull Block... blocks) { + return canDestroy(Set.of(blocks)); + } + + public @NotNull ItemMetaBuilder set(@NotNull ItemTag tag, @Nullable T value) { + if (value != null) { + tag.write(nbt, value); + } else { + this.nbt.removeTag(tag.getKey()); + } + return this; + } + + @Contract("-> new") + public abstract @NotNull ItemMeta build(); + + public abstract void read(@NotNull NBTCompound nbtCompound); + + protected abstract @NotNull Supplier<@NotNull ItemMetaBuilder> getSupplier(); + + protected void handleCompound(@NotNull String key, + @NotNull Consumer<@NotNull NBTCompound> consumer) { + NBTCompound compound = null; + boolean newNbt = false; + if (nbt.containsKey(key)) { + NBT dNbt = nbt.get(key); + if (dNbt instanceof NBTCompound) { + compound = (NBTCompound) dNbt; + } + } else { + compound = new NBTCompound(); + newNbt = true; + } + + if (compound != null) { + consumer.accept(compound); + + if (newNbt && compound.getSize() > 0) { + this.nbt.set(key, compound); + } else if (!newNbt && compound.getSize() == 0) { + this.nbt.removeTag(key); + } + + } + } + + protected void handleNullable(@Nullable Object value, + @NotNull String key, + @NotNull NBTCompound nbtCompound, + @NotNull Supplier<@NotNull NBT> supplier) { + if (value != null) { + nbtCompound.set(key, supplier.get()); + } else { + nbtCompound.removeTag(key); + } + } + + protected void handleCollection(@NotNull Collection objects, + @NotNull String key, + @NotNull NBTCompound nbtCompound, + @NotNull Supplier<@NotNull NBT> supplier) { + if (!objects.isEmpty()) { + nbtCompound.set(key, supplier.get()); + } else { + nbtCompound.removeTag(key); + } + } + + protected void handleMap(@NotNull Map objects, + @NotNull String key, + @NotNull NBTCompound nbtCompound, + @NotNull Supplier<@NotNull NBT> supplier) { + if (!objects.isEmpty()) { + nbtCompound.set(key, supplier.get()); + } else { + nbtCompound.removeTag(key); + } + } + + @Contract(value = "_, _ -> new", pure = true) + public static @NotNull ItemMetaBuilder fromNBT(@NotNull ItemMetaBuilder src, @NotNull NBTCompound nbtCompound) { + ItemMetaBuilder dest = src.getSupplier().get(); + NBTUtils.loadDataIntoMeta(dest, nbtCompound); + return dest; + } + + public interface Provider { + } + +} diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 756662cca..50a6b37e1 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -1,903 +1,180 @@ package net.minestom.server.item; -import it.unimi.dsi.fastutil.objects.Object2ShortMap; -import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.event.HoverEvent.ShowItem; import net.kyori.adventure.text.event.HoverEventSource; -import net.minestom.server.MinecraftServer; -import net.minestom.server.chat.JsonMessage; -import net.minestom.server.data.Data; -import net.minestom.server.data.DataContainer; -import net.minestom.server.entity.ItemEntity; -import net.minestom.server.entity.Player; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.PlayerInventory; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.item.attribute.ItemAttribute; -import net.minestom.server.item.metadata.*; import net.minestom.server.item.rule.VanillaStackingRule; -import net.minestom.server.network.packet.server.play.SetSlotPacket; -import net.minestom.server.registry.Registries; -import net.minestom.server.utils.BlockPosition; -import net.minestom.server.utils.Direction; import net.minestom.server.utils.NBTUtils; -import net.minestom.server.utils.clone.PublicCloneable; -import net.minestom.server.utils.ownership.OwnershipHandler; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import java.util.*; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.IntUnaryOperator; import java.util.function.UnaryOperator; -import java.util.stream.Collectors; - -// TODO should we cache a ByteBuf of this item for faster packet write /** - * Represents an item in an inventory ({@link PlayerInventory}, {@link Inventory}) or on the ground ({@link ItemEntity}). + * Represents an immutable item to be placed inside {@link net.minestom.server.inventory.PlayerInventory}, + * {@link net.minestom.server.inventory.Inventory} or even on the ground {@link net.minestom.server.entity.ItemEntity}. *

- * An item stack cannot be null, you can however use {@link #getAirItem()} instead. - *

- * WARNING: all setters will not update the item automatically, it will need to be refreshed manually. - * Here a non-exhaustive list of what you can do to update the item: - * {@link PlayerInventory#refreshSlot(short)}, {@link Inventory#refreshSlot(short)} or a raw {@link SetSlotPacket}. + * An item stack cannot be null, {@link ItemStack#AIR} should be used instead. */ -public class ItemStack implements DataContainer, PublicCloneable, HoverEventSource { +public class ItemStack implements HoverEventSource { - public static final OwnershipHandler DATA_OWNERSHIP = new OwnershipHandler<>(); - public static final String OWNERSHIP_DATA_KEY = "ownership_identifier"; - private static final StackingRule VANILLA_STACKING_RULE = new VanillaStackingRule(64); + /** + * Constant AIR item. Should be used instead of 'null'. + */ + public static final @NotNull ItemStack AIR = ItemStack.of(Material.AIR); - private final UUID identifier; + private final StackingRule stackingRule = new VanillaStackingRule(64); - private Material material; + private final Material material; + private final int amount; + private final ItemMeta meta; - private static StackingRule defaultStackingRule; - private ItemMeta itemMeta; - - private byte amount; - private int damage; - - private Component displayName; - private boolean unbreakable; - private List lore; - - private Object2ShortMap enchantmentMap; - private List attributes; - - private int hideFlag; - private int customModelData; - - private StackingRule stackingRule; - private Data data; - - private Set canDestroy; - private Set canPlaceOn; - - { - if (defaultStackingRule == null) - defaultStackingRule = VANILLA_STACKING_RULE; - this.stackingRule = defaultStackingRule; - } - - public ItemStack(@NotNull Material material, byte amount, int damage) { - this.identifier = DATA_OWNERSHIP.generateIdentifier(); + protected ItemStack(@NotNull Material material, int amount, + @NotNull ItemMeta meta) { this.material = material; this.amount = amount; - this.damage = damage; - this.lore = new ArrayList<>(); - - this.enchantmentMap = new Object2ShortOpenHashMap<>(); - this.attributes = new ArrayList<>(); - - this.canDestroy = new HashSet<>(); - this.canPlaceOn = new HashSet<>(); - - this.itemMeta = findMeta(); + this.meta = meta; } - public ItemStack(@NotNull Material material, byte amount) { - this(material, amount, (short) 0); + @Contract(value = "_ -> new", pure = true) + public static @NotNull ItemStackBuilder builder(@NotNull Material material) { + return new ItemStackBuilder(material); } - public ItemStack(@NotNull Material material) { - this(material, (byte) 1, (short) 0); + @Contract(value = "_ ,_ -> new", pure = true) + public static @NotNull ItemStack of(@NotNull Material material, int amount) { + return builder(material).amount(amount).build(); } - /** - * Gets a new {@link ItemStack} with the material sets to {@link Material#AIR}. - *

- * Used when you require a "null item". - * - * @return an air item - */ - @NotNull - public static ItemStack getAirItem() { - return new ItemStack(Material.AIR, (byte) 0); + @Contract(value = "_ -> new", pure = true) + public static @NotNull ItemStack of(@NotNull Material material) { + return of(material, 1); } - /** - * Gets the default {@link StackingRule} for newly created {@link ItemStack}. - * - * @return the default stacking rule - */ - @NotNull - public static StackingRule getDefaultStackingRule() { - return defaultStackingRule; + @Contract(pure = true) + public @NotNull Material getMaterial() { + return material; } - /** - * Changes the default stacking rule for created item stack. - * - * @param defaultStackingRule the default item stack - * @throws NullPointerException if {@code defaultStackingRule} is null - */ - public static void setDefaultStackingRule(@NotNull StackingRule defaultStackingRule) { - ItemStack.defaultStackingRule = defaultStackingRule; + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemStack with(@NotNull Consumer<@NotNull ItemStackBuilder> builderConsumer) { + var builder = builder(); + builderConsumer.accept(builder); + return builder.build(); } - /** - * Loads an {@link ItemStack} from nbt. - * - * @param nbt the nbt compound containing the item - * @return the parsed item stack - */ - @NotNull - public static ItemStack fromNBT(@NotNull NBTCompound nbt) { - if (!nbt.containsKey("id") || !nbt.containsKey("Count")) - throw new IllegalArgumentException("Invalid item NBT, must at least contain 'id' and 'Count' tags"); - final Material material = Registries.getMaterial(nbt.getString("id")); - final byte count = nbt.getAsByte("Count"); - - ItemStack s = new ItemStack(material, count); - - NBTCompound tag = nbt.getCompound("tag"); - if (tag != null) { - NBTUtils.loadDataIntoItem(s, tag); - } - return s; + @Contract(pure = true) + public int getAmount() { + return amount; } - /** - * Gets if the item material is {@link Material#AIR}. - * - * @return true if the material is air, false otherwise - */ + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemStack withAmount(int amount) { + return builder().amount(amount).build(); + } + + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemStack withAmount(@NotNull IntUnaryOperator intUnaryOperator) { + return withAmount(intUnaryOperator.applyAsInt(amount)); + } + + @Contract(value = "_, _ -> new", pure = true) + public > @NotNull ItemStack withMeta(Class metaType, Consumer metaConsumer) { + return builder().meta(metaType, metaConsumer).build(); + } + + @Contract(value = "_ -> new", pure = true) + public @NotNull ItemStack withMeta(@NotNull UnaryOperator<@NotNull ItemMetaBuilder> metaOperator) { + return builder().meta(metaOperator).build(); + } + + @Contract(pure = true) + public @Nullable Component getDisplayName() { + return meta.getDisplayName(); + } + + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemStack withDisplayName(@Nullable Component displayName) { + return builder().displayName(displayName).build(); + } + + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) { + return withDisplayName(componentUnaryOperator.apply(getDisplayName())); + } + + @Contract(pure = true) + public @NotNull List<@NotNull Component> getLore() { + return meta.getLore(); + } + + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemStack withLore(@NotNull List<@NotNull Component> lore) { + return builder().lore(lore).build(); + } + + @Contract(value = "_, -> new", pure = true) + public @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) { + return withLore(loreUnaryOperator.apply(getLore())); + } + + @Contract(pure = true) + public @NotNull StackingRule getStackingRule() { + return stackingRule; + } + + @Contract(pure = true) + public @NotNull ItemMeta getMeta() { + return meta; + } + + @Contract(pure = true) public boolean isAir() { - return material == Material.AIR; + return material.equals(Material.AIR); } - /** - * Gets if two items are similar. - * It does not take {@link #getAmount()} and {@link #getStackingRule()} in consideration. - * - * @param itemStack The ItemStack to compare to - * @return true if both items are similar - */ + @Contract(pure = true) public boolean isSimilar(@NotNull ItemStack itemStack) { - synchronized (ItemStack.class) { - if (itemStack.getIdentifier().equals(identifier)) { - return true; - } - - final boolean displayNameCheck = Objects.equals(displayName, itemStack.displayName); - final boolean loreCheck = Objects.equals(lore, itemStack.lore); - - final Data itemData = itemStack.getData(); - final boolean dataCheck = (data == null && itemData == null) || - (data != null && data.equals(itemData)); - - final boolean sameMeta = (itemStack.itemMeta == null && itemMeta == null) || - (itemStack.itemMeta != null && itemMeta != null && (itemStack.itemMeta.isSimilar(itemMeta))); - - return itemStack.getMaterial() == material && - displayNameCheck && - loreCheck && - itemStack.isUnbreakable() == unbreakable && - itemStack.getDamage() == damage && - itemStack.enchantmentMap.equals(enchantmentMap) && - itemStack.attributes.equals(attributes) && - itemStack.hideFlag == hideFlag && - sameMeta && - dataCheck && - itemStack.canPlaceOn.equals(canPlaceOn) && - itemStack.canDestroy.equals(canDestroy); - } + return material.equals(itemStack.material) && + meta.equals(itemStack.meta); } @Override public boolean equals(Object o) { - return o instanceof ItemStack && - isSimilar((ItemStack) o) && ((ItemStack) o).getAmount() == getAmount(); - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; - /** - * Checks if this item can be placed on the block. - * This should be enforced only for adventure mode players. - * - * @param block the block's namespaceID - * @return true if it can be placed, false otherwise - */ - public boolean canPlaceOn(String block) { - return canPlaceOn.contains(block); - } + ItemStack itemStack = (ItemStack) o; - /** - * Gets the blocks that this item can be placed on - * - * @return the {@link Set} of blocks - */ - public Set getCanPlaceOn() { - return canPlaceOn; - } - - /** - * Checks if this item is allowed to break the provided block. - * This should be enforced only for adventure mode players. - * - * @param block the block's namespaceID - * @return true if this item can destroy it, otherwise false - */ - public boolean canDestroy(String block) { - return canDestroy.contains(block); - } - - /** - * Gets the blocks that this item can destroy - * - * @return the {@link Set} of blocks - */ - public Set getCanDestroy() { - return canDestroy; - } - - /** - * Gets the item damage (durability). - * - * @return the item damage - */ - public int getDamage() { - return damage; - } - - /** - * Sets the item damage (durability). - * - * @param damage the item damage - */ - public void setDamage(int damage) { - this.damage = damage; - } - - /** - * Gets the item amount. - *

- * WARNING: for amount computation it would be better to use {@link StackingRule#getAmount(ItemStack)} - * to support all stacking implementation. - * - * @return the item amount - */ - public byte getAmount() { - return amount; - } - - /** - * Changes the item amount. - *

- * WARNING: for amount computation it would be better to use {@link StackingRule#getAmount(ItemStack)} - * to support all stacking implementation. - * - * @param amount the new item amount - */ - public void setAmount(byte amount) { - this.amount = amount; - } - - /** - * Gets the special meta object for this item. - *

- * Can be null if not any. - * - * @return the item meta - */ - @Nullable - public ItemMeta getItemMeta() { - return itemMeta; - } - - /** - * Changes the item meta linked to this item. - *

- * WARNING: be sure to have nbt data useful for this item, items should automatically get the appropriate - * item meta. - * - * @param itemMeta the new item meta - */ - public void setItemMeta(@Nullable ItemMeta itemMeta) { - this.itemMeta = itemMeta; - } - - /** - * Gets the item display name. - * - * @return the item display name, can be null if not present - * @deprecated Use {@link #getDisplayName()} - */ - @Deprecated - @Nullable - public JsonMessage getDisplayNameJson() { - return JsonMessage.fromComponent(displayName); - } - - /** - * Gets the item display name. - * - * @return the item display name, can be null if not present - */ - @Nullable - public Component getDisplayName() { - return displayName; - } - - /** - * Sets the item display name. - * - * @param displayName the item display name - * @deprecated Use {@link #setDisplayName(Component)} - */ - @Deprecated - public void setDisplayName(@Nullable JsonMessage displayName) { - this.setDisplayName(displayName == null ? null : displayName.asComponent()); - } - - /** - * Sets the item display name. - * - * @param displayName the item display name - */ - public void setDisplayName(@Nullable Component displayName) { - this.displayName = displayName; - } - - /** - * Gets if the item has a display name. - * - * @return the item display name - */ - public boolean hasDisplayName() { - return displayName != null; - } - - /** - * Gets the item lore. - * - * @return a modifiable list containing the item lore, can be empty if not present - * @deprecated Use {@link #getLore()} - */ - @Deprecated - @NotNull - public List getLoreJson() { - return lore.stream().map(JsonMessage::fromComponent).collect(Collectors.toList()); - } - - /** - * Gets the item lore. - * - * @return a modifiable list containing the item lore, can be empty if not present - */ - @NotNull - public List getLore() { - return lore; - } - - /** - * Sets the item lore. - * - * @param lore the item lore, can be empty to remove - * @deprecated Use {@link #setLore} - */ - @Deprecated - public void setLoreJson(@NotNull List lore) { - this.lore = lore.stream().map(JsonMessage::asComponent).collect(Collectors.toList()); - } - - /** - * Sets the item lore. - * - * @param lore the item lore, can be empty to remove - */ - @NotNull - public void setLore(List lore) { - this.lore = lore; - } - - /** - * Gets if the item has a lore. - * - * @return true if the item has lore, false otherwise - */ - public boolean hasLore() { - return lore != null && !lore.isEmpty(); - } - - /** - * Gets the item enchantment map. - * - * @return an unmodifiable map containing the item enchantments - */ - @NotNull - public Map getEnchantmentMap() { - return Collections.unmodifiableMap(enchantmentMap); - } - - /** - * Sets an enchantment level. - * - * @param enchantment the enchantment type - * @param level the enchantment level - */ - public void setEnchantment(@NotNull Enchantment enchantment, short level) { - if (level < 1) { - removeEnchantment(enchantment); - return; - } - - this.enchantmentMap.put(enchantment, level); - } - - /** - * Removes an enchantment. - * - * @param enchantment the enchantment type - */ - public void removeEnchantment(@NotNull Enchantment enchantment) { - this.enchantmentMap.removeShort(enchantment); - } - - /** - * Gets an enchantment level. - * - * @param enchantment the enchantment type - * @return the stored enchantment level, 0 if not present - */ - public int getEnchantmentLevel(@NotNull Enchantment enchantment) { - return this.enchantmentMap.getOrDefault(enchantment, (short) 0); - } - - /** - * Gets the item attributes. - * - * @return an unmodifiable {@link List} containing the item attributes - */ - @NotNull - public List getAttributes() { - return Collections.unmodifiableList(attributes); - } - - /** - * Gets the {@link ItemAttribute} with the specified internal name. - * - * @param internalName the internal name of the attribute - * @return the {@link ItemAttribute} with the internal name, null if not found - */ - public ItemAttribute getAttribute(@NotNull String internalName) { - for (ItemAttribute itemAttribute : attributes) { - if (itemAttribute.getInternalName().equals(internalName)) - return itemAttribute; - } - return null; - } - - /** - * Adds an attribute to the item. - * - * @param itemAttribute the attribute to add - */ - public void addAttribute(@NotNull ItemAttribute itemAttribute) { - this.attributes.add(itemAttribute); - } - - /** - * Removes an attribute to the item. - * - * @param itemAttribute the attribute to remove - */ - public void removeAttribute(@NotNull ItemAttribute itemAttribute) { - this.attributes.remove(itemAttribute); - } - - /** - * Gets the item hide flag. - * - * @return the item hide flag - */ - public int getHideFlag() { - return hideFlag; - } - - /** - * Changes the item hide flag. This is the integer sent when updating the item hide flag. - * - * @param hideFlag the new item hide flag - */ - public void setHideFlag(int hideFlag) { - this.hideFlag = hideFlag; - } - - /** - * Gets the item custom model data. - * - * @return the item custom model data - */ - public int getCustomModelData() { - return customModelData; - } - - /** - * Changes the item custom model data. - * - * @param customModelData the new item custom data model - */ - public void setCustomModelData(int customModelData) { - this.customModelData = customModelData; - } - - /** - * Adds flags to the item. - * - * @param flags the flags to add - */ - public void addItemFlags(@NotNull ItemFlag... flags) { - for (ItemFlag f : flags) { - this.hideFlag |= getBitModifier(f); - } - } - - /** - * Removes flags from the item. - * - * @param flags the flags to remove - */ - public void removeItemFlags(@NotNull ItemFlag... flags) { - for (ItemFlag f : flags) { - this.hideFlag &= ~getBitModifier(f); - } - } - - /** - * Gets the item flags. - * - * @return an unmodifiable {@link Set} containing the item flags - */ - @NotNull - public Set getItemFlags() { - Set currentFlags = EnumSet.noneOf(ItemFlag.class); - - for (ItemFlag f : ItemFlag.values()) { - if (hasItemFlag(f)) { - currentFlags.add(f); - } - } - - return Collections.unmodifiableSet(currentFlags); - } - - /** - * Gets if the item has an item flag. - * - * @param flag the item flag - * @return true if the item has the flag {@code flag}, false otherwise - */ - public boolean hasItemFlag(@NotNull ItemFlag flag) { - final int bitModifier = getBitModifier(flag); - return (this.hideFlag & bitModifier) == bitModifier; - } - - /** - * Gets if the item is unbreakable. - * - * @return true if the item is unbreakable, false otherwise - */ - public boolean isUnbreakable() { - return unbreakable; - } - - /** - * Makes the item unbreakable. - * - * @param unbreakable true to make the item unbreakable, false otherwise - */ - public void setUnbreakable(boolean unbreakable) { - this.unbreakable = unbreakable; - } - - /** - * Gets the unique identifier of this object. - *

- * This value is non persistent and will be randomized once this item is separated with a right-click, - * when copied and when the server restart. It is used internally by the data ownership system. - * - * @return this item unique identifier - */ - @NotNull - public UUID getIdentifier() { - return identifier; - } - - /** - * Gets the item {@link Material}. - * - * @return the item material - */ - @NotNull - public Material getMaterial() { - return material; - } - - /** - * Changes the item {@link Material}. - * - * @param material the new material - */ - public void setMaterial(@NotNull Material material) { - this.material = material; - } - - /** - * Gets if the item has any nbt tag. - * - * @return true if the item has nbt tag, false otherwise - */ - public boolean hasNbtTag() { - return hasDisplayName() || - hasLore() || - damage != 0 || - isUnbreakable() || - !enchantmentMap.isEmpty() || - !attributes.isEmpty() || - hideFlag != 0 || - customModelData != 0 || - (itemMeta != null && itemMeta.hasNbt()) || - (data != null && !data.isEmpty()) || - !canDestroy.isEmpty() || - !canPlaceOn.isEmpty(); - } - - /** - * Clones this item stack. - *

- * Be aware that the identifier ({@link #getIdentifier()}) will change. - * - * @return a cloned item stack with a different identifier - */ - @NotNull - @Override - public ItemStack clone() { - try { - ItemStack itemStack = (ItemStack) super.clone(); - itemStack.setDisplayName(displayName); - itemStack.setUnbreakable(unbreakable); - if (lore != null) { - itemStack.setLore(new ArrayList<>(lore)); - } - if (stackingRule != null) { - itemStack.setStackingRule(stackingRule); - } - - itemStack.enchantmentMap = new Object2ShortOpenHashMap<>(enchantmentMap); - itemStack.attributes = new ArrayList<>(attributes); - - itemStack.hideFlag = hideFlag; - itemStack.customModelData = customModelData; - - itemStack.canPlaceOn = new HashSet<>(canPlaceOn); - itemStack.canDestroy = new HashSet<>(canDestroy); - - if (itemMeta != null) - itemStack.itemMeta = itemMeta.clone(); - - final Data data = getData(); - if (data != null) - itemStack.setData(data.clone()); - - return itemStack; - } catch (CloneNotSupportedException e) { - MinecraftServer.getExceptionManager().handleException(e); - return null; - } - } - - @Nullable - @Override - public Data getData() { - return data; - } - - /** - * Sets the data of this item. - * - * @param data the new {@link Data} of this container, null to remove it - */ - @Override - public void setData(@Nullable Data data) { - DATA_OWNERSHIP.saveOwnObject(getIdentifier(), data); - this.data = data; - } - - /** - * Gets the item {@link StackingRule}. - * - * @return the item stacking rule - */ - @NotNull - public StackingRule getStackingRule() { - return stackingRule; - } - - /** - * Changes the {@link StackingRule} of the item. - * - * @param stackingRule the new item stacking rule - * @throws NullPointerException if {@code stackingRule} is null - */ - public void setStackingRule(@NotNull StackingRule stackingRule) { - this.stackingRule = stackingRule; - } - - /** - * Consumes this item by a specific amount. - *

- * Will return null if the amount's amount isn't enough. - * - * @param amount the quantity to consume - * @return the new item with the updated amount, null if the item cannot be consumed by this much - */ - @Nullable - public ItemStack consume(int amount) { - final int currentAmount = stackingRule.getAmount(this); - if (currentAmount < amount) - return null; - return stackingRule.apply(this, currentAmount - amount); - } - - private byte getBitModifier(@NotNull ItemFlag hideFlag) { - return (byte) (1 << hideFlag.ordinal()); - } - - /** - * Finds the {@link ItemMeta} based on the material type. - * - * @return the item meta, null if none found - */ - @Nullable - private ItemMeta findMeta() { - if (material == Material.POTION || - material == Material.LINGERING_POTION || - material == Material.SPLASH_POTION || - material == Material.TIPPED_ARROW) - return new PotionMeta(); - - if (material == Material.FILLED_MAP) - return new MapMeta(); - - if (material == Material.COMPASS) - return new CompassMeta(); - - if (material == Material.ENCHANTED_BOOK) - return new EnchantedBookMeta(); - - if (material == Material.CROSSBOW) - return new CrossbowMeta(); - - if (material == Material.WRITABLE_BOOK) - return new WritableBookMeta(); - - if (material == Material.WRITTEN_BOOK) - return new WrittenBookMeta(); - - if (material == Material.FIREWORK_STAR) - return new FireworkEffectMeta(); - - if (material == Material.FIREWORK_ROCKET) - return new FireworkMeta(); - - if (material == Material.PLAYER_HEAD) - return new PlayerHeadMeta(); - - if (material == Material.LEATHER_HELMET || - material == Material.LEATHER_CHESTPLATE || - material == Material.LEATHER_LEGGINGS || - material == Material.LEATHER_BOOTS) - return new LeatherArmorMeta(); - - return null; - } - - /** - * Creates a {@link NBTCompound} containing the data of this item. - *

- * WARNING: modifying the returned nbt will not affect the item. - * - * @return this item nbt - */ - @NotNull - public NBTCompound toNBT() { - NBTCompound compound = new NBTCompound() - .setByte("Count", amount) - .setString("id", material.getName()); - if (hasNbtTag()) { - NBTCompound additionalTag = new NBTCompound(); - NBTUtils.saveDataIntoNBT(this, additionalTag); - compound.set("tag", additionalTag); - } - return compound; - } - - /** - * WARNING: not implemented yet. - *

- * This is be called each time an item is serialized to be send to a player, - * can be used to customize the display of the item based on player data. - * - * @param player the player - * @return the custom {@link ItemDisplay} for {@code player}, - * null to use the normal item display name & lore - */ - public ItemDisplay getCustomDisplay(Player player) { - throw new UnsupportedOperationException("Not implemented yet"); + if (amount != itemStack.amount) return false; + if (!stackingRule.equals(itemStack.stackingRule)) return false; + if (material != itemStack.material) return false; + return meta.equals(itemStack.meta); } @Override - public @NotNull HoverEvent asHoverEvent(@NotNull UnaryOperator op) { - return HoverEvent.showItem(op.apply(ShowItem.of(this.material, this.amount, NBTUtils.asBinaryTagHolder(this.toNBT().getCompound("tag"))))); + public int hashCode() { + int result = stackingRule.hashCode(); + result = 31 * result + material.hashCode(); + result = 31 * result + amount; + result = 31 * result + meta.hashCode(); + return result; } - // Callback events - - /** - * Called when the player right clicks with this item. - * - * @param player the player who used the item - * @param hand the hand used - */ - public void onRightClick(@NotNull Player player, @NotNull Player.Hand hand) { + @Contract(value = "-> new", pure = true) + protected @NotNull ItemStackBuilder builder() { + return new ItemStackBuilder(material, meta.builder()) + .amount(amount); } - /** - * Called when the player left clicks with this item. - * - * @param player the player who used the item - * @param hand the hand used - */ - public void onLeftClick(@NotNull Player player, @NotNull Player.Hand hand) { - } - - /** - * Called when the player right clicks with this item on a block. - * - * @param player the player who used the item - * @param hand the hand used - * @param position the position of the interacted block - * @param blockFace the block face - * @return true if it prevents normal item use (placing blocks for instance) - */ - public boolean onUseOnBlock(@NotNull Player player, @NotNull Player.Hand hand, @NotNull BlockPosition position, @NotNull Direction blockFace) { - return false; - } - - /** - * Called when the player click on this item on an inventory. - *

- * Executed before any events. - * - * @param player the player who clicked on the item - * @param clickType the click type - * @param slot the slot clicked - * @param playerInventory true if the click is in the player inventory - */ - public void onInventoryClick(@NotNull Player player, @NotNull ClickType clickType, int slot, boolean playerInventory) { - + @Override + public @NotNull HoverEvent asHoverEvent(@NotNull UnaryOperator op) { + return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(this.material, + this.amount, + NBTUtils.asBinaryTagHolder(this.meta.toNBT().getCompound("tag"))))); } } diff --git a/src/main/java/net/minestom/server/item/ItemStackBuilder.java b/src/main/java/net/minestom/server/item/ItemStackBuilder.java new file mode 100644 index 000000000..71547a751 --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemStackBuilder.java @@ -0,0 +1,122 @@ +package net.minestom.server.item; + +import net.kyori.adventure.text.Component; +import net.minestom.server.item.metadata.*; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +public class ItemStackBuilder { + + private final Material material; + private int amount; + protected ItemMetaBuilder metaBuilder; + + protected ItemStackBuilder(@NotNull Material material, @NotNull ItemMetaBuilder metaBuilder) { + this.material = material; + this.amount = 1; + this.metaBuilder = metaBuilder; + } + + private static final Map> MATERIAL_SUPPLIER_MAP = new ConcurrentHashMap<>(); + + static { + MATERIAL_SUPPLIER_MAP.put(Material.POTION, PotionMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.LINGERING_POTION, PotionMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.SPLASH_POTION, PotionMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.TIPPED_ARROW, PotionMeta.Builder::new); + + MATERIAL_SUPPLIER_MAP.put(Material.FILLED_MAP, MapMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.COMPASS, CompassMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.ENCHANTED_BOOK, EnchantedBookMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.CROSSBOW, CrossbowMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.WRITABLE_BOOK, WritableBookMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.WRITTEN_BOOK, WrittenBookMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.FIREWORK_STAR, FireworkEffectMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.FIREWORK_ROCKET, FireworkMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.PLAYER_HEAD, PlayerHeadMeta.Builder::new); + + MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_HELMET, LeatherArmorMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_CHESTPLATE, LeatherArmorMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_LEGGINGS, LeatherArmorMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_BOOTS, LeatherArmorMeta.Builder::new); + } + + protected ItemStackBuilder(@NotNull Material material) { + this(material, + MATERIAL_SUPPLIER_MAP.getOrDefault(material, DefaultMeta::new).get()); + } + + @Contract(value = "_ -> this") + public @NotNull ItemStackBuilder amount(int amount) { + this.amount = amount; + return this; + } + + @Contract(value = "_ -> this") + public @NotNull ItemStackBuilder meta(@NotNull ItemMeta itemMeta) { + this.metaBuilder = itemMeta.builder(); + return this; + } + + @Contract(value = "_ -> this") + public @NotNull ItemStackBuilder meta(@NotNull UnaryOperator<@NotNull ItemMetaBuilder> itemMetaConsumer) { + this.metaBuilder = itemMetaConsumer.apply(metaBuilder); + return this; + } + + @Contract(value = "_, _ -> this") + public > @NotNull ItemStackBuilder meta(@NotNull Class metaType, @NotNull Consumer<@NotNull T> itemMetaConsumer) { + itemMetaConsumer.accept((T) metaBuilder); + return this; + } + + @Contract(value = "_ -> this") + public @NotNull ItemStackBuilder displayName(@Nullable Component displayName) { + this.metaBuilder.displayName(displayName); + return this; + } + + @Contract(value = "_ -> this") + public @NotNull ItemStackBuilder lore(@NotNull List<@NotNull Component> lore) { + this.metaBuilder.lore(lore); + return this; + } + + @Contract(value = "_ -> this") + public @NotNull ItemStackBuilder lore(Component... lore) { + this.metaBuilder.lore(lore); + return this; + } + + @Contract(value = "-> new", pure = true) + public @NotNull ItemStack build() { + return new ItemStack(material, amount, metaBuilder.build()); + } + + private static final class DefaultMeta extends ItemMetaBuilder { + @Override + public @NotNull ItemMeta build() { + return new ItemMeta(this); + } + + @Override + public void read(@NotNull NBTCompound nbtCompound) { + // Empty + } + + @Override + protected @NotNull Supplier<@NotNull ItemMetaBuilder> getSupplier() { + return DefaultMeta::new; + } + } + +} diff --git a/src/main/java/net/minestom/server/item/ItemTag.java b/src/main/java/net/minestom/server/item/ItemTag.java new file mode 100644 index 000000000..ad5350042 --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemTag.java @@ -0,0 +1,97 @@ +package net.minestom.server.item; + +import org.jetbrains.annotations.NotNull; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class ItemTag { + + private final String key; + private final Function readFunction; + private final BiConsumer writeConsumer; + + private ItemTag(@NotNull String key, + @NotNull Function readFunction, + @NotNull BiConsumer writeConsumer) { + this.key = key; + this.readFunction = readFunction; + this.writeConsumer = writeConsumer; + } + + public @NotNull String getKey() { + return key; + } + + protected T read(@NotNull NBTCompound nbtCompound) { + return readFunction.apply(nbtCompound); + } + + protected void write(@NotNull NBTCompound nbtCompound, @NotNull T value) { + this.writeConsumer.accept(nbtCompound, value); + } + + public static @NotNull ItemTag Byte(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getByte(key), + (nbtCompound, value) -> nbtCompound.setByte(key, value)); + } + + public static @NotNull ItemTag Short(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getShort(key), + (nbtCompound, value) -> nbtCompound.setShort(key, value)); + } + + public static @NotNull ItemTag Integer(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getInt(key), + (nbtCompound, integer) -> nbtCompound.setInt(key, integer)); + } + + public static @NotNull ItemTag Long(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getLong(key), + (nbtCompound, value) -> nbtCompound.setLong(key, value)); + } + + public static @NotNull ItemTag Float(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getFloat(key), + (nbtCompound, value) -> nbtCompound.setFloat(key, value)); + } + + public static @NotNull ItemTag Double(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getDouble(key), + (nbtCompound, value) -> nbtCompound.setDouble(key, value)); + } + + public static @NotNull ItemTag ByteArray(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getByteArray(key), + (nbtCompound, value) -> nbtCompound.setByteArray(key, value)); + } + + public static @NotNull ItemTag String(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getString(key), + (nbtCompound, value) -> nbtCompound.setString(key, value)); + } + + // TODO List/Compound + + public static @NotNull ItemTag IntArray(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getIntArray(key), + (nbtCompound, value) -> nbtCompound.setIntArray(key, value)); + } + + public static @NotNull ItemTag LongArray(@NotNull String key) { + return new ItemTag<>(key, + nbtCompound -> nbtCompound.getLongArray(key), + (nbtCompound, value) -> nbtCompound.setLongArray(key, value)); + } + +} diff --git a/src/main/java/net/minestom/server/item/StackingRule.java b/src/main/java/net/minestom/server/item/StackingRule.java index 707a79016..68c22862e 100644 --- a/src/main/java/net/minestom/server/item/StackingRule.java +++ b/src/main/java/net/minestom/server/item/StackingRule.java @@ -1,7 +1,10 @@ package net.minestom.server.item; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import java.util.function.IntUnaryOperator; + /** * Represents the stacking rule of an {@link ItemStack}. * This can be used to mimic the vanilla one (using the displayed item quantity) @@ -40,10 +43,15 @@ public abstract class StackingRule { * * @param item the {@link ItemStack} to applies the size to * @param newAmount the new item size - * @return the new {@link ItemStack} with the new amount + * @return a new {@link ItemStack item} with the specified amount */ - @NotNull - public abstract ItemStack apply(@NotNull ItemStack item, int newAmount); + @Contract("_, _ -> new") + public abstract @NotNull ItemStack apply(@NotNull ItemStack item, int newAmount); + + @Contract("_, _ -> new") + public @NotNull ItemStack apply(@NotNull ItemStack item, @NotNull IntUnaryOperator amountOperator) { + return apply(item, amountOperator.applyAsInt(getAmount(item))); + } /** * Used to determine the current stack size of an {@link ItemStack}. diff --git a/src/main/java/net/minestom/server/item/metadata/CompassMeta.java b/src/main/java/net/minestom/server/item/metadata/CompassMeta.java index da5424d51..a046f0597 100644 --- a/src/main/java/net/minestom/server/item/metadata/CompassMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/CompassMeta.java @@ -1,103 +1,107 @@ package net.minestom.server.item.metadata; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.utils.Position; -import net.minestom.server.utils.clone.CloneUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import java.util.Objects; +import java.util.function.Supplier; -public class CompassMeta extends ItemMeta { +public class CompassMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private boolean lodestoneTracked; - private String lodestoneDimension; + private final boolean lodestoneTracked; + private final String lodestoneDimension; + private final Position lodestonePosition; - private Position lodestonePosition; + protected CompassMeta(ItemMetaBuilder metaBuilder, + boolean lodestoneTracked, + @Nullable String lodestoneDimension, + @Nullable Position lodestonePosition) { + super(metaBuilder); + this.lodestoneTracked = lodestoneTracked; + this.lodestoneDimension = lodestoneDimension; + this.lodestonePosition = lodestonePosition; + } public boolean isLodestoneTracked() { return lodestoneTracked; } - public void setLodestoneTracked(boolean lodestoneTracked) { - this.lodestoneTracked = lodestoneTracked; - } - - @Nullable - public String getLodestoneDimension() { + public @Nullable String getLodestoneDimension() { return lodestoneDimension; } - public void setLodestoneDimension(@Nullable String lodestoneDimension) { - this.lodestoneDimension = lodestoneDimension; - } - - @Nullable - public Position getLodestonePosition() { + public @Nullable Position getLodestonePosition() { return lodestonePosition; } - public void setLodestonePosition(@Nullable Position lodestonePosition) { - this.lodestonePosition = lodestonePosition; - } + public static class Builder extends ItemMetaBuilder { - @Override - public boolean hasNbt() { - return true; - } + private boolean lodestoneTracked; + private String lodestoneDimension; + private Position lodestonePosition; - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof CompassMeta)) - return false; - CompassMeta compassMeta = (CompassMeta) itemMeta; - return (compassMeta.lodestoneTracked == lodestoneTracked) && - (Objects.equals(compassMeta.lodestoneDimension, lodestoneDimension)) && - (Objects.equals(compassMeta.lodestonePosition, lodestonePosition)); - } - - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("LodestoneTracked")) { - this.lodestoneTracked = compound.getByte("LodestoneTracked") == 1; - } - if (compound.containsKey("LodestoneDimension")) { - this.lodestoneDimension = compound.getString("LodestoneDimension"); - } - if (compound.containsKey("LodestonePos")) { - final NBTCompound posCompound = compound.getCompound("LodestonePos"); - final int x = posCompound.getInt("X"); - final int y = posCompound.getInt("Y"); - final int z = posCompound.getInt("Z"); - - this.lodestonePosition = new Position(x, y, z); - } - } - - @Override - public void write(@NotNull NBTCompound compound) { - compound.setByte("LodestoneTracked", (byte) (lodestoneTracked ? 1 : 0)); - if (lodestoneDimension != null) { - compound.setString("LodestoneDimension", lodestoneDimension); + public Builder lodestoneTracked(boolean lodestoneTracked) { + this.lodestoneTracked = lodestoneTracked; + this.nbt.setByte("LodestoneTracked", (byte) (lodestoneTracked ? 1 : 0)); + return this; } - if (lodestonePosition != null) { - NBTCompound posCompound = new NBTCompound(); - posCompound.setInt("X", (int) lodestonePosition.getX()); - posCompound.setInt("Y", (int) lodestonePosition.getY()); - posCompound.setInt("Z", (int) lodestonePosition.getZ()); - compound.set("LodestonePos", posCompound); + public Builder lodestoneDimension(@Nullable String lodestoneDimension) { + this.lodestoneDimension = lodestoneDimension; + + if (lodestoneDimension != null) { + this.nbt.setString("LodestoneDimension", lodestoneDimension); + } else { + this.nbt.removeTag("LodestoneDimension"); + } + + return this; } - } - @NotNull - @Override - public ItemMeta clone() { - CompassMeta compassMeta = (CompassMeta) super.clone(); - compassMeta.lodestoneTracked = lodestoneTracked; - compassMeta.lodestoneDimension = lodestoneDimension; - compassMeta.lodestonePosition = CloneUtils.optionalClone(lodestonePosition); + public Builder lodestonePosition(@Nullable Position lodestonePosition) { + this.lodestonePosition = lodestonePosition; - return compassMeta; + if (lodestonePosition != null) { + NBTCompound posCompound = new NBTCompound(); + posCompound.setInt("X", (int) lodestonePosition.getX()); + posCompound.setInt("Y", (int) lodestonePosition.getY()); + posCompound.setInt("Z", (int) lodestonePosition.getZ()); + this.nbt.set("LodestonePos", posCompound); + } else { + this.nbt.removeTag("LodestonePos"); + } + + return this; + } + + @Override + public @NotNull CompassMeta build() { + return new CompassMeta(this, lodestoneTracked, lodestoneDimension, lodestonePosition); + } + + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("LodestoneTracked")) { + lodestoneTracked(nbtCompound.getByte("LodestoneTracked") == 1); + } + if (nbtCompound.containsKey("LodestoneDimension")) { + lodestoneDimension(nbtCompound.getString("LodestoneDimension")); + } + if (nbtCompound.containsKey("LodestonePos")) { + final NBTCompound posCompound = nbtCompound.getCompound("LodestonePos"); + final int x = posCompound.getInt("X"); + final int y = posCompound.getInt("Y"); + final int z = posCompound.getInt("Z"); + lodestonePosition(new Position(x, y, z)); + } + } + + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; + } } } diff --git a/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java b/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java index 9926a41b3..7c64abcf2 100644 --- a/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java @@ -1,50 +1,37 @@ package net.minestom.server.item.metadata; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.registry.Registries; import net.minestom.server.utils.NBTUtils; -import net.minestom.server.utils.clone.CloneUtils; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTList; import org.jglrxavpok.hephaistos.nbt.NBTTypes; -public class CrossbowMeta extends ItemMeta { +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; - private boolean triple; - private ItemStack projectile1, projectile2, projectile3; +public class CrossbowMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private boolean charged; - - /** - * Sets the projectile of this crossbow. - * - * @param projectile the projectile of the crossbow - */ - public void setProjectile(@NotNull ItemStack projectile) { - Check.argCondition(projectile.isAir(), "the projectile of your crossbow isn't visible"); - this.projectile1 = projectile; - this.triple = false; - } - - /** - * Sets the triple projectiles of this crossbow. - * - * @param projectile1 the projectile 1 - * @param projectile2 the projectile 2 - * @param projectile3 the projectile 3 - */ - public void setProjectiles(@NotNull ItemStack projectile1, @NotNull ItemStack projectile2, @NotNull ItemStack projectile3) { - Check.argCondition(projectile1.isAir(), "the projectile1 of your crossbow isn't visible"); - Check.argCondition(projectile2.isAir(), "the projectile2 of your crossbow isn't visible"); - Check.argCondition(projectile3.isAir(), "the projectile3 of your crossbow isn't visible"); + private final boolean triple; + private final ItemStack projectile1, projectile2, projectile3; + private final boolean charged; + protected CrossbowMeta(@NotNull ItemMetaBuilder metaBuilder, + boolean triple, + ItemStack projectile1, ItemStack projectile2, ItemStack projectile3, + boolean charged) { + super(metaBuilder); + this.triple = triple; this.projectile1 = projectile1; this.projectile2 = projectile2; this.projectile3 = projectile3; - this.triple = true; + this.charged = charged; } /** @@ -59,27 +46,27 @@ public class CrossbowMeta extends ItemMeta { /** * Gets the first projectile. * - * @return the first projectile, null if not present + * @return the first projectile */ - public ItemStack getProjectile1() { + public @NotNull ItemStack getProjectile1() { return projectile1; } /** * Gets the second projectile. * - * @return the second projectile, null if not present + * @return the second projectile */ - public ItemStack getProjectile2() { + public @NotNull ItemStack getProjectile2() { return projectile2; } /** * Gets the third projectile. * - * @return the third projectile, null if not present + * @return the third projectile */ - public ItemStack getProjectile3() { + public @NotNull ItemStack getProjectile3() { return projectile3; } @@ -92,115 +79,112 @@ public class CrossbowMeta extends ItemMeta { return charged; } - /** - * Makes the bow charged or uncharged. - * - * @param charged true to make the crossbow charged, false otherwise - */ - public void setCharged(boolean charged) { - this.charged = charged; - } + public static class Builder extends ItemMetaBuilder { - @Override - public boolean hasNbt() { - return projectile1 != null && !projectile1.isAir(); - } + private boolean triple; + private ItemStack projectile1, projectile2, projectile3 = ItemStack.AIR; + private boolean charged; - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof CrossbowMeta)) - return false; + /** + * Sets the projectile of this crossbow. + * + * @param projectile the projectile of the crossbow, air to remove + */ + public Builder projectile(@NotNull ItemStack projectile) { + this.projectile1 = projectile; + this.triple = false; - final CrossbowMeta crossbowMeta = (CrossbowMeta) itemMeta; - final boolean checkCount = triple && crossbowMeta.triple; - if (!checkCount) - return false; + NBTList chargedProjectiles = new NBTList<>(NBTTypes.TAG_Compound); + if (!projectile.isAir()) { + chargedProjectiles.add(getItemCompound(projectile)); + } + this.nbt.set("ChargedProjectiles", chargedProjectiles); - if (projectile1.isSimilar(crossbowMeta.projectile1) && - projectile2.isSimilar(crossbowMeta.projectile2) && - projectile3.isSimilar(crossbowMeta.projectile3)) { - return true; + return this; } - return !triple && (projectile1.isSimilar(crossbowMeta.projectile1)); - } + /** + * Sets the triple projectiles of this crossbow. + * + * @param projectile1 the projectile 1 + * @param projectile2 the projectile 2 + * @param projectile3 the projectile 3 + */ + public Builder projectiles(@NotNull ItemStack projectile1, @NotNull ItemStack projectile2, @NotNull ItemStack projectile3) { + Check.argCondition(projectile1.isAir(), "the projectile1 of your crossbow isn't visible"); + Check.argCondition(projectile2.isAir(), "the projectile2 of your crossbow isn't visible"); + Check.argCondition(projectile3.isAir(), "the projectile3 of your crossbow isn't visible"); - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("ChargedProjectiles")) { - final NBTList projectilesList = compound.getList("ChargedProjectiles"); - int index = 0; - for (NBTCompound projectileCompound : projectilesList) { - final byte count = projectileCompound.getByte("Count"); - final String id = projectileCompound.getString("id"); - final Material material = Registries.getMaterial(id); + this.projectile1 = projectile1; + this.projectile2 = projectile2; + this.projectile3 = projectile3; + this.triple = true; - final NBTCompound tagsCompound = projectileCompound.getCompound("tag"); + NBTList chargedProjectiles = new NBTList<>(NBTTypes.TAG_Compound); + chargedProjectiles.add(getItemCompound(projectile1)); + chargedProjectiles.add(getItemCompound(projectile2)); + chargedProjectiles.add(getItemCompound(projectile3)); + this.nbt.set("ChargedProjectiles", chargedProjectiles); - ItemStack itemStack = new ItemStack(material, count); - NBTUtils.loadDataIntoItem(itemStack, tagsCompound); + return this; + } - index++; + /** + * Makes the bow charged or uncharged. + * + * @param charged true to make the crossbow charged, false otherwise + */ + public Builder charged(boolean charged) { + this.charged = charged; + this.nbt.setByte("Charged", (byte) (charged ? 1 : 0)); + return this; + } - if (index == 1) { - projectile1 = itemStack; - } else if (index == 2) { - projectile2 = itemStack; - } else if (index == 3) { - projectile3 = itemStack; + @Override + public @NotNull CrossbowMeta build() { + return new CrossbowMeta(this, triple, projectile1, projectile2, projectile3, charged); + } + + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("ChargedProjectiles")) { + final NBTList projectilesList = nbtCompound.getList("ChargedProjectiles"); + List projectiles = new ArrayList<>(); + for (NBTCompound projectileCompound : projectilesList) { + final byte count = projectileCompound.getByte("Count"); + final String id = projectileCompound.getString("id"); + final Material material = Registries.getMaterial(id); + + final NBTCompound tagsCompound = projectileCompound.getCompound("tag"); + + ItemStack itemStack = NBTUtils.loadItem(material, count, tagsCompound); + + projectiles.add(itemStack); + } + + if (projectiles.size() == 1) { + projectile(projectiles.get(0)); + } else if (projectiles.size() == 3) { + projectiles(projectiles.get(0), projectiles.get(1), projectiles.get(2)); } } - } - if (compound.containsKey("Charged")) { - this.charged = compound.getByte("Charged") == 1; - } - } - - @Override - public void write(@NotNull NBTCompound compound) { - if (projectile1 != null || projectile2 != null || projectile3 != null) { - NBTList chargedProjectiles = new NBTList<>(NBTTypes.TAG_Compound); - if (projectile1 != null) { - chargedProjectiles.add(getItemCompound(projectile1)); + if (nbtCompound.containsKey("Charged")) { + charged(nbtCompound.getByte("Charged") == 1); } - if (projectile2 != null) { - chargedProjectiles.add(getItemCompound(projectile2)); - } - if (projectile3 != null) { - chargedProjectiles.add(getItemCompound(projectile3)); - } - compound.set("ChargedProjectiles", chargedProjectiles); } - if (charged) { - compound.setByte("Charged", (byte) (charged ? 1 : 0)); + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; + } + + private @NotNull NBTCompound getItemCompound(@NotNull ItemStack itemStack) { + NBTCompound compound = itemStack.getMeta().toNBT(); + compound.setByte("Count", (byte) itemStack.getAmount()); + compound.setString("id", itemStack.getMaterial().getName()); + return compound; } } - - @NotNull - @Override - public ItemMeta clone() { - CrossbowMeta crossbowMeta = (CrossbowMeta) super.clone(); - crossbowMeta.triple = triple; - crossbowMeta.projectile1 = CloneUtils.optionalClone(projectile1); - crossbowMeta.projectile2 = CloneUtils.optionalClone(projectile2); - crossbowMeta.projectile3 = CloneUtils.optionalClone(projectile3); - - crossbowMeta.charged = charged; - - return crossbowMeta; - } - - @NotNull - private NBTCompound getItemCompound(@NotNull ItemStack itemStack) { - NBTCompound compound = new NBTCompound(); - - compound.setByte("Count", itemStack.getAmount()); - compound.setString("id", itemStack.getMaterial().getName()); - NBTUtils.saveDataIntoNBT(itemStack, compound); - - return compound; - } -} +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java b/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java index 7f280c8b2..3f5c218ab 100644 --- a/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java @@ -1,18 +1,25 @@ package net.minestom.server.item.metadata; -import it.unimi.dsi.fastutil.objects.Object2ShortMap; -import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap; import net.minestom.server.item.Enchantment; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.utils.NBTUtils; import org.jetbrains.annotations.NotNull; import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; -public class EnchantedBookMeta extends ItemMeta { +public class EnchantedBookMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private final Object2ShortMap storedEnchantmentMap = new Object2ShortOpenHashMap<>(); + private final Map storedEnchantmentMap; + + protected EnchantedBookMeta(@NotNull ItemMetaBuilder metaBuilder, Map storedEnchantmentMap) { + super(metaBuilder); + this.storedEnchantmentMap = new HashMap<>(storedEnchantmentMap); + } /** * Gets the stored enchantment map. @@ -20,76 +27,41 @@ public class EnchantedBookMeta extends ItemMeta { * * @return an unmodifiable map containing the item stored enchantments */ - @NotNull - public Map getStoredEnchantmentMap() { + public @NotNull Map getStoredEnchantmentMap() { return Collections.unmodifiableMap(storedEnchantmentMap); } - /** - * Sets a stored enchantment level. - * - * @param enchantment the enchantment type - * @param level the enchantment level - */ - public void setStoredEnchantment(@NotNull Enchantment enchantment, short level) { - if (level < 1) { - removeStoredEnchantment(enchantment); - return; + public static class Builder extends ItemMetaBuilder { + + private Map enchantments = new HashMap<>(); + + public @NotNull Builder enchantments(Map enchantments) { + this.enchantments = enchantments; + NBTUtils.writeEnchant(nbt, "StoredEnchantments", enchantments); + return this; } - this.storedEnchantmentMap.put(enchantment, level); - } - - /** - * Removes a stored enchantment. - * - * @param enchantment the enchantment type - */ - public void removeStoredEnchantment(@NotNull Enchantment enchantment) { - this.storedEnchantmentMap.removeShort(enchantment); - } - - /** - * Gets a stored enchantment level. - * - * @param enchantment the enchantment type - * @return the stored enchantment level, 0 if not present - */ - public int getStoredEnchantmentLevel(@NotNull Enchantment enchantment) { - return this.storedEnchantmentMap.getOrDefault(enchantment, (short) 0); - } - - @Override - public boolean hasNbt() { - return !storedEnchantmentMap.isEmpty(); - } - - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - return itemMeta instanceof EnchantedBookMeta && - ((EnchantedBookMeta) itemMeta).storedEnchantmentMap.equals(storedEnchantmentMap); - } - - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("StoredEnchantments")) { - NBTUtils.loadEnchantments(compound.getList("StoredEnchantments"), this::setStoredEnchantment); + public @NotNull Builder enchantment(Enchantment enchantment, short level) { + this.enchantments.put(enchantment, level); + enchantments(enchantments); + return this; } - } - @Override - public void write(@NotNull NBTCompound compound) { - if (!storedEnchantmentMap.isEmpty()) { - NBTUtils.writeEnchant(compound, "StoredEnchantments", storedEnchantmentMap); + @Override + public @NotNull EnchantedBookMeta build() { + return new EnchantedBookMeta(this, enchantments); } - } - @NotNull - @Override - public ItemMeta clone() { - EnchantedBookMeta enchantedBookMeta = (EnchantedBookMeta) super.clone(); - enchantedBookMeta.storedEnchantmentMap.putAll(storedEnchantmentMap); + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("StoredEnchantments")) { + NBTUtils.loadEnchantments(nbtCompound.getList("StoredEnchantments"), this::enchantment); + } + } - return enchantedBookMeta; + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; + } } } diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java index 045dee42e..1af345b36 100644 --- a/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java @@ -1,92 +1,52 @@ package net.minestom.server.item.metadata; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.item.firework.FireworkEffect; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; -public class FireworkEffectMeta extends ItemMeta { +import java.util.function.Supplier; - private FireworkEffect fireworkEffect; +public class FireworkEffectMeta extends ItemMeta implements ItemMetaBuilder.Provider { + + private final FireworkEffect fireworkEffect; + + protected FireworkEffectMeta(@NotNull ItemMetaBuilder metaBuilder, FireworkEffect fireworkEffect) { + super(metaBuilder); + this.fireworkEffect = fireworkEffect; + } - /** - * Retrieves the firework effect for this meta. - * - * @return The current firework effect, or {@code null} if none. - */ - @Nullable public FireworkEffect getFireworkEffect() { return fireworkEffect; } - /** - * Changes the {@link FireworkEffect} for this item meta. - * - * @param fireworkEffect The new firework effect, or {@code null}. - */ - public void setFireworkEffect(@Nullable FireworkEffect fireworkEffect) { - this.fireworkEffect = fireworkEffect; - } + public static class Builder extends ItemMetaBuilder { - /** - * Whether if this item meta has an effect. - * - * @return {@code true} if this item meta has an effect, otherwise {@code false}. - */ - public boolean hasFireworkEffect() { - return this.fireworkEffect != null; - } + private FireworkEffect fireworkEffect; - /** - * {@inheritDoc} - */ - @Override - public boolean hasNbt() { - return this.hasFireworkEffect(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof FireworkEffectMeta)) { - return false; + public Builder effect(@Nullable FireworkEffect fireworkEffect) { + this.fireworkEffect = fireworkEffect; + this.nbt.set("Explosion", this.fireworkEffect.asCompound()); + return this; } - FireworkEffectMeta fireworkEffectMeta = (FireworkEffectMeta) itemMeta; - return fireworkEffectMeta.fireworkEffect == this.fireworkEffect; - } - - /** - * {@inheritDoc} - */ - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("Explosion")) { - this.fireworkEffect = FireworkEffect.fromCompound(compound.getCompound("Explosion")); + @Override + public @NotNull FireworkEffectMeta build() { + return new FireworkEffectMeta(this, fireworkEffect); } - } + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("Explosion")) { + effect(FireworkEffect.fromCompound(nbtCompound.getCompound("Explosion"))); + } + } - /** - * {@inheritDoc} - */ - @Override - public void write(@NotNull NBTCompound compound) { - if (this.fireworkEffect != null) { - compound.set("Explosion", this.fireworkEffect.asCompound()); + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; } } - - /** - * {@inheritDoc} - */ - @NotNull - @Override - public ItemMeta clone() { - FireworkEffectMeta fireworkEffectMeta = (FireworkEffectMeta) super.clone(); - fireworkEffectMeta.fireworkEffect = this.fireworkEffect; - return fireworkEffectMeta; - } -} +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java index ca8d7ec5f..757bd739f 100644 --- a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java @@ -1,179 +1,92 @@ package net.minestom.server.item.metadata; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.item.firework.FireworkEffect; import org.jetbrains.annotations.NotNull; import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTList; import org.jglrxavpok.hephaistos.nbt.NBTTypes; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; -/** - * Represents a firework rocket meta data and its effects. - */ -public class FireworkMeta extends ItemMeta { +public class FireworkMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private List effects = new CopyOnWriteArrayList<>(); - private byte flightDuration; + private final List effects; + private final byte flightDuration; - /** - * Adds a firework effect to this firework. - * - * @param effect The firework effect to be added. - */ - public void addFireworkEffect(FireworkEffect effect) { - this.effects.add(effect); - } - - /** - * Adds an array of firework effects to this firework. - * - * @param effects An array of firework effects to be added. - */ - public void addFireworkEffects(FireworkEffect... effects) { - this.effects.addAll(Arrays.asList(effects)); - } - - /** - * Removes a firework effect from this firework. - * - * @param index The index of the firework effect to be removed. - * @throws IndexOutOfBoundsException If index {@literal < 0 or index >} {@link #getEffectSize()} - */ - public void removeFireworkEffect(int index) throws IndexOutOfBoundsException { - this.effects.remove(index); - } - - /** - * Removes a firework effects from this firework. - * - * @param effect The effect to be removed. - */ - public void removeFireworkEffect(FireworkEffect effect) { - this.effects.remove(effect); - } - - /** - * Retrieves a collection with all effects in this firework. - * - * @return A collection with all effects in this firework. - */ - public List getEffects() { - return effects; - } - - /** - * Retrieves the size of effects in this firework. - * - * @return The size of the effects. - */ - public int getEffectSize() { - return this.effects.size(); - } - - /** - * Removes all effects from this firework. - */ - public void clearEffects() { - this.effects.clear(); - } - - /** - * Whether this firework has any effects. - * - * @return {@code true} if this firework has any effects, otherwise {@code false}. - */ - public boolean hasEffects() { - return this.effects.isEmpty(); - } - - /** - * Changes the flight duration of this firework. - * - * @param flightDuration The new flight duration for this firework. - */ - public void setFlightDuration(byte flightDuration) { + protected FireworkMeta(@NotNull ItemMetaBuilder metaBuilder, List effects, + byte flightDuration) { + super(metaBuilder); + this.effects = new ArrayList<>(effects); this.flightDuration = flightDuration; } - /** - * Returns the flight duration of this firework - * @return the flight duration of this firework - */ + public List getEffects() { + return Collections.unmodifiableList(effects); + } + public byte getFlightDuration() { return flightDuration; } - /** - * {@inheritDoc} - */ - @Override - public boolean hasNbt() { - return this.flightDuration == 0 || !this.effects.isEmpty(); - } + public static class Builder extends ItemMetaBuilder { - /** - * {@inheritDoc} - */ - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - return false; - } + private List effects = new CopyOnWriteArrayList<>(); + private byte flightDuration; - /** - * {@inheritDoc} - */ - @Override - public void read(@NotNull NBTCompound compound) { - this.effects.clear(); - if (compound.containsKey("Fireworks")) { - NBTCompound fireworksCompound = compound.getCompound("Fireworks"); + public Builder effects(List effects) { + this.effects = effects; + handleCompound("Fireworks", nbtCompound -> { + NBTList explosions = new NBTList<>(NBTTypes.TAG_Compound); + for (FireworkEffect effect : this.effects) { + explosions.add(effect.asCompound()); + } + nbtCompound.set("Explosions", explosions); + }); + return this; + } - if (fireworksCompound.containsKey("Flight")) { - this.flightDuration = fireworksCompound.getAsByte("Flight"); - } + public Builder flightDuration(byte flightDuration) { + this.flightDuration = flightDuration; + handleCompound("Fireworks", nbtCompound -> { + nbtCompound.setByte("Flight", this.flightDuration); + }); + return this; + } - if (fireworksCompound.containsKey("Explosions")) { - NBTList explosions = fireworksCompound.getList("Explosions"); + @Override + public @NotNull FireworkMeta build() { + return new FireworkMeta(this, effects, flightDuration); + } - for (NBTCompound explosion : explosions) { - this.effects.add(FireworkEffect.fromCompound(explosion)); + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("Fireworks")) { + NBTCompound fireworksCompound = nbtCompound.getCompound("Fireworks"); + + if (fireworksCompound.containsKey("Flight")) { + flightDuration(fireworksCompound.getAsByte("Flight")); + } + + if (fireworksCompound.containsKey("Explosions")) { + NBTList explosions = fireworksCompound.getList("Explosions"); + + for (NBTCompound explosion : explosions) { + this.effects.add(FireworkEffect.fromCompound(explosion)); + } + effects(effects); } } - - } - } - - /** - * {@inheritDoc} - */ - @Override - public void write(@NotNull NBTCompound compound) { - NBTCompound fireworksCompound = new NBTCompound(); - fireworksCompound.setByte("Flight", this.flightDuration); - - NBTList explosions = new NBTList<>(NBTTypes.TAG_Compound); - for (FireworkEffect effect : this.effects) { - explosions.add(effect.asCompound()); } - fireworksCompound.set("Explosions", explosions); - - compound.set("Fireworks", fireworksCompound); + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; + } } - - /** - * {@inheritDoc} - */ - @NotNull - @Override - public ItemMeta clone() { - FireworkMeta fireworkMeta = (FireworkMeta) super.clone(); - fireworkMeta.effects = this.effects; - fireworkMeta.flightDuration = this.flightDuration; - - return fireworkMeta; - } -} +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/item/metadata/ItemMeta.java b/src/main/java/net/minestom/server/item/metadata/ItemMeta.java deleted file mode 100644 index 4e200e331..000000000 --- a/src/main/java/net/minestom/server/item/metadata/ItemMeta.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.item.ItemStack; -import net.minestom.server.utils.clone.PublicCloneable; -import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; - -/** - * Represents nbt data only available for a type of item. - */ -public abstract class ItemMeta implements PublicCloneable { - - /** - * Gets if this meta object contains any useful data to send to the client. - * - * @return true if this item has nbt data, false otherwise - */ - public abstract boolean hasNbt(); - - /** - * Gets if the two ItemMeta are similar. - *

- * It is used by {@link ItemStack#isSimilar(ItemStack)}. - * - * @param itemMeta the second item meta to check - * @return true if the two meta are similar, false otherwise - */ - public abstract boolean isSimilar(@NotNull ItemMeta itemMeta); - - /** - * Reads nbt data from a compound. - *

- * WARNING: it is possible that it contains no useful data, - * it has to be checked before getting anything. - * - * @param compound the compound containing the data - */ - public abstract void read(@NotNull NBTCompound compound); - - /** - * Writes nbt data to a compound. - * - * @param compound the compound receiving the item meta data - */ - public abstract void write(@NotNull NBTCompound compound); - - /** - * {@inheritDoc} - */ - @NotNull - @Override - public ItemMeta clone() { - try { - return (ItemMeta) super.clone(); - } catch (CloneNotSupportedException e) { - MinecraftServer.getExceptionManager().handleException(e); - throw new IllegalStateException("Weird thing happened"); - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java b/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java index 0926074aa..da392a894 100644 --- a/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java @@ -1,175 +1,61 @@ package net.minestom.server.item.metadata; -import net.minestom.server.chat.ChatColor; import net.minestom.server.color.Color; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; -/** - * Represents the item meta for leather armor parts. - */ -public class LeatherArmorMeta extends ItemMeta { - private boolean modified; - private Color color; +import java.util.function.Supplier; - /** - * Sets the color of the leather armor piece. - * - * @param color the color of the leather armor - * @deprecated Use {@link #setColor(Color)} - */ - @Deprecated - public void setColor(ChatColor color) { - this.setColor(color.asColor()); - } +public class LeatherArmorMeta extends ItemMeta implements ItemMetaBuilder.Provider { - /** - * Changes the color of the leather armor piece. - * - * @param red The red color of the leather armor piece. - * @param green The green color of the leather armor piece. - * @param blue The blue color of the leather armor piece. - * @deprecated Use {@link #setColor(Color)} - */ - @Deprecated - public void setColor(byte red, byte green, byte blue) { - this.setColor(new Color(red, green, blue)); - } + private final Color color; - /** - * Sets the color of this leather armor piece. - * - * @param color the new color - */ - public void setColor(@NotNull Color color) { - this.modified = !color.equals(this.color); + protected LeatherArmorMeta(@NotNull ItemMetaBuilder metaBuilder, @Nullable Color color) { + super(metaBuilder); this.color = color; } - /** - * Gets the color of this leather armor piece. - * - * @return the color - */ - public @NotNull Color getColor() { - return this.color; + public @Nullable Color getColor() { + return color; } - /** - * Resets the color to the default leather one. - */ - public void reset() { - this.color = new Color(0, 0, 0); - this.modified = false; - } + public static class Builder extends ItemMetaBuilder { - /** - * Gets the red component. - * - * @return the red component - * @deprecated Use {@link #getColor} - */ - @Deprecated - public int getRed() { - return this.color.getRed(); - } + private Color color; - /** - * Gets the green component. - * - * @return the green component - * @deprecated Use {@link #getColor} - */ - @Deprecated - public int getGreen() { - return this.color.getGreen(); - } + public Builder color(@Nullable Color color) { + this.color = color; + handleCompound("display", nbtCompound -> { + if (color != null) { + nbtCompound.setInt("color", color.asRGB()); + } else { + nbtCompound.removeTag("color"); + } + }); + return this; + } - /** - * Gets the blue component. - * - * @return the blue component - * @deprecated Use {@link #getColor} - */ - @Deprecated - public int getBlue() { - return this.color.getBlue(); - } + @Override + public @NotNull LeatherArmorMeta build() { + return new LeatherArmorMeta(this, color); + } - /** - * Gets if the color of this armor piece have been changed. - * - * @return true if the color has been changed, false otherwise - */ - public boolean isModified() { - return modified; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean hasNbt() { - return modified; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof LeatherArmorMeta)) return false; - final LeatherArmorMeta leatherArmorMeta = (LeatherArmorMeta) itemMeta; - return leatherArmorMeta.isModified() == isModified() - && leatherArmorMeta.getColor().equals(getColor()); - } - - /** - * {@inheritDoc} - */ - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("display")) { - final NBTCompound nbtCompound = compound.getCompound("display"); - if (nbtCompound.containsKey("color")) { - final int color = nbtCompound.getInt("color"); - - // Sets the color of the leather armor piece - // This also fixes that the armor pieces do not decolorize again when you are in creative - // mode. - this.setColor(new Color(color)); + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("display")) { + final NBTCompound displayCompound = nbtCompound.getCompound("display"); + if (displayCompound.containsKey("color")) { + color(new Color(displayCompound.getInt("color"))); + } } } - } - /** - * {@inheritDoc} - */ - @Override - public void write(@NotNull NBTCompound compound) { - if (modified) { - NBTCompound displayCompound; - if (!compound.containsKey("display")) { - displayCompound = new NBTCompound(); - } else { - displayCompound = compound.getCompound("display"); - } - displayCompound.setInt("color", color.asRGB()); - // Adds the color compound to the display compound - compound.set("display", displayCompound); + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; } } - - /** - * {@inheritDoc} - */ - @NotNull - @Override - public ItemMeta clone() { - LeatherArmorMeta leatherArmorMeta = (LeatherArmorMeta) super.clone(); - leatherArmorMeta.modified = this.isModified(); - leatherArmorMeta.color = color; - - return leatherArmorMeta; - } } diff --git a/src/main/java/net/minestom/server/item/metadata/MapMeta.java b/src/main/java/net/minestom/server/item/metadata/MapMeta.java index 7fef87752..c7068b093 100644 --- a/src/main/java/net/minestom/server/item/metadata/MapMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/MapMeta.java @@ -3,28 +3,36 @@ package net.minestom.server.item.metadata; import net.minestom.server.MinecraftServer; import net.minestom.server.chat.ChatColor; import net.minestom.server.color.Color; -import net.minestom.server.utils.clone.CloneUtils; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.utils.clone.PublicCloneable; import org.jetbrains.annotations.NotNull; import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTList; import org.jglrxavpok.hephaistos.nbt.NBTTypes; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; public class MapMeta extends ItemMeta { - private int mapId; - private int mapScaleDirection = 1; - private List decorations = new CopyOnWriteArrayList<>(); - private Color mapColor = new Color(0, 0, 0); + private final int mapId; + private final int mapScaleDirection; + private final List decorations; + private final Color mapColor; - public MapMeta() { - } - - public MapMeta(int id) { - this.mapId = id; + protected MapMeta(ItemMetaBuilder metaBuilder, + int mapId, + int mapScaleDirection, + @NotNull List decorations, + @NotNull Color mapColor) { + super(metaBuilder); + this.mapId = mapId; + this.mapScaleDirection = mapScaleDirection; + this.decorations = decorations; + this.mapColor = mapColor; } /** @@ -36,15 +44,6 @@ public class MapMeta extends ItemMeta { return mapId; } - /** - * Changes the map id. - * - * @param mapId the new map id - */ - public void setMapId(int mapId) { - this.mapId = mapId; - } - /** * Gets the map scale direction. * @@ -54,15 +53,6 @@ public class MapMeta extends ItemMeta { return mapScaleDirection; } - /** - * Changes the map scale direction. - * - * @param mapScaleDirection the new map scale direction - */ - public void setMapScaleDirection(int mapScaleDirection) { - this.mapScaleDirection = mapScaleDirection; - } - /** * Gets the map decorations. * @@ -72,15 +62,6 @@ public class MapMeta extends ItemMeta { return decorations; } - /** - * Changes the map decorations list. - * - * @param decorations the new map decorations list - */ - public void setDecorations(List decorations) { - this.decorations = decorations; - } - /** * Gets the map color. * @@ -101,94 +82,28 @@ public class MapMeta extends ItemMeta { return this.mapColor; } - /** - * Changes the map color. - * - * @param mapColor the new map color - * @deprecated Use {@link #setMapColor(Color)} - */ - @Deprecated - public void setMapColor(ChatColor mapColor) { - this.setMapColor(mapColor.asColor()); - } + public static class Builder extends ItemMetaBuilder { - /** - * Changes the map color. - * - * @param color the new map color - */ - public void setMapColor(@NotNull Color color) { - this.mapColor = color; - } + private int mapId; + private int mapScaleDirection = 1; + private List decorations = new CopyOnWriteArrayList<>(); + private Color mapColor = new Color(0, 0, 0); - @Override - public boolean hasNbt() { - return true; - } - - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof MapMeta)) - return false; - - final MapMeta mapMeta = (MapMeta) itemMeta; - return mapMeta.mapId == mapId && - mapMeta.mapScaleDirection == mapScaleDirection && - mapMeta.decorations.equals(decorations) && - mapMeta.mapColor == mapColor; - } - - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("map")) { - this.mapId = compound.getAsInt("map"); + public Builder mapId(int value) { + this.mapId = value; + this.nbt.setInt("map", mapId); + return this; } - if (compound.containsKey("map_scale_direction")) { - this.mapScaleDirection = compound.getAsInt("map_scale_direction"); + public Builder mapScaleDirection(int value) { + this.mapScaleDirection = value; + this.nbt.setInt("map_scale_direction", value); + return this; } - if (compound.containsKey("Decorations")) { - final NBTList decorationsList = compound.getList("Decorations"); - for (NBTCompound decorationCompound : decorationsList) { - final String id = decorationCompound.getString("id"); - final byte type = decorationCompound.getAsByte("type"); - byte x = 0; + public Builder decorations(List value) { + this.decorations = value; - if (decorationCompound.containsKey("x")) { - x = decorationCompound.getAsByte("x"); - } - - byte z = 0; - if (decorationCompound.containsKey("z")) { - z = decorationCompound.getAsByte("z"); - } - - double rotation = 0.0; - if (decorationCompound.containsKey("rot")) { - rotation = decorationCompound.getAsDouble("rot"); - } - - this.decorations.add(new MapDecoration(id, type, x, z, rotation)); - } - } - - if (compound.containsKey("display")) { - final NBTCompound displayCompound = compound.getCompound("display"); - if (displayCompound.containsKey("MapColor")) { - this.mapColor = new Color(displayCompound.getAsInt("MapColor")); - } - } - - } - - @Override - public void write(@NotNull NBTCompound compound) { - compound.setInt("map", mapId); - - compound.setInt("map_scale_direction", mapScaleDirection); - - if (!decorations.isEmpty()) { NBTList decorationsList = new NBTList<>(NBTTypes.TAG_Compound); for (MapDecoration decoration : decorations) { NBTCompound decorationCompound = new NBTCompound(); @@ -200,29 +115,80 @@ public class MapMeta extends ItemMeta { decorationsList.add(decorationCompound); } - compound.set("Decorations", decorationsList); + this.nbt.set("Decorations", decorationsList); + + return this; } - { + public Builder mapColor(Color value) { + this.mapColor = value; + NBTCompound displayCompound; - if (compound.containsKey("display")) { - displayCompound = compound.getCompound("display"); + if (nbt.containsKey("display")) { + displayCompound = nbt.getCompound("display"); } else { displayCompound = new NBTCompound(); + this.nbt.set("display", displayCompound); } displayCompound.setInt("MapColor", mapColor.asRGB()); - } - } - @NotNull - @Override - public ItemMeta clone() { - MapMeta mapMeta = (MapMeta) super.clone(); - mapMeta.setMapId(mapId); - mapMeta.setMapScaleDirection(mapScaleDirection); - mapMeta.decorations = CloneUtils.cloneCopyOnWriteArrayList(decorations); - mapMeta.setMapColor(mapColor); - return mapMeta; + return this; + } + + @Override + public @NotNull ItemMeta build() { + return new MapMeta(this, mapId, mapScaleDirection, decorations, mapColor); + } + + @Override + public void read(@NotNull NBTCompound compound) { + if (compound.containsKey("map")) { + mapId(compound.getAsInt("map")); + } + + if (compound.containsKey("map_scale_direction")) { + mapScaleDirection(compound.getAsInt("map_scale_direction")); + } + + if (compound.containsKey("Decorations")) { + final NBTList decorationsList = compound.getList("Decorations"); + List mapDecorations = new ArrayList<>(); + for (NBTCompound decorationCompound : decorationsList) { + final String id = decorationCompound.getString("id"); + final byte type = decorationCompound.getAsByte("type"); + byte x = 0; + + if (decorationCompound.containsKey("x")) { + x = decorationCompound.getAsByte("x"); + } + + byte z = 0; + if (decorationCompound.containsKey("z")) { + z = decorationCompound.getAsByte("z"); + } + + double rotation = 0.0; + if (decorationCompound.containsKey("rot")) { + rotation = decorationCompound.getAsDouble("rot"); + } + + mapDecorations.add(new MapDecoration(id, type, x, z, rotation)); + } + decorations(mapDecorations); + } + + if (compound.containsKey("display")) { + final NBTCompound displayCompound = compound.getCompound("display"); + if (displayCompound.containsKey("MapColor")) { + mapColor(new Color(displayCompound.getAsInt("MapColor"))); + } + } + } + + @Override + protected @NotNull Supplier<@NotNull ItemMetaBuilder> getSupplier() { + return Builder::new; + } } public static class MapDecoration implements PublicCloneable { diff --git a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java index 18f1ae07e..10d8f0a06 100644 --- a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java @@ -1,7 +1,8 @@ package net.minestom.server.item.metadata; -import net.minestom.server.entity.Player; import net.minestom.server.entity.PlayerSkin; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.utils.Utils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -10,152 +11,85 @@ import org.jglrxavpok.hephaistos.nbt.NBTList; import org.jglrxavpok.hephaistos.nbt.NBTTypes; import java.util.UUID; +import java.util.function.Supplier; -/** - * Represents a skull that can have an owner. - */ -public class PlayerHeadMeta extends ItemMeta { +public class PlayerHeadMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private UUID skullOwner; - private PlayerSkin playerSkin; + private final UUID skullOwner; + private final PlayerSkin playerSkin; - /** - * Sets the owner of the skull. - * - * @param player The new owner of the skull. - * @return {@code true} if the owner was successfully set, otherwise {@code false}. - */ - public boolean setOwningPlayer(@NotNull Player player) { - if (player.getSkin() != null) { - this.skullOwner = player.getUuid(); - this.playerSkin = player.getSkin(); - return true; - } - return false; + protected PlayerHeadMeta(@NotNull ItemMetaBuilder metaBuilder, UUID skullOwner, + PlayerSkin playerSkin) { + super(metaBuilder); + this.skullOwner = skullOwner; + this.playerSkin = playerSkin; } - /** - * Retrieves the owner of the head. - * - * @return The head's owner. - */ - @Nullable public UUID getSkullOwner() { return skullOwner; } - /** - * Changes the owner of the head. - * - * @param skullOwner The new head owner. - */ - public void setSkullOwner(@NotNull UUID skullOwner) { - this.skullOwner = skullOwner; - } - - /** - * Retrieves the skin of the head. - * - * @return The head's skin. - */ - @Nullable public PlayerSkin getPlayerSkin() { return playerSkin; } - /** - * Changes the skin of the head. - * - * @param playerSkin The new skin for the head. - */ - public void setPlayerSkin(@NotNull PlayerSkin playerSkin) { - this.playerSkin = playerSkin; - } + public static class Builder extends ItemMetaBuilder { - /** - * {@inheritDoc} - */ - @Override - public boolean hasNbt() { - return this.skullOwner != null || playerSkin != null; - } + private UUID skullOwner; + private PlayerSkin playerSkin; - /** - * {@inheritDoc} - */ - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof PlayerHeadMeta)) - return false; - final PlayerHeadMeta playerHeadMeta = (PlayerHeadMeta) itemMeta; - return playerHeadMeta.playerSkin == playerSkin; - } + public Builder skullOwner(@Nullable UUID skullOwner) { + this.skullOwner = skullOwner; + handleCompound("SkullOwner", nbtCompound -> { + nbtCompound.setIntArray("Id", Utils.uuidToIntArray(this.skullOwner)); + }); + return this; + } - /** - * {@inheritDoc} - */ - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("SkullOwner")) { - NBTCompound skullOwnerCompound = compound.getCompound("SkullOwner"); + public Builder playerSkin(@Nullable PlayerSkin playerSkin) { + this.playerSkin = playerSkin; + handleCompound("SkullOwner", nbtCompound -> { + NBTList textures = new NBTList<>(NBTTypes.TAG_Compound); + String value = this.playerSkin.getTextures() == null ? "" : this.playerSkin.getTextures(); + String signature = this.playerSkin.getSignature() == null ? "" : this.playerSkin.getSignature(); + textures.add(new NBTCompound().setString("Value", value).setString("Signature", signature)); + nbtCompound.set("Properties", new NBTCompound().set("textures", textures)); + }); + return this; + } - if (skullOwnerCompound.containsKey("Id")) { - this.skullOwner = Utils.intArrayToUuid(skullOwnerCompound.getIntArray("Id")); - } + @Override + public @NotNull PlayerHeadMeta build() { + return new PlayerHeadMeta(this, skullOwner, playerSkin); + } - if (skullOwnerCompound.containsKey("Properties")) { - NBTCompound propertyCompound = skullOwnerCompound.getCompound("Properties"); + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("SkullOwner")) { + NBTCompound skullOwnerCompound = nbtCompound.getCompound("SkullOwner"); - if (propertyCompound.containsKey("textures")) { - NBTList textures = propertyCompound.getList("textures"); - if (textures != null) { - NBTCompound nbt = textures.get(0); - this.playerSkin = new PlayerSkin(nbt.getString("Value"), nbt.getString("Signature")); - } + if (skullOwnerCompound.containsKey("Id")) { + skullOwner(Utils.intArrayToUuid(skullOwnerCompound.getIntArray("Id"))); } + if (skullOwnerCompound.containsKey("Properties")) { + NBTCompound propertyCompound = skullOwnerCompound.getCompound("Properties"); + + if (propertyCompound.containsKey("textures")) { + NBTList textures = propertyCompound.getList("textures"); + if (textures != null) { + NBTCompound nbt = textures.get(0); + playerSkin(new PlayerSkin(nbt.getString("Value"), nbt.getString("Signature"))); + } + } + + } } - - } - } - - /** - * {@inheritDoc} - */ - @Override - public void write(@NotNull NBTCompound compound) { - NBTCompound skullOwnerCompound = new NBTCompound(); - // Sets the identifier for the skull - if (this.skullOwner != null) - skullOwnerCompound.setIntArray("Id", Utils.uuidToIntArray(this.skullOwner)); - - if (this.playerSkin == null && this.skullOwner != null) { - this.playerSkin = PlayerSkin.fromUuid(this.skullOwner.toString()); } - if (this.playerSkin != null) { - NBTList textures = new NBTList<>(NBTTypes.TAG_Compound); - String value = this.playerSkin.getTextures() == null ? "" : this.playerSkin.getTextures(); - String signature = this.playerSkin.getSignature() == null ? "" : this.playerSkin.getSignature(); - textures.add(new NBTCompound().setString("Value", value).setString("Signature", signature)); - skullOwnerCompound.set("Properties", new NBTCompound().set("textures", textures)); + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; } - - compound.set("SkullOwner", skullOwnerCompound); - } - - /** - * {@inheritDoc} - */ - @NotNull - @Override - public ItemMeta clone() { - PlayerHeadMeta playerHeadMeta = (PlayerHeadMeta) super.clone(); - playerHeadMeta.skullOwner = this.skullOwner; - playerHeadMeta.playerSkin = this.playerSkin; - return playerHeadMeta; - } - - } diff --git a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java b/src/main/java/net/minestom/server/item/metadata/PotionMeta.java index cc5d2df24..bbf4b0e98 100644 --- a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PotionMeta.java @@ -1,11 +1,11 @@ package net.minestom.server.item.metadata; -import net.minestom.server.chat.ChatColor; import net.minestom.server.color.Color; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.potion.CustomPotionEffect; import net.minestom.server.potion.PotionType; import net.minestom.server.registry.Registries; -import net.minestom.server.utils.clone.CloneUtils; import net.minestom.server.utils.time.TimeUnit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -13,124 +13,53 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTList; import org.jglrxavpok.hephaistos.nbt.NBTTypes; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; -/** - * Item meta for - * {@link net.minestom.server.item.Material#POTION}, - * {@link net.minestom.server.item.Material#LINGERING_POTION}, - * {@link net.minestom.server.item.Material#SPLASH_POTION}, - * {@link net.minestom.server.item.Material#TIPPED_ARROW}. - */ -public class PotionMeta extends ItemMeta { +public class PotionMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private PotionType potionType; + private final PotionType potionType; + private final List customPotionEffects; + private final Color color; - // Not final because of #clone() - private List customPotionEffects = new CopyOnWriteArrayList<>(); + protected PotionMeta(@NotNull ItemMetaBuilder metaBuilder, @Nullable PotionType potionType, + List customPotionEffects, + Color color) { + super(metaBuilder); + this.potionType = potionType; + this.customPotionEffects = new ArrayList<>(customPotionEffects); + this.color = color; + } - private Color color; - - /** - * Gets the potion type. - * - * @return the potion type - */ - @Nullable public PotionType getPotionType() { return potionType; } - /** - * Changes the potion type. - * - * @param potionType the new potion type - */ - public void setPotionType(@Nullable PotionType potionType) { - this.potionType = potionType; - } - - /** - * Get a list of {@link CustomPotionEffect}. - * - * @return the custom potion effect list - */ - @NotNull public List getCustomPotionEffects() { return customPotionEffects; } - /** - * Changes the color of the potion. - * - * @param color the new color of the potion - * @deprecated Use {@link #setColor(Color)} - */ - @Deprecated - public void setColor(ChatColor color) { - this.setColor(color.asColor()); + public Color getColor() { + return color; } - /** - * Changes the color of the potion. - * - * @param color the new color of the potion - */ - public void setColor(@Nullable Color color) { - this.color = color; - } + public static class Builder extends ItemMetaBuilder { - @Override - public boolean hasNbt() { - return potionType != null || - !customPotionEffects.isEmpty(); - } + private PotionType potionType; + private List customPotionEffects = new ArrayList<>(); + private Color color; - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof PotionMeta)) - return false; - PotionMeta potionMeta = (PotionMeta) itemMeta; - return potionMeta.potionType == potionType && - potionMeta.customPotionEffects.equals(customPotionEffects) && - potionMeta.color.equals(color); - } - - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("Potion")) { - this.potionType = Registries.getPotionType(compound.getString("Potion")); + public Builder potionType(@NotNull PotionType potionType) { + this.potionType = potionType; + this.nbt.setString("Potion", potionType.getNamespaceID()); + return this; } - if (compound.containsKey("CustomPotionEffects")) { - NBTList customEffectList = compound.getList("CustomPotionEffects"); - for (NBTCompound potionCompound : customEffectList) { - final byte id = potionCompound.getAsByte("Id"); - final byte amplifier = potionCompound.getAsByte("Amplifier"); - final int duration = potionCompound.containsKey("Duration") ? potionCompound.getNumber("Duration").intValue() : (int) TimeUnit.SECOND.toMilliseconds(30); - final boolean ambient = potionCompound.containsKey("Ambient") ? potionCompound.getAsByte("Ambient") == 1 : false; - final boolean showParticles = potionCompound.containsKey("ShowParticles") ? potionCompound.getAsByte("ShowParticles") == 1 : true; - final boolean showIcon = potionCompound.containsKey("ShowIcon") ? potionCompound.getAsByte("ShowIcon") == 1 : true; + public Builder effects(@NotNull List customPotionEffects) { + this.customPotionEffects = customPotionEffects; - this.customPotionEffects.add( - new CustomPotionEffect(id, amplifier, duration, ambient, showParticles, showIcon)); - } - } - - if (compound.containsKey("CustomPotionColor")) { - this.color = new Color(compound.getInt("CustomPotionColor")); - } - } - - @Override - public void write(@NotNull NBTCompound compound) { - if (potionType != null) { - compound.setString("Potion", potionType.getNamespaceID()); - } - if (!customPotionEffects.isEmpty()) { NBTList potionList = new NBTList<>(NBTTypes.TAG_Compound); - for (CustomPotionEffect customPotionEffect : customPotionEffects) { NBTCompound potionCompound = new NBTCompound(); potionCompound.setByte("Id", customPotionEffect.getId()); @@ -142,25 +71,52 @@ public class PotionMeta extends ItemMeta { potionList.add(potionCompound); } + this.nbt.set("CustomPotionEffects", potionList); - compound.set("CustomPotionEffects", potionList); + return this; } - if (color != null) { - compound.setInt("CustomPotionColor", color.asRGB()); + public Builder color(@NotNull Color color) { + this.color = color; + this.nbt.setInt("CustomPotionColor", color.asRGB()); + return this; } + @Override + public @NotNull PotionMeta build() { + return new PotionMeta(this, potionType, customPotionEffects, color); + } + + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("Potion")) { + potionType(Registries.getPotionType(nbtCompound.getString("Potion"))); + } + + if (nbtCompound.containsKey("CustomPotionEffects")) { + NBTList customEffectList = nbtCompound.getList("CustomPotionEffects"); + for (NBTCompound potionCompound : customEffectList) { + final byte id = potionCompound.getAsByte("Id"); + final byte amplifier = potionCompound.getAsByte("Amplifier"); + final int duration = potionCompound.containsKey("Duration") ? potionCompound.getNumber("Duration").intValue() : (int) TimeUnit.SECOND.toMilliseconds(30); + final boolean ambient = potionCompound.containsKey("Ambient") ? potionCompound.getAsByte("Ambient") == 1 : false; + final boolean showParticles = potionCompound.containsKey("ShowParticles") ? potionCompound.getAsByte("ShowParticles") == 1 : true; + final boolean showIcon = potionCompound.containsKey("ShowIcon") ? potionCompound.getAsByte("ShowIcon") == 1 : true; + + this.customPotionEffects.add( + new CustomPotionEffect(id, amplifier, duration, ambient, showParticles, showIcon)); + } + effects(customPotionEffects); + } + + if (nbtCompound.containsKey("CustomPotionColor")) { + color(new Color(nbtCompound.getInt("CustomPotionColor"))); + } + } + + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; + } } - - @NotNull - @Override - public ItemMeta clone() { - PotionMeta potionMeta = (PotionMeta) super.clone(); - potionMeta.potionType = potionType; - potionMeta.customPotionEffects = CloneUtils.cloneCopyOnWriteArrayList(customPotionEffects); - - potionMeta.color = color; - - return potionMeta; - } -} +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/item/metadata/SpawnEggMeta.java b/src/main/java/net/minestom/server/item/metadata/SpawnEggMeta.java index 868298b1f..2c56be1a8 100644 --- a/src/main/java/net/minestom/server/item/metadata/SpawnEggMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/SpawnEggMeta.java @@ -1,50 +1,50 @@ package net.minestom.server.item.metadata; import net.minestom.server.entity.EntityType; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; -// TODO for which item -public class SpawnEggMeta extends ItemMeta { +import java.util.function.Supplier; - private EntityType entityType; +public class SpawnEggMeta extends ItemMeta implements ItemMetaBuilder.Provider { - @Override - public boolean hasNbt() { - return entityType != null; + private final EntityType entityType; + + protected SpawnEggMeta(@NotNull ItemMetaBuilder metaBuilder, @Nullable EntityType entityType) { + super(metaBuilder); + this.entityType = entityType; } - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof SpawnEggMeta)) - return false; - final SpawnEggMeta spawnEggMeta = (SpawnEggMeta) itemMeta; - return spawnEggMeta.entityType == entityType; + public @Nullable EntityType getEntityType() { + return entityType; } - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("EntityTag")) { + public static class Builder extends ItemMetaBuilder { + + private EntityType entityType; + + public Builder entityType(@Nullable EntityType entityType) { + this.entityType = entityType; + // TODO nbt + return this; + } + + @Override + public @NotNull SpawnEggMeta build() { + return new SpawnEggMeta(this, entityType); + } + + @Override + public void read(@NotNull NBTCompound nbtCompound) { // TODO } - } - @Override - public void write(@NotNull NBTCompound compound) { - if (!hasNbt()) - return; - NBTCompound entityCompound = new NBTCompound(); - if (entityType != null) { - entityCompound.setString("id", entityType.getNamespaceID()); + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; } - } - - @NotNull - @Override - public ItemMeta clone() { - SpawnEggMeta spawnEggMeta = (SpawnEggMeta) super.clone(); - spawnEggMeta.entityType = entityType; - return spawnEggMeta; - } -} +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java b/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java index d5f36e001..815ab6a5d 100644 --- a/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java @@ -3,6 +3,8 @@ package net.minestom.server.item.metadata; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.adventure.AdventureSerializer; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; @@ -11,110 +13,97 @@ import org.jglrxavpok.hephaistos.nbt.NBTString; import org.jglrxavpok.hephaistos.nbt.NBTTypes; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; -public class WritableBookMeta extends ItemMeta { +public class WritableBookMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private String title; - private String author; - private List pages = new ArrayList<>(); + private final String author; + private final String title; + private final List pages; - @Nullable - public String getTitle() { - return title; - } - - public void setTitle(@Nullable String title) { + protected WritableBookMeta(@NotNull ItemMetaBuilder metaBuilder, + @Nullable String author, @Nullable String title, + @NotNull List<@NotNull Component> pages) { + super(metaBuilder); + this.author = author; this.title = title; + this.pages = new ArrayList<>(pages); } - @Nullable - public String getAuthor() { + public @Nullable String getAuthor() { return author; } - public void setAuthor(@Nullable String author) { - this.author = author; + public @Nullable String getTitle() { + return title; } - /** - * Gets an array list containing the book pages. - *

- * The list is modifiable. - * - * @return a modifiable {@link ArrayList} containing the book pages - */ - @NotNull - public List getPages() { - return pages; + public @NotNull List<@NotNull Component> getPages() { + return Collections.unmodifiableList(pages); } - /** - * Sets the pages list of this book. - * - * @param pages the pages list - */ - public void setPages(@NotNull List pages) { - this.pages = pages; - } + public static class Builder extends ItemMetaBuilder { - @Override - public boolean hasNbt() { - return !pages.isEmpty(); - } + private String author; + private String title; + private List pages = new ArrayList<>(); - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof WritableBookMeta)) - return false; - final WritableBookMeta writableBookMeta = (WritableBookMeta) itemMeta; - return writableBookMeta.pages.equals(pages); - } - - @Override - public void read(@NotNull NBTCompound compound) { - - if (compound.containsKey("title")) { - this.title = compound.getString("title"); + public Builder author(@Nullable String author) { + this.author = author; + handleNullable(author, "author", nbt, + () -> new NBTString(Objects.requireNonNull(author))); + return this; } - if (compound.containsKey("author")) { - this.author = compound.getString("author"); + public Builder title(@Nullable String title) { + this.title = title; + handleNullable(title, "title", nbt, + () -> new NBTString(Objects.requireNonNull(title))); + return this; } - if (compound.containsKey("pages")) { - final NBTList list = compound.getList("pages"); - for (NBTString page : list) { - this.pages.add(GsonComponentSerializer.gson().deserialize(page.getValue())); + public Builder pages(@NotNull List<@NotNull Component> pages) { + this.pages = pages; + + handleCollection(pages, "pages", nbt, () -> { + NBTList list = new NBTList<>(NBTTypes.TAG_String); + for (Component page : pages) { + list.add(new NBTString(AdventureSerializer.serialize(page))); + } + return list; + }); + + return this; + } + + @Override + public @NotNull WritableBookMeta build() { + return new WritableBookMeta(this, author, title, pages); + } + + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("author")) { + author(nbtCompound.getString("author")); + } + if (nbtCompound.containsKey("title")) { + title(nbtCompound.getString("title")); + } + if (nbtCompound.containsKey("pages")) { + final NBTList list = nbtCompound.getList("pages"); + for (NBTString page : list) { + this.pages.add(GsonComponentSerializer.gson().deserialize(page.getValue())); + } + pages(pages); } } - } - @Override - public void write(@NotNull NBTCompound compound) { - - if (title != null) { - compound.setString("title", title); - } - - if (author != null) { - compound.setString("author", author); - } - - if (!pages.isEmpty()) { - NBTList list = new NBTList<>(NBTTypes.TAG_String); - for (Component page : pages) { - list.add(new NBTString(AdventureSerializer.serialize(page))); - } - compound.set("pages", list); + @Override + protected @NotNull Supplier getSupplier() { + return WritableBookMeta.Builder::new; } } - - @NotNull - @Override - public ItemMeta clone() { - WritableBookMeta writableBookMeta = (WritableBookMeta) super.clone(); - writableBookMeta.pages = new ArrayList<>(pages); - return writableBookMeta; - } -} +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java b/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java index 48331d3c1..02c922003 100644 --- a/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java @@ -5,192 +5,53 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.adventure.AdventureSerializer; import net.minestom.server.adventure.Localizable; +import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemMetaBuilder; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTList; -import org.jglrxavpok.hephaistos.nbt.NBTString; -import org.jglrxavpok.hephaistos.nbt.NBTTypes; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.function.Supplier; -public class WrittenBookMeta extends ItemMeta { +public class WrittenBookMeta extends ItemMeta implements ItemMetaBuilder.Provider { - private boolean resolved; - private WrittenBookGeneration generation; - private String author; - private String title; - private List pages = new ArrayList<>(); + private final boolean resolved; + private final WrittenBookGeneration generation; + private final String author; + private final String title; + private final List pages; + + protected WrittenBookMeta(@NotNull ItemMetaBuilder metaBuilder, boolean resolved, + @Nullable WrittenBookGeneration generation, + @Nullable String author, @Nullable String title, + @NotNull List<@NotNull Component> pages) { + super(metaBuilder); + this.resolved = resolved; + this.generation = generation; + this.author = author; + this.title = title; + this.pages = new ArrayList<>(pages); + } - /** - * Gets if the book is resolved. - * - * @return true if the book is resolved, false otherwise - */ public boolean isResolved() { return resolved; } - /** - * Sets to true when the book (or a book from the stack) - * is opened for the first time after being created. - * - * @param resolved true to make the book resolved, false otherwise - */ - public void setResolved(boolean resolved) { - this.resolved = resolved; - } - - /** - * Gets the copy tier of the book. - * - * @return the copy tier of the book - */ - public WrittenBookGeneration getGeneration() { + public @Nullable WrittenBookGeneration getGeneration() { return generation; } - /** - * Sets the copy tier of the book. - * - * @param generation the copy tier of the book - */ - public void setGeneration(WrittenBookGeneration generation) { - this.generation = generation; - } - - /** - * Gets the author of the book. - * - * @return the author of the book - */ - public String getAuthor() { + public @Nullable String getAuthor() { return author; } - /** - * Sets the author of the book. - * - * @param author the author of the book - */ - public void setAuthor(String author) { - this.author = author; - } - - /** - * Gets the title of the book. - * - * @return the title of the book - */ - public String getTitle() { + public @Nullable String getTitle() { return title; } - /** - * Sets the title of the book. - * - * @param title the title of the book - */ - public void setTitle(String title) { - this.title = title; - } - - /** - * Gets an {@link ArrayList} containing all the pages. - *

- * The list is not modifiable as it is . - * - * @return a modifiable {@link ArrayList} with the pages of the book - */ - @Deprecated - public List getPagesJson() { - return pages; - } - - /** - * Sets the {@link ArrayList} containing the book pages. - * - * @param pages the array list containing the book pages - */ - public void setPages(List pages) { - this.pages = pages; - } - - @Override - public boolean hasNbt() { - return resolved || generation != null || - author != null || title != null || - !pages.isEmpty(); - } - - @Override - public boolean isSimilar(@NotNull ItemMeta itemMeta) { - if (!(itemMeta instanceof WrittenBookMeta)) - return false; - final WrittenBookMeta writtenBookMeta = (WrittenBookMeta) itemMeta; - return writtenBookMeta.resolved == resolved && - writtenBookMeta.generation == generation && - writtenBookMeta.author.equals(author) && - writtenBookMeta.title.equals(title) && - writtenBookMeta.pages.equals(pages); - } - - @Override - public void read(@NotNull NBTCompound compound) { - if (compound.containsKey("resolved")) { - this.resolved = compound.getByte("resolved") == 1; - } - if (compound.containsKey("generation")) { - this.generation = WrittenBookGeneration.values()[compound.getInt("generation")]; - } - if (compound.containsKey("author")) { - this.author = compound.getString("author"); - } - if (compound.containsKey("title")) { - this.title = compound.getString("title"); - } - if (compound.containsKey("pages")) { - final NBTList list = compound.getList("pages"); - for (NBTString page : list) { - this.pages.add(GsonComponentSerializer.gson().deserialize(page.getValue())); - } - } - } - - @Override - public void write(@NotNull NBTCompound compound) { - if (resolved) { - compound.setByte("resolved", (byte) 1); - } - if (generation != null) { - compound.setInt("generation", generation.ordinal()); - } - if (author != null) { - compound.setString("author", author); - } - if (title != null) { - compound.setString("title", title); - } - if (!pages.isEmpty()) { - NBTList list = new NBTList<>(NBTTypes.TAG_String); - for (Component page : pages) { - list.add(new NBTString(AdventureSerializer.serialize(page))); - } - compound.set("pages", list); - } - } - - @NotNull - @Override - public ItemMeta clone() { - WrittenBookMeta writtenBookMeta = (WrittenBookMeta) super.clone(); - writtenBookMeta.resolved = resolved; - writtenBookMeta.generation = generation; - writtenBookMeta.author = author; - writtenBookMeta.title = title; - writtenBookMeta.pages.addAll(pages); - - return writtenBookMeta; + public @NotNull List<@NotNull Component> getPages() { + return Collections.unmodifiableList(pages); } public enum WrittenBookGeneration { @@ -201,21 +62,104 @@ public class WrittenBookMeta extends ItemMeta { * Creates a written book meta from an Adventure book. This meta will not be * resolved and the generation will default to {@link WrittenBookGeneration#ORIGINAL}. * - * @param book the book + * @param book the book * @param localizable who the book is for - * * @return the meta */ public static @NotNull WrittenBookMeta fromAdventure(@NotNull Book book, @NotNull Localizable localizable) { - // make the book - WrittenBookMeta meta = new WrittenBookMeta(); - meta.resolved = false; - meta.generation = WrittenBookGeneration.ORIGINAL; - meta.author = AdventureSerializer.translateAndSerialize(book.author(), localizable); - meta.title = AdventureSerializer.translateAndSerialize(book.title(), localizable); - meta.pages = new ArrayList<>(); - meta.pages.addAll(book.pages()); + return new Builder() + .resolved(false) + .generation(WrittenBookGeneration.ORIGINAL) + .author(AdventureSerializer.translateAndSerialize(book.author(), localizable)) + .title(AdventureSerializer.translateAndSerialize(book.title(), localizable)) + .pages(book.pages()) + .build(); + } - return meta; + public static class Builder extends ItemMetaBuilder { + + private boolean resolved; + private WrittenBookGeneration generation; + private String author; + private String title; + private List pages = new ArrayList<>(); + + public Builder resolved(boolean resolved) { + this.resolved = resolved; + this.nbt.setByte("resolved", (byte) (resolved ? 1 : 0)); + return this; + } + + public Builder generation(@Nullable WrittenBookGeneration generation) { + this.generation = generation; + handleNullable(generation, "generation", nbt, + () -> new NBTInt(Objects.requireNonNull(generation).ordinal())); + return this; + } + + public Builder author(@Nullable String author) { + this.author = author; + handleNullable(author, "author", nbt, + () -> new NBTString(Objects.requireNonNull(author))); + return this; + } + + public Builder title(@Nullable String title) { + this.title = title; + handleNullable(title, "title", nbt, + () -> new NBTString(Objects.requireNonNull(title))); + return this; + } + + public Builder pages(@NotNull List<@NotNull Component> pages) { + this.pages = pages; + + handleCollection(pages, "pages", nbt, () -> { + NBTList list = new NBTList<>(NBTTypes.TAG_String); + for (Component page : pages) { + list.add(new NBTString(AdventureSerializer.serialize(page))); + } + return list; + }); + + return this; + } + + public Builder pages(Component... pages) { + return pages(Arrays.asList(pages)); + } + + @Override + public @NotNull WrittenBookMeta build() { + return new WrittenBookMeta(this, resolved, generation, author, title, pages); + } + + @Override + public void read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey("resolved")) { + resolved(nbtCompound.getByte("resolved") == 1); + } + if (nbtCompound.containsKey("generation")) { + generation(WrittenBookGeneration.values()[nbtCompound.getInt("generation")]); + } + if (nbtCompound.containsKey("author")) { + author(nbtCompound.getString("author")); + } + if (nbtCompound.containsKey("title")) { + title(nbtCompound.getString("title")); + } + if (nbtCompound.containsKey("pages")) { + final NBTList list = nbtCompound.getList("pages"); + for (NBTString page : list) { + this.pages.add(GsonComponentSerializer.gson().deserialize(page.getValue())); + } + pages(pages); + } + } + + @Override + protected @NotNull Supplier getSupplier() { + return Builder::new; + } } } diff --git a/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java b/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java index 04261a7a2..7edbf6a2a 100644 --- a/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java +++ b/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java @@ -25,10 +25,9 @@ public class VanillaStackingRule extends StackingRule { @Override public ItemStack apply(@NotNull ItemStack item, int newAmount) { if (newAmount <= 0) - return ItemStack.getAirItem(); + return ItemStack.AIR; - item.setAmount((byte) newAmount); - return item; + return item.withAmount(newAmount); } @Override diff --git a/src/main/java/net/minestom/server/listener/AnimationListener.java b/src/main/java/net/minestom/server/listener/AnimationListener.java index 571f2586a..c40511a3b 100644 --- a/src/main/java/net/minestom/server/listener/AnimationListener.java +++ b/src/main/java/net/minestom/server/listener/AnimationListener.java @@ -10,7 +10,7 @@ public class AnimationListener { public static void animationListener(ClientAnimationPacket packet, Player player) { final Player.Hand hand = packet.hand; final ItemStack itemStack = player.getItemInHand(hand); - itemStack.onLeftClick(player, hand); + //itemStack.onLeftClick(player, hand); PlayerHandAnimationEvent handAnimationEvent = new PlayerHandAnimationEvent(player, hand); player.callCancellableEvent(PlayerHandAnimationEvent.class, handAnimationEvent, () -> { switch (hand) { diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index bd51a6775..413d34a6d 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -52,7 +52,8 @@ public class BlockPlacementListener { final ItemStack usedItem = player.getItemInHand(hand); // Interact at block - final boolean cancel = usedItem.onUseOnBlock(player, hand, blockPosition, direction); + // FIXME: onUseOnBlock + final boolean cancel = false;//usedItem.onUseOnBlock(player, hand, blockPosition, direction); PlayerBlockInteractEvent playerBlockInteractEvent = new PlayerBlockInteractEvent(player, blockPosition, hand, blockFace); playerBlockInteractEvent.setCancelled(cancel); playerBlockInteractEvent.setBlockingItemUse(cancel); @@ -85,7 +86,8 @@ public class BlockPlacementListener { canPlaceBlock = false; //Spectators can't place blocks } else if (player.getGameMode() == GameMode.ADVENTURE) { //Check if the block can placed on the block - canPlaceBlock = usedItem.canPlaceOn(instance.getBlock(blockPosition).getName()); + Block placeBlock = instance.getBlock(blockPosition); + canPlaceBlock = usedItem.getMeta().getCanPlaceOn().contains(placeBlock); } } @@ -166,11 +168,8 @@ public class BlockPlacementListener { // Block consuming if (playerBlockPlaceEvent.doesConsumeBlock()) { // Consume the block in the player's hand - final ItemStack newUsedItem = usedItem.consume(1); - - if (newUsedItem != null) { - playerInventory.setItemInHand(hand, newUsedItem); - } + final ItemStack newUsedItem = usedItem.getStackingRule().apply(usedItem, usedItem.getAmount() - 1); + playerInventory.setItemInHand(hand, newUsedItem); } } else { refreshChunk = true; @@ -196,8 +195,6 @@ public class BlockPlacementListener { if (refreshChunk) { chunk.sendChunkSectionUpdate(ChunkUtils.getSectionAt(blockPosition.getY()), player); } - - player.getInventory().refreshSlot(player.getHeldSlot()); } } diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index add48b2a0..8ed5da8e3 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -47,7 +47,8 @@ public class PlayerDiggingListener { } else if (player.getGameMode() == GameMode.ADVENTURE) { //Check if the item can break the block with the current item ItemStack itemInMainHand = player.getItemInMainHand(); - if (!itemInMainHand.canDestroy(instance.getBlock(blockPosition).getName())) { + Block destroyedBlock = instance.getBlock(blockPosition); + if (!itemInMainHand.getMeta().getCanDestroy().contains(destroyedBlock)) { sendAcknowledgePacket(player, blockPosition, blockStateId, ClientPlayerDiggingPacket.Status.STARTED_DIGGING, false); return; @@ -111,7 +112,7 @@ public class PlayerDiggingListener { } else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM_STACK) { final ItemStack droppedItemStack = player.getInventory().getItemInMainHand(); - dropItem(player, droppedItemStack, ItemStack.getAirItem()); + dropItem(player, droppedItemStack, ItemStack.AIR); } else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM) { @@ -123,14 +124,11 @@ public class PlayerDiggingListener { if (handAmount <= dropAmount) { // Drop the whole item without copy - dropItem(player, handItem, ItemStack.getAirItem()); + dropItem(player, handItem, ItemStack.AIR); } else { // Drop a single item, need a copy - ItemStack droppedItemStack2 = handItem.clone(); + ItemStack droppedItemStack2 = stackingRule.apply(handItem, dropAmount); - droppedItemStack2 = stackingRule.apply(droppedItemStack2, dropAmount); - - handItem = handItem.clone(); // Force the copy handItem = stackingRule.apply(handItem, handAmount - dropAmount); dropItem(player, droppedItemStack2, handItem); diff --git a/src/main/java/net/minestom/server/listener/UseItemListener.java b/src/main/java/net/minestom/server/listener/UseItemListener.java index 20214ede1..8e008ba6d 100644 --- a/src/main/java/net/minestom/server/listener/UseItemListener.java +++ b/src/main/java/net/minestom/server/listener/UseItemListener.java @@ -15,7 +15,7 @@ public class UseItemListener { final PlayerInventory inventory = player.getInventory(); final Player.Hand hand = packet.hand; ItemStack itemStack = hand == Player.Hand.MAIN ? inventory.getItemInMainHand() : inventory.getItemInOffHand(); - itemStack.onRightClick(player, hand); + //itemStack.onRightClick(player, hand); PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(player, hand, itemStack); player.callEvent(PlayerUseItemEvent.class, useItemEvent); diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java index bc2935457..779ce8bb1 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java @@ -13,7 +13,7 @@ public class ClientClickWindowPacket extends ClientPlayPacket { public byte button; public short actionNumber; public int mode; - public ItemStack item = ItemStack.getAirItem(); + public ItemStack item = ItemStack.AIR; @Override public void read(@NotNull BinaryReader reader) { diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java index d70b26db3..2cf718f2f 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull; public class ClientCreativeInventoryActionPacket extends ClientPlayPacket { public short slot; - public ItemStack item = ItemStack.getAirItem(); + public ItemStack item = ItemStack.AIR; @Override public void read(@NotNull BinaryReader reader) { diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientEditBookPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientEditBookPacket.java index 910bae4aa..c3e662ac8 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientEditBookPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientEditBookPacket.java @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull; public class ClientEditBookPacket extends ClientPlayPacket { - public ItemStack book = ItemStack.getAirItem(); + public ItemStack book = ItemStack.AIR; public boolean isSigning; public Player.Hand hand = Player.Hand.MAIN; diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientNameItemPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientNameItemPacket.java index 180300b18..664b12433 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientNameItemPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientNameItemPacket.java @@ -17,7 +17,7 @@ public class ClientNameItemPacket extends ClientPlayPacket { @Override public void write(@NotNull BinaryWriter writer) { if(itemName.length() > Short.MAX_VALUE) { - throw new IllegalArgumentException("Item name cannot be longer than Short.MAX_VALUE characters!"); + throw new IllegalArgumentException("ItemStack name cannot be longer than Short.MAX_VALUE characters!"); } writer.writeSizedString(itemName); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java index cd4f6a365..b8b3d5d32 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java @@ -25,7 +25,8 @@ public class AdvancementsPacket implements ComponentHoldingServerPacket { public String[] identifiersToRemove = new String[0]; public ProgressMapping[] progressMappings = new ProgressMapping[0]; - public AdvancementsPacket() {} + public AdvancementsPacket() { + } @Override public void write(@NotNull BinaryWriter writer) { @@ -149,14 +150,14 @@ public class AdvancementsPacket implements ComponentHoldingServerPacket { @Override public void read(@NotNull BinaryReader reader) { boolean hasParent = reader.readBoolean(); - if(hasParent) { + if (hasParent) { parentIdentifier = reader.readSizedString(Integer.MAX_VALUE); } else { parentIdentifier = null; } boolean hasDisplay = reader.readBoolean(); - if(hasDisplay) { + if (hasDisplay) { displayData = new DisplayData(); displayData.read(reader); } else { @@ -177,7 +178,7 @@ public class AdvancementsPacket implements ComponentHoldingServerPacket { public static class DisplayData implements Writeable, Readable { public Component title = Component.empty(); // Only text public Component description = Component.empty(); // Only text - public ItemStack icon = ItemStack.getAirItem(); + public ItemStack icon = ItemStack.AIR; public FrameType frameType = FrameType.TASK; public int flags; public String backgroundTexture = ""; @@ -205,7 +206,7 @@ public class AdvancementsPacket implements ComponentHoldingServerPacket { icon = reader.readItemStack(); frameType = FrameType.values()[reader.readVarInt()]; flags = reader.readInt(); - if((flags & 0x1) != 0) { + if ((flags & 0x1) != 0) { backgroundTexture = reader.readSizedString(Integer.MAX_VALUE); } else { backgroundTexture = null; @@ -302,7 +303,7 @@ public class AdvancementsPacket implements ComponentHoldingServerPacket { @Override public void read(@NotNull BinaryReader reader) { achieved = reader.readBoolean(); - if(achieved) { + if (achieved) { dateOfAchieving = reader.readLong(); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java index ef99c2800..3768ad4ff 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java @@ -14,7 +14,8 @@ public class DeclareRecipesPacket implements ServerPacket { public DeclaredRecipe[] recipes = new DeclaredRecipe[0]; - public DeclareRecipesPacket() {} + public DeclareRecipesPacket() { + } @Override public void write(@NotNull BinaryWriter writer) { @@ -68,7 +69,7 @@ public class DeclareRecipesPacket implements ServerPacket { break; default: - throw new UnsupportedOperationException("Unrecognized type: "+type+" (id is "+id+")"); + throw new UnsupportedOperationException("Unrecognized type: " + type + " (id is " + id + ")"); } } } @@ -193,7 +194,7 @@ public class DeclareRecipesPacket implements ServerPacket { width = reader.readVarInt(); height = reader.readVarInt(); group = reader.readSizedString(Integer.MAX_VALUE); - ingredients = new Ingredient[width*height]; + ingredients = new Ingredient[width * height]; for (int i = 0; i < width * height; i++) { ingredients[i] = new Ingredient(); ingredients[i].read(reader); @@ -442,7 +443,7 @@ public class DeclareRecipesPacket implements ServerPacket { public void read(@NotNull BinaryReader reader) { group = reader.readSizedString(Integer.MAX_VALUE); ingredient = new Ingredient(); - ingredient.read( reader); + ingredient.read(reader); result = reader.readItemStack(); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java index 3a50c4e77..a2e92abdd 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java @@ -17,7 +17,8 @@ public class EntityEquipmentPacket implements ServerPacket { public Slot[] slots; public ItemStack[] itemStacks; - public EntityEquipmentPacket() {} + public EntityEquipmentPacket() { + } @Override public void write(@NotNull BinaryWriter writer) { @@ -53,7 +54,7 @@ public class EntityEquipmentPacket implements ServerPacket { boolean hasRemaining = true; List slots = new LinkedList<>(); List stacks = new LinkedList<>(); - while(hasRemaining) { + while (hasRemaining) { byte slotEnum = reader.readByte(); hasRemaining = (slotEnum & 0x80) == 0x80; diff --git a/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java index 94e361fa0..20c3eb619 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java @@ -1,7 +1,6 @@ package net.minestom.server.network.packet.server.play; -import net.minestom.server.chat.ColoredText; -import net.minestom.server.chat.JsonMessage; +import net.kyori.adventure.text.Component; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; @@ -12,26 +11,27 @@ public class OpenWindowPacket implements ServerPacket { public int windowId; public int windowType; - public JsonMessage title = ColoredText.of(""); + public Component title = Component.text(""); - public OpenWindowPacket() {} + public OpenWindowPacket() { + } - public OpenWindowPacket(String title) { - this.title = ColoredText.of(title); + public OpenWindowPacket(Component title) { + this.title = title; } @Override public void write(@NotNull BinaryWriter writer) { writer.writeVarInt(windowId); writer.writeVarInt(windowType); - writer.writeJsonMessage(title); + writer.writeComponent(title); } @Override public void read(@NotNull BinaryReader reader) { windowId = reader.readVarInt(); windowType = reader.readVarInt(); - title = reader.readJsonMessage(Integer.MAX_VALUE); + title = reader.readComponent(Integer.MAX_VALUE); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java index bd4777537..d4d6676fe 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java @@ -14,7 +14,7 @@ public class SetSlotPacket implements ServerPacket { public ItemStack itemStack; public SetSlotPacket() { - itemStack = ItemStack.getAirItem(); + itemStack = ItemStack.AIR; } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java index 9d559616a..0ff36a2f7 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java @@ -96,7 +96,7 @@ public class TradeListPacket implements ServerPacket { result = reader.readItemStack(); boolean hasSecondItem = reader.readBoolean(); - if(hasSecondItem) { + if (hasSecondItem) { inputItem2 = reader.readItemStack(); } else { inputItem2 = null; diff --git a/src/main/java/net/minestom/server/recipe/ShapedRecipe.java b/src/main/java/net/minestom/server/recipe/ShapedRecipe.java index 529b2142f..0add30148 100644 --- a/src/main/java/net/minestom/server/recipe/ShapedRecipe.java +++ b/src/main/java/net/minestom/server/recipe/ShapedRecipe.java @@ -17,11 +17,11 @@ public abstract class ShapedRecipe extends Recipe { private ItemStack result; protected ShapedRecipe(@NotNull String recipeId, - int width, - int height, - @NotNull String group, - @Nullable List ingredients, - @NotNull ItemStack result) { + int width, + int height, + @NotNull String group, + @Nullable List ingredients, + @NotNull ItemStack result) { super(RecipeType.SHAPED, recipeId); this.width = width; this.height = height; diff --git a/src/main/java/net/minestom/server/thread/ThreadProvider.java b/src/main/java/net/minestom/server/thread/ThreadProvider.java index 937fb3237..8640736ab 100644 --- a/src/main/java/net/minestom/server/thread/ThreadProvider.java +++ b/src/main/java/net/minestom/server/thread/ThreadProvider.java @@ -38,7 +38,7 @@ public abstract class ThreadProvider { { // Default thread count in the pool (cores * 2) - setThreadCount(NettyRuntime.availableProcessors() * 2); + setThreadCount(1); } /** diff --git a/src/main/java/net/minestom/server/utils/NBTUtils.java b/src/main/java/net/minestom/server/utils/NBTUtils.java index 3c967c6df..4fe49d468 100644 --- a/src/main/java/net/minestom/server/utils/NBTUtils.java +++ b/src/main/java/net/minestom/server/utils/NBTUtils.java @@ -5,22 +5,18 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.util.Codec; import net.minestom.server.MinecraftServer; -import net.minestom.server.adventure.AdventureSerializer; import net.minestom.server.attribute.Attribute; import net.minestom.server.attribute.AttributeOperation; -import net.minestom.server.data.Data; -import net.minestom.server.data.DataType; +import net.minestom.server.instance.block.Block; import net.minestom.server.inventory.Inventory; import net.minestom.server.item.Enchantment; +import net.minestom.server.item.ItemMetaBuilder; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.item.attribute.AttributeSlot; import net.minestom.server.item.attribute.ItemAttribute; -import net.minestom.server.item.metadata.ItemMeta; import net.minestom.server.registry.Registries; import net.minestom.server.utils.binary.BinaryReader; -import net.minestom.server.utils.binary.BinaryWriter; -import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -48,6 +44,7 @@ public final class NBTUtils { /** * Turns an {@link NBTCompound} into an Adventure {@link BinaryTagHolder}. + * * @param tag the tag, if any * @return the binary tag holder, or {@code null} if the tag was null */ @@ -69,15 +66,17 @@ public final class NBTUtils { public static void loadAllItems(@NotNull NBTList items, @NotNull Inventory destination) { destination.clear(); for (NBTCompound tag : items) { - Material item = Registries.getMaterial(tag.getString("id")); - if (item == Material.AIR) { - item = Material.STONE; + Material material = Registries.getMaterial(tag.getString("id")); + if (material == Material.AIR) { + material = Material.STONE; } - ItemStack stack = new ItemStack(item, tag.getByte("Count")); + byte count = tag.getByte("Count"); + NBTCompound nbtCompound = null; if (tag.containsKey("tag")) { - loadDataIntoItem(stack, tag.getCompound("tag")); + nbtCompound = tag.getCompound("tag"); } - destination.setItemStack(tag.getByte("Slot"), stack); + ItemStack itemStack = loadItem(material, count, nbtCompound); + destination.setItemStack(tag.getByte("Slot"), itemStack); } } @@ -86,12 +85,11 @@ public final class NBTUtils { final ItemStack stack = inventory.getItemStack(i); NBTCompound nbt = new NBTCompound(); - NBTCompound tag = new NBTCompound(); - saveDataIntoNBT(stack, tag); + NBTCompound tag = stack.getMeta().toNBT(); nbt.set("tag", tag); nbt.setByte("Slot", (byte) i); - nbt.setByte("Count", stack.getAmount()); + nbt.setByte("Count", (byte) stack.getAmount()); nbt.setString("id", stack.getMaterial().getName()); list.add(nbt); @@ -113,48 +111,60 @@ public final class NBTUtils { nbt.set(listName, enchantList); } - @Nullable + @NotNull public static ItemStack readItemStack(@NotNull BinaryReader reader) { final boolean present = reader.readBoolean(); if (!present) { - return ItemStack.getAirItem(); + return ItemStack.AIR; } final int id = reader.readVarInt(); if (id == -1) { // Drop mode - return ItemStack.getAirItem(); + return ItemStack.AIR; } final Material material = Material.fromId((short) id); final byte count = reader.readByte(); - ItemStack item = new ItemStack(material, count); + NBTCompound nbtCompound = null; try { final NBT itemNBT = reader.readTag(); if (itemNBT instanceof NBTCompound) { // can also be a TAG_End if no data - NBTCompound nbt = (NBTCompound) itemNBT; - loadDataIntoItem(item, nbt); + nbtCompound = (NBTCompound) itemNBT; } } catch (IOException | NBTException e) { MinecraftServer.getExceptionManager().handleException(e); } - return item; + return loadItem(material, count, nbtCompound); + } + + public static @NotNull ItemStack loadItem(@NotNull Material material, int count, @Nullable NBTCompound nbtCompound) { + return ItemStack.builder(material) + .amount(count) + .meta(metaBuilder -> { + if (nbtCompound != null) { + return ItemMetaBuilder.fromNBT(metaBuilder, nbtCompound); + } else { + return metaBuilder; + } + }) + .build(); } @SuppressWarnings("ConstantConditions") - public static void loadDataIntoItem(@NotNull ItemStack item, @NotNull NBTCompound nbt) { - if (nbt.containsKey("Damage")) item.setDamage(nbt.getInt("Damage")); - if (nbt.containsKey("Unbreakable")) item.setUnbreakable(nbt.getAsByte("Unbreakable") == 1); - if (nbt.containsKey("HideFlags")) item.setHideFlag(nbt.getInt("HideFlags")); + public static void loadDataIntoMeta(@NotNull ItemMetaBuilder metaBuilder, @NotNull NBTCompound nbt) { + if (nbt.containsKey("Damage")) metaBuilder.damage(nbt.getInt("Damage")); + if (nbt.containsKey("Unbreakable")) metaBuilder.unbreakable(nbt.getAsByte("Unbreakable") == 1); + if (nbt.containsKey("HideFlags")) metaBuilder.hideFlag(nbt.getInt("HideFlags")); if (nbt.containsKey("display")) { final NBTCompound display = nbt.getCompound("display"); if (display.containsKey("Name")) { final String rawName = display.getString("Name"); final Component displayName = GsonComponentSerializer.gson().deserialize(rawName); - item.setDisplayName(displayName); + metaBuilder.displayName(displayName); } if (display.containsKey("Lore")) { NBTList loreList = display.getList("Lore"); @@ -162,19 +172,20 @@ public final class NBTUtils { for (NBTString s : loreList) { lore.add(GsonComponentSerializer.gson().deserialize(s.getValue())); } - item.setLore(lore); + metaBuilder.lore(lore); } } // Enchantments if (nbt.containsKey("Enchantments")) { - loadEnchantments(nbt.getList("Enchantments"), item::setEnchantment); + loadEnchantments(nbt.getList("Enchantments"), metaBuilder::enchantment); } // Attributes if (nbt.containsKey("AttributeModifiers")) { - NBTList attributes = nbt.getList("AttributeModifiers"); - for (NBTCompound attributeNBT : attributes) { + List attributes = new ArrayList<>(); + NBTList nbtAttributes = nbt.getList("AttributeModifiers"); + for (NBTCompound attributeNBT : nbtAttributes) { final UUID uuid; { final int[] uuidArray = attributeNBT.getIntArray("UUID"); @@ -208,54 +219,43 @@ public final class NBTUtils { // Add attribute final ItemAttribute itemAttribute = new ItemAttribute(uuid, name, attribute, attributeOperation, value, attributeSlot); - item.addAttribute(itemAttribute); - } - } - - // Hide flags - { - if (nbt.containsKey("HideFlags")) { - item.setHideFlag(nbt.getInt("HideFlags")); + attributes.add(itemAttribute); } + metaBuilder.attributes(attributes); } // Custom model data { if (nbt.containsKey("CustomModelData")) { - item.setCustomModelData(nbt.getInt("CustomModelData")); + metaBuilder.customModelData(nbt.getInt("CustomModelData")); } } - // Meta specific field - final ItemMeta itemMeta = item.getItemMeta(); - if (itemMeta != null) { - itemMeta.read(nbt); - } + // Meta specific fields + metaBuilder.read(nbt); - // Ownership - { - if (nbt.containsKey(ItemStack.OWNERSHIP_DATA_KEY)) { - final String identifierString = nbt.getString(ItemStack.OWNERSHIP_DATA_KEY); - final UUID identifier = UUID.fromString(identifierString); - final Data data = ItemStack.DATA_OWNERSHIP.getOwnObject(identifier); - if (data != null) { - item.setData(data); - } - } - } - - //CanPlaceOn + // CanPlaceOn { if (nbt.containsKey("CanPlaceOn")) { NBTList canPlaceOn = nbt.getList("CanPlaceOn"); - canPlaceOn.forEach(x -> item.getCanPlaceOn().add(x.getValue())); + Set blocks = new HashSet<>(); + for (NBTString blockNamespace : canPlaceOn) { + Block block = Registries.getBlock(blockNamespace.getValue()); + blocks.add(block); + } + metaBuilder.canPlaceOn(blocks); } } - //CanDestroy + // CanDestroy { if (nbt.containsKey("CanDestroy")) { - NBTList canPlaceOn = nbt.getList("CanDestroy"); - canPlaceOn.forEach(x -> item.getCanDestroy().add(x.getValue())); + NBTList canDestroy = nbt.getList("CanDestroy"); + Set blocks = new HashSet<>(); + for (NBTString blockNamespace : canDestroy) { + Block block = Registries.getBlock(blockNamespace.getValue()); + blocks.add(block); + } + metaBuilder.canDestroy(blocks); } } } @@ -273,243 +273,6 @@ public final class NBTUtils { } } - public static void writeItemStack(BinaryWriter packet, ItemStack itemStack) { - if (itemStack == null || itemStack.isAir()) { - packet.writeBoolean(false); - } else { - packet.writeBoolean(true); - packet.writeVarInt(itemStack.getMaterial().getId()); - packet.writeByte(itemStack.getAmount()); - - if (!itemStack.hasNbtTag()) { - packet.writeByte((byte) NBTTypes.TAG_End); // No nbt - return; - } - - NBTCompound itemNBT = new NBTCompound(); - - // Vanilla compound - saveDataIntoNBT(itemStack, itemNBT); - - // End custom model data - packet.writeNBT("", itemNBT); - } - } - - public static void saveDataIntoNBT(@NotNull ItemStack itemStack, @NotNull NBTCompound itemNBT) { - // Unbreakable - if (itemStack.isUnbreakable()) { - itemNBT.setInt("Unbreakable", 1); - } - - // Start damage - { - final int damage = itemStack.getDamage(); - if (damage > 0) { - itemNBT.setInt("Damage", damage); - } - } - // End damage - - // Display - final boolean hasDisplayName = itemStack.hasDisplayName(); - final boolean hasLore = itemStack.hasLore(); - - if (hasDisplayName || hasLore) { - NBTCompound displayNBT = new NBTCompound(); - if (hasDisplayName) { - final String name = AdventureSerializer.serialize(itemStack.getDisplayName()); - displayNBT.setString("Name", name); - } - - if (hasLore) { - final List lore = itemStack.getLore(); - - final NBTList loreNBT = new NBTList<>(NBTTypes.TAG_String); - for (Component line : lore) { - loreNBT.add(new NBTString(GsonComponentSerializer.gson().serialize(line))); - } - displayNBT.set("Lore", loreNBT); - } - - itemNBT.set("display", displayNBT); - } - // End display - - // Start enchantment - { - final Map enchantmentMap = itemStack.getEnchantmentMap(); - if (!enchantmentMap.isEmpty()) { - writeEnchant(itemNBT, "Enchantments", enchantmentMap); - } - } - // End enchantment - - // Start attribute - { - final List itemAttributes = itemStack.getAttributes(); - if (!itemAttributes.isEmpty()) { - NBTList attributesNBT = new NBTList<>(NBTTypes.TAG_Compound); - - for (ItemAttribute itemAttribute : itemAttributes) { - final UUID uuid = itemAttribute.getUuid(); - attributesNBT.add( - new NBTCompound() - .setIntArray("UUID", Utils.uuidToIntArray(uuid)) - .setDouble("Amount", itemAttribute.getValue()) - .setString("Slot", itemAttribute.getSlot().name().toLowerCase()) - .setString("AttributeName", itemAttribute.getAttribute().getKey()) - .setInt("Operation", itemAttribute.getOperation().getId()) - .setString("Name", itemAttribute.getInternalName()) - ); - } - itemNBT.set("AttributeModifiers", attributesNBT); - } - } - // End attribute - - // Start hide flags - { - final int hideFlag = itemStack.getHideFlag(); - if (hideFlag != 0) { - itemNBT.setInt("HideFlags", hideFlag); - } - } - // End hide flags - - // Start custom model data - { - final int customModelData = itemStack.getCustomModelData(); - if (customModelData != 0) { - itemNBT.setInt("CustomModelData", customModelData); - } - } - // End custom model data - - // Start custom meta - { - final ItemMeta itemMeta = itemStack.getItemMeta(); - if (itemMeta != null) { - itemMeta.write(itemNBT); - } - } - // End custom meta - - // Start ownership - { - final Data data = itemStack.getData(); - if (data != null && !data.isEmpty()) { - final UUID identifier = itemStack.getIdentifier(); - itemNBT.setString(ItemStack.OWNERSHIP_DATA_KEY, identifier.toString()); - } - } - // End ownership - - //CanDestroy - { - Set canDestroy = itemStack.getCanDestroy(); - if (canDestroy.size() > 0) { - NBTList list = new NBTList<>(NBTTypes.TAG_String); - canDestroy.forEach(x -> list.add(new NBTString(x))); - itemNBT.set("CanDestroy", list); - } - } - - //CanDestroy - { - Set canPlaceOn = itemStack.getCanPlaceOn(); - if (canPlaceOn.size() > 0) { - NBTList list = new NBTList<>(NBTTypes.TAG_String); - canPlaceOn.forEach(x -> list.add(new NBTString(x))); - itemNBT.set("CanPlaceOn", list); - } - } - } - - /** - * Converts an object into its {@link NBT} equivalent. - *

- * If {@code type} is not a primitive type or primitive array and {@code supportDataType} is true, - * the data will be encoded with the appropriate {@link DataType} into a byte array. - * - * @param value the value to convert - * @param type the type of the value, used to know which {@link DataType} to use if {@code value} is not a primitive type - * @param supportDataType true to allow using a {@link DataType} to encode {@code value} into a byte array if not a primitive type - * @return the converted value, null if {@code type} is not a primitive type and {@code supportDataType} is false - */ - @Nullable - public static NBT toNBT(@NotNull Object value, @NotNull Class type, boolean supportDataType) { - type = PrimitiveConversion.getObjectClass(type); - if (type.equals(Boolean.class)) { - // No boolean type in NBT - return new NBTByte((byte) (((boolean) value) ? 1 : 0)); - } else if (type.equals(Byte.class)) { - return new NBTByte((byte) value); - } else if (type.equals(Character.class)) { - // No char type in NBT - return new NBTShort((short) value); - } else if (type.equals(Short.class)) { - return new NBTShort((short) value); - } else if (type.equals(Integer.class)) { - return new NBTInt((int) value); - } else if (type.equals(Long.class)) { - return new NBTLong((long) value); - } else if (type.equals(Float.class)) { - return new NBTFloat((float) value); - } else if (type.equals(Double.class)) { - return new NBTDouble((double) value); - } else if (type.equals(String.class)) { - return new NBTString((String) value); - } else if (type.equals(Byte[].class)) { - return new NBTByteArray((byte[]) value); - } else if (type.equals(Integer[].class)) { - return new NBTIntArray((int[]) value); - } else if (type.equals(Long[].class)) { - return new NBTLongArray((long[]) value); - } else { - if (supportDataType) { - // Custom NBT type, try to encode using the data manager - DataType dataType = MinecraftServer.getDataManager().getDataType(type); - Check.notNull(dataType, "The type '" + type + "' is not registered in DataManager and not a primitive type."); - - BinaryWriter writer = new BinaryWriter(); - dataType.encode(writer, value); - - final byte[] encodedValue = writer.toByteArray(); - - return new NBTByteArray(encodedValue); - } else { - return null; - } - } - } - - /** - * Converts a nbt object to its raw value. - *

- * Currently support number, string, byte/int/long array. - * - * @param nbt the nbt tag to convert - * @return the value representation of a tag - * @throws UnsupportedOperationException if the tag type is not supported - */ - @NotNull - public static Object fromNBT(@NotNull NBT nbt) { - if (nbt instanceof NBTNumber) { - return ((NBTNumber) nbt).getValue(); - } else if (nbt instanceof NBTString) { - return ((NBTString) nbt).getValue(); - } else if (nbt instanceof NBTByteArray) { - return ((NBTByteArray) nbt).getValue(); - } else if (nbt instanceof NBTIntArray) { - return ((NBTIntArray) nbt).getValue(); - } else if (nbt instanceof NBTLongArray) { - return ((NBTLongArray) nbt).getValue(); - } - - throw new UnsupportedOperationException("NBT type " + nbt.getClass() + " is not handled properly."); - } - @FunctionalInterface public interface EnchantmentSetter { void applyEnchantment(Enchantment name, short level); diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java index 152f65268..2f6edee3d 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java @@ -9,7 +9,6 @@ import net.minestom.server.adventure.AdventureSerializer; import net.minestom.server.chat.JsonMessage; import net.minestom.server.item.ItemStack; import net.minestom.server.utils.BlockPosition; -import net.minestom.server.utils.NBTUtils; import net.minestom.server.utils.SerializerUtils; import net.minestom.server.utils.Utils; import org.jetbrains.annotations.NotNull; @@ -264,7 +263,14 @@ public class BinaryWriter extends OutputStream { } public void writeItemStack(@NotNull ItemStack itemStack) { - NBTUtils.writeItemStack(this, itemStack); + if (itemStack.isAir()) { + writeBoolean(false); + } else { + writeBoolean(true); + writeVarInt(itemStack.getMaterial().getId()); + writeByte((byte) itemStack.getAmount()); + write(itemStack.getMeta()); + } } public void writeNBT(@NotNull String name, @NotNull NBT tag) { diff --git a/src/main/java/net/minestom/server/utils/ownership/OwnershipHandler.java b/src/main/java/net/minestom/server/utils/ownership/OwnershipHandler.java deleted file mode 100644 index 7cfdfca08..000000000 --- a/src/main/java/net/minestom/server/utils/ownership/OwnershipHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package net.minestom.server.utils.ownership; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Convenient class to keep trace of objects linked to an {@link UUID}. - * - * @param the owned object type - */ -public class OwnershipHandler { - - // identifier = the object having an ownership - private ConcurrentHashMap ownershipDataMap = new ConcurrentHashMap<>(); - - /** - * Generates a new unique identifier. - *

- * Does call {@link UUID#randomUUID()} internally. - * - * @return a new generated identifier - */ - public UUID generateIdentifier() { - return UUID.randomUUID(); - } - - /** - * Retrieves the owned object based on its identifier. - * - * @param identifier the object identifier - * @return the own object, null if not found - */ - @Nullable - public T getOwnObject(@NotNull UUID identifier) { - return ownershipDataMap.get(identifier); - } - - /** - * Saves, replace or remove the own object of an identifier. - * - * @param identifier the identifier of the object - * @param value the value of the object, can override the previous value, null means removing the identifier - */ - public void saveOwnObject(@NotNull UUID identifier, @Nullable T value) { - if (value != null) { - this.ownershipDataMap.put(identifier, value); - } else { - clearCache(identifier); - } - } - - public void clearCache(@NotNull UUID identifier) { - this.ownershipDataMap.remove(identifier); - } - -} diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeParticles.java b/src/main/java/net/minestom/server/world/biomes/BiomeParticles.java index 0cef0feec..721e89790 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeParticles.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeParticles.java @@ -100,7 +100,7 @@ public class BiomeParticles { @Override public NBTCompound toNbt() { //todo test count might be wrong type - NBTCompound nbtCompound = item.toNBT(); + NBTCompound nbtCompound = item.getMeta().toNBT(); nbtCompound.setString("type", type); return nbtCompound; } diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java index 7b2adda4a..ac677a688 100644 --- a/src/test/java/demo/Main.java +++ b/src/test/java/demo/Main.java @@ -47,6 +47,7 @@ public class Main { commandManager.register(new EchoCommand()); commandManager.register(new SummonCommand()); commandManager.register(new RemoveCommand()); + commandManager.register(new GiveCommand()); commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED))); diff --git a/src/test/java/demo/PlayerInit.java b/src/test/java/demo/PlayerInit.java index 6c34c2fc1..3a99035b9 100644 --- a/src/test/java/demo/PlayerInit.java +++ b/src/test/java/demo/PlayerInit.java @@ -1,6 +1,5 @@ package demo; -import com.google.common.util.concurrent.AtomicDouble; import demo.generator.ChunkGeneratorDemo; import demo.generator.NoiseTestGenerator; import net.kyori.adventure.text.Component; @@ -27,18 +26,20 @@ import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.InventoryType; import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.ItemTag; import net.minestom.server.item.Material; +import net.minestom.server.item.metadata.CompassMeta; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.network.ConnectionManager; import net.minestom.server.ping.ResponseDataConsumer; -import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; -import net.minestom.server.utils.inventory.PlayerInventoryUtils; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; import java.util.Collection; +import java.util.Collections; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @@ -57,24 +58,40 @@ public class PlayerInit { instanceContainer.enableAutoChunkLoad(true); instanceContainer.setChunkGenerator(chunkGeneratorDemo); - inventory = new Inventory(InventoryType.CHEST_1_ROW, "Test inventory"); + inventory = new Inventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory")); /*inventory.addInventoryCondition((p, slot, clickType, inventoryConditionResult) -> { p.sendMessage("click type inventory: " + clickType); System.out.println("slot inv: " + slot)0; inventoryConditionResult.setCancel(slot == 3); });*/ - //inventory.setItemStack(3, new ItemStack(Material.DIAMOND, (byte) 34)); - } + inventory.setItemStack(3, ItemStack.of(Material.DIAMOND, 34)); - private static final AtomicDouble LAST_TICK_TIME = new AtomicDouble(); + { + CompassMeta compassMeta = new CompassMeta.Builder() + .lodestonePosition(new Position(0, 0, 0)) + .build(); + + ItemStack itemStack = ItemStack.builder(Material.COMPASS) + .meta(CompassMeta.class, builder -> { + builder.lodestonePosition(new Position(0, 0, 0)); + builder.set(ItemTag.Integer("int"), 25); + }) + .build(); + + itemStack = itemStack.with(itemBuilder -> itemBuilder + .amount(10) + .meta(CompassMeta.class, builder -> { + builder.lodestonePosition(new Position(5, 0, 0)); + }) + .lore(Component.text("Lore"))); + } + + } public static void init() { ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager(); - MinecraftServer.getUpdateManager().addTickMonitor(tickMonitor -> - LAST_TICK_TIME.set(tickMonitor.getTickTime())); - MinecraftServer.getSchedulerManager().buildTask(() -> { Collection players = connectionManager.getOnlinePlayers(); @@ -85,9 +102,7 @@ public class PlayerInit { long ramUsage = benchmarkManager.getUsedMemory(); ramUsage /= 1e6; // bytes to MB - final Component header = Component.text("RAM USAGE: " + ramUsage + " MB") - .append(Component.newline()) - .append(Component.text("TICK TIME: " + MathUtils.round(LAST_TICK_TIME.get(), 2) + "ms")); + final Component header = Component.text("RAM USAGE: " + ramUsage + " MB"); final Component footer = benchmarkManager.getCpuMonitoringMessage(); Audiences.players().sendPlayerListHeaderAndFooter(header, footer); @@ -189,6 +204,7 @@ public class PlayerInit { globalEventHandler.addEventCallback(PlayerLoginEvent.class, event -> { final Player player = event.getPlayer(); + player.sendMessage("test"); event.setSpawningInstance(instanceContainer); int x = Math.abs(ThreadLocalRandom.current().nextInt()) % 1000 - 250; @@ -210,19 +226,31 @@ public class PlayerInit { player.setPermissionLevel(4); PlayerInventory inventory = player.getInventory(); - ItemStack itemStack = new ItemStack(Material.STONE, (byte) 64); + ItemStack itemStack = ItemStack.builder(Material.STONE) + .amount(64) + .meta(itemMetaBuilder -> + itemMetaBuilder.canPlaceOn(Set.of(Block.STONE)) + .canDestroy(Set.of(Block.DIAMOND_ORE))) + .build(); + + System.out.println(itemStack.getMeta().toSNBT()); + + //itemStack = itemStack.withStore(storeBuilder -> storeBuilder.set("key2", 25, Integer::sum)); + inventory.addItemStack(itemStack); { - ItemStack item = new ItemStack(Material.DIAMOND_CHESTPLATE, (byte) 1); - inventory.setChestplate(item); - item.setDisplayName(ColoredText.of("test")); + ItemStack item = ItemStack.builder(Material.DIAMOND_CHESTPLATE) + .displayName(Component.text("test")) + .lore(Component.text("lore")) + .build(); - inventory.refreshSlot((short) PlayerInventoryUtils.CHESTPLATE_SLOT); + //inventory.setChestplate(item); + inventory.setChestplate(item.with(itemStackBuilder -> { + itemStackBuilder.lore(Collections.emptyList()); + })); } - - //player.getInventory().addItemStack(new ItemStack(Material.STONE, (byte) 32)); }); globalEventHandler.addEventCallback(PlayerBlockBreakEvent.class, event -> { diff --git a/src/test/java/demo/commands/GiveCommand.java b/src/test/java/demo/commands/GiveCommand.java new file mode 100644 index 000000000..5e5baba26 --- /dev/null +++ b/src/test/java/demo/commands/GiveCommand.java @@ -0,0 +1,58 @@ +package demo.commands; + +import net.kyori.adventure.text.Component; +import net.minestom.server.command.builder.Command; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Player; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.TransactionOption; +import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.entity.EntityFinder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static net.minestom.server.command.builder.arguments.ArgumentType.Integer; +import static net.minestom.server.command.builder.arguments.ArgumentType.*; + +public class GiveCommand extends Command { + public GiveCommand() { + super("give"); + + setDefaultExecutor((sender, context) -> + sender.sendMessage(Component.text("Usage: /give []"))); + + addSyntax((sender, context) -> { + final EntityFinder entityFinder = context.get("target"); + int count = context.get("count"); + count = Math.min(count, PlayerInventory.INVENTORY_SIZE * 64); + ItemStack itemStack = context.get("item"); + + List itemStacks; + if (count <= 64) { + itemStack = itemStack.withAmount(count); + itemStacks = Collections.singletonList(itemStack); + } else { + itemStacks = new ArrayList<>(); + while (count > 64) { + itemStacks.add(itemStack.withAmount(64)); + count -= 64; + } + itemStacks.add(itemStack.withAmount(count)); + } + + final List targets = entityFinder.find(sender); + for (Entity target : targets) { + if (target instanceof Player) { + Player player = (Player) target; + player.getInventory().addItemStacks(itemStacks, TransactionOption.ALL); + } + } + + sender.sendMessage(Component.text("Items have been given successfully!")); + + }, Entity("target").onlyPlayers(true), ItemStack("item"), Integer("count").setDefaultValue(() -> 1)); + + } +} diff --git a/src/test/java/readwritepackets/ReadWritePackets.java b/src/test/java/readwritepackets/ReadWritePackets.java index 56c192e77..5f28d8152 100644 --- a/src/test/java/readwritepackets/ReadWritePackets.java +++ b/src/test/java/readwritepackets/ReadWritePackets.java @@ -47,13 +47,13 @@ public class ReadWritePackets { private Collection checkImplementationPresence(Class packetClass) throws IOException { ClassPath cp = ClassPath.from(ClassLoader.getSystemClassLoader()); List allTests = new LinkedList<>(); - for(ClassPath.ClassInfo classInfo : cp.getAllClasses()) { - if(!classInfo.getPackageName().startsWith("net.minestom.server.network.packet")) + for (ClassPath.ClassInfo classInfo : cp.getAllClasses()) { + if (!classInfo.getPackageName().startsWith("net.minestom.server.network.packet")) continue; try { Class clazz = classInfo.load(); - if(packetClass.isAssignableFrom(clazz) && !clazz.isInterface() && ((clazz.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT)) { - allTests.add(DynamicTest.dynamicTest("WriteThenRead "+clazz.getSimpleName(), () -> { + if (packetClass.isAssignableFrom(clazz) && !clazz.isInterface() && ((clazz.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT)) { + allTests.add(DynamicTest.dynamicTest("WriteThenRead " + clazz.getSimpleName(), () -> { // required for managers to be loaded MinecraftServer.init(); @@ -63,14 +63,13 @@ public class ReadWritePackets { T packet; // exceptions - if(clazz.getSimpleName().equals("EntityEquipmentPacket")) { + if (clazz.getSimpleName().equals("EntityEquipmentPacket")) { // requires at least one slot and one item EntityEquipmentPacket p = new EntityEquipmentPacket(); - p.itemStacks = new ItemStack[] { ItemStack.getAirItem() }; - p.slots = new EntityEquipmentPacket.Slot[] { EntityEquipmentPacket.Slot.MAIN_HAND }; + p.itemStacks = new ItemStack[]{ItemStack.AIR}; + p.slots = new EntityEquipmentPacket.Slot[]{EntityEquipmentPacket.Slot.MAIN_HAND}; packet = (T) p; - } - else { + } else { packet = (T) constructor.newInstance(); }