Merge pull request #206 from Minestom/item-api

New Item API
This commit is contained in:
TheMode 2021-04-12 04:03:23 +02:00 committed by GitHub
commit 270a466a81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 2369 additions and 3397 deletions

View File

@ -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);

View File

@ -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);
}
/**

View File

@ -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

View File

@ -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);
}

View File

@ -42,13 +42,11 @@ public class ArgumentItemStack extends Argument<ItemStack> {
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<ItemStack> {
throw new ArgumentSyntaxException("Item NBT is invalid", input, INVALID_NBT);
}
NBTUtils.loadDataIntoItem(itemStack, compound);
return itemStack;
return NBTUtils.loadItem(material, 1, compound);
}
}

View File

@ -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<Inventory> {
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<Inventory> {
@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();

View File

@ -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, () -> {

View File

@ -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

View File

@ -343,7 +343,7 @@ public class Metadata {
case TYPE_OPTCHAT:
return (Value<T>) OptChat((Component) null);
case TYPE_SLOT:
return (Value<T>) Slot(ItemStack.getAirItem());
return (Value<T>) Slot(ItemStack.AIR);
case TYPE_BOOLEAN:
return (Value<T>) Boolean(false);
case TYPE_ROTATION:

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -31,10 +31,10 @@ public class LootTable {
}
public List<ItemStack> generate(Data arguments) {
if(arguments == null)
if (arguments == null)
arguments = Data.EMPTY;
List<ItemStack> 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<ItemStack> 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<Entry> 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<ItemStack> output, Data arguments) {
for(Condition c : conditions) {
if(!c.test(arguments))
for (Condition c : conditions) {
if (!c.test(arguments))
return;
}
generate(output, arguments);

View File

@ -17,11 +17,11 @@ public class AlternativesEntry extends LootTable.Entry {
@Override
public void generate(List<ItemStack> 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;
}
}

View File

@ -23,11 +23,11 @@ public class ItemEntry extends LootTable.Entry {
@Override
public void generate(List<ItemStack> 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);
}
}

View File

@ -17,11 +17,11 @@ public class SequenceEntry extends LootTable.Entry {
@Override
public void generate(List<ItemStack> 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;
}
}

View File

@ -27,19 +27,19 @@ public class TagEntry extends LootTable.Entry {
@Override
public void generate(List<ItemStack> output, Data arguments) {
Set<NamespaceID> 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));
}
}
}

View File

@ -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<InventoryCondition> 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 <T> @NotNull T processItemStack(@NotNull ItemStack itemStack,
@NotNull TransactionType type,
@NotNull TransactionOption<T> option) {
var pair = type.process(this, itemStack);
return option.fill(this, pair.left(), pair.right());
}
public synchronized <T> @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
@NotNull TransactionType type,
@NotNull TransactionOption<T> option) {
List<T> 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 <T> @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> 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 <T> @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
@NotNull TransactionOption<T> 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 <T> @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> 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 <T> @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
@NotNull TransactionOption<T> 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.
* <p>
* 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;
}
}

View File

@ -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<Player> viewers = new CopyOnWriteArraySet<>();
private final Set<Player> unmodifiableViewers = Collections.unmodifiableSet(viewers);
// (player -> cursor item) map, used by the click listeners
private final ConcurrentHashMap<Player, ItemStack> cursorPlayersItem = new ConcurrentHashMap<>();
// list of conditions/callbacks assigned to this inventory
private final List<InventoryCondition> 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<InventoryCondition> 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<Player> 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.
* <p>
* 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
* <p>
* 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;
}
}

View File

@ -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).
* <p>
* 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.
* <p>
* 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<InventoryCondition> 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);
}
}
}

View File

@ -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<InventoryCondition> 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<InventoryCondition> 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;
}
}

View File

@ -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<T> {
/**
* Place as much as the item as possible.
* <p>
* The remaining, can be air.
*/
TransactionOption<ItemStack> ALL = (inventory, result, itemChangesMap) -> {
itemChangesMap.forEach(inventory::safeItemInsert);
return result;
};
/**
* Only place the item if can be fully added.
* <p>
* Returns true if the item has been added, false if nothing changed.
*/
TransactionOption<Boolean> 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.
* <p>
* Returns true if the item can be fully added, false otherwise.
*/
TransactionOption<Boolean> DRY_RUN = (inventory, result, itemChangesMap) -> result.isAir();
@NotNull T fill(@NotNull AbstractInventory inventory,
@NotNull ItemStack result,
@NotNull Map<@NotNull Integer, @NotNull ItemStack> itemChangesMap);
}

