diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java index 2516082d7..2fb89afc3 100644 --- a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java +++ b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java @@ -6,8 +6,9 @@ import net.minestom.server.entity.GameMode; import net.minestom.server.entity.type.decoration.EntityItemFrame; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceManager; +import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -import net.minestom.server.item.metadata.MapMeta; +import net.minestom.server.item.meta.MapMeta; import net.minestom.server.map.Framebuffer; import net.minestom.server.map.LargeFramebuffer; import net.minestom.server.map.MapColors; @@ -67,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/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/inventory/AbstractInventory.java b/src/main/java/net/minestom/server/inventory/AbstractInventory.java new file mode 100644 index 000000000..88abcb383 --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/AbstractInventory.java @@ -0,0 +1,316 @@ +package net.minestom.server.inventory; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minestom.server.inventory.condition.InventoryCondition; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.StackingRule; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.UnaryOperator; + +/** + * Represents an inventory where items can be modified/retrieved. + */ +public abstract class AbstractInventory { + + /** + * 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 abstract void setItemStack(int slot, @NotNull ItemStack itemStack); + + protected abstract void safeItemInsert(int slot, @NotNull ItemStack itemStack); + + /** + * Adds an {@link ItemStack} to the inventory and sends 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 + */ + public boolean addItemStack(@NotNull ItemStack itemStack) { + Int2ObjectMap itemChangesMap = new Int2ObjectOpenHashMap<>(); + + final StackingRule stackingRule = itemStack.getStackingRule(); + for (int i = 0; i < getInnerSize(); i++) { + ItemStack inventoryItem = 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, inventoryItem.withAmount(totalAmount)); + itemStack = ItemStack.AIR; + break; + } + } + } + for (int i = 0; i < getInnerSize(); i++) { + ItemStack inventoryItem = getItemStack(i); + if (!inventoryItem.isAir()) { + continue; + } + // Fill the slot + itemChangesMap.put(i, itemStack); + itemStack = ItemStack.AIR; + break; + } + + if (itemStack.isAir()) { + // Item can be fully placed inside the inventory, do so + itemChangesMap.forEach(this::safeItemInsert); + return true; + } else { + // Inventory cannot accept the item fully + return false; + } + } + + /** + * Adds {@link ItemStack}s to the inventory and sends relevant updates to the viewer(s). + *

+ * Even items cannot be fully added, the amount of {@code itemStack}s will be updated. + * + * @param itemStacks items to add + * @return list of itemstacks that could not be successfully fully added, empty list otherwise + */ + public List addItemStacks(@NotNull List itemStacks) { + List result = new ArrayList<>(); + itemStacks.forEach(itemStack -> { + if (!addItemStack(itemStack)) { + result.add(itemStack); + } + }); + return result; + } + + /** + * Checks whether {@link ItemStack} can be fully added to the inventory. + * + * @param itemStack the item to be checked + * @return true if the item can be fully added to the inventory, false otherwise + */ + public boolean canAddItemStack(@NotNull ItemStack itemStack) { + final StackingRule stackingRule = itemStack.getStackingRule(); + int amountLeft = itemStack.getAmount(); + for (int i = 0; i < getInnerSize(); i++) { + ItemStack inventoryItem = getItemStack(i); + if (stackingRule.canBeStacked(itemStack, inventoryItem)) { + final int itemAmount = stackingRule.getAmount(inventoryItem); + if (itemAmount == stackingRule.getMaxSize()) + continue; + if (!stackingRule.canApply(itemStack, amountLeft + itemAmount)) { + // Slot cannot accept the whole item, reduce amount to 'itemStack' + amountLeft -= stackingRule.getMaxSize() - itemAmount; + } else { + return true; + } + } else if (inventoryItem.isAir()) { + return true; + } + } + return false; + } + + /** + * Checks whether {@link ItemStack}s can be fully added to the inventory. + * + * @param itemStacks items to be checked + * @return true if all the items can be fully added to the inventory, false otherwise + */ + public boolean canAddItemStacks(@NotNull List itemStacks) { + return itemStacks.stream().allMatch(this::canAddItemStack); + } + + /** + * Takes an {@link ItemStack} from the inventory and sends relevant update to the viewer(s). + *

+ * Even the item cannot be fully taken, the amount of {@code itemStack} will be updated. + * + * @param itemStack the item to take + * @return true if the item has been successfully fully taken, false otherwise + */ + public boolean takeItemStack(@NotNull ItemStack itemStack) { + Int2ObjectMap itemChangesMap = new Int2ObjectOpenHashMap<>(); + + final StackingRule stackingRule = itemStack.getStackingRule(); + for (int i = 0; i < getInnerSize(); i++) { + ItemStack inventoryItem = 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 = ItemStack.AIR; + break; + } + itemChangesMap.put(i, ItemStack.AIR); + itemStack = itemStack.withAmount(amount -> amount - itemAmount); + if (itemStack.getAmount() == 0) { + itemStack = ItemStack.AIR; + break; + } + } + } + + if (itemStack.isAir()) { + // Item can be fully taken from the inventory, do so + itemChangesMap.forEach(this::safeItemInsert); + return true; + } else { + return false; + } + } + + /** + * Takes {@link ItemStack}s from the inventory and sends relevant updates to the viewer(s). + *

+ * Even items cannot be fully taken, the amount of {@code itemStack}s will be updated. + * + * @param itemStacks items to take + * @return list of itemstacks that could not be successfully fully taken, empty list otherwise + */ + public List takeItemStacks(@NotNull List itemStacks) { + List result = new ArrayList<>(); + itemStacks.forEach(itemStack -> { + if (!takeItemStack(itemStack)) { + result.add(itemStack); + } + }); + return result; + } + + /** + * Checks whether {@link ItemStack} can be fully taken from the inventory. + * + * @param itemStack the item to be checked + * @return true if the item can be fully taken from the inventory, false otherwise + */ + public boolean canTakeItemStack(@NotNull ItemStack itemStack) { + final StackingRule stackingRule = itemStack.getStackingRule(); + for (int i = 0; i < getInnerSize(); i++) { + ItemStack inventoryItem = 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) { + return true; + } + itemStack = itemStack.withAmount(amount -> amount - itemAmount); + } + } + return false; + } + + /** + * Checks whether {@link ItemStack}s can be fully taken from the inventory. + * + * @param itemStacks items to be checked + * @return true if all the items can be fully taken from the inventory, false otherwise + */ + public boolean canTakeItemStacks(@NotNull List itemStacks) { + return itemStacks.stream().allMatch(this::canTakeItemStack); + } + + public 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 abstract 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 + public abstract 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 + public abstract ItemStack[] getItemStacks(); + + /** + * Gets the size of the inventory. + * + * @return the inventory's size + */ + public abstract int getSize(); + + /** + * 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 + */ + @NotNull + public abstract List getInventoryConditions(); + + /** + * Adds a new {@link InventoryCondition} to this inventory. + * + * @param inventoryCondition the inventory condition to add + */ + public abstract 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 + */ + 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); + } + } +} diff --git a/src/main/java/net/minestom/server/inventory/Inventory.java b/src/main/java/net/minestom/server/inventory/Inventory.java index f3baf2fc3..0bb92d6e9 100644 --- a/src/main/java/net/minestom/server/inventory/Inventory.java +++ b/src/main/java/net/minestom/server/inventory/Inventory.java @@ -37,7 +37,7 @@ import java.util.function.UnaryOperator; * 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 InventoryClickHandler, Viewable, DataContainer { // incremented each time an inventory is created (used in the window packets) private static final AtomicInteger LAST_INVENTORY_ID = new AtomicInteger(); @@ -150,19 +150,13 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View @Override public synchronized boolean addItemStack(@NotNull ItemStack itemStack) { // Make the method synchronized - return InventoryModifier.super.addItemStack(itemStack); - } - - @Override - public synchronized boolean addItemStack(@NotNull ItemStack itemStack, int startSlot, int endSlot) { - // Make the method synchronized - return InventoryModifier.super.addItemStack(itemStack, startSlot, endSlot); + return super.addItemStack(itemStack); } @Override public synchronized void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) { // Make the method synchronized - InventoryModifier.super.replaceItemStack(slot, operator); + super.replaceItemStack(slot, operator); } @Override @@ -299,7 +293,8 @@ 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) { + @Override + protected synchronized void safeItemInsert(int slot, @NotNull ItemStack itemStack) { this.itemStacks[slot] = itemStack; SetSlotPacket setSlotPacket = new SetSlotPacket(); setSlotPacket.windowId = getWindowId(); 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 0a52f59d4..000000000 --- a/src/main/java/net/minestom/server/inventory/InventoryModifier.java +++ /dev/null @@ -1,145 +0,0 @@ -package net.minestom.server.inventory; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.minestom.server.inventory.condition.InventoryCondition; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.StackingRule; -import net.minestom.server.utils.validate.Check; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.function.UnaryOperator; - -/** - * 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 - */ - default boolean addItemStack(@NotNull ItemStack itemStack) { - return addItemStack(itemStack, 0, getSize()); - } - - default boolean addItemStack(@NotNull ItemStack itemStack, int startSlot, int endSlot) { - Int2ObjectMap itemChangesMap = new Int2ObjectOpenHashMap<>(); - - final StackingRule stackingRule = itemStack.getStackingRule(); - for (int i = startSlot; i < endSlot; i++) { - ItemStack inventoryItem = getItemStack(i); - 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, inventoryItem.withAmount(totalAmount)); - itemStack = ItemStack.AIR; - break; - } - } else if (inventoryItem.isAir()) { - // Fill the slot - itemChangesMap.put(i, itemStack); - itemStack = ItemStack.AIR; - break; - } - } - - if (itemStack.isAir()) { - // Item can be fully placed inside the inventory, do so - itemChangesMap.forEach(this::setItemStack); - return true; - } else { - // Inventory cannot accept the item fully - return false; - } - } - - default 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). - */ - 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 d5a79ff4b..72ef75e96 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -30,9 +30,10 @@ 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 InventoryClickHandler, EquipmentHandler, DataContainer { public static final int INVENTORY_SIZE = 46; + public static final int INNER_INVENTORY_SIZE = 36; protected final Player player; protected final ItemStack[] itemStacks = new ItemStack[INVENTORY_SIZE]; @@ -93,24 +94,13 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler return false; itemStack = addItemStackEvent.getItemStack(); - return InventoryModifier.super.addItemStack(itemStack, 0, getSize() - 10); - } - - @Override - public synchronized boolean addItemStack(@NotNull ItemStack itemStack, int startSlot, int endSlot) { - PlayerAddItemStackEvent addItemStackEvent = new PlayerAddItemStackEvent(player, itemStack); - player.callEvent(PlayerAddItemStackEvent.class, addItemStackEvent); - if (addItemStackEvent.isCancelled()) - return false; - - itemStack = addItemStackEvent.getItemStack(); - return InventoryModifier.super.addItemStack(itemStack, startSlot, endSlot); + return super.addItemStack(itemStack); } @Override public synchronized void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) { // Make the method synchronized - InventoryModifier.super.replaceItemStack(slot, operator); + super.replaceItemStack(slot, operator); } @Override @@ -130,6 +120,11 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler return INVENTORY_SIZE; } + @Override + public int getInnerSize() { + return INNER_INVENTORY_SIZE; + } + @NotNull @Override public ItemStack getItemInMainHand() { @@ -237,6 +232,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"); 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/meta/MapMeta.java b/src/main/java/net/minestom/server/item/meta/MapMeta.java new file mode 100644 index 000000000..e67d05e8e --- /dev/null +++ b/src/main/java/net/minestom/server/item/meta/MapMeta.java @@ -0,0 +1,278 @@ +package net.minestom.server.item.meta; + +import net.minestom.server.MinecraftServer; +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.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.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; + +public class MapMeta extends ItemMeta { + + private final int mapId; + private final int mapScaleDirection; + private final List decorations; + private final Color mapColor; + + 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; + } + + /** + * Gets the map id. + * + * @return the map id + */ + public int getMapId() { + return mapId; + } + + /** + * Gets the map scale direction. + * + * @return the map scale direction + */ + public int getMapScaleDirection() { + return mapScaleDirection; + } + + /** + * Gets the map decorations. + * + * @return a modifiable list containing all the map decorations + */ + public List getDecorations() { + return decorations; + } + + /** + * Gets the map color. + * + * @return the map color + * @deprecated Use {@link #getMapColor()} + */ + @Deprecated + public ChatColor getLegacyMapColor() { + return this.mapColor.asLegacyChatColor(); + } + + /** + * Gets the map color. + * + * @return the map color + */ + public @NotNull Color getMapColor() { + return this.mapColor; + } + + public static class Builder extends ItemMetaBuilder { + + private int mapId; + private int mapScaleDirection = 1; + private List decorations = new CopyOnWriteArrayList<>(); + private Color mapColor = new Color(0, 0, 0); + + public Builder mapId(int value) { + this.mapId = value; + return this; + } + + public Builder mapScaleDirection(int value) { + this.mapScaleDirection = value; + return this; + } + + public Builder decorations(List value) { + this.decorations = value; + return this; + } + + public Builder mapColor(Color value) { + this.mapColor = value; + 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")) { + this.mapId = compound.getAsInt("map"); + } + + if (compound.containsKey("map_scale_direction")) { + this.mapScaleDirection = compound.getAsInt("map_scale_direction"); + } + + 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; + + 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(); + decorationCompound.setString("id", decoration.getId()); + decorationCompound.setByte("type", decoration.getType()); + decorationCompound.setByte("x", decoration.getX()); + decorationCompound.setByte("z", decoration.getZ()); + decorationCompound.setDouble("rot", decoration.getRotation()); + + decorationsList.add(decorationCompound); + } + compound.set("Decorations", decorationsList); + } + + { + NBTCompound displayCompound; + if (compound.containsKey("display")) { + displayCompound = compound.getCompound("display"); + } else { + displayCompound = new NBTCompound(); + } + displayCompound.setInt("MapColor", mapColor.asRGB()); + } + } + + @Override + protected void deepClone(@NotNull ItemMetaBuilder metaBuilder) { + var mapBuilder = (MapMeta.Builder) metaBuilder; + mapBuilder.mapId = mapId; + mapBuilder.mapScaleDirection = mapScaleDirection; + mapBuilder.decorations = decorations; + mapBuilder.mapColor = mapColor; + } + + @Override + protected @NotNull Supplier<@NotNull ItemMetaBuilder> getSupplier() { + return Builder::new; + } + } + + public static class MapDecoration implements PublicCloneable { + private final String id; + private final byte type; + private final byte x, z; + private final double rotation; + + public MapDecoration(@NotNull String id, byte type, byte x, byte z, double rotation) { + this.id = id; + this.type = type; + this.x = x; + this.z = z; + this.rotation = rotation; + } + + /** + * Gets the arbitrary decoration id. + * + * @return the decoration id + */ + public String getId() { + return id; + } + + /** + * Gets the decoration type. + * + * @return the decoration type + * @see Map icons + */ + public byte getType() { + return type; + } + + /** + * Gets the X position of the decoration. + * + * @return the X position + */ + public byte getX() { + return x; + } + + /** + * Gets the Z position of the decoration. + * + * @return the Z position + */ + public byte getZ() { + return z; + } + + /** + * Gets the rotation of the symbol (0;360). + * + * @return the rotation of the symbol + */ + public double getRotation() { + return rotation; + } + + @NotNull + @Override + public MapDecoration clone() { + try { + return (MapDecoration) super.clone(); + } catch (CloneNotSupportedException e) { + MinecraftServer.getExceptionManager().handleException(e); + throw new IllegalStateException("Something weird happened"); + } + } + } + +} diff --git a/src/main/java/net/minestom/server/item/metadata/ItemMeta.java b/src/main/java/net/minestom/server/item/metadata/ItemMeta.java index 72effbebe..4e200e331 100644 --- a/src/main/java/net/minestom/server/item/metadata/ItemMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/ItemMeta.java @@ -1,6 +1,7 @@ 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; 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 f29e25e97..7fef87752 100644 --- a/src/main/java/net/minestom/server/item/metadata/MapMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/MapMeta.java @@ -1,10 +1,8 @@ package net.minestom.server.item.metadata; -import net.kyori.adventure.text.format.TextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.chat.ChatColor; import net.minestom.server.color.Color; -import net.minestom.server.color.DyeColor; import net.minestom.server.utils.clone.CloneUtils; import net.minestom.server.utils.clone.PublicCloneable; import org.jetbrains.annotations.NotNull;