View File

@ -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<ItemStack> 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<ItemStack> 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<ItemStack, Map<Integer, ItemStack>> process(@NotNull AbstractInventory inventory,
@NotNull ItemStack itemStack);
}

View File

@ -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) {

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<Component> lore;
private final Map<Enchantment, Short> enchantmentMap;
private final List<ItemAttribute> attributes;
private final int customModelData;
private final Set<Block> canDestroy;
private final Set<Block> 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<Enchantment, Short> 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> T getOrDefault(@NotNull ItemTag<T> tag, @Nullable T defaultValue) {
var key = tag.getKey();
if (nbt.containsKey(key)) {
return tag.read(toNBT());
} else {
return defaultValue;
}
}
public <T> @Nullable T get(@NotNull ItemTag<T> 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();
}
}

View File

@ -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<Component> lore = new ArrayList<>();
protected Map<Enchantment, Short> enchantmentMap = new HashMap<>();
protected List<ItemAttribute> attributes = new ArrayList<>();
protected int customModelData;
protected Set<Block> canDestroy = new HashSet<>();
protected Set<Block> 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<NBTString> 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<Enchantment, Short> 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<NBTCompound> 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<NBTString> 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<NBTString> 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 <T> @NotNull ItemMetaBuilder set(@NotNull ItemTag<T> 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<T extends ItemMetaBuilder> {
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<ItemMetaBuilder>> 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 <T extends ItemMetaBuilder, U extends ItemMetaBuilder.Provider<T>> @NotNull ItemStackBuilder meta(@NotNull Class<U> 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;
}
}
}

View File

@ -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<T> {
private final String key;
private final Function<NBTCompound, T> readFunction;
private final BiConsumer<NBTCompound, T> writeConsumer;
private ItemTag(@NotNull String key,
@NotNull Function<NBTCompound, T> readFunction,
@NotNull BiConsumer<NBTCompound, T> 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> Byte(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getByte(key),
(nbtCompound, value) -> nbtCompound.setByte(key, value));
}
public static @NotNull ItemTag<Short> Short(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getShort(key),
(nbtCompound, value) -> nbtCompound.setShort(key, value));
}
public static @NotNull ItemTag<Integer> Integer(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getInt(key),
(nbtCompound, integer) -> nbtCompound.setInt(key, integer));
}
public static @NotNull ItemTag<Long> Long(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getLong(key),
(nbtCompound, value) -> nbtCompound.setLong(key, value));
}
public static @NotNull ItemTag<Float> Float(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getFloat(key),
(nbtCompound, value) -> nbtCompound.setFloat(key, value));
}
public static @NotNull ItemTag<Double> Double(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getDouble(key),
(nbtCompound, value) -> nbtCompound.setDouble(key, value));
}
public static @NotNull ItemTag<byte[]> ByteArray(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getByteArray(key),
(nbtCompound, value) -> nbtCompound.setByteArray(key, value));
}
public static @NotNull ItemTag<String> 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<int[]> IntArray(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getIntArray(key),
(nbtCompound, value) -> nbtCompound.setIntArray(key, value));
}
public static @NotNull ItemTag<long[]> LongArray(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getLongArray(key),
(nbtCompound, value) -> nbtCompound.setLongArray(key, value));
}
}

View File

@ -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}.

View File

@ -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<CompassMeta.Builder> {
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<ItemMetaBuilder> getSupplier() {
return Builder::new;
}
}
}

View File

@ -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<SpawnEggMeta.Builder> {
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<NBTCompound> 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<NBTCompound> 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<NBTCompound> 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<NBTCompound> projectilesList = nbtCompound.getList("ChargedProjectiles");
List<ItemStack> 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<NBTCompound> 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<ItemMetaBuilder> 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;
}
}
}

View File

@ -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<EnchantedBookMeta.Builder> {
private final Object2ShortMap<Enchantment> storedEnchantmentMap = new Object2ShortOpenHashMap<>();
private final Map<Enchantment, Short> storedEnchantmentMap;
protected EnchantedBookMeta(@NotNull ItemMetaBuilder metaBuilder, Map<Enchantment, Short> 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<Enchantment, Short> getStoredEnchantmentMap() {
public @NotNull Map<Enchantment, Short> 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<Enchantment, Short> enchantments = new HashMap<>();
public @NotNull Builder enchantments(Map<Enchantment, Short> 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<ItemMetaBuilder> getSupplier() {
return Builder::new;
}
}
}

View File

@ -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<FireworkEffectMeta.Builder> {
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<ItemMetaBuilder> getSupplier() {
return Builder::new;
}
}
/**
* {@inheritDoc}
*/
@NotNull
@Override
public ItemMeta clone() {
FireworkEffectMeta fireworkEffectMeta = (FireworkEffectMeta) super.clone();
fireworkEffectMeta.fireworkEffect = this.fireworkEffect;
return fireworkEffectMeta;
}
}
}

View File

@ -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<FireworkMeta.Builder> {
private List<FireworkEffect> effects = new CopyOnWriteArrayList<>();
private byte flightDuration;
private final List<FireworkEffect> 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<FireworkEffect> 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<FireworkEffect> 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<FireworkEffect> 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<FireworkEffect> 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<FireworkEffect> effects) {
this.effects = effects;
handleCompound("Fireworks", nbtCompound -> {
NBTList<NBTCompound> 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<NBTCompound> 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<NBTCompound> 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<NBTCompound> 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<ItemMetaBuilder> 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;
}
}
}

View File

@ -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<ItemMeta> {
/**
* 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.
* <p>
* 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.
* <p>
* 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");
}
}
}

View File

@ -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<LeatherArmorMeta.Builder> {
/**
* 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<ItemMetaBuilder> getSupplier() {
return Builder::new;
}
}
/**
* {@inheritDoc}
*/
@NotNull
@Override
public ItemMeta clone() {
LeatherArmorMeta leatherArmorMeta = (LeatherArmorMeta) super.clone();
leatherArmorMeta.modified = this.isModified();
leatherArmorMeta.color = color;
return leatherArmorMeta;
}
}

View File

@ -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<MapDecoration> decorations = new CopyOnWriteArrayList<>();
private Color mapColor = new Color(0, 0, 0);
private final int mapId;
private final int mapScaleDirection;
private final List<MapDecoration> decorations;
private final Color mapColor;
public MapMeta() {
}
public MapMeta(int id) {
this.mapId = id;
protected MapMeta(ItemMetaBuilder metaBuilder,
int mapId,
int mapScaleDirection,
@NotNull List<MapDecoration> 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<MapDecoration> 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<MapDecoration> 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<NBTCompound> 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<MapDecoration> 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<NBTCompound> 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<NBTCompound> decorationsList = compound.getList("Decorations");
List<MapDecoration> 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<MapDecoration> {

View File

@ -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<PlayerHeadMeta.Builder> {
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<NBTCompound> 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<NBTCompound> 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<NBTCompound> 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<NBTCompound> 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<ItemMetaBuilder> 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;
}
}

View File

@ -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<PotionMeta.Builder> {
private PotionType potionType;
private final PotionType potionType;
private final List<CustomPotionEffect> customPotionEffects;
private final Color color;
// Not final because of #clone()
private List<CustomPotionEffect> customPotionEffects = new CopyOnWriteArrayList<>();
protected PotionMeta(@NotNull ItemMetaBuilder metaBuilder, @Nullable PotionType potionType,
List<CustomPotionEffect> 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<CustomPotionEffect> 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<CustomPotionEffect> 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<NBTCompound> 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<CustomPotionEffect> 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<NBTCompound> 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<NBTCompound> 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<ItemMetaBuilder> 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;
}
}
}

View File

@ -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<SpawnEggMeta.Builder> {
@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<ItemMetaBuilder> getSupplier() {
return Builder::new;
}
}
@NotNull
@Override
public ItemMeta clone() {
SpawnEggMeta spawnEggMeta = (SpawnEggMeta) super.clone();
spawnEggMeta.entityType = entityType;
return spawnEggMeta;
}
}
}

View File

@ -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<WritableBookMeta.Builder> {
private String title;
private String author;
private List<Component> pages = new ArrayList<>();
private final String author;
private final String title;
private final List<Component> 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.
* <p>
* The list is modifiable.
*
* @return a modifiable {@link ArrayList} containing the book pages
*/
@NotNull
public List<Component> 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<Component> 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<Component> 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<NBTString> 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<NBTString> 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<NBTString> 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<NBTString> 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<ItemMetaBuilder> getSupplier() {
return WritableBookMeta.Builder::new;
}
}
@NotNull
@Override
public ItemMeta clone() {
WritableBookMeta writableBookMeta = (WritableBookMeta) super.clone();
writableBookMeta.pages = new ArrayList<>(pages);
return writableBookMeta;
}
}
}

View File

@ -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<WrittenBookMeta.Builder> {
private boolean resolved;
private WrittenBookGeneration generation;
private String author;
private String title;
private List<Component> pages = new ArrayList<>();
private final boolean resolved;
private final WrittenBookGeneration generation;
private final String author;
private final String title;
private final List<Component> 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.
* <p>
* The list is not modifiable as it is .
*
* @return a modifiable {@link ArrayList} with the pages of the book
*/
@Deprecated
public List<Component> getPagesJson() {
return pages;
}
/**
* Sets the {@link ArrayList} containing the book pages.
*
* @param pages the array list containing the book pages
*/
public void setPages(List<Component> 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<NBTString> 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<NBTString> 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<Component> 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<NBTString> 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<NBTString> list = nbtCompound.getList("pages");
for (NBTString page : list) {
this.pages.add(GsonComponentSerializer.gson().deserialize(page.getValue()));
}
pages(pages);
}
}
@Override
protected @NotNull Supplier<ItemMetaBuilder> getSupplier() {
return Builder::new;
}
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<Slot> slots = new LinkedList<>();
List<ItemStack> stacks = new LinkedList<>();
while(hasRemaining) {
while (hasRemaining) {
byte slotEnum = reader.readByte();
hasRemaining = (slotEnum & 0x80) == 0x80;

View File

@ -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

View File

@ -14,7 +14,7 @@ public class SetSlotPacket implements ServerPacket {
public ItemStack itemStack;
public SetSlotPacket() {
itemStack = ItemStack.getAirItem();
itemStack = ItemStack.AIR;
}
@Override

View File

@ -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;

View File

@ -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<DeclareRecipesPacket.Ingredient> ingredients,
@NotNull ItemStack result) {
int width,
int height,
@NotNull String group,
@Nullable List<DeclareRecipesPacket.Ingredient> ingredients,
@NotNull ItemStack result) {
super(RecipeType.SHAPED, recipeId);
this.width = width;
this.height = height;

View File

@ -38,7 +38,7 @@ public abstract class ThreadProvider {
{
// Default thread count in the pool (cores * 2)
setThreadCount(NettyRuntime.availableProcessors() * 2);
setThreadCount(1);
}
/**

View File

@ -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<NBTCompound> 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<NBTString> 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<NBTCompound> attributes = nbt.getList("AttributeModifiers");
for (NBTCompound attributeNBT : attributes) {
List<ItemAttribute> attributes = new ArrayList<>();
NBTList<NBTCompound> 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<NBTString> canPlaceOn = nbt.getList("CanPlaceOn");
canPlaceOn.forEach(x -> item.getCanPlaceOn().add(x.getValue()));
Set<Block> 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<NBTString> canPlaceOn = nbt.getList("CanDestroy");
canPlaceOn.forEach(x -> item.getCanDestroy().add(x.getValue()));
NBTList<NBTString> canDestroy = nbt.getList("CanDestroy");
Set<Block> 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<Component> lore = itemStack.getLore();
final NBTList<NBTString> 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<Enchantment, Short> enchantmentMap = itemStack.getEnchantmentMap();
if (!enchantmentMap.isEmpty()) {
writeEnchant(itemNBT, "Enchantments", enchantmentMap);
}
}
// End enchantment
// Start attribute
{
final List<ItemAttribute> itemAttributes = itemStack.getAttributes();
if (!itemAttributes.isEmpty()) {
NBTList<NBTCompound> 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<String> canDestroy = itemStack.getCanDestroy();
if (canDestroy.size() > 0) {
NBTList<NBTString> list = new NBTList<>(NBTTypes.TAG_String);
canDestroy.forEach(x -> list.add(new NBTString(x)));
itemNBT.set("CanDestroy", list);
}
}
//CanDestroy
{
Set<String> canPlaceOn = itemStack.getCanPlaceOn();
if (canPlaceOn.size() > 0) {
NBTList<NBTString> 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.
* <p>
* 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.
* <p>
* 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);

View File

@ -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) {

View File

@ -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 <T> the owned object type
*/
public class OwnershipHandler<T> {
// identifier = the object having an ownership
private ConcurrentHashMap<UUID, T> ownershipDataMap = new ConcurrentHashMap<>();
/**
* Generates a new unique identifier.
* <p>
* 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);
}
}

View File

@ -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;
}

View File

@ -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)));

View File

@ -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<Player> 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 -> {

View File

@ -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 <target> <item> [<count>]")));
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<ItemStack> 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<Entity> 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));
}
}

View File

@ -47,13 +47,13 @@ public class ReadWritePackets {
private <T extends Readable & Writeable> Collection<DynamicTest> checkImplementationPresence(Class<T> packetClass) throws IOException {
ClassPath cp = ClassPath.from(ClassLoader.getSystemClassLoader());
List<DynamicTest> 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();
}