From 04893721b16c231d4694c5a9da33e1b314b5f6fe Mon Sep 17 00:00:00 2001 From: GoldenStack Date: Tue, 8 Aug 2023 14:58:09 -0500 Subject: [PATCH 01/46] Inventory rework (88 squashed commits) --- .../java/net/minestom/demo/PlayerInit.java | 6 +- .../minestom/demo/commands/GiveCommand.java | 4 +- .../minestom/server/entity/EquipmentSlot.java | 11 +- .../net/minestom/server/entity/Player.java | 101 ++-- .../inventory/InventoryButtonClickEvent.java | 48 ++ .../event/inventory/InventoryClickEvent.java | 86 ++-- .../event/inventory/InventoryCloseEvent.java | 9 +- .../inventory/InventoryItemChangeEvent.java | 11 +- .../event/inventory/InventoryOpenEvent.java | 14 +- .../inventory/InventoryPostClickEvent.java | 60 +++ .../inventory/InventoryPreClickEvent.java | 87 +--- .../PlayerInventoryItemChangeEvent.java | 31 -- .../event/player/PlayerSwapItemEvent.java | 78 --- .../server/event/trait/InventoryEvent.java | 6 +- .../server/inventory/AbstractInventory.java | 258 ---------- .../server/inventory/ContainerInventory.java | 172 +++++++ .../server/inventory/EquipmentHandler.java | 11 +- .../minestom/server/inventory/Inventory.java | 426 ++++------------ .../inventory/InventoryClickHandler.java | 83 --- .../server/inventory/InventoryImpl.java | 298 +++++++++++ .../server/inventory/InventoryType.java | 2 +- .../server/inventory/PlayerInventory.java | 441 +++++++--------- .../server/inventory/TransactionOperator.java | 116 +++++ .../server/inventory/TransactionOption.java | 34 +- .../server/inventory/TransactionType.java | 160 +++--- .../server/inventory/click/Click.java | 280 +++++++++++ .../inventory/click/ClickProcessors.java | 219 ++++++++ .../server/inventory/click/ClickType.java | 26 - .../click/InventoryClickProcessor.java | 476 ------------------ .../inventory/click/InventoryClickResult.java | 43 -- .../condition/InventoryCondition.java | 25 - .../condition/InventoryConditionResult.java | 41 -- .../server/inventory/type/AnvilInventory.java | 4 +- .../inventory/type/BeaconInventory.java | 4 +- .../inventory/type/BrewingStandInventory.java | 4 +- .../type/EnchantmentTableInventory.java | 4 +- .../inventory/type/FurnaceInventory.java | 36 +- .../inventory/type/VillagerInventory.java | 12 +- .../net/minestom/server/item/ItemStack.java | 3 +- .../listener/BlockPlacementListener.java | 2 +- .../server/listener/BookListener.java | 6 +- .../CreativeInventoryActionListener.java | 33 +- .../listener/PlayerDiggingListener.java | 51 +- .../server/listener/UseItemListener.java | 9 +- .../server/listener/WindowListener.java | 105 +--- .../manager/PacketListenerManager.java | 1 + .../utils/inventory/PlayerInventoryUtils.java | 141 +++--- .../inventory/InventoryCloseStateTest.java | 2 +- .../inventory/InventoryIntegrationTest.java | 44 +- .../server/inventory/InventoryTest.java | 10 +- .../inventory/PlayerCreativeSlotTest.java | 4 +- .../PlayerInventoryIntegrationTest.java | 6 +- .../inventory/PlayerSlotConversionTest.java | 60 --- .../click/ClickPreprocessorTest.java | 96 ++++ .../server/inventory/click/ClickUtils.java | 84 ++++ .../integration/HeldClickIntegrationTest.java | 190 ------- .../integration/LeftClickIntegrationTest.java | 173 ------- .../RightClickIntegrationTest.java | 194 ------- .../type/InventoryCreativeDropItemTest.java | 33 ++ .../type/InventoryCreativeSetItemTest.java | 33 ++ .../click/type/InventoryDoubleClickTest.java | 91 ++++ .../click/type/InventoryDropCursorTest.java | 51 ++ .../click/type/InventoryDropSlotTest.java | 41 ++ .../click/type/InventoryHotbarSwapTest.java | 33 ++ .../click/type/InventoryLeftClickTest.java | 49 ++ .../click/type/InventoryLeftDragTest.java | 102 ++++ .../click/type/InventoryMiddleClickTest.java | 40 ++ .../click/type/InventoryMiddleDragTest.java | 50 ++ .../click/type/InventoryOffhandSwapTest.java | 32 ++ .../click/type/InventoryRightClickTest.java | 67 +++ .../click/type/InventoryRightDragTest.java | 77 +++ .../click/type/InventoryShiftClickTest.java | 149 ++++++ 72 files changed, 2916 insertions(+), 2873 deletions(-) create mode 100644 src/main/java/net/minestom/server/event/inventory/InventoryButtonClickEvent.java create mode 100644 src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java delete mode 100644 src/main/java/net/minestom/server/event/inventory/PlayerInventoryItemChangeEvent.java delete mode 100644 src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java delete mode 100644 src/main/java/net/minestom/server/inventory/AbstractInventory.java create mode 100644 src/main/java/net/minestom/server/inventory/ContainerInventory.java delete mode 100644 src/main/java/net/minestom/server/inventory/InventoryClickHandler.java create mode 100644 src/main/java/net/minestom/server/inventory/InventoryImpl.java create mode 100644 src/main/java/net/minestom/server/inventory/TransactionOperator.java create mode 100644 src/main/java/net/minestom/server/inventory/click/Click.java create mode 100644 src/main/java/net/minestom/server/inventory/click/ClickProcessors.java delete mode 100644 src/main/java/net/minestom/server/inventory/click/ClickType.java delete mode 100644 src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java delete mode 100644 src/main/java/net/minestom/server/inventory/click/InventoryClickResult.java delete mode 100644 src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java delete mode 100644 src/main/java/net/minestom/server/inventory/condition/InventoryConditionResult.java delete mode 100644 src/test/java/net/minestom/server/inventory/PlayerSlotConversionTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/ClickPreprocessorTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/ClickUtils.java delete mode 100644 src/test/java/net/minestom/server/inventory/click/integration/HeldClickIntegrationTest.java delete mode 100644 src/test/java/net/minestom/server/inventory/click/integration/LeftClickIntegrationTest.java delete mode 100644 src/test/java/net/minestom/server/inventory/click/integration/RightClickIntegrationTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java create mode 100644 src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 81974b749..417fa6276 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -26,7 +26,7 @@ import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.LightingChunk; import net.minestom.server.instance.block.Block; -import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryType; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; @@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicReference; public class PlayerInit { - private static final Inventory inventory; + private static final ContainerInventory inventory; private static final EventNode DEMO_NODE = EventNode.all("demo") .addListener(EntityAttackEvent.class, event -> { @@ -187,7 +187,7 @@ public class PlayerInit { // System.out.println("light end"); // }); - inventory = new Inventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory")); + inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory")); inventory.setItemStack(3, ItemStack.of(Material.DIAMOND, 34)); } diff --git a/demo/src/main/java/net/minestom/demo/commands/GiveCommand.java b/demo/src/main/java/net/minestom/demo/commands/GiveCommand.java index 10a43bd07..97f5330eb 100644 --- a/demo/src/main/java/net/minestom/demo/commands/GiveCommand.java +++ b/demo/src/main/java/net/minestom/demo/commands/GiveCommand.java @@ -4,10 +4,10 @@ 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 net.minestom.server.utils.inventory.PlayerInventoryUtils; import java.util.ArrayList; import java.util.List; @@ -25,7 +25,7 @@ public class GiveCommand extends Command { addSyntax((sender, context) -> { final EntityFinder entityFinder = context.get("target"); int count = context.get("count"); - count = Math.min(count, PlayerInventory.INVENTORY_SIZE * 64); + count = Math.min(count, PlayerInventoryUtils.INVENTORY_SIZE * 64); ItemStack itemStack = context.get("item"); List itemStacks; diff --git a/src/main/java/net/minestom/server/entity/EquipmentSlot.java b/src/main/java/net/minestom/server/entity/EquipmentSlot.java index 2fa5bc189..1ec135bcb 100644 --- a/src/main/java/net/minestom/server/entity/EquipmentSlot.java +++ b/src/main/java/net/minestom/server/entity/EquipmentSlot.java @@ -1,19 +1,18 @@ package net.minestom.server.entity; import net.minestom.server.item.attribute.AttributeSlot; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import java.util.List; -import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; - public enum EquipmentSlot { MAIN_HAND(false, -1), OFF_HAND(false, -1), - BOOTS(true, BOOTS_SLOT), - LEGGINGS(true, LEGGINGS_SLOT), - CHESTPLATE(true, CHESTPLATE_SLOT), - HELMET(true, HELMET_SLOT); + BOOTS(true, PlayerInventoryUtils.BOOTS_SLOT), + LEGGINGS(true, PlayerInventoryUtils.LEGGINGS_SLOT), + CHESTPLATE(true, PlayerInventoryUtils.CHESTPLATE_SLOT), + HELMET(true, PlayerInventoryUtils.HELMET_SLOT); private static final List ARMORS = List.of(BOOTS, LEGGINGS, CHESTPLATE, HELMET); diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index b9df6b5c2..bd2113b50 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -45,9 +45,10 @@ import net.minestom.server.event.player.*; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.EntityTracker; import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; import net.minestom.server.inventory.Inventory; +import net.minestom.server.instance.block.Block; import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.click.Click; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.item.metadata.WrittenBookMeta; @@ -178,6 +179,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, private int level; private int portalCooldown = 0; + protected Click.Preprocessor clickPreprocessor = new Click.Preprocessor(); protected PlayerInventory inventory; private Inventory openInventory; // Used internally to allow the closing of inventory within the inventory listener @@ -239,7 +241,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, setRespawnPoint(Pos.ZERO); this.settings = new PlayerSettings(); - this.inventory = new PlayerInventory(this); + this.inventory = new PlayerInventory(); setCanPickupItem(true); // By default @@ -370,6 +372,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, refreshHealth(); // Heal and send health packet refreshAbilities(); // Send abilities packet + inventory.addViewer(this); + return setInstance(spawnInstance); } @@ -1008,11 +1012,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable, .pages(book.pages())) .build(); // Set book in offhand - sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, writtenBook)); + sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFF_HAND_SLOT, writtenBook)); // Open the book sendPacket(new OpenBookPacket(Hand.OFF)); // Restore the item in offhand - sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, getItemInOffHand())); + sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFF_HAND_SLOT, getItemInOffHand())); } @Override @@ -1717,6 +1721,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable, this.belowNameTag = belowNameTag; } + public @NotNull Click.Preprocessor clickPreprocessor() { + return clickPreprocessor; + } + /** * Gets the player open inventory. * @@ -1726,6 +1734,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable, return openInventory; } + private void tryCloseInventory(boolean fromClient) { + var closedInventory = getOpenInventory(); + if (closedInventory == null) return; + + didCloseInventory = fromClient; + + if (closedInventory.removeViewer(this)) { + if (closedInventory == getOpenInventory()) { + this.openInventory = null; + } + } + } + /** * Opens the specified Inventory, close the previous inventory if existing. * @@ -1736,21 +1757,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable, InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this); EventDispatcher.callCancellable(inventoryOpenEvent, () -> { - Inventory openInventory = getOpenInventory(); - if (openInventory != null) { - openInventory.removeViewer(this); - } + tryCloseInventory(false); Inventory newInventory = inventoryOpenEvent.getInventory(); - if (newInventory == null) { - // just close the inventory - return; + if (newInventory.addViewer(this)) { + this.openInventory = newInventory; } - - sendPacket(new OpenWindowPacket(newInventory.getWindowId(), - newInventory.getInventoryType().getWindowType(), newInventory.getTitle())); - newInventory.addViewer(this); - this.openInventory = newInventory; }); return !inventoryOpenEvent.isCancelled(); } @@ -1765,37 +1777,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, @ApiStatus.Internal public void closeInventory(boolean fromClient) { - Inventory openInventory = getOpenInventory(); - - // Drop cursor item when closing inventory - ItemStack cursorItem; - if (openInventory == null) { - cursorItem = getInventory().getCursorItem(); - getInventory().setCursorItem(ItemStack.AIR); - } else { - cursorItem = openInventory.getCursorItem(this); - openInventory.setCursorItem(this, ItemStack.AIR); - } - if (!cursorItem.isAir()) { - // Add item to inventory if he hasn't been able to drop it - if (!dropItem(cursorItem)) { - getInventory().addItemStack(cursorItem); - } - } - - if (openInventory == getOpenInventory()) { - CloseWindowPacket closeWindowPacket; - if (openInventory == null) { - closeWindowPacket = new CloseWindowPacket((byte) 0); - } else { - closeWindowPacket = new CloseWindowPacket(openInventory.getWindowId()); - openInventory.removeViewer(this); // Clear cache - this.openInventory = null; - } - if (!fromClient) sendPacket(closeWindowPacket); - inventory.update(); - this.didCloseInventory = true; - } + tryCloseInventory(fromClient); + inventory.update(); } /** @@ -2304,62 +2287,62 @@ public class Player extends LivingEntity implements CommandSender, Localizable, @Override public @NotNull ItemStack getItemInMainHand() { - return inventory.getItemInMainHand(); + return inventory.getEquipment(EquipmentSlot.MAIN_HAND, getHeldSlot()); } @Override public void setItemInMainHand(@NotNull ItemStack itemStack) { - inventory.setItemInMainHand(itemStack); + inventory.setEquipment(EquipmentSlot.MAIN_HAND, getHeldSlot(), itemStack); } @Override public @NotNull ItemStack getItemInOffHand() { - return inventory.getItemInOffHand(); + return inventory.getEquipment(EquipmentSlot.OFF_HAND, getHeldSlot()); } @Override public void setItemInOffHand(@NotNull ItemStack itemStack) { - inventory.setItemInOffHand(itemStack); + inventory.setEquipment(EquipmentSlot.OFF_HAND, getHeldSlot(), itemStack); } @Override public @NotNull ItemStack getHelmet() { - return inventory.getHelmet(); + return inventory.getEquipment(EquipmentSlot.HELMET, getHeldSlot()); } @Override public void setHelmet(@NotNull ItemStack itemStack) { - inventory.setHelmet(itemStack); + inventory.setEquipment(EquipmentSlot.HELMET, getHeldSlot(), itemStack); } @Override public @NotNull ItemStack getChestplate() { - return inventory.getChestplate(); + return inventory.getEquipment(EquipmentSlot.CHESTPLATE, getHeldSlot()); } @Override public void setChestplate(@NotNull ItemStack itemStack) { - inventory.setChestplate(itemStack); + inventory.setEquipment(EquipmentSlot.CHESTPLATE, getHeldSlot(), itemStack); } @Override public @NotNull ItemStack getLeggings() { - return inventory.getLeggings(); + return inventory.getEquipment(EquipmentSlot.LEGGINGS, getHeldSlot()); } @Override public void setLeggings(@NotNull ItemStack itemStack) { - inventory.setLeggings(itemStack); + inventory.setEquipment(EquipmentSlot.LEGGINGS, getHeldSlot(), itemStack); } @Override public @NotNull ItemStack getBoots() { - return inventory.getBoots(); + return inventory.getEquipment(EquipmentSlot.BOOTS, getHeldSlot()); } @Override public void setBoots(@NotNull ItemStack itemStack) { - inventory.setBoots(itemStack); + inventory.setEquipment(EquipmentSlot.BOOTS, getHeldSlot(), itemStack); } @Override diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryButtonClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryButtonClickEvent.java new file mode 100644 index 000000000..a249a4366 --- /dev/null +++ b/src/main/java/net/minestom/server/event/inventory/InventoryButtonClickEvent.java @@ -0,0 +1,48 @@ +package net.minestom.server.event.inventory; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.trait.InventoryEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.inventory.Inventory; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player clicks an inventory button. + * See wiki.vg for slot number details. + */ +public class InventoryButtonClickEvent implements InventoryEvent, PlayerInstanceEvent { + + private final Inventory inventory; + private final Player player; + private final byte button; + + public InventoryButtonClickEvent(@NotNull Inventory inventory, @NotNull Player player, byte button) { + this.inventory = inventory; + this.player = player; + this.button = button; + } + + /** + * Gets the player who clicked the button in the inventory. + * + * @return the player who clicked + */ + @Override + public @NotNull Player getPlayer() { + return player; + } + + @NotNull + @Override + public Inventory getInventory() { + return inventory; + } + + /** + * Gets the inventory button number that the player clicked. This is different from inventory slots. + * @return the button clicked by the player + */ + public byte getButton() { + return button; + } +} diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java index aa8a89366..ca47f20d1 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java @@ -1,89 +1,93 @@ package net.minestom.server.event.inventory; import net.minestom.server.entity.Player; +import net.minestom.server.event.trait.CancellableEvent; import net.minestom.server.event.trait.InventoryEvent; import net.minestom.server.event.trait.PlayerInstanceEvent; import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.item.ItemStack; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.click.Click; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** - * Called after {@link InventoryPreClickEvent}, this event cannot be cancelled and items related to the click - * are already moved. + * Called after {@link InventoryPreClickEvent} and before {@link InventoryPostClickEvent}. */ -public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent { +public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent { + private final PlayerInventory playerInventory; private final Inventory inventory; private final Player player; - private final int slot; - private final ClickType clickType; - private final ItemStack clickedItem; - private final ItemStack cursorItem; + private final Click.Info info; + private Click.Result changes; - public InventoryClickEvent(@Nullable Inventory inventory, @NotNull Player player, - int slot, @NotNull ClickType clickType, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { + private boolean cancelled; + + public InventoryClickEvent(@NotNull PlayerInventory playerInventory, @NotNull Inventory inventory, + @NotNull Player player, @NotNull Click.Info info, @NotNull Click.Result changes) { + this.playerInventory = playerInventory; this.inventory = inventory; this.player = player; - this.slot = slot; - this.clickType = clickType; - this.clickedItem = clicked; - this.cursorItem = cursor; + this.info = info; + this.changes = changes; } /** - * Gets the player who clicked in the inventory. + * Gets the player who is trying to click on the inventory. * - * @return the player who clicked in the inventory + * @return the player who clicked */ - @NotNull - public Player getPlayer() { + public @NotNull Player getPlayer() { return player; } /** - * Gets the clicked slot number. + * Gets the info about the click that occurred. This is enough to fully describe the click. * - * @return the clicked slot number + * @return the click info */ - public int getSlot() { - return slot; + public @NotNull Click.Info getClickInfo() { + return info; } /** - * Gets the click type. + * Gets the changes that will occur as a result of this click. * - * @return the click type + * @return the changes */ - @NotNull - public ClickType getClickType() { - return clickType; + public @NotNull Click.Result getChanges() { + return changes; } /** - * Gets the clicked item. + * Updates the changes that will occur as a result of this click. * - * @return the clicked item + * @param changes the new results */ - @NotNull - public ItemStack getClickedItem() { - return clickedItem; + public void setChanges(@NotNull Click.Result changes) { + this.changes = changes; } /** - * Gets the item in the player cursor. + * Gets the player inventory that was involved with the click. * - * @return the cursor item + * @return the player inventory */ - @NotNull - public ItemStack getCursorItem() { - return cursorItem; + public @NotNull PlayerInventory getPlayerInventory() { + return playerInventory; } @Override - public @Nullable Inventory getInventory() { + public @NotNull Inventory getInventory() { return inventory; } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java index 9099c7dc4..2225d2313 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java @@ -14,9 +14,9 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent private final Inventory inventory; private final Player player; - private Inventory newInventory; + private @Nullable Inventory newInventory; - public InventoryCloseEvent(@Nullable Inventory inventory, @NotNull Player player) { + public InventoryCloseEvent(@NotNull Inventory inventory, @NotNull Player player) { this.inventory = inventory; this.player = player; } @@ -36,8 +36,7 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent * * @return the new inventory to open, null if there isn't any */ - @Nullable - public Inventory getNewInventory() { + public @Nullable Inventory getNewInventory() { return newInventory; } @@ -51,7 +50,7 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent } @Override - public @Nullable Inventory getInventory() { + public @NotNull Inventory getInventory() { return inventory; } } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryItemChangeEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryItemChangeEvent.java index 0629dd505..0df419e80 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryItemChangeEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryItemChangeEvent.java @@ -2,19 +2,14 @@ package net.minestom.server.event.inventory; import net.minestom.server.event.trait.InventoryEvent; import net.minestom.server.event.trait.RecursiveEvent; -import net.minestom.server.inventory.AbstractInventory; import net.minestom.server.inventory.Inventory; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** - * Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked. + * Called when a slot was changed in an inventory. * This event cannot be cancelled and items related to the change are already moved. - * - * @see PlayerInventoryItemChangeEvent */ -@SuppressWarnings("JavadocReference") public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent { private final Inventory inventory; @@ -22,7 +17,7 @@ public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent private final ItemStack previousItem; private final ItemStack newItem; - public InventoryItemChangeEvent(@Nullable Inventory inventory, int slot, + public InventoryItemChangeEvent(@NotNull Inventory inventory, int slot, @NotNull ItemStack previousItem, @NotNull ItemStack newItem) { this.inventory = inventory; this.slot = slot; @@ -58,7 +53,7 @@ public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent } @Override - public @Nullable Inventory getInventory() { + public @NotNull Inventory getInventory() { return inventory; } } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java index bb50abd4a..3f4f73e7e 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java @@ -6,7 +6,6 @@ import net.minestom.server.event.trait.InventoryEvent; import net.minestom.server.event.trait.PlayerInstanceEvent; import net.minestom.server.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Called when a player open an {@link Inventory}. @@ -15,12 +14,12 @@ import org.jetbrains.annotations.Nullable; */ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent { - private Inventory inventory; private final Player player; + private Inventory inventory; private boolean cancelled; - public InventoryOpenEvent(@Nullable Inventory inventory, @NotNull Player player) { + public InventoryOpenEvent(@NotNull Inventory inventory, @NotNull Player player) { this.inventory = inventory; this.player = player; } @@ -36,13 +35,12 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent, } /** - * Gets the inventory to open, this could have been change by the {@link #setInventory(Inventory)}. + * Gets the inventory to open. * - * @return the inventory to open, null to just close the current inventory if any + * @return the inventory to open */ - @Nullable @Override - public Inventory getInventory() { + public @NotNull Inventory getInventory() { return inventory; } @@ -53,7 +51,7 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent, * * @param inventory the inventory to open */ - public void setInventory(@Nullable Inventory inventory) { + public void setInventory(@NotNull Inventory inventory) { this.inventory = inventory; } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java new file mode 100644 index 000000000..23a29fc2e --- /dev/null +++ b/src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java @@ -0,0 +1,60 @@ +package net.minestom.server.event.inventory; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.trait.InventoryEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.click.Click; +import org.jetbrains.annotations.NotNull; + +/** + * Called after {@link InventoryClickEvent}, this event cannot be cancelled and items related to the click + * are already moved. + */ +public class InventoryPostClickEvent implements InventoryEvent, PlayerInstanceEvent { + + private final Player player; + private final Inventory inventory; + private final Click.Info info; + private final Click.Result changes; + + public InventoryPostClickEvent(@NotNull Player player, @NotNull Inventory inventory, @NotNull Click.Info info, @NotNull Click.Result changes) { + this.player = player; + this.inventory = inventory; + this.info = info; + this.changes = changes; + } + + /** + * Gets the player who clicked in the inventory. + * + * @return the player who clicked in the inventory + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Gets the info about the click that was already processed. + * + * @return the click info + */ + public @NotNull Click.Info getClickInfo() { + return info; + } + + /** + * Gets the changes that occurred as a result of this click. + * + * @return the changes + */ + public @NotNull Click.Result getChanges() { + return changes; + } + + @Override + public @NotNull Inventory getInventory() { + return inventory; + } +} diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java index 85fe79902..c9d2d56b7 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java @@ -5,35 +5,28 @@ import net.minestom.server.event.trait.CancellableEvent; import net.minestom.server.event.trait.InventoryEvent; import net.minestom.server.event.trait.PlayerInstanceEvent; import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.item.ItemStack; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.click.Click; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Called before {@link InventoryClickEvent}, used to potentially cancel the click. */ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent { + private final PlayerInventory playerInventory; private final Inventory inventory; private final Player player; - private final int slot; - private final ClickType clickType; - private ItemStack clickedItem; - private ItemStack cursorItem; + private Click.Info info; private boolean cancelled; - public InventoryPreClickEvent(@Nullable Inventory inventory, - @NotNull Player player, - int slot, @NotNull ClickType clickType, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { + public InventoryPreClickEvent(@NotNull PlayerInventory playerInventory, @NotNull Inventory inventory, + @NotNull Player player, @NotNull Click.Info info) { + this.playerInventory = playerInventory; this.inventory = inventory; this.player = player; - this.slot = slot; - this.clickType = clickType; - this.clickedItem = clicked; - this.cursorItem = cursor; + this.info = info; } /** @@ -41,66 +34,41 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve * * @return the player who clicked */ - @NotNull - public Player getPlayer() { + public @NotNull Player getPlayer() { return player; } /** - * Gets the clicked slot number. + * Gets the info about the click that occurred. This is enough to fully describe the click. * - * @return the clicked slot number + * @return the click info */ - public int getSlot() { - return slot; + public @NotNull Click.Info getClickInfo() { + return info; } /** - * Gets the click type. + * Updates the information about the click that occurred. This completely overrides the previous click, but it may + * require the inventory to be updated. * - * @return the click type + * @param info the new click info */ - @NotNull - public ClickType getClickType() { - return clickType; + public void setClickInfo(@NotNull Click.Info info) { + this.info = info; } /** - * Gets the item who have been clicked. + * Gets the player inventory that was involved with the click. * - * @return the clicked item + * @return the player inventory */ - @NotNull - public ItemStack getClickedItem() { - return clickedItem; + public @NotNull PlayerInventory getPlayerInventory() { + return playerInventory; } - /** - * Changes the clicked item. - * - * @param clickedItem the clicked item - */ - public void setClickedItem(@NotNull ItemStack clickedItem) { - this.clickedItem = clickedItem; - } - - /** - * Gets the item who was in the player cursor. - * - * @return the cursor item - */ - @NotNull - public ItemStack getCursorItem() { - return cursorItem; - } - - /** - * Changes the cursor item. - * - * @param cursorItem the cursor item - */ - public void setCursorItem(@NotNull ItemStack cursorItem) { - this.cursorItem = cursorItem; + @Override + public @NotNull Inventory getInventory() { + return inventory; } @Override @@ -112,9 +80,4 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve public void setCancelled(boolean cancel) { this.cancelled = cancel; } - - @Override - public @Nullable Inventory getInventory() { - return inventory; - } } diff --git a/src/main/java/net/minestom/server/event/inventory/PlayerInventoryItemChangeEvent.java b/src/main/java/net/minestom/server/event/inventory/PlayerInventoryItemChangeEvent.java deleted file mode 100644 index c73990c80..000000000 --- a/src/main/java/net/minestom/server/event/inventory/PlayerInventoryItemChangeEvent.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.minestom.server.event.inventory; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.trait.PlayerInstanceEvent; -import net.minestom.server.inventory.AbstractInventory; -import net.minestom.server.inventory.PlayerInventory; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -/** - * Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked on a {@link PlayerInventory}. - * This event cannot be cancelled and items related to the change are already moved. - *

- * When this event is being called, {@link InventoryItemChangeEvent} listeners will also be triggered, so you can - * listen only for an ancestor event and check whether it is an instance of that class. - */ -@SuppressWarnings("JavadocReference") -public class PlayerInventoryItemChangeEvent extends InventoryItemChangeEvent implements PlayerInstanceEvent { - - private final Player player; - - public PlayerInventoryItemChangeEvent(@NotNull Player player, int slot, @NotNull ItemStack previousItem, @NotNull ItemStack newItem) { - super(null, slot, previousItem, newItem); - this.player = player; - } - - @Override - public @NotNull Player getPlayer() { - return player; - } -} diff --git a/src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java b/src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java deleted file mode 100644 index 477bb74fd..000000000 --- a/src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.minestom.server.event.player; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.trait.CancellableEvent; -import net.minestom.server.event.trait.PlayerInstanceEvent; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -/** - * Called when a player is trying to swap his main and off hand item. - */ -public class PlayerSwapItemEvent implements PlayerInstanceEvent, CancellableEvent { - - private final Player player; - private ItemStack mainHandItem; - private ItemStack offHandItem; - - private boolean cancelled; - - public PlayerSwapItemEvent(@NotNull Player player, @NotNull ItemStack mainHandItem, @NotNull ItemStack offHandItem) { - this.player = player; - this.mainHandItem = mainHandItem; - this.offHandItem = offHandItem; - } - - /** - * Gets the item which will be in player main hand after the event. - * - * @return the item in main hand - */ - @NotNull - public ItemStack getMainHandItem() { - return mainHandItem; - } - - /** - * Changes the item which will be in the player main hand. - * - * @param mainHandItem the main hand item - */ - public void setMainHandItem(@NotNull ItemStack mainHandItem) { - this.mainHandItem = mainHandItem; - } - - /** - * Gets the item which will be in player off hand after the event. - * - * @return the item in off hand - */ - @NotNull - public ItemStack getOffHandItem() { - return offHandItem; - } - - /** - * Changes the item which will be in the player off hand. - * - * @param offHandItem the off hand item - */ - public void setOffHandItem(@NotNull ItemStack offHandItem) { - this.offHandItem = offHandItem; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } - - @Override - public @NotNull Player getPlayer() { - return player; - } -} diff --git a/src/main/java/net/minestom/server/event/trait/InventoryEvent.java b/src/main/java/net/minestom/server/event/trait/InventoryEvent.java index 726741531..2c887a635 100644 --- a/src/main/java/net/minestom/server/event/trait/InventoryEvent.java +++ b/src/main/java/net/minestom/server/event/trait/InventoryEvent.java @@ -2,7 +2,7 @@ package net.minestom.server.event.trait; import net.minestom.server.event.Event; import net.minestom.server.inventory.Inventory; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; /** * Represents any event inside an {@link Inventory}. @@ -12,7 +12,7 @@ public interface InventoryEvent extends Event { /** * Gets the inventory. * - * @return the inventory, null if this is a player's inventory + * @return the inventory (may be a player inventory) */ - @Nullable Inventory getInventory(); + @NotNull Inventory getInventory(); } diff --git a/src/main/java/net/minestom/server/inventory/AbstractInventory.java b/src/main/java/net/minestom/server/inventory/AbstractInventory.java deleted file mode 100644 index 99cfc2fb0..000000000 --- a/src/main/java/net/minestom/server/inventory/AbstractInventory.java +++ /dev/null @@ -1,258 +0,0 @@ -package net.minestom.server.inventory; - -import net.minestom.server.event.EventDispatcher; -import net.minestom.server.event.inventory.InventoryItemChangeEvent; -import net.minestom.server.event.inventory.PlayerInventoryItemChangeEvent; -import net.minestom.server.inventory.click.InventoryClickProcessor; -import net.minestom.server.inventory.condition.InventoryCondition; -import net.minestom.server.item.ItemStack; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.Taggable; -import net.minestom.server.utils.MathUtils; -import net.minestom.server.utils.validate.Check; -import org.jetbrains.annotations.NotNull; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -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 sealed abstract class AbstractInventory implements InventoryClickHandler, Taggable - permits Inventory, PlayerInventory { - - private static final VarHandle ITEM_UPDATER = MethodHandles.arrayElementVarHandle(ItemStack[].class); - - private final int size; - protected final ItemStack[] itemStacks; - - // list of conditions/callbacks assigned to this inventory - protected final List inventoryConditions = new CopyOnWriteArrayList<>(); - // the click processor which process all the clicks in the inventory - protected final InventoryClickProcessor clickProcessor = new InventoryClickProcessor(); - - private final TagHandler tagHandler = TagHandler.newHandler(); - - 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); - } - - /** - * Inserts safely an item into the inventory. - *

- * This will update the slot for all viewers and warn the inventory that - * the window items packet is not up-to-date. - * - * @param slot the internal slot id - * @param itemStack the item to insert (use air instead of null) - * @throws IllegalArgumentException if the slot {@code slot} does not exist - */ - protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) { - ItemStack previous; - synchronized (this) { - Check.argCondition( - !MathUtils.isBetween(slot, 0, getSize()), - "The slot {0} does not exist in this inventory", - slot - ); - previous = itemStacks[slot]; - if (itemStack.equals(previous)) return; // Avoid sending updates if the item has not changed - UNSAFE_itemInsert(slot, itemStack, sendPacket); - } - if (this instanceof PlayerInventory inv) { - EventDispatcher.call(new PlayerInventoryItemChangeEvent(inv.player, slot, previous, itemStack)); - } else if (this instanceof Inventory inv) { - EventDispatcher.call(new InventoryItemChangeEvent(inv, slot, previous, itemStack)); - } - } - - protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack) { - safeItemInsert(slot, itemStack, true); - } - - protected abstract void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket); - - public synchronized @NotNull T processItemStack(@NotNull ItemStack itemStack, - @NotNull TransactionType type, - @NotNull TransactionOption option) { - return option.fill(type, this, itemStack); - } - - public synchronized @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, - @NotNull TransactionType type, - @NotNull TransactionOption option) { - List result = new ArrayList<>(itemStacks.size()); - itemStacks.forEach(itemStack -> { - T transactionResult = processItemStack(itemStack, type, option); - result.add(transactionResult); - }); - return result; - } - - /** - * Adds an {@link ItemStack} to the inventory and sends relevant update to the viewer(s). - * - * @param itemStack the item to add - * @param option the transaction option - * @return true if the item has been successfully added, false otherwise - */ - public @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { - return processItemStack(itemStack, TransactionType.ADD, option); - } - - public boolean addItemStack(@NotNull ItemStack itemStack) { - return addItemStack(itemStack, TransactionOption.ALL_OR_NOTHING); - } - - /** - * Adds {@link ItemStack}s to the inventory and sends relevant updates to the viewer(s). - * - * @param itemStacks items to add - * @param option the transaction option - * @return the operation results - */ - public @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, - @NotNull TransactionOption option) { - return processItemStacks(itemStacks, TransactionType.ADD, option); - } - - /** - * Takes an {@link ItemStack} from the inventory and sends relevant update to the viewer(s). - * - * @param itemStack the item to take - * @return true if the item has been successfully fully taken, false otherwise - */ - public @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { - return processItemStack(itemStack, TransactionType.TAKE, option); - } - - /** - * Takes {@link ItemStack}s from the inventory and sends relevant updates to the viewer(s). - * - * @param itemStacks items to take - * @return the operation results - */ - public @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, - @NotNull TransactionOption option) { - return processItemStacks(itemStacks, TransactionType.TAKE, option); - } - - public synchronized void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) { - var currentItem = getItemStack(slot); - setItemStack(slot, operator.apply(currentItem)); - } - - /** - * Clears the inventory and send relevant update to the viewer(s). - */ - public synchronized void clear() { - // Clear the item array - for (int i = 0; i < size; i++) { - safeItemInsert(i, ItemStack.AIR, false); - } - // 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 (ItemStack) ITEM_UPDATER.getVolatile(itemStacks, slot); - } - - /** - * Gets all the {@link ItemStack} in the inventory. - *

- * Be aware that the returned array does not need to be the original one, - * meaning that modifying it directly may not work. - * - * @return an array containing all the inventory's items - */ - public @NotNull ItemStack[] getItemStacks() { - return itemStacks.clone(); - } - - /** - * Gets the size of the inventory. - * - * @return the inventory's size - */ - public int getSize() { - return size; - } - - /** - * Gets the size of the "inner inventory" (which includes only "usable" slots). - * - * @return inner inventory's size - */ - public int getInnerSize() { - return getSize(); - } - - /** - * Gets all the {@link InventoryCondition} of this inventory. - * - * @return a modifiable {@link List} containing all the inventory conditions - */ - public @NotNull List<@NotNull InventoryCondition> getInventoryConditions() { - return inventoryConditions; - } - - /** - * Adds a new {@link InventoryCondition} to this inventory. - * - * @param inventoryCondition the inventory condition to add - */ - public void addInventoryCondition(@NotNull InventoryCondition inventoryCondition) { - this.inventoryConditions.add(inventoryCondition); - } - - /** - * Places all the items of {@code itemStacks} into the internal array. - * - * @param itemStacks the array to copy the content from - * @throws IllegalArgumentException if the size of the array is not equal to {@link #getSize()} - * @throws NullPointerException if {@code itemStacks} contains one null element or more - */ - public void copyContents(@NotNull ItemStack[] itemStacks) { - Check.argCondition(itemStacks.length != getSize(), - "The size of the array has to be of the same size as the inventory: " + getSize()); - - for (int i = 0; i < itemStacks.length; i++) { - final ItemStack itemStack = itemStacks[i]; - Check.notNull(itemStack, "The item array cannot contain any null element!"); - setItemStack(i, itemStack); - } - } - - @Override - public @NotNull TagHandler tagHandler() { - return tagHandler; - } -} diff --git a/src/main/java/net/minestom/server/inventory/ContainerInventory.java b/src/main/java/net/minestom/server/inventory/ContainerInventory.java new file mode 100644 index 000000000..8ae0274c6 --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/ContainerInventory.java @@ -0,0 +1,172 @@ +package net.minestom.server.inventory; + +import net.kyori.adventure.text.Component; +import net.minestom.server.entity.Player; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.event.inventory.InventoryClickEvent; +import net.minestom.server.event.inventory.InventoryPostClickEvent; +import net.minestom.server.event.inventory.InventoryPreClickEvent; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.server.play.OpenWindowPacket; +import net.minestom.server.network.packet.server.play.WindowPropertyPacket; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; + +/** + * Represents an inventory which can be viewed by a collection of {@link Player}. + *

+ * You can create one with {@link ContainerInventory#ContainerInventory(InventoryType, String)} or by making your own subclass. + * It can then be opened using {@link Player#openInventory(Inventory)}. + */ +public non-sealed class ContainerInventory extends InventoryImpl { + + /** + * Processes a click, returning a result. This will call events for the click. + * @param inventory the clicked inventory (could be a player inventory) + * @param player the player who clicked + * @param info the click info describing the click + * @return the click result, or null if the click did not occur + */ + public static @Nullable Click.Result handleClick(@NotNull Inventory inventory, @NotNull Player player, @NotNull Click.Info info, @NotNull BiFunction processor) { + PlayerInventory playerInventory = player.getInventory(); + + InventoryPreClickEvent preClickEvent = new InventoryPreClickEvent(playerInventory, inventory, player, info); + EventDispatcher.call(preClickEvent); + + Click.Info newInfo = preClickEvent.getClickInfo(); + + if (!preClickEvent.isCancelled()) { + Click.Getter getter = new Click.Getter(inventory::getItemStack, playerInventory::getItemStack, playerInventory.getCursorItem(), inventory.getSize()); + Click.Result changes = processor.apply(newInfo, getter); + + InventoryClickEvent clickEvent = new InventoryClickEvent(playerInventory, inventory, player, newInfo, changes); + EventDispatcher.call(clickEvent); + + if (!clickEvent.isCancelled()) { + Click.Result newChanges = clickEvent.getChanges(); + + apply(newChanges, player, inventory); + + var postClickEvent = new InventoryPostClickEvent(player, inventory, newInfo, newChanges); + EventDispatcher.call(postClickEvent); + + if (!info.equals(newInfo) || !changes.equals(newChanges)) { + inventory.update(player); + if (inventory != playerInventory) { + playerInventory.update(player); + } + } + + return newChanges; + } + } + + inventory.update(player); + if (inventory != playerInventory) { + playerInventory.update(player); + } + return null; + } + + public static void apply(@NotNull Click.Result result, @NotNull Player player, @NotNull Inventory inventory) { + for (var entry : result.changes().entrySet()) { + inventory.setItemStack(entry.getKey(), entry.getValue()); + } + + for (var entry : result.playerInventoryChanges().entrySet()) { + player.getInventory().setItemStack(entry.getKey(), entry.getValue()); + } + + if (result.newCursorItem() != null) { + player.getInventory().setCursorItem(result.newCursorItem()); + } + + if (result.sideEffects() instanceof Click.SideEffect.DropFromPlayer drop) { + for (ItemStack item : drop.items()) { + player.dropItem(item); + } + } + } + + private static final AtomicInteger ID_COUNTER = new AtomicInteger(); + + private final byte id; + private final InventoryType inventoryType; + private Component title; + + public ContainerInventory(@NotNull InventoryType inventoryType, @NotNull Component title) { + super(inventoryType.getSize()); + this.id = generateId(); + this.inventoryType = inventoryType; + this.title = title; + } + + public ContainerInventory(@NotNull InventoryType inventoryType, @NotNull String title) { + this(inventoryType, Component.text(title)); + } + + private static byte generateId() { + return (byte) ID_COUNTER.updateAndGet(i -> i + 1 >= 128 ? 1 : i + 1); + } + + /** + * Gets the inventory type of this inventory. + * + * @return the inventory type + */ + public @NotNull InventoryType getInventoryType() { + return inventoryType; + } + + /** + * Gets the inventory title of this inventory. + * + * @return the inventory title + */ + public @NotNull Component getTitle() { + return title; + } + + /** + * Changes the inventory title of this inventory. + * + * @param title the new inventory title + */ + public void setTitle(@NotNull Component title) { + this.title = title; + + // Reopen and update this inventory with the new title + sendPacketToViewers(new OpenWindowPacket(getWindowId(), getInventoryType().getWindowType(), title)); + update(); + } + + @Override + public byte getWindowId() { + return id; + } + + @Override + public boolean addViewer(@NotNull Player player) { + if (!this.viewers.add(player)) return false; + + player.sendPacket(new OpenWindowPacket(getWindowId(), inventoryType.getWindowType(), getTitle())); + update(player); + return true; + } + + /** + * Sends a window property to all viewers. + * + * @param property the property to send + * @param value the value of the property + * @see https://wiki.vg/Protocol#Set_Container_Property + */ + protected void sendProperty(@NotNull InventoryProperty property, short value) { + sendPacketToViewers(new WindowPropertyPacket(getWindowId(), property.getProperty(), value)); + } + +} diff --git a/src/main/java/net/minestom/server/inventory/EquipmentHandler.java b/src/main/java/net/minestom/server/inventory/EquipmentHandler.java index 576dddd4d..003cb61b5 100644 --- a/src/main/java/net/minestom/server/inventory/EquipmentHandler.java +++ b/src/main/java/net/minestom/server/inventory/EquipmentHandler.java @@ -163,10 +163,19 @@ public interface EquipmentHandler { * @param slot the slot of the equipment */ default void syncEquipment(@NotNull EquipmentSlot slot) { + syncEquipment(slot, getEquipment(slot)); + } + + /** + * Sends a specific equipment to viewers. + * + * @param slot the slot of the equipment + * @param itemStack the item to be sent for the slot + */ + default void syncEquipment(@NotNull EquipmentSlot slot, @NotNull ItemStack itemStack) { Check.stateCondition(!(this instanceof Entity), "Only accessible for Entity"); Entity entity = (Entity) this; - final ItemStack itemStack = getEquipment(slot); entity.sendPacketToViewers(new EntityEquipmentPacket(entity.getEntityId(), Map.of(slot, itemStack))); } diff --git a/src/main/java/net/minestom/server/inventory/Inventory.java b/src/main/java/net/minestom/server/inventory/Inventory.java index be9c688fa..70c45f5b6 100644 --- a/src/main/java/net/minestom/server/inventory/Inventory.java +++ b/src/main/java/net/minestom/server/inventory/Inventory.java @@ -1,394 +1,146 @@ package net.minestom.server.inventory; -import net.kyori.adventure.text.Component; import net.minestom.server.Viewable; import net.minestom.server.entity.Player; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.inventory.click.InventoryClickResult; +import net.minestom.server.inventory.click.Click; import net.minestom.server.item.ItemStack; -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.utils.inventory.PlayerInventoryUtils; +import net.minestom.server.tag.Taggable; +import org.jetbrains.annotations.ApiStatus; 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.CopyOnWriteArraySet; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.UnaryOperator; /** - * Represents an inventory which can be viewed by a collection of {@link Player}. - *

- * 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)}. + * Represents a generic inventory that can be interacted with. */ -public non-sealed class Inventory extends AbstractInventory implements Viewable { - private static final AtomicInteger ID_COUNTER = new AtomicInteger(); - - // the id of this inventory - private final byte id; - // the type of this inventory - private final InventoryType inventoryType; - // the title of this inventory - private Component title; - - private final int offset; - - // the players currently viewing this inventory - private final Set viewers = new CopyOnWriteArraySet<>(); - private final Set unmodifiableViewers = Collections.unmodifiableSet(viewers); - // (player -> cursor item) map, used by the click listeners - private final ConcurrentHashMap cursorPlayersItem = new ConcurrentHashMap<>(); - - public Inventory(@NotNull InventoryType inventoryType, @NotNull Component title) { - super(inventoryType.getSize()); - this.id = generateId(); - this.inventoryType = inventoryType; - this.title = title; - - this.offset = getSize(); - } - - public Inventory(@NotNull InventoryType inventoryType, @NotNull String title) { - this(inventoryType, Component.text(title)); - } - - private static byte generateId() { - return (byte) ID_COUNTER.updateAndGet(i -> i + 1 >= 128 ? 1 : i + 1); - } +public sealed interface Inventory extends Taggable, Viewable permits InventoryImpl { /** - * Gets the inventory type. - * - * @return the inventory type + * Gets the size of this inventory. This should be a constant number. + * @return the size */ - public @NotNull InventoryType getInventoryType() { - return inventoryType; - } + int getSize(); /** - * Gets the inventory title. + * Gets the {@link ItemStack} at the specified slot. * - * @return the inventory title + * @param slot the slot to check + * @return the item in the slot {@code slot} */ - public @NotNull Component getTitle() { - return title; - } + @NotNull ItemStack getItemStack(int slot); /** - * Changes the inventory title. + * Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s). * - * @param title the new inventory title + * @param slot the slot to set the item + * @param itemStack the item to set */ - public void setTitle(@NotNull Component title) { - this.title = title; - // Re-open the inventory - sendPacketToViewers(new OpenWindowPacket(getWindowId(), getInventoryType().getWindowType(), title)); - // Send inventory items - update(); - } + void setItemStack(int slot, @NotNull ItemStack itemStack); /** - * Gets this window id. + * Gets the window ID of this window, as a byte. + * + * @return the window ID + */ + byte getWindowId(); + + /** + * Handles the provided click from the given player, returning the results after it is applied. If the results are + * null, this indicates that the click was cancelled or was otherwise not processed. + * + * @param player the player that clicked + * @param info the information about the player's click + * @return the results of the click, or null if the click was cancelled or otherwise was not handled + */ + @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info); + + /** + * Gets all the {@link ItemStack} in the inventory. *

- * This is the id that the client will send to identify the affected inventory, mostly used by packets. + * Be aware that the returned array does not need to be the original one, + * meaning that modifying it directly may not work. * - * @return the window id + * @return an array containing all the inventory's items */ - public byte getWindowId() { - return id; - } - - @Override - public synchronized void clear() { - this.cursorPlayersItem.clear(); - super.clear(); - } + @NotNull ItemStack[] getItemStacks(); /** - * Refreshes the inventory for all viewers. + * 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 */ - @Override - public void update() { - this.viewers.forEach(p -> p.sendPacket(createNewWindowItemsPacket(p))); - } + void copyContents(@NotNull ItemStack[] itemStacks); /** - * Refreshes the inventory for a specific viewer. - *

- * The player needs to be a viewer, otherwise nothing is sent. - * - * @param player the player to update the inventory + * Clears the inventory and send relevant update to the viewer(s). */ - public void update(@NotNull Player player) { - if (!isViewer(player)) return; - player.sendPacket(createNewWindowItemsPacket(player)); - } - - @Override - public @NotNull Set getViewers() { - return unmodifiableViewers; - } + void clear(); /** - * This will not open the inventory for {@code player}, use {@link Player#openInventory(Inventory)}. - * - * @param player the viewer to add - * @return true if the player has successfully been added + * Updates the inventory for all viewers. */ - @Override - public boolean addViewer(@NotNull Player player) { - final boolean result = this.viewers.add(player); - update(player); - return result; - } + void update(); /** - * This will not close the inventory for {@code player}, use {@link Player#closeInventory()}. + * Updates the inventory for a specific viewer. * - * @param player the viewer to remove - * @return true if the player has successfully been removed + * @param player the player to update the inventory for */ - @Override - public boolean removeViewer(@NotNull Player player) { - final boolean result = this.viewers.remove(player); - setCursorItem(player, ItemStack.AIR); - this.clickProcessor.clearCache(player); - return result; - } + void update(@NotNull Player player); /** - * Gets the cursor item of a viewer. + * Replaces the item in the slot according to the operator. * - * @param player the player to get the cursor item from - * @return the player cursor item, air item if the player is not a viewer + * @param slot the slot to replace + * @param operator the operator to apply to the slot */ - public @NotNull ItemStack getCursorItem(@NotNull Player player) { - return cursorPlayersItem.getOrDefault(player, ItemStack.AIR); - } + void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator); + + @NotNull T processItemStack(@NotNull ItemStack itemStack, + @NotNull TransactionType type, + @NotNull TransactionOption option); + + @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, + @NotNull TransactionType type, @NotNull TransactionOption option); /** - * Changes the cursor item of a viewer, - * does nothing if player is not a viewer. + * Adds an {@link ItemStack} to the inventory and sends relevant update to the viewer(s). * - * @param player the player to change the cursor item - * @param cursorItem the new player cursor item + * @param itemStack the item to add + * @param option the transaction option + * @return true if the item has been successfully added, false otherwise */ - public void setCursorItem(@NotNull Player player, @NotNull ItemStack cursorItem) { - final ItemStack currentCursorItem = cursorPlayersItem.getOrDefault(player, ItemStack.AIR); - if (!currentCursorItem.equals(cursorItem)) { - player.sendPacket(SetSlotPacket.createCursorPacket(cursorItem)); - } - if (!cursorItem.isAir()) { - this.cursorPlayersItem.put(player, cursorItem); - } else { - this.cursorPlayersItem.remove(player); - } - } + @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option); - @Override - protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) { - itemStacks[slot] = itemStack; - if (sendPacket) sendPacketToViewers(new SetSlotPacket(getWindowId(), 0, (short) slot, itemStack)); - } - - private @NotNull WindowItemsPacket createNewWindowItemsPacket(Player player) { - return new WindowItemsPacket(getWindowId(), 0, List.of(getItemStacks()), cursorPlayersItem.getOrDefault(player, ItemStack.AIR)); - } + boolean addItemStack(@NotNull ItemStack itemStack); /** - * Sends a window property to all viewers. + * Adds {@link ItemStack}s to the inventory and sends relevant updates to the viewer(s). * - * @param property the property to send - * @param value the value of the property - * @see https://wiki.vg/Protocol#Window_Property + * @param itemStacks items to add + * @param option the transaction option + * @return the operation results */ - protected void sendProperty(@NotNull InventoryProperty property, short value) { - sendPacketToViewers(new WindowPropertyPacket(getWindowId(), property.getProperty(), value)); - } + @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, @NotNull TransactionOption option); - @Override - public boolean leftClick(@NotNull Player player, int slot) { - final PlayerInventory playerInventory = player.getInventory(); - final ItemStack cursor = getCursorItem(player); - final boolean isInWindow = isClickInWindow(slot); - final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); - final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot); - final InventoryClickResult clickResult = clickProcessor.leftClick(player, - isInWindow ? this : playerInventory, clickSlot, clicked, cursor); - if (clickResult.isCancel()) { - updateAll(player); - return false; - } - if (isInWindow) { - setItemStack(slot, clickResult.getClicked()); - } else { - playerInventory.setItemStack(clickSlot, clickResult.getClicked()); - } - this.cursorPlayersItem.put(player, clickResult.getCursor()); - callClickEvent(player, isInWindow ? this : null, slot, ClickType.LEFT_CLICK, clicked, cursor); - return true; - } + /** + * 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 + */ + @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option); - @Override - public boolean rightClick(@NotNull Player player, int slot) { - final PlayerInventory playerInventory = player.getInventory(); - final ItemStack cursor = getCursorItem(player); - final boolean isInWindow = isClickInWindow(slot); - final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); - final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot); - final InventoryClickResult clickResult = clickProcessor.rightClick(player, - isInWindow ? this : playerInventory, clickSlot, clicked, cursor); - if (clickResult.isCancel()) { - updateAll(player); - return false; - } - if (isInWindow) { - setItemStack(slot, clickResult.getClicked()); - } else { - playerInventory.setItemStack(clickSlot, clickResult.getClicked()); - } - this.cursorPlayersItem.put(player, clickResult.getCursor()); - callClickEvent(player, isInWindow ? this : null, slot, ClickType.RIGHT_CLICK, clicked, cursor); - return true; - } + /** + * Takes {@link ItemStack}s from the inventory and sends relevant updates to the viewer(s). + * + * @param itemStacks items to take + * @return the operation results + */ + @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, @NotNull TransactionOption option); - @Override - public boolean shiftClick(@NotNull Player player, int slot) { - final PlayerInventory playerInventory = player.getInventory(); - final boolean isInWindow = isClickInWindow(slot); - final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); - final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot); - final ItemStack cursor = getCursorItem(player); // Isn't used in the algorithm - final InventoryClickResult clickResult = clickProcessor.shiftClick( - isInWindow ? this : playerInventory, - isInWindow ? playerInventory : this, - 0, isInWindow ? playerInventory.getInnerSize() : getInnerSize(), 1, - player, clickSlot, clicked, cursor); - if (clickResult.isCancel()) { - updateAll(player); - return false; - } - if (isInWindow) { - setItemStack(slot, clickResult.getClicked()); - } else { - playerInventory.setItemStack(clickSlot, clickResult.getClicked()); - } - updateAll(player); // FIXME: currently not properly client-predicted - this.cursorPlayersItem.put(player, clickResult.getCursor()); - return true; - } - - @Override - public boolean changeHeld(@NotNull Player player, int slot, int key) { - final int convertedKey = key == 40 ? PlayerInventoryUtils.OFFHAND_SLOT : key; - final PlayerInventory playerInventory = player.getInventory(); - final boolean isInWindow = isClickInWindow(slot); - final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); - final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot); - final ItemStack heldItem = playerInventory.getItemStack(convertedKey); - final InventoryClickResult clickResult = clickProcessor.changeHeld(player, - isInWindow ? this : playerInventory, clickSlot, convertedKey, clicked, heldItem); - if (clickResult.isCancel()) { - updateAll(player); - return false; - } - if (isInWindow) { - setItemStack(slot, clickResult.getClicked()); - } else { - playerInventory.setItemStack(clickSlot, clickResult.getClicked()); - } - playerInventory.setItemStack(convertedKey, clickResult.getCursor()); - callClickEvent(player, isInWindow ? this : null, slot, ClickType.CHANGE_HELD, clicked, getCursorItem(player)); - return true; - } - - @Override - public boolean middleClick(@NotNull Player player, int slot) { - // TODO - update(player); - return false; - } - - @Override - public boolean drop(@NotNull Player player, boolean all, int slot, int button) { - final PlayerInventory playerInventory = player.getInventory(); - final boolean isInWindow = isClickInWindow(slot); - final boolean outsideDrop = slot == -999; - final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); - final ItemStack clicked = outsideDrop ? - ItemStack.AIR : (isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)); - final ItemStack cursor = getCursorItem(player); - final InventoryClickResult clickResult = clickProcessor.drop(player, - isInWindow ? this : playerInventory, all, clickSlot, button, clicked, cursor); - if (clickResult.isCancel()) { - updateAll(player); - return false; - } - final ItemStack resultClicked = clickResult.getClicked(); - if (!outsideDrop && resultClicked != null) { - if (isInWindow) { - setItemStack(slot, resultClicked); - } else { - playerInventory.setItemStack(clickSlot, resultClicked); - } - } - this.cursorPlayersItem.put(player, clickResult.getCursor()); - return true; - } - - @Override - public boolean dragging(@NotNull Player player, int slot, int button) { - final PlayerInventory playerInventory = player.getInventory(); - final boolean isInWindow = isClickInWindow(slot); - final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); - final ItemStack clicked = slot != -999 ? - (isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)) : - ItemStack.AIR; - final ItemStack cursor = getCursorItem(player); - final InventoryClickResult clickResult = clickProcessor.dragging(player, - slot != -999 ? (isInWindow ? this : playerInventory) : null, - clickSlot, button, - clicked, cursor); - if (clickResult == null || clickResult.isCancel()) { - updateAll(player); - return false; - } - this.cursorPlayersItem.put(player, clickResult.getCursor()); - updateAll(player); // FIXME: currently not properly client-predicted - return true; - } - - @Override - public boolean doubleClick(@NotNull Player player, int slot) { - final PlayerInventory playerInventory = player.getInventory(); - final boolean isInWindow = isClickInWindow(slot); - final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset); - final ItemStack clicked = slot != -999 ? - (isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)) : - ItemStack.AIR; - final ItemStack cursor = getCursorItem(player); - final InventoryClickResult clickResult = clickProcessor.doubleClick(isInWindow ? this : playerInventory, - this, player, clickSlot, clicked, cursor); - if (clickResult.isCancel()) { - updateAll(player); - return false; - } - this.cursorPlayersItem.put(player, clickResult.getCursor()); - updateAll(player); // FIXME: currently not properly client-predicted - return true; - } - - private boolean isClickInWindow(int slot) { - return slot < getSize(); - } - - private void updateAll(Player player) { - player.getInventory().update(); - update(player); - } } diff --git a/src/main/java/net/minestom/server/inventory/InventoryClickHandler.java b/src/main/java/net/minestom/server/inventory/InventoryClickHandler.java deleted file mode 100644 index 5fa175416..000000000 --- a/src/main/java/net/minestom/server/inventory/InventoryClickHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.minestom.server.inventory; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.EventDispatcher; -import net.minestom.server.event.inventory.InventoryClickEvent; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -/** - * Represents an inventory which can receive click input. - * All methods returning boolean returns true if the action is successful, false otherwise. - *

- * See https://wiki.vg/Protocol#Click_Window for more information. - */ -public sealed interface InventoryClickHandler permits AbstractInventory { - - /** - * Called when a {@link Player} left click in the inventory. Can also be to drop the cursor item - * - * @param player the player who clicked - * @param slot the slot number - * @return true if the click hasn't been cancelled, false otherwise - */ - boolean leftClick(@NotNull Player player, int slot); - - /** - * Called when a {@link Player} right click in the inventory. Can also be to drop the cursor item - * - * @param player the player who clicked - * @param slot the slot number - * @return true if the click hasn't been cancelled, false otherwise - */ - boolean rightClick(@NotNull Player player, int slot); - - /** - * Called when a {@link Player} shift click in the inventory - * - * @param player the player who clicked - * @param slot the slot number - * @return true if the click hasn't been cancelled, false otherwise - */ - boolean shiftClick(@NotNull Player player, int slot); // shift + left/right click have the same behavior - - /** - * Called when a {@link Player} held click in the inventory - * - * @param player the player who clicked - * @param slot the slot number - * @param key the held slot (0-8) pressed - * @return true if the click hasn't been cancelled, false otherwise - */ - boolean changeHeld(@NotNull Player player, int slot, int key); - - boolean middleClick(@NotNull Player player, int slot); - - /** - * Called when a {@link Player} press the drop button - * - * @param player the player who clicked - * @param all - * @param slot the slot number - * @param button -999 if clicking outside, normal if he is not - * @return true if the drop hasn't been cancelled, false otherwise - */ - boolean drop(@NotNull Player player, boolean all, int slot, int button); - - boolean dragging(@NotNull Player player, int slot, int button); - - /** - * Called when a {@link Player} double click in the inventory - * - * @param player the player who clicked - * @param slot the slot number - * @return true if the click hasn't been cancelled, false otherwise - */ - boolean doubleClick(@NotNull Player player, int slot); - - default void callClickEvent(@NotNull Player player, Inventory inventory, int slot, - @NotNull ClickType clickType, @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - EventDispatcher.call(new InventoryClickEvent(inventory, player, slot, clickType, clicked, cursor)); - } -} diff --git a/src/main/java/net/minestom/server/inventory/InventoryImpl.java b/src/main/java/net/minestom/server/inventory/InventoryImpl.java new file mode 100644 index 000000000..9a3269369 --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/InventoryImpl.java @@ -0,0 +1,298 @@ +package net.minestom.server.inventory; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.event.inventory.InventoryItemChangeEvent; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.ClickProcessors; +import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.server.play.CloseWindowPacket; +import net.minestom.server.network.packet.server.play.SetSlotPacket; +import net.minestom.server.network.packet.server.play.WindowItemsPacket; +import net.minestom.server.tag.TagHandler; +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.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.UnaryOperator; +import java.util.stream.IntStream; + +sealed abstract class InventoryImpl implements Inventory permits ContainerInventory, PlayerInventory { + + private static final VarHandle ITEM_UPDATER = MethodHandles.arrayElementVarHandle(ItemStack[].class); + + private final int size; + protected final ItemStack[] itemStacks; + + private final TagHandler tagHandler = TagHandler.newHandler(); + + protected final ReentrantLock lock = new ReentrantLock(); + + // the players currently viewing this inventory + protected final Set viewers = new CopyOnWriteArraySet<>(); + protected final Set unmodifiableViewers = Collections.unmodifiableSet(viewers); + + protected InventoryImpl(int size) { + this.size = size; + this.itemStacks = new ItemStack[size]; + Arrays.fill(itemStacks, ItemStack.AIR); + } + + @Override + public int getSize() { + return size; + } + + @Override + public @NotNull TagHandler tagHandler() { + return tagHandler; + } + + @Override + public @NotNull Set getViewers() { + return unmodifiableViewers; + } + + @Override + public boolean addViewer(@NotNull Player player) { + if (!this.viewers.add(player)) return false; + + update(player); + return true; + } + + @Override + public boolean removeViewer(@NotNull Player player) { + if (!this.viewers.remove(player)) return false; + + ItemStack cursorItem = player.getInventory().getCursorItem(); + player.getInventory().setCursorItem(ItemStack.AIR); + + if (!cursorItem.isAir()) { + // Drop the item if it can not be added back to the inventory + if (!player.getInventory().addItemStack(cursorItem)) { + player.dropItem(cursorItem); + } + } + + player.clickPreprocessor().clearCache(); + if (player.didCloseInventory()) { + player.UNSAFE_changeDidCloseInventory(false); + } else { + player.sendPacket(new CloseWindowPacket(getWindowId())); + } + return true; + } + + /** + * Updates the provided slot for this inventory's viewers. + * + * @param slot the slot to update + * @param itemStack the item treated as in the slot + */ + protected void updateSlot(int slot, @NotNull ItemStack itemStack) { + sendPacketToViewers(new SetSlotPacket(getWindowId(), 0, (short) slot, itemStack)); + } + + @Override + public void update() { + this.viewers.forEach(this::update); + } + + @Override + public void update(@NotNull Player player) { + player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(itemStacks), player.getInventory().getCursorItem())); + } + + @Override + public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) { + var processor = ClickProcessors.standard( + (builder, item, slot) -> slot >= getSize() ? + IntStream.range(0, getSize()) : + PlayerInventory.getInnerShiftClickSlots(getSize()), + (builder, item, slot) -> IntStream.concat( + IntStream.range(0, getSize()), + PlayerInventory.getInnerDoubleClickSlots(getSize()) + )); + return ContainerInventory.handleClick(this, player, info, processor); + } + + @Override + public @NotNull ItemStack getItemStack(int slot) { + return (ItemStack) ITEM_UPDATER.getVolatile(itemStacks, slot); + } + + @Override + public @NotNull ItemStack[] getItemStacks() { + return itemStacks.clone(); + } + + @Override + 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 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 final void safeItemInsert(int slot, @NotNull ItemStack itemStack) { + safeItemInsert(slot, itemStack, true); + } + + /** + * Inserts safely an item into the inventory. + *

+ * This will update the slot for all viewers and warn the inventory that + * the window items packet is not up-to-date. + * + * @param slot the internal slot id + * @param itemStack the item to insert (use air instead of null) + * @throws IllegalArgumentException if the slot {@code slot} does not exist + */ + protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) { + lock.lock(); + + try { + ItemStack previous = itemStacks[slot]; + if (itemStack.equals(previous)) return; // Avoid sending updates if the item has not changed + + UNSAFE_itemInsert(slot, itemStack); + if (sendPacket) updateSlot(slot, itemStack); + + EventDispatcher.call(new InventoryItemChangeEvent(this, slot, previous, itemStack)); + } finally { + lock.unlock(); + } + } + + protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack) { + itemStacks[slot] = itemStack; + } + + @Override + public void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) { + lock.lock(); + + try { + var currentItem = getItemStack(slot); + setItemStack(slot, operator.apply(currentItem)); + } finally { + lock.unlock(); + } + } + + @Override + public void clear() { + lock.lock(); + + try { + for (Player viewer : getViewers()) { + viewer.getInventory().setCursorItem(ItemStack.AIR, false); + } + + // Clear the item array + for (int i = 0; i < size; i++) { + safeItemInsert(i, ItemStack.AIR, false); + } + // Send the cleared inventory to viewers + update(); + } finally { + lock.unlock(); + } + } + + @Override + public @NotNull T processItemStack(@NotNull ItemStack itemStack, + @NotNull TransactionType type, + @NotNull TransactionOption option) { + lock.lock(); + try { + return option.fill(type, this, itemStack); + } finally { + lock.unlock(); + } + } + + @Override + public @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, + @NotNull TransactionType type, + @NotNull TransactionOption option) { + List result = new ArrayList<>(itemStacks.size()); + + lock.lock(); + try { + for (ItemStack item : itemStacks) { + result.add(processItemStack(item, type, option)); + } + } finally { + lock.unlock(); + } + return result; + } + + @Override + public @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { + List slots = IntStream.range(0, getSize()).boxed().toList(); + return processItemStack(itemStack, TransactionType.add(slots, slots), option); + } + + @Override + public boolean addItemStack(@NotNull ItemStack itemStack) { + return addItemStack(itemStack, TransactionOption.ALL_OR_NOTHING); + } + + @Override + public @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, + @NotNull TransactionOption option) { + List result = new ArrayList<>(itemStacks.size()); + + lock.lock(); + try { + for (ItemStack item : itemStacks) { + result.add(addItemStack(item, option)); + } + } finally { + lock.unlock(); + } + return result; + } + + @Override + public @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { + return processItemStack(itemStack, TransactionType.take(IntStream.range(0, getSize()).boxed().toList()), option); + } + + @Override + public @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, + @NotNull TransactionOption option) { + List result = new ArrayList<>(itemStacks.size()); + + lock.lock(); + try { + for (ItemStack item : itemStacks) { + result.add(takeItemStack(item, option)); + } + } finally { + lock.unlock(); + } + return result; + } + +} diff --git a/src/main/java/net/minestom/server/inventory/InventoryType.java b/src/main/java/net/minestom/server/inventory/InventoryType.java index 9b77808c0..804cc4619 100644 --- a/src/main/java/net/minestom/server/inventory/InventoryType.java +++ b/src/main/java/net/minestom/server/inventory/InventoryType.java @@ -1,7 +1,7 @@ package net.minestom.server.inventory; /** - * Represents a type of {@link Inventory} + * Represents a type of {@link ContainerInventory} */ public enum InventoryType { diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index 988ecdc0e..d7a08bed7 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -4,327 +4,230 @@ import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.Player; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.item.EntityEquipEvent; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.inventory.click.InventoryClickResult; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.ClickProcessors; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; /** * Represents the inventory of a {@link Player}, retrieved with {@link Player#getInventory()}. */ -public non-sealed class PlayerInventory extends AbstractInventory implements EquipmentHandler { - public static final int INVENTORY_SIZE = 46; - public static final int INNER_INVENTORY_SIZE = 36; +public non-sealed class PlayerInventory extends InventoryImpl { + + public static @NotNull IntStream getInnerShiftClickSlots(int size) { + return IntStream.range(0, 36).map(i -> i + size); + } + + public static @NotNull IntStream getInnerDoubleClickSlots(int size) { + return IntStream.range(0, 36).map(i -> i + size); + } + + private static int getSlotIndex(@NotNull EquipmentSlot slot, int heldSlot) { + return switch (slot) { + case HELMET, CHESTPLATE, LEGGINGS, BOOTS -> slot.armorSlot(); + case OFF_HAND -> OFF_HAND_SLOT; + case MAIN_HAND -> heldSlot; + }; + } + + private static @Nullable EquipmentSlot fromSlotIndex(int slot, int heldSlot) { + return switch (slot) { + case HELMET_SLOT -> EquipmentSlot.HELMET; + case CHESTPLATE_SLOT -> EquipmentSlot.CHESTPLATE; + case LEGGINGS_SLOT -> EquipmentSlot.LEGGINGS; + case BOOTS_SLOT -> EquipmentSlot.BOOTS; + case OFF_HAND_SLOT -> EquipmentSlot.OFF_HAND; + default -> slot == heldSlot ? EquipmentSlot.MAIN_HAND : null; + }; + } + + private static final List FILL_ADD_SLOTS = IntStream.concat( + IntStream.of(OFF_HAND_SLOT), + IntStream.range(0, 36) + ).boxed().toList(); + + private static final List AIR_ADD_SLOTS = IntStream.range(0, 36).boxed().toList(); + + private static final List TAKE_SLOTS = Stream.of( + IntStream.range(0, 36), + IntStream.of(OFF_HAND_SLOT), + IntStream.range(36, 45) + ).flatMapToInt(i -> i).boxed().toList(); - protected final Player player; private ItemStack cursorItem = ItemStack.AIR; - public PlayerInventory(@NotNull Player player) { + public PlayerInventory() { super(INVENTORY_SIZE); - this.player = player; } @Override - public synchronized void clear() { - cursorItem = ItemStack.AIR; - super.clear(); - // Update equipments - this.player.sendPacketToViewersAndSelf(player.getEquipmentsPacket()); - } - - @Override - public int getInnerSize() { - return INNER_INVENTORY_SIZE; - } - - @Override - public @NotNull ItemStack getItemInMainHand() { - return getItemStack(player.getHeldSlot()); - } - - @Override - public void setItemInMainHand(@NotNull ItemStack itemStack) { - safeItemInsert(player.getHeldSlot(), itemStack); - } - - @Override - public @NotNull ItemStack getItemInOffHand() { - return getItemStack(OFFHAND_SLOT); - } - - @Override - public void setItemInOffHand(@NotNull ItemStack itemStack) { - safeItemInsert(OFFHAND_SLOT, itemStack); - } - - @Override - public @NotNull ItemStack getHelmet() { - return getItemStack(HELMET_SLOT); - } - - @Override - public void setHelmet(@NotNull ItemStack itemStack) { - safeItemInsert(HELMET_SLOT, itemStack); - } - - @Override - public @NotNull ItemStack getChestplate() { - return getItemStack(CHESTPLATE_SLOT); - } - - @Override - public void setChestplate(@NotNull ItemStack itemStack) { - safeItemInsert(CHESTPLATE_SLOT, itemStack); - } - - @Override - public @NotNull ItemStack getLeggings() { - return getItemStack(LEGGINGS_SLOT); - } - - @Override - public void setLeggings(@NotNull ItemStack itemStack) { - safeItemInsert(LEGGINGS_SLOT, itemStack); - } - - @Override - public @NotNull ItemStack getBoots() { - return getItemStack(BOOTS_SLOT); - } - - @Override - public void setBoots(@NotNull ItemStack itemStack) { - safeItemInsert(BOOTS_SLOT, itemStack); + public byte getWindowId() { + return 0; } /** - * Refreshes the player inventory by sending a {@link WindowItemsPacket} containing all. - * the inventory items - */ - @Override - public void update() { - this.player.sendPacket(createWindowItemsPacket()); - } - - /** - * Gets the item in player cursor. - * - * @return the cursor item + * Gets the cursor item of this inventory + * @return the cursor item that is shared between all viewers */ public @NotNull ItemStack getCursorItem() { return cursorItem; } /** - * Changes the player cursor item. - * - * @param cursorItem the new cursor item + * Sets the cursor item for all viewers of this inventory. + * @param cursorItem the new item (will not update if same as current) */ public void setCursorItem(@NotNull ItemStack cursorItem) { + setCursorItem(cursorItem, true); + } + + /** + * Sets the cursor item for all viewers of this inventory. + * @param cursorItem the new item (will not update if same as current) + * @param sendPacket whether or not to send a packet + */ + public void setCursorItem(@NotNull ItemStack cursorItem, boolean sendPacket) { if (this.cursorItem.equals(cursorItem)) return; - this.cursorItem = cursorItem; - final SetSlotPacket setSlotPacket = SetSlotPacket.createCursorPacket(cursorItem); - this.player.sendPacket(setSlotPacket); + + lock.lock(); + try { + this.cursorItem = cursorItem; + } finally { + lock.unlock(); + } + + if (!sendPacket) return; + sendPacketToViewers(SetSlotPacket.createCursorPacket(cursorItem)); } @Override - protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) { - final EquipmentSlot equipmentSlot = switch (slot) { - case HELMET_SLOT -> EquipmentSlot.HELMET; - case CHESTPLATE_SLOT -> EquipmentSlot.CHESTPLATE; - case LEGGINGS_SLOT -> EquipmentSlot.LEGGINGS; - case BOOTS_SLOT -> EquipmentSlot.BOOTS; - case OFFHAND_SLOT -> EquipmentSlot.OFF_HAND; - default -> slot == player.getHeldSlot() ? EquipmentSlot.MAIN_HAND : null; - }; - if (equipmentSlot != null) { + public void updateSlot(int slot, @NotNull ItemStack itemStack) { + SetSlotPacket defaultPacket = new SetSlotPacket(getWindowId(), 0, (short) PlayerInventoryUtils.minestomToProtocol(slot), itemStack); + + for (Player player : getViewers()) { + Inventory open = player.getOpenInventory(); + if (open != null && slot >= 0 && slot < INNER_SIZE) { + player.sendPacket(new SetSlotPacket(open.getWindowId(), 0, (short) PlayerInventoryUtils.minestomToProtocol(slot, open.getSize()), itemStack)); + } else if (open == null || slot == OFF_HAND_SLOT) { + player.sendPacket(defaultPacket); + } + + var equipmentSlot = fromSlotIndex(slot, player.getHeldSlot()); + if (equipmentSlot == null) continue; + + player.syncEquipment(equipmentSlot, itemStack); + } + } + + @Override + public void update(@NotNull Player player) { + ItemStack[] local = getItemStacks(); + ItemStack[] mapped = new ItemStack[getSize()]; + + for (int slot = 0; slot < getSize(); slot++) { + mapped[PlayerInventoryUtils.minestomToProtocol(slot)] = local[slot]; + } + + player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(mapped), getCursorItem())); + } + + @Override + protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack) { + for (var player : getViewers()) { + final EquipmentSlot equipmentSlot = fromSlotIndex(slot, player.getHeldSlot()); + if (equipmentSlot == null) continue; + EntityEquipEvent entityEquipEvent = new EntityEquipEvent(player, itemStack, equipmentSlot); EventDispatcher.call(entityEquipEvent); itemStack = entityEquipEvent.getEquippedItem(); } - this.itemStacks[slot] = itemStack; - if (sendPacket) { - // Sync equipment - if (equipmentSlot != null) this.player.syncEquipment(equipmentSlot); - // Refresh slot - sendSlotRefresh((short) convertToPacketSlot(slot), itemStack); - } - } - - /** - * Refreshes an inventory slot. - * - * @param slot the packet slot, - * see {@link net.minestom.server.utils.inventory.PlayerInventoryUtils#convertToPacketSlot(int)} - * @param itemStack the item stack in the slot - */ - protected void sendSlotRefresh(short slot, ItemStack itemStack) { - var openInventory = player.getOpenInventory(); - if (openInventory != null && slot >= OFFSET && slot < OFFSET + INNER_INVENTORY_SIZE) { - this.player.sendPacket(new SetSlotPacket(openInventory.getWindowId(), 0, (short) (slot + openInventory.getSize() - OFFSET), itemStack)); - } else if (openInventory == null || slot == OFFHAND_SLOT) { - this.player.sendPacket(new SetSlotPacket((byte) 0, 0, slot, itemStack)); - } - } - - /** - * Gets a {@link WindowItemsPacket} with all the items in the inventory. - * - * @return a {@link WindowItemsPacket} with inventory items - */ - private WindowItemsPacket createWindowItemsPacket() { - ItemStack[] convertedSlots = new ItemStack[INVENTORY_SIZE]; - for (int i = 0; i < itemStacks.length; i++) { - final int slot = convertToPacketSlot(i); - convertedSlots[slot] = itemStacks[i]; - } - return new WindowItemsPacket((byte) 0, 0, List.of(convertedSlots), cursorItem); + super.UNSAFE_itemInsert(slot, itemStack); } @Override - public boolean leftClick(@NotNull Player player, int slot) { - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - final ItemStack cursor = getCursorItem(); - final ItemStack clicked = getItemStack(convertedSlot); - final InventoryClickResult clickResult = clickProcessor.leftClick(player, this, convertedSlot, clicked, cursor); - if (clickResult.isCancel()) { - update(); - return false; + public void clear() { + lock.lock(); + try { + super.clear(); + + for (var player : getViewers()) { + player.sendPacketToViewersAndSelf(player.getEquipmentsPacket()); + } + } finally { + lock.unlock(); } - setItemStack(convertedSlot, clickResult.getClicked()); - setCursorItem(clickResult.getCursor()); - callClickEvent(player, null, convertedSlot, ClickType.LEFT_CLICK, clicked, cursor); - return true; } @Override - public boolean rightClick(@NotNull Player player, int slot) { - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - final ItemStack cursor = getCursorItem(); - final ItemStack clicked = getItemStack(convertedSlot); - final InventoryClickResult clickResult = clickProcessor.rightClick(player, this, convertedSlot, clicked, cursor); - if (clickResult.isCancel()) { - update(); - return false; - } - setItemStack(convertedSlot, clickResult.getClicked()); - setCursorItem(clickResult.getCursor()); - callClickEvent(player, null, convertedSlot, ClickType.RIGHT_CLICK, clicked, cursor); - return true; + public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) { + var processor = ClickProcessors.standard( + (getter, item, slot) -> { + List slots = new ArrayList<>(); + + var equipmentSlot = item.material().registry().equipmentSlot(); + if (equipmentSlot != null && slot != equipmentSlot.armorSlot()) { + slots.add(equipmentSlot.armorSlot()); + } + + if (item.material() == Material.SHIELD && slot != OFF_HAND_SLOT) { + slots.add(OFF_HAND_SLOT); + } + + if (slot < 9 || slot > 35) { + IntStream.range(9, 36).forEach(slots::add); + } + + if (slot < 0 || slot > 8) { + IntStream.range(0, 9).forEach(slots::add); + } + + if (slot == CRAFT_RESULT) { + Collections.reverse(slots); + } + + return slots.stream().mapToInt(i -> i); + }, + (getter, item, slot) -> Stream.of( + IntStream.range(CRAFT_SLOT_1, CRAFT_SLOT_4 + 1), // 1-4 + IntStream.range(HELMET_SLOT, BOOTS_SLOT + 1), // 5-8 + IntStream.range(9, 36), // 9-35 + IntStream.range(0, 9), // 36-44 + IntStream.of(OFF_HAND_SLOT) // 45 + ).flatMapToInt(i -> i) + ); + return ContainerInventory.handleClick(this, player, info, processor); + } + + public @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot, int heldSlot) { + return getItemStack(getSlotIndex(slot, heldSlot)); + } + + public void setEquipment(@NotNull EquipmentSlot slot, int heldSlot, @NotNull ItemStack newValue) { + setItemStack(getSlotIndex(slot, heldSlot), newValue); } @Override - public boolean middleClick(@NotNull Player player, int slot) { - // TODO - update(); - return false; + public @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { + return processItemStack(itemStack, TransactionType.add(FILL_ADD_SLOTS, AIR_ADD_SLOTS), option); } @Override - public boolean drop(@NotNull Player player, boolean all, int slot, int button) { - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - final ItemStack cursor = getCursorItem(); - final boolean outsideDrop = slot == -999; - final ItemStack clicked = outsideDrop ? ItemStack.AIR : getItemStack(convertedSlot); - final InventoryClickResult clickResult = clickProcessor.drop(player, this, - all, convertedSlot, button, clicked, cursor); - if (clickResult.isCancel()) { - update(); - return false; - } - final ItemStack resultClicked = clickResult.getClicked(); - if (resultClicked != null && !outsideDrop) { - setItemStack(convertedSlot, resultClicked); - } - setCursorItem(clickResult.getCursor()); - return true; + public @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { + return processItemStack(itemStack, TransactionType.take(TAKE_SLOTS), option); } - @Override - public boolean shiftClick(@NotNull Player player, int slot) { - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - final ItemStack cursor = getCursorItem(); - final ItemStack clicked = getItemStack(convertedSlot); - final boolean hotBarClick = convertSlot(slot, OFFSET) < 9; - final int start = hotBarClick ? 9 : 0; - final int end = hotBarClick ? getSize() - 9 : 8; - final InventoryClickResult clickResult = clickProcessor.shiftClick( - this, this, - start, end, 1, - player, convertedSlot, clicked, cursor); - if (clickResult.isCancel()) { - update(); - return false; - } - setItemStack(convertedSlot, clickResult.getClicked()); - setCursorItem(clickResult.getCursor()); - update(); // FIXME: currently not properly client-predicted - return true; - } - - @Override - public boolean changeHeld(@NotNull Player player, int slot, int key) { - final int convertedKey = key == 40 ? OFFHAND_SLOT : key; - final ItemStack cursorItem = getCursorItem(); - if (!cursorItem.isAir()) return false; - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - final ItemStack heldItem = getItemStack(convertedKey); - final ItemStack clicked = getItemStack(convertedSlot); - final InventoryClickResult clickResult = clickProcessor.changeHeld(player, this, convertedSlot, convertedKey, clicked, heldItem); - if (clickResult.isCancel()) { - update(); - return false; - } - setItemStack(convertedSlot, clickResult.getClicked()); - setItemStack(convertedKey, clickResult.getCursor()); - callClickEvent(player, null, convertedSlot, ClickType.CHANGE_HELD, clicked, cursorItem); - return true; - } - - @Override - public boolean dragging(@NotNull Player player, int slot, int button) { - final ItemStack cursor = getCursorItem(); - final ItemStack clicked = slot != -999 ? getItemStackFromPacketSlot(slot) : ItemStack.AIR; - final InventoryClickResult clickResult = clickProcessor.dragging(player, this, - convertPlayerInventorySlot(slot, OFFSET), button, clicked, cursor); - if (clickResult == null || clickResult.isCancel()) { - update(); - return false; - } - setCursorItem(clickResult.getCursor()); - update(); // FIXME: currently not properly client-predicted - return true; - } - - @Override - public boolean doubleClick(@NotNull Player player, int slot) { - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - final ItemStack cursor = getCursorItem(); - final ItemStack clicked = getItemStack(convertedSlot); - final InventoryClickResult clickResult = clickProcessor.doubleClick(this, this, player, convertedSlot, clicked, cursor); - if (clickResult.isCancel()) { - update(); - return false; - } - setCursorItem(clickResult.getCursor()); - update(); // FIXME: currently not properly client-predicted - return true; - } - - private void setItemStackFromPacketSlot(int slot, @NotNull ItemStack itemStack) { - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - setItemStack(convertedSlot, itemStack); - } - - private ItemStack getItemStackFromPacketSlot(int slot) { - final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET); - return itemStacks[convertedSlot]; - } } diff --git a/src/main/java/net/minestom/server/inventory/TransactionOperator.java b/src/main/java/net/minestom/server/inventory/TransactionOperator.java new file mode 100644 index 000000000..df298e6fe --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/TransactionOperator.java @@ -0,0 +1,116 @@ +package net.minestom.server.inventory; + +import it.unimi.dsi.fastutil.Pair; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.StackingRule; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.BiPredicate; + +/** + * A transaction operator is a simpler operation that takes two items and returns two items. + *
+ * This allows a significant amount of logic reuse, since many operations are just the {@link #flip(TransactionOperator) flipped} + * version of others. + */ +public interface TransactionOperator { + + /** + * Creates a new operator that filters the given one using the provided predicate + */ + static @NotNull TransactionOperator filter(@NotNull TransactionOperator operator, @NotNull BiPredicate predicate) { + return (left, right) -> predicate.test(left, right) ? operator.apply(left, right) : null; + } + + /** + * Creates a new operator that flips the left and right before providing it to the given operator. + */ + static @NotNull TransactionOperator flip(@NotNull TransactionOperator operator) { + return (left, right) -> { + var pair = operator.apply(right, left); + return pair != null ? Pair.of(pair.right(), pair.left()) : null; + }; + } + + /** + * Provides operators that try to stack up to the provided number of items to the left. + */ + static @NotNull TransactionOperator stackLeftN(int count) { + return (left, right) -> { + final StackingRule rule = StackingRule.get(); + + // Quick exit if the right is air (nothing can be transferred anyway) + // If the left is air then we know it can be transferred, but it can also be transferred if they're stackable + // and left isn't full, even if left isn't air. + if (right.isAir() || (!left.isAir() && !(rule.canBeStacked(left, right) && rule.getAmount(left) < rule.getMaxSize(left)))) { + return null; + } + + int leftAmount = left.isAir() ? 0 : rule.getAmount(left); + int rightAmount = rule.getAmount(right); + + int addedAmount = Math.min(Math.min(rightAmount, count), rule.getMaxSize(left) - leftAmount); + + if (addedAmount == 0) return null; + + return Pair.of(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); + }; + } + + /** + * Stacks as many items to the left as possible, including if the left is an air item.
+ * This will not swap the items if they are of different types. + */ + TransactionOperator STACK_LEFT = (left, right) -> { + final StackingRule rule = StackingRule.get(); + + // Quick exit if the right is air (nothing can be transferred anyway) + // If the left is air then we know it can be transferred, but it can also be transferred if they're stackable + // and left isn't full, even if left isn't air. + if (right.isAir() || (!left.isAir() && !(rule.canBeStacked(left, right) && rule.getAmount(left) < rule.getMaxSize(left)))) { + return null; + } + + int leftAmount = left.isAir() ? 0 : rule.getAmount(left); + int rightAmount = rule.getAmount(right); + + int addedAmount = Math.min(rightAmount, rule.getMaxSize(left) - leftAmount); + + return Pair.of(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); + }; + + /** + * Stacks as many items to the right as possible. This is the flipped version of {@link #STACK_LEFT}. + */ + TransactionOperator STACK_RIGHT = flip(STACK_LEFT); + + /** + * Takes as many items as possible from both stacks, if the given items are stackable. + * This is a symmetric operation. + */ + TransactionOperator TAKE = (left, right) -> { + final StackingRule rule = StackingRule.get(); + + if (right.isAir() || !rule.canBeStacked(left, right)) { + return null; + } + + int leftAmount = rule.getAmount(left); + int rightAmount = rule.getAmount(right); + + int subtracted = Math.min(leftAmount, rightAmount); + + return Pair.of(rule.apply(left, leftAmount - subtracted), rule.apply(right, rightAmount - subtracted)); + }; + + /** + * Applies this operation to the two given items. + * They are unnamed as to abstract the operations performed from inventories. + * @param left the "left" item + * @param right the "right" item + * @return the resulting pair, or null to indicate no changes + */ + @Nullable Pair apply(@NotNull ItemStack left, @NotNull ItemStack right); + +} diff --git a/src/main/java/net/minestom/server/inventory/TransactionOption.java b/src/main/java/net/minestom/server/inventory/TransactionOption.java index 577784ebd..4fe1417ca 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionOption.java +++ b/src/main/java/net/minestom/server/inventory/TransactionOption.java @@ -1,32 +1,30 @@ package net.minestom.server.inventory; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; -import java.util.Map; - @FunctionalInterface public interface TransactionOption { /** - * Place as much as the item as possible. - *

- * The remaining, can be air. + * Performs as much of the operation as is possible. + * Returns the remaining item in the operation (can be air). */ TransactionOption ALL = (inventory, result, itemChangesMap) -> { - itemChangesMap.forEach(inventory::safeItemInsert); + itemChangesMap.forEach(inventory::setItemStack); return result; }; /** - * Only place the item if can be fully added. - *

- * Returns true if the item has been added, false if nothing changed. + * Performs the operation atomically (only if the operation resulted in air), returning whether or not the operation + * was performed. */ TransactionOption ALL_OR_NOTHING = (inventory, result, itemChangesMap) -> { if (result.isAir()) { // Item can be fully placed inside the inventory, do so - itemChangesMap.forEach(inventory::safeItemInsert); + itemChangesMap.forEach(inventory::setItemStack); return true; } else { // Inventory cannot accept the item fully @@ -35,20 +33,14 @@ public interface TransactionOption { }; /** - * Loop through the inventory items without changing anything. - *

- * Returns true if the item can be fully added, false otherwise. + * Discards the result of the operation, returning whether or not the operation could have finished. */ TransactionOption DRY_RUN = (inventory, result, itemChangesMap) -> result.isAir(); - @NotNull T fill(@NotNull AbstractInventory inventory, - @NotNull ItemStack result, - @NotNull Map<@NotNull Integer, @NotNull ItemStack> itemChangesMap); + @NotNull T fill(@NotNull Inventory inventory, @NotNull ItemStack result, @NotNull Int2ObjectMap itemChangesMap); - default @NotNull T fill(@NotNull TransactionType type, - @NotNull AbstractInventory inventory, - @NotNull ItemStack itemStack) { - var pair = type.process(inventory, itemStack); - return fill(inventory, pair.left(), pair.right()); + default @NotNull T fill(@NotNull TransactionType type, @NotNull Inventory inventory, @NotNull ItemStack itemStack) { + Pair> result = type.process(itemStack, inventory::getItemStack); + return fill(inventory, result.left(), result.right()); } } diff --git a/src/main/java/net/minestom/server/inventory/TransactionType.java b/src/main/java/net/minestom/server/inventory/TransactionType.java index 253b4a4e3..2ab3e7c3d 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionType.java +++ b/src/main/java/net/minestom/server/inventory/TransactionType.java @@ -1,124 +1,86 @@ package net.minestom.server.inventory; import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; 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; +import java.util.List; +import java.util.function.IntFunction; /** - * Represents a type of transaction that you can apply to an {@link AbstractInventory}. + * Represents a type of transaction that you can apply to an {@link Inventory}. */ -@FunctionalInterface public interface TransactionType { + /** + * Applies a transaction operator to a given list of slots, turning it into a TransactionType. + */ + static @NotNull TransactionType general(@NotNull TransactionOperator operator, @NotNull List slots) { + return (item, getter) -> { + Int2ObjectMap map = new Int2ObjectArrayMap<>(); + for (int slot : slots) { + ItemStack slotItem = getter.apply(slot); + + Pair result = operator.apply(slotItem, item); + if (result == null) continue; + + map.put(slot, result.first()); + item = result.second(); + } + + return Pair.of(item, map); + }; + } + + /** + * Joins two transaction types consecutively. + * This will use the same getter in both cases, so ensure that any potential overlap between the transaction types + * will not result in unexpected behaviour (e.g. item duping). + */ + static @NotNull TransactionType join(@NotNull TransactionType first, @NotNull TransactionType second) { + return (item, getter) -> { + // Calculate results + Pair> f = first.process(item, getter); + Pair> s = second.process(f.left(), getter); + + // Join results + Int2ObjectMap map = new Int2ObjectArrayMap<>(); + map.putAll(f.right()); + map.putAll(s.right()); + return Pair.of(s.left(), map); + }; + } + /** * Adds an item to the inventory. * Can either take an air slot or be stacked. + * + * @param fill the list of slots that will be added to if they already have some of the item in it + * @param air the list of slots that will be added to if they have air (may be different from {@code fill}). */ - TransactionType ADD = (inventory, itemStack, slotPredicate, start, end, step) -> { - Int2ObjectMap itemChangesMap = new Int2ObjectOpenHashMap<>(); - final StackingRule stackingRule = StackingRule.get(); - // Check filled slot (not air) - for (int i = start; i < end; i += step) { - ItemStack inventoryItem = inventory.getItemStack(i); - if (inventoryItem.isAir()) { - continue; - } - if (stackingRule.canBeStacked(itemStack, inventoryItem)) { - final int itemAmount = stackingRule.getAmount(inventoryItem); - final int maxSize = stackingRule.getMaxSize(inventoryItem); - if (itemAmount >= maxSize) continue; - if (!slotPredicate.test(i, inventoryItem)) { - // Cancelled transaction - 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, maxSize)); - itemStack = stackingRule.apply(itemStack, totalAmount - maxSize); - } 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 = start; i < end; i += step) { - ItemStack inventoryItem = inventory.getItemStack(i); - if (!inventoryItem.isAir()) continue; - if (!slotPredicate.test(i, inventoryItem)) { - // Cancelled transaction - continue; - } - // Fill the slot - itemChangesMap.put(i, itemStack); - itemStack = stackingRule.apply(itemStack, 0); - break; - } - return Pair.of(itemStack, itemChangesMap); - }; + static @NotNull TransactionType add(@NotNull List fill, @NotNull List air) { + var first = general((slotItem, extra) -> !slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null, fill); + var second = general((slotItem, extra) -> slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null, air); + return TransactionType.join(first, second); + } /** * Takes an item from the inventory. * Can either transform items to air or reduce their amount. + * @param takeSlots the ordered list of slots that will be taken from (if possible) */ - TransactionType TAKE = (inventory, itemStack, slotPredicate, start, end, step) -> { - Int2ObjectMap itemChangesMap = new Int2ObjectOpenHashMap<>(); - final StackingRule stackingRule = StackingRule.get(); - for (int i = start; i < end; i += step) { - final ItemStack inventoryItem = inventory.getItemStack(i); - if (inventoryItem.isAir()) continue; - if (stackingRule.canBeStacked(itemStack, inventoryItem)) { - if (!slotPredicate.test(i, inventoryItem)) { - // Cancelled transaction - continue; - } - - final int itemAmount = stackingRule.getAmount(inventoryItem); - final int itemStackAmount = stackingRule.getAmount(itemStack); - if (itemStackAmount < itemAmount) { - itemChangesMap.put(i, stackingRule.apply(inventoryItem, itemAmount - itemStackAmount)); - itemStack = stackingRule.apply(itemStack, 0); - break; - } - itemChangesMap.put(i, stackingRule.apply(inventoryItem, 0)); - itemStack = stackingRule.apply(itemStack, itemStackAmount - itemAmount); - if (stackingRule.getAmount(itemStack) == 0) { - itemStack = stackingRule.apply(itemStack, 0); - break; - } - } - } - return Pair.of(itemStack, itemChangesMap); - }; - - @NotNull Pair> process(@NotNull AbstractInventory inventory, - @NotNull ItemStack itemStack, - @NotNull SlotPredicate slotPredicate, - int start, int end, int step); - - default @NotNull Pair> process(@NotNull AbstractInventory inventory, - @NotNull ItemStack itemStack, - @NotNull SlotPredicate slotPredicate) { - return process(inventory, itemStack, slotPredicate, 0, inventory.getInnerSize(), 1); + static @NotNull TransactionType take(@NotNull List takeSlots) { + return general(TransactionOperator.TAKE, takeSlots); } - default @NotNull Pair> process(@NotNull AbstractInventory inventory, - @NotNull ItemStack itemStack) { - return process(inventory, itemStack, (slot, itemStack1) -> true); - } + /** + * Processes the provided item into the given inventory. + * @param itemStack the item to process + * @param inventory the inventory function; must support #get and #put operations. + * @return the remaining portion of the processed item + */ + @NotNull Pair> process(@NotNull ItemStack itemStack, @NotNull IntFunction inventory); - @FunctionalInterface - interface SlotPredicate { - boolean test(int slot, @NotNull ItemStack itemStack); - } } diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java new file mode 100644 index 000000000..159a00e3f --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -0,0 +1,280 @@ +package net.minestom.server.inventory.click; + +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.IntFunction; + +public final class Click { + + /** + * Contains information about a click. These are equal to the packet slot IDs from the Minecraft protocol.. + * The inventory used should be known from context. + */ + public sealed interface Info { + + record Left(int slot) implements Info {} + record Right(int slot) implements Info {} + record Middle(int slot) implements Info {} // Creative only + + record LeftShift(int slot) implements Info {} + record RightShift(int slot) implements Info {} + + record Double(int slot) implements Info {} + + record LeftDrag(List slots) implements Info {} + record RightDrag(List slots) implements Info {} + record MiddleDrag(List slots) implements Info {} // Creative only + + record LeftDropCursor() implements Info {} + record RightDropCursor() implements Info {} + record MiddleDropCursor() implements Info {} + + record DropSlot(int slot, boolean all) implements Info {} + + record HotbarSwap(int hotbarSlot, int clickedSlot) implements Info {} + record OffhandSwap(int slot) implements Info {} + + record CreativeSetItem(int slot, @NotNull ItemStack item) implements Info {} + record CreativeDropItem(@NotNull ItemStack item) implements Info {} + + } + + /** + * Preprocesses click packets, turning them into {@link Info} instances for further processing. + */ + public static final class Preprocessor { + + private final List leftDrag = new ArrayList<>(); + private final List rightDrag = new ArrayList<>(); + private final List middleDrag = new ArrayList<>(); + + public void clearCache() { + leftDrag.clear(); + rightDrag.clear(); + middleDrag.clear(); + } + + /** + * Processes the provided click packet, turning it into a {@link Info}. + * This will do simple verification of the packet before sending it to {@link #process(ClientClickWindowPacket.ClickType, int, byte, boolean)}. + * + * @param packet the raw click packet + * @param isCreative whether or not the player is in creative mode (used for ignoring some actions) + * @return the information about the click, or nothing if there was no immediately usable information + */ + public @Nullable Click.Info process(@NotNull ClientClickWindowPacket packet, @NotNull Inventory inventory, boolean isCreative) { + final int originalSlot = packet.slot(); + final byte button = packet.button(); + final ClientClickWindowPacket.ClickType type = packet.clickType(); + + int slot = inventory instanceof PlayerInventory ? PlayerInventoryUtils.protocolToMinestom(originalSlot) : originalSlot; + if (originalSlot == -999) slot = -999; + + boolean creativeRequired = switch (type) { + case CLONE -> true; + case QUICK_CRAFT -> button == 8 || button == 9 || button == 10; + default -> false; + }; + + if (creativeRequired && !isCreative) return null; + + int maxSize = inventory.getSize() + (inventory instanceof PlayerInventory ? 0 : PlayerInventoryUtils.INNER_SIZE); + return process(type, slot, button, slot >= 0 && slot < maxSize); + } + + /** + * Processes a packet into click info. + * + * @param type the type of the click + * @param slot the clicked slot + * @param button the sent button + * @param valid whether or not {@code slot} fits within the inventory (may be unused, depending on click) + * @return the information about the click, or nothing if there was no immediately usable information + */ + public @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, + int slot, byte button, boolean valid) { + return switch (type) { + case PICKUP -> { + if (slot == -999) { + yield switch (button) { + case 0 -> new Info.LeftDropCursor(); + case 1 -> new Info.RightDropCursor(); + case 2 -> new Info.MiddleDropCursor(); + default -> null; + }; + } + + if (!valid) yield null; + + yield switch (button) { + case 0 -> new Info.Left(slot); + case 1 -> new Info.Right(slot); + default -> null; + }; + } + case QUICK_MOVE -> { + if (!valid) yield null; + yield button == 0 ? new Info.LeftShift(slot) : new Info.RightShift(slot); + } + case SWAP -> { + if (!valid) { + yield null; + } else if (button >= 0 && button < 9) { + yield new Info.HotbarSwap(button, slot); + } else if (button == 40) { + yield new Info.OffhandSwap(slot); + } else { + yield null; + } + } + case CLONE -> valid ? new Info.Middle(slot) : null; + case THROW -> valid ? new Info.DropSlot(slot, button == 1) : null; + case QUICK_CRAFT -> { + // Handle drag finishes + if (button == 2) { + var list = List.copyOf(leftDrag); + leftDrag.clear(); + yield new Info.LeftDrag(list); + } else if (button == 6) { + var list = List.copyOf(rightDrag); + rightDrag.clear(); + yield new Info.RightDrag(list); + } else if (button == 10) { + var list = List.copyOf(middleDrag); + middleDrag.clear(); + yield new Info.MiddleDrag(list); + } + + Consumer> tryAdd = list -> { + if (valid && !list.contains(slot)) { + list.add(slot); + } + }; + + switch (button) { + case 0 -> leftDrag.clear(); + case 4 -> rightDrag.clear(); + case 8 -> middleDrag.clear(); + + case 1 -> tryAdd.accept(leftDrag); + case 5 -> tryAdd.accept(rightDrag); + case 9 -> tryAdd.accept(middleDrag); + } + + yield null; + } + case PICKUP_ALL -> valid ? new Info.Double(slot) : null; + }; + } + + } + + public record Getter(@NotNull IntFunction main, @NotNull IntFunction player, + @NotNull ItemStack cursor, int mainSize) { + + public @NotNull ItemStack get(int slot) { + if (slot < mainSize()) { + return main.apply(slot); + } else { + return player.apply(PlayerInventoryUtils.protocolToMinestom(slot, mainSize())); + } + } + + public @NotNull Click.Setter setter() { + return new Setter(mainSize); + } + } + + public static class Setter { + + private final Map main = new HashMap<>(); + private final Map player = new HashMap<>(); + private @Nullable ItemStack cursor; + private @Nullable SideEffect sideEffect; + + private final int clickedSize; + + Setter(int clickedSize) { + this.clickedSize = clickedSize; + } + + public @NotNull Setter set(int slot, @NotNull ItemStack item) { + if (slot >= clickedSize) { + int converted = PlayerInventoryUtils.protocolToMinestom(slot, clickedSize); + return setPlayer(converted, item); + } else { + main.put(slot, item); + return this; + } + } + + public @NotNull Setter setPlayer(int slot, @NotNull ItemStack item) { + player.put(slot, item); + return this; + } + + public @NotNull Setter cursor(@Nullable ItemStack newCursorItem) { + this.cursor = newCursorItem; + return this; + } + + public @NotNull Setter sideEffects(@Nullable SideEffect sideEffect) { + this.sideEffect = sideEffect; + return this; + } + + public @NotNull Click.Result build() { + return new Result(main, player, cursor, sideEffect); + } + + } + + /** + * Stores changes that occurred or will occur as the result of a click. + * @param changes the map of changes that will occur to the inventory + * @param playerInventoryChanges the map of changes that will occur to the player inventory + * @param newCursorItem the player's cursor item after this click. Null indicates no change + * @param sideEffects the side effects of this click + */ + public record Result(@NotNull Map changes, @NotNull Map playerInventoryChanges, + @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { + + public static @NotNull Result nothing() { + return new Result(Map.of(), Map.of(), null, null); + } + + public Result { + changes = Map.copyOf(changes); + playerInventoryChanges = Map.copyOf(playerInventoryChanges); + } + + } + + /** + * Represents side effects that may occur as the result of an inventory click. + */ + public sealed interface SideEffect { + + record DropFromPlayer(@NotNull List items) implements SideEffect { + + public DropFromPlayer { + items = List.copyOf(items); + } + + public DropFromPlayer(@NotNull ItemStack @NotNull ... items) { + this(List.of(items)); + } + } + } +} diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java new file mode 100644 index 000000000..ec38f5b77 --- /dev/null +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -0,0 +1,219 @@ +package net.minestom.server.inventory.click; + +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minestom.server.inventory.TransactionOperator; +import net.minestom.server.inventory.TransactionType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.StackingRule; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.stream.IntStream; + +/** + * Provides standard implementations of most click functions. + */ +public final class ClickProcessors { + + private static final @NotNull StackingRule RULE = StackingRule.get(); + + public static @NotNull Click.Result leftClick(int slot, @NotNull Click.Getter getter) { + ItemStack cursor = getter.cursor(); + ItemStack clickedItem = getter.get(slot); + + Pair pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); + if (pair != null) { // Stackable items, combine their counts + return getter.setter().set(slot, pair.left()).cursor(pair.right()).build(); + } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them + return getter.setter().set(slot, cursor).cursor(clickedItem).build(); + } else { + return Click.Result.nothing(); + } + } + + public static @NotNull Click.Result rightClick(int slot, @NotNull Click.Getter getter) { + ItemStack cursor = getter.cursor(); + ItemStack clickedItem = getter.get(slot); + + if (cursor.isAir() && clickedItem.isAir()) return Click.Result.nothing(); // Both are air, no changes + + if (cursor.isAir()) { // Take half (rounded up) of the clicked item + int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); + Pair cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); + return cursorSlot == null ? Click.Result.nothing() : + getter.setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build(); + } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over + Pair slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); + return slotCursor == null ? Click.Result.nothing() : + getter.setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build(); + } else { // Two existing of items of different types, so switch + return getter.setter().cursor(clickedItem).set(slot, cursor).build(); + } + } + + public static @NotNull Click.Result middleClick(int slot, @NotNull Click.Getter getter) { + var item = getter.get(slot); + if (getter.cursor().isAir() && !item.isAir()) { + return getter.setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); + } else { + return Click.Result.nothing(); + } + } + + public static @NotNull Click.Result shiftClick(int slot, @NotNull List slots, @NotNull Click.Getter getter) { + ItemStack clicked = getter.get(slot); + + slots = new ArrayList<>(slots); + slots.removeIf(i -> i == slot); + + Pair> result = TransactionType.add(slots, slots).process(clicked, getter::get); + Click.Setter setter = getter.setter(); + result.right().forEach(setter::set); + + return !result.left().equals(clicked) ? + setter.set(slot, result.left()).build() : + setter.build(); + } + + public static @NotNull Click.Result doubleClick(@NotNull List slots, @NotNull Click.Getter getter) { + var cursor = getter.cursor(); + if (cursor.isAir()) Click.Result.nothing(); + + var unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); + var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); + + Pair> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); + Click.Setter setter = getter.setter(); + result.right().forEach(setter::set); + + return !result.left().equals(cursor) ? + setter.cursor(result.left()).build() : + setter.build(); + } + + public static @NotNull Click.Result dragClick(int countPerSlot, @NotNull List slots, @NotNull Click.Getter getter) { + var cursor = getter.cursor(); + if (cursor.isAir()) return Click.Result.nothing(); + + Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); + Click.Setter setter = getter.setter(); + result.right().forEach(setter::set); + + return !result.left().equals(cursor) ? + setter.cursor(result.left()).build() : + setter.build(); + } + + public static @NotNull Click.Result middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { + var cursor = getter.cursor(); + + Click.Setter setter = getter.setter(); + + for (int slot : slots) { + if (getter.get(slot).isAir()) { + setter.set(slot, cursor); + } + } + + return setter.build(); + } + + public static @NotNull Click.Result dropFromCursor(int amount, @NotNull Click.Getter getter) { + var cursor = getter.cursor(); + if (cursor.isAir()) Click.Result.nothing(); // Do nothing + + var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); + if (pair == null) return Click.Result.nothing(); + + return getter.setter().cursor(pair.right()) + .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) + .build(); + } + + public static @NotNull Click.Result dropFromSlot(int slot, int amount, @NotNull Click.Getter getter) { + var item = getter.get(slot); + if (item.isAir()) return Click.Result.nothing(); // Do nothing + + var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); + if (pair == null) return Click.Result.nothing(); + + return getter.setter().set(slot, pair.right()) + .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) + .build(); + } + + /** + * Handles clicks, given a shift click provider and a double click provider.
+ * When shift clicks or double clicks need to be handled, the slots provided from the relevant handler will be + * checked in their given order.
+ * For example, double clicking will collect items of the same type as the cursor; the slots provided by the double + * click slot provider will be checked sequentially and used if they have the same type as + * @param shiftClickSlots the shift click slot supplier + * @param doubleClickSlots the double click slot supplier + */ + public static @NotNull BiFunction standard(@NotNull SlotSuggestor shiftClickSlots, @NotNull SlotSuggestor doubleClickSlots) { + return (info, getter) -> switch (info) { + case Click.Info.Left(int slot) -> leftClick(slot, getter); + case Click.Info.Right(int slot) -> rightClick(slot, getter); + case Click.Info.Middle(int slot) -> middleClick(slot, getter); + case Click.Info.LeftShift(int slot) -> shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter); + case Click.Info.RightShift(int slot) -> shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter); + case Click.Info.Double(int slot) -> doubleClick(doubleClickSlots.getList(getter, getter.get(slot), slot), getter); + case Click.Info.LeftDrag(List slots) -> { + int cursorAmount = RULE.getAmount(getter.cursor()); + int amount = (int) Math.floor(cursorAmount / (double) slots.size()); + yield dragClick(amount, slots, getter); + } + case Click.Info.RightDrag(List slots) -> dragClick(1, slots, getter); + case Click.Info.MiddleDrag(List slots) -> middleDragClick(slots, getter); + case Click.Info.DropSlot(int slot, boolean all) -> dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter); + case Click.Info.LeftDropCursor() -> dropFromCursor(getter.cursor().amount(), getter); + case Click.Info.RightDropCursor() -> dropFromCursor(1, getter); + case Click.Info.MiddleDropCursor() -> Click.Result.nothing(); + case Click.Info.HotbarSwap(int hotbarSlot, int clickedSlot) -> { + var hotbarItem = getter.player().apply(hotbarSlot); + var selectedItem = getter.get(clickedSlot); + if (hotbarItem.equals(selectedItem)) yield Click.Result.nothing(); + + yield getter.setter().setPlayer(hotbarSlot, selectedItem).set(clickedSlot, hotbarItem).build(); + } + case Click.Info.OffhandSwap(int slot) -> { + var offhandItem = getter.player().apply(PlayerInventoryUtils.OFF_HAND_SLOT); + var selectedItem = getter.get(slot); + if (offhandItem.equals(selectedItem)) yield Click.Result.nothing(); + + yield getter.setter().setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).set(slot, offhandItem).build(); + } + case Click.Info.CreativeSetItem(int slot, ItemStack item) -> getter.setter().set(slot, item).build(); + case Click.Info.CreativeDropItem(ItemStack item) -> getter.setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); + }; + } + + /** + * A generic interface for providing options for clicks like shift clicks and double clicks.
+ * This addresses the issue of certain click operations only being able to interact with certain slots: for example, + * shift clicking an item out of an inventory can only put it in the player's inner inventory slots, and will never + * put the item anywhere else in the inventory or the player's inventory.
+ */ + @FunctionalInterface + public interface SlotSuggestor { + + /** + * Suggests slots to be used for this operation. + * @param builder the result builder + * @param item the item clicked + * @param slot the slot of the clicked item + * @return the list of slots, in order of priority, to be used for this operation + */ + @NotNull IntStream get(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot); + + default @NotNull List getList(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot) { + return get(builder, item, slot).boxed().toList(); + } + } + +} diff --git a/src/main/java/net/minestom/server/inventory/click/ClickType.java b/src/main/java/net/minestom/server/inventory/click/ClickType.java deleted file mode 100644 index c6fe8eda8..000000000 --- a/src/main/java/net/minestom/server/inventory/click/ClickType.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.minestom.server.inventory.click; - -public enum ClickType { - - LEFT_CLICK, - RIGHT_CLICK, - CHANGE_HELD, - - START_SHIFT_CLICK, - SHIFT_CLICK, - - START_LEFT_DRAGGING, - START_RIGHT_DRAGGING, - - LEFT_DRAGGING, - RIGHT_DRAGGING, - - END_LEFT_DRAGGING, - END_RIGHT_DRAGGING, - - START_DOUBLE_CLICK, - DOUBLE_CLICK, - - DROP - -} diff --git a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java deleted file mode 100644 index 634a9ef2a..000000000 --- a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java +++ /dev/null @@ -1,476 +0,0 @@ -package net.minestom.server.inventory.click; - -import net.minestom.server.entity.EquipmentSlot; -import net.minestom.server.entity.Player; -import net.minestom.server.event.EventDispatcher; -import net.minestom.server.event.inventory.InventoryClickEvent; -import net.minestom.server.event.inventory.InventoryPreClickEvent; -import net.minestom.server.inventory.AbstractInventory; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.PlayerInventory; -import net.minestom.server.inventory.TransactionType; -import net.minestom.server.inventory.condition.InventoryCondition; -import net.minestom.server.inventory.condition.InventoryConditionResult; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.item.StackingRule; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; - -@ApiStatus.Internal -public final class InventoryClickProcessor { - // Dragging maps - private final Map> leftDraggingMap = new ConcurrentHashMap<>(); - private final Map> rightDraggingMap = new ConcurrentHashMap<>(); - - public @NotNull InventoryClickResult leftClick(@NotNull Player player, @NotNull AbstractInventory inventory, - int slot, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - final var result = startCondition(player, inventory, slot, ClickType.LEFT_CLICK, clicked, cursor); - if (result.isCancel()) return result; - clicked = result.getClicked(); - cursor = result.getCursor(); - final StackingRule rule = StackingRule.get(); - if (rule.canBeStacked(cursor, clicked)) { - // Try to stack items - final int totalAmount = rule.getAmount(cursor) + rule.getAmount(clicked); - final int maxSize = rule.getMaxSize(cursor); - if (!rule.canApply(clicked, totalAmount)) { - // Size is too big, stack as much as possible into clicked - result.setCursor(rule.apply(cursor, totalAmount - maxSize)); - result.setClicked(rule.apply(clicked, maxSize)); - } else { - // Merge cursor item clicked - result.setCursor(rule.apply(cursor, 0)); - result.setClicked(rule.apply(clicked, totalAmount)); - } - } else { - // Items are not compatible, swap them - result.setCursor(clicked); - result.setClicked(cursor); - } - return result; - } - - public @NotNull InventoryClickResult rightClick(@NotNull Player player, @NotNull AbstractInventory inventory, - int slot, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - final var result = startCondition(player, inventory, slot, ClickType.RIGHT_CLICK, clicked, cursor); - if (result.isCancel()) return result; - clicked = result.getClicked(); - cursor = result.getCursor(); - final StackingRule rule = StackingRule.get(); - if (rule.canBeStacked(clicked, cursor)) { - // Items can be stacked - final int amount = rule.getAmount(clicked) + 1; - if (!rule.canApply(clicked, amount)) { - // Size too large, stop here - return result; - } else { - // Add 1 to clicked - result.setCursor(rule.apply(cursor, operand -> operand - 1)); - result.setClicked(rule.apply(clicked, amount)); - } - } else { - // Items cannot be stacked - if (cursor.isAir()) { - // Take half of clicked - final int amount = (int) Math.ceil((double) rule.getAmount(clicked) / 2d); - result.setCursor(rule.apply(clicked, amount)); - result.setClicked(rule.apply(clicked, operand -> operand / 2)); - } else { - if (clicked.isAir()) { - // Put 1 to clicked - result.setCursor(rule.apply(cursor, operand -> operand - 1)); - result.setClicked(rule.apply(cursor, 1)); - } else { - // Swap items - result.setCursor(clicked); - result.setClicked(cursor); - } - } - } - return result; - } - - public @NotNull InventoryClickResult changeHeld(@NotNull Player player, @NotNull AbstractInventory inventory, - int slot, int key, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - // Verify the clicked item - InventoryClickResult clickResult = startCondition(player, inventory, slot, ClickType.CHANGE_HELD, clicked, cursor); - if (clickResult.isCancel()) return clickResult; - // Verify the destination (held bar) - clickResult = startCondition(player, player.getInventory(), key, ClickType.CHANGE_HELD, clicked, cursor); - if (clickResult.isCancel()) return clickResult; - // Swap items - clickResult.setClicked(cursor); - clickResult.setCursor(clicked); - return clickResult; - } - - public @NotNull InventoryClickResult shiftClick(@NotNull AbstractInventory inventory, @NotNull AbstractInventory targetInventory, - int start, int end, int step, - @NotNull Player player, int slot, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - InventoryClickResult clickResult = startCondition(player, inventory, slot, ClickType.START_SHIFT_CLICK, clicked, cursor); - if (clickResult.isCancel()) return clickResult; - if (clicked.isAir()) return clickResult.cancelled(); - - // Handle armor equip - if (inventory instanceof PlayerInventory && targetInventory instanceof PlayerInventory) { - final Material material = clicked.material(); - final EquipmentSlot equipmentSlot = material.registry().equipmentSlot(); - if (equipmentSlot != null) { - // Shift-click equip - final ItemStack currentArmor = player.getEquipment(equipmentSlot); - if (currentArmor.isAir()) { - final int armorSlot = equipmentSlot.armorSlot(); - InventoryClickResult result = startCondition(player, targetInventory, armorSlot, ClickType.SHIFT_CLICK, clicked, cursor); - if (result.isCancel()) return clickResult; - result.setClicked(ItemStack.AIR); - result.setCursor(cursor); - player.setEquipment(equipmentSlot, clicked); - return result; - } - } - } - - clickResult.setCancel(true); - final var pair = TransactionType.ADD.process(targetInventory, clicked, (index, itemStack) -> { - if (inventory == targetInventory && index == slot) - return false; // Prevent item lose/duplication - InventoryClickResult result = startCondition(player, targetInventory, index, ClickType.SHIFT_CLICK, itemStack, cursor); - if (result.isCancel()) { - return false; - } - clickResult.setCancel(false); - return true; - }, start, end, step); - - final ItemStack itemResult = pair.left(); - final Map itemChangesMap = pair.right(); - itemChangesMap.forEach((Integer s, ItemStack itemStack) -> { - targetInventory.setItemStack(s, itemStack); - callClickEvent(player, targetInventory, s, ClickType.SHIFT_CLICK, itemStack, cursor); - }); - clickResult.setClicked(itemResult); - return clickResult; - } - - public @Nullable InventoryClickResult dragging(@NotNull Player player, @Nullable AbstractInventory inventory, - int slot, int button, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - InventoryClickResult clickResult = null; - final StackingRule stackingRule = StackingRule.get(); - if (slot != -999) { - // Add slot - if (button == 1) { - // Add left - List left = leftDraggingMap.get(player); - if (left == null) return null; - left.add(new DragData(slot, inventory)); - } else if (button == 5) { - // Add right - List right = rightDraggingMap.get(player); - if (right == null) return null; - right.add(new DragData(slot, inventory)); - } else if (button == 9) { - // Add middle - // TODO - } - } else { - // Drag instruction - if (button == 0) { - // Start left - clickResult = startCondition(player, inventory, slot, ClickType.START_LEFT_DRAGGING, clicked, cursor); - if (!clickResult.isCancel()) this.leftDraggingMap.put(player, new ArrayList<>()); - } else if (button == 2) { - // End left - final List slots = leftDraggingMap.remove(player); - if (slots == null) return null; - final int slotCount = slots.size(); - final int cursorAmount = stackingRule.getAmount(cursor); - if (slotCount > cursorAmount) return null; - for (DragData s : slots) { - // Apply each drag element - final ItemStack slotItem = s.inventory.getItemStack(s.slot); - clickResult = startCondition(player, s.inventory, s.slot, ClickType.LEFT_DRAGGING, slotItem, cursor); - if (clickResult.isCancel()) { - return clickResult; - } - } - clickResult = startCondition(player, inventory, slot, ClickType.END_LEFT_DRAGGING, clicked, cursor); - if (clickResult.isCancel()) return clickResult; - // Should be size of each defined slot (if not full) - final int slotSize = (int) ((float) cursorAmount / (float) slotCount); - // Place all waiting drag action - int finalCursorAmount = cursorAmount; - for (DragData dragData : slots) { - final var inv = dragData.inventory; - final int s = dragData.slot; - ItemStack slotItem = inv.getItemStack(s); - final int amount = stackingRule.getAmount(slotItem); - if (stackingRule.canBeStacked(cursor, slotItem)) { - if (stackingRule.canApply(slotItem, amount + slotSize)) { - // Append divided amount to slot - slotItem = stackingRule.apply(slotItem, a -> a + slotSize); - finalCursorAmount -= slotSize; - } else { - // Amount too big, fill as much as possible - final int maxSize = stackingRule.getMaxSize(cursor); - final int removedAmount = maxSize - amount; - slotItem = stackingRule.apply(slotItem, maxSize); - finalCursorAmount -= removedAmount; - } - } else if (slotItem.isAir()) { - // Slot is empty, add divided amount - slotItem = stackingRule.apply(cursor, slotSize); - finalCursorAmount -= slotSize; - } - inv.setItemStack(s, slotItem); - callClickEvent(player, inv, s, ClickType.LEFT_DRAGGING, slotItem, cursor); - } - // Update the cursor - clickResult.setCursor(stackingRule.apply(cursor, finalCursorAmount)); - } else if (button == 4) { - // Start right - clickResult = startCondition(player, inventory, slot, ClickType.START_RIGHT_DRAGGING, clicked, cursor); - if (!clickResult.isCancel()) this.rightDraggingMap.put(player, new ArrayList<>()); - } else if (button == 6) { - // End right - final List slots = rightDraggingMap.remove(player); - if (slots == null) return null; - final int size = slots.size(); - int cursorAmount = stackingRule.getAmount(cursor); - if (size > cursorAmount) return null; - // Verify if each slot can be modified (or cancel the whole drag) - for (DragData s : slots) { - final ItemStack slotItem = s.inventory.getItemStack(s.slot); - clickResult = startCondition(player, s.inventory, s.slot, ClickType.RIGHT_DRAGGING, slotItem, cursor); - if (clickResult.isCancel()) { - return clickResult; - } - } - clickResult = startCondition(player, inventory, slot, ClickType.END_RIGHT_DRAGGING, clicked, cursor); - if (clickResult.isCancel()) return clickResult; - // Place all waiting drag action - int finalCursorAmount = cursorAmount; - for (DragData dragData : slots) { - final var inv = dragData.inventory; - final int s = dragData.slot; - ItemStack slotItem = inv.getItemStack(s); - if (stackingRule.canBeStacked(cursor, slotItem)) { - // Compatible item in the slot, increment by 1 - final int amount = stackingRule.getAmount(slotItem) + 1; - if (stackingRule.canApply(slotItem, amount)) { - slotItem = stackingRule.apply(slotItem, amount); - finalCursorAmount -= 1; - } - } else if (slotItem.isAir()) { - // No item at the slot, place one - slotItem = stackingRule.apply(cursor, 1); - finalCursorAmount -= 1; - } - inv.setItemStack(s, slotItem); - callClickEvent(player, inv, s, ClickType.RIGHT_DRAGGING, slotItem, cursor); - } - // Update the cursor - clickResult.setCursor(stackingRule.apply(cursor, finalCursorAmount)); - } - } - return clickResult; - } - - public @NotNull InventoryClickResult doubleClick(@NotNull AbstractInventory clickedInventory, @NotNull AbstractInventory inventory, @NotNull Player player, int slot, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - InventoryClickResult clickResult = startCondition(player, clickedInventory, slot, ClickType.START_DOUBLE_CLICK, clicked, cursor); - if (clickResult.isCancel()) return clickResult; - if (cursor.isAir()) return clickResult.cancelled(); - - final StackingRule rule = StackingRule.get(); - final int amount = rule.getAmount(cursor); - final int maxSize = rule.getMaxSize(cursor); - final int remainingAmount = maxSize - amount; - if (remainingAmount == 0) { - // Item is already full - return clickResult; - } - final BiFunction func = (inv, rest) -> { - var pair = TransactionType.TAKE.process(inv, rest, (index, itemStack) -> { - if (index == slot) // Prevent item lose/duplication - return false; - final InventoryClickResult result = startCondition(player, inv, index, ClickType.DOUBLE_CLICK, itemStack, cursor); - return !result.isCancel(); - }); - final ItemStack itemResult = pair.left(); - var itemChangesMap = pair.right(); - itemChangesMap.forEach((Integer s, ItemStack itemStack) -> { - inv.setItemStack(s, itemStack); - callClickEvent(player, inv, s, ClickType.DOUBLE_CLICK, itemStack, cursor); - }); - return itemResult; - }; - - ItemStack remain = rule.apply(cursor, remainingAmount); - final var playerInventory = player.getInventory(); - // Retrieve remain - if (Objects.equals(clickedInventory, inventory)) { - // Clicked inside inventory - remain = func.apply(inventory, remain); - if (!remain.isAir()) { - remain = func.apply(playerInventory, remain); - } - } else if (clickedInventory == playerInventory) { - // Clicked inside player inventory, but with another inventory open - remain = func.apply(playerInventory, remain); - if (!remain.isAir()) { - remain = func.apply(inventory, remain); - } - } else { - // Clicked inside player inventory - remain = func.apply(playerInventory, remain); - } - - // Update cursor based on the remaining - if (remain.isAir()) { - // Item has been filled - clickResult.setCursor(rule.apply(cursor, maxSize)); - } else { - final int tookAmount = remainingAmount - rule.getAmount(remain); - clickResult.setCursor(rule.apply(cursor, amount + tookAmount)); - } - return clickResult; - } - - public @NotNull InventoryClickResult drop(@NotNull Player player, @NotNull AbstractInventory inventory, - boolean all, int slot, int button, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - final InventoryClickResult clickResult = startCondition(player, inventory, slot, ClickType.DROP, clicked, cursor); - if (clickResult.isCancel()) return clickResult; - - final StackingRule rule = StackingRule.get(); - - ItemStack resultClicked = clicked; - ItemStack resultCursor = cursor; - - if (slot == -999) { - // Click outside - if (button == 0) { - // Left (drop all) - final int amount = rule.getAmount(resultCursor); - final ItemStack dropItem = rule.apply(resultCursor, amount); - final boolean dropResult = player.dropItem(dropItem); - clickResult.setCancel(!dropResult); - if (dropResult) { - resultCursor = rule.apply(resultCursor, 0); - } - } else if (button == 1) { - // Right (drop 1) - final ItemStack dropItem = rule.apply(resultCursor, 1); - final boolean dropResult = player.dropItem(dropItem); - clickResult.setCancel(!dropResult); - if (dropResult) { - final int amount = rule.getAmount(resultCursor); - final int newAmount = amount - 1; - resultCursor = rule.apply(resultCursor, newAmount); - } - } - - } else if (!all) { - if (button == 0) { - // Drop key Q (drop 1) - final ItemStack dropItem = rule.apply(resultClicked, 1); - final boolean dropResult = player.dropItem(dropItem); - clickResult.setCancel(!dropResult); - if (dropResult) { - final int amount = rule.getAmount(resultClicked); - final int newAmount = amount - 1; - resultClicked = rule.apply(resultClicked, newAmount); - } - } else if (button == 1) { - // Ctrl + Drop key Q (drop all) - final int amount = rule.getAmount(resultClicked); - final ItemStack dropItem = rule.apply(resultClicked, amount); - final boolean dropResult = player.dropItem(dropItem); - clickResult.setCancel(!dropResult); - if (dropResult) { - resultClicked = rule.apply(resultClicked, 0); - } - } - } - - clickResult.setClicked(resultClicked); - clickResult.setCursor(resultCursor); - - return clickResult; - } - - private @NotNull InventoryClickResult startCondition(@NotNull Player player, - @Nullable AbstractInventory inventory, - int slot, @NotNull ClickType clickType, - @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - final InventoryClickResult clickResult = new InventoryClickResult(clicked, cursor); - final Inventory eventInventory = inventory instanceof Inventory ? (Inventory) inventory : null; - - // Reset the didCloseInventory field - // Wait for inventory conditions + events to possibly close the inventory - player.UNSAFE_changeDidCloseInventory(false); - // InventoryPreClickEvent - { - InventoryPreClickEvent inventoryPreClickEvent = new InventoryPreClickEvent(eventInventory, player, slot, clickType, - clickResult.getClicked(), clickResult.getCursor()); - EventDispatcher.call(inventoryPreClickEvent); - clickResult.setCursor(inventoryPreClickEvent.getCursorItem()); - clickResult.setClicked(inventoryPreClickEvent.getClickedItem()); - if (inventoryPreClickEvent.isCancelled()) { - clickResult.setCancel(true); - } - } - // Inventory conditions - { - if (inventory != null) { - final List inventoryConditions = inventory.getInventoryConditions(); - if (!inventoryConditions.isEmpty()) { - for (InventoryCondition inventoryCondition : inventoryConditions) { - var result = new InventoryConditionResult(clickResult.getClicked(), clickResult.getCursor()); - inventoryCondition.accept(player, slot, clickType, result); - - clickResult.setCursor(result.getCursorItem()); - clickResult.setClicked(result.getClickedItem()); - if (result.isCancel()) { - clickResult.setCancel(true); - } - } - // Cancel the click if the inventory has been closed by Player#closeInventory within an inventory listener - if (player.didCloseInventory()) { - clickResult.setCancel(true); - player.UNSAFE_changeDidCloseInventory(false); - } - } - } - } - return clickResult; - } - - private void callClickEvent(@NotNull Player player, @Nullable AbstractInventory inventory, int slot, - @NotNull ClickType clickType, @NotNull ItemStack clicked, @NotNull ItemStack cursor) { - final Inventory eventInventory = inventory instanceof Inventory ? (Inventory) inventory : null; - EventDispatcher.call(new InventoryClickEvent(eventInventory, player, slot, clickType, clicked, cursor)); - } - - public void clearCache(@NotNull Player player) { - this.leftDraggingMap.remove(player); - this.rightDraggingMap.remove(player); - } - - private record DragData(int slot, AbstractInventory inventory) { - } -} diff --git a/src/main/java/net/minestom/server/inventory/click/InventoryClickResult.java b/src/main/java/net/minestom/server/inventory/click/InventoryClickResult.java deleted file mode 100644 index 5a5384565..000000000 --- a/src/main/java/net/minestom/server/inventory/click/InventoryClickResult.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.minestom.server.inventory.click; - -import net.minestom.server.item.ItemStack; - -public final class InventoryClickResult { - private ItemStack clicked; - private ItemStack cursor; - private boolean cancel; - - public InventoryClickResult(ItemStack clicked, ItemStack cursor) { - this.clicked = clicked; - this.cursor = cursor; - } - - public ItemStack getClicked() { - return clicked; - } - - void setClicked(ItemStack clicked) { - this.clicked = clicked; - } - - public ItemStack getCursor() { - return cursor; - } - - void setCursor(ItemStack cursor) { - this.cursor = cursor; - } - - public boolean isCancel() { - return cancel; - } - - void setCancel(boolean cancel) { - this.cancel = cancel; - } - - InventoryClickResult cancelled() { - setCancel(true); - return this; - } -} diff --git a/src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java b/src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java deleted file mode 100644 index 121adade1..000000000 --- a/src/main/java/net/minestom/server/inventory/condition/InventoryCondition.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.minestom.server.inventory.condition; - -import net.minestom.server.entity.Player; -import net.minestom.server.inventory.AbstractInventory; -import net.minestom.server.inventory.click.ClickType; - -/** - * 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. - */ -@FunctionalInterface -public interface InventoryCondition { - - /** - * Called when a {@link Player} clicks in the inventory where this {@link InventoryCondition} has been added to. - * - * @param player the player who clicked in the inventory - * @param slot the slot clicked, can be -999 if the click is out of the inventory - * @param clickType the click type - * @param inventoryConditionResult the result of this callback - */ - void accept(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult); -} diff --git a/src/main/java/net/minestom/server/inventory/condition/InventoryConditionResult.java b/src/main/java/net/minestom/server/inventory/condition/InventoryConditionResult.java deleted file mode 100644 index 2399c12d8..000000000 --- a/src/main/java/net/minestom/server/inventory/condition/InventoryConditionResult.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.minestom.server.inventory.condition; - -import net.minestom.server.item.ItemStack; - -/** - * Used by {@link InventoryCondition} to step in inventory click processing. - */ -public class InventoryConditionResult { - - private ItemStack clickedItem, cursorItem; - private boolean cancel; - - public InventoryConditionResult(ItemStack clickedItem, ItemStack cursorItem) { - this.clickedItem = clickedItem; - this.cursorItem = cursorItem; - } - - public ItemStack getClickedItem() { - return clickedItem; - } - - public void setClickedItem(ItemStack clickedItem) { - this.clickedItem = clickedItem; - } - - public ItemStack getCursorItem() { - return cursorItem; - } - - public void setCursorItem(ItemStack cursorItem) { - this.cursorItem = cursorItem; - } - - public boolean isCancel() { - return cancel; - } - - public void setCancel(boolean cancel) { - this.cancel = cancel; - } -} diff --git a/src/main/java/net/minestom/server/inventory/type/AnvilInventory.java b/src/main/java/net/minestom/server/inventory/type/AnvilInventory.java index 8a7c9f2c1..0f5c419dc 100644 --- a/src/main/java/net/minestom/server/inventory/type/AnvilInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/AnvilInventory.java @@ -1,12 +1,12 @@ package net.minestom.server.inventory.type; import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryProperty; import net.minestom.server.inventory.InventoryType; import org.jetbrains.annotations.NotNull; -public class AnvilInventory extends Inventory { +public class AnvilInventory extends ContainerInventory { private short repairCost; diff --git a/src/main/java/net/minestom/server/inventory/type/BeaconInventory.java b/src/main/java/net/minestom/server/inventory/type/BeaconInventory.java index 30551dae6..0915d1841 100644 --- a/src/main/java/net/minestom/server/inventory/type/BeaconInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/BeaconInventory.java @@ -1,13 +1,13 @@ package net.minestom.server.inventory.type; import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryProperty; import net.minestom.server.inventory.InventoryType; import net.minestom.server.potion.PotionEffect; import org.jetbrains.annotations.NotNull; -public class BeaconInventory extends Inventory { +public class BeaconInventory extends ContainerInventory { private short powerLevel; private PotionEffect firstPotionEffect; diff --git a/src/main/java/net/minestom/server/inventory/type/BrewingStandInventory.java b/src/main/java/net/minestom/server/inventory/type/BrewingStandInventory.java index ed0bc7d78..7dc1e68bd 100644 --- a/src/main/java/net/minestom/server/inventory/type/BrewingStandInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/BrewingStandInventory.java @@ -1,12 +1,12 @@ package net.minestom.server.inventory.type; import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryProperty; import net.minestom.server.inventory.InventoryType; import org.jetbrains.annotations.NotNull; -public class BrewingStandInventory extends Inventory { +public class BrewingStandInventory extends ContainerInventory { private short brewTime; private short fuelTime; diff --git a/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java b/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java index bd855c32a..3d0ca7473 100644 --- a/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java @@ -1,13 +1,13 @@ package net.minestom.server.inventory.type; import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryProperty; import net.minestom.server.inventory.InventoryType; import net.minestom.server.item.Enchantment; import org.jetbrains.annotations.NotNull; -public class EnchantmentTableInventory extends Inventory { +public class EnchantmentTableInventory extends ContainerInventory { private final short[] levelRequirements = new short[EnchantmentSlot.values().length]; private short seed; diff --git a/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java b/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java index 9e357da99..92fc59a03 100644 --- a/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java @@ -1,12 +1,18 @@ package net.minestom.server.inventory.type; import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.Inventory; +import net.minestom.server.entity.Player; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryProperty; import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.click.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class FurnaceInventory extends Inventory { +import java.util.stream.IntStream; + +public class FurnaceInventory extends ContainerInventory { private short remainingFuelTick; private short maximumFuelBurnTime; @@ -21,6 +27,32 @@ public class FurnaceInventory extends Inventory { super(InventoryType.FURNACE, title); } + /** + * Client prediction appears to disallow shift clicking into furnace inventories.
+ * Instead: + * - Shift clicks in the inventory go to the player inventory like normal + * - Shift clicks in the hotbar go to the storage + * - Shift clicks in the storage go to the hotbar + */ + @Override + public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) { + var processor = ClickProcessors.standard( + (builder, item, slot) -> { + if (slot < getSize()) { + return PlayerInventory.getInnerShiftClickSlots(getSize()); + } else if (slot < getSize() + 27) { + return IntStream.range(27, 36).map(i -> i + getSize()); + } else { + return IntStream.range(0, 27).map(i -> i + getSize()); + } + }, + (builder, item, slot) -> IntStream.concat( + IntStream.range(0, getSize()), + PlayerInventory.getInnerDoubleClickSlots(getSize()) + )); + return ContainerInventory.handleClick(this, player, info, processor); + } + /** * Represents the amount of tick until the fire icon come empty. * diff --git a/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java b/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java index 048fa5813..36637d5ea 100644 --- a/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java @@ -2,7 +2,7 @@ package net.minestom.server.inventory.type; import net.kyori.adventure.text.Component; import net.minestom.server.entity.Player; -import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryType; import net.minestom.server.network.packet.server.CachedPacket; import net.minestom.server.network.packet.server.play.TradeListPacket; @@ -12,7 +12,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class VillagerInventory extends Inventory { +public class VillagerInventory extends ContainerInventory { private final CachedPacket tradeCache = new CachedPacket(this::createTradePacket); private final List trades = new ArrayList<>(); private int villagerLevel; @@ -82,14 +82,12 @@ public class VillagerInventory extends Inventory { public void update() { super.update(); this.tradeCache.invalidate(); - sendPacketToViewers(tradeCache); } @Override - public boolean addViewer(@NotNull Player player) { - final boolean result = super.addViewer(player); - if (result) player.sendPacket(tradeCache); - return result; + public void update(@NotNull Player player) { + super.update(player); + player.sendPacket(tradeCache); } private TradeListPacket createTradePacket() { diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 4a3ca866b..7d0beeb82 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -5,6 +5,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import net.minestom.server.adventure.MinestomAdventure; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagReadable; @@ -20,7 +21,7 @@ import java.util.function.UnaryOperator; /** * Represents an immutable item to be placed inside {@link net.minestom.server.inventory.PlayerInventory}, - * {@link net.minestom.server.inventory.Inventory} or even on the ground {@link net.minestom.server.entity.ItemEntity}. + * {@link ContainerInventory} or even on the ground {@link net.minestom.server.entity.ItemEntity}. *

* An item stack cannot be null, {@link ItemStack#AIR} should be used instead. */ diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 46980b6ac..9da15ee9a 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -170,7 +170,7 @@ public class BlockPlacementListener { if (playerBlockPlaceEvent.doesConsumeBlock()) { // Consume the block in the player's hand final ItemStack newUsedItem = usedItem.consume(1); - playerInventory.setItemInHand(hand, newUsedItem); + player.setItemInHand(hand, newUsedItem); } else { // Prevent invisible item on client playerInventory.update(); diff --git a/src/main/java/net/minestom/server/listener/BookListener.java b/src/main/java/net/minestom/server/listener/BookListener.java index 936a39448..1630a1391 100644 --- a/src/main/java/net/minestom/server/listener/BookListener.java +++ b/src/main/java/net/minestom/server/listener/BookListener.java @@ -5,12 +5,14 @@ import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.book.EditBookEvent; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.play.ClientEditBookPacket; -import net.minestom.server.utils.inventory.PlayerInventoryUtils; public class BookListener { public static void listener(ClientEditBookPacket packet, Player player) { - int slot = PlayerInventoryUtils.convertClientInventorySlot(packet.slot()); + int slot = packet.slot(); + if (slot < 0 || slot > 8) return; + + // Do not need to convert slot as hotbar slots correspond to Minestom inventory slots ItemStack itemStack = player.getInventory().getItemStack(slot); EventDispatcher.call(new EditBookEvent(player, itemStack, packet.pages(), packet.title())); } diff --git a/src/main/java/net/minestom/server/listener/CreativeInventoryActionListener.java b/src/main/java/net/minestom/server/listener/CreativeInventoryActionListener.java index 1554fd5bf..87fd8092d 100644 --- a/src/main/java/net/minestom/server/listener/CreativeInventoryActionListener.java +++ b/src/main/java/net/minestom/server/listener/CreativeInventoryActionListener.java @@ -1,35 +1,24 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; -import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.click.Click; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.play.ClientCreativeInventoryActionPacket; import net.minestom.server.utils.inventory.PlayerInventoryUtils; -import java.util.Objects; - public final class CreativeInventoryActionListener { public static void listener(ClientCreativeInventoryActionPacket packet, Player player) { if (!player.isCreative()) return; - short slot = packet.slot(); - final ItemStack item = packet.item(); - if (slot == -1) { - // Drop item - player.dropItem(item); - return; + + ItemStack item = packet.item(); + + if (packet.slot() == -1) { // -1 here indicates a drop + player.getInventory().handleClick(player, new Click.Info.CreativeDropItem(item)); } - // Bounds check - // 0 is crafting result inventory slot, ignore attempts to place into it - if (slot < 1 || slot > PlayerInventoryUtils.OFFHAND_SLOT) { - return; - } - // Set item - slot = (short) PlayerInventoryUtils.convertPlayerInventorySlot(slot, PlayerInventoryUtils.OFFSET); - PlayerInventory inventory = player.getInventory(); - if (Objects.equals(inventory.getItemStack(slot), item)) { - // Item is already present, ignore - return; - } - inventory.setItemStack(slot, item); + + int slot = PlayerInventoryUtils.protocolToMinestom(packet.slot()); + if (slot == -1) return; // -1 after conversion indicates an invalid slot + + player.getInventory().handleClick(player, new Click.Info.CreativeSetItem(slot, item)); } } diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index 9c3beec2c..231fb8139 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -10,13 +10,11 @@ import net.minestom.server.event.item.ItemUpdateStateEvent; import net.minestom.server.event.player.PlayerCancelDiggingEvent; import net.minestom.server.event.player.PlayerFinishDiggingEvent; import net.minestom.server.event.player.PlayerStartDiggingEvent; -import net.minestom.server.event.player.PlayerSwapItemEvent; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockFace; -import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.click.Click; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.StackingRule; import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket; import net.minestom.server.network.packet.server.play.AcknowledgeBlockChangePacket; import net.minestom.server.network.packet.server.play.BlockEntityDataPacket; @@ -43,13 +41,13 @@ public final class PlayerDiggingListener { if (!instance.isChunkLoaded(blockPosition)) return; diggingResult = finishDigging(player, instance, blockPosition, packet.blockFace()); } else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM_STACK) { - dropStack(player); + player.getInventory().handleClick(player, new Click.Info.DropSlot(player.getHeldSlot(), true)); } else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM) { - dropSingle(player); + player.getInventory().handleClick(player, new Click.Info.DropSlot(player.getHeldSlot(), false)); } else if (status == ClientPlayerDiggingPacket.Status.UPDATE_ITEM_STATE) { updateItemState(player); } else if (status == ClientPlayerDiggingPacket.Status.SWAP_ITEM_HAND) { - swapItemHand(player); + player.getInventory().handleClick(player, new Click.Info.OffhandSwap(player.getHeldSlot())); } // Acknowledge start/cancel/finish digging status if (diggingResult != null) { @@ -124,26 +122,6 @@ public final class PlayerDiggingListener { return false; } - private static void dropStack(Player player) { - final ItemStack droppedItemStack = player.getInventory().getItemInMainHand(); - dropItem(player, droppedItemStack, ItemStack.AIR); - } - - private static void dropSingle(Player player) { - final ItemStack handItem = player.getInventory().getItemInMainHand(); - final StackingRule stackingRule = StackingRule.get(); - final int handAmount = stackingRule.getAmount(handItem); - if (handAmount <= 1) { - // Drop the whole item without copy - dropItem(player, handItem, ItemStack.AIR); - } else { - // Drop a single item - dropItem(player, - stackingRule.apply(handItem, 1), // Single dropped item - stackingRule.apply(handItem, handAmount - 1)); // Updated hand - } - } - private static void updateItemState(Player player) { LivingEntityMeta meta = player.getLivingEntityMeta(); if (meta == null || !meta.isHandActive()) return; @@ -162,17 +140,6 @@ public final class PlayerDiggingListener { } } - private static void swapItemHand(Player player) { - final PlayerInventory inventory = player.getInventory(); - final ItemStack mainHand = inventory.getItemInMainHand(); - final ItemStack offHand = inventory.getItemInOffHand(); - PlayerSwapItemEvent swapItemEvent = new PlayerSwapItemEvent(player, offHand, mainHand); - EventDispatcher.callCancellable(swapItemEvent, () -> { - inventory.setItemInMainHand(swapItemEvent.getMainHandItem()); - inventory.setItemInOffHand(swapItemEvent.getOffHandItem()); - }); - } - private static DiggingResult breakBlock(Instance instance, Player player, Point blockPosition, Block previousBlock, BlockFace blockFace) { @@ -191,16 +158,6 @@ public final class PlayerDiggingListener { return new DiggingResult(updatedBlock, success); } - private static void dropItem(@NotNull Player player, - @NotNull ItemStack droppedItem, @NotNull ItemStack handItem) { - final PlayerInventory playerInventory = player.getInventory(); - if (player.dropItem(droppedItem)) { - playerInventory.setItemInMainHand(handItem); - } else { - playerInventory.update(); - } - } - private record DiggingResult(Block block, boolean success) { } } diff --git a/src/main/java/net/minestom/server/listener/UseItemListener.java b/src/main/java/net/minestom/server/listener/UseItemListener.java index 4ffb8dd45..93b6443e2 100644 --- a/src/main/java/net/minestom/server/listener/UseItemListener.java +++ b/src/main/java/net/minestom/server/listener/UseItemListener.java @@ -14,9 +14,8 @@ import net.minestom.server.network.packet.client.play.ClientUseItemPacket; public class UseItemListener { public static void useItemListener(ClientUseItemPacket packet, Player player) { - final PlayerInventory inventory = player.getInventory(); final Player.Hand hand = packet.hand(); - ItemStack itemStack = hand == Player.Hand.MAIN ? inventory.getItemInMainHand() : inventory.getItemInOffHand(); + ItemStack itemStack = player.getItemInHand(hand); //itemStack.onRightClick(player, hand); PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(player, hand, itemStack); EventDispatcher.call(useItemEvent); @@ -33,10 +32,10 @@ public class UseItemListener { // Equip armor with right click final EquipmentSlot equipmentSlot = material.registry().equipmentSlot(); if (equipmentSlot != null) { - final ItemStack currentlyEquipped = playerInventory.getEquipment(equipmentSlot); + final ItemStack currentlyEquipped = player.getEquipment(equipmentSlot); if (currentlyEquipped.isAir()) { - playerInventory.setEquipment(equipmentSlot, itemStack); - playerInventory.setItemInHand(hand, currentlyEquipped); + player.setEquipment(equipmentSlot, itemStack); + player.setItemInHand(hand, currentlyEquipped); } } diff --git a/src/main/java/net/minestom/server/listener/WindowListener.java b/src/main/java/net/minestom/server/listener/WindowListener.java index dfdf2e675..357bc1771 100644 --- a/src/main/java/net/minestom/server/listener/WindowListener.java +++ b/src/main/java/net/minestom/server/listener/WindowListener.java @@ -2,79 +2,29 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; import net.minestom.server.event.EventDispatcher; +import net.minestom.server.event.inventory.InventoryButtonClickEvent; import net.minestom.server.event.inventory.InventoryCloseEvent; -import net.minestom.server.inventory.AbstractInventory; import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.PlayerInventory; -import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.common.ClientPongPacket; +import net.minestom.server.network.packet.client.play.ClientClickWindowButtonPacket; import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; import net.minestom.server.network.packet.client.play.ClientCloseWindowPacket; import net.minestom.server.network.packet.server.common.PingPacket; -import net.minestom.server.network.packet.server.play.SetSlotPacket; public class WindowListener { public static void clickWindowListener(ClientClickWindowPacket packet, Player player) { final int windowId = packet.windowId(); - final AbstractInventory inventory = windowId == 0 ? player.getInventory() : player.getOpenInventory(); - if (inventory == null) { - // Invalid packet - return; + final Inventory inventory = windowId == 0 ? player.getInventory() : player.getOpenInventory(); + + // Prevent some invalid packets + if (inventory == null || packet.slot() == -1) return; + + var info = player.clickPreprocessor().process(packet, inventory, player.isCreative()); + if (info != null) { + inventory.handleClick(player, info); } - final short slot = packet.slot(); - final byte button = packet.button(); - final ClientClickWindowPacket.ClickType clickType = packet.clickType(); - - boolean successful = false; - - // prevent click in a non-interactive slot (why does it exist?) - if (slot == -1) { - return; - } - if (clickType == ClientClickWindowPacket.ClickType.PICKUP) { - if (button == 0) { - if (slot != -999) { - successful = inventory.leftClick(player, slot); - } else { - successful = inventory.drop(player, true, slot, button); - } - } else if (button == 1) { - if (slot != -999) { - successful = inventory.rightClick(player, slot); - } else { - successful = inventory.drop(player, false, slot, button); - } - } - } else if (clickType == ClientClickWindowPacket.ClickType.QUICK_MOVE) { - successful = inventory.shiftClick(player, slot); - } else if (clickType == ClientClickWindowPacket.ClickType.SWAP) { - successful = inventory.changeHeld(player, slot, button); - } else if (clickType == ClientClickWindowPacket.ClickType.CLONE) { - successful = player.isCreative(); - if (successful) { - setCursor(player, inventory, packet.clickedItem()); - } - } else if (clickType == ClientClickWindowPacket.ClickType.THROW) { - successful = inventory.drop(player, false, slot, button); - } else if (clickType == ClientClickWindowPacket.ClickType.QUICK_CRAFT) { - successful = inventory.dragging(player, slot, button); - } else if (clickType == ClientClickWindowPacket.ClickType.PICKUP_ALL) { - successful = inventory.doubleClick(player, slot); - } - - // Prevent ghost item when the click is cancelled - if (!successful) { - player.getInventory().update(); - if (inventory instanceof Inventory) { - ((Inventory) inventory).update(player); - } - } - - // Prevent the player from picking a ghost item in cursor - refreshCursorItem(player, inventory); - // (Why is the ping packet necessary?) player.sendPacket(new PingPacket((1 << 30) | (windowId << 16))); } @@ -85,7 +35,10 @@ public class WindowListener { public static void closeWindowListener(ClientCloseWindowPacket packet, Player player) { // if windowId == 0 then it is player's inventory, meaning that they hadn't been any open inventory packet - InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(player.getOpenInventory(), player); + var openInventory = player.getOpenInventory(); + if (openInventory == null) openInventory = player.getInventory(); + + InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(openInventory, player); EventDispatcher.call(inventoryCloseEvent); player.closeInventory(true); @@ -95,30 +48,12 @@ public class WindowListener { player.openInventory(newInventory); } - /** - * @param player the player to refresh the cursor item - * @param inventory the player open inventory, null if not any (could be player inventory) - */ - private static void refreshCursorItem(Player player, AbstractInventory inventory) { - ItemStack cursorItem; - if (inventory instanceof PlayerInventory playerInventory) { - cursorItem = playerInventory.getCursorItem(); - } else if (inventory instanceof Inventory standardInventory) { - cursorItem = standardInventory.getCursorItem(player); - } else { - throw new RuntimeException("Invalid inventory: " + inventory.getClass()); - } - final SetSlotPacket setSlotPacket = SetSlotPacket.createCursorPacket(cursorItem); - player.sendPacket(setSlotPacket); + public static void buttonClickListener(ClientClickWindowButtonPacket packet, Player player) { + var openInventory = player.getOpenInventory(); + if (openInventory == null) openInventory = player.getInventory(); + + InventoryButtonClickEvent event = new InventoryButtonClickEvent(openInventory, player, packet.buttonId()); + EventDispatcher.call(event); } - private static void setCursor(Player player, AbstractInventory inventory, ItemStack itemStack) { - if (inventory instanceof PlayerInventory playerInventory) { - playerInventory.setCursorItem(itemStack); - } else if (inventory instanceof Inventory standardInventory) { - standardInventory.setCursorItem(player, itemStack); - } else { - throw new RuntimeException("Invalid inventory: " + inventory.getClass()); - } - } } diff --git a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java index 3a45935f8..5e15289ed 100644 --- a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java +++ b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java @@ -64,6 +64,7 @@ public final class PacketListenerManager { setPlayListener(ClientChatMessagePacket.class, ChatMessageListener::chatMessageListener); setPlayListener(ClientClickWindowPacket.class, WindowListener::clickWindowListener); setPlayListener(ClientCloseWindowPacket.class, WindowListener::closeWindowListener); + setPlayListener(ClientClickWindowButtonPacket.class, WindowListener::buttonClickListener); setPlayListener(ClientConfigurationAckPacket.class, PlayConfigListener::configAckListener); setPlayListener(ClientPongPacket.class, WindowListener::pong); setPlayListener(ClientEntityActionPacket.class, EntityActionListener::listener); diff --git a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java index 5c240b75e..793753f6e 100644 --- a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java +++ b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java @@ -1,8 +1,19 @@ package net.minestom.server.utils.inventory; + +/** + * Minestom uses different slot IDs for player inventories as the Minecraft protocol uses a strange system (e.g. the + * crafting result is the first slot).
+ * These can be mapped 1:1 to and from protocol slots using {@link #minestomToProtocol(int)} and {@link #protocolToMinestom(int)}.
+ * + * Read about protocol slot IDs here. + */ public final class PlayerInventoryUtils { - public static final int OFFSET = 9; + public static final int INVENTORY_SIZE = 46; + public static final int INNER_SIZE = 36; + + public static final int PROTOCOL_OFFSET = 9; public static final int CRAFT_RESULT = 36; public static final int CRAFT_SLOT_1 = 37; @@ -14,21 +25,49 @@ public final class PlayerInventoryUtils { public static final int CHESTPLATE_SLOT = 42; public static final int LEGGINGS_SLOT = 43; public static final int BOOTS_SLOT = 44; - public static final int OFFHAND_SLOT = 45; + public static final int OFF_HAND_SLOT = 45; private PlayerInventoryUtils() { } /** - * Converts a packet slot to an internal one. - * - * @param slot the packet slot - * @param offset the slot count separating the up part of the inventory to the bottom part (armor/craft in PlayerInventory, inventory slots in others) - * the offset for the player inventory is {@link #OFFSET} - * @return a packet which can be use internally with Minestom + * Converts a Minestom slot ID to a Minecraft protocol slot ID.
+ * This is the inverse of {@link #protocolToMinestom(int)}. + * @param slot the internal slot ID to convert + * @return the protocol slot ID, or -1 if the given slot could not be converted */ - public static int convertPlayerInventorySlot(int slot, int offset) { + public static int minestomToProtocol(int slot) { + return switch (slot) { + case CRAFT_RESULT -> 0; + case CRAFT_SLOT_1 -> 1; + case CRAFT_SLOT_2 -> 2; + case CRAFT_SLOT_3 -> 3; + case CRAFT_SLOT_4 -> 4; + case HELMET_SLOT -> 5; + case CHESTPLATE_SLOT -> 6; + case LEGGINGS_SLOT -> 7; + case BOOTS_SLOT -> 8; + case OFF_HAND_SLOT -> OFF_HAND_SLOT; + default -> { + if (slot >= 0 && slot <= 8) { + yield slot + 36; + } else if (slot >= 9 && slot <= 35) { + yield slot; + } else { + yield -1; // Unknown slot ID + } + } + }; + } + + /** + * Converts a Minecraft protocol slot ID to a Minestom slot ID.
+ * This is the inverse of {@link #minestomToProtocol(int)}. + * @param slot the protocol slot ID to convert + * @return the Minestom slot ID, or -1 if the given slot could not be converted + */ + public static int protocolToMinestom(int slot) { return switch (slot) { case 0 -> CRAFT_RESULT; case 1 -> CRAFT_SLOT_1; @@ -39,57 +78,47 @@ public final class PlayerInventoryUtils { case 6 -> CHESTPLATE_SLOT; case 7 -> LEGGINGS_SLOT; case 8 -> BOOTS_SLOT; - default -> convertSlot(slot, offset); + case OFF_HAND_SLOT -> OFF_HAND_SLOT; + default -> { + if (slot >= 36 && slot <= 44) { + yield slot - 36; + } else if (slot >= 9 && slot <= 35) { + yield slot; + } else { + yield -1; // Unknown slot ID + } + } }; - - } - - public static int convertSlot(int slot, int offset) { - final int rowSize = 9; - slot -= offset; - if (slot >= rowSize * 3 && slot < rowSize * 4) { - slot = slot % 9; - } else { - slot = slot + rowSize; - } - return slot; - } - - - /** - * Used to convert internal slot to one used in packets - * - * @param slot the internal slot - * @return a slot id which can be used for packets - */ - public static int convertToPacketSlot(int slot) { - if (slot > -1 && slot < 9) { // Held bar 0-8 - slot = slot + 36; - } else if (slot > 8 && slot < 36) { // Inventory 9-35 - slot = slot; - } else if (slot >= CRAFT_RESULT && slot <= CRAFT_SLOT_4) { // Crafting 36-40 - slot = slot - 36; - } else if (slot >= HELMET_SLOT && slot <= BOOTS_SLOT) { // Armor 41-44 - slot = slot - 36; - } else if (slot == OFFHAND_SLOT) { // Off hand - slot = 45; - } - return slot; } /** - * Used to convert the clients inventory slot to a Minestom slot. - * The client's inventory does not count the crafting slots. + * Converts the given slot into a protocol ID directly after the provided inventory. + * This is intended for when a player's inner inventory is interacted with while a player has another inventory + * open.
+ * This is the inverse of {@link #protocolToMinestom(int, int)}. * - * @param slot the client slot - * @return a slot which can be used internally with Minestom + * @param slot the player slot that was interacted with + * @param openInventorySize the size of the inventory opened by the player (not the player's inventory) + * @return the protocol slot ID */ - public static int convertClientInventorySlot(int slot) { - if (slot == 36) return BOOTS_SLOT; - if (slot == 37) return LEGGINGS_SLOT; - if (slot == 38) return CHESTPLATE_SLOT; - if (slot == 39) return HELMET_SLOT; - if (slot == 40) return OFFHAND_SLOT; - return slot; + public static int minestomToProtocol(int slot, int openInventorySize) { + return PlayerInventoryUtils.minestomToProtocol(slot) + openInventorySize - PROTOCOL_OFFSET; } -} + + /** + * Converts the given protocol ID that is directly after the provided inventory's slots into a player inventory slot + * ID. This is intended for when a player's inner inventory is interacted with while a player has another inventory + * open.
+ * This is the inverse of {@link #minestomToProtocol(int, int)}. + * + * @param slot the protocol slot ID, situated directly after the slot IDs for the open inventory + * @param openInventorySize the size of the inventory opened by the player (not the player's inventory) + * @return the player slot ID + */ + public static int protocolToMinestom(int slot, int openInventorySize) { + if (slot < openInventorySize) return -1; + + return PlayerInventoryUtils.protocolToMinestom(slot - openInventorySize + PROTOCOL_OFFSET); + } + +} \ No newline at end of file diff --git a/src/test/java/net/minestom/server/inventory/InventoryCloseStateTest.java b/src/test/java/net/minestom/server/inventory/InventoryCloseStateTest.java index 21d92088f..92464906b 100644 --- a/src/test/java/net/minestom/server/inventory/InventoryCloseStateTest.java +++ b/src/test/java/net/minestom/server/inventory/InventoryCloseStateTest.java @@ -22,7 +22,7 @@ public class InventoryCloseStateTest { assertEquals(instance, player.getInstance()); var packetTracker = connection.trackIncoming(CloseWindowPacket.class); - var inventory = new Inventory(InventoryType.CHEST_2_ROW, Component.text("Test")); + var inventory = new ContainerInventory(InventoryType.CHEST_2_ROW, Component.text("Test")); player.openInventory(inventory); player.closeInventory(); // Closes the inventory server-side, should send a CloseWindowPacket player.openInventory(inventory); diff --git a/src/test/java/net/minestom/server/inventory/InventoryIntegrationTest.java b/src/test/java/net/minestom/server/inventory/InventoryIntegrationTest.java index 68efd7a19..6bff7043a 100644 --- a/src/test/java/net/minestom/server/inventory/InventoryIntegrationTest.java +++ b/src/test/java/net/minestom/server/inventory/InventoryIntegrationTest.java @@ -5,7 +5,7 @@ import net.minestom.server.utils.inventory.PlayerInventoryUtils; import net.minestom.testing.Env; import net.minestom.testing.EnvTest; import net.minestom.server.coordinate.Pos; -import net.minestom.server.event.item.ItemDropEvent; +import net.minestom.server.event.inventory.InventoryItemChangeEvent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; @@ -27,7 +27,7 @@ public class InventoryIntegrationTest { var player = connection.connect(instance, new Pos(0, 42, 0)).join(); assertEquals(instance, player.getInstance()); - Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty()); + ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty()); player.openInventory(inventory); assertEquals(inventory, player.getOpenInventory()); @@ -51,20 +51,20 @@ public class InventoryIntegrationTest { var player = connection.connect(instance, new Pos(0, 42, 0)).join(); assertEquals(instance, player.getInstance()); - Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty()); + ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty()); player.openInventory(inventory); assertEquals(inventory, player.getOpenInventory()); var packetTracker = connection.trackIncoming(SetSlotPacket.class); - inventory.setCursorItem(player, MAGIC_STACK); + player.getInventory().setCursorItem(MAGIC_STACK); packetTracker.assertSingle(slot -> assertEquals(MAGIC_STACK, slot.itemStack())); // Setting a slot should send a packet packetTracker = connection.trackIncoming(SetSlotPacket.class); - inventory.setCursorItem(player, MAGIC_STACK); + player.getInventory().setCursorItem(MAGIC_STACK); packetTracker.assertEmpty(); // Setting the same slot to the same ItemStack should not send another packet packetTracker = connection.trackIncoming(SetSlotPacket.class); - inventory.setCursorItem(player, ItemStack.AIR); + player.getInventory().setCursorItem(ItemStack.AIR); packetTracker.assertSingle(slot -> assertEquals(ItemStack.AIR, slot.itemStack())); // Setting a slot should send a packet } @@ -75,7 +75,7 @@ public class InventoryIntegrationTest { var player = connection.connect(instance, new Pos(0, 42, 0)).join(); assertEquals(instance, player.getInstance()); - Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty()); + ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty()); player.openInventory(inventory); assertEquals(inventory, player.getOpenInventory()); @@ -85,7 +85,7 @@ public class InventoryIntegrationTest { inventory.setItemStack(3, MAGIC_STACK); inventory.setItemStack(19, MAGIC_STACK); inventory.setItemStack(40, MAGIC_STACK); - inventory.setCursorItem(player, MAGIC_STACK); + player.getInventory().setCursorItem(MAGIC_STACK); setSlotTracker.assertCount(5); @@ -116,7 +116,7 @@ public class InventoryIntegrationTest { var instance = env.createFlatInstance(); var connection = env.createConnection(); var player = connection.connect(instance, new Pos(0, 42, 0)).join(); - final var inventory = new Inventory(InventoryType.CHEST_1_ROW, "title"); + final var inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title"); player.openInventory(inventory); assertSame(inventory, player.getOpenInventory()); player.closeInventory(); @@ -124,24 +124,24 @@ public class InventoryIntegrationTest { } @Test - public void openInventoryOnItemDropFromInventoryClosingTest(Env env) { + public void openInventoryOnItemAddFromInventoryClosingTest(Env env) { var instance = env.createFlatInstance(); var connection = env.createConnection(); var player = connection.connect(instance, new Pos(0, 42, 0)).join(); - var listener = env.listen(ItemDropEvent.class); - final var firstInventory = new Inventory(InventoryType.CHEST_1_ROW, "title"); + var listener = env.listen(InventoryItemChangeEvent.class); + final var firstInventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title"); player.openInventory(firstInventory); assertSame(firstInventory, player.getOpenInventory()); - firstInventory.setCursorItem(player, ItemStack.of(Material.STONE)); + player.getInventory().setCursorItem(ItemStack.of(Material.STONE)); listener.followup(); player.closeInventory(); assertNull(player.getOpenInventory()); player.openInventory(firstInventory); - firstInventory.setCursorItem(player, ItemStack.of(Material.STONE)); - final var secondInventory = new Inventory(InventoryType.CHEST_1_ROW, "title"); - listener.followup(event -> event.getPlayer().openInventory(secondInventory)); + player.getInventory().setCursorItem(ItemStack.of(Material.STONE)); + final var secondInventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title"); + listener.followup(event -> player.openInventory(secondInventory)); player.closeInventory(); assertSame(secondInventory, player.getOpenInventory()); } @@ -156,17 +156,16 @@ public class InventoryIntegrationTest { var player = connection.connect(instance, new Pos(0, 42, 0)).join(); assertEquals(instance, player.getInstance()); - Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty()); + Inventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty()); player.openInventory(inventory); assertEquals(inventory, player.getOpenInventory()); // Ensure that slots not in the inner inventory are sent separately var packetTracker = connection.trackIncoming(SetSlotPacket.class); - player.getInventory().setItemStack(PlayerInventoryUtils.OFFHAND_SLOT, MAGIC_STACK); + player.getInventory().setItemStack(PlayerInventoryUtils.OFF_HAND_SLOT, MAGIC_STACK); packetTracker.assertSingle(slot -> { - System.out.println(slot); assertEquals((byte) 0, slot.windowId()); - assertEquals(PlayerInventoryUtils.OFFHAND_SLOT, slot.slot()); + assertEquals(PlayerInventoryUtils.OFF_HAND_SLOT, slot.slot()); assertEquals(MAGIC_STACK, slot.itemStack()); }); @@ -175,8 +174,7 @@ public class InventoryIntegrationTest { player.getInventory().setItemStack(0, MAGIC_STACK); // Test with first inner inventory slot packetTracker.assertSingle(slot -> { assertEquals(inventory.getWindowId(), slot.windowId()); - System.out.println(slot.slot()); - assertEquals(PlayerInventoryUtils.convertToPacketSlot(0) - PlayerInventoryUtils.OFFSET + inventory.getSize(), slot.slot()); + assertEquals(PlayerInventoryUtils.minestomToProtocol(0, inventory.getSize()), slot.slot()); assertEquals(MAGIC_STACK, slot.itemStack()); }); @@ -184,7 +182,7 @@ public class InventoryIntegrationTest { player.getInventory().setItemStack(35, MAGIC_STACK); // Test with last inner inventory slot packetTracker.assertSingle(slot -> { assertEquals(inventory.getWindowId(), slot.windowId()); - assertEquals(PlayerInventoryUtils.convertToPacketSlot(35) - PlayerInventoryUtils.OFFSET + inventory.getSize(), slot.slot()); + assertEquals(PlayerInventoryUtils.minestomToProtocol(35, inventory.getSize()), slot.slot()); assertEquals(MAGIC_STACK, slot.itemStack()); }); } diff --git a/src/test/java/net/minestom/server/inventory/InventoryTest.java b/src/test/java/net/minestom/server/inventory/InventoryTest.java index 612e3b162..0a83a7a9b 100644 --- a/src/test/java/net/minestom/server/inventory/InventoryTest.java +++ b/src/test/java/net/minestom/server/inventory/InventoryTest.java @@ -17,7 +17,7 @@ public class InventoryTest { @Test public void testCreation() { - Inventory inventory = new Inventory(InventoryType.CHEST_1_ROW, "title"); + ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title"); assertEquals(InventoryType.CHEST_1_ROW, inventory.getInventoryType()); assertEquals(Component.text("title"), inventory.getTitle()); @@ -30,7 +30,7 @@ public class InventoryTest { var item1 = ItemStack.of(Material.DIAMOND); var item2 = ItemStack.of(Material.GOLD_INGOT); - Inventory inventory = new Inventory(InventoryType.CHEST_1_ROW, "title"); + ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title"); assertSame(ItemStack.AIR, inventory.getItemStack(0)); inventory.setItemStack(0, item1); assertSame(item1, inventory.getItemStack(0)); @@ -54,7 +54,7 @@ public class InventoryTest { @Test public void testTake() { ItemStack item = ItemStack.of(Material.DIAMOND, 32); - Inventory inventory = new Inventory(InventoryType.CHEST_1_ROW, "title"); + ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title"); inventory.setItemStack(0, item); assertTrue(inventory.takeItemStack(item, TransactionOption.DRY_RUN)); assertTrue(inventory.takeItemStack(item.withAmount(31), TransactionOption.DRY_RUN)); @@ -67,7 +67,7 @@ public class InventoryTest { @Test public void testAdd() { - Inventory inventory = new Inventory(InventoryType.HOPPER, "title"); + ContainerInventory inventory = new ContainerInventory(InventoryType.HOPPER, "title"); assertTrue(inventory.addItemStack(ItemStack.of(Material.DIAMOND, 32), TransactionOption.ALL_OR_NOTHING)); assertTrue(inventory.addItemStack(ItemStack.of(Material.GOLD_BLOCK, 32), TransactionOption.ALL_OR_NOTHING)); assertTrue(inventory.addItemStack(ItemStack.of(Material.MAP, 32), TransactionOption.ALL_OR_NOTHING)); @@ -79,7 +79,7 @@ public class InventoryTest { @Test public void testIds() { for (int i = 0; i <= 1000; ++i) { - final byte windowId = new Inventory(InventoryType.CHEST_1_ROW, "title").getWindowId(); + final byte windowId = new ContainerInventory(InventoryType.CHEST_1_ROW, "title").getWindowId(); assertTrue(windowId > 0); } } diff --git a/src/test/java/net/minestom/server/inventory/PlayerCreativeSlotTest.java b/src/test/java/net/minestom/server/inventory/PlayerCreativeSlotTest.java index a18215deb..e02755f34 100644 --- a/src/test/java/net/minestom/server/inventory/PlayerCreativeSlotTest.java +++ b/src/test/java/net/minestom/server/inventory/PlayerCreativeSlotTest.java @@ -25,9 +25,9 @@ public class PlayerCreativeSlotTest { assertEquals(instance, player.getInstance()); player.setGameMode(GameMode.CREATIVE); - player.addPacketToQueue(new ClientCreativeInventoryActionPacket((short) PlayerInventoryUtils.OFFHAND_SLOT, ItemStack.of(Material.STICK))); + player.addPacketToQueue(new ClientCreativeInventoryActionPacket((short) PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.STICK))); player.interpretPacketQueue(); - assertEquals(Material.STICK, player.getInventory().getItemInOffHand().material()); + assertEquals(Material.STICK, player.getItemInOffHand().material()); } @Test diff --git a/src/test/java/net/minestom/server/inventory/PlayerInventoryIntegrationTest.java b/src/test/java/net/minestom/server/inventory/PlayerInventoryIntegrationTest.java index 268a34a27..03b838ab1 100644 --- a/src/test/java/net/minestom/server/inventory/PlayerInventoryIntegrationTest.java +++ b/src/test/java/net/minestom/server/inventory/PlayerInventoryIntegrationTest.java @@ -118,19 +118,19 @@ public class PlayerInventoryIntegrationTest { var equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class); // Setting to an item should send EntityEquipmentPacket to viewer - playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, MAGIC_STACK); + playerArmored.setEquipment(EquipmentSlot.HELMET, MAGIC_STACK); equipmentTracker.assertSingle(entityEquipmentPacket -> { assertEquals(MAGIC_STACK, entityEquipmentPacket.equipments().get(EquipmentSlot.HELMET)); }); // Setting to the same item shouldn't send packet equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class); - playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, MAGIC_STACK); + playerArmored.setEquipment(EquipmentSlot.HELMET, MAGIC_STACK); equipmentTracker.assertEmpty(); // Setting to air should send packet equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class); - playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, ItemStack.AIR); + playerArmored.setEquipment(EquipmentSlot.HELMET, ItemStack.AIR); equipmentTracker.assertSingle(entityEquipmentPacket -> { assertEquals(ItemStack.AIR, entityEquipmentPacket.equipments().get(EquipmentSlot.HELMET)); }); diff --git a/src/test/java/net/minestom/server/inventory/PlayerSlotConversionTest.java b/src/test/java/net/minestom/server/inventory/PlayerSlotConversionTest.java deleted file mode 100644 index 992051039..000000000 --- a/src/test/java/net/minestom/server/inventory/PlayerSlotConversionTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.minestom.server.inventory; - -import org.junit.jupiter.api.Test; - -import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Test conversion from packet slots to internal ones (used in events and inventory methods) - */ -public class PlayerSlotConversionTest { - - @Test - public void hotbar() { - // Convert 36-44 into 0-8 - for (int i = 0; i < 9; i++) { - assertEquals(i, convertPlayerInventorySlot(i + 36, OFFSET)); - } - } - - @Test - public void mainInventory() { - // No conversion, slots should stay 9-35 - for (int i = 9; i < 9 * 4; i++) { - assertEquals(i, convertPlayerInventorySlot(i, OFFSET)); - } - } - - @Test - public void armor() { - assertEquals(HELMET_SLOT, 41); - assertEquals(CHESTPLATE_SLOT, 42); - assertEquals(LEGGINGS_SLOT, 43); - assertEquals(BOOTS_SLOT, 44); - assertEquals(OFFHAND_SLOT, 45); - - // Convert 5-8 & 45 into 41-45 - assertEquals(HELMET_SLOT, convertPlayerInventorySlot(5, OFFSET)); - assertEquals(CHESTPLATE_SLOT, convertPlayerInventorySlot(6, OFFSET)); - assertEquals(LEGGINGS_SLOT, convertPlayerInventorySlot(7, OFFSET)); - assertEquals(BOOTS_SLOT, convertPlayerInventorySlot(8, OFFSET)); - assertEquals(OFFHAND_SLOT, convertPlayerInventorySlot(45, OFFSET)); - } - - @Test - public void craft() { - assertEquals(CRAFT_RESULT, 36); - assertEquals(CRAFT_SLOT_1, 37); - assertEquals(CRAFT_SLOT_2, 38); - assertEquals(CRAFT_SLOT_3, 39); - assertEquals(CRAFT_SLOT_4, 40); - - // Convert 0-4 into 36-40 - assertEquals(CRAFT_RESULT, convertPlayerInventorySlot(0, OFFSET)); - assertEquals(CRAFT_SLOT_1, convertPlayerInventorySlot(1, OFFSET)); - assertEquals(CRAFT_SLOT_2, convertPlayerInventorySlot(2, OFFSET)); - assertEquals(CRAFT_SLOT_3, convertPlayerInventorySlot(3, OFFSET)); - assertEquals(CRAFT_SLOT_4, convertPlayerInventorySlot(4, OFFSET)); - } -} diff --git a/src/test/java/net/minestom/server/inventory/click/ClickPreprocessorTest.java b/src/test/java/net/minestom/server/inventory/click/ClickPreprocessorTest.java new file mode 100644 index 000000000..d90d2c3e8 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/ClickPreprocessorTest.java @@ -0,0 +1,96 @@ +package net.minestom.server.inventory.click; + +import it.unimi.dsi.fastutil.ints.IntList; +import net.minestom.server.entity.GameMode; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.*; +import static net.minestom.server.network.packet.client.play.ClientClickWindowPacket.ClickType.*; + +public class ClickPreprocessorTest { + + @Test + public void testPickupType() { + assertProcessed(new Click.Info.LeftDropCursor(), clickPacket(PICKUP, 1, 0, -999)); + assertProcessed(new Click.Info.RightDropCursor(), clickPacket(PICKUP, 1, 1, -999)); + assertProcessed(new Click.Info.MiddleDropCursor(), clickPacket(PICKUP, 1, 2, -999)); + + assertProcessed(new Click.Info.Left(0), clickPacket(PICKUP, 1, 0, 0)); + assertProcessed(new Click.Info.Left(SIZE), clickPacket(PICKUP, 1, 0, 5)); + assertProcessed(null, clickPacket(PICKUP, 1, 0, 99)); + + assertProcessed(new Click.Info.Right(0), clickPacket(PICKUP, 1, 1, 0)); + assertProcessed(new Click.Info.Right(SIZE), clickPacket(PICKUP, 1, 1, 5)); + assertProcessed(null, clickPacket(PICKUP, 1, 1, 99)); + + assertProcessed(null, clickPacket(PICKUP, 1, -1, 0)); + assertProcessed(null, clickPacket(PICKUP, 1, 2, 0)); + } + + @Test + public void testQuickMoveType() { + assertProcessed(new Click.Info.LeftShift(0), clickPacket(QUICK_MOVE, 1, 0, 0)); + assertProcessed(new Click.Info.LeftShift(SIZE), clickPacket(QUICK_MOVE, 1, 0, 5)); + assertProcessed(null, clickPacket(QUICK_MOVE, 1, 0, -1)); + } + + @Test + public void testSwapType() { + assertProcessed(null, clickPacket(SWAP, 1, 0, -1)); + assertProcessed(new Click.Info.HotbarSwap(0, 2), clickPacket(SWAP, 1, 0, 2)); + assertProcessed(new Click.Info.HotbarSwap(8, 2), clickPacket(SWAP, 1, 8, 2)); + assertProcessed(new Click.Info.OffhandSwap(2), clickPacket(SWAP, 1, 40, 2)); + + assertProcessed(null, clickPacket(SWAP, 1, 9, 2)); + assertProcessed(null, clickPacket(SWAP, 1, 39, 2)); + } + + @Test + public void testCloneType() { + var player = createPlayer(); + player.setGameMode(GameMode.CREATIVE); + + assertProcessed(null, clickPacket(CLONE, 1, 0, 0)); + assertProcessed(player, new Click.Info.Middle(0), clickPacket(CLONE, 1, 0, 0)); + assertProcessed(player, null, clickPacket(CLONE, 1, 0, -1)); + } + + @Test + public void testThrowType() { + assertProcessed(new Click.Info.DropSlot(0, true), clickPacket(THROW, 1, 1, 0)); + + assertProcessed(new Click.Info.DropSlot(0, false), clickPacket(THROW, 1, 0, 0)); + assertProcessed(new Click.Info.DropSlot(0, true), clickPacket(THROW, 1, 1, 0)); + + assertProcessed(new Click.Info.DropSlot(1, false), clickPacket(THROW, 1, 0, 1)); + assertProcessed(new Click.Info.DropSlot(1, true), clickPacket(THROW, 1, 1, 1)); + } + + @Test + public void testQuickCraft() { + var processor = createPreprocessor(); + var player = createPlayer(); + + assertProcessed(player, null, clickPacket(QUICK_CRAFT, 1, 8, 0)); + assertProcessed(player, null, clickPacket(QUICK_CRAFT, 1, 9, 0)); + assertProcessed(player, null, clickPacket(QUICK_CRAFT, 1, 10, 0)); + + player.setGameMode(GameMode.CREATIVE); + + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 0, 0)); + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 1, 0)); + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 1, 1)); + assertProcessed(processor, player, new Click.Info.LeftDrag(IntList.of(0, 1)), clickPacket(QUICK_CRAFT, 1, 2, 0)); + + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 4, 0)); + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 5, 0)); + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 5, 1)); + assertProcessed(processor, player, new Click.Info.RightDrag(IntList.of(0, 1)), clickPacket(QUICK_CRAFT, 1, 6, 0)); + + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 8, 0)); + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 9, 0)); + assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 9, 1)); + assertProcessed(processor, player, new Click.Info.MiddleDrag(IntList.of(0, 1)), clickPacket(QUICK_CRAFT, 1, 10, 0)); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java new file mode 100644 index 000000000..ef2797cdc --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java @@ -0,0 +1,84 @@ +package net.minestom.server.inventory.click; + +import net.minestom.server.entity.Player; +import net.minestom.server.inventory.ContainerInventory; +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; +import net.minestom.server.network.packet.server.SendablePacket; +import net.minestom.server.network.player.PlayerConnection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.net.SocketAddress; +import java.util.List; +import java.util.UUID; +import java.util.function.UnaryOperator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ClickUtils { + + public static final @NotNull InventoryType TYPE = InventoryType.HOPPER; + + public static final int SIZE = TYPE.getSize(); // Default hopper size + + public static @NotNull Inventory createInventory() { + return new ContainerInventory(TYPE, "TestInventory"); + } + + public static @NotNull Click.Preprocessor createPreprocessor() { + return new Click.Preprocessor(); + } + + public static @NotNull Player createPlayer() { + return new Player(UUID.randomUUID(), "TestPlayer", new PlayerConnection() { + @Override + public void sendPacket(@NotNull SendablePacket packet) {} + + @Override + public @NotNull SocketAddress getRemoteAddress() { + return null; + } + + @Override + public void disconnect() {} + }); + } + + public static void assertClick(@NotNull UnaryOperator initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator expectedChanges) { + var player = createPlayer(); + var inventory = createInventory(); + + ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); + var changes = inventory.handleClick(player, info); + assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); + } + + public static void assertPlayerClick(@NotNull UnaryOperator initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator expectedChanges) { + var player = createPlayer(); + var inventory = player.getInventory(); + + ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); + var changes = inventory.handleClick(player, info); + assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); + } + + public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { + assertEquals(info, preprocessor.process(packet, createInventory(), player.isCreative())); + } + + public static void assertProcessed(@NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { + assertProcessed(new Click.Preprocessor(), player, info, packet); + } + + public static void assertProcessed(@Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { + assertProcessed(createPlayer(), info, packet); + } + + public static @NotNull ClientClickWindowPacket clickPacket(@NotNull ClientClickWindowPacket.ClickType type, int windowId, int button, int slot) { + return new ClientClickWindowPacket((byte) windowId, 0, (short) slot, (byte) button, type, List.of(), ItemStack.AIR); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/integration/HeldClickIntegrationTest.java b/src/test/java/net/minestom/server/inventory/click/integration/HeldClickIntegrationTest.java deleted file mode 100644 index 18ac2c326..000000000 --- a/src/test/java/net/minestom/server/inventory/click/integration/HeldClickIntegrationTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package net.minestom.server.inventory.click.integration; - -import net.minestom.server.coordinate.Pos; -import net.minestom.server.entity.Player; -import net.minestom.server.event.inventory.InventoryPreClickEvent; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; -import net.minestom.server.utils.inventory.PlayerInventoryUtils; -import net.minestom.testing.Env; -import net.minestom.testing.EnvTest; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@EnvTest -public class HeldClickIntegrationTest { - - @Test - public void heldSelf(Env env) { - var instance = env.createFlatInstance(); - var player = env.createPlayer(instance, new Pos(0, 40, 0)); - var inventory = player.getInventory(); - var listener = env.listen(InventoryPreClickEvent.class); - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)); - inventory.setItemStack(2, ItemStack.of(Material.GOLD_INGOT)); - inventory.setItemStack(3, ItemStack.of(Material.EGG)); - inventory.setItemStack(6, ItemStack.of(Material.DIAMOND)); - // Empty - { - listener.followup(event -> { - assertNull(event.getInventory()); // Player inventory - assertTrue(event.getSlot() == 4 || event.getSlot() == 5); - assertEquals(ClickType.CHANGE_HELD, event.getClickType()); - - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.AIR, event.getCursorItem()); - - assertEquals(ItemStack.AIR, event.getClickedItem()); - }); - heldClick(player, 4, 5); - } - // Swap air - { - listener.followup(event -> { - assertNull(event.getInventory()); // Player inventory - assertTrue(event.getSlot() == 1 || event.getSlot() == 0); - assertEquals(ClickType.CHANGE_HELD, event.getClickType()); - - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.AIR, event.getCursorItem()); - - assertEquals(ItemStack.of(Material.DIAMOND), event.getClickedItem()); - }); - heldClick(player, 1, 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(0)); - } - // Swap items - { - listener.followup(event -> { - assertTrue(event.getSlot() == 0 || event.getSlot() == 2); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - }); - heldClick(player, 0, 2); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2)); - assertEquals(ItemStack.of(Material.GOLD_INGOT), inventory.getItemStack(0)); - } - // Swap offhand - { - listener.followup(event -> { - assertTrue(event.getSlot() == 3 || event.getSlot() == 45 /* Vanilla offhand slot is 40, Minestom is 45 */); - }); - heldClick(player, 3, 40); - assertEquals(ItemStack.AIR, inventory.getItemStack(3)); - assertEquals(ItemStack.of(Material.EGG), inventory.getItemInOffHand()); - } - // Cancel event - { - listener.followup(event -> event.setCancelled(true)); - heldClick(player, 2, 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2)); - assertEquals(ItemStack.of(Material.GOLD_INGOT), inventory.getItemStack(0)); - } - } - - @Test - public void heldExternal(Env env) { - var instance = env.createFlatInstance(); - var player = env.createPlayer(instance, new Pos(0, 40, 0)); - var inventory = new Inventory(InventoryType.HOPPER, "test"); - var playerInv = player.getInventory(); - player.openInventory(inventory); - var listener = env.listen(InventoryPreClickEvent.class); - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)); - inventory.setItemStack(2, ItemStack.of(Material.GOLD_INGOT)); - inventory.setItemStack(3, ItemStack.of(Material.EGG)); - inventory.setItemStack(4, ItemStack.of(Material.DIAMOND)); - // Empty - { - listener.followup(event -> { - if (event.getInventory() != null) assertEquals(inventory, event.getInventory()); - assertEquals(0, event.getSlot()); - assertEquals(ClickType.CHANGE_HELD, event.getClickType()); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - }); - heldClickOpenInventory(player, 0, 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.AIR, inventory.getItemStack(0)); - } - // Swap empty - { - listener.followup(event -> { - if (event.getInventory() != null) assertEquals(inventory, event.getInventory()); - assertTrue(event.getSlot() == 1 || event.getSlot() == 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - }); - heldClickOpenInventory(player, 1, 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - assertEquals(ItemStack.of(Material.DIAMOND), playerInv.getItemStack(0)); - } - // Swap items - { - listener.followup(event -> { - if (event.getInventory() != null) assertEquals(inventory, event.getInventory()); - assertTrue(event.getSlot() == 2 || event.getSlot() == 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - }); - heldClickOpenInventory(player, 2, 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2)); - assertEquals(ItemStack.of(Material.GOLD_INGOT), playerInv.getItemStack(0)); - } - // Swap offhand - { - listener.followup(event -> { - if (event.getInventory() != null) assertEquals(inventory, event.getInventory()); - assertTrue(event.getSlot() == 3 || event.getSlot() == 45); - }); - heldClickOpenInventory(player, 3, 40); - assertEquals(ItemStack.AIR, inventory.getItemStack(3)); - assertEquals(ItemStack.of(Material.EGG), playerInv.getItemInOffHand()); - } - // Cancel event - { - listener.followup(event -> event.setCancelled(true)); - heldClickOpenInventory(player, 2, 0); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2)); - assertEquals(ItemStack.of(Material.GOLD_INGOT), playerInv.getItemStack(0)); - } - } - - private void heldClickOpenInventory(Player player, int slot, int target) { - _heldClick(player.getOpenInventory(), true, player, slot, target); - } - - private void heldClick(Player player, int slot, int target) { - _heldClick(player.getOpenInventory(), false, player, slot, target); - } - - private void _heldClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot, int target) { - final byte windowId = openInventory != null ? openInventory.getWindowId() : 0; - if (clickOpenInventory) { - assert openInventory != null; - // Do not touch slot - } else { - int offset = openInventory != null ? openInventory.getInnerSize() : 0; - slot = PlayerInventoryUtils.convertToPacketSlot(slot); - if (openInventory != null) { - slot = slot - 9 + offset; - } - } - player.addPacketToQueue(new ClientClickWindowPacket(windowId, 0, (short) slot, (byte) target, - ClientClickWindowPacket.ClickType.SWAP, List.of(), ItemStack.AIR)); - player.interpretPacketQueue(); - } -} diff --git a/src/test/java/net/minestom/server/inventory/click/integration/LeftClickIntegrationTest.java b/src/test/java/net/minestom/server/inventory/click/integration/LeftClickIntegrationTest.java deleted file mode 100644 index af9d31a48..000000000 --- a/src/test/java/net/minestom/server/inventory/click/integration/LeftClickIntegrationTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package net.minestom.server.inventory.click.integration; - - -import net.minestom.server.coordinate.Pos; -import net.minestom.server.entity.Player; -import net.minestom.server.event.inventory.InventoryPreClickEvent; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; -import net.minestom.server.utils.inventory.PlayerInventoryUtils; -import net.minestom.testing.Env; -import net.minestom.testing.EnvTest; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -@EnvTest -public class LeftClickIntegrationTest { - - @Test - public void leftSelf(Env env) { - var instance = env.createFlatInstance(); - var player = env.createPlayer(instance, new Pos(0, 40, 0)); - var inventory = player.getInventory(); - var listener = env.listen(InventoryPreClickEvent.class); - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)); - // Empty click - { - listener.followup(event -> { - assertNull(event.getInventory()); // Player inventory - assertEquals(0, event.getSlot()); - assertEquals(ClickType.LEFT_CLICK, event.getClickType()); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - }); - leftClick(player, 0); - } - // Pickup diamond - { - listener.followup(event -> { - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - }); - leftClick(player, 1); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - } - // Place it back - { - listener.followup(event -> { - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - }); - leftClick(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - } - // Cancel event - { - listener.followup(event -> event.setCancelled(true)); - leftClick(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem(), "Left click cancellation did not work"); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - } - // Change items - { - listener.followup(event -> { - event.setClickedItem(ItemStack.of(Material.DIAMOND, 5)); - event.setCursorItem(ItemStack.of(Material.DIAMOND)); - }); - leftClick(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND, 6), inventory.getItemStack(1)); - } - } - - @Test - public void leftExternal(Env env) { - var instance = env.createFlatInstance(); - var player = env.createPlayer(instance, new Pos(0, 40, 0)); - var inventory = new Inventory(InventoryType.HOPPER, "test"); - player.openInventory(inventory); - var listener = env.listen(InventoryPreClickEvent.class); - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)); - // Empty click in player inv - { - listener.followup(event -> { - assertNull(event.getInventory()); // Player inventory - assertEquals(0, event.getSlot()); - assertEquals(ClickType.LEFT_CLICK, event.getClickType()); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - }); - leftClick(player, 0); - } - // Pickup diamond - { - listener.followup(event -> { - assertEquals(inventory, event.getInventory()); - assertEquals(1, event.getSlot()); - // Ensure that the inventory didn't change yet - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - }); - leftClickOpenInventory(player, 1); - // Verify inventory changes - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player)); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - } - // Place it back - { - listener.followup(event -> { - assertEquals(inventory, event.getInventory()); - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player)); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - }); - leftClickOpenInventory(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - } - // Cancel event - { - listener.followup(event -> event.setCancelled(true)); - leftClickOpenInventory(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player), "Left click cancellation did not work"); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - } - // Change items - { - listener.followup(event -> { - assertNull(event.getInventory()); - assertEquals(9, event.getSlot()); - event.setClickedItem(ItemStack.of(Material.DIAMOND, 5)); - event.setCursorItem(ItemStack.of(Material.DIAMOND)); - }); - leftClick(player, 9); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND, 6), player.getInventory().getItemStack(9)); - } - } - - private void leftClickOpenInventory(Player player, int slot) { - _leftClick(player.getOpenInventory(), true, player, slot); - } - - private void leftClick(Player player, int slot) { - _leftClick(player.getOpenInventory(), false, player, slot); - } - - private void _leftClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot) { - final byte windowId = openInventory != null ? openInventory.getWindowId() : 0; - if (clickOpenInventory) { - assert openInventory != null; - // Do not touch slot - } else { - int offset = openInventory != null ? openInventory.getInnerSize() : 0; - slot = PlayerInventoryUtils.convertToPacketSlot(slot); - if (openInventory != null) { - slot = slot - 9 + offset; - } - } - player.addPacketToQueue(new ClientClickWindowPacket(windowId, 0, (short) slot, (byte) 0, - ClientClickWindowPacket.ClickType.PICKUP, List.of(), ItemStack.AIR)); - player.interpretPacketQueue(); - } -} diff --git a/src/test/java/net/minestom/server/inventory/click/integration/RightClickIntegrationTest.java b/src/test/java/net/minestom/server/inventory/click/integration/RightClickIntegrationTest.java deleted file mode 100644 index 98d2be504..000000000 --- a/src/test/java/net/minestom/server/inventory/click/integration/RightClickIntegrationTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package net.minestom.server.inventory.click.integration; - -import net.minestom.server.coordinate.Pos; -import net.minestom.server.entity.Player; -import net.minestom.server.event.inventory.InventoryPreClickEvent; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.inventory.click.ClickType; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; -import net.minestom.server.utils.inventory.PlayerInventoryUtils; -import net.minestom.testing.Env; -import net.minestom.testing.EnvTest; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -@EnvTest -public class RightClickIntegrationTest { - - @Test - public void rightSelf(Env env) { - var instance = env.createFlatInstance(); - var player = env.createPlayer(instance, new Pos(0, 40, 0)); - var inventory = player.getInventory(); - var listener = env.listen(InventoryPreClickEvent.class); - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)); - inventory.setItemStack(2, ItemStack.of(Material.DIAMOND)); - // Empty click - { - listener.followup(event -> { - assertNull(event.getInventory()); // Player inventory - assertEquals(0, event.getSlot()); - assertEquals(ClickType.RIGHT_CLICK, event.getClickType()); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - }); - rightClick(player, 0); - } - // Pickup diamond - { - listener.followup(event -> { - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - }); - rightClick(player, 1); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - } - // Place it back - { - listener.followup(event -> { - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - }); - rightClick(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - } - // Pickup diamond - { - listener.followup(event -> { - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - }); - rightClick(player, 1); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem()); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - } - // Stack diamond - { - listener.followup(event -> { - assertEquals(2, event.getSlot()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2)); - }); - rightClick(player, 2); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND, 2), inventory.getItemStack(2)); - } - // Cancel event - { - listener.followup(event -> event.setCancelled(true)); - rightClick(player, 2); - assertEquals(ItemStack.AIR, inventory.getCursorItem(), "Left click cancellation did not work"); - assertEquals(ItemStack.of(Material.DIAMOND, 2), inventory.getItemStack(2)); - } - // Change items - { - listener.followup(event -> { - event.setClickedItem(ItemStack.of(Material.DIAMOND, 5)); - event.setCursorItem(ItemStack.of(Material.DIAMOND)); - }); - rightClick(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem()); - assertEquals(ItemStack.of(Material.DIAMOND, 6), inventory.getItemStack(1)); - } - } - - @Test - public void rightExternal(Env env) { - var instance = env.createFlatInstance(); - var player = env.createPlayer(instance, new Pos(0, 40, 0)); - var inventory = new Inventory(InventoryType.HOPPER, "test"); - player.openInventory(inventory); - var listener = env.listen(InventoryPreClickEvent.class); - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)); - // Empty click in player inv - { - listener.followup(event -> { - assertNull(event.getInventory()); // Player inventory - assertEquals(0, event.getSlot()); - assertEquals(ClickType.RIGHT_CLICK, event.getClickType()); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - }); - rightClick(player, 0); - } - // Pickup diamond - { - listener.followup(event -> { - assertEquals(inventory, event.getInventory()); - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1)); - }); - rightClickOpenInventory(player, 1); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player)); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - } - // Place back to player inv - { - listener.followup(event -> { - assertNull(event.getInventory()); - assertEquals(1, event.getSlot()); - assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player)); - assertEquals(ItemStack.AIR, inventory.getItemStack(1)); - assertEquals(ItemStack.AIR, player.getInventory().getItemStack(1)); - }); - rightClick(player, 1); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND), player.getInventory().getItemStack(1)); - } - // Cancel event - { - listener.followup(event -> event.setCancelled(true)); - rightClick(player, 1); - assertEquals(ItemStack.of(Material.DIAMOND), player.getInventory().getItemStack(1), "Left click cancellation did not work"); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - } - // Change items - { - listener.followup(event -> { - assertNull(event.getInventory()); - assertEquals(9, event.getSlot()); - event.setClickedItem(ItemStack.of(Material.DIAMOND, 5)); - event.setCursorItem(ItemStack.of(Material.DIAMOND)); - }); - rightClick(player, 9); - assertEquals(ItemStack.AIR, inventory.getCursorItem(player)); - assertEquals(ItemStack.of(Material.DIAMOND, 6), player.getInventory().getItemStack(9)); - } - } - - private void rightClickOpenInventory(Player player, int slot) { - _rightClick(player.getOpenInventory(), true, player, slot); - } - - private void rightClick(Player player, int slot) { - _rightClick(player.getOpenInventory(), false, player, slot); - } - - private void _rightClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot) { - final byte windowId = openInventory != null ? openInventory.getWindowId() : 0; - if (clickOpenInventory) { - assert openInventory != null; - // Do not touch slot - } else { - int offset = openInventory != null ? openInventory.getInnerSize() : 0; - slot = PlayerInventoryUtils.convertToPacketSlot(slot); - if (openInventory != null) { - slot = slot - 9 + offset; - } - } - player.addPacketToQueue(new ClientClickWindowPacket(windowId, 0, (short) slot, (byte) 1, - ClientClickWindowPacket.ClickType.PICKUP, List.of(), ItemStack.AIR)); - player.interpretPacketQueue(); - } -} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java new file mode 100644 index 000000000..92c398241 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java @@ -0,0 +1,33 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryCreativeDropItemTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testDropItem() { + assertClick( + builder -> builder, + new Click.Info.CreativeDropItem(ItemStack.of(Material.DIRT, 64)), + builder -> builder.sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.DIRT, 64))) + ); + + // Make sure it doesn't drop a full stack + assertClick( + builder -> builder, + new Click.Info.CreativeDropItem(ItemStack.of(Material.DIRT, 1)), + builder -> builder.sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.DIRT, 1))) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java new file mode 100644 index 000000000..e67924f0e --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java @@ -0,0 +1,33 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryCreativeSetItemTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testSetItem() { + assertClick( + builder -> builder, + new Click.Info.CreativeSetItem(0, ItemStack.of(Material.DIRT, 64)), + builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)) + ); + + // Make sure it doesn't set a full stack + assertClick( + builder -> builder, + new Click.Info.CreativeSetItem(0, ItemStack.of(Material.DIRT, 1)), + builder -> builder.set(0, ItemStack.of(Material.DIRT, 1)) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java new file mode 100644 index 000000000..5d83eb936 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java @@ -0,0 +1,91 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryDoubleClickTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.Double(0), builder -> builder); + } + + @Test + public void testCannotTakeAny() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Double(0), + builder -> builder + ); + } + + @Test + public void testPartialTake() { + assertClick( + builder -> builder.set(1, ItemStack.of(Material.STONE, 48)).cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Double(0), + builder -> builder.set(1, ItemStack.of(Material.STONE, 16)).cursor(ItemStack.of(Material.STONE, 64)) + ); + } + + @Test + public void testTakeAll() { + assertClick( + builder -> builder.set(1, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Double(0), + builder -> builder.set(1, ItemStack.AIR).cursor(ItemStack.of(Material.STONE, 64)) + ); + + assertClick( + builder -> builder.set(1, ItemStack.of(Material.STONE, 16)).cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Double(0), + builder -> builder.set(1, ItemStack.AIR).cursor(ItemStack.of(Material.STONE, 48)) + ); + } + + @Test + public void testTakeSeparated() { + assertClick( + builder -> builder + .set(1, ItemStack.of(Material.STONE, 16)) + .set(2, ItemStack.of(Material.STONE, 16)) + .cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Double(0), + builder -> builder + .set(1, ItemStack.AIR) + .set(2, ItemStack.AIR) + .cursor(ItemStack.of(Material.STONE, 64)) + ); + + assertClick( + builder -> builder + .set(1, ItemStack.of(Material.STONE, 16)) + .set(2, ItemStack.of(Material.STONE, 32)) + .cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Double(0), + builder -> builder + .set(1, ItemStack.AIR) + .set(2, ItemStack.of(Material.STONE, 16)) + .cursor(ItemStack.of(Material.STONE, 64)) + ); + } + + @Test + public void testCursorFull() { + assertClick( + builder -> builder.set(1, ItemStack.of(Material.STONE, 48)).cursor(ItemStack.of(Material.STONE, 64)), + new Click.Info.Double(0), + builder -> builder + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java new file mode 100644 index 000000000..abfe71bb2 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java @@ -0,0 +1,51 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryDropCursorTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.LeftDropCursor(), builder -> builder); + assertClick(builder -> builder, new Click.Info.MiddleDropCursor(), builder -> builder); + assertClick(builder -> builder, new Click.Info.RightDropCursor(), builder -> builder); + } + + @Test + public void testDropEntireStack() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.LeftDropCursor(), + builder -> builder.cursor(ItemStack.AIR).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 32))) + ); + } + + @Test + public void testDropSingleItem() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.RightDropCursor(), + builder -> builder.cursor(ItemStack.of(Material.STONE, 31)).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 1))) + ); + } + + @Test + public void testMiddleClickNoop() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.MiddleDropCursor(), + builder -> builder + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java new file mode 100644 index 000000000..1bed2e19c --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java @@ -0,0 +1,41 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryDropSlotTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.DropSlot(0, false), builder -> builder); + assertClick(builder -> builder, new Click.Info.DropSlot(0, true), builder -> builder); + } + + @Test + public void testDropEntireStack() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE, 32)), + new Click.Info.DropSlot(0, true), + builder -> builder.set(0, ItemStack.AIR).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 32))) + ); + } + + @Test + public void testDropSingleItem() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE, 32)), + new Click.Info.DropSlot(0, false), + builder -> builder.set(0, ItemStack.of(Material.STONE, 31)).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 1))) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java new file mode 100644 index 000000000..6ba864350 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java @@ -0,0 +1,33 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryHotbarSwapTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + for (int i = 0; i < 9; i++) { + assertClick(builder -> builder, new Click.Info.HotbarSwap(i, 9), builder -> builder); + } + } + + @Test + public void testSwappedItems() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT)).setPlayer(0, ItemStack.of(Material.STONE)), + new Click.Info.HotbarSwap(0, 0), + builder -> builder.set(0, ItemStack.of(Material.STONE)).setPlayer(0, ItemStack.of(Material.DIRT)) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java new file mode 100644 index 000000000..1ff08b07a --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java @@ -0,0 +1,49 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryLeftClickTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.Left(0), builder -> builder); + } + + @Test + public void testInsertEntireStack() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Left(0), + builder -> builder.set(0, ItemStack.of(Material.STONE, 64)).cursor(ItemStack.AIR) + ); + } + + @Test + public void testInsertPartialStack() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 48)), + new Click.Info.Left(0), + builder -> builder.set(0, ItemStack.of(Material.STONE, 64)).cursor(ItemStack.of(Material.STONE, 16)) + ); + } + + @Test + public void testSwitchItems() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + new Click.Info.Left(0), + builder -> builder.set(0, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.STONE)) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java new file mode 100644 index 000000000..a860af4a8 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java @@ -0,0 +1,102 @@ +package net.minestom.server.inventory.click.type; + +import it.unimi.dsi.fastutil.ints.IntList; +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryLeftDragTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoCursor() { + assertClick(builder -> builder, new Click.Info.LeftDrag(IntList.of(0)), builder -> builder); + } + + @Test + public void testDistributeNone() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.LeftDrag(IntList.of()), + builder -> builder + ); + } + + @Test + public void testDistributeOne() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.LeftDrag(IntList.of(0)), + builder -> builder.set(0, ItemStack.of(Material.DIRT, 32)).cursor(ItemStack.of(Material.AIR)) + ); + } + + @Test + public void testDistributeExactlyEnough() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.LeftDrag(IntList.of(0, 1)), + builder -> builder.set(0, ItemStack.of(Material.DIRT, 16)).set(1, ItemStack.of(Material.DIRT, 16)).cursor(ItemStack.of(Material.AIR)) + ); + + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 30)), + new Click.Info.LeftDrag(IntList.of(0, 1, 2)), + builder -> builder + .set(0, ItemStack.of(Material.DIRT, 10)) + .set(1, ItemStack.of(Material.DIRT, 10)) + .set(2, ItemStack.of(Material.DIRT, 10)) + .cursor(ItemStack.of(Material.AIR)) + ); + } + + @Test + public void testRemainderItems() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.LeftDrag(IntList.of(0, 1, 2)), + builder -> builder + .set(0, ItemStack.of(Material.DIRT, 10)) + .set(1, ItemStack.of(Material.DIRT, 10)) + .set(2, ItemStack.of(Material.DIRT, 10)) + .cursor(ItemStack.of(Material.DIRT, 2)) + ); + + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 25)), + new Click.Info.LeftDrag(IntList.of(0, 1, 2, 3)), + builder -> builder + .set(0, ItemStack.of(Material.DIRT, 6)) + .set(1, ItemStack.of(Material.DIRT, 6)) + .set(2, ItemStack.of(Material.DIRT, 6)) + .set(3, ItemStack.of(Material.DIRT, 6)) + .cursor(ItemStack.of(Material.DIRT, 1)) + ); + } + + @Test + public void testDistributeOverExisting() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT, 16)).cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.LeftDrag(IntList.of(0)), + builder -> builder.set(0, ItemStack.of(Material.DIRT, 48)).cursor(ItemStack.of(Material.AIR)) + ); + } + + @Test + public void testDistributeOverFull() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)).cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.LeftDrag(IntList.of(0)), + builder -> builder + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java new file mode 100644 index 000000000..7142f0434 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java @@ -0,0 +1,40 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryMiddleClickTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.Middle(0), builder -> builder); + } + + @Test + public void testCopy() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)), + new Click.Info.Middle(0), + builder -> builder.cursor(ItemStack.of(Material.DIRT, 64)) + ); + } + + @Test + public void testCopyNotFull() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT, 32)), + new Click.Info.Middle(0), + builder -> builder.cursor(ItemStack.of(Material.DIRT, 64)) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java new file mode 100644 index 000000000..72890fb30 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java @@ -0,0 +1,50 @@ +package net.minestom.server.inventory.click.type; + +import it.unimi.dsi.fastutil.ints.IntList; +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryMiddleDragTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.MiddleDrag(IntList.of()), builder -> builder); + } + + @Test + public void testExistingSlots() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + new Click.Info.MiddleDrag(IntList.of(0)), + builder -> builder + ); + } + + @Test + public void testPartialExistingSlots() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + new Click.Info.MiddleDrag(IntList.of(0, 1)), + builder -> builder.set(1, ItemStack.of(Material.DIRT)) + ); + } + + @Test + public void testFullCopy() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT)), + new Click.Info.MiddleDrag(IntList.of(0, 1)), + builder -> builder.set(0, ItemStack.of(Material.DIRT)).set(1, ItemStack.of(Material.DIRT)) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java new file mode 100644 index 000000000..cb2c4b4ab --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java @@ -0,0 +1,32 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryOffhandSwapTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.OffhandSwap(0), builder -> builder); + } + + @Test + public void testSwappedItems() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT)).setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.STONE)), + new Click.Info.OffhandSwap(0), + builder -> builder.set(0, ItemStack.of(Material.STONE)).setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.DIRT)) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java new file mode 100644 index 000000000..c8ad77ea5 --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java @@ -0,0 +1,67 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryRightClickTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.Right(0), builder -> builder); + } + + @Test + public void testAddOne() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Right(0), + builder -> builder.set(0, ItemStack.of(Material.STONE, 33)).cursor(ItemStack.of(Material.STONE, 31)) + ); + } + + @Test + public void testClickedStackFull() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE, 64)).cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Right(0), + builder -> builder + ); + } + + @Test + public void testTakeHalf() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE, 32)), + new Click.Info.Right(0), + builder -> builder.set(0, ItemStack.of(Material.STONE, 16)).cursor(ItemStack.of(Material.STONE, 16)) + ); + } + + @Test + public void testLeaveOne() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + new Click.Info.Right(0), + builder -> builder.set(0, ItemStack.of(Material.STONE, 1)).cursor(ItemStack.of(Material.STONE, 31)) + ); + } + + @Test + public void testSwitchItems() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + new Click.Info.Right(0), + builder -> builder.set(0, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.STONE)) + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java new file mode 100644 index 000000000..c24cd7b4d --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java @@ -0,0 +1,77 @@ +package net.minestom.server.inventory.click.type; + +import it.unimi.dsi.fastutil.ints.IntList; +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.assertClick; + +public class InventoryRightDragTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoCursor() { + assertClick(builder -> builder, new Click.Info.RightDrag(IntList.of(0)), builder -> builder); + } + + @Test + public void testDistributeNone() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.RightDrag(IntList.of()), + builder -> builder + ); + } + + @Test + public void testDistributeOne() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.RightDrag(IntList.of(0)), + builder -> builder.set(0, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.DIRT, 31)) + ); + } + + @Test + public void testDistributeExactlyEnough() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 2)), + new Click.Info.RightDrag(IntList.of(0, 1)), + builder -> builder.set(0, ItemStack.of(Material.DIRT)).set(1, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.AIR)) + ); + } + + @Test + public void testTooManySlots() { + assertClick( + builder -> builder.cursor(ItemStack.of(Material.DIRT, 2)), + new Click.Info.RightDrag(IntList.of(0, 1, 2)), + builder -> builder.set(0, ItemStack.of(Material.DIRT)).set(1, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.AIR)) + ); + } + + @Test + public void testDistributeOverExisting() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT, 16)).cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.RightDrag(IntList.of(0)), + builder -> builder.set(0, ItemStack.of(Material.DIRT, 17)).cursor(ItemStack.of(Material.DIRT, 31)) + ); + } + + @Test + public void testDistributeOverFull() { + assertClick( + builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)).cursor(ItemStack.of(Material.DIRT, 32)), + new Click.Info.RightDrag(IntList.of(0)), + builder -> builder + ); + } + +} diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java new file mode 100644 index 000000000..24723325f --- /dev/null +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java @@ -0,0 +1,149 @@ +package net.minestom.server.inventory.click.type; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; +import org.junit.jupiter.api.Test; + +import static net.minestom.server.inventory.click.ClickUtils.*; + +public class InventoryShiftClickTest { + + static { + MinecraftServer.init(); + } + + @Test + public void testNoChanges() { + assertClick(builder -> builder, new Click.Info.LeftShift(0), builder -> builder); + assertClick(builder -> builder, new Click.Info.RightShift(0), builder -> builder); + } + + @Test + public void testSimpleTransfer() { + assertClick( + builder -> builder.setPlayer(9, ItemStack.of(Material.STONE, 32)), + new Click.Info.LeftShift(SIZE), + builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).setPlayer(9, ItemStack.AIR) + ); + } + + @Test + public void testSeparatedTransfer() { + assertClick( + builder -> builder + .setPlayer(9, ItemStack.of(Material.STONE, 64)) + .set(0, ItemStack.of(Material.STONE, 32)) + .set(1, ItemStack.of(Material.STONE, 32)) + , + new Click.Info.LeftShift(SIZE), + builder -> builder + .setPlayer(9, ItemStack.AIR) + .set(0, ItemStack.of(Material.STONE, 64)) + .set(1, ItemStack.of(Material.STONE, 64)) + + ); + } + + @Test + public void testSeparatedAndNewTransfer() { + assertClick( + builder -> builder + .setPlayer(9, ItemStack.of(Material.STONE, 64)) + .set(0, ItemStack.of(Material.STONE, 32)), + new Click.Info.LeftShift(SIZE), + builder -> builder + .setPlayer(9, ItemStack.AIR) + .set(0, ItemStack.of(Material.STONE, 64)) + .set(1, ItemStack.of(Material.STONE, 32)) + + ); + } + + @Test + public void testPartialTransfer() { + assertClick( + builder -> builder + .setPlayer(9, ItemStack.of(Material.STONE, 64)) + .set(0, ItemStack.of(Material.STONE, 32)) + .set(1, ItemStack.of(Material.DIRT)) + .set(2, ItemStack.of(Material.DIRT)) + .set(3, ItemStack.of(Material.DIRT)) + .set(4, ItemStack.of(Material.DIRT)), + new Click.Info.LeftShift(SIZE), + builder -> builder + .setPlayer(9, ItemStack.of(Material.STONE, 32)) + .set(0, ItemStack.of(Material.STONE, 64)) + + ); + } + + @Test + public void testCannotTransfer() { + assertClick( + builder -> builder + .setPlayer(9, ItemStack.of(Material.STONE, 64)) + .set(0, ItemStack.of(Material.STONE, 64)) + .set(1, ItemStack.of(Material.DIRT)) + .set(2, ItemStack.of(Material.DIRT)) + .set(3, ItemStack.of(Material.DIRT)) + .set(4, ItemStack.of(Material.DIRT)), + new Click.Info.LeftShift(SIZE), // Equivalent to player slot 9 + builder -> builder + ); + + assertClick( + builder -> builder + .setPlayer(9, ItemStack.of(Material.STONE, 64)) + .set(0, ItemStack.of(Material.DIRT)) + .set(1, ItemStack.of(Material.DIRT)) + .set(2, ItemStack.of(Material.DIRT)) + .set(3, ItemStack.of(Material.DIRT)) + .set(4, ItemStack.of(Material.DIRT)), + new Click.Info.LeftShift(SIZE), // Equivalent to player slot 9 + builder -> builder + ); + } + + @Test + public void testPlayerInteraction() { + assertPlayerClick( + builder -> builder.set(9, ItemStack.of(Material.STONE, 32)), + new Click.Info.LeftShift(9), + builder -> builder.set(9, ItemStack.AIR).set(0, ItemStack.of(Material.STONE, 32)) + ); + + assertPlayerClick( + builder -> builder.set(8, ItemStack.of(Material.STONE, 32)), + new Click.Info.LeftShift(8), + builder -> builder.set(8, ItemStack.AIR).set(9, ItemStack.of(Material.STONE, 32)) + ); + + assertPlayerClick( + builder -> builder.set(9, ItemStack.of(Material.IRON_CHESTPLATE)), + new Click.Info.LeftShift(9), + builder -> builder.set(9, ItemStack.AIR).set(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE)) + ); + + assertPlayerClick( + builder -> builder.set(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE)), + new Click.Info.LeftShift(PlayerInventoryUtils.CHESTPLATE_SLOT), + builder -> builder.set(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.AIR).set(9, ItemStack.of(Material.IRON_CHESTPLATE)) + ); + + assertPlayerClick( + builder -> builder.set(9, ItemStack.of(Material.SHIELD)), + new Click.Info.LeftShift(9), + builder -> builder.set(9, ItemStack.AIR).set(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD)) + ); + + assertPlayerClick( + builder -> builder.set(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD)), + new Click.Info.LeftShift(PlayerInventoryUtils.OFF_HAND_SLOT), + builder -> builder.set(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.AIR).set(9, ItemStack.of(Material.SHIELD)) + ); + } + +} From 84ead9b25c20a21a172bd6fb5c6b2f2c52a63b71 Mon Sep 17 00:00:00 2001 From: GoldenStack Date: Thu, 11 Apr 2024 13:12:45 -0500 Subject: [PATCH 02/46] Remove fastutil references in API --- .../server/inventory/TransactionOption.java | 7 ++++--- .../minestom/server/inventory/TransactionType.java | 13 +++++++------ .../server/inventory/click/ClickProcessors.java | 8 ++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/TransactionOption.java b/src/main/java/net/minestom/server/inventory/TransactionOption.java index 4fe1417ca..d39109de0 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionOption.java +++ b/src/main/java/net/minestom/server/inventory/TransactionOption.java @@ -1,10 +1,11 @@ package net.minestom.server.inventory; import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Map; + @FunctionalInterface public interface TransactionOption { @@ -37,10 +38,10 @@ public interface TransactionOption { */ TransactionOption DRY_RUN = (inventory, result, itemChangesMap) -> result.isAir(); - @NotNull T fill(@NotNull Inventory inventory, @NotNull ItemStack result, @NotNull Int2ObjectMap itemChangesMap); + @NotNull T fill(@NotNull Inventory inventory, @NotNull ItemStack result, @NotNull Map itemChangesMap); default @NotNull T fill(@NotNull TransactionType type, @NotNull Inventory inventory, @NotNull ItemStack itemStack) { - Pair> result = type.process(itemStack, inventory::getItemStack); + Pair> result = type.process(itemStack, inventory::getItemStack); return fill(inventory, result.left(), result.right()); } } diff --git a/src/main/java/net/minestom/server/inventory/TransactionType.java b/src/main/java/net/minestom/server/inventory/TransactionType.java index 2ab3e7c3d..73364b519 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionType.java +++ b/src/main/java/net/minestom/server/inventory/TransactionType.java @@ -7,6 +7,7 @@ import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; import java.util.List; +import java.util.Map; import java.util.function.IntFunction; /** @@ -42,11 +43,11 @@ public interface TransactionType { static @NotNull TransactionType join(@NotNull TransactionType first, @NotNull TransactionType second) { return (item, getter) -> { // Calculate results - Pair> f = first.process(item, getter); - Pair> s = second.process(f.left(), getter); + Pair> f = first.process(item, getter); + Pair> s = second.process(f.left(), getter); // Join results - Int2ObjectMap map = new Int2ObjectArrayMap<>(); + Map map = new Int2ObjectArrayMap<>(); map.putAll(f.right()); map.putAll(s.right()); return Pair.of(s.left(), map); @@ -78,9 +79,9 @@ public interface TransactionType { /** * Processes the provided item into the given inventory. * @param itemStack the item to process - * @param inventory the inventory function; must support #get and #put operations. - * @return the remaining portion of the processed item + * @param inventory the inventory function + * @return the remaining portion of the processed item, as well as the changes */ - @NotNull Pair> process(@NotNull ItemStack itemStack, @NotNull IntFunction inventory); + @NotNull Pair> process(@NotNull ItemStack itemStack, @NotNull IntFunction inventory); } diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index ec38f5b77..7a159b0fb 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -1,7 +1,6 @@ package net.minestom.server.inventory.click; import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minestom.server.inventory.TransactionOperator; import net.minestom.server.inventory.TransactionType; import net.minestom.server.item.ItemStack; @@ -11,6 +10,7 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.function.BiFunction; import java.util.stream.IntStream; @@ -70,7 +70,7 @@ public final class ClickProcessors { slots = new ArrayList<>(slots); slots.removeIf(i -> i == slot); - Pair> result = TransactionType.add(slots, slots).process(clicked, getter::get); + Pair> result = TransactionType.add(slots, slots).process(clicked, getter::get); Click.Setter setter = getter.setter(); result.right().forEach(setter::set); @@ -86,7 +86,7 @@ public final class ClickProcessors { var unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); - Pair> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); + Pair> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); Click.Setter setter = getter.setter(); result.right().forEach(setter::set); @@ -99,7 +99,7 @@ public final class ClickProcessors { var cursor = getter.cursor(); if (cursor.isAir()) return Click.Result.nothing(); - Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); + Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); Click.Setter setter = getter.setter(); result.right().forEach(setter::set); From a0c915a5c97497ad8c21d04bf555e8f8d442470a Mon Sep 17 00:00:00 2001 From: themode Date: Thu, 11 Apr 2024 21:31:40 +0200 Subject: [PATCH 03/46] Style change --- .../server/inventory/click/Click.java | 70 ++++++++++--------- .../inventory/click/ClickProcessors.java | 67 +++++++++--------- .../utils/inventory/PlayerInventoryUtils.java | 14 ++-- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 159a00e3f..31da28e7a 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -22,19 +22,35 @@ public final class Click { * The inventory used should be known from context. */ public sealed interface Info { - record Left(int slot) implements Info {} record Right(int slot) implements Info {} - record Middle(int slot) implements Info {} // Creative only + record Middle(int slot) implements Info { + // Creative only + } record LeftShift(int slot) implements Info {} record RightShift(int slot) implements Info {} record Double(int slot) implements Info {} - record LeftDrag(List slots) implements Info {} - record RightDrag(List slots) implements Info {} - record MiddleDrag(List slots) implements Info {} // Creative only + record LeftDrag(List slots) implements Info { + public LeftDrag { + slots = List.copyOf(slots); + } + } + + record RightDrag(List slots) implements Info { + public RightDrag { + slots = List.copyOf(slots); + } + } + + record MiddleDrag(List slots) implements Info { + // Creative only + public MiddleDrag { + slots = List.copyOf(slots); + } + } record LeftDropCursor() implements Info {} record RightDropCursor() implements Info {} @@ -47,14 +63,12 @@ public final class Click { record CreativeSetItem(int slot, @NotNull ItemStack item) implements Info {} record CreativeDropItem(@NotNull ItemStack item) implements Info {} - } /** * Preprocesses click packets, turning them into {@link Info} instances for further processing. */ public static final class Preprocessor { - private final List leftDrag = new ArrayList<>(); private final List rightDrag = new ArrayList<>(); private final List middleDrag = new ArrayList<>(); @@ -69,8 +83,8 @@ public final class Click { * Processes the provided click packet, turning it into a {@link Info}. * This will do simple verification of the packet before sending it to {@link #process(ClientClickWindowPacket.ClickType, int, byte, boolean)}. * - * @param packet the raw click packet - * @param isCreative whether or not the player is in creative mode (used for ignoring some actions) + * @param packet the raw click packet + * @param isCreative whether the player is in creative mode (used for ignoring some actions) * @return the information about the click, or nothing if there was no immediately usable information */ public @Nullable Click.Info process(@NotNull ClientClickWindowPacket packet, @NotNull Inventory inventory, boolean isCreative) { @@ -81,25 +95,24 @@ public final class Click { int slot = inventory instanceof PlayerInventory ? PlayerInventoryUtils.protocolToMinestom(originalSlot) : originalSlot; if (originalSlot == -999) slot = -999; - boolean creativeRequired = switch (type) { + final boolean creativeRequired = switch (type) { case CLONE -> true; case QUICK_CRAFT -> button == 8 || button == 9 || button == 10; default -> false; }; - if (creativeRequired && !isCreative) return null; - int maxSize = inventory.getSize() + (inventory instanceof PlayerInventory ? 0 : PlayerInventoryUtils.INNER_SIZE); + final int maxSize = inventory.getSize() + (inventory instanceof PlayerInventory ? 0 : PlayerInventoryUtils.INNER_SIZE); return process(type, slot, button, slot >= 0 && slot < maxSize); } /** * Processes a packet into click info. * - * @param type the type of the click - * @param slot the clicked slot - * @param button the sent button - * @param valid whether or not {@code slot} fits within the inventory (may be unused, depending on click) + * @param type the type of the click + * @param slot the clicked slot + * @param button the sent button + * @param valid whether {@code slot} fits within the inventory (may be unused, depending on click) * @return the information about the click, or nothing if there was no immediately usable information */ public @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, @@ -182,7 +195,6 @@ public final class Click { public record Getter(@NotNull IntFunction main, @NotNull IntFunction player, @NotNull ItemStack cursor, int mainSize) { - public @NotNull ItemStack get(int slot) { if (slot < mainSize()) { return main.apply(slot); @@ -196,8 +208,7 @@ public final class Click { } } - public static class Setter { - + public static final class Setter { private final Map main = new HashMap<>(); private final Map player = new HashMap<>(); private @Nullable ItemStack cursor; @@ -237,37 +248,32 @@ public final class Click { public @NotNull Click.Result build() { return new Result(main, player, cursor, sideEffect); } - } /** * Stores changes that occurred or will occur as the result of a click. - * @param changes the map of changes that will occur to the inventory + * + * @param changes the map of changes that will occur to the inventory * @param playerInventoryChanges the map of changes that will occur to the player inventory - * @param newCursorItem the player's cursor item after this click. Null indicates no change - * @param sideEffects the side effects of this click + * @param newCursorItem the player's cursor item after this click. Null indicates no change + * @param sideEffects the side effects of this click */ - public record Result(@NotNull Map changes, @NotNull Map playerInventoryChanges, + public record Result(@NotNull Map changes, + @NotNull Map playerInventoryChanges, @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { - - public static @NotNull Result nothing() { - return new Result(Map.of(), Map.of(), null, null); - } + public static final Result NOTHING = new Result(Map.of(), Map.of(), null, null); public Result { changes = Map.copyOf(changes); playerInventoryChanges = Map.copyOf(playerInventoryChanges); } - } /** * Represents side effects that may occur as the result of an inventory click. */ public sealed interface SideEffect { - - record DropFromPlayer(@NotNull List items) implements SideEffect { - + record DropFromPlayer(@NotNull List<@NotNull ItemStack> items) implements SideEffect { public DropFromPlayer { items = List.copyOf(items); } diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index 7a159b0fb..3a5ae9d65 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -18,12 +18,11 @@ import java.util.stream.IntStream; * Provides standard implementations of most click functions. */ public final class ClickProcessors { - private static final @NotNull StackingRule RULE = StackingRule.get(); public static @NotNull Click.Result leftClick(int slot, @NotNull Click.Getter getter) { - ItemStack cursor = getter.cursor(); - ItemStack clickedItem = getter.get(slot); + final ItemStack cursor = getter.cursor(); + final ItemStack clickedItem = getter.get(slot); Pair pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); if (pair != null) { // Stackable items, combine their counts @@ -31,24 +30,23 @@ public final class ClickProcessors { } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them return getter.setter().set(slot, cursor).cursor(clickedItem).build(); } else { - return Click.Result.nothing(); + return Click.Result.NOTHING; } } public static @NotNull Click.Result rightClick(int slot, @NotNull Click.Getter getter) { - ItemStack cursor = getter.cursor(); - ItemStack clickedItem = getter.get(slot); - - if (cursor.isAir() && clickedItem.isAir()) return Click.Result.nothing(); // Both are air, no changes + final ItemStack cursor = getter.cursor(); + final ItemStack clickedItem = getter.get(slot); + if (cursor.isAir() && clickedItem.isAir()) return Click.Result.NOTHING; // Both are air, no changes if (cursor.isAir()) { // Take half (rounded up) of the clicked item int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); Pair cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); - return cursorSlot == null ? Click.Result.nothing() : + return cursorSlot == null ? Click.Result.NOTHING : getter.setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build(); } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over Pair slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); - return slotCursor == null ? Click.Result.nothing() : + return slotCursor == null ? Click.Result.NOTHING : getter.setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build(); } else { // Two existing of items of different types, so switch return getter.setter().cursor(clickedItem).set(slot, cursor).build(); @@ -60,12 +58,12 @@ public final class ClickProcessors { if (getter.cursor().isAir() && !item.isAir()) { return getter.setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); } else { - return Click.Result.nothing(); + return Click.Result.NOTHING; } } public static @NotNull Click.Result shiftClick(int slot, @NotNull List slots, @NotNull Click.Getter getter) { - ItemStack clicked = getter.get(slot); + final ItemStack clicked = getter.get(slot); slots = new ArrayList<>(slots); slots.removeIf(i -> i == slot); @@ -80,8 +78,8 @@ public final class ClickProcessors { } public static @NotNull Click.Result doubleClick(@NotNull List slots, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - if (cursor.isAir()) Click.Result.nothing(); + final ItemStack cursor = getter.cursor(); + if (cursor.isAir()) return Click.Result.NOTHING; var unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); @@ -96,8 +94,8 @@ public final class ClickProcessors { } public static @NotNull Click.Result dragClick(int countPerSlot, @NotNull List slots, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - if (cursor.isAir()) return Click.Result.nothing(); + final ItemStack cursor = getter.cursor(); + if (cursor.isAir()) return Click.Result.NOTHING; Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); Click.Setter setter = getter.setter(); @@ -109,25 +107,22 @@ public final class ClickProcessors { } public static @NotNull Click.Result middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - + final ItemStack cursor = getter.cursor(); Click.Setter setter = getter.setter(); - for (int slot : slots) { if (getter.get(slot).isAir()) { setter.set(slot, cursor); } } - return setter.build(); } public static @NotNull Click.Result dropFromCursor(int amount, @NotNull Click.Getter getter) { - var cursor = getter.cursor(); - if (cursor.isAir()) Click.Result.nothing(); // Do nothing + final ItemStack cursor = getter.cursor(); + if (cursor.isAir()) return Click.Result.NOTHING; // Do nothing var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); - if (pair == null) return Click.Result.nothing(); + if (pair == null) return Click.Result.NOTHING; return getter.setter().cursor(pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) @@ -135,11 +130,11 @@ public final class ClickProcessors { } public static @NotNull Click.Result dropFromSlot(int slot, int amount, @NotNull Click.Getter getter) { - var item = getter.get(slot); - if (item.isAir()) return Click.Result.nothing(); // Do nothing + final ItemStack item = getter.get(slot); + if (item.isAir()) return Click.Result.NOTHING; // Do nothing var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); - if (pair == null) return Click.Result.nothing(); + if (pair == null) return Click.Result.NOTHING; return getter.setter().set(slot, pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) @@ -150,9 +145,10 @@ public final class ClickProcessors { * Handles clicks, given a shift click provider and a double click provider.
* When shift clicks or double clicks need to be handled, the slots provided from the relevant handler will be * checked in their given order.
- * For example, double clicking will collect items of the same type as the cursor; the slots provided by the double + * For example, double-clicking will collect items of the same type as the cursor; the slots provided by the double * click slot provider will be checked sequentially and used if they have the same type as - * @param shiftClickSlots the shift click slot supplier + * + * @param shiftClickSlots the shift click slot supplier * @param doubleClickSlots the double click slot supplier */ public static @NotNull BiFunction standard(@NotNull SlotSuggestor shiftClickSlots, @NotNull SlotSuggestor doubleClickSlots) { @@ -173,18 +169,18 @@ public final class ClickProcessors { case Click.Info.DropSlot(int slot, boolean all) -> dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter); case Click.Info.LeftDropCursor() -> dropFromCursor(getter.cursor().amount(), getter); case Click.Info.RightDropCursor() -> dropFromCursor(1, getter); - case Click.Info.MiddleDropCursor() -> Click.Result.nothing(); + case Click.Info.MiddleDropCursor() -> Click.Result.NOTHING; case Click.Info.HotbarSwap(int hotbarSlot, int clickedSlot) -> { var hotbarItem = getter.player().apply(hotbarSlot); var selectedItem = getter.get(clickedSlot); - if (hotbarItem.equals(selectedItem)) yield Click.Result.nothing(); + if (hotbarItem.equals(selectedItem)) yield Click.Result.NOTHING; yield getter.setter().setPlayer(hotbarSlot, selectedItem).set(clickedSlot, hotbarItem).build(); } case Click.Info.OffhandSwap(int slot) -> { var offhandItem = getter.player().apply(PlayerInventoryUtils.OFF_HAND_SLOT); var selectedItem = getter.get(slot); - if (offhandItem.equals(selectedItem)) yield Click.Result.nothing(); + if (offhandItem.equals(selectedItem)) yield Click.Result.NOTHING; yield getter.setter().setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).set(slot, offhandItem).build(); } @@ -204,16 +200,17 @@ public final class ClickProcessors { /** * Suggests slots to be used for this operation. + * * @param builder the result builder - * @param item the item clicked - * @param slot the slot of the clicked item + * @param item the item clicked + * @param slot the slot of the clicked item * @return the list of slots, in order of priority, to be used for this operation */ - @NotNull IntStream get(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot); + @NotNull + IntStream get(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot); default @NotNull List getList(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot) { return get(builder, item, slot).boxed().toList(); } } - } diff --git a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java index 793753f6e..17fba1f4f 100644 --- a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java +++ b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java @@ -5,11 +5,10 @@ package net.minestom.server.utils.inventory; * Minestom uses different slot IDs for player inventories as the Minecraft protocol uses a strange system (e.g. the * crafting result is the first slot).
* These can be mapped 1:1 to and from protocol slots using {@link #minestomToProtocol(int)} and {@link #protocolToMinestom(int)}.
- * + *

* Read about protocol slot IDs here. */ public final class PlayerInventoryUtils { - public static final int INVENTORY_SIZE = 46; public static final int INNER_SIZE = 36; @@ -28,12 +27,12 @@ public final class PlayerInventoryUtils { public static final int OFF_HAND_SLOT = 45; private PlayerInventoryUtils() { - } /** * Converts a Minestom slot ID to a Minecraft protocol slot ID.
* This is the inverse of {@link #protocolToMinestom(int)}. + * * @param slot the internal slot ID to convert * @return the protocol slot ID, or -1 if the given slot could not be converted */ @@ -64,6 +63,7 @@ public final class PlayerInventoryUtils { /** * Converts a Minecraft protocol slot ID to a Minestom slot ID.
* This is the inverse of {@link #minestomToProtocol(int)}. + * * @param slot the protocol slot ID to convert * @return the Minestom slot ID, or -1 if the given slot could not be converted */ @@ -97,7 +97,7 @@ public final class PlayerInventoryUtils { * open.
* This is the inverse of {@link #protocolToMinestom(int, int)}. * - * @param slot the player slot that was interacted with + * @param slot the player slot that was interacted with * @param openInventorySize the size of the inventory opened by the player (not the player's inventory) * @return the protocol slot ID */ @@ -111,14 +111,12 @@ public final class PlayerInventoryUtils { * open.
* This is the inverse of {@link #minestomToProtocol(int, int)}. * - * @param slot the protocol slot ID, situated directly after the slot IDs for the open inventory + * @param slot the protocol slot ID, situated directly after the slot IDs for the open inventory * @param openInventorySize the size of the inventory opened by the player (not the player's inventory) * @return the player slot ID */ public static int protocolToMinestom(int slot, int openInventorySize) { if (slot < openInventorySize) return -1; - return PlayerInventoryUtils.protocolToMinestom(slot - openInventorySize + PROTOCOL_OFFSET); } - -} \ No newline at end of file +} From a350c29296c9bbb85322cb38ee68d11af5f96058 Mon Sep 17 00:00:00 2001 From: themode Date: Thu, 11 Apr 2024 22:00:20 +0200 Subject: [PATCH 04/46] Remove Click's Inventory dependency --- .../server/inventory/PlayerInventory.java | 2 +- .../server/inventory/click/Click.java | 38 ++++++++++--------- .../server/listener/WindowListener.java | 14 ++++--- .../server/inventory/click/ClickUtils.java | 12 +++--- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index d7a08bed7..5fc4a9d37 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -97,7 +97,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { /** * Sets the cursor item for all viewers of this inventory. * @param cursorItem the new item (will not update if same as current) - * @param sendPacket whether or not to send a packet + * @param sendPacket whether to send a packet */ public void setCursorItem(@NotNull ItemStack cursorItem, boolean sendPacket) { if (this.cursorItem.equals(cursorItem)) return; diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 31da28e7a..7623a6d4b 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -1,7 +1,5 @@ package net.minestom.server.inventory.click; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; import net.minestom.server.utils.inventory.PlayerInventoryUtils; @@ -74,9 +72,9 @@ public final class Click { private final List middleDrag = new ArrayList<>(); public void clearCache() { - leftDrag.clear(); - rightDrag.clear(); - middleDrag.clear(); + this.leftDrag.clear(); + this.rightDrag.clear(); + this.middleDrag.clear(); } /** @@ -87,23 +85,28 @@ public final class Click { * @param isCreative whether the player is in creative mode (used for ignoring some actions) * @return the information about the click, or nothing if there was no immediately usable information */ - public @Nullable Click.Info process(@NotNull ClientClickWindowPacket packet, @NotNull Inventory inventory, boolean isCreative) { - final int originalSlot = packet.slot(); + public @Nullable Click.Info processPlayerClick(@NotNull ClientClickWindowPacket packet, boolean isCreative) { + if (requireCreative(packet) && !isCreative) return null; + final int slot = packet.slot() != -999 ? PlayerInventoryUtils.protocolToMinestom(packet.slot()) : -999; + final int maxSize = PlayerInventoryUtils.INNER_SIZE; + return process(packet.clickType(), slot, packet.button(), slot >= 0 && slot < maxSize); + } + + public @Nullable Click.Info processContainerClick(@NotNull ClientClickWindowPacket packet, int inventorySize, boolean isCreative) { + if (requireCreative(packet) && !isCreative) return null; + final int slot = packet.slot(); + final int maxSize = inventorySize + PlayerInventoryUtils.INNER_SIZE; + return process(packet.clickType(), slot, packet.button(), slot >= 0 && slot < maxSize); + } + + private boolean requireCreative(ClientClickWindowPacket packet) { final byte button = packet.button(); final ClientClickWindowPacket.ClickType type = packet.clickType(); - - int slot = inventory instanceof PlayerInventory ? PlayerInventoryUtils.protocolToMinestom(originalSlot) : originalSlot; - if (originalSlot == -999) slot = -999; - - final boolean creativeRequired = switch (type) { + return switch (type) { case CLONE -> true; case QUICK_CRAFT -> button == 8 || button == 9 || button == 10; default -> false; }; - if (creativeRequired && !isCreative) return null; - - final int maxSize = inventory.getSize() + (inventory instanceof PlayerInventory ? 0 : PlayerInventoryUtils.INNER_SIZE); - return process(type, slot, button, slot >= 0 && slot < maxSize); } /** @@ -115,7 +118,7 @@ public final class Click { * @param valid whether {@code slot} fits within the inventory (may be unused, depending on click) * @return the information about the click, or nothing if there was no immediately usable information */ - public @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, + private @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, int slot, byte button, boolean valid) { return switch (type) { case PICKUP -> { @@ -190,7 +193,6 @@ public final class Click { case PICKUP_ALL -> valid ? new Info.Double(slot) : null; }; } - } public record Getter(@NotNull IntFunction main, @NotNull IntFunction player, diff --git a/src/main/java/net/minestom/server/listener/WindowListener.java b/src/main/java/net/minestom/server/listener/WindowListener.java index 357bc1771..1014c65ec 100644 --- a/src/main/java/net/minestom/server/listener/WindowListener.java +++ b/src/main/java/net/minestom/server/listener/WindowListener.java @@ -5,6 +5,7 @@ import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.inventory.InventoryButtonClickEvent; import net.minestom.server.event.inventory.InventoryCloseEvent; import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.click.Click; import net.minestom.server.network.packet.client.common.ClientPongPacket; import net.minestom.server.network.packet.client.play.ClientClickWindowButtonPacket; import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; @@ -15,15 +16,17 @@ public class WindowListener { public static void clickWindowListener(ClientClickWindowPacket packet, Player player) { final int windowId = packet.windowId(); - final Inventory inventory = windowId == 0 ? player.getInventory() : player.getOpenInventory(); + final boolean playerInventory = windowId == 0; + final Inventory inventory = playerInventory ? player.getInventory() : player.getOpenInventory(); // Prevent some invalid packets if (inventory == null || packet.slot() == -1) return; - var info = player.clickPreprocessor().process(packet, inventory, player.isCreative()); - if (info != null) { - inventory.handleClick(player, info); - } + Click.Preprocessor preprocessor = player.clickPreprocessor(); + final Click.Info info = playerInventory ? + preprocessor.processPlayerClick(packet, player.isCreative()) : + preprocessor.processContainerClick(packet, inventory.getSize(), player.isCreative()); + if (info != null) inventory.handleClick(player, info); // (Why is the ping packet necessary?) player.sendPacket(new PingPacket((1 << 30) | (windowId << 16))); @@ -55,5 +58,4 @@ public class WindowListener { InventoryButtonClickEvent event = new InventoryButtonClickEvent(openInventory, player, packet.buttonId()); EventDispatcher.call(event); } - } diff --git a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java index ef2797cdc..0e2d5b75f 100644 --- a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java +++ b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java @@ -18,8 +18,7 @@ import java.util.function.UnaryOperator; import static org.junit.jupiter.api.Assertions.assertEquals; -public class ClickUtils { - +public final class ClickUtils { public static final @NotNull InventoryType TYPE = InventoryType.HOPPER; public static final int SIZE = TYPE.getSize(); // Default hopper size @@ -35,7 +34,8 @@ public class ClickUtils { public static @NotNull Player createPlayer() { return new Player(UUID.randomUUID(), "TestPlayer", new PlayerConnection() { @Override - public void sendPacket(@NotNull SendablePacket packet) {} + public void sendPacket(@NotNull SendablePacket packet) { + } @Override public @NotNull SocketAddress getRemoteAddress() { @@ -43,7 +43,8 @@ public class ClickUtils { } @Override - public void disconnect() {} + public void disconnect() { + } }); } @@ -66,7 +67,7 @@ public class ClickUtils { } public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { - assertEquals(info, preprocessor.process(packet, createInventory(), player.isCreative())); + assertEquals(info, preprocessor.processContainerClick(packet, createInventory().getSize(), player.isCreative())); } public static void assertProcessed(@NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { @@ -80,5 +81,4 @@ public class ClickUtils { public static @NotNull ClientClickWindowPacket clickPacket(@NotNull ClientClickWindowPacket.ClickType type, int windowId, int button, int slot) { return new ClientClickWindowPacket((byte) windowId, 0, (short) slot, (byte) button, type, List.of(), ItemStack.AIR); } - } From 401d5b57a77a5985da0f1db0680411258d98a4f8 Mon Sep 17 00:00:00 2001 From: themode Date: Thu, 11 Apr 2024 22:11:00 +0200 Subject: [PATCH 05/46] More style --- .../server/inventory/ContainerInventory.java | 12 ++++-------- .../server/inventory/PlayerInventory.java | 16 ++++++---------- .../server/inventory/TransactionOption.java | 4 ++-- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/ContainerInventory.java b/src/main/java/net/minestom/server/inventory/ContainerInventory.java index 8ae0274c6..a37caeb24 100644 --- a/src/main/java/net/minestom/server/inventory/ContainerInventory.java +++ b/src/main/java/net/minestom/server/inventory/ContainerInventory.java @@ -36,23 +36,20 @@ public non-sealed class ContainerInventory extends InventoryImpl { InventoryPreClickEvent preClickEvent = new InventoryPreClickEvent(playerInventory, inventory, player, info); EventDispatcher.call(preClickEvent); - - Click.Info newInfo = preClickEvent.getClickInfo(); - if (!preClickEvent.isCancelled()) { + final Click.Info newInfo = preClickEvent.getClickInfo(); Click.Getter getter = new Click.Getter(inventory::getItemStack, playerInventory::getItemStack, playerInventory.getCursorItem(), inventory.getSize()); - Click.Result changes = processor.apply(newInfo, getter); + final Click.Result changes = processor.apply(newInfo, getter); InventoryClickEvent clickEvent = new InventoryClickEvent(playerInventory, inventory, player, newInfo, changes); EventDispatcher.call(clickEvent); if (!clickEvent.isCancelled()) { - Click.Result newChanges = clickEvent.getChanges(); + final Click.Result newChanges = clickEvent.getChanges(); apply(newChanges, player, inventory); - var postClickEvent = new InventoryPostClickEvent(player, inventory, newInfo, newChanges); - EventDispatcher.call(postClickEvent); + EventDispatcher.call(new InventoryPostClickEvent(player, inventory, newInfo, newChanges)); if (!info.equals(newInfo) || !changes.equals(newChanges)) { inventory.update(player); @@ -168,5 +165,4 @@ public non-sealed class ContainerInventory extends InventoryImpl { protected void sendProperty(@NotNull InventoryProperty property, short value) { sendPacketToViewers(new WindowPropertyPacket(getWindowId(), property.getProperty(), value)); } - } diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index 5fc4a9d37..1d75c6977 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -80,6 +80,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { /** * Gets the cursor item of this inventory + * * @return the cursor item that is shared between all viewers */ public @NotNull ItemStack getCursorItem() { @@ -88,6 +89,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { /** * Sets the cursor item for all viewers of this inventory. + * * @param cursorItem the new item (will not update if same as current) */ public void setCursorItem(@NotNull ItemStack cursorItem) { @@ -96,6 +98,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { /** * Sets the cursor item for all viewers of this inventory. + * * @param cursorItem the new item (will not update if same as current) * @param sendPacket whether to send a packet */ @@ -109,14 +112,12 @@ public non-sealed class PlayerInventory extends InventoryImpl { lock.unlock(); } - if (!sendPacket) return; - sendPacketToViewers(SetSlotPacket.createCursorPacket(cursorItem)); + if (sendPacket) sendPacketToViewers(SetSlotPacket.createCursorPacket(cursorItem)); } @Override public void updateSlot(int slot, @NotNull ItemStack itemStack) { SetSlotPacket defaultPacket = new SetSlotPacket(getWindowId(), 0, (short) PlayerInventoryUtils.minestomToProtocol(slot), itemStack); - for (Player player : getViewers()) { Inventory open = player.getOpenInventory(); if (open != null && slot >= 0 && slot < INNER_SIZE) { @@ -136,11 +137,9 @@ public non-sealed class PlayerInventory extends InventoryImpl { public void update(@NotNull Player player) { ItemStack[] local = getItemStacks(); ItemStack[] mapped = new ItemStack[getSize()]; - for (int slot = 0; slot < getSize(); slot++) { mapped[PlayerInventoryUtils.minestomToProtocol(slot)] = local[slot]; } - player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(mapped), getCursorItem())); } @@ -154,7 +153,6 @@ public non-sealed class PlayerInventory extends InventoryImpl { EventDispatcher.call(entityEquipEvent); itemStack = entityEquipEvent.getEquippedItem(); } - super.UNSAFE_itemInsert(slot, itemStack); } @@ -163,8 +161,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { lock.lock(); try { super.clear(); - - for (var player : getViewers()) { + for (Player player : getViewers()) { player.sendPacketToViewersAndSelf(player.getEquipmentsPacket()); } } finally { @@ -178,7 +175,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { (getter, item, slot) -> { List slots = new ArrayList<>(); - var equipmentSlot = item.material().registry().equipmentSlot(); + final EquipmentSlot equipmentSlot = item.material().registry().equipmentSlot(); if (equipmentSlot != null && slot != equipmentSlot.armorSlot()) { slots.add(equipmentSlot.armorSlot()); } @@ -229,5 +226,4 @@ public non-sealed class PlayerInventory extends InventoryImpl { public @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption option) { return processItemStack(itemStack, TransactionType.take(TAKE_SLOTS), option); } - } diff --git a/src/main/java/net/minestom/server/inventory/TransactionOption.java b/src/main/java/net/minestom/server/inventory/TransactionOption.java index d39109de0..707822439 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionOption.java +++ b/src/main/java/net/minestom/server/inventory/TransactionOption.java @@ -19,7 +19,7 @@ public interface TransactionOption { }; /** - * Performs the operation atomically (only if the operation resulted in air), returning whether or not the operation + * Performs the operation atomically (only if the operation resulted in air), returning whether the operation * was performed. */ TransactionOption ALL_OR_NOTHING = (inventory, result, itemChangesMap) -> { @@ -34,7 +34,7 @@ public interface TransactionOption { }; /** - * Discards the result of the operation, returning whether or not the operation could have finished. + * Discards the result of the operation, returning whether the operation could have finished. */ TransactionOption DRY_RUN = (inventory, result, itemChangesMap) -> result.isAir(); From f33565a82e10e820edd591c6daf76a6661a0cf27 Mon Sep 17 00:00:00 2001 From: themode Date: Thu, 11 Apr 2024 22:42:02 +0200 Subject: [PATCH 06/46] Store all processors in ClickProcessors --- .../server/inventory/ContainerInventory.java | 16 ++- .../server/inventory/InventoryImpl.java | 21 ---- .../server/inventory/PlayerInventory.java | 40 +----- .../inventory/click/ClickProcessors.java | 114 +++++++++++++++++- .../inventory/type/FurnaceInventory.java | 32 ----- 5 files changed, 121 insertions(+), 102 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/ContainerInventory.java b/src/main/java/net/minestom/server/inventory/ContainerInventory.java index a37caeb24..2bb6d6041 100644 --- a/src/main/java/net/minestom/server/inventory/ContainerInventory.java +++ b/src/main/java/net/minestom/server/inventory/ContainerInventory.java @@ -7,6 +7,7 @@ import net.minestom.server.event.inventory.InventoryClickEvent; import net.minestom.server.event.inventory.InventoryPostClickEvent; import net.minestom.server.event.inventory.InventoryPreClickEvent; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.ClickProcessors; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.OpenWindowPacket; import net.minestom.server.network.packet.server.play.WindowPropertyPacket; @@ -14,7 +15,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiFunction; /** * Represents an inventory which can be viewed by a collection of {@link Player}. @@ -26,12 +26,14 @@ public non-sealed class ContainerInventory extends InventoryImpl { /** * Processes a click, returning a result. This will call events for the click. + * * @param inventory the clicked inventory (could be a player inventory) - * @param player the player who clicked - * @param info the click info describing the click + * @param player the player who clicked + * @param info the click info describing the click * @return the click result, or null if the click did not occur */ - public static @Nullable Click.Result handleClick(@NotNull Inventory inventory, @NotNull Player player, @NotNull Click.Info info, @NotNull BiFunction processor) { + public static @Nullable Click.Result handleClick(@NotNull Inventory inventory, @NotNull Player player, @NotNull Click.Info info, + @NotNull ClickProcessors.InventoryProcessor processor) { PlayerInventory playerInventory = player.getInventory(); InventoryPreClickEvent preClickEvent = new InventoryPreClickEvent(playerInventory, inventory, player, info); @@ -141,6 +143,12 @@ public non-sealed class ContainerInventory extends InventoryImpl { update(); } + @Override + public @Nullable Click.Result handleClick(@NotNull Player player, Click.@NotNull Info info) { + return ContainerInventory.handleClick(this, player, info, + ClickProcessors.PROCESSORS_MAP.getOrDefault(inventoryType, ClickProcessors.GENERIC_PROCESSOR)); + } + @Override public byte getWindowId() { return id; diff --git a/src/main/java/net/minestom/server/inventory/InventoryImpl.java b/src/main/java/net/minestom/server/inventory/InventoryImpl.java index 9a3269369..675f0f925 100644 --- a/src/main/java/net/minestom/server/inventory/InventoryImpl.java +++ b/src/main/java/net/minestom/server/inventory/InventoryImpl.java @@ -3,8 +3,6 @@ package net.minestom.server.inventory; import net.minestom.server.entity.Player; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.inventory.InventoryItemChangeEvent; -import net.minestom.server.inventory.click.Click; -import net.minestom.server.inventory.click.ClickProcessors; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.CloseWindowPacket; import net.minestom.server.network.packet.server.play.SetSlotPacket; @@ -13,7 +11,6 @@ import net.minestom.server.tag.TagHandler; 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.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; @@ -110,19 +107,6 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(itemStacks), player.getInventory().getCursorItem())); } - @Override - public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) { - var processor = ClickProcessors.standard( - (builder, item, slot) -> slot >= getSize() ? - IntStream.range(0, getSize()) : - PlayerInventory.getInnerShiftClickSlots(getSize()), - (builder, item, slot) -> IntStream.concat( - IntStream.range(0, getSize()), - PlayerInventory.getInnerDoubleClickSlots(getSize()) - )); - return ContainerInventory.handleClick(this, player, info, processor); - } - @Override public @NotNull ItemStack getItemStack(int slot) { return (ItemStack) ITEM_UPDATER.getVolatile(itemStacks, slot); @@ -168,7 +152,6 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent */ protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) { lock.lock(); - try { ItemStack previous = itemStacks[slot]; if (itemStack.equals(previous)) return; // Avoid sending updates if the item has not changed @@ -189,7 +172,6 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent @Override public void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) { lock.lock(); - try { var currentItem = getItemStack(slot); setItemStack(slot, operator.apply(currentItem)); @@ -201,12 +183,10 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent @Override public void clear() { lock.lock(); - try { for (Player viewer : getViewers()) { viewer.getInventory().setCursorItem(ItemStack.AIR, false); } - // Clear the item array for (int i = 0; i < size; i++) { safeItemInsert(i, ItemStack.AIR, false); @@ -294,5 +274,4 @@ sealed abstract class InventoryImpl implements Inventory permits ContainerInvent } return result; } - } diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index 1d75c6977..acf89cb85 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -7,15 +7,12 @@ import net.minestom.server.event.item.EntityEquipEvent; import net.minestom.server.inventory.click.Click; import net.minestom.server.inventory.click.ClickProcessors; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -171,42 +168,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { @Override public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) { - var processor = ClickProcessors.standard( - (getter, item, slot) -> { - List slots = new ArrayList<>(); - - final EquipmentSlot equipmentSlot = item.material().registry().equipmentSlot(); - if (equipmentSlot != null && slot != equipmentSlot.armorSlot()) { - slots.add(equipmentSlot.armorSlot()); - } - - if (item.material() == Material.SHIELD && slot != OFF_HAND_SLOT) { - slots.add(OFF_HAND_SLOT); - } - - if (slot < 9 || slot > 35) { - IntStream.range(9, 36).forEach(slots::add); - } - - if (slot < 0 || slot > 8) { - IntStream.range(0, 9).forEach(slots::add); - } - - if (slot == CRAFT_RESULT) { - Collections.reverse(slots); - } - - return slots.stream().mapToInt(i -> i); - }, - (getter, item, slot) -> Stream.of( - IntStream.range(CRAFT_SLOT_1, CRAFT_SLOT_4 + 1), // 1-4 - IntStream.range(HELMET_SLOT, BOOTS_SLOT + 1), // 5-8 - IntStream.range(9, 36), // 9-35 - IntStream.range(0, 9), // 36-44 - IntStream.of(OFF_HAND_SLOT) // 45 - ).flatMapToInt(i -> i) - ); - return ContainerInventory.handleClick(this, player, info, processor); + return ContainerInventory.handleClick(this, player, info, ClickProcessors.PLAYER_PROCESSOR); } public @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot, int heldSlot) { diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index 3a5ae9d65..bf1da5c67 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -1,18 +1,27 @@ package net.minestom.server.inventory.click; import it.unimi.dsi.fastutil.Pair; +import net.minestom.server.entity.EquipmentSlot; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.inventory.TransactionOperator; import net.minestom.server.inventory.TransactionType; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; import net.minestom.server.item.StackingRule; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static java.util.Map.entry; +import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; /** * Provides standard implementations of most click functions. @@ -151,14 +160,17 @@ public final class ClickProcessors { * @param shiftClickSlots the shift click slot supplier * @param doubleClickSlots the double click slot supplier */ - public static @NotNull BiFunction standard(@NotNull SlotSuggestor shiftClickSlots, @NotNull SlotSuggestor doubleClickSlots) { + public static ClickProcessors.@NotNull InventoryProcessor standard(@NotNull SlotSuggestor shiftClickSlots, @NotNull SlotSuggestor doubleClickSlots) { return (info, getter) -> switch (info) { case Click.Info.Left(int slot) -> leftClick(slot, getter); case Click.Info.Right(int slot) -> rightClick(slot, getter); case Click.Info.Middle(int slot) -> middleClick(slot, getter); - case Click.Info.LeftShift(int slot) -> shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter); - case Click.Info.RightShift(int slot) -> shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter); - case Click.Info.Double(int slot) -> doubleClick(doubleClickSlots.getList(getter, getter.get(slot), slot), getter); + case Click.Info.LeftShift(int slot) -> + shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter); + case Click.Info.RightShift(int slot) -> + shiftClick(slot, shiftClickSlots.getList(getter, getter.get(slot), slot), getter); + case Click.Info.Double(int slot) -> + doubleClick(doubleClickSlots.getList(getter, getter.get(slot), slot), getter); case Click.Info.LeftDrag(List slots) -> { int cursorAmount = RULE.getAmount(getter.cursor()); int amount = (int) Math.floor(cursorAmount / (double) slots.size()); @@ -166,7 +178,8 @@ public final class ClickProcessors { } case Click.Info.RightDrag(List slots) -> dragClick(1, slots, getter); case Click.Info.MiddleDrag(List slots) -> middleDragClick(slots, getter); - case Click.Info.DropSlot(int slot, boolean all) -> dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter); + case Click.Info.DropSlot(int slot, boolean all) -> + dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter); case Click.Info.LeftDropCursor() -> dropFromCursor(getter.cursor().amount(), getter); case Click.Info.RightDropCursor() -> dropFromCursor(1, getter); case Click.Info.MiddleDropCursor() -> Click.Result.NOTHING; @@ -185,10 +198,14 @@ public final class ClickProcessors { yield getter.setter().setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).set(slot, offhandItem).build(); } case Click.Info.CreativeSetItem(int slot, ItemStack item) -> getter.setter().set(slot, item).build(); - case Click.Info.CreativeDropItem(ItemStack item) -> getter.setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); + case Click.Info.CreativeDropItem(ItemStack item) -> + getter.setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); }; } + public interface InventoryProcessor extends BiFunction { + } + /** * A generic interface for providing options for clicks like shift clicks and double clicks.
* This addresses the issue of certain click operations only being able to interact with certain slots: for example, @@ -213,4 +230,89 @@ public final class ClickProcessors { return get(builder, item, slot).boxed().toList(); } } + + /** + * Handle player inventory (without any container open). + */ + public static final InventoryProcessor PLAYER_PROCESSOR = ClickProcessors.standard( + (getter, item, slot) -> { + List slots = new ArrayList<>(); + + final EquipmentSlot equipmentSlot = item.material().registry().equipmentSlot(); + if (equipmentSlot != null && slot != equipmentSlot.armorSlot()) { + slots.add(equipmentSlot.armorSlot()); + } + + if (item.material() == Material.SHIELD && slot != OFF_HAND_SLOT) { + slots.add(OFF_HAND_SLOT); + } + + if (slot < 9 || slot > 35) IntStream.range(9, 36).forEach(slots::add); + if (slot < 0 || slot > 8) IntStream.range(0, 9).forEach(slots::add); + + if (slot == CRAFT_RESULT) { + Collections.reverse(slots); + } + + return slots.stream().mapToInt(i -> i); + }, + (getter, item, slot) -> Stream.of( + IntStream.range(CRAFT_SLOT_1, CRAFT_SLOT_4 + 1), // 1-4 + IntStream.range(HELMET_SLOT, BOOTS_SLOT + 1), // 5-8 + IntStream.range(9, 36), // 9-35 + IntStream.range(0, 9), // 36-44 + IntStream.of(OFF_HAND_SLOT) // 45 + ).flatMapToInt(i -> i) + ); + + /** + * Assumes all the container's slots to be accessible. + */ + public static final InventoryProcessor GENERIC_PROCESSOR = ClickProcessors.standard( + (builder, item, slot) -> { + final int size = builder.mainSize(); + return slot >= size ? + IntStream.range(0, size) : + PlayerInventory.getInnerShiftClickSlots(size); + }, + (builder, item, slot) -> { + final int size = builder.mainSize(); + return IntStream.concat( + IntStream.range(0, size), + PlayerInventory.getInnerDoubleClickSlots(size) + ); + }); + + + // SPECIALIZED PROCESSORS DEFINITIONS + + /** + * Client prediction appears to disallow shift clicking into furnace inventories.
+ * Instead: + * - Shift clicks in the inventory go to the player inventory like normal + * - Shift clicks in the hotbar go to the storage + * - Shift clicks in the storage go to the hotbar + */ + public static final InventoryProcessor FURNACE_PROCESSOR = ClickProcessors.standard( + (builder, item, slot) -> { + final int size = builder.mainSize(); + if (slot < size) { + return PlayerInventory.getInnerShiftClickSlots(size); + } else if (slot < size + 27) { + return IntStream.range(27, 36).map(i -> i + size); + } else { + return IntStream.range(0, 27).map(i -> i + size); + } + }, + (builder, item, slot) -> { + final int size = builder.mainSize(); + return IntStream.concat( + IntStream.range(0, size), + PlayerInventory.getInnerDoubleClickSlots(size) + ); + }); + + public static final Map PROCESSORS_MAP = Map.ofEntries( + entry(InventoryType.FURNACE, FURNACE_PROCESSOR) + ); } diff --git a/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java b/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java index 92fc59a03..269da3aa9 100644 --- a/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/FurnaceInventory.java @@ -1,16 +1,10 @@ package net.minestom.server.inventory.type; import net.kyori.adventure.text.Component; -import net.minestom.server.entity.Player; import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryProperty; import net.minestom.server.inventory.InventoryType; -import net.minestom.server.inventory.PlayerInventory; -import net.minestom.server.inventory.click.*; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.stream.IntStream; public class FurnaceInventory extends ContainerInventory { @@ -27,32 +21,6 @@ public class FurnaceInventory extends ContainerInventory { super(InventoryType.FURNACE, title); } - /** - * Client prediction appears to disallow shift clicking into furnace inventories.
- * Instead: - * - Shift clicks in the inventory go to the player inventory like normal - * - Shift clicks in the hotbar go to the storage - * - Shift clicks in the storage go to the hotbar - */ - @Override - public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) { - var processor = ClickProcessors.standard( - (builder, item, slot) -> { - if (slot < getSize()) { - return PlayerInventory.getInnerShiftClickSlots(getSize()); - } else if (slot < getSize() + 27) { - return IntStream.range(27, 36).map(i -> i + getSize()); - } else { - return IntStream.range(0, 27).map(i -> i + getSize()); - } - }, - (builder, item, slot) -> IntStream.concat( - IntStream.range(0, getSize()), - PlayerInventory.getInnerDoubleClickSlots(getSize()) - )); - return ContainerInventory.handleClick(this, player, info, processor); - } - /** * Represents the amount of tick until the fire icon come empty. * From ea7ef09be74254e7c5e845fc5b1f1f965c6a2c02 Mon Sep 17 00:00:00 2001 From: themode Date: Thu, 11 Apr 2024 22:47:47 +0200 Subject: [PATCH 07/46] Move some constants out of PlayerInventory --- .../minestom/server/inventory/PlayerInventory.java | 9 --------- .../server/inventory/click/ClickProcessors.java | 9 ++++----- .../server/utils/inventory/PlayerInventoryUtils.java | 12 ++++++++++++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index acf89cb85..a64743a4d 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -23,15 +23,6 @@ import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; * Represents the inventory of a {@link Player}, retrieved with {@link Player#getInventory()}. */ public non-sealed class PlayerInventory extends InventoryImpl { - - public static @NotNull IntStream getInnerShiftClickSlots(int size) { - return IntStream.range(0, 36).map(i -> i + size); - } - - public static @NotNull IntStream getInnerDoubleClickSlots(int size) { - return IntStream.range(0, 36).map(i -> i + size); - } - private static int getSlotIndex(@NotNull EquipmentSlot slot, int heldSlot) { return switch (slot) { case HELMET, CHESTPLATE, LEGGINGS, BOOTS -> slot.armorSlot(); diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index bf1da5c67..666618b5f 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -3,7 +3,6 @@ package net.minestom.server.inventory.click; import it.unimi.dsi.fastutil.Pair; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.inventory.InventoryType; -import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.inventory.TransactionOperator; import net.minestom.server.inventory.TransactionType; import net.minestom.server.item.ItemStack; @@ -273,13 +272,13 @@ public final class ClickProcessors { final int size = builder.mainSize(); return slot >= size ? IntStream.range(0, size) : - PlayerInventory.getInnerShiftClickSlots(size); + PlayerInventoryUtils.getInnerShiftClickSlots(size); }, (builder, item, slot) -> { final int size = builder.mainSize(); return IntStream.concat( IntStream.range(0, size), - PlayerInventory.getInnerDoubleClickSlots(size) + PlayerInventoryUtils.getInnerDoubleClickSlots(size) ); }); @@ -297,7 +296,7 @@ public final class ClickProcessors { (builder, item, slot) -> { final int size = builder.mainSize(); if (slot < size) { - return PlayerInventory.getInnerShiftClickSlots(size); + return PlayerInventoryUtils.getInnerShiftClickSlots(size); } else if (slot < size + 27) { return IntStream.range(27, 36).map(i -> i + size); } else { @@ -308,7 +307,7 @@ public final class ClickProcessors { final int size = builder.mainSize(); return IntStream.concat( IntStream.range(0, size), - PlayerInventory.getInnerDoubleClickSlots(size) + PlayerInventoryUtils.getInnerDoubleClickSlots(size) ); }); diff --git a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java index 17fba1f4f..d10663854 100644 --- a/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java +++ b/src/main/java/net/minestom/server/utils/inventory/PlayerInventoryUtils.java @@ -1,6 +1,10 @@ package net.minestom.server.utils.inventory; +import org.jetbrains.annotations.NotNull; + +import java.util.stream.IntStream; + /** * Minestom uses different slot IDs for player inventories as the Minecraft protocol uses a strange system (e.g. the * crafting result is the first slot).
@@ -26,6 +30,14 @@ public final class PlayerInventoryUtils { public static final int BOOTS_SLOT = 44; public static final int OFF_HAND_SLOT = 45; + public static @NotNull IntStream getInnerShiftClickSlots(int size) { + return IntStream.range(0, 36).map(i -> i + size); + } + + public static @NotNull IntStream getInnerDoubleClickSlots(int size) { + return IntStream.range(0, 36).map(i -> i + size); + } + private PlayerInventoryUtils() { } From 511c3d3e2e27c0f4e44891c80db74bc7edf69ff3 Mon Sep 17 00:00:00 2001 From: GoldenStack Date: Fri, 12 Apr 2024 13:21:04 -0500 Subject: [PATCH 08/46] Add Click.Change --- .../server/inventory/ContainerInventory.java | 20 ++++-- .../server/inventory/click/Click.java | 42 +++++-------- .../inventory/click/ClickProcessors.java | 32 +++++----- .../server/inventory/click/ClickUtils.java | 61 ++++++++++++++++--- 4 files changed, 97 insertions(+), 58 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/ContainerInventory.java b/src/main/java/net/minestom/server/inventory/ContainerInventory.java index 2bb6d6041..3510d817c 100644 --- a/src/main/java/net/minestom/server/inventory/ContainerInventory.java +++ b/src/main/java/net/minestom/server/inventory/ContainerInventory.java @@ -11,6 +11,7 @@ import net.minestom.server.inventory.click.ClickProcessors; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.OpenWindowPacket; import net.minestom.server.network.packet.server.play.WindowPropertyPacket; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -72,16 +73,23 @@ public non-sealed class ContainerInventory extends InventoryImpl { } public static void apply(@NotNull Click.Result result, @NotNull Player player, @NotNull Inventory inventory) { - for (var entry : result.changes().entrySet()) { - inventory.setItemStack(entry.getKey(), entry.getValue()); - } + PlayerInventory playerInventory = player.getInventory(); - for (var entry : result.playerInventoryChanges().entrySet()) { - player.getInventory().setItemStack(entry.getKey(), entry.getValue()); + for (var change : result.changes()) { + if (change instanceof Click.Change.Main(int slot, ItemStack item)) { + if (slot < inventory.getSize()) { + inventory.setItemStack(slot, item); + } else { + int converted = PlayerInventoryUtils.protocolToMinestom(slot, inventory.getSize()); + playerInventory.setItemStack(converted, item); + } + } else if (change instanceof Click.Change.Player(int slot, ItemStack item)) { + playerInventory.setItemStack(slot, item); + } } if (result.newCursorItem() != null) { - player.getInventory().setCursorItem(result.newCursorItem()); + playerInventory.setCursorItem(result.newCursorItem()); } if (result.sideEffects() instanceof Click.SideEffect.DropFromPlayer drop) { diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 7623a6d4b..448c6309f 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -7,9 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import java.util.function.IntFunction; @@ -204,36 +202,28 @@ public final class Click { return player.apply(PlayerInventoryUtils.protocolToMinestom(slot, mainSize())); } } + } - public @NotNull Click.Setter setter() { - return new Setter(mainSize); - } + public sealed interface Change { + record Main(int slot, @NotNull ItemStack item) implements Change {} + record Player(int slot, @NotNull ItemStack item) implements Change {} } public static final class Setter { - private final Map main = new HashMap<>(); - private final Map player = new HashMap<>(); + private final List changes = new ArrayList<>(); private @Nullable ItemStack cursor; private @Nullable SideEffect sideEffect; - private final int clickedSize; - - Setter(int clickedSize) { - this.clickedSize = clickedSize; + Setter() { } public @NotNull Setter set(int slot, @NotNull ItemStack item) { - if (slot >= clickedSize) { - int converted = PlayerInventoryUtils.protocolToMinestom(slot, clickedSize); - return setPlayer(converted, item); - } else { - main.put(slot, item); - return this; - } + changes.add(new Change.Main(slot, item)); + return this; } public @NotNull Setter setPlayer(int slot, @NotNull ItemStack item) { - player.put(slot, item); + changes.add(new Change.Player(slot, item)); return this; } @@ -248,26 +238,22 @@ public final class Click { } public @NotNull Click.Result build() { - return new Result(main, player, cursor, sideEffect); + return new Result(changes, cursor, sideEffect); } } /** * Stores changes that occurred or will occur as the result of a click. * - * @param changes the map of changes that will occur to the inventory - * @param playerInventoryChanges the map of changes that will occur to the player inventory + * @param changes the list of changes that will occur * @param newCursorItem the player's cursor item after this click. Null indicates no change * @param sideEffects the side effects of this click */ - public record Result(@NotNull Map changes, - @NotNull Map playerInventoryChanges, - @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { - public static final Result NOTHING = new Result(Map.of(), Map.of(), null, null); + public record Result(@NotNull List changes, @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { + public static final Result NOTHING = new Result(List.of(), null, null); public Result { - changes = Map.copyOf(changes); - playerInventoryChanges = Map.copyOf(playerInventoryChanges); + changes = List.copyOf(changes); } } diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index 666618b5f..cc6892cbf 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -34,9 +34,9 @@ public final class ClickProcessors { Pair pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); if (pair != null) { // Stackable items, combine their counts - return getter.setter().set(slot, pair.left()).cursor(pair.right()).build(); + return new Click.Setter().set(slot, pair.left()).cursor(pair.right()).build(); } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them - return getter.setter().set(slot, cursor).cursor(clickedItem).build(); + return new Click.Setter().set(slot, cursor).cursor(clickedItem).build(); } else { return Click.Result.NOTHING; } @@ -51,20 +51,20 @@ public final class ClickProcessors { int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); Pair cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); return cursorSlot == null ? Click.Result.NOTHING : - getter.setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build(); + new Click.Setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build(); } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over Pair slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); return slotCursor == null ? Click.Result.NOTHING : - getter.setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build(); + new Click.Setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build(); } else { // Two existing of items of different types, so switch - return getter.setter().cursor(clickedItem).set(slot, cursor).build(); + return new Click.Setter().cursor(clickedItem).set(slot, cursor).build(); } } public static @NotNull Click.Result middleClick(int slot, @NotNull Click.Getter getter) { var item = getter.get(slot); if (getter.cursor().isAir() && !item.isAir()) { - return getter.setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); + return new Click.Setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); } else { return Click.Result.NOTHING; } @@ -77,7 +77,7 @@ public final class ClickProcessors { slots.removeIf(i -> i == slot); Pair> result = TransactionType.add(slots, slots).process(clicked, getter::get); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); result.right().forEach(setter::set); return !result.left().equals(clicked) ? @@ -93,7 +93,7 @@ public final class ClickProcessors { var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); Pair> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); result.right().forEach(setter::set); return !result.left().equals(cursor) ? @@ -106,7 +106,7 @@ public final class ClickProcessors { if (cursor.isAir()) return Click.Result.NOTHING; Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); result.right().forEach(setter::set); return !result.left().equals(cursor) ? @@ -116,7 +116,7 @@ public final class ClickProcessors { public static @NotNull Click.Result middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); - Click.Setter setter = getter.setter(); + Click.Setter setter = new Click.Setter(); for (int slot : slots) { if (getter.get(slot).isAir()) { setter.set(slot, cursor); @@ -132,7 +132,7 @@ public final class ClickProcessors { var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); if (pair == null) return Click.Result.NOTHING; - return getter.setter().cursor(pair.right()) + return new Click.Setter().cursor(pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) .build(); } @@ -144,7 +144,7 @@ public final class ClickProcessors { var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); if (pair == null) return Click.Result.NOTHING; - return getter.setter().set(slot, pair.right()) + return new Click.Setter().set(slot, pair.right()) .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) .build(); } @@ -187,18 +187,18 @@ public final class ClickProcessors { var selectedItem = getter.get(clickedSlot); if (hotbarItem.equals(selectedItem)) yield Click.Result.NOTHING; - yield getter.setter().setPlayer(hotbarSlot, selectedItem).set(clickedSlot, hotbarItem).build(); + yield new Click.Setter().set(clickedSlot, hotbarItem).setPlayer(hotbarSlot, selectedItem).build(); } case Click.Info.OffhandSwap(int slot) -> { var offhandItem = getter.player().apply(PlayerInventoryUtils.OFF_HAND_SLOT); var selectedItem = getter.get(slot); if (offhandItem.equals(selectedItem)) yield Click.Result.NOTHING; - yield getter.setter().setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).set(slot, offhandItem).build(); + yield new Click.Setter().set(slot, offhandItem).setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).build(); } - case Click.Info.CreativeSetItem(int slot, ItemStack item) -> getter.setter().set(slot, item).build(); + case Click.Info.CreativeSetItem(int slot, ItemStack item) -> new Click.Setter().set(slot, item).build(); case Click.Info.CreativeDropItem(ItemStack item) -> - getter.setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); + new Click.Setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); }; } diff --git a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java index 0e2d5b75f..bdc9cac7f 100644 --- a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java +++ b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java @@ -8,12 +8,12 @@ import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.net.SocketAddress; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.function.UnaryOperator; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -52,18 +52,63 @@ public final class ClickUtils { var player = createPlayer(); var inventory = createInventory(); - ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); - var changes = inventory.handleClick(player, info); - assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); + var expected = expectedChanges.apply(new Click.Setter()).build(); + + ContainerInventory.apply(initialChanges.apply(new Click.Setter()).build(), player, inventory); + var actual = inventory.handleClick(player, info); + + assertChanges(expected, actual, inventory.getSize()); } public static void assertPlayerClick(@NotNull UnaryOperator initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator expectedChanges) { var player = createPlayer(); var inventory = player.getInventory(); - ContainerInventory.apply(initialChanges.apply(new Click.Setter(inventory.getSize())).build(), player, inventory); - var changes = inventory.handleClick(player, info); - assertEquals(expectedChanges.apply(new Click.Setter(inventory.getSize())).build(), changes); + var expected = expectedChanges.apply(new Click.Setter()).build(); + + ContainerInventory.apply(initialChanges.apply(new Click.Setter()).build(), player, inventory); + var actual = inventory.handleClick(player, info); + + assertChanges(expected, actual, inventory.getSize()); + } + + public static void assertChanges(Click.Result expected, Click.Result actual, int size) { + if (expected == null || actual == null) { + assertEquals(expected, actual); + return; + } + + assertEquals(foldMain(expected.changes(), size), foldMain(actual.changes(), size)); + assertEquals(foldPlayer(expected.changes(), size), foldPlayer(actual.changes(), size)); + + assertEquals(expected.newCursorItem(), actual.newCursorItem()); + assertEquals(expected.sideEffects(), actual.sideEffects()); + } + + private static Map foldMain(List changes, int size) { + Map map = new HashMap<>(); + + for (var change : changes) { + if (change instanceof Click.Change.Main(int slot, ItemStack item) && slot < size) { + map.put(slot, item); + } + } + + return map; + } + + private static Map foldPlayer(List changes, int size) { + Map map = new HashMap<>(); + + for (var change : changes) { + if (change instanceof Click.Change.Main(int slot, ItemStack item) && slot >= size) { + map.put(PlayerInventoryUtils.protocolToMinestom(slot, size), item); + } else if (change instanceof Click.Change.Player(int slot, ItemStack item)) { + map.put(slot, item); + } + } + + return map; } public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { From d417f42b74a76795eaa66487731381f2c854a2c8 Mon Sep 17 00:00:00 2001 From: GoldenStack Date: Sat, 13 Apr 2024 19:36:18 -0500 Subject: [PATCH 09/46] Fix player inventory size usage --- src/main/java/net/minestom/server/inventory/click/Click.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 448c6309f..67f99b99f 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -86,7 +86,7 @@ public final class Click { public @Nullable Click.Info processPlayerClick(@NotNull ClientClickWindowPacket packet, boolean isCreative) { if (requireCreative(packet) && !isCreative) return null; final int slot = packet.slot() != -999 ? PlayerInventoryUtils.protocolToMinestom(packet.slot()) : -999; - final int maxSize = PlayerInventoryUtils.INNER_SIZE; + final int maxSize = PlayerInventoryUtils.INVENTORY_SIZE; return process(packet.clickType(), slot, packet.button(), slot >= 0 && slot < maxSize); } From 755f93444852a46231841c47ed22a4fcaac84505 Mon Sep 17 00:00:00 2001 From: GoldenStack Date: Sat, 13 Apr 2024 21:35:40 -0500 Subject: [PATCH 10/46] Switch Click.Result to List --- .../event/inventory/InventoryClickEvent.java | 10 +- .../inventory/InventoryPostClickEvent.java | 8 +- .../server/inventory/ContainerInventory.java | 42 +++--- .../minestom/server/inventory/Inventory.java | 3 +- .../server/inventory/PlayerInventory.java | 2 +- .../server/inventory/click/Click.java | 62 +------- .../inventory/click/ClickProcessors.java | 133 +++++++++--------- .../server/inventory/click/ClickUtils.java | 77 +++++----- .../type/InventoryCreativeDropItemTest.java | 18 +-- .../type/InventoryCreativeSetItemTest.java | 18 +-- .../click/type/InventoryDoubleClickTest.java | 48 +++---- .../click/type/InventoryDropCursorTest.java | 24 ++-- .../click/type/InventoryDropSlotTest.java | 18 ++- .../click/type/InventoryHotbarSwapTest.java | 14 +- .../click/type/InventoryLeftClickTest.java | 21 +-- .../click/type/InventoryLeftDragTest.java | 69 +++++---- .../click/type/InventoryMiddleClickTest.java | 17 ++- .../click/type/InventoryMiddleDragTest.java | 22 +-- .../click/type/InventoryOffhandSwapTest.java | 16 ++- .../click/type/InventoryRightClickTest.java | 30 ++-- .../click/type/InventoryRightDragTest.java | 31 ++-- .../click/type/InventoryShiftClickTest.java | 126 +++++++++-------- 22 files changed, 390 insertions(+), 419 deletions(-) diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java index ca47f20d1..3a6164bef 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java @@ -9,6 +9,8 @@ import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.inventory.click.Click; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * Called after {@link InventoryPreClickEvent} and before {@link InventoryPostClickEvent}. */ @@ -18,12 +20,12 @@ public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent, private final Inventory inventory; private final Player player; private final Click.Info info; - private Click.Result changes; + private List changes; private boolean cancelled; public InventoryClickEvent(@NotNull PlayerInventory playerInventory, @NotNull Inventory inventory, - @NotNull Player player, @NotNull Click.Info info, @NotNull Click.Result changes) { + @NotNull Player player, @NotNull Click.Info info, @NotNull List changes) { this.playerInventory = playerInventory; this.inventory = inventory; this.player = player; @@ -54,7 +56,7 @@ public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent, * * @return the changes */ - public @NotNull Click.Result getChanges() { + public @NotNull List getChanges() { return changes; } @@ -63,7 +65,7 @@ public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent, * * @param changes the new results */ - public void setChanges(@NotNull Click.Result changes) { + public void setChanges(@NotNull List changes) { this.changes = changes; } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java index 23a29fc2e..86eca2b45 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryPostClickEvent.java @@ -7,6 +7,8 @@ import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.click.Click; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * Called after {@link InventoryClickEvent}, this event cannot be cancelled and items related to the click * are already moved. @@ -16,9 +18,9 @@ public class InventoryPostClickEvent implements InventoryEvent, PlayerInstanceEv private final Player player; private final Inventory inventory; private final Click.Info info; - private final Click.Result changes; + private final List changes; - public InventoryPostClickEvent(@NotNull Player player, @NotNull Inventory inventory, @NotNull Click.Info info, @NotNull Click.Result changes) { + public InventoryPostClickEvent(@NotNull Player player, @NotNull Inventory inventory, @NotNull Click.Info info, @NotNull List changes) { this.player = player; this.inventory = inventory; this.info = info; @@ -49,7 +51,7 @@ public class InventoryPostClickEvent implements InventoryEvent, PlayerInstanceEv * * @return the changes */ - public @NotNull Click.Result getChanges() { + public @NotNull List getChanges() { return changes; } diff --git a/src/main/java/net/minestom/server/inventory/ContainerInventory.java b/src/main/java/net/minestom/server/inventory/ContainerInventory.java index 3510d817c..c9f7d049c 100644 --- a/src/main/java/net/minestom/server/inventory/ContainerInventory.java +++ b/src/main/java/net/minestom/server/inventory/ContainerInventory.java @@ -15,6 +15,7 @@ import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** @@ -33,7 +34,7 @@ public non-sealed class ContainerInventory extends InventoryImpl { * @param info the click info describing the click * @return the click result, or null if the click did not occur */ - public static @Nullable Click.Result handleClick(@NotNull Inventory inventory, @NotNull Player player, @NotNull Click.Info info, + public static @Nullable List handleClick(@NotNull Inventory inventory, @NotNull Player player, @NotNull Click.Info info, @NotNull ClickProcessors.InventoryProcessor processor) { PlayerInventory playerInventory = player.getInventory(); @@ -42,13 +43,13 @@ public non-sealed class ContainerInventory extends InventoryImpl { if (!preClickEvent.isCancelled()) { final Click.Info newInfo = preClickEvent.getClickInfo(); Click.Getter getter = new Click.Getter(inventory::getItemStack, playerInventory::getItemStack, playerInventory.getCursorItem(), inventory.getSize()); - final Click.Result changes = processor.apply(newInfo, getter); + final List changes = processor.apply(newInfo, getter); InventoryClickEvent clickEvent = new InventoryClickEvent(playerInventory, inventory, player, newInfo, changes); EventDispatcher.call(clickEvent); if (!clickEvent.isCancelled()) { - final Click.Result newChanges = clickEvent.getChanges(); + final List newChanges = clickEvent.getChanges(); apply(newChanges, player, inventory); @@ -72,29 +73,22 @@ public non-sealed class ContainerInventory extends InventoryImpl { return null; } - public static void apply(@NotNull Click.Result result, @NotNull Player player, @NotNull Inventory inventory) { + public static void apply(@NotNull List changes, @NotNull Player player, @NotNull Inventory inventory) { PlayerInventory playerInventory = player.getInventory(); - for (var change : result.changes()) { - if (change instanceof Click.Change.Main(int slot, ItemStack item)) { - if (slot < inventory.getSize()) { - inventory.setItemStack(slot, item); - } else { - int converted = PlayerInventoryUtils.protocolToMinestom(slot, inventory.getSize()); - playerInventory.setItemStack(converted, item); + for (var change : changes) { + switch (change) { + case Click.Change.Main(int slot, ItemStack item) -> { + if (slot < inventory.getSize()) { + inventory.setItemStack(slot, item); + } else { + int converted = PlayerInventoryUtils.protocolToMinestom(slot, inventory.getSize()); + playerInventory.setItemStack(converted, item); + } } - } else if (change instanceof Click.Change.Player(int slot, ItemStack item)) { - playerInventory.setItemStack(slot, item); - } - } - - if (result.newCursorItem() != null) { - playerInventory.setCursorItem(result.newCursorItem()); - } - - if (result.sideEffects() instanceof Click.SideEffect.DropFromPlayer drop) { - for (ItemStack item : drop.items()) { - player.dropItem(item); + case Click.Change.Player(int slot, ItemStack item) -> playerInventory.setItemStack(slot, item); + case Click.Change.Cursor(ItemStack item) -> playerInventory.setCursorItem(item); + case Click.Change.DropFromPlayer(ItemStack item) -> player.dropItem(item); } } } @@ -152,7 +146,7 @@ public non-sealed class ContainerInventory extends InventoryImpl { } @Override - public @Nullable Click.Result handleClick(@NotNull Player player, Click.@NotNull Info info) { + public @Nullable List handleClick(@NotNull Player player, Click.@NotNull Info info) { return ContainerInventory.handleClick(this, player, info, ClickProcessors.PROCESSORS_MAP.getOrDefault(inventoryType, ClickProcessors.GENERIC_PROCESSOR)); } diff --git a/src/main/java/net/minestom/server/inventory/Inventory.java b/src/main/java/net/minestom/server/inventory/Inventory.java index 70c45f5b6..4a8e4f946 100644 --- a/src/main/java/net/minestom/server/inventory/Inventory.java +++ b/src/main/java/net/minestom/server/inventory/Inventory.java @@ -5,7 +5,6 @@ import net.minestom.server.entity.Player; import net.minestom.server.inventory.click.Click; import net.minestom.server.item.ItemStack; import net.minestom.server.tag.Taggable; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,7 +53,7 @@ public sealed interface Inventory extends Taggable, Viewable permits InventoryIm * @param info the information about the player's click * @return the results of the click, or null if the click was cancelled or otherwise was not handled */ - @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info); + @Nullable List handleClick(@NotNull Player player, @NotNull Click.Info info); /** * Gets all the {@link ItemStack} in the inventory. diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index a64743a4d..5b1bf975c 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -158,7 +158,7 @@ public non-sealed class PlayerInventory extends InventoryImpl { } @Override - public @Nullable Click.Result handleClick(@NotNull Player player, @NotNull Click.Info info) { + public @Nullable List handleClick(@NotNull Player player, @NotNull Click.Info info) { return ContainerInventory.handleClick(this, player, info, ClickProcessors.PLAYER_PROCESSOR); } diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 67f99b99f..04ada34b1 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -205,70 +205,16 @@ public final class Click { } public sealed interface Change { - record Main(int slot, @NotNull ItemStack item) implements Change {} - record Player(int slot, @NotNull ItemStack item) implements Change {} - } - - public static final class Setter { - private final List changes = new ArrayList<>(); - private @Nullable ItemStack cursor; - private @Nullable SideEffect sideEffect; - - Setter() { + record Main(int slot, @NotNull ItemStack item) implements Change { } - public @NotNull Setter set(int slot, @NotNull ItemStack item) { - changes.add(new Change.Main(slot, item)); - return this; + record Player(int slot, @NotNull ItemStack item) implements Change { } - public @NotNull Setter setPlayer(int slot, @NotNull ItemStack item) { - changes.add(new Change.Player(slot, item)); - return this; + record Cursor(@NotNull ItemStack item) implements Change { } - public @NotNull Setter cursor(@Nullable ItemStack newCursorItem) { - this.cursor = newCursorItem; - return this; - } - - public @NotNull Setter sideEffects(@Nullable SideEffect sideEffect) { - this.sideEffect = sideEffect; - return this; - } - - public @NotNull Click.Result build() { - return new Result(changes, cursor, sideEffect); - } - } - - /** - * Stores changes that occurred or will occur as the result of a click. - * - * @param changes the list of changes that will occur - * @param newCursorItem the player's cursor item after this click. Null indicates no change - * @param sideEffects the side effects of this click - */ - public record Result(@NotNull List changes, @Nullable ItemStack newCursorItem, @Nullable Click.SideEffect sideEffects) { - public static final Result NOTHING = new Result(List.of(), null, null); - - public Result { - changes = List.copyOf(changes); - } - } - - /** - * Represents side effects that may occur as the result of an inventory click. - */ - public sealed interface SideEffect { - record DropFromPlayer(@NotNull List<@NotNull ItemStack> items) implements SideEffect { - public DropFromPlayer { - items = List.copyOf(items); - } - - public DropFromPlayer(@NotNull ItemStack @NotNull ... items) { - this(List.of(items)); - } + record DropFromPlayer(@NotNull ItemStack item) implements Change { } } } diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index cc6892cbf..57bc1f0dc 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -8,6 +8,7 @@ import net.minestom.server.inventory.TransactionType; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.item.StackingRule; +import net.minestom.server.inventory.click.Click.Change.*; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; @@ -28,125 +29,123 @@ import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; public final class ClickProcessors { private static final @NotNull StackingRule RULE = StackingRule.get(); - public static @NotNull Click.Result leftClick(int slot, @NotNull Click.Getter getter) { + public static @NotNull List leftClick(int slot, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); final ItemStack clickedItem = getter.get(slot); Pair pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); if (pair != null) { // Stackable items, combine their counts - return new Click.Setter().set(slot, pair.left()).cursor(pair.right()).build(); + return List.of(new Main(slot, pair.left()), new Cursor(pair.right())); } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them - return new Click.Setter().set(slot, cursor).cursor(clickedItem).build(); + return List.of(new Main(slot, cursor), new Cursor(clickedItem)); } else { - return Click.Result.NOTHING; + return List.of(); } } - public static @NotNull Click.Result rightClick(int slot, @NotNull Click.Getter getter) { + public static @NotNull List rightClick(int slot, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); final ItemStack clickedItem = getter.get(slot); - if (cursor.isAir() && clickedItem.isAir()) return Click.Result.NOTHING; // Both are air, no changes + if (cursor.isAir() && clickedItem.isAir()) return List.of(); // Both are air, no changes if (cursor.isAir()) { // Take half (rounded up) of the clicked item int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); Pair cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); - return cursorSlot == null ? Click.Result.NOTHING : - new Click.Setter().cursor(cursorSlot.left()).set(slot, cursorSlot.right()).build(); + if (cursorSlot == null) return List.of(); + return List.of(new Main(slot, cursorSlot.right()), new Cursor(cursorSlot.left())); } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over Pair slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); - return slotCursor == null ? Click.Result.NOTHING : - new Click.Setter().set(slot, slotCursor.left()).cursor(slotCursor.right()).build(); + if (slotCursor == null) return List.of(); + return List.of(new Main(slot, slotCursor.left()), new Cursor(slotCursor.right())); } else { // Two existing of items of different types, so switch - return new Click.Setter().cursor(clickedItem).set(slot, cursor).build(); + return List.of(new Cursor(clickedItem), new Main(slot, cursor)); } } - public static @NotNull Click.Result middleClick(int slot, @NotNull Click.Getter getter) { + public static @NotNull List middleClick(int slot, @NotNull Click.Getter getter) { var item = getter.get(slot); - if (getter.cursor().isAir() && !item.isAir()) { - return new Click.Setter().cursor(RULE.apply(item, RULE.getMaxSize(item))).build(); - } else { - return Click.Result.NOTHING; - } + if (!getter.cursor().isAir() || item.isAir()) return List.of(); + + return List.of(new Cursor(RULE.apply(item, RULE.getMaxSize(item)))); } - public static @NotNull Click.Result shiftClick(int slot, @NotNull List slots, @NotNull Click.Getter getter) { + public static @NotNull List shiftClick(int slot, @NotNull List slots, @NotNull Click.Getter getter) { final ItemStack clicked = getter.get(slot); slots = new ArrayList<>(slots); slots.removeIf(i -> i == slot); Pair> result = TransactionType.add(slots, slots).process(clicked, getter::get); - Click.Setter setter = new Click.Setter(); - result.right().forEach(setter::set); + List changes = new ArrayList<>(); + result.right().forEach((slotId, item) -> changes.add(new Main(slotId, item))); - return !result.left().equals(clicked) ? - setter.set(slot, result.left()).build() : - setter.build(); + if (!result.left().equals(clicked)) { + changes.add(new Main(slot, result.left())); + } + + return changes; } - public static @NotNull Click.Result doubleClick(@NotNull List slots, @NotNull Click.Getter getter) { + public static @NotNull List doubleClick(@NotNull List slots, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); - if (cursor.isAir()) return Click.Result.NOTHING; + if (cursor.isAir()) return List.of(); var unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); Pair> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); - Click.Setter setter = new Click.Setter(); - result.right().forEach(setter::set); + List changes = new ArrayList<>(); + result.right().forEach((slotId, item) -> changes.add(new Main(slotId, item))); - return !result.left().equals(cursor) ? - setter.cursor(result.left()).build() : - setter.build(); + if (!result.left().equals(cursor)) { + changes.add(new Cursor(result.left())); + } + + return changes; } - public static @NotNull Click.Result dragClick(int countPerSlot, @NotNull List slots, @NotNull Click.Getter getter) { + public static @NotNull List dragClick(int countPerSlot, @NotNull List slots, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); - if (cursor.isAir()) return Click.Result.NOTHING; + if (cursor.isAir()) return List.of(); Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); - Click.Setter setter = new Click.Setter(); - result.right().forEach(setter::set); + List changes = new ArrayList<>(); + result.right().forEach((slotId, item) -> changes.add(new Main(slotId, item))); - return !result.left().equals(cursor) ? - setter.cursor(result.left()).build() : - setter.build(); - } - - public static @NotNull Click.Result middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { - final ItemStack cursor = getter.cursor(); - Click.Setter setter = new Click.Setter(); - for (int slot : slots) { - if (getter.get(slot).isAir()) { - setter.set(slot, cursor); - } + if (!result.left().equals(cursor)) { + changes.add(new Cursor(result.left())); } - return setter.build(); + + return changes; } - public static @NotNull Click.Result dropFromCursor(int amount, @NotNull Click.Getter getter) { + public static @NotNull List middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); - if (cursor.isAir()) return Click.Result.NOTHING; // Do nothing + + return slots.stream() + .filter(slot -> getter.get(slot).isAir()) + .map(slot -> (Click.Change) new Main(slot, cursor)) + .toList(); + } + + public static @NotNull List dropFromCursor(int amount, @NotNull Click.Getter getter) { + final ItemStack cursor = getter.cursor(); + if (cursor.isAir()) return List.of(); // Do nothing var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); - if (pair == null) return Click.Result.NOTHING; + if (pair == null) return List.of(); - return new Click.Setter().cursor(pair.right()) - .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) - .build(); + return List.of(new Cursor(pair.right()), new DropFromPlayer(pair.left())); } - public static @NotNull Click.Result dropFromSlot(int slot, int amount, @NotNull Click.Getter getter) { + public static @NotNull List dropFromSlot(int slot, int amount, @NotNull Click.Getter getter) { final ItemStack item = getter.get(slot); - if (item.isAir()) return Click.Result.NOTHING; // Do nothing + if (item.isAir()) return List.of(); // Do nothing var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); - if (pair == null) return Click.Result.NOTHING; + if (pair == null) return List.of(); - return new Click.Setter().set(slot, pair.right()) - .sideEffects(new Click.SideEffect.DropFromPlayer(pair.left())) - .build(); + return List.of(new Main(slot, pair.right()), new DropFromPlayer(pair.left())); } /** @@ -181,28 +180,28 @@ public final class ClickProcessors { dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter); case Click.Info.LeftDropCursor() -> dropFromCursor(getter.cursor().amount(), getter); case Click.Info.RightDropCursor() -> dropFromCursor(1, getter); - case Click.Info.MiddleDropCursor() -> Click.Result.NOTHING; + case Click.Info.MiddleDropCursor() -> List.of(); case Click.Info.HotbarSwap(int hotbarSlot, int clickedSlot) -> { var hotbarItem = getter.player().apply(hotbarSlot); var selectedItem = getter.get(clickedSlot); - if (hotbarItem.equals(selectedItem)) yield Click.Result.NOTHING; + if (hotbarItem.equals(selectedItem)) yield List.of(); - yield new Click.Setter().set(clickedSlot, hotbarItem).setPlayer(hotbarSlot, selectedItem).build(); + yield List.of(new Main(clickedSlot, hotbarItem), new Player(hotbarSlot, selectedItem)); } case Click.Info.OffhandSwap(int slot) -> { var offhandItem = getter.player().apply(PlayerInventoryUtils.OFF_HAND_SLOT); var selectedItem = getter.get(slot); - if (offhandItem.equals(selectedItem)) yield Click.Result.NOTHING; + if (offhandItem.equals(selectedItem)) yield List.of(); - yield new Click.Setter().set(slot, offhandItem).setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, selectedItem).build(); + yield List.of(new Main(slot, offhandItem), new Player(OFF_HAND_SLOT, selectedItem)); } - case Click.Info.CreativeSetItem(int slot, ItemStack item) -> new Click.Setter().set(slot, item).build(); + case Click.Info.CreativeSetItem(int slot, ItemStack item) -> List.of(new Main(slot, item)); case Click.Info.CreativeDropItem(ItemStack item) -> - new Click.Setter().sideEffects(new Click.SideEffect.DropFromPlayer(item)).build(); + List.of(new DropFromPlayer(item)); }; } - public interface InventoryProcessor extends BiFunction { + public interface InventoryProcessor extends BiFunction> { } /** diff --git a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java index bdc9cac7f..d91cb2dff 100644 --- a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java +++ b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java @@ -5,6 +5,7 @@ import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.InventoryType; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.player.PlayerConnection; @@ -14,7 +15,6 @@ import org.jetbrains.annotations.Nullable; import java.net.SocketAddress; import java.util.*; -import java.util.function.UnaryOperator; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,6 +31,14 @@ public final class ClickUtils { return new Click.Preprocessor(); } + public static @NotNull ItemStack magic(int amount) { + return ItemStack.of(Material.STONE, amount); + } + + public static @NotNull ItemStack magic2(int amount) { + return ItemStack.of(Material.DIRT, amount); + } + public static @NotNull Player createPlayer() { return new Player(UUID.randomUUID(), "TestPlayer", new PlayerConnection() { @Override @@ -48,67 +56,56 @@ public final class ClickUtils { }); } - public static void assertClick(@NotNull UnaryOperator initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator expectedChanges) { + public static void assertClick(@NotNull List initial, @NotNull Click.Info info, @NotNull List expected) { var player = createPlayer(); var inventory = createInventory(); - var expected = expectedChanges.apply(new Click.Setter()).build(); - - ContainerInventory.apply(initialChanges.apply(new Click.Setter()).build(), player, inventory); + ContainerInventory.apply(initial, player, inventory); var actual = inventory.handleClick(player, info); assertChanges(expected, actual, inventory.getSize()); } - public static void assertPlayerClick(@NotNull UnaryOperator initialChanges, @NotNull Click.Info info, @NotNull UnaryOperator expectedChanges) { + public static void assertPlayerClick(@NotNull List initial, @NotNull Click.Info info, @NotNull List expected) { var player = createPlayer(); var inventory = player.getInventory(); - var expected = expectedChanges.apply(new Click.Setter()).build(); - - ContainerInventory.apply(initialChanges.apply(new Click.Setter()).build(), player, inventory); + ContainerInventory.apply(initial, player, inventory); var actual = inventory.handleClick(player, info); assertChanges(expected, actual, inventory.getSize()); } - public static void assertChanges(Click.Result expected, Click.Result actual, int size) { - if (expected == null || actual == null) { - assertEquals(expected, actual); - return; - } - - assertEquals(foldMain(expected.changes(), size), foldMain(actual.changes(), size)); - assertEquals(foldPlayer(expected.changes(), size), foldPlayer(actual.changes(), size)); - - assertEquals(expected.newCursorItem(), actual.newCursorItem()); - assertEquals(expected.sideEffects(), actual.sideEffects()); + public static void assertChanges(List expected, List actual, int size) { + assertEquals(ChangeResult.make(expected, size), ChangeResult.make(actual, size)); } - private static Map foldMain(List changes, int size) { - Map map = new HashMap<>(); + private record ChangeResult(Map main, Map player, + @Nullable ItemStack cursor, List drops) { + private static ChangeResult make(@NotNull List changes, int size) { + Map main = new HashMap<>(); + Map player = new HashMap<>(); + @Nullable ItemStack cursor = null; + List drops = new ArrayList<>(); - for (var change : changes) { - if (change instanceof Click.Change.Main(int slot, ItemStack item) && slot < size) { - map.put(slot, item); + for (var change : changes) { + switch (change) { + case Click.Change.Main(int slot, ItemStack item) -> { + if (slot < size) { + main.put(slot, item); + } else { + player.put(PlayerInventoryUtils.protocolToMinestom(slot, size), item); + } + } + case Click.Change.Player(int slot, ItemStack item) -> player.put(slot, item); + case Click.Change.Cursor(ItemStack item) -> cursor = item; + case Click.Change.DropFromPlayer(ItemStack item) -> drops.add(item); + } } + + return new ChangeResult(main, player, cursor, drops); } - return map; - } - - private static Map foldPlayer(List changes, int size) { - Map map = new HashMap<>(); - - for (var change : changes) { - if (change instanceof Click.Change.Main(int slot, ItemStack item) && slot >= size) { - map.put(PlayerInventoryUtils.protocolToMinestom(slot, size), item); - } else if (change instanceof Click.Change.Player(int slot, ItemStack item)) { - map.put(slot, item); - } - } - - return map; } public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java index 92c398241..9bde9d2fb 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeDropItemTest.java @@ -2,11 +2,13 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; +import net.minestom.server.inventory.click.Click.Change.DropFromPlayer; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryCreativeDropItemTest { @@ -17,16 +19,16 @@ public class InventoryCreativeDropItemTest { @Test public void testDropItem() { assertClick( - builder -> builder, - new Click.Info.CreativeDropItem(ItemStack.of(Material.DIRT, 64)), - builder -> builder.sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.DIRT, 64))) + List.of(), + new Click.Info.CreativeDropItem(magic(64)), + List.of(new DropFromPlayer(magic(64))) ); // Make sure it doesn't drop a full stack assertClick( - builder -> builder, - new Click.Info.CreativeDropItem(ItemStack.of(Material.DIRT, 1)), - builder -> builder.sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.DIRT, 1))) + List.of(), + new Click.Info.CreativeDropItem(magic(1)), + List.of(new DropFromPlayer(magic(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java index e67924f0e..7e1d0a0f1 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryCreativeSetItemTest.java @@ -2,11 +2,13 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; +import net.minestom.server.inventory.click.Click.Change.Main; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryCreativeSetItemTest { @@ -17,16 +19,16 @@ public class InventoryCreativeSetItemTest { @Test public void testSetItem() { assertClick( - builder -> builder, - new Click.Info.CreativeSetItem(0, ItemStack.of(Material.DIRT, 64)), - builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)) + List.of(), + new Click.Info.CreativeSetItem(0, magic(64)), + List.of(new Main(0, magic(64))) ); // Make sure it doesn't set a full stack assertClick( - builder -> builder, - new Click.Info.CreativeSetItem(0, ItemStack.of(Material.DIRT, 1)), - builder -> builder.set(0, ItemStack.of(Material.DIRT, 1)) + List.of(), + new Click.Info.CreativeSetItem(0, magic(1)), + List.of(new Main(0, magic(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java index 5d83eb936..770b7d1cd 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryDoubleClickTest.java @@ -2,11 +2,15 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.Main; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryDoubleClickTest { @@ -16,75 +20,63 @@ public class InventoryDoubleClickTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.Double(0), builder -> builder); + assertClick(List.of(), new Click.Info.Double(0), List.of()); } @Test public void testCannotTakeAny() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Cursor(magic(32))), new Click.Info.Double(0), - builder -> builder + List.of() ); } @Test public void testPartialTake() { assertClick( - builder -> builder.set(1, ItemStack.of(Material.STONE, 48)).cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(1, magic(48)), new Cursor(magic(32))), new Click.Info.Double(0), - builder -> builder.set(1, ItemStack.of(Material.STONE, 16)).cursor(ItemStack.of(Material.STONE, 64)) + List.of(new Main(1, magic(16)), new Cursor(magic(64))) ); } @Test public void testTakeAll() { assertClick( - builder -> builder.set(1, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(1, magic(32)), new Cursor(magic(32))), new Click.Info.Double(0), - builder -> builder.set(1, ItemStack.AIR).cursor(ItemStack.of(Material.STONE, 64)) + List.of(new Main(1, ItemStack.AIR), new Cursor(magic(64))) ); assertClick( - builder -> builder.set(1, ItemStack.of(Material.STONE, 16)).cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(1, magic(16)), new Cursor(magic(32))), new Click.Info.Double(0), - builder -> builder.set(1, ItemStack.AIR).cursor(ItemStack.of(Material.STONE, 48)) + List.of(new Main(1, ItemStack.AIR), new Cursor(magic(48))) ); } @Test public void testTakeSeparated() { assertClick( - builder -> builder - .set(1, ItemStack.of(Material.STONE, 16)) - .set(2, ItemStack.of(Material.STONE, 16)) - .cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(1, magic(16)), new Main(2, magic(16)), new Cursor(magic(32))), new Click.Info.Double(0), - builder -> builder - .set(1, ItemStack.AIR) - .set(2, ItemStack.AIR) - .cursor(ItemStack.of(Material.STONE, 64)) + List.of(new Main(1, ItemStack.AIR), new Main(2, ItemStack.AIR), new Cursor(magic(64))) ); assertClick( - builder -> builder - .set(1, ItemStack.of(Material.STONE, 16)) - .set(2, ItemStack.of(Material.STONE, 32)) - .cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(1, magic(16)), new Main(2, magic(32)), new Cursor(magic(32))), new Click.Info.Double(0), - builder -> builder - .set(1, ItemStack.AIR) - .set(2, ItemStack.of(Material.STONE, 16)) - .cursor(ItemStack.of(Material.STONE, 64)) + List.of(new Main(1, ItemStack.AIR), new Main(2, magic(16)), new Cursor(magic(64))) ); } @Test public void testCursorFull() { assertClick( - builder -> builder.set(1, ItemStack.of(Material.STONE, 48)).cursor(ItemStack.of(Material.STONE, 64)), + List.of(new Main(1, magic(48)), new Cursor(magic(64))), new Click.Info.Double(0), - builder -> builder + List.of() ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java index abfe71bb2..d3ed64e64 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropCursorTest.java @@ -2,11 +2,15 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.DropFromPlayer; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryDropCursorTest { @@ -16,35 +20,35 @@ public class InventoryDropCursorTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.LeftDropCursor(), builder -> builder); - assertClick(builder -> builder, new Click.Info.MiddleDropCursor(), builder -> builder); - assertClick(builder -> builder, new Click.Info.RightDropCursor(), builder -> builder); + assertClick(List.of(), new Click.Info.LeftDropCursor(), List.of()); + assertClick(List.of(), new Click.Info.MiddleDropCursor(), List.of()); + assertClick(List.of(), new Click.Info.RightDropCursor(), List.of()); } @Test public void testDropEntireStack() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Cursor(magic(32))), new Click.Info.LeftDropCursor(), - builder -> builder.cursor(ItemStack.AIR).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 32))) + List.of(new Cursor(ItemStack.AIR), new DropFromPlayer(magic(32))) ); } @Test public void testDropSingleItem() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Cursor(magic(32))), new Click.Info.RightDropCursor(), - builder -> builder.cursor(ItemStack.of(Material.STONE, 31)).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 1))) + List.of(new Cursor(magic(31)), new DropFromPlayer(magic(1))) ); } @Test public void testMiddleClickNoop() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Cursor(magic(32))), new Click.Info.MiddleDropCursor(), - builder -> builder + List.of() ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java index 1bed2e19c..90f99a73b 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryDropSlotTest.java @@ -2,11 +2,15 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.Click.Change.DropFromPlayer; +import net.minestom.server.inventory.click.Click.Change.Main; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryDropSlotTest { @@ -16,25 +20,25 @@ public class InventoryDropSlotTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.DropSlot(0, false), builder -> builder); - assertClick(builder -> builder, new Click.Info.DropSlot(0, true), builder -> builder); + assertClick(List.of(), new Click.Info.DropSlot(0, false), List.of()); + assertClick(List.of(), new Click.Info.DropSlot(0, true), List.of()); } @Test public void testDropEntireStack() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE, 32)), + List.of(new Main(0, magic(32))), new Click.Info.DropSlot(0, true), - builder -> builder.set(0, ItemStack.AIR).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 32))) + List.of(new Main(0, ItemStack.AIR), new DropFromPlayer(magic(32))) ); } @Test public void testDropSingleItem() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE, 32)), + List.of(new Main(0, magic(32))), new Click.Info.DropSlot(0, false), - builder -> builder.set(0, ItemStack.of(Material.STONE, 31)).sideEffects(new Click.SideEffect.DropFromPlayer(ItemStack.of(Material.STONE, 1))) + List.of(new Main(0, magic(31)), new DropFromPlayer(magic(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java index 6ba864350..f74718a8b 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryHotbarSwapTest.java @@ -2,11 +2,13 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; +import net.minestom.server.inventory.click.Click.Change.Main; +import net.minestom.server.inventory.click.Click.Change.Player; import org.junit.jupiter.api.Test; -import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import java.util.List; + +import static net.minestom.server.inventory.click.ClickUtils.*; public class InventoryHotbarSwapTest { @@ -17,16 +19,16 @@ public class InventoryHotbarSwapTest { @Test public void testNoChanges() { for (int i = 0; i < 9; i++) { - assertClick(builder -> builder, new Click.Info.HotbarSwap(i, 9), builder -> builder); + assertClick(List.of(), new Click.Info.HotbarSwap(i, 9), List.of()); } } @Test public void testSwappedItems() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT)).setPlayer(0, ItemStack.of(Material.STONE)), + List.of(new Main(0, magic2(1)), new Player(0, magic(1))), new Click.Info.HotbarSwap(0, 0), - builder -> builder.set(0, ItemStack.of(Material.STONE)).setPlayer(0, ItemStack.of(Material.DIRT)) + List.of(new Main(0, magic(1)), new Player(0, magic2(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java index 1ff08b07a..2d8532607 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftClickTest.java @@ -2,11 +2,14 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.Main; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import org.junit.jupiter.api.Test; -import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import java.util.List; + +import static net.minestom.server.inventory.click.ClickUtils.*; public class InventoryLeftClickTest { @@ -16,33 +19,33 @@ public class InventoryLeftClickTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.Left(0), builder -> builder); + assertClick(List.of(), new Click.Info.Left(0), List.of()); } @Test public void testInsertEntireStack() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(0, magic(32)), new Click.Change.Cursor(magic(32))), new Click.Info.Left(0), - builder -> builder.set(0, ItemStack.of(Material.STONE, 64)).cursor(ItemStack.AIR) + List.of(new Main(0, magic(64)), new Cursor(ItemStack.AIR)) ); } @Test public void testInsertPartialStack() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 48)), + List.of(new Main(0, magic(32)), new Cursor(magic(48))), new Click.Info.Left(0), - builder -> builder.set(0, ItemStack.of(Material.STONE, 64)).cursor(ItemStack.of(Material.STONE, 16)) + List.of(new Main(0, magic(64)), new Cursor(magic(16))) ); } @Test public void testSwitchItems() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + List.of(new Main(0, magic(1)), new Cursor(magic2(1))), new Click.Info.Left(0), - builder -> builder.set(0, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.STONE)) + List.of(new Main(0, magic2(1)), new Cursor(magic(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java index a860af4a8..fb708e71a 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryLeftDragTest.java @@ -3,11 +3,15 @@ package net.minestom.server.inventory.click.type; import it.unimi.dsi.fastutil.ints.IntList; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.Main; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryLeftDragTest { @@ -17,85 +21,88 @@ public class InventoryLeftDragTest { @Test public void testNoCursor() { - assertClick(builder -> builder, new Click.Info.LeftDrag(IntList.of(0)), builder -> builder); + assertClick(List.of(), new Click.Info.LeftDrag(IntList.of(0)), List.of()); } @Test public void testDistributeNone() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Cursor(magic(32))), new Click.Info.LeftDrag(IntList.of()), - builder -> builder + List.of() ); } @Test public void testDistributeOne() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Cursor(magic(32))), new Click.Info.LeftDrag(IntList.of(0)), - builder -> builder.set(0, ItemStack.of(Material.DIRT, 32)).cursor(ItemStack.of(Material.AIR)) + List.of(new Main(0, magic(32)), new Cursor(ItemStack.AIR)) ); } @Test public void testDistributeExactlyEnough() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Cursor(magic(32))), new Click.Info.LeftDrag(IntList.of(0, 1)), - builder -> builder.set(0, ItemStack.of(Material.DIRT, 16)).set(1, ItemStack.of(Material.DIRT, 16)).cursor(ItemStack.of(Material.AIR)) + List.of(new Main(0, magic(16)), new Main(1, magic(16)), new Cursor(ItemStack.AIR)) ); assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 30)), + List.of(new Cursor(magic(30))), new Click.Info.LeftDrag(IntList.of(0, 1, 2)), - builder -> builder - .set(0, ItemStack.of(Material.DIRT, 10)) - .set(1, ItemStack.of(Material.DIRT, 10)) - .set(2, ItemStack.of(Material.DIRT, 10)) - .cursor(ItemStack.of(Material.AIR)) + List.of( + new Main(0, magic(10)), + new Main(1, magic(10)), + new Main(2, magic(10)), + new Cursor(ItemStack.AIR) + ) ); } @Test public void testRemainderItems() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Cursor(magic(32))), new Click.Info.LeftDrag(IntList.of(0, 1, 2)), - builder -> builder - .set(0, ItemStack.of(Material.DIRT, 10)) - .set(1, ItemStack.of(Material.DIRT, 10)) - .set(2, ItemStack.of(Material.DIRT, 10)) - .cursor(ItemStack.of(Material.DIRT, 2)) + List.of( + new Main(0, magic(10)), + new Main(1, magic(10)), + new Main(2, magic(10)), + new Cursor(magic(2)) + ) ); assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 25)), + List.of(new Cursor(magic(25))), new Click.Info.LeftDrag(IntList.of(0, 1, 2, 3)), - builder -> builder - .set(0, ItemStack.of(Material.DIRT, 6)) - .set(1, ItemStack.of(Material.DIRT, 6)) - .set(2, ItemStack.of(Material.DIRT, 6)) - .set(3, ItemStack.of(Material.DIRT, 6)) - .cursor(ItemStack.of(Material.DIRT, 1)) + List.of( + new Main(0, magic(6)), + new Main(1, magic(6)), + new Main(2, magic(6)), + new Main(3, magic(6)), + new Cursor(magic(1)) + ) ); } @Test public void testDistributeOverExisting() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT, 16)).cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Main(0, magic(16)), new Cursor(magic(32))), new Click.Info.LeftDrag(IntList.of(0)), - builder -> builder.set(0, ItemStack.of(Material.DIRT, 48)).cursor(ItemStack.of(Material.AIR)) + List.of(new Main(0, magic(48)), new Cursor(ItemStack.AIR)) ); } @Test public void testDistributeOverFull() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)).cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Main(0, magic(64)), new Cursor(magic(32))), new Click.Info.LeftDrag(IntList.of(0)), - builder -> builder + List.of() ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java index 7142f0434..b0adf1892 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleClickTest.java @@ -2,11 +2,14 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.Main; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryMiddleClickTest { @@ -16,24 +19,24 @@ public class InventoryMiddleClickTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.Middle(0), builder -> builder); + assertClick(List.of(), new Click.Info.Middle(0), List.of()); } @Test public void testCopy() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)), + List.of(new Main(0, magic(64))), new Click.Info.Middle(0), - builder -> builder.cursor(ItemStack.of(Material.DIRT, 64)) + List.of(new Cursor(magic(64))) ); } @Test public void testCopyNotFull() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT, 32)), + List.of(new Main(0, magic(32))), new Click.Info.Middle(0), - builder -> builder.cursor(ItemStack.of(Material.DIRT, 64)) + List.of(new Cursor(magic(64))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java index 72890fb30..4d6ec5de9 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryMiddleDragTest.java @@ -3,11 +3,13 @@ package net.minestom.server.inventory.click.type; import it.unimi.dsi.fastutil.ints.IntList; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.Main; import org.junit.jupiter.api.Test; -import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import java.util.List; + +import static net.minestom.server.inventory.click.ClickUtils.*; public class InventoryMiddleDragTest { @@ -17,33 +19,33 @@ public class InventoryMiddleDragTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.MiddleDrag(IntList.of()), builder -> builder); + assertClick(List.of(), new Click.Info.MiddleDrag(IntList.of()), List.of()); } @Test public void testExistingSlots() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + List.of(new Main(0, magic(1)), new Cursor(magic2(1))), new Click.Info.MiddleDrag(IntList.of(0)), - builder -> builder + List.of() ); } @Test public void testPartialExistingSlots() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + List.of(new Main(0, magic(1)), new Cursor(magic2(1))), new Click.Info.MiddleDrag(IntList.of(0, 1)), - builder -> builder.set(1, ItemStack.of(Material.DIRT)) + List.of(new Main(1, magic2(1))) ); } @Test public void testFullCopy() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT)), + List.of(new Cursor(magic(1))), new Click.Info.MiddleDrag(IntList.of(0, 1)), - builder -> builder.set(0, ItemStack.of(Material.DIRT)).set(1, ItemStack.of(Material.DIRT)) + List.of(new Main(0, magic(1)), new Main(1, magic(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java index cb2c4b4ab..5e7fd7d9a 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryOffhandSwapTest.java @@ -2,12 +2,14 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.utils.inventory.PlayerInventoryUtils; +import net.minestom.server.inventory.click.Click.Change.Main; +import net.minestom.server.inventory.click.Click.Change.Player; import org.junit.jupiter.api.Test; -import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import java.util.List; + +import static net.minestom.server.inventory.click.ClickUtils.*; +import static net.minestom.server.utils.inventory.PlayerInventoryUtils.OFF_HAND_SLOT; public class InventoryOffhandSwapTest { @@ -17,15 +19,15 @@ public class InventoryOffhandSwapTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.OffhandSwap(0), builder -> builder); + assertClick(List.of(), new Click.Info.OffhandSwap(0), List.of()); } @Test public void testSwappedItems() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT)).setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.STONE)), + List.of(new Main(0, magic2(1)), new Player(OFF_HAND_SLOT, magic(1))), new Click.Info.OffhandSwap(0), - builder -> builder.set(0, ItemStack.of(Material.STONE)).setPlayer(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.DIRT)) + List.of(new Main(0, magic(1)), new Player(OFF_HAND_SLOT, magic2(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java index c8ad77ea5..0f47a6da8 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightClickTest.java @@ -2,11 +2,13 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.Main; import org.junit.jupiter.api.Test; -import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import java.util.List; + +import static net.minestom.server.inventory.click.ClickUtils.*; public class InventoryRightClickTest { @@ -16,51 +18,51 @@ public class InventoryRightClickTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.Right(0), builder -> builder); + assertClick(List.of(), new Click.Info.Right(0), List.of()); } @Test public void testAddOne() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(0, magic(32)), new Cursor(magic(32))), new Click.Info.Right(0), - builder -> builder.set(0, ItemStack.of(Material.STONE, 33)).cursor(ItemStack.of(Material.STONE, 31)) + List.of(new Main(0, magic(33)), new Cursor(magic(31))) ); } @Test public void testClickedStackFull() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE, 64)).cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Main(0, magic(64)), new Cursor(magic(32))), new Click.Info.Right(0), - builder -> builder + List.of() ); } @Test public void testTakeHalf() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE, 32)), + List.of(new Main(0, magic(32))), new Click.Info.Right(0), - builder -> builder.set(0, ItemStack.of(Material.STONE, 16)).cursor(ItemStack.of(Material.STONE, 16)) + List.of(new Main(0, magic(16)), new Cursor(magic(16))) ); } @Test public void testLeaveOne() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.STONE, 32)), + List.of(new Cursor(magic(32))), new Click.Info.Right(0), - builder -> builder.set(0, ItemStack.of(Material.STONE, 1)).cursor(ItemStack.of(Material.STONE, 31)) + List.of(new Main(0, magic(1)), new Cursor(magic(31))) ); } @Test public void testSwitchItems() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.STONE)).cursor(ItemStack.of(Material.DIRT)), + List.of(new Main(0, magic(1)), new Cursor(magic2(1))), new Click.Info.Right(0), - builder -> builder.set(0, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.STONE)) + List.of(new Main(0, magic2(1)), new Cursor(magic(1))) ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java index c24cd7b4d..117cc4fbd 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryRightDragTest.java @@ -3,11 +3,14 @@ package net.minestom.server.inventory.click.type; import it.unimi.dsi.fastutil.ints.IntList; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.Click.Change.*; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.assertClick; +import static net.minestom.server.inventory.click.ClickUtils.magic; public class InventoryRightDragTest { @@ -17,60 +20,60 @@ public class InventoryRightDragTest { @Test public void testNoCursor() { - assertClick(builder -> builder, new Click.Info.RightDrag(IntList.of(0)), builder -> builder); + assertClick(List.of(), new Click.Info.RightDrag(IntList.of(0)), List.of()); } @Test public void testDistributeNone() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Cursor(magic(32))), new Click.Info.RightDrag(IntList.of()), - builder -> builder + List.of() ); } @Test public void testDistributeOne() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Cursor(magic(32))), new Click.Info.RightDrag(IntList.of(0)), - builder -> builder.set(0, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.DIRT, 31)) + List.of(new Main(0, magic(1)), new Cursor(magic(31))) ); } @Test public void testDistributeExactlyEnough() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 2)), + List.of(new Cursor(magic(2))), new Click.Info.RightDrag(IntList.of(0, 1)), - builder -> builder.set(0, ItemStack.of(Material.DIRT)).set(1, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.AIR)) + List.of(new Main(0, magic(1)), new Main(1, magic(1)), new Cursor(ItemStack.AIR)) ); } @Test public void testTooManySlots() { assertClick( - builder -> builder.cursor(ItemStack.of(Material.DIRT, 2)), + List.of(new Cursor(magic(2))), new Click.Info.RightDrag(IntList.of(0, 1, 2)), - builder -> builder.set(0, ItemStack.of(Material.DIRT)).set(1, ItemStack.of(Material.DIRT)).cursor(ItemStack.of(Material.AIR)) + List.of(new Main(0, magic(1)), new Main(1, magic(1)), new Cursor(ItemStack.AIR)) ); } @Test public void testDistributeOverExisting() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT, 16)).cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Main(0, magic(16)), new Cursor(magic(32))), new Click.Info.RightDrag(IntList.of(0)), - builder -> builder.set(0, ItemStack.of(Material.DIRT, 17)).cursor(ItemStack.of(Material.DIRT, 31)) + List.of(new Main(0, magic(17)), new Cursor(magic(31))) ); } @Test public void testDistributeOverFull() { assertClick( - builder -> builder.set(0, ItemStack.of(Material.DIRT, 64)).cursor(ItemStack.of(Material.DIRT, 32)), + List.of(new Main(0, magic(64)), new Cursor(magic(32))), new Click.Info.RightDrag(IntList.of(0)), - builder -> builder + List.of() ); } diff --git a/src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java b/src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java index 24723325f..d17b69cc6 100644 --- a/src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java +++ b/src/test/java/net/minestom/server/inventory/click/type/InventoryShiftClickTest.java @@ -2,11 +2,14 @@ package net.minestom.server.inventory.click.type; import net.minestom.server.MinecraftServer; import net.minestom.server.inventory.click.Click; +import net.minestom.server.inventory.click.Click.Change.*; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.junit.jupiter.api.Test; +import java.util.List; + import static net.minestom.server.inventory.click.ClickUtils.*; public class InventoryShiftClickTest { @@ -17,132 +20,133 @@ public class InventoryShiftClickTest { @Test public void testNoChanges() { - assertClick(builder -> builder, new Click.Info.LeftShift(0), builder -> builder); - assertClick(builder -> builder, new Click.Info.RightShift(0), builder -> builder); + assertClick(List.of(), new Click.Info.LeftShift(0), List.of()); + assertClick(List.of(), new Click.Info.RightShift(0), List.of()); } @Test public void testSimpleTransfer() { assertClick( - builder -> builder.setPlayer(9, ItemStack.of(Material.STONE, 32)), + List.of(new Player(9, magic(32))), new Click.Info.LeftShift(SIZE), - builder -> builder.set(0, ItemStack.of(Material.STONE, 32)).setPlayer(9, ItemStack.AIR) + List.of(new Main(0, magic(32)), new Player(9, ItemStack.AIR)) ); } @Test public void testSeparatedTransfer() { assertClick( - builder -> builder - .setPlayer(9, ItemStack.of(Material.STONE, 64)) - .set(0, ItemStack.of(Material.STONE, 32)) - .set(1, ItemStack.of(Material.STONE, 32)) - , + List.of( + new Player(9, magic(64)), + new Main(0, magic(32)), + new Main(1, magic(32)) + ), new Click.Info.LeftShift(SIZE), - builder -> builder - .setPlayer(9, ItemStack.AIR) - .set(0, ItemStack.of(Material.STONE, 64)) - .set(1, ItemStack.of(Material.STONE, 64)) - + List.of( + new Player(9, ItemStack.AIR), + new Main(0, magic(64)), + new Main(1, magic(64)) + ) ); } @Test public void testSeparatedAndNewTransfer() { assertClick( - builder -> builder - .setPlayer(9, ItemStack.of(Material.STONE, 64)) - .set(0, ItemStack.of(Material.STONE, 32)), + List.of( + new Player(9, magic(64)), + new Main(0, magic(32)) + ), new Click.Info.LeftShift(SIZE), - builder -> builder - .setPlayer(9, ItemStack.AIR) - .set(0, ItemStack.of(Material.STONE, 64)) - .set(1, ItemStack.of(Material.STONE, 32)) - + List.of( + new Player(9, ItemStack.AIR), + new Main(0, magic(64)), + new Main(1, magic(32)) + ) ); } @Test public void testPartialTransfer() { assertClick( - builder -> builder - .setPlayer(9, ItemStack.of(Material.STONE, 64)) - .set(0, ItemStack.of(Material.STONE, 32)) - .set(1, ItemStack.of(Material.DIRT)) - .set(2, ItemStack.of(Material.DIRT)) - .set(3, ItemStack.of(Material.DIRT)) - .set(4, ItemStack.of(Material.DIRT)), + List.of( + new Player(9, magic(64)), + new Main(0, magic(32)), + new Main(1, magic2(1)), + new Main(2, magic2(1)), + new Main(3, magic2(1)), + new Main(4, magic2(1)) + ), new Click.Info.LeftShift(SIZE), - builder -> builder - .setPlayer(9, ItemStack.of(Material.STONE, 32)) - .set(0, ItemStack.of(Material.STONE, 64)) - + List.of(new Player(9, magic(32)), new Main(0, magic(64))) ); } @Test public void testCannotTransfer() { assertClick( - builder -> builder - .setPlayer(9, ItemStack.of(Material.STONE, 64)) - .set(0, ItemStack.of(Material.STONE, 64)) - .set(1, ItemStack.of(Material.DIRT)) - .set(2, ItemStack.of(Material.DIRT)) - .set(3, ItemStack.of(Material.DIRT)) - .set(4, ItemStack.of(Material.DIRT)), + List.of( + new Player(9, magic(64)), + new Main(0, magic(64)), + new Main(1, magic2(1)), + new Main(2, magic2(1)), + new Main(3, magic2(1)), + new Main(4, magic2(1)) + ), new Click.Info.LeftShift(SIZE), // Equivalent to player slot 9 - builder -> builder + List.of() ); assertClick( - builder -> builder - .setPlayer(9, ItemStack.of(Material.STONE, 64)) - .set(0, ItemStack.of(Material.DIRT)) - .set(1, ItemStack.of(Material.DIRT)) - .set(2, ItemStack.of(Material.DIRT)) - .set(3, ItemStack.of(Material.DIRT)) - .set(4, ItemStack.of(Material.DIRT)), + List.of( + new Player(9, magic(64)), + new Main(0, magic2(1)), + new Main(1, magic2(1)), + new Main(2, magic2(1)), + new Main(3, magic2(1)), + new Main(4, magic2(1)) + ), new Click.Info.LeftShift(SIZE), // Equivalent to player slot 9 - builder -> builder + List.of() ); } @Test public void testPlayerInteraction() { assertPlayerClick( - builder -> builder.set(9, ItemStack.of(Material.STONE, 32)), + List.of(new Main(9, magic(32))), new Click.Info.LeftShift(9), - builder -> builder.set(9, ItemStack.AIR).set(0, ItemStack.of(Material.STONE, 32)) + List.of(new Main(9, ItemStack.AIR), new Main(0, magic(32))) ); assertPlayerClick( - builder -> builder.set(8, ItemStack.of(Material.STONE, 32)), + List.of(new Main(8, magic(32))), new Click.Info.LeftShift(8), - builder -> builder.set(8, ItemStack.AIR).set(9, ItemStack.of(Material.STONE, 32)) + List.of(new Main(8, ItemStack.AIR), new Main(9, magic(32))) ); assertPlayerClick( - builder -> builder.set(9, ItemStack.of(Material.IRON_CHESTPLATE)), + List.of(new Main(9, ItemStack.of(Material.IRON_CHESTPLATE))), new Click.Info.LeftShift(9), - builder -> builder.set(9, ItemStack.AIR).set(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE)) + List.of(new Main(9, ItemStack.AIR), new Main(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE))) ); assertPlayerClick( - builder -> builder.set(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE)), + List.of(new Main(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE))), new Click.Info.LeftShift(PlayerInventoryUtils.CHESTPLATE_SLOT), - builder -> builder.set(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.AIR).set(9, ItemStack.of(Material.IRON_CHESTPLATE)) + List.of(new Main(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.AIR), new Main(9, ItemStack.of(Material.IRON_CHESTPLATE))) ); assertPlayerClick( - builder -> builder.set(9, ItemStack.of(Material.SHIELD)), + List.of(new Main(9, ItemStack.of(Material.SHIELD))), new Click.Info.LeftShift(9), - builder -> builder.set(9, ItemStack.AIR).set(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD)) + List.of(new Main(9, ItemStack.AIR), new Main(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD))) ); assertPlayerClick( - builder -> builder.set(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD)), + List.of(new Main(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD))), new Click.Info.LeftShift(PlayerInventoryUtils.OFF_HAND_SLOT), - builder -> builder.set(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.AIR).set(9, ItemStack.of(Material.SHIELD)) + List.of(new Main(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.AIR), new Main(9, ItemStack.of(Material.SHIELD))) ); } From bb31cf8b20ca5851dfe985e69cee95d7ecf54804 Mon Sep 17 00:00:00 2001 From: themode Date: Sun, 14 Apr 2024 05:17:19 +0200 Subject: [PATCH 11/46] Little simplification --- .../server/inventory/click/Click.java | 98 +++++++------------ .../server/listener/WindowListener.java | 4 +- .../server/inventory/click/ClickUtils.java | 3 +- 3 files changed, 38 insertions(+), 67 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 04ada34b1..1f49e998e 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -6,9 +6,9 @@ import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; -import java.util.function.Consumer; +import java.util.Set; import java.util.function.IntFunction; public final class Click { @@ -65,9 +65,9 @@ public final class Click { * Preprocesses click packets, turning them into {@link Info} instances for further processing. */ public static final class Preprocessor { - private final List leftDrag = new ArrayList<>(); - private final List rightDrag = new ArrayList<>(); - private final List middleDrag = new ArrayList<>(); + private final Set leftDrag = new LinkedHashSet<>(); + private final Set rightDrag = new LinkedHashSet<>(); + private final Set middleDrag = new LinkedHashSet<>(); public void clearCache() { this.leftDrag.clear(); @@ -77,30 +77,31 @@ public final class Click { /** * Processes the provided click packet, turning it into a {@link Info}. - * This will do simple verification of the packet before sending it to {@link #process(ClientClickWindowPacket.ClickType, int, byte, boolean)}. + * This will do simple verification of the packet before sending it to {@link #process(ClientClickWindowPacket.ClickType, int, byte)}. * - * @param packet the raw click packet - * @param isCreative whether the player is in creative mode (used for ignoring some actions) + * @param packet the raw click packet + * @param isCreative whether the player is in creative mode (used for ignoring some actions) + * @param containerSize the size of the open container, or null if the player inventory is open * @return the information about the click, or nothing if there was no immediately usable information */ - public @Nullable Click.Info processPlayerClick(@NotNull ClientClickWindowPacket packet, boolean isCreative) { + public @Nullable Click.Info processClick(@NotNull ClientClickWindowPacket packet, boolean isCreative, @Nullable Integer containerSize) { if (requireCreative(packet) && !isCreative) return null; - final int slot = packet.slot() != -999 ? PlayerInventoryUtils.protocolToMinestom(packet.slot()) : -999; - final int maxSize = PlayerInventoryUtils.INVENTORY_SIZE; - return process(packet.clickType(), slot, packet.button(), slot >= 0 && slot < maxSize); - } - - public @Nullable Click.Info processContainerClick(@NotNull ClientClickWindowPacket packet, int inventorySize, boolean isCreative) { - if (requireCreative(packet) && !isCreative) return null; - final int slot = packet.slot(); - final int maxSize = inventorySize + PlayerInventoryUtils.INNER_SIZE; - return process(packet.clickType(), slot, packet.button(), slot >= 0 && slot < maxSize); + final int slot = packet.slot() == -999 ? -999 : + containerSize == null ? PlayerInventoryUtils.protocolToMinestom(packet.slot()) : packet.slot(); + final int maxSize = containerSize != null ? containerSize + PlayerInventoryUtils.INNER_SIZE : PlayerInventoryUtils.INVENTORY_SIZE; + if (packet.clickType() == ClientClickWindowPacket.ClickType.PICKUP && slot == -999) { + if (packet.button() == 0) return new Info.LeftDropCursor(); + if (packet.button() == 1) return new Info.RightDropCursor(); + if (packet.button() == 2) return new Info.MiddleDropCursor(); + } + final boolean valid = slot >= 0 && slot < maxSize; + if (!valid) return null; + return process(packet.clickType(), slot, packet.button()); } private boolean requireCreative(ClientClickWindowPacket packet) { final byte button = packet.button(); - final ClientClickWindowPacket.ClickType type = packet.clickType(); - return switch (type) { + return switch (packet.clickType()) { case CLONE -> true; case QUICK_CRAFT -> button == 8 || button == 9 || button == 10; default -> false; @@ -113,38 +114,18 @@ public final class Click { * @param type the type of the click * @param slot the clicked slot * @param button the sent button - * @param valid whether {@code slot} fits within the inventory (may be unused, depending on click) * @return the information about the click, or nothing if there was no immediately usable information */ - private @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, - int slot, byte button, boolean valid) { + private @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, int slot, byte button) { return switch (type) { - case PICKUP -> { - if (slot == -999) { - yield switch (button) { - case 0 -> new Info.LeftDropCursor(); - case 1 -> new Info.RightDropCursor(); - case 2 -> new Info.MiddleDropCursor(); - default -> null; - }; - } - - if (!valid) yield null; - - yield switch (button) { - case 0 -> new Info.Left(slot); - case 1 -> new Info.Right(slot); - default -> null; - }; - } - case QUICK_MOVE -> { - if (!valid) yield null; - yield button == 0 ? new Info.LeftShift(slot) : new Info.RightShift(slot); - } + case PICKUP -> switch (button) { + case 0 -> new Info.Left(slot); + case 1 -> new Info.Right(slot); + default -> null; + }; + case QUICK_MOVE -> button == 0 ? new Info.LeftShift(slot) : new Info.RightShift(slot); case SWAP -> { - if (!valid) { - yield null; - } else if (button >= 0 && button < 9) { + if (button >= 0 && button < 9) { yield new Info.HotbarSwap(button, slot); } else if (button == 40) { yield new Info.OffhandSwap(slot); @@ -152,8 +133,8 @@ public final class Click { yield null; } } - case CLONE -> valid ? new Info.Middle(slot) : null; - case THROW -> valid ? new Info.DropSlot(slot, button == 1) : null; + case CLONE -> new Info.Middle(slot); + case THROW -> new Info.DropSlot(slot, button == 1); case QUICK_CRAFT -> { // Handle drag finishes if (button == 2) { @@ -170,25 +151,18 @@ public final class Click { yield new Info.MiddleDrag(list); } - Consumer> tryAdd = list -> { - if (valid && !list.contains(slot)) { - list.add(slot); - } - }; - switch (button) { case 0 -> leftDrag.clear(); case 4 -> rightDrag.clear(); case 8 -> middleDrag.clear(); - case 1 -> tryAdd.accept(leftDrag); - case 5 -> tryAdd.accept(rightDrag); - case 9 -> tryAdd.accept(middleDrag); + case 1 -> leftDrag.add(slot); + case 5 -> rightDrag.add(slot); + case 9 -> middleDrag.add(slot); } - yield null; } - case PICKUP_ALL -> valid ? new Info.Double(slot) : null; + case PICKUP_ALL -> new Info.Double(slot); }; } } diff --git a/src/main/java/net/minestom/server/listener/WindowListener.java b/src/main/java/net/minestom/server/listener/WindowListener.java index 1014c65ec..cb887368a 100644 --- a/src/main/java/net/minestom/server/listener/WindowListener.java +++ b/src/main/java/net/minestom/server/listener/WindowListener.java @@ -23,9 +23,7 @@ public class WindowListener { if (inventory == null || packet.slot() == -1) return; Click.Preprocessor preprocessor = player.clickPreprocessor(); - final Click.Info info = playerInventory ? - preprocessor.processPlayerClick(packet, player.isCreative()) : - preprocessor.processContainerClick(packet, inventory.getSize(), player.isCreative()); + final Click.Info info = preprocessor.processClick(packet, player.isCreative(), playerInventory ? null : inventory.getSize()); if (info != null) inventory.handleClick(player, info); // (Why is the ping packet necessary?) diff --git a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java index d91cb2dff..da7c24ec8 100644 --- a/src/test/java/net/minestom/server/inventory/click/ClickUtils.java +++ b/src/test/java/net/minestom/server/inventory/click/ClickUtils.java @@ -105,11 +105,10 @@ public final class ClickUtils { return new ChangeResult(main, player, cursor, drops); } - } public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { - assertEquals(info, preprocessor.processContainerClick(packet, createInventory().getSize(), player.isCreative())); + assertEquals(info, preprocessor.processClick(packet, player.isCreative(), createInventory().getSize())); } public static void assertProcessed(@NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) { From 8bb3ece80310635a2d5e42e4afa6f73adf4f104d Mon Sep 17 00:00:00 2001 From: themode Date: Sun, 14 Apr 2024 22:57:21 +0200 Subject: [PATCH 12/46] Inline requireCreative --- .../server/inventory/click/Click.java | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/click/Click.java b/src/main/java/net/minestom/server/inventory/click/Click.java index 1f49e998e..8c166ceed 100644 --- a/src/main/java/net/minestom/server/inventory/click/Click.java +++ b/src/main/java/net/minestom/server/inventory/click/Click.java @@ -18,16 +18,24 @@ public final class Click { * The inventory used should be known from context. */ public sealed interface Info { - record Left(int slot) implements Info {} - record Right(int slot) implements Info {} + record Left(int slot) implements Info { + } + + record Right(int slot) implements Info { + } + record Middle(int slot) implements Info { // Creative only } - record LeftShift(int slot) implements Info {} - record RightShift(int slot) implements Info {} + record LeftShift(int slot) implements Info { + } - record Double(int slot) implements Info {} + record RightShift(int slot) implements Info { + } + + record Double(int slot) implements Info { + } record LeftDrag(List slots) implements Info { public LeftDrag { @@ -48,17 +56,29 @@ public final class Click { } } - record LeftDropCursor() implements Info {} - record RightDropCursor() implements Info {} - record MiddleDropCursor() implements Info {} + record LeftDropCursor() implements Info { + } - record DropSlot(int slot, boolean all) implements Info {} + record RightDropCursor() implements Info { + } - record HotbarSwap(int hotbarSlot, int clickedSlot) implements Info {} - record OffhandSwap(int slot) implements Info {} + record MiddleDropCursor() implements Info { + } - record CreativeSetItem(int slot, @NotNull ItemStack item) implements Info {} - record CreativeDropItem(@NotNull ItemStack item) implements Info {} + record DropSlot(int slot, boolean all) implements Info { + } + + record HotbarSwap(int hotbarSlot, int clickedSlot) implements Info { + } + + record OffhandSwap(int slot) implements Info { + } + + record CreativeSetItem(int slot, @NotNull ItemStack item) implements Info { + } + + record CreativeDropItem(@NotNull ItemStack item) implements Info { + } } /** @@ -85,27 +105,24 @@ public final class Click { * @return the information about the click, or nothing if there was no immediately usable information */ public @Nullable Click.Info processClick(@NotNull ClientClickWindowPacket packet, boolean isCreative, @Nullable Integer containerSize) { - if (requireCreative(packet) && !isCreative) return null; - final int slot = packet.slot() == -999 ? -999 : - containerSize == null ? PlayerInventoryUtils.protocolToMinestom(packet.slot()) : packet.slot(); - final int maxSize = containerSize != null ? containerSize + PlayerInventoryUtils.INNER_SIZE : PlayerInventoryUtils.INVENTORY_SIZE; - if (packet.clickType() == ClientClickWindowPacket.ClickType.PICKUP && slot == -999) { - if (packet.button() == 0) return new Info.LeftDropCursor(); - if (packet.button() == 1) return new Info.RightDropCursor(); - if (packet.button() == 2) return new Info.MiddleDropCursor(); - } - final boolean valid = slot >= 0 && slot < maxSize; - if (!valid) return null; - return process(packet.clickType(), slot, packet.button()); - } - - private boolean requireCreative(ClientClickWindowPacket packet) { final byte button = packet.button(); - return switch (packet.clickType()) { + final boolean requireCreative = switch (packet.clickType()) { case CLONE -> true; case QUICK_CRAFT -> button == 8 || button == 9 || button == 10; default -> false; }; + if (requireCreative && !isCreative) return null; + final int slot = packet.slot() == -999 ? -999 : + containerSize == null ? PlayerInventoryUtils.protocolToMinestom(packet.slot()) : packet.slot(); + final int maxSize = containerSize != null ? containerSize + PlayerInventoryUtils.INNER_SIZE : PlayerInventoryUtils.INVENTORY_SIZE; + if (packet.clickType() == ClientClickWindowPacket.ClickType.PICKUP && slot == -999) { + if (button == 0) return new Info.LeftDropCursor(); + if (button == 1) return new Info.RightDropCursor(); + if (button == 2) return new Info.MiddleDropCursor(); + } + final boolean valid = slot >= 0 && slot < maxSize; + if (!valid) return null; + return process(packet.clickType(), slot, button); } /** From 41b442eab852728608f92dbeccb3013d5e07d868 Mon Sep 17 00:00:00 2001 From: themode Date: Mon, 15 Apr 2024 00:03:37 +0200 Subject: [PATCH 13/46] Remove fastutil pair --- .../server/inventory/TransactionOperator.java | 61 ++++++++++--------- .../server/inventory/TransactionOption.java | 8 +-- .../server/inventory/TransactionType.java | 52 ++++++++++------ .../inventory/click/ClickProcessors.java | 53 ++++++++-------- 4 files changed, 97 insertions(+), 77 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/TransactionOperator.java b/src/main/java/net/minestom/server/inventory/TransactionOperator.java index df298e6fe..21204cfc7 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionOperator.java +++ b/src/main/java/net/minestom/server/inventory/TransactionOperator.java @@ -1,12 +1,11 @@ package net.minestom.server.inventory; -import it.unimi.dsi.fastutil.Pair; import net.minestom.server.item.ItemStack; import net.minestom.server.item.StackingRule; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.function.BiPredicate; +import java.util.function.UnaryOperator; /** * A transaction operator is a simpler operation that takes two items and returns two items. @@ -14,22 +13,26 @@ import java.util.function.BiPredicate; * This allows a significant amount of logic reuse, since many operations are just the {@link #flip(TransactionOperator) flipped} * version of others. */ -public interface TransactionOperator { +public interface TransactionOperator extends UnaryOperator { /** * Creates a new operator that filters the given one using the provided predicate */ static @NotNull TransactionOperator filter(@NotNull TransactionOperator operator, @NotNull BiPredicate predicate) { - return (left, right) -> predicate.test(left, right) ? operator.apply(left, right) : null; + return (entry) -> { + final ItemStack left = entry.left(); + final ItemStack right = entry.right(); + return predicate.test(left, right) ? operator.apply(entry) : null; + }; } /** * Creates a new operator that flips the left and right before providing it to the given operator. */ static @NotNull TransactionOperator flip(@NotNull TransactionOperator operator) { - return (left, right) -> { - var pair = operator.apply(right, left); - return pair != null ? Pair.of(pair.right(), pair.left()) : null; + return (entry) -> { + final Entry pair = operator.apply(entry.reverse()); + return pair != null ? new Entry(pair.right(), pair.left()) : null; }; } @@ -37,7 +40,9 @@ public interface TransactionOperator { * Provides operators that try to stack up to the provided number of items to the left. */ static @NotNull TransactionOperator stackLeftN(int count) { - return (left, right) -> { + return (entry) -> { + final ItemStack left = entry.left(); + final ItemStack right = entry.right(); final StackingRule rule = StackingRule.get(); // Quick exit if the right is air (nothing can be transferred anyway) @@ -54,7 +59,7 @@ public interface TransactionOperator { if (addedAmount == 0) return null; - return Pair.of(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); + return new Entry(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); }; } @@ -62,7 +67,9 @@ public interface TransactionOperator { * Stacks as many items to the left as possible, including if the left is an air item.
* This will not swap the items if they are of different types. */ - TransactionOperator STACK_LEFT = (left, right) -> { + TransactionOperator STACK_LEFT = (entry) -> { + final ItemStack left = entry.left(); + final ItemStack right = entry.right(); final StackingRule rule = StackingRule.get(); // Quick exit if the right is air (nothing can be transferred anyway) @@ -77,7 +84,7 @@ public interface TransactionOperator { int addedAmount = Math.min(rightAmount, rule.getMaxSize(left) - leftAmount); - return Pair.of(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); + return new Entry(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); }; /** @@ -89,28 +96,26 @@ public interface TransactionOperator { * Takes as many items as possible from both stacks, if the given items are stackable. * This is a symmetric operation. */ - TransactionOperator TAKE = (left, right) -> { + TransactionOperator TAKE = (entry) -> { + final ItemStack left = entry.left(); + final ItemStack right = entry.right(); final StackingRule rule = StackingRule.get(); - if (right.isAir() || !rule.canBeStacked(left, right)) { return null; } - - int leftAmount = rule.getAmount(left); - int rightAmount = rule.getAmount(right); - - int subtracted = Math.min(leftAmount, rightAmount); - - return Pair.of(rule.apply(left, leftAmount - subtracted), rule.apply(right, rightAmount - subtracted)); + final int leftAmount = rule.getAmount(left); + final int rightAmount = rule.getAmount(right); + final int subtracted = Math.min(leftAmount, rightAmount); + return new Entry(rule.apply(left, leftAmount - subtracted), rule.apply(right, rightAmount - subtracted)); }; - /** - * Applies this operation to the two given items. - * They are unnamed as to abstract the operations performed from inventories. - * @param left the "left" item - * @param right the "right" item - * @return the resulting pair, or null to indicate no changes - */ - @Nullable Pair apply(@NotNull ItemStack left, @NotNull ItemStack right); + default Entry apply(@NotNull ItemStack left, @NotNull ItemStack right) { + return apply(new Entry(left, right)); + } + record Entry(@NotNull ItemStack left, @NotNull ItemStack right) { + public Entry reverse() { + return new Entry(right, left); + } + } } diff --git a/src/main/java/net/minestom/server/inventory/TransactionOption.java b/src/main/java/net/minestom/server/inventory/TransactionOption.java index 707822439..dd321c1b6 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionOption.java +++ b/src/main/java/net/minestom/server/inventory/TransactionOption.java @@ -1,6 +1,5 @@ package net.minestom.server.inventory; -import it.unimi.dsi.fastutil.Pair; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; @@ -38,10 +37,11 @@ public interface TransactionOption { */ TransactionOption DRY_RUN = (inventory, result, itemChangesMap) -> result.isAir(); - @NotNull T fill(@NotNull Inventory inventory, @NotNull ItemStack result, @NotNull Map itemChangesMap); + @NotNull + T fill(@NotNull Inventory inventory, @NotNull ItemStack result, @NotNull Map itemChangesMap); default @NotNull T fill(@NotNull TransactionType type, @NotNull Inventory inventory, @NotNull ItemStack itemStack) { - Pair> result = type.process(itemStack, inventory::getItemStack); - return fill(inventory, result.left(), result.right()); + final TransactionType.Entry result = type.apply(itemStack, inventory::getItemStack); + return fill(inventory, result.remaining(), result.changes()); } } diff --git a/src/main/java/net/minestom/server/inventory/TransactionType.java b/src/main/java/net/minestom/server/inventory/TransactionType.java index 73364b519..44f14a342 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionType.java +++ b/src/main/java/net/minestom/server/inventory/TransactionType.java @@ -1,6 +1,5 @@ package net.minestom.server.inventory; -import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minestom.server.item.ItemStack; @@ -8,12 +7,13 @@ import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Map; +import java.util.function.BiFunction; import java.util.function.IntFunction; /** * Represents a type of transaction that you can apply to an {@link Inventory}. */ -public interface TransactionType { +public interface TransactionType extends BiFunction<@NotNull ItemStack, @NotNull IntFunction, TransactionType.@NotNull Entry> { /** * Applies a transaction operator to a given list of slots, turning it into a TransactionType. @@ -22,16 +22,14 @@ public interface TransactionType { return (item, getter) -> { Int2ObjectMap map = new Int2ObjectArrayMap<>(); for (int slot : slots) { - ItemStack slotItem = getter.apply(slot); - - Pair result = operator.apply(slotItem, item); + final ItemStack slotItem = getter.apply(slot); + final TransactionOperator.Entry result = operator.apply(slotItem, item); if (result == null) continue; - map.put(slot, result.first()); - item = result.second(); + map.put(slot, result.left()); + item = result.right(); } - - return Pair.of(item, map); + return new Entry(item, map); }; } @@ -43,14 +41,13 @@ public interface TransactionType { static @NotNull TransactionType join(@NotNull TransactionType first, @NotNull TransactionType second) { return (item, getter) -> { // Calculate results - Pair> f = first.process(item, getter); - Pair> s = second.process(f.left(), getter); - + final Entry f = first.apply(item, getter); + final Entry s = second.apply(f.remaining(), getter); // Join results Map map = new Int2ObjectArrayMap<>(); - map.putAll(f.right()); - map.putAll(s.right()); - return Pair.of(s.left(), map); + map.putAll(f.changes()); + map.putAll(s.changes()); + return new Entry(s.remaining(), map); }; } @@ -59,17 +56,26 @@ public interface TransactionType { * Can either take an air slot or be stacked. * * @param fill the list of slots that will be added to if they already have some of the item in it - * @param air the list of slots that will be added to if they have air (may be different from {@code fill}). + * @param air the list of slots that will be added to if they have air (may be different from {@code fill}). */ static @NotNull TransactionType add(@NotNull List fill, @NotNull List air) { - var first = general((slotItem, extra) -> !slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null, fill); - var second = general((slotItem, extra) -> slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null, air); + final TransactionType first = general(entry -> { + final ItemStack slotItem = entry.left(); + final ItemStack extra = entry.right(); + return !slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null; + }, fill); + final TransactionType second = general(entry -> { + final ItemStack slotItem = entry.left(); + final ItemStack extra = entry.right(); + return slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null; + }, air); return TransactionType.join(first, second); } /** * Takes an item from the inventory. * Can either transform items to air or reduce their amount. + * * @param takeSlots the ordered list of slots that will be taken from (if possible) */ static @NotNull TransactionType take(@NotNull List takeSlots) { @@ -78,10 +84,18 @@ public interface TransactionType { /** * Processes the provided item into the given inventory. + * * @param itemStack the item to process * @param inventory the inventory function * @return the remaining portion of the processed item, as well as the changes */ - @NotNull Pair> process(@NotNull ItemStack itemStack, @NotNull IntFunction inventory); + @Override + @NotNull + Entry apply(@NotNull ItemStack itemStack, @NotNull IntFunction inventory); + record Entry(@NotNull ItemStack remaining, @NotNull Map<@NotNull Integer, @NotNull ItemStack> changes) { + public Entry { + changes = Map.copyOf(changes); + } + } } diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index 57bc1f0dc..5d77f98e4 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -1,14 +1,16 @@ package net.minestom.server.inventory.click; -import it.unimi.dsi.fastutil.Pair; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.inventory.InventoryType; import net.minestom.server.inventory.TransactionOperator; import net.minestom.server.inventory.TransactionType; +import net.minestom.server.inventory.click.Click.Change.Cursor; +import net.minestom.server.inventory.click.Click.Change.DropFromPlayer; +import net.minestom.server.inventory.click.Click.Change.Main; +import net.minestom.server.inventory.click.Click.Change.Player; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.item.StackingRule; -import net.minestom.server.inventory.click.Click.Change.*; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; @@ -33,7 +35,7 @@ public final class ClickProcessors { final ItemStack cursor = getter.cursor(); final ItemStack clickedItem = getter.get(slot); - Pair pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); + final TransactionOperator.Entry pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); if (pair != null) { // Stackable items, combine their counts return List.of(new Main(slot, pair.left()), new Cursor(pair.right())); } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them @@ -50,11 +52,11 @@ public final class ClickProcessors { if (cursor.isAir()) { // Take half (rounded up) of the clicked item int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); - Pair cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); + final TransactionOperator.Entry cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); if (cursorSlot == null) return List.of(); return List.of(new Main(slot, cursorSlot.right()), new Cursor(cursorSlot.left())); } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over - Pair slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); + final TransactionOperator.Entry slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); if (slotCursor == null) return List.of(); return List.of(new Main(slot, slotCursor.left()), new Cursor(slotCursor.right())); } else { // Two existing of items of different types, so switch @@ -63,9 +65,8 @@ public final class ClickProcessors { } public static @NotNull List middleClick(int slot, @NotNull Click.Getter getter) { - var item = getter.get(slot); + final ItemStack item = getter.get(slot); if (!getter.cursor().isAir() || item.isAir()) return List.of(); - return List.of(new Cursor(RULE.apply(item, RULE.getMaxSize(item)))); } @@ -75,12 +76,12 @@ public final class ClickProcessors { slots = new ArrayList<>(slots); slots.removeIf(i -> i == slot); - Pair> result = TransactionType.add(slots, slots).process(clicked, getter::get); + final TransactionType.Entry result = TransactionType.add(slots, slots).apply(clicked, getter::get); List changes = new ArrayList<>(); - result.right().forEach((slotId, item) -> changes.add(new Main(slotId, item))); + result.changes().forEach((slotId, item) -> changes.add(new Main(slotId, item))); - if (!result.left().equals(clicked)) { - changes.add(new Main(slot, result.left())); + if (!result.remaining().equals(clicked)) { + changes.add(new Main(slot, result.remaining())); } return changes; @@ -90,15 +91,17 @@ public final class ClickProcessors { final ItemStack cursor = getter.cursor(); if (cursor.isAir()) return List.of(); - var unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); - var stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); + final TransactionType unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, + (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); + final TransactionType stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, + (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); - Pair> result = TransactionType.join(unstacked, stacked).process(cursor, getter::get); + final TransactionType.Entry result = TransactionType.join(unstacked, stacked).apply(cursor, getter::get); List changes = new ArrayList<>(); - result.right().forEach((slotId, item) -> changes.add(new Main(slotId, item))); + result.changes().forEach((slotId, item) -> changes.add(new Main(slotId, item))); - if (!result.left().equals(cursor)) { - changes.add(new Cursor(result.left())); + if (!result.remaining().equals(cursor)) { + changes.add(new Cursor(result.remaining())); } return changes; @@ -108,12 +111,12 @@ public final class ClickProcessors { final ItemStack cursor = getter.cursor(); if (cursor.isAir()) return List.of(); - Pair> result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).process(cursor, getter::get); + final TransactionType.Entry result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).apply(cursor, getter::get); List changes = new ArrayList<>(); - result.right().forEach((slotId, item) -> changes.add(new Main(slotId, item))); + result.changes().forEach((slotId, item) -> changes.add(new Main(slotId, item))); - if (!result.left().equals(cursor)) { - changes.add(new Cursor(result.left())); + if (!result.remaining().equals(cursor)) { + changes.add(new Cursor(result.remaining())); } return changes; @@ -121,7 +124,6 @@ public final class ClickProcessors { public static @NotNull List middleDragClick(@NotNull List slots, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); - return slots.stream() .filter(slot -> getter.get(slot).isAir()) .map(slot -> (Click.Change) new Main(slot, cursor)) @@ -132,7 +134,7 @@ public final class ClickProcessors { final ItemStack cursor = getter.cursor(); if (cursor.isAir()) return List.of(); // Do nothing - var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); + final TransactionOperator.Entry pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor); if (pair == null) return List.of(); return List.of(new Cursor(pair.right()), new DropFromPlayer(pair.left())); @@ -142,7 +144,7 @@ public final class ClickProcessors { final ItemStack item = getter.get(slot); if (item.isAir()) return List.of(); // Do nothing - var pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); + final TransactionOperator.Entry pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item); if (pair == null) return List.of(); return List.of(new Main(slot, pair.right()), new DropFromPlayer(pair.left())); @@ -196,8 +198,7 @@ public final class ClickProcessors { yield List.of(new Main(slot, offhandItem), new Player(OFF_HAND_SLOT, selectedItem)); } case Click.Info.CreativeSetItem(int slot, ItemStack item) -> List.of(new Main(slot, item)); - case Click.Info.CreativeDropItem(ItemStack item) -> - List.of(new DropFromPlayer(item)); + case Click.Info.CreativeDropItem(ItemStack item) -> List.of(new DropFromPlayer(item)); }; } From 5188c15245efb4ceabe1e314fc45198805450753 Mon Sep 17 00:00:00 2001 From: mworzala Date: Sun, 17 Mar 2024 16:07:29 -0400 Subject: [PATCH 14/46] feat: initial conversion to adventure nbt. no tests, no anvil --- build.gradle.kts | 3 - .../minestom/demo/block/CampfireHandler.java | 31 +- gradle/libs.versions.toml | 14 +- .../server/adventure/MinestomAdventure.java | 13 +- .../NBTLegacyHoverEventSerializer.java | 91 +- .../nbt/NbtComponentSerializer.java | 4 +- .../nbt/NbtComponentSerializerImpl.java | 233 +++-- .../minecraft/ArgumentItemStack.java | 13 +- .../minecraft/ArgumentNbtCompoundTag.java | 23 +- .../arguments/minecraft/ArgumentNbtTag.java | 21 +- .../net/minestom/server/entity/Metadata.java | 4 +- .../minestom/server/entity/MetadataImpl.java | 4 +- .../server/entity/damage/DamageType.java | 8 +- .../server/entity/damage/DamageTypeImpl.java | 47 +- .../server/entity/metadata/PlayerMeta.java | 17 +- .../minestom/server/instance/AnvilLoader.java | 797 +++++++++--------- .../server/instance/DynamicChunk.java | 14 +- .../server/instance/ExplosionSupplier.java | 6 +- .../minestom/server/instance/Instance.java | 4 +- .../server/instance/InstanceContainer.java | 4 +- .../server/instance/LightingChunk.java | 15 +- .../minestom/server/instance/block/Block.java | 8 +- .../server/instance/block/BlockImpl.java | 19 +- .../net/minestom/server/item/ItemMeta.java | 4 +- .../minestom/server/item/ItemMetaImpl.java | 20 +- .../net/minestom/server/item/ItemStack.java | 24 +- .../minestom/server/item/ItemStackImpl.java | 21 +- .../server/item/armor/TrimManager.java | 53 +- .../server/item/armor/TrimMaterial.java | 6 +- .../server/item/armor/TrimMaterialImpl.java | 26 +- .../server/item/armor/TrimPattern.java | 6 +- .../server/item/armor/TrimPatternImpl.java | 17 +- .../server/item/firework/FireworkEffect.java | 41 +- .../server/item/metadata/PlayerHeadMeta.java | 24 +- .../listener/PlayerDiggingListener.java | 4 +- .../minestom/server/message/Messenger.java | 17 +- .../server/network/ConnectionManager.java | 21 +- .../server/network/NetworkBuffer.java | 12 +- .../configuration/RegistryDataPacket.java | 6 +- .../server/play/BlockEntityDataPacket.java | 6 +- .../server/play/EntityEffectPacket.java | 6 +- .../server/play/NbtQueryResponsePacket.java | 7 +- .../packet/server/play/data/ChunkData.java | 10 +- .../server/permission/Permission.java | 10 +- .../server/permission/PermissionHandler.java | 5 +- .../server/permission/PermissionVerifier.java | 6 +- .../net/minestom/server/tag/Serializers.java | 47 +- .../java/net/minestom/server/tag/Tag.java | 58 +- .../net/minestom/server/tag/TagHandler.java | 17 +- .../minestom/server/tag/TagHandlerImpl.java | 72 +- .../minestom/server/tag/TagNbtSeparator.java | 48 +- .../net/minestom/server/tag/TagRecord.java | 8 +- .../minestom/server/tag/TagSerializer.java | 8 +- .../server/tag/TagSerializerImpl.java | 14 +- .../net/minestom/server/utils/NBTUtils.java | 59 ++ .../server/utils/binary/BinaryBuffer.java | 4 - .../server/utils/binary/BinaryReader.java | 4 +- .../server/utils/binary/BinaryWriter.java | 4 +- .../server/utils/block/BlockUtils.java | 24 +- .../minestom/server/world/DimensionType.java | 87 +- .../server/world/DimensionTypeManager.java | 26 +- .../minestom/server/world/biomes/Biome.java | 23 +- .../server/world/biomes/BiomeEffects.java | 85 +- .../server/world/biomes/BiomeManager.java | 31 +- .../server/world/biomes/BiomeParticle.java | 61 +- 65 files changed, 1195 insertions(+), 1230 deletions(-) create mode 100644 src/main/java/net/minestom/server/utils/NBTUtils.java diff --git a/build.gradle.kts b/build.gradle.kts index 7140bb97d..b1916f02d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -72,10 +72,7 @@ dependencies { api(libs.jetbrainsAnnotations) api(libs.bundles.adventure) api(libs.hydrazine) - api(libs.bundles.kotlin) - api(libs.bundles.hephaistos) implementation(libs.minestomData) - implementation(libs.dependencyGetter) // Performance/data structures implementation(libs.caffeine) diff --git a/demo/src/main/java/net/minestom/demo/block/CampfireHandler.java b/demo/src/main/java/net/minestom/demo/block/CampfireHandler.java index c399993e3..663c10914 100644 --- a/demo/src/main/java/net/minestom/demo/block/CampfireHandler.java +++ b/demo/src/main/java/net/minestom/demo/block/CampfireHandler.java @@ -1,5 +1,9 @@ package net.minestom.demo.block; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; @@ -10,10 +14,6 @@ import net.minestom.server.tag.TagWritable; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTList; -import org.jglrxavpok.hephaistos.nbt.NBTType; import java.util.ArrayList; import java.util.Collection; @@ -22,16 +22,17 @@ import java.util.List; public class CampfireHandler implements BlockHandler { public static final Tag> ITEMS = Tag.View(new TagSerializer<>() { - private final Tag internal = Tag.NBT("Items"); + private final Tag internal = Tag.NBT("Items"); @Override public @Nullable List read(@NotNull TagReadable reader) { - NBTList item = (NBTList) reader.getTag(internal); + ListBinaryTag item = (ListBinaryTag) reader.getTag(internal); if (item == null) return null; List result = new ArrayList<>(); - item.forEach(nbtCompound -> { - int amount = nbtCompound.getAsByte("Count"); + item.forEach(childTag -> { + CompoundBinaryTag nbtCompound = (CompoundBinaryTag) childTag; + int amount = nbtCompound.getByte("Count"); String id = nbtCompound.getString("id"); Material material = Material.fromNamespaceId(id); result.add(ItemStack.of(material, amount)); @@ -45,14 +46,14 @@ public class CampfireHandler implements BlockHandler { writer.removeTag(internal); return; } - writer.setTag(internal, NBT.List( - NBTType.TAG_Compound, + writer.setTag(internal, ListBinaryTag.listBinaryTag( + BinaryTagTypes.COMPOUND, value.stream() - .map(item -> NBT.Compound(nbt -> { - nbt.setByte("Count", (byte) item.amount()); - nbt.setByte("Slot", (byte) 1); - nbt.setString("id", item.material().name()); - })) + .map(item -> (BinaryTag) CompoundBinaryTag.builder() + .putByte("Count", (byte) item.amount()) + .putByte("Slot", (byte) 1) + .putString("id", item.material().name()) + .build()) .toList() )); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fb1a5af9d..8230be930 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ metadata.format.version = "1.1" # Important dependencies data = "1.20.4-rv4" -adventure = "4.15.0" +adventure = "4.16.0" kotlin = "1.7.22" dependencyGetter = "v1.0.1" hydrazine = "1.7.2" @@ -41,22 +41,16 @@ nexuspublish = "1.3.0" # Important Dependencies # Adventure adventure-api = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" } +adventure-nbt = { group = "net.kyori", name = "adventure-nbt", version.ref = "adventure" } adventure-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" } adventure-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" } adventure-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" } adventure-text-logger-slf4j = { group = "net.kyori", name = "adventure-text-logger-slf4j", version.ref = "adventure" } -# Kotlin -kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } -kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } - # Miscellaneous hydrazine = { group = "com.github.MadMartian", name = "hydrazine-path-finding", version.ref = "hydrazine" } minestomData = { group = "net.minestom", name = "data", version.ref = "data" } -dependencyGetter = { group = "com.github.Minestom", name = "DependencyGetter", version.ref = "dependencyGetter" } jetbrainsAnnotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrainsAnnotations" } -hephaistos-common = { group = "io.github.jglrxavpok.hephaistos", name = "common", version.ref = "hephaistos" } -hephaistos-gson = { group = "io.github.jglrxavpok.hephaistos", name = "gson", version.ref = "hephaistos" } slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j"} # Performance / Data Structures @@ -86,11 +80,9 @@ logback-classic = { group = "ch.qos.logback", name = "logback-classic", version. [bundles] -kotlin = ["kotlin-stdlib-jdk8", "kotlin-reflect"] flare = ["flare", "flare-fastutil"] -adventure = ["adventure-api", "adventure-serializer-gson", "adventure-serializer-legacy", "adventure-serializer-plain", "adventure-text-logger-slf4j"] +adventure = ["adventure-api", "adventure-nbt", "adventure-serializer-gson", "adventure-serializer-legacy", "adventure-serializer-plain", "adventure-text-logger-slf4j"] junit = ["junit-api", "junit-engine", "junit-params", "junit-suite-api", "junit-suite-engine"] -hephaistos = ["hephaistos-common", "hephaistos-gson"] logback = ["logback-core", "logback-classic"] [plugins] diff --git a/src/main/java/net/minestom/server/adventure/MinestomAdventure.java b/src/main/java/net/minestom/server/adventure/MinestomAdventure.java index fe62f85e6..10ffcdfa7 100644 --- a/src/main/java/net/minestom/server/adventure/MinestomAdventure.java +++ b/src/main/java/net/minestom/server/adventure/MinestomAdventure.java @@ -1,21 +1,18 @@ package net.minestom.server.adventure; -import java.io.StringReader; - +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.TagStringIO; import net.kyori.adventure.text.Component; import net.kyori.adventure.translation.GlobalTranslator; import net.kyori.adventure.util.Codec; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.util.Locale; import java.util.Objects; import java.util.function.BiFunction; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTException; -import org.jglrxavpok.hephaistos.parser.SNBTParser; - /** * Adventure related constants, etc. */ @@ -23,8 +20,8 @@ public final class MinestomAdventure { /** * A codec to convert between strings and NBT. */ - public static final Codec NBT_CODEC - = Codec.codec(encoded -> new SNBTParser(new StringReader(encoded)).parse(), NBT::toSNBT); + public static final Codec NBT_CODEC + = Codec.codec(TagStringIO.get()::asCompound, TagStringIO.get()::asString); /** * If components should be automatically translated in outgoing packets. diff --git a/src/main/java/net/minestom/server/adventure/provider/NBTLegacyHoverEventSerializer.java b/src/main/java/net/minestom/server/adventure/provider/NBTLegacyHoverEventSerializer.java index 690592629..12770ffe6 100644 --- a/src/main/java/net/minestom/server/adventure/provider/NBTLegacyHoverEventSerializer.java +++ b/src/main/java/net/minestom/server/adventure/provider/NBTLegacyHoverEventSerializer.java @@ -1,6 +1,7 @@ package net.minestom.server.adventure.provider; import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; @@ -9,14 +10,10 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.util.Codec; import net.minestom.server.adventure.MinestomAdventure; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTException; import java.io.IOException; import java.util.Objects; import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer { static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer(); @@ -30,76 +27,46 @@ final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer @Override public HoverEvent.@NotNull ShowItem deserializeShowItem(@NotNull Component input) throws IOException { final String raw = PlainTextComponentSerializer.plainText().serialize(input); - try { - // attempt the parse - final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw); - if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound"); - final NBTCompound tag = contents.getCompound(ITEM_TAG); + // attempt the parse + final CompoundBinaryTag contents = MinestomAdventure.NBT_CODEC.decode(raw); + final CompoundBinaryTag tag = contents.getCompound(ITEM_TAG); - // create the event - return HoverEvent.ShowItem.showItem( - Key.key(Objects.requireNonNullElse(contents.getString(ITEM_TYPE), "")), - Objects.requireNonNullElse(contents.getByte(ITEM_COUNT), (byte) 1), - tag == null ? null : BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC) - ); - } catch (final NBTException e) { - throw new IOException(e); - } + // create the event + return HoverEvent.ShowItem.showItem( + Key.key(contents.getString(ITEM_TYPE, "")), + contents.getByte(ITEM_COUNT, (byte) 1), + tag.size() == 0 ? null : BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC) + ); } @Override public HoverEvent.@NotNull ShowEntity deserializeShowEntity(@NotNull Component input, Codec.Decoder componentDecoder) throws IOException { final String raw = PlainTextComponentSerializer.plainText().serialize(input); - try { - final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw); - if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound"); - - return HoverEvent.ShowEntity.showEntity( - Key.key(Objects.requireNonNullElse(contents.getString(ENTITY_TYPE), "")), - UUID.fromString(Objects.requireNonNullElse(contents.getString(ENTITY_ID), "")), - componentDecoder.decode(Objects.requireNonNullElse(contents.getString(ENTITY_NAME), "")) - ); - } catch (NBTException e) { - throw new IOException(e); - } + final CompoundBinaryTag contents = MinestomAdventure.NBT_CODEC.decode(raw); + return HoverEvent.ShowEntity.showEntity( + Key.key(contents.getString(ENTITY_TYPE, "")), + UUID.fromString(Objects.requireNonNullElse(contents.getString(ENTITY_ID), "")), + componentDecoder.decode(Objects.requireNonNullElse(contents.getString(ENTITY_NAME), "")) + ); } @Override public @NotNull Component serializeShowItem(HoverEvent.@NotNull ShowItem input) throws IOException { - AtomicReference exception = new AtomicReference<>(null); - final NBTCompound tag = NBT.Compound(t -> { - t.setString(ITEM_TYPE, input.item().asString()); - t.setByte(ITEM_COUNT, (byte) input.count()); - - final BinaryTagHolder nbt = input.nbt(); - if (nbt != null) { - try { - t.set(ITEM_TAG, nbt.get(MinestomAdventure.NBT_CODEC)); - } catch (NBTException e) { - exception.set(e); - } - } - }); - - if (exception.get() != null) { - throw new IOException(exception.get()); - } - - return Component.text(MinestomAdventure.NBT_CODEC.encode(tag)); + CompoundBinaryTag.Builder tag = CompoundBinaryTag.builder(); + tag.putString(ITEM_TYPE, input.item().asString()); + tag.putByte(ITEM_COUNT, (byte) input.count()); + final BinaryTagHolder nbt = input.nbt(); + if (nbt != null) tag.put(ITEM_TAG, nbt.get(MinestomAdventure.NBT_CODEC)); + return Component.text(MinestomAdventure.NBT_CODEC.encode(tag.build())); } @Override - public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, Codec.Encoder componentEncoder) { - final NBTCompound tag = NBT.Compound(t -> { - t.setString(ENTITY_ID, input.id().toString()); - t.setString(ENTITY_TYPE, input.type().asString()); - - final Component name = input.name(); - if (name != null) { - t.setString(ENTITY_NAME, componentEncoder.encode(name)); - } - }); - - return Component.text(MinestomAdventure.NBT_CODEC.encode(tag)); + public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, Codec.Encoder componentEncoder) throws IOException { + CompoundBinaryTag.Builder tag = CompoundBinaryTag.builder(); + tag.putString(ENTITY_ID, input.id().toString()); + tag.putString(ENTITY_TYPE, input.type().asString()); + final Component name = input.name(); + if (name != null) tag.putString(ENTITY_NAME, componentEncoder.encode(name)); + return Component.text(MinestomAdventure.NBT_CODEC.encode(tag.build())); } } diff --git a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializer.java b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializer.java index a397352e9..e707655ae 100644 --- a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializer.java +++ b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializer.java @@ -1,11 +1,11 @@ package net.minestom.server.adventure.serializer.nbt; +import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.ComponentSerializer; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -public interface NbtComponentSerializer extends ComponentSerializer { +public interface NbtComponentSerializer extends ComponentSerializer { static @NotNull NbtComponentSerializer nbt() { return NbtComponentSerializerImpl.INSTANCE; } diff --git a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java index 5920920c5..b459b05da 100644 --- a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java +++ b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java @@ -1,6 +1,7 @@ package net.minestom.server.adventure.serializer.nbt; import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.*; import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.*; import net.kyori.adventure.text.event.ClickEvent; @@ -12,14 +13,10 @@ import net.kyori.adventure.text.format.TextDecoration; import net.minestom.server.utils.validate.Check; import org.intellij.lang.annotations.Subst; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTList; -import org.jglrxavpok.hephaistos.nbt.NBTType; -import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound; import java.util.ArrayList; import java.util.Collection; +import java.util.Set; import java.util.UUID; //todo write tests for me!! @@ -27,19 +24,19 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { static final NbtComponentSerializer INSTANCE = new NbtComponentSerializerImpl(); @Override - public @NotNull Component deserialize(@NotNull NBT input) { + public @NotNull Component deserialize(@NotNull BinaryTag input) { return deserializeAnyComponent(input); } @Override - public @NotNull NBT serialize(@NotNull Component component) { + public @NotNull BinaryTag serialize(@NotNull Component component) { return serializeComponent(component); } // DESERIALIZATION - private @NotNull Component deserializeAnyComponent(@NotNull NBT nbt) { - if (nbt instanceof NBTCompound compound) { + private @NotNull Component deserializeAnyComponent(@NotNull BinaryTag nbt) { + if (nbt instanceof CompoundBinaryTag compound) { return deserializeComponent(compound); } else { //todo raw string + list @@ -47,12 +44,12 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { } } - private @NotNull Component deserializeComponent(@NotNull NBTCompound compound) { + private @NotNull Component deserializeComponent(@NotNull CompoundBinaryTag compound) { ComponentBuilder builder; - var type = compound.getString("type"); - if (type != null) { + var type = compound.get("type"); + if (type instanceof StringBinaryTag sType) { // If type is specified, use that - builder = switch (type) { + builder = switch (sType.value()) { case "text" -> deserializeTextComponent(compound); case "translatable" -> deserializeTranslatableComponent(compound); case "score" -> deserializeScoreComponent(compound); @@ -63,26 +60,25 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { }; } else { // Try to infer the type from the fields present. - if (compound.containsKey("text")) { + Set keys = compound.keySet(); + if (keys.contains("text")) { builder = deserializeTextComponent(compound); - } else if (compound.containsKey("translate")) { + } else if (keys.contains("translate")) { builder = deserializeTranslatableComponent(compound); - } else if (compound.containsKey("score")) { + } else if (keys.contains("score")) { builder = deserializeScoreComponent(compound); - } else if (compound.containsKey("selector")) { + } else if (keys.contains("selector")) { builder = deserializeSelectorComponent(compound); - } else if (compound.containsKey("keybind")) { + } else if (keys.contains("keybind")) { builder = deserializeKeybindComponent(compound); - } else if (compound.containsKey("nbt")) { + } else if (keys.contains("nbt")) { builder = deserializeNbtComponent(compound); } else throw new UnsupportedOperationException("Unable to infer component type"); } // Children - var extra = compound.getList("extra"); - Check.argCondition(extra != null && !extra.getSubtagType().equals(NBTType.TAG_Compound), - "Extra field must be a list of compounds"); - if (extra != null) { + var extra = compound.getList("extra", BinaryTagTypes.COMPOUND); + if (extra.size() > 0) { var list = new ArrayList(); for (var child : extra) list.add(deserializeAnyComponent(child)); builder.append(list); @@ -91,7 +87,7 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { // Formatting var style = Style.style(); var color = compound.getString("color"); - if (color != null) { + if (!color.isEmpty()) { var hexColor = TextColor.fromHexString(color); if (hexColor != null) { style.color(hexColor); @@ -105,57 +101,60 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { } } @Subst("minecraft:default") var font = compound.getString("font"); - if (font != null) style.font(Key.key(font)); - var bold = compound.getByte("bold"); - if (bold != null) style.decoration(TextDecoration.BOLD, bold == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); - var italic = compound.getByte("italic"); - if (italic != null) style.decoration(TextDecoration.ITALIC, italic == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); - var underlined = compound.getByte("underlined"); - if (underlined != null) style.decoration(TextDecoration.UNDERLINED, underlined == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); - var strikethrough = compound.getByte("strikethrough"); - if (strikethrough != null) style.decoration(TextDecoration.STRIKETHROUGH, strikethrough == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); - var obfuscated = compound.getByte("obfuscated"); - if (obfuscated != null) style.decoration(TextDecoration.OBFUSCATED, obfuscated == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); + if (!font.isEmpty()) style.font(Key.key(font)); + BinaryTag bold = compound.get("bold"); + if (bold instanceof ByteBinaryTag b) + style.decoration(TextDecoration.BOLD, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); + BinaryTag italic = compound.get("italic"); + if (italic instanceof ByteBinaryTag b) + style.decoration(TextDecoration.ITALIC, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); + BinaryTag underlined = compound.get("underlined"); + if (underlined instanceof ByteBinaryTag b) + style.decoration(TextDecoration.UNDERLINED, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); + BinaryTag strikethrough = compound.get("strikethrough"); + if (strikethrough instanceof ByteBinaryTag b) + style.decoration(TextDecoration.STRIKETHROUGH, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); + BinaryTag obfuscated = compound.get("obfuscated"); + if (obfuscated instanceof ByteBinaryTag b) + style.decoration(TextDecoration.OBFUSCATED, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE); builder.style(style.build()); // Interactivity var insertion = compound.getString("insertion"); - if (insertion != null) builder.insertion(insertion); + if (!insertion.isEmpty()) builder.insertion(insertion); var clickEvent = compound.getCompound("clickEvent"); - if (clickEvent != null) builder.clickEvent(deserializeClickEvent(clickEvent)); + if (clickEvent.size() > 0) builder.clickEvent(deserializeClickEvent(clickEvent)); var hoverEvent = compound.getCompound("hoverEvent"); - if (hoverEvent != null) builder.hoverEvent(deserializeHoverEvent(hoverEvent)); + if (hoverEvent.size() > 0) builder.hoverEvent(deserializeHoverEvent(hoverEvent)); return builder.build(); } - private @NotNull ComponentBuilder deserializeTextComponent(@NotNull NBTCompound compound) { + private @NotNull ComponentBuilder deserializeTextComponent(@NotNull CompoundBinaryTag compound) { var text = compound.getString("text"); Check.notNull(text, "Text component must have a text field"); return Component.text().content(text); } - private @NotNull ComponentBuilder deserializeTranslatableComponent(@NotNull NBTCompound compound) { + private @NotNull ComponentBuilder deserializeTranslatableComponent(@NotNull CompoundBinaryTag compound) { var key = compound.getString("translate"); Check.notNull(key, "Translatable component must have a translate field"); var builder = Component.translatable().key(key); - var fallback = compound.getString("fallback"); - if (fallback != null) builder.fallback(fallback); + var fallback = compound.get("fallback"); + if (fallback instanceof StringBinaryTag s) builder.fallback(s.value()); - NBTList args = compound.getList("with"); - Check.argCondition(args != null && !args.getSubtagType().equals(NBTType.TAG_Compound), - "Translatable component with field must be a list of compounds"); - if (args != null) { + ListBinaryTag args = compound.getList("with", BinaryTagTypes.COMPOUND); + if (args.size() > 0) { var list = new ArrayList(); - for (var arg : args) list.add(deserializeComponent(arg)); + for (var arg : args) list.add(deserializeComponent((CompoundBinaryTag) arg)); builder.arguments(list); } return builder; } - private @NotNull ComponentBuilder deserializeScoreComponent(@NotNull NBTCompound compound) { + private @NotNull ComponentBuilder deserializeScoreComponent(@NotNull CompoundBinaryTag compound) { var scoreCompound = compound.getCompound("score"); Check.notNull(scoreCompound, "Score component must have a score field"); var name = scoreCompound.getString("name"); @@ -165,14 +164,14 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { var builder = Component.score().name(name).objective(objective); var value = scoreCompound.getString("value"); - if (value != null) + if (!value.isEmpty()) //noinspection deprecation builder.value(value); return builder; } - private @NotNull ComponentBuilder deserializeSelectorComponent(@NotNull NBTCompound compound) { + private @NotNull ComponentBuilder deserializeSelectorComponent(@NotNull CompoundBinaryTag compound) { var selector = compound.getString("selector"); Check.notNull(selector, "Selector component must have a selector field"); var builder = Component.selector().pattern(selector); @@ -183,17 +182,17 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { return builder; } - private @NotNull ComponentBuilder deserializeKeybindComponent(@NotNull NBTCompound compound) { + private @NotNull ComponentBuilder deserializeKeybindComponent(@NotNull CompoundBinaryTag compound) { var keybind = compound.getString("keybind"); Check.notNull(keybind, "Keybind component must have a keybind field"); return Component.keybind().keybind(keybind); } - private @NotNull ComponentBuilder deserializeNbtComponent(@NotNull NBTCompound compound) { + private @NotNull ComponentBuilder deserializeNbtComponent(@NotNull CompoundBinaryTag compound) { throw new UnsupportedOperationException("NBTComponent is not implemented yet"); } - private @NotNull ClickEvent deserializeClickEvent(@NotNull NBTCompound compound) { + private @NotNull ClickEvent deserializeClickEvent(@NotNull CompoundBinaryTag compound) { var actionName = compound.getString("action"); Check.notNull(actionName, "Click event must have an action field"); var action = ClickEvent.Action.NAMES.value(actionName); @@ -203,7 +202,7 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { return ClickEvent.clickEvent(action, value); } - private @NotNull HoverEvent deserializeHoverEvent(@NotNull NBTCompound compound) { + private @NotNull HoverEvent deserializeHoverEvent(@NotNull CompoundBinaryTag compound) { var actionName = compound.getString("action"); Check.notNull(actionName, "Hover event must have an action field"); var contents = compound.getCompound("contents"); @@ -216,13 +215,12 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { @Subst("minecraft:stick") var id = contents.getString("id"); Check.notNull(id, "Show item hover event must have an id field"); var count = contents.getInt("count"); - var countInt = count == null ? 1 : count; var tag = contents.getString("tag"); - var binaryTag = tag == null ? null : BinaryTagHolder.binaryTagHolder(tag); - return HoverEvent.showItem(Key.key(id), countInt, binaryTag); + var binaryTag = tag.isEmpty() ? null : BinaryTagHolder.binaryTagHolder(tag); + return HoverEvent.showItem(Key.key(id), count, binaryTag); } else if (action == HoverEvent.Action.SHOW_ENTITY) { var name = contents.getCompound("name"); - var nameComponent = name == null ? null : deserializeComponent(name); + var nameComponent = name.size() == 0 ? null : deserializeComponent(name); @Subst("minecraft:pig") var type = contents.getString("type"); Check.notNull(type, "Show entity hover event must have a type field"); var id = contents.getString("id"); @@ -235,36 +233,36 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { // SERIALIZATION - private @NotNull NBT serializeComponent(@NotNull Component component) { - MutableNBTCompound compound = new MutableNBTCompound(); + private @NotNull CompoundBinaryTag serializeComponent(@NotNull Component component) { + CompoundBinaryTag.Builder compound = CompoundBinaryTag.builder(); // Base component types if (component instanceof TextComponent text) { - compound.setString("type", "text"); - compound.setString("text", text.content()); + compound.putString("type", "text"); + compound.putString("text", text.content()); } else if (component instanceof TranslatableComponent translatable) { - compound.setString("type", "translatable"); - compound.setString("translate", translatable.key()); + compound.putString("type", "translatable"); + compound.putString("translate", translatable.key()); var fallback = translatable.fallback(); - if (fallback != null) compound.setString("fallback", fallback); + if (fallback != null) compound.putString("fallback", fallback); var args = translatable.arguments(); - if (!args.isEmpty()) compound.set("with", serializeTranslationArgs(args)); + if (!args.isEmpty()) compound.put("with", serializeTranslationArgs(args)); } else if (component instanceof ScoreComponent score) { - compound.setString("type", "score"); - var scoreCompound = new MutableNBTCompound(); - scoreCompound.setString("name", score.name()); - scoreCompound.setString("objective", score.objective()); + compound.putString("type", "score"); + CompoundBinaryTag.Builder scoreCompound = CompoundBinaryTag.builder(); + scoreCompound.putString("name", score.name()); + scoreCompound.putString("objective", score.objective()); @SuppressWarnings("deprecation") var value = score.value(); - if (value != null) scoreCompound.setString("value", value); - compound.set("score", scoreCompound.toCompound()); + if (value != null) scoreCompound.putString("value", value); + compound.put("score", scoreCompound.build()); } else if (component instanceof SelectorComponent selector) { - compound.setString("type", "selector"); - compound.setString("selector", selector.pattern()); + compound.putString("type", "selector"); + compound.putString("selector", selector.pattern()); var separator = selector.separator(); - if (separator != null) compound.set("separator", serializeComponent(separator)); + if (separator != null) compound.put("separator", serializeComponent(separator)); } else if (component instanceof KeybindComponent keybind) { - compound.setString("type", "keybind"); - compound.setString("keybind", keybind.keybind()); + compound.putString("type", "keybind"); + compound.putString("keybind", keybind.keybind()); } else if (component instanceof NBTComponent nbt) { //todo throw new UnsupportedOperationException("NBTComponent is not implemented yet"); @@ -274,10 +272,10 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { // Children if (!component.children().isEmpty()) { - var children = new ArrayList(); + ListBinaryTag.Builder children = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); for (var child : component.children()) children.add(serializeComponent(child)); - compound.set("extra", new NBTList<>(NBTType.TAG_Compound, children)); + compound.put("extra", children.build()); } // Formatting @@ -285,94 +283,89 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { var color = style.color(); if (color != null) { if (color instanceof NamedTextColor named) { - compound.setString("color", named.toString()); + compound.putString("color", named.toString()); } else { - compound.setString("color", color.asHexString()); + compound.putString("color", color.asHexString()); } } var font = style.font(); if (font != null) - compound.setString("font", font.toString()); + compound.putString("font", font.toString()); var bold = style.decoration(TextDecoration.BOLD); if (bold != TextDecoration.State.NOT_SET) - setBool(compound, "bold", bold == TextDecoration.State.TRUE); + compound.putBoolean("bold", bold == TextDecoration.State.TRUE); var italic = style.decoration(TextDecoration.ITALIC); if (italic != TextDecoration.State.NOT_SET) - setBool(compound, "italic", italic == TextDecoration.State.TRUE); + compound.putBoolean("italic", italic == TextDecoration.State.TRUE); var underlined = style.decoration(TextDecoration.UNDERLINED); if (underlined != TextDecoration.State.NOT_SET) - setBool(compound, "underlined", underlined == TextDecoration.State.TRUE); + compound.putBoolean("underlined", underlined == TextDecoration.State.TRUE); var strikethrough = style.decoration(TextDecoration.STRIKETHROUGH); if (strikethrough != TextDecoration.State.NOT_SET) - setBool(compound, "strikethrough", strikethrough == TextDecoration.State.TRUE); + compound.putBoolean("strikethrough", strikethrough == TextDecoration.State.TRUE); var obfuscated = style.decoration(TextDecoration.OBFUSCATED); if (obfuscated != TextDecoration.State.NOT_SET) - setBool(compound, "obfuscated", obfuscated == TextDecoration.State.TRUE); + compound.putBoolean("obfuscated", obfuscated == TextDecoration.State.TRUE); // Interactivity var insertion = component.insertion(); - if (insertion != null) compound.setString("insertion", insertion); + if (insertion != null) compound.putString("insertion", insertion); var clickEvent = component.clickEvent(); - if (clickEvent != null) compound.set("clickEvent", serializeClickEvent(clickEvent)); + if (clickEvent != null) compound.put("clickEvent", serializeClickEvent(clickEvent)); var hoverEvent = component.hoverEvent(); - if (hoverEvent != null) compound.set("hoverEvent", serializeHoverEvent(hoverEvent)); + if (hoverEvent != null) compound.put("hoverEvent", serializeHoverEvent(hoverEvent)); - return compound.toCompound(); + return compound.build(); } - private @NotNull NBT serializeTranslationArgs(@NotNull Collection args) { - var list = new ArrayList(); + private @NotNull BinaryTag serializeTranslationArgs(@NotNull Collection args) { + ListBinaryTag.Builder argList = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); for (var arg : args) - list.add(serializeComponent(arg.asComponent())); - return new NBTList<>(NBTType.TAG_Compound, list); + argList.add(serializeComponent(arg.asComponent())); + return argList.build(); } - private @NotNull NBT serializeClickEvent(@NotNull ClickEvent event) { - var compound = new MutableNBTCompound(); - compound.setString("action", event.action().toString()); - compound.setString("value", event.value()); - return compound.toCompound(); + private @NotNull BinaryTag serializeClickEvent(@NotNull ClickEvent event) { + return CompoundBinaryTag.builder() + .putString("action", event.action().toString()) + .putString("value", event.value()) + .build(); } @SuppressWarnings("unchecked") - private @NotNull NBT serializeHoverEvent(@NotNull HoverEvent event) { - var compound = new MutableNBTCompound(); + private @NotNull BinaryTag serializeHoverEvent(@NotNull HoverEvent event) { + CompoundBinaryTag.Builder compound = CompoundBinaryTag.builder(); //todo surely there is a better way to do this? - compound.setString("action", event.action().toString()); + compound.putString("action", event.action().toString()); if (event.action() == HoverEvent.Action.SHOW_TEXT) { var value = ((HoverEvent) event).value(); - compound.set("contents", serializeComponent(value)); + compound.put("contents", serializeComponent(value)); } else if (event.action() == HoverEvent.Action.SHOW_ITEM) { var value = ((HoverEvent) event).value(); - var itemCompound = new MutableNBTCompound(); - itemCompound.setString("id", value.item().asString()); - if (value.count() != 1) itemCompound.setInt("count", value.count()); + CompoundBinaryTag.Builder itemCompound = CompoundBinaryTag.builder(); + itemCompound.putString("id", value.item().asString()); + if (value.count() != 1) itemCompound.putInt("count", value.count()); var tag = value.nbt(); - if (tag != null) itemCompound.setString("tag", tag.string()); + if (tag != null) itemCompound.putString("tag", tag.string()); - compound.set("contents", itemCompound.toCompound()); + compound.put("contents", itemCompound.build()); } else if (event.action() == HoverEvent.Action.SHOW_ENTITY) { var value = ((HoverEvent) event).value(); - var entityCompound = new MutableNBTCompound(); + CompoundBinaryTag.Builder entityCompound = CompoundBinaryTag.builder(); var name = value.name(); - if (name != null) entityCompound.set("name", serializeComponent(name)); - entityCompound.setString("type", value.type().asString()); - entityCompound.setString("id", value.id().toString()); + if (name != null) entityCompound.put("name", serializeComponent(name)); + entityCompound.putString("type", value.type().asString()); + entityCompound.putString("id", value.id().toString()); - compound.set("contents", entityCompound.toCompound()); + compound.put("contents", entityCompound.build()); } else { throw new UnsupportedOperationException("Unknown hover event action: " + event.action()); } - return compound.toCompound(); + return compound.build(); } - private void setBool(@NotNull MutableNBTCompound compound, @NotNull String key, boolean value) { - compound.setByte(key, value ? (byte) 1 : 0); - } - - } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java index 1b6841111..18e5ef25e 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java @@ -1,16 +1,15 @@ package net.minestom.server.command.builder.arguments.minecraft; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.TagStringIO; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTException; -import org.jglrxavpok.hephaistos.parser.SNBTParser; -import java.io.StringReader; +import java.io.IOException; /** * Argument which can be used to retrieve an {@link ItemStack} from its material and with NBT data. @@ -65,10 +64,10 @@ public class ArgumentItemStack extends Argument { final String sNBT = input.substring(nbtIndex).replace("\\\"", "\""); - NBTCompound compound; + CompoundBinaryTag compound; try { - compound = (NBTCompound) new SNBTParser(new StringReader(sNBT)).parse(); - } catch (NBTException e) { + compound = TagStringIO.get().asCompound(sNBT); + } catch (IOException e) { throw new ArgumentSyntaxException("Item NBT is invalid", input, INVALID_NBT); } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtCompoundTag.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtCompoundTag.java index 92fb5b3e9..54633a46b 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtCompoundTag.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtCompoundTag.java @@ -1,22 +1,21 @@ package net.minestom.server.command.builder.arguments.minecraft; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.TagStringIO; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTException; -import org.jglrxavpok.hephaistos.parser.SNBTParser; -import java.io.StringReader; +import java.io.IOException; /** - * Argument used to retrieve a {@link NBTCompound} if you need key-value data. + * Argument used to retrieve a {@link CompoundBinaryTag} if you need key-value data. *

* Example: {display:{Name:"{\"text\":\"Sword of Power\"}"}} */ -public class ArgumentNbtCompoundTag extends Argument { +public class ArgumentNbtCompoundTag extends Argument { public static final int INVALID_NBT = 1; @@ -26,15 +25,15 @@ public class ArgumentNbtCompoundTag extends Argument { @NotNull @Override - public NBTCompound parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException { + public CompoundBinaryTag parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException { try { - NBT nbt = new SNBTParser(new StringReader(input)).parse(); + BinaryTag nbt = TagStringIO.get().asCompound(input); - if (!(nbt instanceof NBTCompound)) + if (!(nbt instanceof CompoundBinaryTag compound)) throw new ArgumentSyntaxException("NBTCompound is invalid", input, INVALID_NBT); - return (NBTCompound) nbt; - } catch (NBTException e) { + return compound; + } catch (IOException e) { throw new ArgumentSyntaxException("NBTCompound is invalid", input, INVALID_NBT); } } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtTag.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtTag.java index 86dac51ef..7e5d4ab8c 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtTag.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentNbtTag.java @@ -1,23 +1,22 @@ package net.minestom.server.command.builder.arguments.minecraft; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.TagStringIO; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTException; -import org.jglrxavpok.hephaistos.parser.SNBTParser; -import java.io.StringReader; +import java.io.IOException; /** - * Argument used to retrieve a {@link NBT} based object, can be any kind of tag like - * {@link org.jglrxavpok.hephaistos.nbt.NBTCompound}, {@link org.jglrxavpok.hephaistos.nbt.NBTList}, - * {@link org.jglrxavpok.hephaistos.nbt.NBTInt}, etc... + * Argument used to retrieve a {@link BinaryTag} based object, can be any kind of tag like + * {@link net.kyori.adventure.nbt.CompoundBinaryTag}, {@link net.kyori.adventure.nbt.ListBinaryTag}, + * {@link net.kyori.adventure.nbt.IntBinaryTag}, etc... *

* Example: {display:{Name:"{\"text\":\"Sword of Power\"}"}} or [{display:{Name:"{\"text\":\"Sword of Power\"}"}}] */ -public class ArgumentNbtTag extends Argument { +public class ArgumentNbtTag extends Argument { public static final int INVALID_NBT = 1; @@ -27,10 +26,10 @@ public class ArgumentNbtTag extends Argument { @NotNull @Override - public NBT parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException { + public BinaryTag parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException { try { - return new SNBTParser(new StringReader(input)).parse(); - } catch (NBTException e) { + return TagStringIO.get().asCompound(input); + } catch (IOException e) { throw new ArgumentSyntaxException("Invalid NBT", input, INVALID_NBT); } } diff --git a/src/main/java/net/minestom/server/entity/Metadata.java b/src/main/java/net/minestom/server/entity/Metadata.java index 4de2509d9..f208bf2db 100644 --- a/src/main/java/net/minestom/server/entity/Metadata.java +++ b/src/main/java/net/minestom/server/entity/Metadata.java @@ -1,5 +1,6 @@ package net.minestom.server.entity; +import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; import net.minestom.server.entity.metadata.animal.FrogMeta; @@ -15,7 +16,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -import org.jglrxavpok.hephaistos.nbt.NBT; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; @@ -89,7 +89,7 @@ public final class Metadata { return new MetadataImpl.EntryImpl<>(TYPE_OPTBLOCKSTATE, value, NetworkBuffer.OPT_BLOCK_STATE); } - public static Entry NBT(@NotNull NBT nbt) { + public static Entry NBT(@NotNull BinaryTag nbt) { return new MetadataImpl.EntryImpl<>(TYPE_NBT, nbt, NetworkBuffer.NBT); } diff --git a/src/main/java/net/minestom/server/entity/MetadataImpl.java b/src/main/java/net/minestom/server/entity/MetadataImpl.java index d2d89cb92..8a58a9d4e 100644 --- a/src/main/java/net/minestom/server/entity/MetadataImpl.java +++ b/src/main/java/net/minestom/server/entity/MetadataImpl.java @@ -1,5 +1,6 @@ package net.minestom.server.entity; +import net.kyori.adventure.nbt.EndBinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.metadata.animal.FrogMeta; @@ -13,7 +14,6 @@ import net.minestom.server.utils.Direction; import net.minestom.server.utils.collection.ObjectArray; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; -import org.jglrxavpok.hephaistos.nbt.NBTEnd; import static net.minestom.server.entity.Metadata.*; import static net.minestom.server.network.NetworkBuffer.VAR_INT; @@ -38,7 +38,7 @@ final class MetadataImpl { EMPTY_VALUES.set(TYPE_OPTUUID, OptUUID(null)); EMPTY_VALUES.set(TYPE_BLOCKSTATE, BlockState(Block.AIR.id())); EMPTY_VALUES.set(TYPE_OPTBLOCKSTATE, OptBlockState(null)); - EMPTY_VALUES.set(TYPE_NBT, NBT(NBTEnd.INSTANCE)); + EMPTY_VALUES.set(TYPE_NBT, NBT(EndBinaryTag.endBinaryTag())); //EMPTY_VALUES.set(TYPE_PARTICLE -> throw new UnsupportedOperationException(); EMPTY_VALUES.set(TYPE_VILLAGERDATA, VillagerData(0, 0, 0)); EMPTY_VALUES.set(TYPE_OPTVARINT, OptVarInt(null)); diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java index 6f6fec6fd..2d2a20cc2 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageType.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -1,12 +1,12 @@ package net.minestom.server.entity.damage; -import net.minestom.server.registry.StaticProtocolObject; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Collection; @@ -36,7 +36,7 @@ public sealed interface DamageType extends StaticProtocolObject, DamageTypes per return registry().scaling(); } - NBTCompound asNBT(); + CompoundBinaryTag asNBT(); static @NotNull Collection<@NotNull DamageType> values() { return DamageTypeImpl.values(); @@ -54,7 +54,7 @@ public sealed interface DamageType extends StaticProtocolObject, DamageTypes per return DamageTypeImpl.getId(id); } - static NBTCompound getNBT() { + static CompoundBinaryTag getNBT() { return DamageTypeImpl.getNBT(); } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java b/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java index 70f97ff4c..b8480e656 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java @@ -1,14 +1,12 @@ package net.minestom.server.entity.damage; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; import net.minestom.server.registry.Registry; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTType; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements DamageType { @@ -33,12 +31,12 @@ record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements Dama } @Override - public NBTCompound asNBT() { - var elem = new HashMap(); - elem.put("exhaustion", NBT.Float(registry.exhaustion())); - elem.put("message_id", NBT.String(registry.messageId())); - elem.put("scaling", NBT.String(registry.scaling())); - return NBT.Compound(elem); + public CompoundBinaryTag asNBT() { + return CompoundBinaryTag.builder() + .putFloat("exhaustion", registry.exhaustion()) + .putString("message_id", registry.messageId()) + .putString("scaling", registry.scaling()) + .build(); } static Collection values() { @@ -55,22 +53,23 @@ record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements Dama return id; } - private static NBTCompound lazyNbt = null; + private static CompoundBinaryTag lazyNbt = null; - static NBTCompound getNBT() { + static CompoundBinaryTag getNBT() { if (lazyNbt == null) { - var damageTypes = values().stream() - .map((damageType) -> NBT.Compound(Map.of( - "id", NBT.Int(damageType.id()), - "name", NBT.String(damageType.name()), - "element", damageType.asNBT() - ))) - .toList(); + var entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); + for (var damageType : values()) { + entries.add(CompoundBinaryTag.builder() + .putInt("id", damageType.id()) + .putString("name", damageType.name()) + .put("element", damageType.asNBT()) + .build()); + } - lazyNbt = NBT.Compound(Map.of( - "type", NBT.String("minecraft:damage_type"), - "value", NBT.List(NBTType.TAG_Compound, damageTypes) - )); + lazyNbt = CompoundBinaryTag.builder() + .putString("type", "minecraft:damage_type") + .put("value", entries.build()) + .build(); } return lazyNbt; } diff --git a/src/main/java/net/minestom/server/entity/metadata/PlayerMeta.java b/src/main/java/net/minestom/server/entity/metadata/PlayerMeta.java index 565229964..9eac1aad0 100644 --- a/src/main/java/net/minestom/server/entity/metadata/PlayerMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/PlayerMeta.java @@ -1,12 +1,11 @@ package net.minestom.server.entity.metadata; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Metadata; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; - -import java.util.Map; public class PlayerMeta extends LivingEntityMeta { public static final byte OFFSET = LivingEntityMeta.MAX_OFFSET; @@ -109,23 +108,23 @@ public class PlayerMeta extends LivingEntityMeta { } @Nullable - public NBT getLeftShoulderEntityData() { + public BinaryTag getLeftShoulderEntityData() { return super.metadata.getIndex(OFFSET + 4, null); } - public void setLeftShoulderEntityData(@Nullable NBT value) { - if (value == null) value = NBT.Compound(Map.of()); + public void setLeftShoulderEntityData(@Nullable BinaryTag value) { + if (value == null) value = CompoundBinaryTag.empty(); super.metadata.setIndex(OFFSET + 4, Metadata.NBT(value)); } @Nullable - public NBT getRightShoulderEntityData() { + public BinaryTag getRightShoulderEntityData() { return super.metadata.getIndex(OFFSET + 5, null); } - public void setRightShoulderEntityData(@Nullable NBT value) { - if (value == null) value = NBT.Compound(Map.of()); + public void setRightShoulderEntityData(@Nullable BinaryTag value) { + if (value == null) value = CompoundBinaryTag.empty(); super.metadata.setIndex(OFFSET + 5, Metadata.NBT(value)); } diff --git a/src/main/java/net/minestom/server/instance/AnvilLoader.java b/src/main/java/net/minestom/server/instance/AnvilLoader.java index 93048a71b..02c96a477 100644 --- a/src/main/java/net/minestom/server/instance/AnvilLoader.java +++ b/src/main/java/net/minestom/server/instance/AnvilLoader.java @@ -1,34 +1,17 @@ package net.minestom.server.instance; -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntIntImmutablePair; import net.minestom.server.MinecraftServer; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.world.biomes.Biome; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.mca.*; -import org.jglrxavpok.hephaistos.mca.readers.ChunkReader; -import org.jglrxavpok.hephaistos.mca.readers.ChunkSectionReader; -import org.jglrxavpok.hephaistos.mca.readers.SectionBiomeInformation; -import org.jglrxavpok.hephaistos.mca.writer.ChunkSectionWriter; -import org.jglrxavpok.hephaistos.mca.writer.ChunkWriter; -import org.jglrxavpok.hephaistos.nbt.*; -import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.*; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -36,7 +19,7 @@ public class AnvilLoader implements IChunkLoader { private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class); private final static Biome PLAINS = MinecraftServer.getBiomeManager().getByName(NamespaceID.from("minecraft:plains")); - private final Map alreadyLoaded = new ConcurrentHashMap<>(); +// private final Map alreadyLoaded = new ConcurrentHashMap<>(); private final Path path; private final Path levelPath; private final Path regionPath; @@ -50,7 +33,7 @@ public class AnvilLoader implements IChunkLoader { private final RegionCache perRegionLoadedChunks = new RegionCache(); // thread local to avoid contention issues with locks - private final ThreadLocal> blockStateId2ObjectCacheTLS = ThreadLocal.withInitial(Int2ObjectArrayMap::new); +// private final ThreadLocal> blockStateId2ObjectCacheTLS = ThreadLocal.withInitial(Int2ObjectArrayMap::new); public AnvilLoader(@NotNull Path path) { this.path = path; @@ -64,409 +47,409 @@ public class AnvilLoader implements IChunkLoader { @Override public void loadInstance(@NotNull Instance instance) { - if (!Files.exists(levelPath)) { - return; - } - try (var reader = new NBTReader(Files.newInputStream(levelPath))) { - final NBTCompound tag = (NBTCompound) reader.read(); - Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING); - instance.tagHandler().updateContent(tag); - } catch (IOException | NBTException e) { - MinecraftServer.getExceptionManager().handleException(e); - } +// if (!Files.exists(levelPath)) { +// return; +// } +// try (var reader = new NBTReader(Files.newInputStream(levelPath))) { +// final NBTCompound tag = (NBTCompound) reader.read(); +// Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING); +// instance.tagHandler().updateContent(tag); +// } catch (IOException | NBTException e) { +// MinecraftServer.getExceptionManager().handleException(e); +// } } @Override public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { - if (!Files.exists(path)) { - // No world folder - return CompletableFuture.completedFuture(null); - } - try { - return loadMCA(instance, chunkX, chunkZ); - } catch (Exception e) { - MinecraftServer.getExceptionManager().handleException(e); - } +// if (!Files.exists(path)) { +// // No world folder +// return CompletableFuture.completedFuture(null); +// } +// try { +// return loadMCA(instance, chunkX, chunkZ); +// } catch (Exception e) { +// MinecraftServer.getExceptionManager().handleException(e); +// } return CompletableFuture.completedFuture(null); } - - private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException, AnvilException { - final RegionFile mcaFile = getMCAFile(instance, chunkX, chunkZ); - if (mcaFile == null) - return CompletableFuture.completedFuture(null); - final NBTCompound chunkData = mcaFile.getChunkData(chunkX, chunkZ); - if (chunkData == null) - return CompletableFuture.completedFuture(null); - - final ChunkReader chunkReader = new ChunkReader(chunkData); - - Chunk chunk = instance.getChunkSupplier().createChunk(instance, chunkX, chunkZ); - synchronized (chunk) { - var yRange = chunkReader.getYRange(); - if (yRange.getStart() < instance.getDimensionType().getMinY()) { - throw new AnvilException( - String.format("Trying to load chunk with minY = %d, but instance dimension type (%s) has a minY of %d", - yRange.getStart(), - instance.getDimensionType().getName().asString(), - instance.getDimensionType().getMinY() - )); - } - if (yRange.getEndInclusive() > instance.getDimensionType().getMaxY()) { - throw new AnvilException( - String.format("Trying to load chunk with maxY = %d, but instance dimension type (%s) has a maxY of %d", - yRange.getEndInclusive(), - instance.getDimensionType().getName().asString(), - instance.getDimensionType().getMaxY() - )); - } - - // TODO: Parallelize block, block entities and biome loading - // Blocks + Biomes - loadSections(chunk, chunkReader); - - // Block entities - loadBlockEntities(chunk, chunkReader); - } - synchronized (perRegionLoadedChunks) { - int regionX = CoordinatesKt.chunkToRegion(chunkX); - int regionZ = CoordinatesKt.chunkToRegion(chunkZ); - var chunks = perRegionLoadedChunks.computeIfAbsent(new IntIntImmutablePair(regionX, regionZ), r -> new HashSet<>()); // region cache may have been removed on another thread due to unloadChunk - chunks.add(new IntIntImmutablePair(chunkX, chunkZ)); - } - return CompletableFuture.completedFuture(chunk); - } - - private @Nullable RegionFile getMCAFile(Instance instance, int chunkX, int chunkZ) { - final int regionX = CoordinatesKt.chunkToRegion(chunkX); - final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); - return alreadyLoaded.computeIfAbsent(RegionFile.Companion.createFileName(regionX, regionZ), n -> { - try { - final Path regionPath = this.regionPath.resolve(n); - if (!Files.exists(regionPath)) { - return null; - } - synchronized (perRegionLoadedChunks) { - Set previousVersion = perRegionLoadedChunks.put(new IntIntImmutablePair(regionX, regionZ), new HashSet<>()); - assert previousVersion == null : "The AnvilLoader cache should not already have data for this region."; - } - return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ, instance.getDimensionType().getMinY(), instance.getDimensionType().getMaxY() - 1); - } catch (IOException | AnvilException e) { - MinecraftServer.getExceptionManager().handleException(e); - return null; - } - }); - } - - private void loadSections(Chunk chunk, ChunkReader chunkReader) { - final HashMap biomeCache = new HashMap<>(); - for (NBTCompound sectionNBT : chunkReader.getSections()) { - ChunkSectionReader sectionReader = new ChunkSectionReader(chunkReader.getMinecraftVersion(), sectionNBT); - - if (sectionReader.isSectionEmpty()) continue; - final int sectionY = sectionReader.getY(); - final int yOffset = Chunk.CHUNK_SECTION_SIZE * sectionY; - - Section section = chunk.getSection(sectionY); - - if (sectionReader.getSkyLight() != null) { - section.setSkyLight(sectionReader.getSkyLight().copyArray()); - } - if (sectionReader.getBlockLight() != null) { - section.setBlockLight(sectionReader.getBlockLight().copyArray()); - } - - // Biomes - if (chunkReader.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) { - SectionBiomeInformation sectionBiomeInformation = chunkReader.readSectionBiomes(sectionReader); - - if (sectionBiomeInformation != null && sectionBiomeInformation.hasBiomeInformation()) { - if (sectionBiomeInformation.isFilledWithSingleBiome()) { - for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { - for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { - for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { - int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x; - int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z; - int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y; - String biomeName = sectionBiomeInformation.getBaseBiome(); - Biome biome = biomeCache.computeIfAbsent(biomeName, n -> - Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS)); - chunk.setBiome(finalX, finalY, finalZ, biome); - } - } - } - } else { - for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { - for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { - for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { - int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x; - int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z; - int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y; - - int index = x / 4 + (z / 4) * 4 + (y / 4) * 16; - String biomeName = sectionBiomeInformation.getBiomes()[index]; - Biome biome = biomeCache.computeIfAbsent(biomeName, n -> - Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS)); - chunk.setBiome(finalX, finalY, finalZ, biome); - } - } - } - } - } - } - - // Blocks - final NBTList blockPalette = sectionReader.getBlockPalette(); - if (blockPalette != null) { - final int[] blockStateIndices = sectionReader.getUncompressedBlockStateIDs(); - Block[] convertedPalette = new Block[blockPalette.getSize()]; - for (int i = 0; i < convertedPalette.length; i++) { - final NBTCompound paletteEntry = blockPalette.get(i); - String blockName = Objects.requireNonNull(paletteEntry.getString("Name")); - if (blockName.equals("minecraft:air")) { - convertedPalette[i] = Block.AIR; - } else { - if (blockName.equals("minecraft:grass")) { - blockName = "minecraft:short_grass"; - } - Block block = Objects.requireNonNull(Block.fromNamespaceId(blockName)); - // Properties - final Map properties = new HashMap<>(); - NBTCompound propertiesNBT = paletteEntry.getCompound("Properties"); - if (propertiesNBT != null) { - for (var property : propertiesNBT) { - if (property.getValue().getID() != NBTType.TAG_String) { - LOGGER.warn("Fail to parse block state properties {}, expected a TAG_String for {}, but contents were {}", - propertiesNBT, - property.getKey(), - property.getValue().toSNBT()); - } else { - properties.put(property.getKey(), ((NBTString) property.getValue()).getValue()); - } - } - } - - if (!properties.isEmpty()) block = block.withProperties(properties); - // Handler - final BlockHandler handler = MinecraftServer.getBlockManager().getHandler(block.name()); - if (handler != null) block = block.withHandler(handler); - - convertedPalette[i] = block; - } - } - - for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { - for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) { - for (int x = 0; x < Chunk.CHUNK_SECTION_SIZE; x++) { - try { - final int blockIndex = y * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE + z * Chunk.CHUNK_SECTION_SIZE + x; - final int paletteIndex = blockStateIndices[blockIndex]; - final Block block = convertedPalette[paletteIndex]; - - chunk.setBlock(x, y + yOffset, z, block); - } catch (Exception e) { - MinecraftServer.getExceptionManager().handleException(e); - } - } - } - } - } - } - } - - private void loadBlockEntities(Chunk loadedChunk, ChunkReader chunkReader) { - for (NBTCompound te : chunkReader.getBlockEntities()) { - final var x = te.getInt("x"); - final var y = te.getInt("y"); - final var z = te.getInt("z"); - if (x == null || y == null || z == null) { - LOGGER.warn("Tile entity has failed to load due to invalid coordinate"); - continue; - } - Block block = loadedChunk.getBlock(x, y, z); - - final String tileEntityID = te.getString("id"); - if (tileEntityID != null) { - final BlockHandler handler = MinecraftServer.getBlockManager().getHandlerOrDummy(tileEntityID); - block = block.withHandler(handler); - } - // Remove anvil tags - MutableNBTCompound mutableCopy = te.toMutableCompound(); - mutableCopy.remove("id"); - mutableCopy.remove("x"); - mutableCopy.remove("y"); - mutableCopy.remove("z"); - mutableCopy.remove("keepPacked"); - // Place block - final var finalBlock = mutableCopy.getSize() > 0 ? - block.withNbt(mutableCopy.toCompound()) : block; - loadedChunk.setBlock(x, y, z, finalBlock); - } - } - +// +// private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException, AnvilException { +// final RegionFile mcaFile = getMCAFile(instance, chunkX, chunkZ); +// if (mcaFile == null) +// return CompletableFuture.completedFuture(null); +// final NBTCompound chunkData = mcaFile.getChunkData(chunkX, chunkZ); +// if (chunkData == null) +// return CompletableFuture.completedFuture(null); +// +// final ChunkReader chunkReader = new ChunkReader(chunkData); +// +// Chunk chunk = instance.getChunkSupplier().createChunk(instance, chunkX, chunkZ); +// synchronized (chunk) { +// var yRange = chunkReader.getYRange(); +// if (yRange.getStart() < instance.getDimensionType().getMinY()) { +// throw new AnvilException( +// String.format("Trying to load chunk with minY = %d, but instance dimension type (%s) has a minY of %d", +// yRange.getStart(), +// instance.getDimensionType().getName().asString(), +// instance.getDimensionType().getMinY() +// )); +// } +// if (yRange.getEndInclusive() > instance.getDimensionType().getMaxY()) { +// throw new AnvilException( +// String.format("Trying to load chunk with maxY = %d, but instance dimension type (%s) has a maxY of %d", +// yRange.getEndInclusive(), +// instance.getDimensionType().getName().asString(), +// instance.getDimensionType().getMaxY() +// )); +// } +// +// // TODO: Parallelize block, block entities and biome loading +// // Blocks + Biomes +// loadSections(chunk, chunkReader); +// +// // Block entities +// loadBlockEntities(chunk, chunkReader); +// } +// synchronized (perRegionLoadedChunks) { +// int regionX = CoordinatesKt.chunkToRegion(chunkX); +// int regionZ = CoordinatesKt.chunkToRegion(chunkZ); +// var chunks = perRegionLoadedChunks.computeIfAbsent(new IntIntImmutablePair(regionX, regionZ), r -> new HashSet<>()); // region cache may have been removed on another thread due to unloadChunk +// chunks.add(new IntIntImmutablePair(chunkX, chunkZ)); +// } +// return CompletableFuture.completedFuture(chunk); +// } +// +// private @Nullable RegionFile getMCAFile(Instance instance, int chunkX, int chunkZ) { +// final int regionX = CoordinatesKt.chunkToRegion(chunkX); +// final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); +// return alreadyLoaded.computeIfAbsent(RegionFile.Companion.createFileName(regionX, regionZ), n -> { +// try { +// final Path regionPath = this.regionPath.resolve(n); +// if (!Files.exists(regionPath)) { +// return null; +// } +// synchronized (perRegionLoadedChunks) { +// Set previousVersion = perRegionLoadedChunks.put(new IntIntImmutablePair(regionX, regionZ), new HashSet<>()); +// assert previousVersion == null : "The AnvilLoader cache should not already have data for this region."; +// } +// return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ, instance.getDimensionType().getMinY(), instance.getDimensionType().getMaxY() - 1); +// } catch (IOException | AnvilException e) { +// MinecraftServer.getExceptionManager().handleException(e); +// return null; +// } +// }); +// } +// +// private void loadSections(Chunk chunk, ChunkReader chunkReader) { +// final HashMap biomeCache = new HashMap<>(); +// for (NBTCompound sectionNBT : chunkReader.getSections()) { +// ChunkSectionReader sectionReader = new ChunkSectionReader(chunkReader.getMinecraftVersion(), sectionNBT); +// +// if (sectionReader.isSectionEmpty()) continue; +// final int sectionY = sectionReader.getY(); +// final int yOffset = Chunk.CHUNK_SECTION_SIZE * sectionY; +// +// Section section = chunk.getSection(sectionY); +// +// if (sectionReader.getSkyLight() != null) { +// section.setSkyLight(sectionReader.getSkyLight().copyArray()); +// } +// if (sectionReader.getBlockLight() != null) { +// section.setBlockLight(sectionReader.getBlockLight().copyArray()); +// } +// +// // Biomes +// if (chunkReader.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) { +// SectionBiomeInformation sectionBiomeInformation = chunkReader.readSectionBiomes(sectionReader); +// +// if (sectionBiomeInformation != null && sectionBiomeInformation.hasBiomeInformation()) { +// if (sectionBiomeInformation.isFilledWithSingleBiome()) { +// for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { +// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { +// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { +// int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x; +// int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z; +// int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y; +// String biomeName = sectionBiomeInformation.getBaseBiome(); +// Biome biome = biomeCache.computeIfAbsent(biomeName, n -> +// Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS)); +// chunk.setBiome(finalX, finalY, finalZ, biome); +// } +// } +// } +// } else { +// for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { +// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { +// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { +// int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x; +// int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z; +// int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y; +// +// int index = x / 4 + (z / 4) * 4 + (y / 4) * 16; +// String biomeName = sectionBiomeInformation.getBiomes()[index]; +// Biome biome = biomeCache.computeIfAbsent(biomeName, n -> +// Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS)); +// chunk.setBiome(finalX, finalY, finalZ, biome); +// } +// } +// } +// } +// } +// } +// +// // Blocks +// final NBTList blockPalette = sectionReader.getBlockPalette(); +// if (blockPalette != null) { +// final int[] blockStateIndices = sectionReader.getUncompressedBlockStateIDs(); +// Block[] convertedPalette = new Block[blockPalette.getSize()]; +// for (int i = 0; i < convertedPalette.length; i++) { +// final NBTCompound paletteEntry = blockPalette.get(i); +// String blockName = Objects.requireNonNull(paletteEntry.getString("Name")); +// if (blockName.equals("minecraft:air")) { +// convertedPalette[i] = Block.AIR; +// } else { +// if (blockName.equals("minecraft:grass")) { +// blockName = "minecraft:short_grass"; +// } +// Block block = Objects.requireNonNull(Block.fromNamespaceId(blockName)); +// // Properties +// final Map properties = new HashMap<>(); +// NBTCompound propertiesNBT = paletteEntry.getCompound("Properties"); +// if (propertiesNBT != null) { +// for (var property : propertiesNBT) { +// if (property.getValue().getID() != NBTType.TAG_String) { +// LOGGER.warn("Fail to parse block state properties {}, expected a TAG_String for {}, but contents were {}", +// propertiesNBT, +// property.getKey(), +// property.getValue().toSNBT()); +// } else { +// properties.put(property.getKey(), ((NBTString) property.getValue()).getValue()); +// } +// } +// } +// +// if (!properties.isEmpty()) block = block.withProperties(properties); +// // Handler +// final BlockHandler handler = MinecraftServer.getBlockManager().getHandler(block.name()); +// if (handler != null) block = block.withHandler(handler); +// +// convertedPalette[i] = block; +// } +// } +// +// for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { +// for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) { +// for (int x = 0; x < Chunk.CHUNK_SECTION_SIZE; x++) { +// try { +// final int blockIndex = y * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE + z * Chunk.CHUNK_SECTION_SIZE + x; +// final int paletteIndex = blockStateIndices[blockIndex]; +// final Block block = convertedPalette[paletteIndex]; +// +// chunk.setBlock(x, y + yOffset, z, block); +// } catch (Exception e) { +// MinecraftServer.getExceptionManager().handleException(e); +// } +// } +// } +// } +// } +// } +// } +// +// private void loadBlockEntities(Chunk loadedChunk, ChunkReader chunkReader) { +// for (NBTCompound te : chunkReader.getBlockEntities()) { +// final var x = te.getInt("x"); +// final var y = te.getInt("y"); +// final var z = te.getInt("z"); +// if (x == null || y == null || z == null) { +// LOGGER.warn("Tile entity has failed to load due to invalid coordinate"); +// continue; +// } +// Block block = loadedChunk.getBlock(x, y, z); +// +// final String tileEntityID = te.getString("id"); +// if (tileEntityID != null) { +// final BlockHandler handler = MinecraftServer.getBlockManager().getHandlerOrDummy(tileEntityID); +// block = block.withHandler(handler); +// } +// // Remove anvil tags +// MutableNBTCompound mutableCopy = te.toMutableCompound(); +// mutableCopy.remove("id"); +// mutableCopy.remove("x"); +// mutableCopy.remove("y"); +// mutableCopy.remove("z"); +// mutableCopy.remove("keepPacked"); +// // Place block +// final var finalBlock = mutableCopy.getSize() > 0 ? +// block.withNbt(mutableCopy.toCompound()) : block; +// loadedChunk.setBlock(x, y, z, finalBlock); +// } +// } +// @Override public @NotNull CompletableFuture saveInstance(@NotNull Instance instance) { - final NBTCompound nbt = instance.tagHandler().asCompound(); - if (nbt.isEmpty()) { - // Instance has no data - return AsyncUtils.VOID_FUTURE; - } - try (NBTWriter writer = new NBTWriter(Files.newOutputStream(levelPath))) { - writer.writeNamed("", nbt); - } catch (IOException e) { - e.printStackTrace(); - } +// final NBTCompound nbt = instance.tagHandler().asCompound(); +// if (nbt.isEmpty()) { +// // Instance has no data +// return AsyncUtils.VOID_FUTURE; +// } +// try (NBTWriter writer = new NBTWriter(Files.newOutputStream(levelPath))) { +// writer.writeNamed("", nbt); +// } catch (IOException e) { +// e.printStackTrace(); +// } return AsyncUtils.VOID_FUTURE; } @Override public @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk) { - final int chunkX = chunk.getChunkX(); - final int chunkZ = chunk.getChunkZ(); - RegionFile mcaFile; - synchronized (alreadyLoaded) { - mcaFile = getMCAFile(chunk.instance, chunkX, chunkZ); - if (mcaFile == null) { - final int regionX = CoordinatesKt.chunkToRegion(chunkX); - final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); - final String n = RegionFile.Companion.createFileName(regionX, regionZ); - File regionFile = new File(regionPath.toFile(), n); - try { - if (!regionFile.exists()) { - if (!regionFile.getParentFile().exists()) { - regionFile.getParentFile().mkdirs(); - } - regionFile.createNewFile(); - } - mcaFile = new RegionFile(new RandomAccessFile(regionFile, "rw"), regionX, regionZ); - alreadyLoaded.put(n, mcaFile); - } catch (AnvilException | IOException e) { - LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); - MinecraftServer.getExceptionManager().handleException(e); - return AsyncUtils.VOID_FUTURE; - } - } - } - ChunkWriter writer = new ChunkWriter(SupportedVersion.Companion.getLatest()); - save(chunk, writer); - try { - LOGGER.debug("Attempt saving at {} {}", chunk.getChunkX(), chunk.getChunkZ()); - mcaFile.writeColumnData(writer.toNBT(), chunk.getChunkX(), chunk.getChunkZ()); - } catch (IOException e) { - LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); - MinecraftServer.getExceptionManager().handleException(e); - return AsyncUtils.VOID_FUTURE; - } +// final int chunkX = chunk.getChunkX(); +// final int chunkZ = chunk.getChunkZ(); +// RegionFile mcaFile; +// synchronized (alreadyLoaded) { +// mcaFile = getMCAFile(chunk.instance, chunkX, chunkZ); +// if (mcaFile == null) { +// final int regionX = CoordinatesKt.chunkToRegion(chunkX); +// final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); +// final String n = RegionFile.Companion.createFileName(regionX, regionZ); +// File regionFile = new File(regionPath.toFile(), n); +// try { +// if (!regionFile.exists()) { +// if (!regionFile.getParentFile().exists()) { +// regionFile.getParentFile().mkdirs(); +// } +// regionFile.createNewFile(); +// } +// mcaFile = new RegionFile(new RandomAccessFile(regionFile, "rw"), regionX, regionZ); +// alreadyLoaded.put(n, mcaFile); +// } catch (AnvilException | IOException e) { +// LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); +// MinecraftServer.getExceptionManager().handleException(e); +// return AsyncUtils.VOID_FUTURE; +// } +// } +// } +// ChunkWriter writer = new ChunkWriter(SupportedVersion.Companion.getLatest()); +// save(chunk, writer); +// try { +// LOGGER.debug("Attempt saving at {} {}", chunk.getChunkX(), chunk.getChunkZ()); +// mcaFile.writeColumnData(writer.toNBT(), chunk.getChunkX(), chunk.getChunkZ()); +// } catch (IOException e) { +// LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); +// MinecraftServer.getExceptionManager().handleException(e); +// return AsyncUtils.VOID_FUTURE; +// } return AsyncUtils.VOID_FUTURE; } - private BlockState getBlockState(final Block block) { - return blockStateId2ObjectCacheTLS.get().computeIfAbsent(block.stateId(), _unused -> new BlockState(block.name(), block.properties())); - } - - private void save(Chunk chunk, ChunkWriter chunkWriter) { - final int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE; - final int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE - 1; - chunkWriter.setYPos(minY); - List blockEntities = new ArrayList<>(); - chunkWriter.setStatus(ChunkColumn.GenerationStatus.Full); - - List sectionData = new ArrayList<>((maxY - minY + 1) / Chunk.CHUNK_SECTION_SIZE); - int[] palettedBiomes = new int[ChunkSection.Companion.getBiomeArraySize()]; - int[] palettedBlockStates = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z]; - for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) { - ChunkSectionWriter sectionWriter = new ChunkSectionWriter(SupportedVersion.Companion.getLatest(), (byte) sectionY); - - Section section = chunk.getSection(sectionY); - sectionWriter.setSkyLights(section.skyLight().array()); - sectionWriter.setBlockLights(section.blockLight().array()); - - BiomePalette biomePalette = new BiomePalette(); - BlockPalette blockPalette = new BlockPalette(); - for (int sectionLocalY = 0; sectionLocalY < Chunk.CHUNK_SECTION_SIZE; sectionLocalY++) { - for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { - for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { - final int y = sectionLocalY + sectionY * Chunk.CHUNK_SECTION_SIZE; - - final int blockIndex = x + sectionLocalY * 16 * 16 + z * 16; - - final Block block = chunk.getBlock(x, y, z); - - final BlockState hephaistosBlockState = getBlockState(block); - blockPalette.increaseReference(hephaistosBlockState); - - palettedBlockStates[blockIndex] = blockPalette.getPaletteIndex(hephaistosBlockState); - - // biome are stored for 4x4x4 volumes, avoid unnecessary work - if (x % 4 == 0 && sectionLocalY % 4 == 0 && z % 4 == 0) { - int biomeIndex = (x / 4) + (sectionLocalY / 4) * 4 * 4 + (z / 4) * 4; - final Biome biome = chunk.getBiome(x, y, z); - final String biomeName = biome.name(); - - biomePalette.increaseReference(biomeName); - palettedBiomes[biomeIndex] = biomePalette.getPaletteIndex(biomeName); - } - - // Block entities - final BlockHandler handler = block.handler(); - final NBTCompound originalNBT = block.nbt(); - if (originalNBT != null || handler != null) { - MutableNBTCompound nbt = originalNBT != null ? - originalNBT.toMutableCompound() : new MutableNBTCompound(); - - if (handler != null) { - nbt.setString("id", handler.getNamespaceId().asString()); - } - nbt.setInt("x", x + Chunk.CHUNK_SIZE_X * chunk.getChunkX()); - nbt.setInt("y", y); - nbt.setInt("z", z + Chunk.CHUNK_SIZE_Z * chunk.getChunkZ()); - nbt.setByte("keepPacked", (byte) 0); - blockEntities.add(nbt.toCompound()); - } - } - } - } - - sectionWriter.setPalettedBiomes(biomePalette, palettedBiomes); - sectionWriter.setPalettedBlockStates(blockPalette, palettedBlockStates); - - sectionData.add(sectionWriter.toNBT()); - } - - chunkWriter.setSectionsData(NBT.List(NBTType.TAG_Compound, sectionData)); - chunkWriter.setBlockEntityData(NBT.List(NBTType.TAG_Compound, blockEntities)); - } - - /** - * Unload a given chunk. Also unloads a region when no chunk from that region is loaded. - * - * @param chunk the chunk to unload - */ - @Override - public void unloadChunk(Chunk chunk) { - final int regionX = CoordinatesKt.chunkToRegion(chunk.chunkX); - final int regionZ = CoordinatesKt.chunkToRegion(chunk.chunkZ); - - final IntIntImmutablePair regionKey = new IntIntImmutablePair(regionX, regionZ); - synchronized (perRegionLoadedChunks) { - Set chunks = perRegionLoadedChunks.get(regionKey); - if (chunks != null) { // if null, trying to unload a chunk from a region that was not created by the AnvilLoader - // don't check return value, trying to unload a chunk not created by the AnvilLoader is valid - chunks.remove(new IntIntImmutablePair(chunk.chunkX, chunk.chunkZ)); - - if (chunks.isEmpty()) { - perRegionLoadedChunks.remove(regionKey); - RegionFile regionFile = alreadyLoaded.remove(RegionFile.Companion.createFileName(regionX, regionZ)); - if (regionFile != null) { - try { - regionFile.close(); - } catch (IOException e) { - MinecraftServer.getExceptionManager().handleException(e); - } - } - } - } - } - } +// private BlockState getBlockState(final Block block) { +// return blockStateId2ObjectCacheTLS.get().computeIfAbsent(block.stateId(), _unused -> new BlockState(block.name(), block.properties())); +// } +// +// private void save(Chunk chunk, ChunkWriter chunkWriter) { +// final int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE; +// final int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE - 1; +// chunkWriter.setYPos(minY); +// List blockEntities = new ArrayList<>(); +// chunkWriter.setStatus(ChunkColumn.GenerationStatus.Full); +// +// List sectionData = new ArrayList<>((maxY - minY + 1) / Chunk.CHUNK_SECTION_SIZE); +// int[] palettedBiomes = new int[ChunkSection.Companion.getBiomeArraySize()]; +// int[] palettedBlockStates = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z]; +// for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) { +// ChunkSectionWriter sectionWriter = new ChunkSectionWriter(SupportedVersion.Companion.getLatest(), (byte) sectionY); +// +// Section section = chunk.getSection(sectionY); +// sectionWriter.setSkyLights(section.skyLight().array()); +// sectionWriter.setBlockLights(section.blockLight().array()); +// +// BiomePalette biomePalette = new BiomePalette(); +// BlockPalette blockPalette = new BlockPalette(); +// for (int sectionLocalY = 0; sectionLocalY < Chunk.CHUNK_SECTION_SIZE; sectionLocalY++) { +// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { +// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { +// final int y = sectionLocalY + sectionY * Chunk.CHUNK_SECTION_SIZE; +// +// final int blockIndex = x + sectionLocalY * 16 * 16 + z * 16; +// +// final Block block = chunk.getBlock(x, y, z); +// +// final BlockState hephaistosBlockState = getBlockState(block); +// blockPalette.increaseReference(hephaistosBlockState); +// +// palettedBlockStates[blockIndex] = blockPalette.getPaletteIndex(hephaistosBlockState); +// +// // biome are stored for 4x4x4 volumes, avoid unnecessary work +// if (x % 4 == 0 && sectionLocalY % 4 == 0 && z % 4 == 0) { +// int biomeIndex = (x / 4) + (sectionLocalY / 4) * 4 * 4 + (z / 4) * 4; +// final Biome biome = chunk.getBiome(x, y, z); +// final String biomeName = biome.name(); +// +// biomePalette.increaseReference(biomeName); +// palettedBiomes[biomeIndex] = biomePalette.getPaletteIndex(biomeName); +// } +// +// // Block entities +// final BlockHandler handler = block.handler(); +// final NBTCompound originalNBT = block.nbt(); +// if (originalNBT != null || handler != null) { +// MutableNBTCompound nbt = originalNBT != null ? +// originalNBT.toMutableCompound() : new MutableNBTCompound(); +// +// if (handler != null) { +// nbt.setString("id", handler.getNamespaceId().asString()); +// } +// nbt.setInt("x", x + Chunk.CHUNK_SIZE_X * chunk.getChunkX()); +// nbt.setInt("y", y); +// nbt.setInt("z", z + Chunk.CHUNK_SIZE_Z * chunk.getChunkZ()); +// nbt.setByte("keepPacked", (byte) 0); +// blockEntities.add(nbt.toCompound()); +// } +// } +// } +// } +// +// sectionWriter.setPalettedBiomes(biomePalette, palettedBiomes); +// sectionWriter.setPalettedBlockStates(blockPalette, palettedBlockStates); +// +// sectionData.add(sectionWriter.toNBT()); +// } +// +// chunkWriter.setSectionsData(NBT.List(NBTType.TAG_Compound, sectionData)); +// chunkWriter.setBlockEntityData(NBT.List(NBTType.TAG_Compound, blockEntities)); +// } +// +// /** +// * Unload a given chunk. Also unloads a region when no chunk from that region is loaded. +// * +// * @param chunk the chunk to unload +// */ +// @Override +// public void unloadChunk(Chunk chunk) { +// final int regionX = CoordinatesKt.chunkToRegion(chunk.chunkX); +// final int regionZ = CoordinatesKt.chunkToRegion(chunk.chunkZ); +// +// final IntIntImmutablePair regionKey = new IntIntImmutablePair(regionX, regionZ); +// synchronized (perRegionLoadedChunks) { +// Set chunks = perRegionLoadedChunks.get(regionKey); +// if (chunks != null) { // if null, trying to unload a chunk from a region that was not created by the AnvilLoader +// // don't check return value, trying to unload a chunk not created by the AnvilLoader is valid +// chunks.remove(new IntIntImmutablePair(chunk.chunkX, chunk.chunkZ)); +// +// if (chunks.isEmpty()) { +// perRegionLoadedChunks.remove(regionKey); +// RegionFile regionFile = alreadyLoaded.remove(RegionFile.Companion.createFileName(regionX, regionZ)); +// if (regionFile != null) { +// try { +// regionFile.close(); +// } catch (IOException e) { +// MinecraftServer.getExceptionManager().handleException(e); +// } +// } +// } +// } +// } +// } @Override public boolean supportsParallelLoading() { diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 87768d763..618084e65 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -2,6 +2,7 @@ package net.minestom.server.instance; import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; @@ -27,8 +28,6 @@ import net.minestom.server.world.biomes.Biome; import net.minestom.server.world.biomes.BiomeManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -225,7 +224,7 @@ public class DynamicChunk extends Chunk { } private @NotNull ChunkDataPacket createChunkPacket() { - final NBTCompound heightmapsNBT = computeHeightmap(); + final CompoundBinaryTag heightmapsNBT = computeHeightmap(); // Data final byte[] data; @@ -242,7 +241,7 @@ public class DynamicChunk extends Chunk { ); } - protected NBTCompound computeHeightmap() { + protected CompoundBinaryTag computeHeightmap() { // TODO: don't hardcode heightmaps // Heightmap int dimensionHeight = getInstance().getDimensionType().getHeight(); @@ -255,9 +254,10 @@ public class DynamicChunk extends Chunk { } } final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight); - return NBT.Compound(Map.of( - "MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)), - "WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight)))); + return CompoundBinaryTag.builder() + .putLongArray("MOTION_BLOCKING", encodeBlocks(motionBlocking, bitsForHeight)) + .putLongArray("WORLD_SURFACE", encodeBlocks(worldSurface, bitsForHeight)) + .build(); } @NotNull UpdateLightPacket createLightPacket() { diff --git a/src/main/java/net/minestom/server/instance/ExplosionSupplier.java b/src/main/java/net/minestom/server/instance/ExplosionSupplier.java index ea408e310..bdb7fcbbe 100644 --- a/src/main/java/net/minestom/server/instance/ExplosionSupplier.java +++ b/src/main/java/net/minestom/server/instance/ExplosionSupplier.java @@ -1,6 +1,6 @@ package net.minestom.server.instance; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import net.kyori.adventure.nbt.CompoundBinaryTag; @FunctionalInterface public interface ExplosionSupplier { @@ -12,9 +12,9 @@ public interface ExplosionSupplier { * @param centerY center Y of the explosion * @param centerZ center Z of the explosion * @param strength strength of the explosion - * @param additionalData data passed via {@link Instance#explode(float, float, float, float, NBTCompound)} )}. Can be null + * @param additionalData data passed via {@link Instance#explode(float, float, float, float, CompoundBinaryTag)} )}. Can be null * @return Explosion object representing the algorithm to use */ - Explosion createExplosion(float centerX, float centerY, float centerZ, float strength, NBTCompound additionalData); + Explosion createExplosion(float centerX, float centerY, float centerZ, float strength, CompoundBinaryTag additionalData); } diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 3cfe29d21..7d959aa25 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -2,6 +2,7 @@ package net.minestom.server.instance; import it.unimi.dsi.fastutil.objects.ObjectArraySet; import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.pointer.Pointers; import net.minestom.server.MinecraftServer; import net.minestom.server.ServerProcess; @@ -45,7 +46,6 @@ import net.minestom.server.world.DimensionType; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.time.Duration; import java.util.*; @@ -792,7 +792,7 @@ public abstract class Instance implements Block.Getter, Block.Setter, * @param additionalData data to pass to the explosion supplier * @throws IllegalStateException If no {@link ExplosionSupplier} was supplied */ - public void explode(float centerX, float centerY, float centerZ, float strength, @Nullable NBTCompound additionalData) { + public void explode(float centerX, float centerY, float centerZ, float strength, @Nullable CompoundBinaryTag additionalData) { final ExplosionSupplier explosionSupplier = getExplosionSupplier(); Check.stateCondition(explosionSupplier == null, "Tried to create an explosion with no explosion supplier"); final Explosion explosion = explosionSupplier.createExplosion(centerX, centerY, centerZ, strength, additionalData); diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index f9fed417a..b62a9c1c7 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -1,6 +1,7 @@ package net.minestom.server.instance; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; @@ -32,7 +33,6 @@ import net.minestom.server.world.DimensionType; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import space.vectrix.flare.fastutil.Long2ObjectSyncMap; @@ -186,7 +186,7 @@ public class InstanceContainer extends Instance { chunk.sendPacketToViewers(new BlockChangePacket(blockPosition, block.stateId())); var registry = block.registry(); if (registry.isBlockEntity()) { - final NBTCompound data = BlockUtils.extractClientNbt(block); + final CompoundBinaryTag data = BlockUtils.extractClientNbt(block); chunk.sendPacketToViewers(new BlockEntityDataPacket(blockPosition, registry.blockEntityId(), data)); } } diff --git a/src/main/java/net/minestom/server/instance/LightingChunk.java b/src/main/java/net/minestom/server/instance/LightingChunk.java index 4bbd33c83..66fbbe01d 100644 --- a/src/main/java/net/minestom/server/instance/LightingChunk.java +++ b/src/main/java/net/minestom/server/instance/LightingChunk.java @@ -1,5 +1,7 @@ package net.minestom.server.instance; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.LongArrayBinaryTag; import net.minestom.server.MinecraftServer; import net.minestom.server.ServerFlag; import net.minestom.server.collision.Shape; @@ -16,8 +18,6 @@ import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.chunk.ChunkUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -202,14 +202,17 @@ public class LightingChunk extends DynamicChunk { } @Override - protected NBTCompound computeHeightmap() { + protected CompoundBinaryTag computeHeightmap() { // Heightmap int[] heightmap = getHeightmap(); int dimensionHeight = getInstance().getDimensionType().getHeight(); final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight); - return NBT.Compound(Map.of( - "MOTION_BLOCKING", NBT.LongArray(encodeBlocks(heightmap, bitsForHeight)), - "WORLD_SURFACE", NBT.LongArray(encodeBlocks(heightmap, bitsForHeight)))); + + LongArrayBinaryTag encoded = LongArrayBinaryTag.longArrayBinaryTag(encodeBlocks(heightmap, bitsForHeight)); + return CompoundBinaryTag.builder() + .put("MOTION_BLOCKING", encoded) + .put("WORLD_SURFACE", encoded) + .build(); } // Lazy compute heightmap diff --git a/src/main/java/net/minestom/server/instance/block/Block.java b/src/main/java/net/minestom/server/instance/block/Block.java index 2f4f90cfb..f12ed3aa2 100644 --- a/src/main/java/net/minestom/server/instance/block/Block.java +++ b/src/main/java/net/minestom/server/instance/block/Block.java @@ -1,15 +1,15 @@ package net.minestom.server.instance.block; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.coordinate.Point; import net.minestom.server.instance.Instance; import net.minestom.server.instance.batch.Batch; -import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagReadable; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.*; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Collection; import java.util.Map; @@ -67,7 +67,7 @@ public sealed interface Block extends StaticProtocolObject, TagReadable, Blocks * @return a new block with different nbt */ @Contract(pure = true) - @NotNull Block withNbt(@Nullable NBTCompound compound); + @NotNull Block withNbt(@Nullable CompoundBinaryTag compound); /** * Creates a new block with the specified {@link BlockHandler handler}. @@ -86,7 +86,7 @@ public sealed interface Block extends StaticProtocolObject, TagReadable, Blocks * @return the block nbt, null if not present */ @Contract(pure = true) - @Nullable NBTCompound nbt(); + @Nullable CompoundBinaryTag nbt(); @Contract(pure = true) default boolean hasNbt() { diff --git a/src/main/java/net/minestom/server/instance/block/BlockImpl.java b/src/main/java/net/minestom/server/instance/block/BlockImpl.java index 6140b7fbe..6760f9464 100644 --- a/src/main/java/net/minestom/server/instance/block/BlockImpl.java +++ b/src/main/java/net/minestom/server/instance/block/BlockImpl.java @@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.registry.Registry; import net.minestom.server.tag.Tag; import net.minestom.server.utils.ArrayUtils; @@ -14,8 +15,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import org.jetbrains.annotations.Unmodifiable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound; import java.time.Duration; import java.util.*; @@ -23,7 +22,7 @@ import java.util.function.Function; record BlockImpl(@NotNull Registry.BlockEntry registry, byte @NotNull [] propertiesArray, - @Nullable NBTCompound nbt, + @Nullable CompoundBinaryTag nbt, @Nullable BlockHandler handler) implements Block { // Block state -> block object private static final ObjectArray BLOCK_STATE_MAP = ObjectArray.singleThread(); @@ -86,7 +85,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry, final int defaultState = properties.getInt("defaultStateId"); return getState(defaultState); }); - private static final Cache NBT_CACHE = Caffeine.newBuilder() + private static final Cache NBT_CACHE = Caffeine.newBuilder() .expireAfterWrite(Duration.ofMinutes(5)) .weakValues() .build(); @@ -144,14 +143,16 @@ record BlockImpl(@NotNull Registry.BlockEntry registry, @Override public @NotNull Block withTag(@NotNull Tag tag, @Nullable T value) { - var temporaryNbt = new MutableNBTCompound(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY)); - tag.write(temporaryNbt, value); - final var finalNbt = temporaryNbt.getSize() > 0 ? NBT_CACHE.get(temporaryNbt.toCompound(), Function.identity()) : null; + var builder = CompoundBinaryTag.builder(); + if (nbt != null) builder.put(nbt); + tag.write(builder, value); + var temporaryNbt = builder.build(); + final var finalNbt = temporaryNbt.size() > 0 ? NBT_CACHE.get(temporaryNbt, Function.identity()) : null; return new BlockImpl(registry, propertiesArray, finalNbt, handler); } @Override - public @NotNull Block withNbt(@Nullable NBTCompound compound) { + public @NotNull Block withNbt(@Nullable CompoundBinaryTag compound) { return new BlockImpl(registry, propertiesArray, compound, handler); } @@ -183,7 +184,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry, @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return tag.read(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY)); + return tag.read(Objects.requireNonNullElse(nbt, CompoundBinaryTag.empty())); } private Map possibleProperties() { diff --git a/src/main/java/net/minestom/server/item/ItemMeta.java b/src/main/java/net/minestom/server/item/ItemMeta.java index bb2a99038..b0c17517c 100644 --- a/src/main/java/net/minestom/server/item/ItemMeta.java +++ b/src/main/java/net/minestom/server/item/ItemMeta.java @@ -1,5 +1,6 @@ package net.minestom.server.item; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.instance.block.Block; import net.minestom.server.item.attribute.ItemAttribute; @@ -12,7 +13,6 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.*; import java.util.function.Consumer; @@ -26,7 +26,7 @@ public sealed interface ItemMeta extends TagReadable, NetworkBuffer.Writer @Contract(value = "_, -> new", pure = true) @NotNull ItemMeta with(@NotNull Consumer<@NotNull Builder> builderConsumer); - @NotNull NBTCompound toNBT(); + @NotNull CompoundBinaryTag toNBT(); @NotNull String toSNBT(); diff --git a/src/main/java/net/minestom/server/item/ItemMetaImpl.java b/src/main/java/net/minestom/server/item/ItemMetaImpl.java index 22483b9e5..063b40111 100644 --- a/src/main/java/net/minestom/server/item/ItemMetaImpl.java +++ b/src/main/java/net/minestom/server/item/ItemMetaImpl.java @@ -1,16 +1,17 @@ package net.minestom.server.item; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.TagStringIO; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import java.io.IOException; import java.util.Objects; import java.util.function.Consumer; -import static net.minestom.server.network.NetworkBuffer.BYTE; import static net.minestom.server.network.NetworkBuffer.NBT; record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta { @@ -29,23 +30,22 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta { } @Override - public @NotNull NBTCompound toNBT() { + public @NotNull CompoundBinaryTag toNBT() { return tagHandler.asCompound(); } @Override public @NotNull String toSNBT() { - return toNBT().toSNBT(); + try { + return TagStringIO.get().asString(toNBT()); + } catch (IOException e) { + throw new RuntimeException("Failed to convert to SNBT", e); + } } @Override public void write(@NotNull NetworkBuffer writer) { - final NBTCompound nbt = toNBT(); - if (nbt.isEmpty()) { - writer.write(BYTE, (byte) 0); - return; - } - writer.write(NBT, nbt); + writer.write(NBT, toNBT()); } @Override diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 7d0beeb82..9a2f17ae1 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -1,5 +1,6 @@ package net.minestom.server.item; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; @@ -12,8 +13,8 @@ import net.minestom.server.tag.TagReadable; import net.minestom.server.tag.TagWritable; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.*; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import java.io.IOException; import java.util.List; import java.util.function.Consumer; import java.util.function.IntUnaryOperator; @@ -48,13 +49,13 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource asHoverEvent(@NotNull UnaryOperator op) { - final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC); - return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.showItem(material(), amount(), tagHolder))); + try { + final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC); + return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.showItem(material(), amount(), tagHolder))); + } catch (IOException e) { + //todo(matt): revisit, + throw new RuntimeException(e); + } } /** @@ -179,7 +185,7 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource 0) builder.put("tag", nbt); + return builder.build(); } @Contract(value = "-> new", pure = true) @@ -129,7 +126,7 @@ record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implement } @Override - public ItemStack.@NotNull Builder meta(@NotNull NBTCompound compound) { + public ItemStack.@NotNull Builder meta(@NotNull CompoundBinaryTag compound) { return metaBuilder(new ItemMetaImpl.Builder(TagHandler.fromCompound(compound))); } diff --git a/src/main/java/net/minestom/server/item/armor/TrimManager.java b/src/main/java/net/minestom/server/item/armor/TrimManager.java index c9c94aa1a..697965fe8 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimManager.java +++ b/src/main/java/net/minestom/server/item/armor/TrimManager.java @@ -1,20 +1,19 @@ package net.minestom.server.item.armor; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; import net.minestom.server.item.Material; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTType; import java.util.HashSet; -import java.util.Map; import java.util.Set; public class TrimManager { private final Set trimMaterials; private final Set trimPatterns; - private NBTCompound trimMaterialCache = null; - private NBTCompound trimPatternCache = null; + private CompoundBinaryTag trimMaterialCache = null; + private CompoundBinaryTag trimPatternCache = null; public TrimManager() { this.trimMaterials = new HashSet<>(); @@ -30,38 +29,28 @@ public class TrimManager { } - public NBTCompound getTrimMaterialNBT() { + public CompoundBinaryTag getTrimMaterialNBT() { if (trimMaterialCache == null) { - var trimMaterials = this.trimMaterials.stream() - .map((trimMaterial) -> NBT.Compound(Map.of( - "id", NBT.Int(trimMaterial.id()), - "name", NBT.String(trimMaterial.name()), - "element", trimMaterial.asNBT() - ))) - .toList(); - - trimMaterialCache = NBT.Compound(Map.of( - "type", NBT.String("minecraft:trim_material"), - "value", NBT.List(NBTType.TAG_Compound, trimMaterials) - )); + ListBinaryTag.Builder entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); + for (TrimMaterial trimMaterial : this.trimMaterials) + entries.add(trimMaterial.asNBT()); + trimMaterialCache = CompoundBinaryTag.builder() + .putString("type", "minecraft:trim_material") + .put("value", entries.build()) + .build(); } return trimMaterialCache; } - public NBTCompound getTrimPatternNBT() { + public CompoundBinaryTag getTrimPatternNBT() { if (trimPatternCache == null) { - var trimPatterns = this.trimPatterns.stream() - .map((trimPattern) -> NBT.Compound(Map.of( - "id", NBT.Int(trimPattern.id()), - "name", NBT.String(trimPattern.name()), - "element", trimPattern.asNBT() - ))) - .toList(); - - trimPatternCache = NBT.Compound(Map.of( - "type", NBT.String("minecraft:trim_pattern"), - "value", NBT.List(NBTType.TAG_Compound, trimPatterns) - )); + ListBinaryTag.Builder entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); + for (TrimPattern trimPattern : this.trimPatterns) + entries.add(trimPattern.asNBT()); + trimPatternCache = CompoundBinaryTag.builder() + .putString("type", "minecraft:trim_pattern") + .put("value", entries.build()) + .build(); } return trimPatternCache; diff --git a/src/main/java/net/minestom/server/item/armor/TrimMaterial.java b/src/main/java/net/minestom/server/item/armor/TrimMaterial.java index adb80be3d..0bf3bb3b4 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimMaterial.java +++ b/src/main/java/net/minestom/server/item/armor/TrimMaterial.java @@ -1,13 +1,13 @@ package net.minestom.server.item.armor; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.item.Material; -import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Collection; import java.util.Map; @@ -84,6 +84,6 @@ public interface TrimMaterial extends StaticProtocolObject { return registry().description(); } - NBTCompound asNBT(); + CompoundBinaryTag asNBT(); } diff --git a/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java b/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java index 95fe79b4d..2af492f28 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java +++ b/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java @@ -1,9 +1,9 @@ package net.minestom.server.item.armor; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; import net.minestom.server.registry.Registry; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Collection; import java.util.Map; @@ -31,19 +31,15 @@ record TrimMaterialImpl(Registry.TrimMaterialEntry registry, int id) implements return CONTAINER.values(); } - public NBTCompound asNBT() { - return NBT.Compound(nbt -> { - nbt.setString("asset_name", assetName()); - nbt.setString("ingredient", ingredient().namespace().asString()); - nbt.setFloat("item_model_index", itemModelIndex()); - nbt.set("override_armor_materials", NBT.Compound(overrideArmorMaterials().entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> NBT.String(entry.getValue()) - )) - )); - nbt.set("description", NbtComponentSerializer.nbt().serialize(description())); - }); + public CompoundBinaryTag asNBT() { + return CompoundBinaryTag.builder() + .putString("asset_name", assetName()) + .putString("ingredient", ingredient().namespace().asString()) + .putFloat("item_model_index", itemModelIndex()) + .put("override_armor_materials", CompoundBinaryTag.from(overrideArmorMaterials().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> StringBinaryTag.stringBinaryTag(entry.getValue()))))) + .put("description", NbtComponentSerializer.nbt().serialize(description())) + .build(); } } diff --git a/src/main/java/net/minestom/server/item/armor/TrimPattern.java b/src/main/java/net/minestom/server/item/armor/TrimPattern.java index 1823e42bc..7b824ba0d 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimPattern.java +++ b/src/main/java/net/minestom/server/item/armor/TrimPattern.java @@ -1,13 +1,13 @@ package net.minestom.server.item.armor; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.item.Material; -import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Collection; @@ -61,6 +61,6 @@ public interface TrimPattern extends StaticProtocolObject { return registry().decal(); } - NBTCompound asNBT(); + CompoundBinaryTag asNBT(); } diff --git a/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java b/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java index 6b4ecd1aa..9d4df9ed0 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java +++ b/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java @@ -1,9 +1,8 @@ package net.minestom.server.item.armor; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; import net.minestom.server.registry.Registry; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; @@ -29,13 +28,13 @@ record TrimPatternImpl(Registry.TrimPatternEntry registry, int id) implements Tr return CONTAINER.values(); } - public NBTCompound asNBT() { - return NBT.Compound(nbt -> { - nbt.setString("asset_id", assetID().asString()); - nbt.setString("template_item", template().namespace().asString()); - nbt.set("description", NbtComponentSerializer.nbt().serialize(description())); - nbt.setByte("decal", (byte) (decal() ? 1 : 0)); - }); + public CompoundBinaryTag asNBT() { + return CompoundBinaryTag.builder() + .putString("asset_id", assetID().asString()) + .putString("template_item", template().namespace().asString()) + .put("description", NbtComponentSerializer.nbt().serialize(description())) + .putBoolean("decal", decal()) + .build(); } } diff --git a/src/main/java/net/minestom/server/item/firework/FireworkEffect.java b/src/main/java/net/minestom/server/item/firework/FireworkEffect.java index 4cbe1570f..e9feaebee 100644 --- a/src/main/java/net/minestom/server/item/firework/FireworkEffect.java +++ b/src/main/java/net/minestom/server/item/firework/FireworkEffect.java @@ -1,14 +1,11 @@ package net.minestom.server.item.firework; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.color.Color; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTIntArray; import java.util.ArrayList; import java.util.List; -import java.util.Map; public record FireworkEffect(boolean flicker, boolean trail, @NotNull FireworkEffectType type, @@ -25,36 +22,34 @@ public record FireworkEffect(boolean flicker, boolean trail, * @param compound The NBT connection, which should be a fireworks effect. * @return A new created firework effect. */ - public static @NotNull FireworkEffect fromCompound(@NotNull NBTCompound compound) { + public static @NotNull FireworkEffect fromCompound(@NotNull CompoundBinaryTag compound) { List primaryColor = new ArrayList<>(); List secondaryColor = new ArrayList<>(); - if (compound.get("Colors") instanceof NBTIntArray colors) { - for (int rgb : colors) primaryColor.add(new Color(rgb)); - } - if (compound.get("FadeColors") instanceof NBTIntArray fadeColors) { - for (int rgb : fadeColors) secondaryColor.add(new Color(rgb)); - } + for (int rgb : compound.getIntArray("Colors")) + primaryColor.add(new Color(rgb)); + for (int rgb : compound.getIntArray("FadeColors")) + secondaryColor.add(new Color(rgb)); - boolean flicker = compound.containsKey("Flicker") && compound.getBoolean("Flicker"); - boolean trail = compound.containsKey("Trail") && compound.getBoolean("Trail"); - FireworkEffectType type = compound.containsKey("Type") ? - FireworkEffectType.byId(compound.getAsByte("Type")) : FireworkEffectType.SMALL_BALL; + boolean flicker = compound.getBoolean("Flicker"); + boolean trail = compound.getBoolean("Trail"); + FireworkEffectType type = FireworkEffectType.byId(compound.getByte("Type")); return new FireworkEffect(flicker, trail, type, primaryColor, secondaryColor); } /** - * Retrieves the {@link FireworkEffect} as a {@link NBTCompound}. + * Retrieves the {@link FireworkEffect} as a {@link CompoundBinaryTag}. * * @return The firework effect as a nbt compound. */ - public @NotNull NBTCompound asCompound() { - return NBT.Compound(Map.of( - "Flicker", NBT.Boolean(flicker), - "Trail", NBT.Boolean(trail), - "Type", NBT.Byte(type.getType()), - "Colors", NBT.IntArray(colors.stream().mapToInt(Color::asRGB).toArray()), - "FadeColors", NBT.IntArray(fadeColors.stream().mapToInt(Color::asRGB).toArray()))); + public @NotNull CompoundBinaryTag asCompound() { + return CompoundBinaryTag.builder() + .putBoolean("Flicker", flicker) + .putBoolean("Trail", trail) + .putByte("Type", (byte) type.getType()) + .putIntArray("Colors", colors.stream().mapToInt(Color::asRGB).toArray()) + .putIntArray("FadeColors", fadeColors.stream().mapToInt(Color::asRGB).toArray()) + .build(); } } diff --git a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java index b22c19577..40b4ca98d 100644 --- a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java @@ -1,32 +1,30 @@ package net.minestom.server.item.metadata; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; import net.minestom.server.entity.PlayerSkin; import net.minestom.server.item.ItemMetaView; import net.minestom.server.tag.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTList; -import org.jglrxavpok.hephaistos.nbt.NBTType; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.UUID; public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView { public static final Tag SKULL_OWNER = Tag.UUID("Id").path("SkullOwner"); public static final Tag SKIN = Tag.Structure("Properties", new TagSerializer() { - private static final Tag TEXTURES = Tag.NBT("textures"); + private static final Tag TEXTURES = Tag.NBT("textures"); @Override public @Nullable PlayerSkin read(@NotNull TagReadable reader) { - final NBT result = reader.getTag(TEXTURES); - if (!(result instanceof NBTList)) return null; - final NBTList textures = (NBTList) result; - final NBTCompound texture = textures.get(0); + final BinaryTag result = reader.getTag(TEXTURES); + if (!(result instanceof ListBinaryTag textures)) return null; + final CompoundBinaryTag texture = textures.getCompound(0); final String value = texture.getString("Value"); final String signature = texture.getString("Signature"); return new PlayerSkin(value, signature); @@ -36,9 +34,9 @@ public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView textures = new NBTList<>(NBTType.TAG_Compound, - List.of(NBT.Compound(Map.of("Value", NBT.String(value), "Signature", NBT.String(signature))))); - writer.setTag(TEXTURES, textures); + writer.setTag(TEXTURES, ListBinaryTag.listBinaryTag(BinaryTagTypes.COMPOUND, List.of( + CompoundBinaryTag.builder().putString("Value", value).putString("Signature", signature).build() + ))); } }).path("SkullOwner"); diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index 231fb8139..729832020 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -1,5 +1,6 @@ package net.minestom.server.listener; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.GameMode; @@ -20,7 +21,6 @@ import net.minestom.server.network.packet.server.play.AcknowledgeBlockChangePack import net.minestom.server.network.packet.server.play.BlockEntityDataPacket; import net.minestom.server.utils.block.BlockUtils; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; public final class PlayerDiggingListener { @@ -56,7 +56,7 @@ public final class PlayerDiggingListener { // Refresh block on player screen in case it had special data (like a sign) var registry = diggingResult.block().registry(); if (registry.isBlockEntity()) { - final NBTCompound data = BlockUtils.extractClientNbt(diggingResult.block()); + final CompoundBinaryTag data = BlockUtils.extractClientNbt(diggingResult.block()); player.sendPacketToViewersAndSelf(new BlockEntityDataPacket(blockPosition, registry.blockEntityId(), data)); } } diff --git a/src/main/java/net/minestom/server/message/Messenger.java b/src/main/java/net/minestom/server/message/Messenger.java index 16af3663e..df3d09acb 100644 --- a/src/main/java/net/minestom/server/message/Messenger.java +++ b/src/main/java/net/minestom/server/message/Messenger.java @@ -1,5 +1,7 @@ package net.minestom.server.message; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.TagStringIO; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.entity.Player; @@ -7,11 +9,8 @@ import net.minestom.server.network.packet.server.play.SystemChatPacket; import net.minestom.server.utils.PacketUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTException; -import org.jglrxavpok.hephaistos.parser.SNBTParser; -import java.io.StringReader; +import java.io.IOException; import java.util.Collection; import java.util.Objects; import java.util.UUID; @@ -27,11 +26,11 @@ public final class Messenger { private static final UUID NO_SENDER = new UUID(0, 0); private static final SystemChatPacket CANNOT_SEND_PACKET = new SystemChatPacket(CANNOT_SEND_MESSAGE, false); - private static final NBTCompound CHAT_REGISTRY; + private static final CompoundBinaryTag CHAT_REGISTRY; static { try { - CHAT_REGISTRY = (NBTCompound) new SNBTParser(new StringReader( + CHAT_REGISTRY = TagStringIO.get().asCompound( """ { "type": "minecraft:chat_type", @@ -57,13 +56,13 @@ public final class Messenger { } } ] }""" - )).parse(); - } catch (NBTException e) { + ); + } catch (IOException e) { throw new RuntimeException(e); } } - public static @NotNull NBTCompound chatRegistry() { + public static @NotNull CompoundBinaryTag chatRegistry() { return CHAT_REGISTRY; } diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 48508908f..ec78114e9 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -1,5 +1,6 @@ package net.minestom.server.network; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; @@ -32,7 +33,8 @@ import org.jctools.queues.MpscUnboundedArrayQueue; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -280,14 +282,15 @@ public final class ConnectionManager { // Registry data (if it should be sent) if (event.willSendRegistryData()) { - var registry = new HashMap(); - registry.put("minecraft:chat_type", Messenger.chatRegistry()); - registry.put("minecraft:dimension_type", MinecraftServer.getDimensionTypeManager().toNBT()); - registry.put("minecraft:worldgen/biome", MinecraftServer.getBiomeManager().toNBT()); - registry.put("minecraft:damage_type", DamageType.getNBT()); - registry.put("minecraft:trim_material", MinecraftServer.getTrimManager().getTrimMaterialNBT()); - registry.put("minecraft:trim_pattern", MinecraftServer.getTrimManager().getTrimPatternNBT()); - player.sendPacket(new RegistryDataPacket(NBT.Compound(registry))); + var registryCompound = CompoundBinaryTag.builder() + .put("minecraft:chat_type", Messenger.chatRegistry()) + .put("minecraft:dimension_type", MinecraftServer.getDimensionTypeManager().toNBT()) + .put("minecraft:worldgen/biome", MinecraftServer.getBiomeManager().toNBT()) + .put("minecraft:damage_type", DamageType.getNBT()) +// .put("minecraft:trim_material", MinecraftServer.getTrimManager().getTrimMaterialNBT()) +// .put("minecraft:trim_pattern", MinecraftServer.getTrimManager().getTrimPatternNBT()) + .build(); + player.sendPacket(new RegistryDataPacket(registryCompound)); player.sendPacket(TagsPacket.DEFAULT_TAGS); } diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index d41c6cd43..f4a168f62 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -1,5 +1,6 @@ package net.minestom.server.network; +import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; import net.minestom.server.entity.Entity; @@ -15,10 +16,9 @@ import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTReader; -import org.jglrxavpok.hephaistos.nbt.NBTWriter; +import java.io.DataInput; +import java.io.DataOutput; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.*; @@ -40,7 +40,7 @@ public final class NetworkBuffer { public static final Type VAR_LONG = new NetworkBufferTypeImpl.VarLongType(); public static final Type RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(); public static final Type STRING = new NetworkBufferTypeImpl.StringType(); - public static final Type NBT = new NetworkBufferTypeImpl.NbtType(); + public static final Type NBT = new NetworkBufferTypeImpl.NbtType(); public static final Type BLOCK_POSITION = new NetworkBufferTypeImpl.BlockPositionType(); public static final Type COMPONENT = new NetworkBufferTypeImpl.ComponentType(); public static final Type JSON_COMPONENT = new NetworkBufferTypeImpl.JsonComponentType(); @@ -80,8 +80,8 @@ public final class NetworkBuffer { int writeIndex; int readIndex; - NBTWriter nbtWriter; - NBTReader nbtReader; + DataOutput nbtWriter; + DataInput nbtReader; public NetworkBuffer(@NotNull ByteBuffer buffer, boolean resizable) { this.nioBuffer = buffer.order(ByteOrder.BIG_ENDIAN); diff --git a/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java index 2b0d4d2ca..1d8aa950a 100644 --- a/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java @@ -1,16 +1,16 @@ package net.minestom.server.network.packet.server.configuration; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import static net.minestom.server.network.NetworkBuffer.NBT; -public record RegistryDataPacket(@NotNull NBTCompound data) implements ServerPacket.Configuration { +public record RegistryDataPacket(@NotNull CompoundBinaryTag data) implements ServerPacket.Configuration { public RegistryDataPacket(@NotNull NetworkBuffer buffer) { - this((NBTCompound) buffer.read(NBT)); + this((CompoundBinaryTag) buffer.read(NBT)); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/BlockEntityDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/BlockEntityDataPacket.java index f878bfbeb..e990226a7 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/BlockEntityDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/BlockEntityDataPacket.java @@ -1,19 +1,19 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.coordinate.Point; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import static net.minestom.server.network.NetworkBuffer.*; public record BlockEntityDataPacket(@NotNull Point blockPosition, int action, - @Nullable NBTCompound data) implements ServerPacket.Play { + @Nullable CompoundBinaryTag data) implements ServerPacket.Play { public BlockEntityDataPacket(@NotNull NetworkBuffer reader) { - this(reader.read(BLOCK_POSITION), reader.read(VAR_INT), (NBTCompound) reader.read(NBT)); + this(reader.read(BLOCK_POSITION), reader.read(VAR_INT), (CompoundBinaryTag) reader.read(NBT)); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java index 0e0b02bf5..35e97f6f1 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java @@ -1,20 +1,20 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.potion.Potion; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import static net.minestom.server.network.NetworkBuffer.*; public record EntityEffectPacket(int entityId, @NotNull Potion potion, - @Nullable NBTCompound factorCodec) implements ServerPacket.Play { + @Nullable CompoundBinaryTag factorCodec) implements ServerPacket.Play { public EntityEffectPacket(@NotNull NetworkBuffer reader) { this(reader.read(VAR_INT), new Potion(reader), - reader.read(BOOLEAN) ? (NBTCompound) reader.read(NBT) : null); + reader.read(BOOLEAN) ? (CompoundBinaryTag) reader.read(NBT) : null); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/NbtQueryResponsePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/NbtQueryResponsePacket.java index 904009817..d48187ec2 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/NbtQueryResponsePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/NbtQueryResponsePacket.java @@ -1,16 +1,17 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.network.ConnectionState; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import static net.minestom.server.network.NetworkBuffer.*; -public record NbtQueryResponsePacket(int transactionId, NBTCompound data) implements ServerPacket.Play { +public record NbtQueryResponsePacket(int transactionId, CompoundBinaryTag data) implements ServerPacket.Play { public NbtQueryResponsePacket(@NotNull NetworkBuffer reader) { - this(reader.read(VAR_INT), (NBTCompound) reader.read(NBT)); + this(reader.read(VAR_INT), (CompoundBinaryTag) reader.read(NBT)); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/data/ChunkData.java b/src/main/java/net/minestom/server/network/packet/server/play/data/ChunkData.java index c0126205d..877832732 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/data/ChunkData.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/data/ChunkData.java @@ -1,12 +1,12 @@ package net.minestom.server.network.packet.server.play.data; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.coordinate.Point; import net.minestom.server.instance.block.Block; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.block.BlockUtils; import net.minestom.server.utils.chunk.ChunkUtils; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.HashMap; import java.util.Map; @@ -14,7 +14,7 @@ import java.util.stream.Collectors; import static net.minestom.server.network.NetworkBuffer.*; -public record ChunkData(@NotNull NBTCompound heightmaps, byte @NotNull [] data, +public record ChunkData(@NotNull CompoundBinaryTag heightmaps, byte @NotNull [] data, @NotNull Map blockEntities) implements NetworkBuffer.Writer { public ChunkData { blockEntities = blockEntities.entrySet() @@ -24,7 +24,7 @@ public record ChunkData(@NotNull NBTCompound heightmaps, byte @NotNull [] data, } public ChunkData(@NotNull NetworkBuffer reader) { - this((NBTCompound) reader.read(NBT), reader.read(BYTE_ARRAY), + this((CompoundBinaryTag) reader.read(NBT), reader.read(BYTE_ARRAY), readBlockEntities(reader)); } @@ -46,7 +46,7 @@ public record ChunkData(@NotNull NBTCompound heightmaps, byte @NotNull [] data, writer.write(SHORT, (short) point.blockY()); // y writer.write(VAR_INT, registry.blockEntityId()); - final NBTCompound nbt = BlockUtils.extractClientNbt(block); + final CompoundBinaryTag nbt = BlockUtils.extractClientNbt(block); assert nbt != null; writer.write(NBT, nbt); // block nbt } @@ -59,7 +59,7 @@ public record ChunkData(@NotNull NBTCompound heightmaps, byte @NotNull [] data, final byte xz = reader.read(BYTE); final short y = reader.read(SHORT); final int blockEntityId = reader.read(VAR_INT); - final NBTCompound nbt = (NBTCompound) reader.read(NBT); + final CompoundBinaryTag nbt = (CompoundBinaryTag) reader.read(NBT); // TODO create block object } return blockEntities; diff --git a/src/main/java/net/minestom/server/permission/Permission.java b/src/main/java/net/minestom/server/permission/Permission.java index 1959e2569..1b7987572 100644 --- a/src/main/java/net/minestom/server/permission/Permission.java +++ b/src/main/java/net/minestom/server/permission/Permission.java @@ -1,23 +1,23 @@ package net.minestom.server.permission; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Objects; /** * Representation of a permission granted to a {@link CommandSender}. * Each permission has a string representation used as an identifier, and an optional - * {@link NBTCompound} used to store additional data. + * {@link CompoundBinaryTag} used to store additional data. *

* The class is immutable. */ public class Permission { private final String permissionName; - private final NBTCompound data; + private final CompoundBinaryTag data; /** * Creates a new permission object with optional data. @@ -25,7 +25,7 @@ public class Permission { * @param permissionName the name of the permission * @param data the optional data of the permission */ - public Permission(@NotNull String permissionName, @Nullable NBTCompound data) { + public Permission(@NotNull String permissionName, @Nullable CompoundBinaryTag data) { this.permissionName = permissionName; this.data = data; } @@ -55,7 +55,7 @@ public class Permission { * @return the nbt data of this permission, can be null if not any */ @Nullable - public NBTCompound getNBTData() { + public CompoundBinaryTag getNBTData() { return data; } diff --git a/src/main/java/net/minestom/server/permission/PermissionHandler.java b/src/main/java/net/minestom/server/permission/PermissionHandler.java index 0683e3497..a4a028a2c 100644 --- a/src/main/java/net/minestom/server/permission/PermissionHandler.java +++ b/src/main/java/net/minestom/server/permission/PermissionHandler.java @@ -2,8 +2,6 @@ package net.minestom.server.permission; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.parser.SNBTParser; import java.util.Set; import java.util.regex.Pattern; @@ -14,8 +12,7 @@ import java.util.regex.Pattern; * Permissions are in-memory only by default. * You have however the capacity to store them persistently as the {@link Permission} object * is serializer-friendly, {@link Permission#getPermissionName()} being a {@link String} - * and {@link Permission#getNBTData()} serializable into a string using {@link NBTCompound#toSNBT()} - * and deserialized back with {@link SNBTParser#parse()}. + * and {@link Permission#getNBTData()} serializable into a string using {@link net.kyori.adventure.nbt.TagStringIO}. */ public interface PermissionHandler { diff --git a/src/main/java/net/minestom/server/permission/PermissionVerifier.java b/src/main/java/net/minestom/server/permission/PermissionVerifier.java index 51323b5d8..99797569c 100644 --- a/src/main/java/net/minestom/server/permission/PermissionVerifier.java +++ b/src/main/java/net/minestom/server/permission/PermissionVerifier.java @@ -1,10 +1,10 @@ package net.minestom.server.permission; +import net.kyori.adventure.nbt.CompoundBinaryTag; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; /** - * Interface used to check if the {@link NBTCompound nbt data} of a {@link Permission} is correct. + * Interface used to check if the {@link CompoundBinaryTag nbt data} of a {@link Permission} is correct. */ @FunctionalInterface public interface PermissionVerifier { @@ -16,5 +16,5 @@ public interface PermissionVerifier { * @return true if {@link PermissionHandler#hasPermission(String, PermissionVerifier)} * should return true, false otherwise */ - boolean isValid(@Nullable NBTCompound nbtCompound); + boolean isValid(@Nullable CompoundBinaryTag nbtCompound); } diff --git a/src/main/java/net/minestom/server/tag/Serializers.java b/src/main/java/net/minestom/server/tag/Serializers.java index 9d14f112b..8a37efc5e 100644 --- a/src/main/java/net/minestom/server/tag/Serializers.java +++ b/src/main/java/net/minestom/server/tag/Serializers.java @@ -1,10 +1,10 @@ package net.minestom.server.tag; +import net.kyori.adventure.nbt.*; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.ServerFlag; import net.minestom.server.item.ItemStack; -import org.jglrxavpok.hephaistos.nbt.*; import java.util.UUID; import java.util.function.Function; @@ -13,40 +13,41 @@ import java.util.function.Function; * Basic serializers for {@link Tag tags}. */ final class Serializers { - static final Entry BYTE = new Entry<>(NBTType.TAG_Byte, NBTByte::getValue, NBT::Byte); - static final Entry BOOLEAN = new Entry<>(NBTType.TAG_Byte, NBTByte::asBoolean, NBT::Boolean); - static final Entry SHORT = new Entry<>(NBTType.TAG_Short, NBTShort::getValue, NBT::Short); - static final Entry INT = new Entry<>(NBTType.TAG_Int, NBTInt::getValue, NBT::Int); - static final Entry LONG = new Entry<>(NBTType.TAG_Long, NBTLong::getValue, NBT::Long); - static final Entry FLOAT = new Entry<>(NBTType.TAG_Float, NBTFloat::getValue, NBT::Float); - static final Entry DOUBLE = new Entry<>(NBTType.TAG_Double, NBTDouble::getValue, NBT::Double); - static final Entry STRING = new Entry<>(NBTType.TAG_String, NBTString::getValue, NBT::String); - static final Entry NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity()); + static final Entry BYTE = new Entry<>(BinaryTagTypes.BYTE, ByteBinaryTag::value, ByteBinaryTag::byteBinaryTag); + static final Entry BOOLEAN = new Entry<>(BinaryTagTypes.BYTE, b -> b.value() != 0, b -> b ? ByteBinaryTag.ONE : ByteBinaryTag.ZERO); + static final Entry SHORT = new Entry<>(BinaryTagTypes.SHORT, ShortBinaryTag::value, ShortBinaryTag::shortBinaryTag); + static final Entry INT = new Entry<>(BinaryTagTypes.INT, IntBinaryTag::value, IntBinaryTag::intBinaryTag); + static final Entry LONG = new Entry<>(BinaryTagTypes.LONG, LongBinaryTag::value, LongBinaryTag::longBinaryTag); + static final Entry FLOAT = new Entry<>(BinaryTagTypes.FLOAT, FloatBinaryTag::value, FloatBinaryTag::floatBinaryTag); + static final Entry DOUBLE = new Entry<>(BinaryTagTypes.DOUBLE, DoubleBinaryTag::value, DoubleBinaryTag::doubleBinaryTag); + static final Entry STRING = new Entry<>(BinaryTagTypes.STRING, StringBinaryTag::value, StringBinaryTag::stringBinaryTag); + static final Entry NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity()); - static final Entry UUID = new Entry<>(NBTType.TAG_Int_Array, intArray -> intArrayToUuid(intArray.getValue().copyArray()), - uuid -> NBT.IntArray(uuidToIntArray(uuid))); - static final Entry ITEM = new Entry<>(NBTType.TAG_Compound, ItemStack::fromItemNBT, ItemStack::toItemNBT); - static final Entry COMPONENT = new Entry<>(NBTType.TAG_String, input -> GsonComponentSerializer.gson().deserialize(input.getValue()), - component -> NBT.String(GsonComponentSerializer.gson().serialize(component))); + static final Entry UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, + intArray -> intArrayToUuid(intArray.value()), + uuid -> IntArrayBinaryTag.intArrayBinaryTag(uuidToIntArray(uuid))); + static final Entry ITEM = new Entry<>(BinaryTagTypes.COMPOUND, ItemStack::fromItemNBT, ItemStack::toItemNBT); + static final Entry COMPONENT = new Entry<>(BinaryTagTypes.STRING, input -> GsonComponentSerializer.gson().deserialize(input.value()), + component -> StringBinaryTag.stringBinaryTag(GsonComponentSerializer.gson().serialize(component))); - static final Entry EMPTY = new Entry<>(NBTType.TAG_Byte, unused -> null, component -> null); + static final Entry EMPTY = new Entry<>(BinaryTagTypes.BYTE, unused -> null, component -> null); - static Entry fromTagSerializer(TagSerializer serializer) { - return new Serializers.Entry<>(NBTType.TAG_Compound, - (NBTCompound compound) -> { - if ((!ServerFlag.SERIALIZE_EMPTY_COMPOUND) && compound.isEmpty()) return null; + static Entry fromTagSerializer(TagSerializer serializer) { + return new Serializers.Entry<>(BinaryTagTypes.COMPOUND, + (CompoundBinaryTag compound) -> { + if ((!ServerFlag.SERIALIZE_EMPTY_COMPOUND) && compound.size() == 0) return null; return serializer.read(TagHandler.fromCompound(compound)); }, (value) -> { - if (value == null) return NBTCompound.EMPTY; + if (value == null) return CompoundBinaryTag.empty(); TagHandler handler = TagHandler.newHandler(); serializer.write(handler, value); return handler.asCompound(); }); } - record Entry(NBTType nbtType, Function reader, Function writer, boolean isPath) { - Entry(NBTType nbtType, Function reader, Function writer) { + record Entry(BinaryTagType nbtType, Function reader, Function writer, boolean isPath) { + Entry(BinaryTagType nbtType, Function reader, Function writer) { this(nbtType, reader, writer, false); } diff --git a/src/main/java/net/minestom/server/tag/Tag.java b/src/main/java/net/minestom/server/tag/Tag.java index 807cc3678..a6a448e28 100644 --- a/src/main/java/net/minestom/server/tag/Tag.java +++ b/src/main/java/net/minestom/server/tag/Tag.java @@ -1,5 +1,6 @@ package net.minestom.server.tag; +import net.kyori.adventure.nbt.*; import net.kyori.adventure.text.Component; import net.minestom.server.item.ItemStack; import net.minestom.server.utils.collection.AutoIncrementMap; @@ -7,11 +8,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike; -import org.jglrxavpok.hephaistos.nbt.NBTList; -import org.jglrxavpok.hephaistos.nbt.NBTType; -import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound; import java.util.Arrays; import java.util.List; @@ -37,7 +33,7 @@ public class Tag { final int index; private final String key; - final Serializers.Entry entry; + final Serializers.Entry entry; private final Supplier defaultValue; final Function readComparator; @@ -48,7 +44,7 @@ public class Tag { Tag(int index, String key, Function readComparator, - Serializers.Entry entry, + Serializers.Entry entry, Supplier defaultValue, PathEntry[] path, UnaryOperator copy, int listScope) { assert index == INDEX_MAP.get(key); this.index = index; @@ -61,8 +57,8 @@ public class Tag { this.listScope = listScope; } - static Tag tag(@NotNull String key, @NotNull Serializers.Entry entry) { - return new Tag<>(INDEX_MAP.get(key), key, entry.reader(), (Serializers.Entry) entry, + static Tag tag(@NotNull String key, @NotNull Serializers.Entry entry) { + return new Tag<>(INDEX_MAP.get(key), key, entry.reader(), (Serializers.Entry) entry, null, null, null, 0); } @@ -98,11 +94,11 @@ public class Tag { public Tag map(@NotNull Function readMap, @NotNull Function writeMap) { var entry = this.entry; - final Function readFunction = entry.reader().andThen(t -> { + final Function readFunction = entry.reader().andThen(t -> { if (t == null) return null; return readMap.apply(t); }); - final Function writeFunction = writeMap.andThen(entry.writer()); + final Function writeFunction = writeMap.andThen(entry.writer()); return new Tag<>(index, key, readMap, new Serializers.Entry<>(entry.nbtType(), readFunction, writeFunction), // Default value @@ -120,18 +116,18 @@ public class Tag { var entry = this.entry; var readFunction = entry.reader(); var writeFunction = entry.writer(); - var listEntry = new Serializers.Entry, NBTList>( - NBTType.TAG_List, + var listEntry = new Serializers.Entry, ListBinaryTag>( + BinaryTagTypes.LIST, read -> { - if (read.isEmpty()) return List.of(); - return read.asListView().stream().map(readFunction).toList(); + if (read.size() == 0) return List.of(); + return read.stream().map(readFunction).toList(); }, write -> { if (write.isEmpty()) - return NBT.List(NBTType.TAG_String); // String is the default type for lists - final List list = write.stream().map(writeFunction).toList(); - final NBTType type = list.get(0).getID(); - return NBT.List(type, list); + return ListBinaryTag.empty(); + final List list = write.stream().map(writeFunction).toList(); + final BinaryTagType type = list.get(0).type(); + return ListBinaryTag.listBinaryTag(type, list); }); UnaryOperator> co = this.copy != null ? ts -> { final int size = ts.size(); @@ -165,8 +161,8 @@ public class Tag { return new Tag<>(index, key, readComparator, entry, defaultValue, pathEntries, copy, listScope); } - public @Nullable T read(@NotNull NBTCompoundLike nbt) { - final NBT readable = isView() ? nbt.toCompound() : nbt.get(key); + public @Nullable T read(@NotNull CompoundBinaryTag nbt) { + final BinaryTag readable = isView() ? nbt : nbt.get(key); final T result; try { if (readable == null || (result = entry.read(readable)) == null) @@ -177,18 +173,20 @@ public class Tag { } } - public void write(@NotNull MutableNBTCompound nbtCompound, @Nullable T value) { + public void write(@NotNull CompoundBinaryTag.Builder nbtCompound, @Nullable T value) { if (value != null) { - final NBT nbt = entry.write(value); - if (isView()) nbtCompound.copyFrom((NBTCompoundLike) nbt); - else nbtCompound.set(key, nbt); + final BinaryTag nbt = entry.write(value); + if (isView()) nbtCompound.put((CompoundBinaryTag) nbt); + else nbtCompound.put(key, nbt); } else { - if (isView()) nbtCompound.clear(); - else nbtCompound.remove(key); + if (isView()) { + // Adventure compound builder doesn't currently have a clear method. + nbtCompound.build().keySet().forEach(nbtCompound::remove); + } else nbtCompound.remove(key); } } - public void writeUnsafe(@NotNull MutableNBTCompound nbtCompound, @Nullable Object value) { + public void writeUnsafe(@NotNull CompoundBinaryTag.Builder nbtCompound, @Nullable Object value) { //noinspection unchecked write(nbtCompound, (T) value); } @@ -279,11 +277,11 @@ public class Tag { } /** - * Creates a flexible tag able to read and write any {@link NBT} objects. + * Creates a flexible tag able to read and write any {@link BinaryTag} objects. *

* Specialized tags are recommended if the type is known as conversion will be required both way (read and write). */ - public static @NotNull Tag NBT(@NotNull String key) { + public static @NotNull Tag NBT(@NotNull String key) { return tag(key, Serializers.NBT_ENTRY); } diff --git a/src/main/java/net/minestom/server/tag/TagHandler.java b/src/main/java/net/minestom/server/tag/TagHandler.java index ded8d4487..241ccf4d4 100644 --- a/src/main/java/net/minestom/server/tag/TagHandler.java +++ b/src/main/java/net/minestom/server/tag/TagHandler.java @@ -1,10 +1,9 @@ package net.minestom.server.tag; +import net.kyori.adventure.nbt.CompoundBinaryTag; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike; import java.util.function.UnaryOperator; @@ -26,7 +25,7 @@ public interface TagHandler extends TagReadable, TagWritable { /** * Creates a copy of this handler. *

- * Similar to {@link #fromCompound(NBTCompoundLike)} using {@link #asCompound()} + * Similar to {@link #fromCompound(CompoundBinaryTag)} using {@link #asCompound()} * with the advantage that cached objects and adaptive optimizations may be reused. * * @return a copy of this handler @@ -36,18 +35,18 @@ public interface TagHandler extends TagReadable, TagWritable { /** * Updates the content of this handler. *

- * Can be used as a clearing method with {@link NBTCompound#EMPTY}. + * Can be used as a clearing method with {@link CompoundBinaryTag#empty()}. * * @param compound the new content of this handler */ - void updateContent(@NotNull NBTCompoundLike compound); + void updateContent(@NotNull CompoundBinaryTag compound); /** - * Converts the content of this handler into a {@link NBTCompound}. + * Converts the content of this handler into a {@link CompoundBinaryTag}. * * @return a nbt compound representation of this handler */ - @NotNull NBTCompound asCompound(); + @NotNull CompoundBinaryTag asCompound(); @ApiStatus.Experimental void updateTag(@NotNull Tag tag, @@ -67,12 +66,12 @@ public interface TagHandler extends TagReadable, TagWritable { } /** - * Copy the content of the given {@link NBTCompoundLike} into a new {@link TagHandler}. + * Copy the content of the given {@link CompoundBinaryTag} into a new {@link TagHandler}. * * @param compound the compound to read tags from * @return a new tag handler with the content of the given compound */ - static @NotNull TagHandler fromCompound(@NotNull NBTCompoundLike compound) { + static @NotNull TagHandler fromCompound(@NotNull CompoundBinaryTag compound) { return TagHandlerImpl.fromCompound(compound); } } diff --git a/src/main/java/net/minestom/server/tag/TagHandlerImpl.java b/src/main/java/net/minestom/server/tag/TagHandlerImpl.java index 6f0403182..702d16cc3 100644 --- a/src/main/java/net/minestom/server/tag/TagHandlerImpl.java +++ b/src/main/java/net/minestom/server/tag/TagHandlerImpl.java @@ -1,22 +1,21 @@ package net.minestom.server.tag; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.ServerFlag; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike; -import org.jglrxavpok.hephaistos.nbt.NBTType; -import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound; import java.lang.invoke.VarHandle; import java.util.Map; import java.util.function.UnaryOperator; final class TagHandlerImpl implements TagHandler { - static final Serializers.Entry NODE_SERIALIZER = new Serializers.Entry<>(NBTType.TAG_Compound, entries -> fromCompound(entries).root, Node::compound, true); + static final Serializers.Entry NODE_SERIALIZER = new Serializers.Entry<>(BinaryTagTypes.COMPOUND, entries -> fromCompound(entries).root, Node::compound, true); private final Node root; private volatile Node copy; @@ -29,8 +28,7 @@ final class TagHandlerImpl implements TagHandler { this.root = new Node(); } - static TagHandlerImpl fromCompound(NBTCompoundLike compoundLike) { - final NBTCompound compound = compoundLike.toCompound(); + static TagHandlerImpl fromCompound(CompoundBinaryTag compound) { TagHandlerImpl handler = new TagHandlerImpl(); TagNbtSeparator.separate(compound, entry -> handler.setTag(entry.tag(), entry.value())); handler.root.compound = compound; @@ -50,7 +48,7 @@ final class TagHandlerImpl implements TagHandler { synchronized (this) { Node syncNode = traversePathWrite(root, tag, value != null); if (syncNode != null) { - syncNode.updateContent(value != null ? (NBTCompound) tag.entry.write(value) : NBTCompound.EMPTY); + syncNode.updateContent(value != null ? (CompoundBinaryTag) tag.entry.write(value) : CompoundBinaryTag.empty()); syncNode.invalidate(); } } @@ -103,7 +101,7 @@ final class TagHandlerImpl implements TagHandler { if (tag.isView()) { final T previousValue = tag.read(node.compound()); final T newValue = value.apply(previousValue); - node.updateContent((NBTCompoundLike) tag.entry.write(newValue)); + node.updateContent((CompoundBinaryTag) tag.entry.write(newValue)); node.invalidate(); return returnPrevious ? previousValue : newValue; } @@ -116,7 +114,7 @@ final class TagHandlerImpl implements TagHandler { if (previousEntry != null) { final Object previousTmp = previousEntry.value; if (previousTmp instanceof Node n) { - final NBTCompound compound = NBT.Compound(Map.of(tag.getKey(), n.compound())); + final CompoundBinaryTag compound = CompoundBinaryTag.from(Map.of(tag.getKey(), n.compound())); previousValue = tag.read(compound); } else { previousValue = (T) previousTmp; @@ -149,12 +147,12 @@ final class TagHandlerImpl implements TagHandler { } @Override - public synchronized void updateContent(@NotNull NBTCompoundLike compound) { + public synchronized void updateContent(@NotNull CompoundBinaryTag compound) { this.root.updateContent(compound); } @Override - public @NotNull NBTCompound asCompound() { + public @NotNull CompoundBinaryTag asCompound() { VarHandle.fullFence(); return root.compound(); } @@ -200,7 +198,7 @@ final class TagHandlerImpl implements TagHandler { // Slow path is taken if the entry comes from a Structure tag, requiring conversion from NBT Node tmp = local; local = new Node(tmp); - if (synEntry != null && synEntry.updatedNbt() instanceof NBTCompound compound) { + if (synEntry != null && synEntry.updatedNbt() instanceof CompoundBinaryTag compound) { local.updateContent(compound); } tmp.entries.put(pathIndex, Entry.makePathEntry(path.name(), local)); @@ -211,8 +209,8 @@ final class TagHandlerImpl implements TagHandler { } private Entry valueToEntry(Node parent, Tag tag, @NotNull T value) { - if (value instanceof NBT nbt) { - if (nbt instanceof NBTCompound compound) { + if (value instanceof BinaryTag nbt) { + if (nbt instanceof CompoundBinaryTag compound) { final TagHandlerImpl handler = fromCompound(compound); return Entry.makePathEntry(tag, new Node(parent, handler.root.entries)); } else { @@ -227,7 +225,7 @@ final class TagHandlerImpl implements TagHandler { final class Node implements TagReadable { final Node parent; final StaticIntMap> entries; - NBTCompound compound; + CompoundBinaryTag compound; public Node(Node parent, StaticIntMap> entries) { this.parent = parent; @@ -260,44 +258,43 @@ final class TagHandlerImpl implements TagHandler { return (T) entry.value; } // Value must be parsed from nbt if the tag is different - final NBT nbt = entry.updatedNbt(); - final Serializers.Entry serializerEntry = tag.entry; - final NBTType type = serializerEntry.nbtType(); - return type == null || type == nbt.getID() ? serializerEntry.read(nbt) : tag.createDefault(); + final BinaryTag nbt = entry.updatedNbt(); + final Serializers.Entry serializerEntry = tag.entry; + final BinaryTagType type = serializerEntry.nbtType(); + return type == null || type.equals(nbt.type()) ? serializerEntry.read(nbt) : tag.createDefault(); } - void updateContent(@NotNull NBTCompoundLike compoundLike) { - final NBTCompound compound = compoundLike.toCompound(); + void updateContent(@NotNull CompoundBinaryTag compound) { final TagHandlerImpl converted = fromCompound(compound); this.entries.updateContent(converted.root.entries); this.compound = compound; } - NBTCompound compound() { - NBTCompound compound; + CompoundBinaryTag compound() { + CompoundBinaryTag compound; if (!ServerFlag.TAG_HANDLER_CACHE_ENABLED || (compound = this.compound) == null) { - MutableNBTCompound tmp = new MutableNBTCompound(); + CompoundBinaryTag.Builder tmp = CompoundBinaryTag.builder(); this.entries.forValues(entry -> { final Tag tag = entry.tag; - final NBT nbt = entry.updatedNbt(); - if (nbt != null && (!tag.entry.isPath() || (!ServerFlag.SERIALIZE_EMPTY_COMPOUND) && !((NBTCompound) nbt).isEmpty())) { + final BinaryTag nbt = entry.updatedNbt(); + if (nbt != null && (!tag.entry.isPath() || (!ServerFlag.SERIALIZE_EMPTY_COMPOUND) && ((CompoundBinaryTag) nbt).size() > 0)) { tmp.put(tag.getKey(), nbt); } }); - this.compound = compound = tmp.toCompound(); + this.compound = compound = tmp.build(); } return compound; } @Contract("null -> !null") Node copy(Node parent) { - MutableNBTCompound tmp = new MutableNBTCompound(); + CompoundBinaryTag.Builder tmp = CompoundBinaryTag.builder(); Node result = new Node(parent, new StaticIntMap.Array<>()); StaticIntMap> entries = result.entries; this.entries.forValues(entry -> { Tag tag = entry.tag; Object value = entry.value; - NBT nbt; + BinaryTag nbt; if (value instanceof Node node) { Node copy = node.copy(result); if (copy == null) @@ -313,9 +310,10 @@ final class TagHandlerImpl implements TagHandler { tmp.put(tag.getKey(), nbt); entries.put(tag.index, valueToEntry(result, tag, value)); }); - if ((!ServerFlag.SERIALIZE_EMPTY_COMPOUND) && tmp.isEmpty() && parent != null) + var compound = tmp.build(); + if ((!ServerFlag.SERIALIZE_EMPTY_COMPOUND) && compound.size() == 0 && parent != null) return null; // Empty child node - result.compound = tmp.toCompound(); + result.compound = compound; return result; } @@ -330,7 +328,7 @@ final class TagHandlerImpl implements TagHandler { private static final class Entry { private final Tag tag; T value; - NBT nbt; + BinaryTag nbt; Entry(Tag tag, T value) { this.tag = tag; @@ -345,9 +343,9 @@ final class TagHandlerImpl implements TagHandler { return makePathEntry(tag.getKey(), node); } - NBT updatedNbt() { + BinaryTag updatedNbt() { if (tag.entry.isPath()) return ((Node) value).compound(); - NBT nbt = this.nbt; + BinaryTag nbt = this.nbt; if (nbt == null) this.nbt = nbt = tag.entry.write(value); return nbt; } @@ -360,7 +358,7 @@ final class TagHandlerImpl implements TagHandler { Node toNode() { if (tag.entry.isPath()) return (Node) value; - if (updatedNbt() instanceof NBTCompound compound) { + if (updatedNbt() instanceof CompoundBinaryTag compound) { // Slow path forcing a conversion of the structure to NBTCompound // TODO should the handler be cached inside the entry? return fromCompound(compound).root; diff --git a/src/main/java/net/minestom/server/tag/TagNbtSeparator.java b/src/main/java/net/minestom/server/tag/TagNbtSeparator.java index d0300f1b6..f6e43cfd6 100644 --- a/src/main/java/net/minestom/server/tag/TagNbtSeparator.java +++ b/src/main/java/net/minestom/server/tag/TagNbtSeparator.java @@ -1,9 +1,7 @@ package net.minestom.server.tag; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTList; -import org.jglrxavpok.hephaistos.nbt.NBTType; +import net.kyori.adventure.nbt.*; +import net.minestom.server.utils.NBTUtils; import java.util.ArrayList; import java.util.List; @@ -15,30 +13,30 @@ import java.util.function.Function; import static java.util.Map.entry; /** - * Handles conversion of {@link NBT} subtypes into one or multiple primitive {@link Tag tags}. + * Handles conversion of {@link BinaryTag} subtypes into one or multiple primitive {@link Tag tags}. */ final class TagNbtSeparator { - static final Map, Function>> SUPPORTED_TYPES = Map.ofEntries( - entry(NBTType.TAG_Byte, Tag::Byte), - entry(NBTType.TAG_Short, Tag::Short), - entry(NBTType.TAG_Int, Tag::Integer), - entry(NBTType.TAG_Long, Tag::Long), - entry(NBTType.TAG_Float, Tag::Float), - entry(NBTType.TAG_Double, Tag::Double), - entry(NBTType.TAG_String, Tag::String)); + static final Map, Function>> SUPPORTED_TYPES = Map.ofEntries( + entry(BinaryTagTypes.BYTE, Tag::Byte), + entry(BinaryTagTypes.SHORT, Tag::Short), + entry(BinaryTagTypes.INT, Tag::Integer), + entry(BinaryTagTypes.LONG, Tag::Long), + entry(BinaryTagTypes.FLOAT, Tag::Float), + entry(BinaryTagTypes.DOUBLE, Tag::Double), + entry(BinaryTagTypes.STRING, Tag::String)); - static void separate(NBTCompound nbtCompound, Consumer consumer) { + static void separate(CompoundBinaryTag nbtCompound, Consumer consumer) { for (var ent : nbtCompound) { convert(new ArrayList<>(), ent.getKey(), ent.getValue(), consumer); } } - static void separate(String key, NBT nbt, Consumer consumer) { + static void separate(String key, BinaryTag nbt, Consumer consumer) { convert(new ArrayList<>(), key, nbt, consumer); } - static Entry separateSingle(String key, NBT nbt) { - assert !(nbt instanceof NBTCompound); + static Entry separateSingle(String key, BinaryTag nbt) { + assert !(nbt instanceof CompoundBinaryTag); AtomicReference> entryRef = new AtomicReference<>(); convert(new ArrayList<>(), key, nbt, entry -> { assert entryRef.getPlain() == null : "Multiple entries found for nbt tag: " + key + " -> " + nbt; @@ -49,28 +47,28 @@ final class TagNbtSeparator { return entry; } - private static void convert(List path, String key, NBT nbt, Consumer consumer) { - var tagFunction = SUPPORTED_TYPES.get(nbt.getID()); + private static void convert(List path, String key, BinaryTag nbt, Consumer consumer) { + var tagFunction = SUPPORTED_TYPES.get(nbt.type()); if (tagFunction != null) { Tag tag = tagFunction.apply(key); - consumer.accept(makeEntry(path, tag, nbt.getValue())); - } else if (nbt instanceof NBTCompound nbtCompound) { + consumer.accept(makeEntry(path, tag, NBTUtils.nbtValueFromTag(nbt))); + } else if (nbt instanceof CompoundBinaryTag nbtCompound) { for (var ent : nbtCompound) { var newPath = new ArrayList<>(path); newPath.add(key); convert(newPath, ent.getKey(), ent.getValue(), consumer); } - } else if (nbt instanceof NBTList nbtList) { - tagFunction = SUPPORTED_TYPES.get(nbtList.getSubtagType()); + } else if (nbt instanceof ListBinaryTag nbtList) { + tagFunction = SUPPORTED_TYPES.get(nbtList.elementType()); if (tagFunction == null) { // Invalid list subtype, fallback to nbt consumer.accept(makeEntry(path, Tag.NBT(key), nbt)); } else { try { var tag = tagFunction.apply(key).list(); - Object[] values = new Object[nbtList.getSize()]; + Object[] values = new Object[nbtList.size()]; for (int i = 0; i < values.length; i++) { - values[i] = nbtList.get(i).getValue(); + values[i] = NBTUtils.nbtValueFromTag(nbtList.get(i)); } consumer.accept(makeEntry(path, Tag.class.cast(tag), List.of(values))); } catch (Exception e) { diff --git a/src/main/java/net/minestom/server/tag/TagRecord.java b/src/main/java/net/minestom/server/tag/TagRecord.java index 3c15d198c..6cc3d33d7 100644 --- a/src/main/java/net/minestom/server/tag/TagRecord.java +++ b/src/main/java/net/minestom/server/tag/TagRecord.java @@ -1,11 +1,11 @@ package net.minestom.server.tag; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -44,7 +44,7 @@ final class TagRecord { final Tag tag; if (componentType.isRecord()) { tag = Tag.Structure(componentName, serializers.get(componentType)); - } else if (NBT.class.isAssignableFrom(componentType)) { + } else if (BinaryTag.class.isAssignableFrom(componentType)) { tag = Tag.NBT(componentName); } else { final var fun = SUPPORTED_TYPES.get(componentType); @@ -73,7 +73,7 @@ final class TagRecord { static final class Serializer implements TagSerializer { final Constructor constructor; final Entry[] entries; - final Serializers.Entry serializerEntry; + final Serializers.Entry serializerEntry; Serializer(Constructor constructor, Entry[] entries) { this.constructor = constructor; diff --git a/src/main/java/net/minestom/server/tag/TagSerializer.java b/src/main/java/net/minestom/server/tag/TagSerializer.java index df3138d4e..314a49c2f 100644 --- a/src/main/java/net/minestom/server/tag/TagSerializer.java +++ b/src/main/java/net/minestom/server/tag/TagSerializer.java @@ -1,9 +1,9 @@ package net.minestom.server.tag; +import net.kyori.adventure.nbt.CompoundBinaryTag; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.function.Function; @@ -31,11 +31,11 @@ public interface TagSerializer { void write(@NotNull TagWritable writer, @NotNull T value); @ApiStatus.Experimental - TagSerializer COMPOUND = TagSerializerImpl.COMPOUND; + TagSerializer COMPOUND = TagSerializerImpl.COMPOUND; @ApiStatus.Experimental - static TagSerializer fromCompound(@NotNull Function reader, - @NotNull Function writer) { + static TagSerializer fromCompound(@NotNull Function reader, + @NotNull Function writer) { return TagSerializerImpl.fromCompound(reader, writer); } } diff --git a/src/main/java/net/minestom/server/tag/TagSerializerImpl.java b/src/main/java/net/minestom/server/tag/TagSerializerImpl.java index 57f8f84be..53bc4d451 100644 --- a/src/main/java/net/minestom/server/tag/TagSerializerImpl.java +++ b/src/main/java/net/minestom/server/tag/TagSerializerImpl.java @@ -1,35 +1,35 @@ package net.minestom.server.tag; +import net.kyori.adventure.nbt.CompoundBinaryTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.function.Function; final class TagSerializerImpl { - public static final TagSerializer COMPOUND = new TagSerializer<>() { + public static final TagSerializer COMPOUND = new TagSerializer<>() { @Override - public @NotNull NBTCompound read(@NotNull TagReadable reader) { + public @NotNull CompoundBinaryTag read(@NotNull TagReadable reader) { return ((TagHandler) reader).asCompound(); } @Override - public void write(@NotNull TagWritable writer, @NotNull NBTCompound value) { + public void write(@NotNull TagWritable writer, @NotNull CompoundBinaryTag value) { TagNbtSeparator.separate(value, entry -> writer.setTag(entry.tag(), entry.value())); } }; - static TagSerializer fromCompound(Function readFunc, Function writeFunc) { + static TagSerializer fromCompound(Function readFunc, Function writeFunc) { return new TagSerializer<>() { @Override public @Nullable T read(@NotNull TagReadable reader) { - final NBTCompound compound = COMPOUND.read(reader); + final CompoundBinaryTag compound = COMPOUND.read(reader); return readFunc.apply(compound); } @Override public void write(@NotNull TagWritable writer, @NotNull T value) { - final NBTCompound compound = writeFunc.apply(value); + final CompoundBinaryTag compound = writeFunc.apply(value); COMPOUND.write(writer, compound); } }; diff --git a/src/main/java/net/minestom/server/utils/NBTUtils.java b/src/main/java/net/minestom/server/utils/NBTUtils.java new file mode 100644 index 000000000..fd07f7dc7 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/NBTUtils.java @@ -0,0 +1,59 @@ +package net.minestom.server.utils; + +import net.kyori.adventure.nbt.*; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class NBTUtils { + private static final BinaryTagType[] TYPES = new BinaryTagType[]{ + BinaryTagTypes.END, + BinaryTagTypes.BYTE, + BinaryTagTypes.SHORT, + BinaryTagTypes.INT, + BinaryTagTypes.LONG, + BinaryTagTypes.FLOAT, + BinaryTagTypes.DOUBLE, + BinaryTagTypes.BYTE_ARRAY, + BinaryTagTypes.STRING, + BinaryTagTypes.LIST, + BinaryTagTypes.COMPOUND, + BinaryTagTypes.INT_ARRAY, + BinaryTagTypes.LONG_ARRAY, + }; + + public static @NotNull BinaryTagType nbtTypeFromId(byte id) { + Check.argCondition(id < 0 || id >= TYPES.length, "Invalid NBT type id: " + id); + return TYPES[id]; + } + + public static @NotNull Object nbtValueFromTag(@NotNull BinaryTag tag) { + if (tag instanceof ByteBinaryTag byteTag) { + return byteTag.value(); + } else if (tag instanceof ShortBinaryTag shortTag) { + return shortTag.value(); + } else if (tag instanceof IntBinaryTag intTag) { + return intTag.value(); + } else if (tag instanceof LongBinaryTag longTag) { + return longTag.value(); + } else if (tag instanceof FloatBinaryTag floatTag) { + return floatTag.value(); + } else if (tag instanceof DoubleBinaryTag doubleTag) { + return doubleTag.value(); + } else if (tag instanceof ByteArrayBinaryTag byteArrayTag) { + return byteArrayTag.value(); + } else if (tag instanceof StringBinaryTag stringTag) { + return stringTag.value(); + } else if (tag instanceof IntArrayBinaryTag intArrayTag) { + return intArrayTag.value(); + } else if (tag instanceof LongArrayBinaryTag longArrayTag) { + return longArrayTag.value(); + } else { + throw new UnsupportedOperationException("Unsupported NBT type: " + tag.getClass()); + } + } + + private NBTUtils() { + } +} diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryBuffer.java b/src/main/java/net/minestom/server/utils/binary/BinaryBuffer.java index c4f607628..ef3e98355 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryBuffer.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryBuffer.java @@ -2,8 +2,6 @@ package net.minestom.server.utils.binary; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTReader; -import org.jglrxavpok.hephaistos.nbt.NBTWriter; import java.io.IOException; import java.nio.ByteBuffer; @@ -17,8 +15,6 @@ import java.nio.channels.WritableByteChannel; @ApiStatus.Internal public final class BinaryBuffer { private ByteBuffer nioBuffer; // To become a `MemorySegment` once released - private NBTReader nbtReader; - private NBTWriter nbtWriter; private final int capacity; private int readerOffset, writerOffset; diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java index dbcaef4cb..5fbcb3b6d 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java @@ -1,5 +1,6 @@ package net.minestom.server.utils.binary; +import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.coordinate.Point; @@ -8,7 +9,6 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.Either; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; import java.io.InputStream; import java.nio.BufferUnderflowException; @@ -263,7 +263,7 @@ public class BinaryReader extends InputStream { return buffer.readableBytes(); } - public NBT readTag() { + public BinaryTag readTag() { return buffer.read(NBT); } diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java index c6ab077ae..7a0eb0116 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java @@ -1,5 +1,6 @@ package net.minestom.server.utils.binary; +import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; @@ -8,7 +9,6 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.Either; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; import java.io.OutputStream; import java.nio.ByteBuffer; @@ -167,7 +167,7 @@ public class BinaryWriter extends OutputStream { this.buffer.write(ITEM, itemStack); } - public void writeNBT(@NotNull String name, @NotNull NBT tag) { + public void writeNBT(@NotNull String name, @NotNull BinaryTag tag) { this.buffer.write(NBT, tag); } diff --git a/src/main/java/net/minestom/server/utils/block/BlockUtils.java b/src/main/java/net/minestom/server/utils/block/BlockUtils.java index dd6684925..36034d1e3 100644 --- a/src/main/java/net/minestom/server/utils/block/BlockUtils.java +++ b/src/main/java/net/minestom/server/utils/block/BlockUtils.java @@ -1,16 +1,14 @@ package net.minestom.server.utils.block; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.tag.Tag; import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Map; import java.util.Objects; @@ -90,22 +88,22 @@ public class BlockUtils { return new Object2ObjectArrayMap<>(keys, values, entryCount); } - public static @Nullable NBTCompound extractClientNbt(@NotNull Block block) { + public static @Nullable CompoundBinaryTag extractClientNbt(@NotNull Block block) { if (!block.registry().isBlockEntity()) return null; // Append handler tags final BlockHandler handler = block.handler(); - final NBTCompound blockNbt = Objects.requireNonNullElseGet(block.nbt(), NBTCompound::new); + final CompoundBinaryTag blockNbt = Objects.requireNonNullElseGet(block.nbt(), CompoundBinaryTag::empty); if (handler != null) { // Extract explicitly defined tags and keep the rest server-side - return NBT.Compound(nbt -> { - for (Tag tag : handler.getBlockEntityTags()) { - final var value = tag.read(blockNbt); - if (value != null) { - // Tag is present and valid - tag.writeUnsafe(nbt, value); - } + var builder = CompoundBinaryTag.builder(); + for (Tag tag : handler.getBlockEntityTags()) { + final var value = tag.read(blockNbt); + if (value != null) { + // Tag is present and valid + tag.writeUnsafe(builder, value); } - }); + } + return builder.build(); } // Complete nbt shall be sent if the block has no handler // Necessary to support all vanilla blocks diff --git a/src/main/java/net/minestom/server/world/DimensionType.java b/src/main/java/net/minestom/server/world/DimensionType.java index 46cf39507..c48aed029 100644 --- a/src/main/java/net/minestom/server/world/DimensionType.java +++ b/src/main/java/net/minestom/server/world/DimensionType.java @@ -1,13 +1,10 @@ package net.minestom.server.world; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.mcdata.SizesKt; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -18,6 +15,9 @@ public class DimensionType { private static final AtomicInteger idCounter = new AtomicInteger(0); + private static final int VANILLA_MIN_Y = -64; + private static final int VANILLA_MAX_Y = 319; + public static final DimensionType OVERWORLD = DimensionType.builder(NamespaceID.from("minecraft:overworld")) .ultrawarm(false) .natural(true) @@ -91,55 +91,56 @@ public class DimensionType { return new DimensionTypeBuilder(); } - public static DimensionType fromNBT(NBTCompound nbt) { + public static DimensionType fromNBT(CompoundBinaryTag nbt) { return DimensionType.builder(NamespaceID.from(nbt.getString("name"))) .ambientLight(nbt.getFloat("ambient_light")) .infiniburn(NamespaceID.from(nbt.getString("infiniburn").replaceFirst("#", ""))) - .natural(nbt.getByte("natural") != 0) - .ceilingEnabled(nbt.getByte("has_ceiling") != 0) - .skylightEnabled(nbt.getByte("has_skylight") != 0) - .ultrawarm(nbt.getByte("ultrawarm") != 0) - .raidCapable(nbt.getByte("has_raids") != 0) - .respawnAnchorSafe(nbt.getByte("respawn_anchor_works") != 0) - .bedSafe(nbt.getByte("bed_works") != 0) + .natural(nbt.getBoolean("natural")) + .ceilingEnabled(nbt.getBoolean("has_ceiling")) + .skylightEnabled(nbt.getBoolean("has_skylight")) + .ultrawarm(nbt.getBoolean("ultrawarm")) + .raidCapable(nbt.getBoolean("has_raids")) + .respawnAnchorSafe(nbt.getBoolean("respawn_anchor_works")) + .bedSafe(nbt.getBoolean("bed_works")) .effects(nbt.getString("effects")) - .piglinSafe(nbt.getByte("piglin_safe") != 0) + .piglinSafe(nbt.getBoolean("piglin_safe")) .logicalHeight(nbt.getInt("logical_height")) .coordinateScale(nbt.getDouble("coordinate_scale")) .build(); } @NotNull - public NBTCompound toIndexedNBT() { - return NBT.Compound(Map.of( - "name", NBT.String(name.toString()), - "id", NBT.Int(id), - "element", toNBT())); + public CompoundBinaryTag toIndexedNBT() { + return CompoundBinaryTag.builder() + .putString("name", name.toString()) + .putInt("id", id) + .put("element", toNBT()) + .build(); } @NotNull - public NBTCompound toNBT() { - return NBT.Compound(nbt -> { - nbt.setFloat("ambient_light", ambientLight); - nbt.setString("infiniburn", "#" + infiniburn.toString()); - nbt.setByte("natural", (byte) (natural ? 0x01 : 0x00)); - nbt.setByte("has_ceiling", (byte) (ceilingEnabled ? 0x01 : 0x00)); - nbt.setByte("has_skylight", (byte) (skylightEnabled ? 0x01 : 0x00)); - nbt.setByte("ultrawarm", (byte) (ultrawarm ? 0x01 : 0x00)); - nbt.setByte("has_raids", (byte) (raidCapable ? 0x01 : 0x00)); - nbt.setByte("respawn_anchor_works", (byte) (respawnAnchorSafe ? 0x01 : 0x00)); - nbt.setByte("bed_works", (byte) (bedSafe ? 0x01 : 0x00)); - nbt.setString("effects", effects); - nbt.setByte("piglin_safe", (byte) (piglinSafe ? 0x01 : 0x00)); - nbt.setInt("min_y", minY); - nbt.setInt("height", height); - nbt.setInt("logical_height", logicalHeight); - nbt.setDouble("coordinate_scale", coordinateScale); - nbt.setString("name", name.toString()); - nbt.setInt("monster_spawn_block_light_limit", 0); - nbt.setInt("monster_spawn_light_level", 11); - if (fixedTime != null) nbt.setLong("fixed_time", fixedTime); - }); + public CompoundBinaryTag toNBT() { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + builder.putFloat("ambient_light", ambientLight); + builder.putString("infiniburn", "#" + infiniburn.toString()); + builder.putByte("natural", (byte) (natural ? 0x01 : 0x00)); + builder.putByte("has_ceiling", (byte) (ceilingEnabled ? 0x01 : 0x00)); + builder.putByte("has_skylight", (byte) (skylightEnabled ? 0x01 : 0x00)); + builder.putByte("ultrawarm", (byte) (ultrawarm ? 0x01 : 0x00)); + builder.putByte("has_raids", (byte) (raidCapable ? 0x01 : 0x00)); + builder.putByte("respawn_anchor_works", (byte) (respawnAnchorSafe ? 0x01 : 0x00)); + builder.putByte("bed_works", (byte) (bedSafe ? 0x01 : 0x00)); + builder.putString("effects", effects); + builder.putByte("piglin_safe", (byte) (piglinSafe ? 0x01 : 0x00)); + builder.putInt("min_y", minY); + builder.putInt("height", height); + builder.putInt("logical_height", logicalHeight); + builder.putDouble("coordinate_scale", coordinateScale); + builder.putString("name", name.toString()); + builder.putInt("monster_spawn_block_light_limit", 0); + builder.putInt("monster_spawn_light_level", 11); + if (fixedTime != null) builder.putLong("fixed_time", fixedTime); + return builder.build(); } @Override @@ -261,9 +262,9 @@ public class DimensionType { private boolean bedSafe = true; private String effects = "minecraft:overworld"; private boolean piglinSafe = false; - private int minY = SizesKt.getVanillaMinY(); - private int logicalHeight = SizesKt.getVanillaMaxY() - SizesKt.getVanillaMinY() + 1; - private int height = SizesKt.getVanillaMaxY() - SizesKt.getVanillaMinY() + 1; + private int minY = VANILLA_MIN_Y; + private int logicalHeight = VANILLA_MAX_Y - VANILLA_MIN_Y + 1; + private int height = VANILLA_MAX_Y - VANILLA_MIN_Y + 1; private double coordinateScale = 1.0; private NamespaceID infiniburn = NamespaceID.from("minecraft:infiniburn_overworld"); diff --git a/src/main/java/net/minestom/server/world/DimensionTypeManager.java b/src/main/java/net/minestom/server/world/DimensionTypeManager.java index 0c38fac88..4627851f3 100644 --- a/src/main/java/net/minestom/server/world/DimensionTypeManager.java +++ b/src/main/java/net/minestom/server/world/DimensionTypeManager.java @@ -1,11 +1,11 @@ package net.minestom.server.world; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTType; import java.util.Collections; import java.util.List; @@ -81,21 +81,19 @@ public final class DimensionTypeManager { } /** - * Creates the {@link NBTCompound} containing all the registered dimensions. + * Creates the {@link CompoundBinaryTag} containing all the registered dimensions. *

* Used when a player connects. * * @return an nbt compound containing the registered dimensions */ - public @NotNull NBTCompound toNBT() { - return NBT.Compound(dimensions -> { - dimensions.setString("type", "minecraft:dimension_type"); - dimensions.set("value", NBT.List( - NBTType.TAG_Compound, - dimensionTypes.stream() - .map(DimensionType::toIndexedNBT) - .toList() - )); - }); + public @NotNull CompoundBinaryTag toNBT() { + ListBinaryTag.Builder entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); + for (DimensionType dimensionType : dimensionTypes) + entries.add(dimensionType.toIndexedNBT()); + return CompoundBinaryTag.builder() + .putString("type", "minecraft:dimension_type") + .put("value", entries.build()) + .build(); } } diff --git a/src/main/java/net/minestom/server/world/biomes/Biome.java b/src/main/java/net/minestom/server/world/biomes/Biome.java index bc9719a4c..0372d6e03 100644 --- a/src/main/java/net/minestom/server/world/biomes/Biome.java +++ b/src/main/java/net/minestom/server/world/biomes/Biome.java @@ -1,5 +1,6 @@ package net.minestom.server.world.biomes; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.coordinate.Point; import net.minestom.server.registry.ProtocolObject; import net.minestom.server.registry.Registry; @@ -8,8 +9,6 @@ import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Locale; @@ -54,18 +53,20 @@ sealed public interface Biome extends ProtocolObject permits BiomeImpl { } } - default @NotNull NBTCompound toNbt() { + default @NotNull CompoundBinaryTag toNbt() { Check.notNull(name(), "The biome namespace cannot be null"); Check.notNull(effects(), "The biome effects cannot be null"); - return NBT.Compound(element -> { - element.setFloat("temperature", temperature()); - element.setFloat("downfall", downfall()); - element.setByte("has_precipitation", (byte) (precipitation() == Precipitation.NONE ? 0 : 1)); - if (temperatureModifier() != TemperatureModifier.NONE) - element.setString("temperature_modifier", temperatureModifier().name().toLowerCase(Locale.ROOT)); - element.set("effects", effects().toNbt()); - }); + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder() + .putFloat("temperature", temperature()) + .putFloat("downfall", downfall()) + .putByte("has_precipitation", (byte) (precipitation() == Precipitation.NONE ? 0 : 1)) + .putString("precipitation", precipitation().name().toLowerCase(Locale.ROOT)); + if (temperatureModifier() != TemperatureModifier.NONE) + builder.putString("temperature_modifier", temperatureModifier().name().toLowerCase(Locale.ROOT)); + return builder + .put("effects", effects().toNbt()) + .build(); } static @NotNull Builder builder() { diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeEffects.java b/src/main/java/net/minestom/server/world/biomes/BiomeEffects.java index 56fb36eae..cac043914 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeEffects.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeEffects.java @@ -1,12 +1,10 @@ package net.minestom.server.world.biomes; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Locale; -import java.util.Map; public record BiomeEffects(int fogColor, int skyColor, int waterColor, int waterFogColor, int foliageColor, int grassColor, @@ -18,29 +16,29 @@ public record BiomeEffects(int fogColor, int skyColor, int waterColor, int water return new Builder(); } - public NBTCompound toNbt() { - return NBT.Compound(nbt -> { - nbt.setInt("fog_color", fogColor); - if (foliageColor != -1) - nbt.setInt("foliage_color", foliageColor); - if (grassColor != -1) - nbt.setInt("grass_color", grassColor); - nbt.setInt("sky_color", skyColor); - nbt.setInt("water_color", waterColor); - nbt.setInt("water_fog_color", waterFogColor); - if (grassColorModifier != null) - nbt.setString("grass_color_modifier", grassColorModifier.name().toLowerCase(Locale.ROOT)); - if (biomeParticle != null) - nbt.set("particle", biomeParticle.toNbt()); - if (ambientSound != null) - nbt.setString("ambient_sound", ambientSound.toString()); - if (moodSound != null) - nbt.set("mood_sound", moodSound.toNbt()); - if (additionsSound != null) - nbt.set("additions_sound", additionsSound.toNbt()); - if (music != null) - nbt.set("music", music.toNbt()); - }); + public CompoundBinaryTag toNbt() { + var builder = CompoundBinaryTag.builder(); + builder.putInt("fog_color", fogColor); + if (foliageColor != -1) + builder.putInt("foliage_color", foliageColor); + if (grassColor != -1) + builder.putInt("grass_color", grassColor); + builder.putInt("sky_color", skyColor); + builder.putInt("water_color", waterColor); + builder.putInt("water_fog_color", waterFogColor); + if (grassColorModifier != null) + builder.putString("grass_color_modifier", grassColorModifier.name().toLowerCase(Locale.ROOT)); + if (biomeParticle != null) + builder.put("particle", biomeParticle.toNbt()); + if (ambientSound != null) + builder.putString("ambient_sound", ambientSound.toString()); + if (moodSound != null) + builder.put("mood_sound", moodSound.toNbt()); + if (additionsSound != null) + builder.put("additions_sound", additionsSound.toNbt()); + if (music != null) + builder.put("music", music.toNbt()); + return builder.build(); } public enum GrassColorModifier { @@ -48,30 +46,33 @@ public record BiomeEffects(int fogColor, int skyColor, int waterColor, int water } public record MoodSound(NamespaceID sound, int tickDelay, int blockSearchExtent, double offset) { - public @NotNull NBTCompound toNbt() { - return NBT.Compound(Map.of( - "sound", NBT.String(sound.toString()), - "tick_delay", NBT.Int(tickDelay), - "block_search_extent", NBT.Int(blockSearchExtent), - "offset", NBT.Double(offset))); + public @NotNull CompoundBinaryTag toNbt() { + return CompoundBinaryTag.builder() + .putString("sound", sound.toString()) + .putInt("tick_delay", tickDelay) + .putInt("block_search_extent", blockSearchExtent) + .putDouble("offset", offset) + .build(); } } public record AdditionsSound(NamespaceID sound, double tickChance) { - public @NotNull NBTCompound toNbt() { - return NBT.Compound(Map.of( - "sound", NBT.String(sound.toString()), - "tick_chance", NBT.Double(tickChance))); + public @NotNull CompoundBinaryTag toNbt() { + return CompoundBinaryTag.builder() + .putString("sound", sound.toString()) + .putDouble("tick_chance", tickChance) + .build(); } } public record Music(NamespaceID sound, int minDelay, int maxDelay, boolean replaceCurrentMusic) { - public @NotNull NBTCompound toNbt() { - return NBT.Compound(Map.of( - "sound", NBT.String(sound.toString()), - "min_delay", NBT.Int(minDelay), - "max_delay", NBT.Int(maxDelay), - "replace_current_music", NBT.Boolean(replaceCurrentMusic))); + public @NotNull CompoundBinaryTag toNbt() { + return CompoundBinaryTag.builder() + .putString("sound", sound.toString()) + .putInt("min_delay", minDelay) + .putInt("max_delay", maxDelay) + .putBoolean("replace_current_music", replaceCurrentMusic) + .build(); } } diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java index 18dfdbe28..9569ca9f6 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java @@ -1,12 +1,12 @@ package net.minestom.server.world.biomes; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTType; import java.util.Collection; import java.util.Collections; @@ -25,7 +25,7 @@ public final class BiomeManager { private final Map idMappings = new ConcurrentHashMap<>(); private final AtomicInteger ID_COUNTER = new AtomicInteger(0); - private NBTCompound nbtCache = null; + private CompoundBinaryTag nbtCache = null; public BiomeManager() { // Need to register plains for the client to work properly @@ -101,18 +101,21 @@ public final class BiomeManager { return getByName(namespace); } - public @NotNull NBTCompound toNBT() { + public @NotNull CompoundBinaryTag toNBT() { if (nbtCache != null) return nbtCache; - nbtCache = NBT.Compound(Map.of( - "type", NBT.String("minecraft:worldgen/biome"), - "value", NBT.List(NBTType.TAG_Compound, biomes.values().stream().map(biome -> { - return NBT.Compound(Map.of( - "id", NBT.Int(getId(biome)), - "name", NBT.String(biome.namespace().toString()), - "element", biome.toNbt() - )); - }).toList()))); + ListBinaryTag.Builder entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); + for (Biome biome : biomes.values()) { + entries.add(CompoundBinaryTag.builder() + .putInt("id", getId(biome)) + .putString("name", biome.namespace().toString()) + .put("element", biome.toNbt()) + .build()); + } + nbtCache = CompoundBinaryTag.builder() + .putString("type", "minecraft:worldgen/biome") + .put("value", entries.build()) + .build(); return nbtCache; } diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java b/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java index f2f3edeb3..42fdd44dc 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java @@ -1,22 +1,22 @@ package net.minestom.server.world.biomes; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.instance.block.Block; import net.minestom.server.item.ItemStack; import net.minestom.server.utils.NamespaceID; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.Map; public record BiomeParticle(float probability, Option option) { - public NBTCompound toNbt() { - return NBT.Compound(Map.of( - "probability", NBT.Float(probability), - "options", option.toNbt())); + public CompoundBinaryTag toNbt() { + return CompoundBinaryTag.builder() + .putFloat("probability", probability) + .put("options", option.toNbt()) + .build(); } public interface Option { - NBTCompound toNbt(); + CompoundBinaryTag toNbt(); } public record BlockOption(Block block) implements Option { @@ -24,15 +24,17 @@ public record BiomeParticle(float probability, Option option) { private static final String type = "block"; @Override - public NBTCompound toNbt() { - return NBT.Compound(nbtCompound -> { - nbtCompound.setString("type", type); - nbtCompound.setString("Name", block.name()); - Map propertiesMap = block.properties(); - if (propertiesMap.size() != 0) { - nbtCompound.set("Properties", NBT.Compound(p -> propertiesMap.forEach(p::setString))); - } - }); + public CompoundBinaryTag toNbt() { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + builder.putString("type", type); + builder.putString("Name", block.name()); + Map propertiesMap = block.properties(); + if (!propertiesMap.isEmpty()) { + CompoundBinaryTag.Builder properties = CompoundBinaryTag.builder(); + propertiesMap.forEach(properties::putString); + builder.put("Properties", properties.build()); + } + return builder.build(); } } @@ -40,13 +42,14 @@ public record BiomeParticle(float probability, Option option) { private static final String type = "dust"; @Override - public NBTCompound toNbt() { - return NBT.Compound(Map.of( - "type", NBT.String(type), - "r", NBT.Float(red), - "g", NBT.Float(green), - "b", NBT.Float(blue), - "scale", NBT.Float(scale))); + public CompoundBinaryTag toNbt() { + return CompoundBinaryTag.builder() + .putString("type", type) + .putFloat("r", red) + .putFloat("g", green) + .putFloat("b", blue) + .putFloat("scale", scale) + .build(); } } @@ -54,17 +57,19 @@ public record BiomeParticle(float probability, Option option) { private static final String type = "item"; @Override - public NBTCompound toNbt() { + public CompoundBinaryTag toNbt() { //todo test count might be wrong type - NBTCompound nbtCompound = item.meta().toNBT(); - return nbtCompound.modify(n -> n.setString("type", type)); + return item.meta().toNBT() + .putString("type", type); } } public record NormalOption(NamespaceID type) implements Option { @Override - public NBTCompound toNbt() { - return NBT.Compound(Map.of("type", NBT.String(type.toString()))); + public CompoundBinaryTag toNbt() { + return CompoundBinaryTag.builder() + .putString("type", type.toString()) + .build(); } } } From 62cb99a524ef903a64d737ac396c55d6b1cad787 Mon Sep 17 00:00:00 2001 From: mworzala Date: Wed, 10 Apr 2024 08:34:12 -0400 Subject: [PATCH 15/46] chore: basic nbt reader/writer for protocol while waiting for adventure --- .../server/network/NetworkBuffer.java | 8 +-- .../server/network/NetworkBufferTypeImpl.java | 55 ++++++++----------- .../minestom/server/tag/TagNbtSeparator.java | 6 +- .../server/utils/nbt/BinaryTagReader.java | 37 +++++++++++++ .../{NBTUtils.java => nbt/BinaryTagUtil.java} | 6 +- .../server/utils/nbt/BinaryTagWriter.java | 40 ++++++++++++++ 6 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java rename src/main/java/net/minestom/server/utils/{NBTUtils.java => nbt/BinaryTagUtil.java} (95%) create mode 100644 src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index f4a168f62..7f1b9a267 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -12,13 +12,13 @@ import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.data.WorldPos; import net.minestom.server.particle.Particle; import net.minestom.server.utils.Direction; +import net.minestom.server.utils.nbt.BinaryTagReader; +import net.minestom.server.utils.nbt.BinaryTagWriter; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.DataInput; -import java.io.DataOutput; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.*; @@ -80,8 +80,8 @@ public final class NetworkBuffer { int writeIndex; int readIndex; - DataOutput nbtWriter; - DataInput nbtReader; + BinaryTagWriter nbtWriter; + BinaryTagReader nbtReader; public NetworkBuffer(@NotNull ByteBuffer buffer, boolean resizable) { this.nioBuffer = buffer.order(ByteOrder.BIG_ENDIAN); diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java index fe6b4068f..3a2e2e4d0 100644 --- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java +++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java @@ -1,5 +1,8 @@ package net.minestom.server.network; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; @@ -10,13 +13,12 @@ import net.minestom.server.item.Material; import net.minestom.server.network.packet.server.play.data.WorldPos; import net.minestom.server.particle.Particle; import net.minestom.server.particle.data.ParticleData; +import net.minestom.server.utils.nbt.BinaryTagReader; +import net.minestom.server.utils.nbt.BinaryTagWriter; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.*; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -283,37 +285,31 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { } } - record NbtType() implements NetworkBufferTypeImpl { + record NbtType() implements NetworkBufferTypeImpl { @Override - public void write(@NotNull NetworkBuffer buffer, org.jglrxavpok.hephaistos.nbt.NBT value) { - NBTWriter nbtWriter = buffer.nbtWriter; + public void write(@NotNull NetworkBuffer buffer, BinaryTag value) { + BinaryTagWriter nbtWriter = buffer.nbtWriter; if (nbtWriter == null) { - nbtWriter = new NBTWriter(new OutputStream() { + nbtWriter = new BinaryTagWriter(new DataOutputStream(new OutputStream() { @Override public void write(int b) { buffer.write(BYTE, (byte) b); } - }, CompressedProcesser.NONE); + })); buffer.nbtWriter = nbtWriter; } try { - if (value == NBTEnd.INSTANCE) { - // Kotlin - https://discord.com/channels/706185253441634317/706186227493109860/1163703658341478462 - buffer.write(BYTE, (byte) NBTType.TAG_End.getOrdinal()); - } else { - buffer.write(BYTE, (byte) value.getID().getOrdinal()); - nbtWriter.writeRaw(value); - } + nbtWriter.writeNameless(value); } catch (IOException e) { throw new RuntimeException(e); } } @Override - public org.jglrxavpok.hephaistos.nbt.NBT read(@NotNull NetworkBuffer buffer) { - NBTReader nbtReader = buffer.nbtReader; + public BinaryTag read(@NotNull NetworkBuffer buffer) { + BinaryTagReader nbtReader = buffer.nbtReader; if (nbtReader == null) { - nbtReader = new NBTReader(new InputStream() { + nbtReader = new BinaryTagReader(new DataInputStream(new InputStream() { @Override public int read() { return buffer.read(BYTE) & 0xFF; @@ -323,15 +319,12 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { public int available() { return buffer.readableBytes(); } - }, CompressedProcesser.NONE); + })); buffer.nbtReader = nbtReader; } try { - byte tagId = buffer.read(BYTE); - if (tagId == NBTType.TAG_End.getOrdinal()) - return NBTEnd.INSTANCE; - return nbtReader.readRaw(tagId); - } catch (IOException | NBTException e) { + return nbtReader.readNameless(); + } catch (IOException e) { throw new RuntimeException(e); } } @@ -362,13 +355,13 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { record ComponentType() implements NetworkBufferTypeImpl { @Override public void write(@NotNull NetworkBuffer buffer, Component value) { - final NBT nbt = NbtComponentSerializer.nbt().serialize(value); + final BinaryTag nbt = NbtComponentSerializer.nbt().serialize(value); buffer.write(NBT, nbt); } @Override public Component read(@NotNull NetworkBuffer buffer) { - final NBT nbt = buffer.read(NBT); + final BinaryTag nbt = buffer.read(NBT); return NbtComponentSerializer.nbt().deserialize(nbt); } } @@ -413,8 +406,8 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { buffer.write(VAR_INT, value.material().id()); buffer.write(BYTE, (byte) value.amount()); // Vanilla does not write an empty object, just an end tag. - NBTCompound nbt = value.meta().toNBT(); - buffer.write(NBT, nbt.isEmpty() ? NBTEnd.INSTANCE : nbt); + CompoundBinaryTag nbt = value.meta().toNBT(); + buffer.write(NBT, nbt.size() == 0 ? EndBinaryTag.endBinaryTag() : nbt); } @Override @@ -427,8 +420,8 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { if (material == null) throw new RuntimeException("Unknown material id: " + id); final int amount = buffer.read(BYTE); - final NBT nbt = buffer.read(NBT); - if (!(nbt instanceof NBTCompound compound)) { + final BinaryTag nbt = buffer.read(NBT); + if (!(nbt instanceof CompoundBinaryTag compound)) { return ItemStack.of(material, amount); } return ItemStack.fromNBT(material, compound, amount); diff --git a/src/main/java/net/minestom/server/tag/TagNbtSeparator.java b/src/main/java/net/minestom/server/tag/TagNbtSeparator.java index f6e43cfd6..83ea125f1 100644 --- a/src/main/java/net/minestom/server/tag/TagNbtSeparator.java +++ b/src/main/java/net/minestom/server/tag/TagNbtSeparator.java @@ -1,7 +1,7 @@ package net.minestom.server.tag; import net.kyori.adventure.nbt.*; -import net.minestom.server.utils.NBTUtils; +import net.minestom.server.utils.nbt.BinaryTagUtil; import java.util.ArrayList; import java.util.List; @@ -51,7 +51,7 @@ final class TagNbtSeparator { var tagFunction = SUPPORTED_TYPES.get(nbt.type()); if (tagFunction != null) { Tag tag = tagFunction.apply(key); - consumer.accept(makeEntry(path, tag, NBTUtils.nbtValueFromTag(nbt))); + consumer.accept(makeEntry(path, tag, BinaryTagUtil.nbtValueFromTag(nbt))); } else if (nbt instanceof CompoundBinaryTag nbtCompound) { for (var ent : nbtCompound) { var newPath = new ArrayList<>(path); @@ -68,7 +68,7 @@ final class TagNbtSeparator { var tag = tagFunction.apply(key).list(); Object[] values = new Object[nbtList.size()]; for (int i = 0; i < values.length; i++) { - values[i] = NBTUtils.nbtValueFromTag(nbtList.get(i)); + values[i] = BinaryTagUtil.nbtValueFromTag(nbtList.get(i)); } consumer.accept(makeEntry(path, Tag.class.cast(tag), List.of(values))); } catch (Exception e) { diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java new file mode 100644 index 000000000..8a68021ad --- /dev/null +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java @@ -0,0 +1,37 @@ +package net.minestom.server.utils.nbt; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.BinaryTagTypes; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInput; +import java.io.IOException; +import java.util.Map; + +// Based on net.kyori.adventure.nbt.BinaryTagReaderImpl licensed under the MIT license. +// https://github.com/KyoriPowered/adventure/blob/main/4/nbt/src/main/java/net/kyori/adventure/nbt/BinaryTagReaderImpl.java +public class BinaryTagReader { + + static { + BinaryTagTypes.COMPOUND.id(); // Force initialization + } + + private final DataInput input; + + public BinaryTagReader(@NotNull DataInput input) { + this.input = input; + } + + public @NotNull BinaryTag readNameless() throws IOException { + BinaryTagType type = BinaryTagUtil.nbtTypeFromId(input.readByte()); + return type.read(input); + } + + public @NotNull Map.Entry readNamed() throws IOException { + BinaryTagType type = BinaryTagUtil.nbtTypeFromId(input.readByte()); + String name = input.readUTF(); + return Map.entry(name, type.read(input)); + } + +} diff --git a/src/main/java/net/minestom/server/utils/NBTUtils.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java similarity index 95% rename from src/main/java/net/minestom/server/utils/NBTUtils.java rename to src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java index fd07f7dc7..d61c1074f 100644 --- a/src/main/java/net/minestom/server/utils/NBTUtils.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java @@ -1,4 +1,4 @@ -package net.minestom.server.utils; +package net.minestom.server.utils.nbt; import net.kyori.adventure.nbt.*; import net.minestom.server.utils.validate.Check; @@ -6,7 +6,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @ApiStatus.Internal -public final class NBTUtils { +public final class BinaryTagUtil { private static final BinaryTagType[] TYPES = new BinaryTagType[]{ BinaryTagTypes.END, BinaryTagTypes.BYTE, @@ -54,6 +54,6 @@ public final class NBTUtils { } } - private NBTUtils() { + private BinaryTagUtil() { } } diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java new file mode 100644 index 000000000..7690d4de0 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java @@ -0,0 +1,40 @@ +package net.minestom.server.utils.nbt; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.BinaryTagTypes; +import org.jetbrains.annotations.NotNull; + +import java.io.DataOutput; +import java.io.IOException; + +// Based on net.kyori.adventure.nbt.BinaryTagWriterImpl licensed under the MIT license. +// https://github.com/KyoriPowered/adventure/blob/main/4/nbt/src/main/java/net/kyori/adventure/nbt/BinaryTagWriterImpl.java +public class BinaryTagWriter { + + static { + BinaryTagTypes.COMPOUND.id(); // Force initialization + } + + private final DataOutput output; + + public BinaryTagWriter(@NotNull DataOutput output) { + this.output = output; + } + + public void writeNameless(@NotNull BinaryTag tag) throws IOException { + //noinspection unchecked + BinaryTagType type = (BinaryTagType) tag.type(); + output.writeByte(type.id()); + type.write(tag, output); + } + + public void readNamed(@NotNull String name, @NotNull BinaryTag tag) throws IOException { + //noinspection unchecked + BinaryTagType type = (BinaryTagType) tag.type(); + output.writeByte(type.id()); + output.writeUTF(name); + type.write(tag, output); + } + +} From 721f70a28e64d3c147990f1049d266f5dea3856c Mon Sep 17 00:00:00 2001 From: mworzala Date: Wed, 10 Apr 2024 00:34:30 -0400 Subject: [PATCH 16/46] feat: joinable server --- build.gradle.kts | 1 + .../java/net/minestom/codegen/Generators.java | 9 +- .../codegen/attribute/AttributeGenerator.java | 249 ------------------ .../minestom/demo/entity/ChickenCreature.java | 4 +- gradle/libs.versions.toml | 2 +- .../minestom/server/entity/EntityTypes.java | 8 + .../server/entity/attribute/Attributes.java | 51 ++++ .../server/entity/damage/DamageTypes.java | 2 + .../server/instance/block/Blocks.java | 4 + .../minestom/server/item/Enchantments.java | 8 +- .../net/minestom/server/item/Materials.java | 38 ++- .../minestom/server/particle/Particles.java | 26 +- .../minestom/server/potion/PotionEffects.java | 12 + .../minestom/server/potion/PotionTypes.java | 10 +- .../minestom/server/sound/SoundEvents.java | 138 +++++++++- .../net/minestom/server/MinecraftServer.java | 4 +- .../minestom/server/attribute/Attribute.java | 66 ----- .../minestom/server/entity/LivingEntity.java | 32 +-- .../net/minestom/server/entity/Metadata.java | 124 +++++---- .../minestom/server/entity/MetadataImpl.java | 25 +- .../net/minestom/server/entity/Player.java | 14 +- .../server/entity/attribute/Attribute.java | 59 +++++ .../entity/attribute/AttributeImpl.java | 32 +++ .../attribute/AttributeInstance.java | 18 +- .../attribute/AttributeModifier.java | 2 +- .../attribute/AttributeOperation.java | 2 +- .../server/entity/damage/DamageType.java | 8 +- .../server/entity/damage/DamageTypeImpl.java | 37 +-- .../entity/metadata/LivingEntityMeta.java | 63 ++--- .../entity/metadata/animal/ArmadilloMeta.java | 32 +++ .../entity/metadata/animal/TurtleMeta.java | 4 +- .../metadata/animal/tameable/WolfMeta.java | 2 + .../metadata/display/ItemDisplayMeta.java | 2 +- .../metadata/item/ItemContainingMeta.java | 2 +- .../metadata/monster/skeleton/BoggedMeta.java | 14 + .../entity/metadata/other/EndCrystalMeta.java | 2 +- .../metadata/other/FallingBlockMeta.java | 2 +- .../entity/metadata/other/ItemFrameMeta.java | 2 +- .../other/OminousItemSpawnerMeta.java | 25 ++ .../projectile/FireworkRocketMeta.java | 3 +- .../entity/metadata/water/DolphinMeta.java | 2 +- .../entity/pathfinding/PFPathingEntity.java | 10 +- .../minestom/server/item/ItemSerializers.java | 8 +- .../net/minestom/server/item/Material.java | 34 ++- .../server/item/attribute/ItemAttribute.java | 4 +- .../server/item/component/CustomData.java | 23 ++ .../server/item/component/ItemComponent.java | 20 ++ .../server/item/metadata/PotionMeta.java | 2 +- .../listener/preplay/HandshakeListener.java | 7 +- .../listener/preplay/LoginListener.java | 2 +- .../minestom/server/message/Messenger.java | 54 ++-- .../server/network/ConnectionManager.java | 22 +- .../server/network/NetworkBuffer.java | 7 + .../server/network/NetworkBufferTypeImpl.java | 24 +- .../packet/client/ClientPacketsHandler.java | 19 +- .../common/ClientCookieResponsePacket.java | 35 +++ .../ClientSelectKnownPacksPacket.java | 29 ++ .../handshake/ClientHandshakePacket.java | 25 +- .../client/play/ClientCommandChatPacket.java | 20 +- .../ClientDebugSampleSubscriptionPacket.java | 18 ++ .../play/ClientSignedCommandChatPacket.java | 34 +++ .../packet/server/ServerPacketIdentifier.java | 62 +++-- .../server/common/CookieRequestPacket.java | 34 +++ .../server/common/CookieStorePacket.java | 54 ++++ .../server/common/ResourcePackPopPacket.java | 2 +- .../server/common/ResourcePackPushPacket.java | 2 +- .../packet/server/common/TransferPacket.java | 32 +++ .../configuration/RegistryDataPacket.java | 32 ++- .../server/configuration/ResetChatPacket.java | 23 ++ .../configuration/SelectKnownPacksPacket.java | 52 ++++ .../server/login/EncryptionRequestPacket.java | 17 +- .../packet/server/play/DebugSamplePacket.java | 29 ++ .../server/play/DeclareRecipesPacket.java | 2 +- ...acket.java => EntityAttributesPacket.java} | 26 +- .../server/play/EntityEffectPacket.java | 11 +- .../server/play/EntityMetaDataPacket.java | 6 +- .../packet/server/play/JoinGamePacket.java | 17 +- .../server/play/ProjectilePowerPacket.java | 28 ++ .../packet/server/play/RespawnPacket.java | 6 +- .../net/minestom/server/potion/Potion.java | 11 +- .../minestom/server/recipe/RecipeManager.java | 2 +- .../minestom/server/registry/Registry.java | 25 +- .../minestom/server/world/DimensionType.java | 10 +- .../server/world/DimensionTypeManager.java | 38 +-- .../minestom/server/world/biomes/Biome.java | 5 + .../server/world/biomes/BiomeManager.java | 97 ++++--- .../entity/EntityMetaIntegrationTest.java | 8 +- .../entity/player/PlayerIntegrationTest.java | 3 +- .../server/item/ItemAttributeTest.java | 5 +- .../server/network/PacketWriteReadTest.java | 6 +- 90 files changed, 1426 insertions(+), 756 deletions(-) delete mode 100644 code-generators/src/main/java/net/minestom/codegen/attribute/AttributeGenerator.java create mode 100644 src/autogenerated/java/net/minestom/server/entity/attribute/Attributes.java delete mode 100644 src/main/java/net/minestom/server/attribute/Attribute.java create mode 100644 src/main/java/net/minestom/server/entity/attribute/Attribute.java create mode 100644 src/main/java/net/minestom/server/entity/attribute/AttributeImpl.java rename src/main/java/net/minestom/server/{ => entity}/attribute/AttributeInstance.java (91%) rename src/main/java/net/minestom/server/{ => entity}/attribute/AttributeModifier.java (97%) rename src/main/java/net/minestom/server/{ => entity}/attribute/AttributeOperation.java (92%) create mode 100644 src/main/java/net/minestom/server/entity/metadata/animal/ArmadilloMeta.java create mode 100644 src/main/java/net/minestom/server/entity/metadata/monster/skeleton/BoggedMeta.java create mode 100644 src/main/java/net/minestom/server/entity/metadata/other/OminousItemSpawnerMeta.java create mode 100644 src/main/java/net/minestom/server/item/component/CustomData.java create mode 100644 src/main/java/net/minestom/server/item/component/ItemComponent.java create mode 100644 src/main/java/net/minestom/server/network/packet/client/common/ClientCookieResponsePacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/client/configuration/ClientSelectKnownPacksPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/client/play/ClientDebugSampleSubscriptionPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/client/play/ClientSignedCommandChatPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/common/CookieRequestPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/common/CookieStorePacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/common/TransferPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/configuration/ResetChatPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/configuration/SelectKnownPacksPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/play/DebugSamplePacket.java rename src/main/java/net/minestom/server/network/packet/server/play/{EntityPropertiesPacket.java => EntityAttributesPacket.java} (70%) create mode 100644 src/main/java/net/minestom/server/network/packet/server/play/ProjectilePowerPacket.java diff --git a/build.gradle.kts b/build.gradle.kts index b1916f02d..f8259daf4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ allprojects { repositories { mavenCentral() + mavenLocal() maven(url = "https://jitpack.io") } diff --git a/code-generators/src/main/java/net/minestom/codegen/Generators.java b/code-generators/src/main/java/net/minestom/codegen/Generators.java index 6cb6ded57..26688c331 100644 --- a/code-generators/src/main/java/net/minestom/codegen/Generators.java +++ b/code-generators/src/main/java/net/minestom/codegen/Generators.java @@ -18,11 +18,9 @@ public class Generators { } File outputFolder = new File(args[0]); - // Generate DyeColors new DyeColorGenerator(resource("dye_colors.json"), outputFolder).generate(); - var generator = new CodeGenerator(outputFolder); generator.generate(resource("blocks.json"), "net.minestom.server.instance.block", "Block", "BlockImpl", "Blocks"); generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "Materials"); @@ -37,16 +35,11 @@ public class Generators { generator.generate(resource("damage_types.json"), "net.minestom.server.entity.damage", "DamageType", "DamageTypeImpl", "DamageTypes"); generator.generate(resource("trim_materials.json"), "net.minestom.server.item.armor", "TrimMaterial", "TrimMaterialImpl", "TrimMaterials"); generator.generate(resource("trim_patterns.json"), "net.minestom.server.item.armor", "TrimPattern", "TrimPatternImpl", "TrimPatterns"); - + generator.generate(resource("attributes.json"), "net.minestom.server.entity.attribute", "Attribute", "AttributeImpl", "Attributes"); // Generate fluids new FluidGenerator(resource("fluids.json"), outputFolder).generate(); - // TODO: Generate attributes -// new AttributeGenerator( -// new File(inputFolder, targetVersion + "_attributes.json"), -// outputFolder -// ).generate(); // TODO: Generate villager professions // new VillagerProfessionGenerator( // new File(inputFolder, targetVersion + "_villager_professions.json"), diff --git a/code-generators/src/main/java/net/minestom/codegen/attribute/AttributeGenerator.java b/code-generators/src/main/java/net/minestom/codegen/attribute/AttributeGenerator.java deleted file mode 100644 index 11ee9eef8..000000000 --- a/code-generators/src/main/java/net/minestom/codegen/attribute/AttributeGenerator.java +++ /dev/null @@ -1,249 +0,0 @@ -package net.minestom.codegen.attribute; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.squareup.javapoet.*; -import net.minestom.codegen.MinestomCodeGenerator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.lang.model.element.Modifier; -import java.io.File; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; - -public final class AttributeGenerator extends MinestomCodeGenerator { - private static final Logger LOGGER = LoggerFactory.getLogger(AttributeGenerator.class); - private final InputStream attributesFile; - private final File outputFolder; - - public AttributeGenerator(@Nullable InputStream attributesFile, @NotNull File outputFolder) { - this.attributesFile = attributesFile; - this.outputFolder = outputFolder; - } - - @Override - public void generate() { - if (attributesFile == null) { - LOGGER.error("Failed to find attributes.json."); - LOGGER.error("Stopped code generation for attributes."); - return; - } - if (!outputFolder.exists() && !outputFolder.mkdirs()) { - LOGGER.error("Output folder for code generation does not exist and could not be created."); - return; - } - // Important classes we use alot - ClassName namespaceIDClassName = ClassName.get("net.minestom.server.utils", "NamespaceID"); - ClassName registryClassName = ClassName.get("net.minestom.server.registry", "Registry"); - - JsonArray attributes = GSON.fromJson(new InputStreamReader(attributesFile), JsonArray.class); - List filesToWrite = new ArrayList<>(); - - ClassName attributeClassName = ClassName.get("net.minestom.server.attribute", "Attribute"); - - // Attribute - TypeSpec.Builder attributeClass = TypeSpec.classBuilder(attributeClassName) - .addSuperinterface(ClassName.get("net.kyori.adventure.key", "Keyed")) - .addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName()); - attributeClass.addField( - FieldSpec.builder(namespaceIDClassName, "id") - .addModifiers(Modifier.PRIVATE, Modifier.FINAL).addAnnotation(NotNull.class).build() - ); - attributeClass.addField( - FieldSpec.builder(TypeName.DOUBLE, "defaultValue") - .addModifiers(Modifier.PRIVATE, Modifier.FINAL).build() - ); - attributeClass.addField( - FieldSpec.builder(TypeName.BOOLEAN, "clientSyncable") - .addModifiers(Modifier.PRIVATE, Modifier.FINAL).build() - ); - attributeClass.addMethod( - MethodSpec.constructorBuilder() - .addParameter(ParameterSpec.builder(namespaceIDClassName, "id").addAnnotation(NotNull.class).build()) - .addParameter(ParameterSpec.builder(TypeName.BOOLEAN, "clientSyncable").build()) - .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "defaultValue").build()) - .addStatement("this.id = id") - .addStatement("this.clientSyncable = clientSyncable") - .addStatement("this.defaultValue = defaultValue") - .addModifiers(Modifier.PROTECTED) - .build() - ); - // Override key method (adventure) - attributeClass.addMethod( - MethodSpec.methodBuilder("key") - .returns(ClassName.get("net.kyori.adventure.key", "Key")) - .addAnnotation(Override.class) - .addAnnotation(NotNull.class) - .addStatement("return this.id") - .addModifiers(Modifier.PUBLIC) - .build() - ); - // getId method - attributeClass.addMethod( - MethodSpec.methodBuilder("getId") - .returns(namespaceIDClassName) - .addAnnotation(NotNull.class) - .addStatement("return this.id") - .addModifiers(Modifier.PUBLIC) - .build() - ); - // getDefaultValue - attributeClass.addMethod( - MethodSpec.methodBuilder("getDefaultValue") - .returns(TypeName.DOUBLE) - .addStatement("return this.defaultValue") - .addModifiers(Modifier.PUBLIC) - .build() - ); - // isClientSyncable - attributeClass.addMethod( - MethodSpec.methodBuilder("isClientSyncable") - .returns(TypeName.BOOLEAN) - .addStatement("return this.clientSyncable") - .addModifiers(Modifier.PUBLIC) - .build() - ); - // isShared - attributeClass.addMethod( - MethodSpec.methodBuilder("isShared") - .addAnnotation(Deprecated.class) - .returns(TypeName.BOOLEAN) - .addStatement("return this.clientSyncable") - .addModifiers(Modifier.PUBLIC) - .build() - ); - // values method - attributeClass.addMethod( - MethodSpec.methodBuilder("values") - .addAnnotation(NotNull.class) - .returns(ParameterizedTypeName.get(ClassName.get(List.class), attributeClassName)) - .addStatement("return $T.ATTRIBUTE_REGISTRY.values()", registryClassName) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .build() - ); - // toString method - attributeClass.addMethod( - MethodSpec.methodBuilder("toString") - .addAnnotation(NotNull.class) - .addAnnotation(Override.class) - .returns(String.class) - // this resolves to [Namespace] - .addStatement("return \"[\" + this.id + \"]\"") - .addModifiers(Modifier.PUBLIC) - .build() - ); - // Creating ClampedAttribute - ClassName clampedAttributeClassName = ClassName.get("net.minestom.server.attribute", "ClampedAttribute"); - - TypeSpec.Builder clampedAttributeClass = TypeSpec.classBuilder(clampedAttributeClassName) - .superclass(attributeClassName) - .addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName()); - clampedAttributeClass.addField( - FieldSpec.builder(TypeName.DOUBLE, "minValue") - .addModifiers(Modifier.PRIVATE, Modifier.FINAL).build() - ); - clampedAttributeClass.addField( - FieldSpec.builder(TypeName.DOUBLE, "maxValue") - .addModifiers(Modifier.PRIVATE, Modifier.FINAL).build() - ); - clampedAttributeClass.addMethod( - MethodSpec.constructorBuilder() - .addParameter(ParameterSpec.builder(namespaceIDClassName, "id").addAnnotation(NotNull.class).build()) - .addParameter(ParameterSpec.builder(TypeName.BOOLEAN, "clientSyncable").build()) - .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "defaultValue").build()) - .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "minValue").build()) - .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "maxValue").build()) - .addStatement("super(id, clientSyncable, defaultValue)") - .addStatement("this.minValue = minValue") - .addStatement("this.maxValue = maxValue") - .addModifiers(Modifier.PROTECTED) - .build() - ); - // getMinValue - clampedAttributeClass.addMethod( - MethodSpec.methodBuilder("getMinValue") - .returns(TypeName.DOUBLE) - .addStatement("return this.minValue") - .addModifiers(Modifier.PUBLIC) - .build() - ); - // getMaxValue - clampedAttributeClass.addMethod( - MethodSpec.methodBuilder("getMaxValue") - .returns(TypeName.DOUBLE) - .addStatement("return this.maxValue") - .addModifiers(Modifier.PUBLIC) - .build() - ); - - CodeBlock.Builder staticBlock = CodeBlock.builder(); - // Use data - for (JsonElement a : attributes) { - JsonObject attribute = a.getAsJsonObject(); - String attributeName = attribute.get("name").getAsString(); - - JsonObject range = attribute.getAsJsonObject("range"); - if (range == null) { - // Normal attribute - attributeClass.addField( - FieldSpec.builder( - attributeClassName, - attributeName - ).initializer( - "new $T($T.from($S), $L, $L)", - attributeClassName, - namespaceIDClassName, - attribute.get("id").getAsString(), - attribute.get("clientSync").getAsBoolean(), - attribute.get("defaultValue").getAsDouble() - ).addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).build() - ); - } else { - // ClampedAttribute - attributeClass.addField( - FieldSpec.builder( - attributeClassName, - attributeName - ).initializer( - "new $T($T.from($S), $L, $L, $L, $L)", - clampedAttributeClassName, - namespaceIDClassName, - attribute.get("id").getAsString(), - attribute.get("clientSync").getAsBoolean(), - attribute.get("defaultValue").getAsDouble(), - range.get("minValue").getAsDouble(), - range.get("maxValue").getAsDouble() - ).addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).build() - ); - } - // Add to static init. - staticBlock.addStatement("$T.ATTRIBUTE_REGISTRY.register($N)", registryClassName, attributeName); - } - attributeClass.addStaticBlock(staticBlock.build()); - - filesToWrite.add( - JavaFile.builder("net.minestom.server.attribute", attributeClass.build()) - .indent(" ") - .skipJavaLangImports(true) - .build() - ); - filesToWrite.add( - JavaFile.builder("net.minestom.server.attribute", clampedAttributeClass.build()) - .indent(" ") - .skipJavaLangImports(true) - .build() - ); - - // Write files to outputFolder - writeFiles( - filesToWrite, - outputFolder - ); - } -} diff --git a/demo/src/main/java/net/minestom/demo/entity/ChickenCreature.java b/demo/src/main/java/net/minestom/demo/entity/ChickenCreature.java index 6f241e422..14ea776f0 100644 --- a/demo/src/main/java/net/minestom/demo/entity/ChickenCreature.java +++ b/demo/src/main/java/net/minestom/demo/entity/ChickenCreature.java @@ -1,9 +1,9 @@ package net.minestom.demo.entity; -import net.minestom.server.attribute.Attribute; import net.minestom.server.entity.EntityCreature; import net.minestom.server.entity.EntityType; import net.minestom.server.entity.ai.goal.RandomStrollGoal; +import net.minestom.server.entity.attribute.Attribute; import java.util.List; @@ -35,7 +35,7 @@ public class ChickenCreature extends EntityCreature { // .build() // ); - getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.1f); + getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0.1); } @Override diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8230be930..cd19340c0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ metadata.format.version = "1.1" [versions] # Important dependencies -data = "1.20.4-rv4" +data = "24w13a-dev" adventure = "4.16.0" kotlin = "1.7.22" dependencyGetter = "v1.0.1" diff --git a/src/autogenerated/java/net/minestom/server/entity/EntityTypes.java b/src/autogenerated/java/net/minestom/server/entity/EntityTypes.java index 9c01f5253..0574ffcf2 100644 --- a/src/autogenerated/java/net/minestom/server/entity/EntityTypes.java +++ b/src/autogenerated/java/net/minestom/server/entity/EntityTypes.java @@ -9,6 +9,8 @@ interface EntityTypes { EntityType AREA_EFFECT_CLOUD = EntityTypeImpl.get("minecraft:area_effect_cloud"); + EntityType ARMADILLO = EntityTypeImpl.get("minecraft:armadillo"); + EntityType ARMOR_STAND = EntityTypeImpl.get("minecraft:armor_stand"); EntityType ARROW = EntityTypeImpl.get("minecraft:arrow"); @@ -25,8 +27,12 @@ interface EntityTypes { EntityType BOAT = EntityTypeImpl.get("minecraft:boat"); + EntityType BOGGED = EntityTypeImpl.get("minecraft:bogged"); + EntityType BREEZE = EntityTypeImpl.get("minecraft:breeze"); + EntityType BREEZE_WIND_CHARGE = EntityTypeImpl.get("minecraft:breeze_wind_charge"); + EntityType CAMEL = EntityTypeImpl.get("minecraft:camel"); EntityType CAT = EntityTypeImpl.get("minecraft:cat"); @@ -121,6 +127,8 @@ interface EntityTypes { EntityType ITEM_FRAME = EntityTypeImpl.get("minecraft:item_frame"); + EntityType OMINOUS_ITEM_SPAWNER = EntityTypeImpl.get("minecraft:ominous_item_spawner"); + EntityType FIREBALL = EntityTypeImpl.get("minecraft:fireball"); EntityType LEASH_KNOT = EntityTypeImpl.get("minecraft:leash_knot"); diff --git a/src/autogenerated/java/net/minestom/server/entity/attribute/Attributes.java b/src/autogenerated/java/net/minestom/server/entity/attribute/Attributes.java new file mode 100644 index 000000000..9f22bbe56 --- /dev/null +++ b/src/autogenerated/java/net/minestom/server/entity/attribute/Attributes.java @@ -0,0 +1,51 @@ +package net.minestom.server.entity.attribute; + +/** + * Code autogenerated, do not edit! + */ +@SuppressWarnings("unused") +interface Attributes { + Attribute GENERIC_ARMOR = AttributeImpl.get("minecraft:generic.armor"); + + Attribute GENERIC_ARMOR_TOUGHNESS = AttributeImpl.get("minecraft:generic.armor_toughness"); + + Attribute GENERIC_ATTACK_DAMAGE = AttributeImpl.get("minecraft:generic.attack_damage"); + + Attribute GENERIC_ATTACK_KNOCKBACK = AttributeImpl.get("minecraft:generic.attack_knockback"); + + Attribute GENERIC_ATTACK_SPEED = AttributeImpl.get("minecraft:generic.attack_speed"); + + Attribute PLAYER_BLOCK_BREAK_SPEED = AttributeImpl.get("minecraft:player.block_break_speed"); + + Attribute PLAYER_BLOCK_INTERACTION_RANGE = AttributeImpl.get("minecraft:player.block_interaction_range"); + + Attribute PLAYER_ENTITY_INTERACTION_RANGE = AttributeImpl.get("minecraft:player.entity_interaction_range"); + + Attribute GENERIC_FALL_DAMAGE_MULTIPLIER = AttributeImpl.get("minecraft:generic.fall_damage_multiplier"); + + Attribute GENERIC_FLYING_SPEED = AttributeImpl.get("minecraft:generic.flying_speed"); + + Attribute GENERIC_FOLLOW_RANGE = AttributeImpl.get("minecraft:generic.follow_range"); + + Attribute GENERIC_GRAVITY = AttributeImpl.get("minecraft:generic.gravity"); + + Attribute GENERIC_JUMP_STRENGTH = AttributeImpl.get("minecraft:generic.jump_strength"); + + Attribute GENERIC_KNOCKBACK_RESISTANCE = AttributeImpl.get("minecraft:generic.knockback_resistance"); + + Attribute GENERIC_LUCK = AttributeImpl.get("minecraft:generic.luck"); + + Attribute GENERIC_MAX_ABSORPTION = AttributeImpl.get("minecraft:generic.max_absorption"); + + Attribute GENERIC_MAX_HEALTH = AttributeImpl.get("minecraft:generic.max_health"); + + Attribute GENERIC_MOVEMENT_SPEED = AttributeImpl.get("minecraft:generic.movement_speed"); + + Attribute GENERIC_SAFE_FALL_DISTANCE = AttributeImpl.get("minecraft:generic.safe_fall_distance"); + + Attribute GENERIC_SCALE = AttributeImpl.get("minecraft:generic.scale"); + + Attribute ZOMBIE_SPAWN_REINFORCEMENTS = AttributeImpl.get("minecraft:zombie.spawn_reinforcements"); + + Attribute GENERIC_STEP_HEIGHT = AttributeImpl.get("minecraft:generic.step_height"); +} diff --git a/src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java b/src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java index b1b11a6bf..2777f3608 100644 --- a/src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java +++ b/src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java @@ -33,6 +33,8 @@ interface DamageTypes { DamageType PLAYER_EXPLOSION = DamageTypeImpl.get("minecraft:player_explosion"); + DamageType SPIT = DamageTypeImpl.get("minecraft:spit"); + DamageType STING = DamageTypeImpl.get("minecraft:sting"); DamageType UNATTRIBUTED_FIREBALL = DamageTypeImpl.get("minecraft:unattributed_fireball"); diff --git a/src/autogenerated/java/net/minestom/server/instance/block/Blocks.java b/src/autogenerated/java/net/minestom/server/instance/block/Blocks.java index fc2a60332..974c15bb9 100644 --- a/src/autogenerated/java/net/minestom/server/instance/block/Blocks.java +++ b/src/autogenerated/java/net/minestom/server/instance/block/Blocks.java @@ -2120,4 +2120,8 @@ interface Blocks { Block CRAFTER = BlockImpl.get("minecraft:crafter"); Block TRIAL_SPAWNER = BlockImpl.get("minecraft:trial_spawner"); + + Block VAULT = BlockImpl.get("minecraft:vault"); + + Block HEAVY_CORE = BlockImpl.get("minecraft:heavy_core"); } diff --git a/src/autogenerated/java/net/minestom/server/item/Enchantments.java b/src/autogenerated/java/net/minestom/server/item/Enchantments.java index 1de0916b1..814705b2d 100644 --- a/src/autogenerated/java/net/minestom/server/item/Enchantments.java +++ b/src/autogenerated/java/net/minestom/server/item/Enchantments.java @@ -43,7 +43,7 @@ interface Enchantments { Enchantment LOOTING = EnchantmentImpl.get("minecraft:looting"); - Enchantment SWEEPING = EnchantmentImpl.get("minecraft:sweeping"); + Enchantment SWEEPING_EDGE = EnchantmentImpl.get("minecraft:sweeping_edge"); Enchantment EFFICIENCY = EnchantmentImpl.get("minecraft:efficiency"); @@ -79,6 +79,12 @@ interface Enchantments { Enchantment PIERCING = EnchantmentImpl.get("minecraft:piercing"); + Enchantment DENSITY = EnchantmentImpl.get("minecraft:density"); + + Enchantment BREACH = EnchantmentImpl.get("minecraft:breach"); + + Enchantment WIND_BURST = EnchantmentImpl.get("minecraft:wind_burst"); + Enchantment MENDING = EnchantmentImpl.get("minecraft:mending"); Enchantment VANISHING_CURSE = EnchantmentImpl.get("minecraft:vanishing_curse"); diff --git a/src/autogenerated/java/net/minestom/server/item/Materials.java b/src/autogenerated/java/net/minestom/server/item/Materials.java index 17aab6094..622f5a00e 100644 --- a/src/autogenerated/java/net/minestom/server/item/Materials.java +++ b/src/autogenerated/java/net/minestom/server/item/Materials.java @@ -175,6 +175,8 @@ interface Materials { Material RAW_GOLD_BLOCK = MaterialImpl.get("minecraft:raw_gold_block"); + Material HEAVY_CORE = MaterialImpl.get("minecraft:heavy_core"); + Material AMETHYST_BLOCK = MaterialImpl.get("minecraft:amethyst_block"); Material BUDDING_AMETHYST = MaterialImpl.get("minecraft:budding_amethyst"); @@ -1593,7 +1595,11 @@ interface Materials { Material TURTLE_HELMET = MaterialImpl.get("minecraft:turtle_helmet"); - Material SCUTE = MaterialImpl.get("minecraft:scute"); + Material TURTLE_SCUTE = MaterialImpl.get("minecraft:turtle_scute"); + + Material ARMADILLO_SCUTE = MaterialImpl.get("minecraft:armadillo_scute"); + + Material WOLF_ARMOR = MaterialImpl.get("minecraft:wolf_armor"); Material FLINT_AND_STEEL = MaterialImpl.get("minecraft:flint_and_steel"); @@ -2015,6 +2021,8 @@ interface Materials { Material GLISTERING_MELON_SLICE = MaterialImpl.get("minecraft:glistering_melon_slice"); + Material ARMADILLO_SPAWN_EGG = MaterialImpl.get("minecraft:armadillo_spawn_egg"); + Material ALLAY_SPAWN_EGG = MaterialImpl.get("minecraft:allay_spawn_egg"); Material AXOLOTL_SPAWN_EGG = MaterialImpl.get("minecraft:axolotl_spawn_egg"); @@ -2025,6 +2033,8 @@ interface Materials { Material BLAZE_SPAWN_EGG = MaterialImpl.get("minecraft:blaze_spawn_egg"); + Material BOGGED_SPAWN_EGG = MaterialImpl.get("minecraft:bogged_spawn_egg"); + Material BREEZE_SPAWN_EGG = MaterialImpl.get("minecraft:breeze_spawn_egg"); Material CAT_SPAWN_EGG = MaterialImpl.get("minecraft:cat_spawn_egg"); @@ -2175,10 +2185,14 @@ interface Materials { Material FIRE_CHARGE = MaterialImpl.get("minecraft:fire_charge"); + Material WIND_CHARGE = MaterialImpl.get("minecraft:wind_charge"); + Material WRITABLE_BOOK = MaterialImpl.get("minecraft:writable_book"); Material WRITTEN_BOOK = MaterialImpl.get("minecraft:written_book"); + Material MACE = MaterialImpl.get("minecraft:mace"); + Material ITEM_FRAME = MaterialImpl.get("minecraft:item_frame"); Material GLOW_ITEM_FRAME = MaterialImpl.get("minecraft:glow_item_frame"); @@ -2387,6 +2401,10 @@ interface Materials { Material PIGLIN_BANNER_PATTERN = MaterialImpl.get("minecraft:piglin_banner_pattern"); + Material FLOW_BANNER_PATTERN = MaterialImpl.get("minecraft:flow_banner_pattern"); + + Material GUSTER_BANNER_PATTERN = MaterialImpl.get("minecraft:guster_banner_pattern"); + Material GOAT_HORN = MaterialImpl.get("minecraft:goat_horn"); Material COMPOSTER = MaterialImpl.get("minecraft:composter"); @@ -2553,6 +2571,10 @@ interface Materials { Material HOST_ARMOR_TRIM_SMITHING_TEMPLATE = MaterialImpl.get("minecraft:host_armor_trim_smithing_template"); + Material FLOW_ARMOR_TRIM_SMITHING_TEMPLATE = MaterialImpl.get("minecraft:flow_armor_trim_smithing_template"); + + Material BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = MaterialImpl.get("minecraft:bolt_armor_trim_smithing_template"); + Material ANGLER_POTTERY_SHERD = MaterialImpl.get("minecraft:angler_pottery_sherd"); Material ARCHER_POTTERY_SHERD = MaterialImpl.get("minecraft:archer_pottery_sherd"); @@ -2569,8 +2591,12 @@ interface Materials { Material EXPLORER_POTTERY_SHERD = MaterialImpl.get("minecraft:explorer_pottery_sherd"); + Material FLOW_POTTERY_SHERD = MaterialImpl.get("minecraft:flow_pottery_sherd"); + Material FRIEND_POTTERY_SHERD = MaterialImpl.get("minecraft:friend_pottery_sherd"); + Material GUSTER_POTTERY_SHERD = MaterialImpl.get("minecraft:guster_pottery_sherd"); + Material HEART_POTTERY_SHERD = MaterialImpl.get("minecraft:heart_pottery_sherd"); Material HEARTBREAK_POTTERY_SHERD = MaterialImpl.get("minecraft:heartbreak_pottery_sherd"); @@ -2585,6 +2611,8 @@ interface Materials { Material PRIZE_POTTERY_SHERD = MaterialImpl.get("minecraft:prize_pottery_sherd"); + Material SCRAPE_POTTERY_SHERD = MaterialImpl.get("minecraft:scrape_pottery_sherd"); + Material SHEAF_POTTERY_SHERD = MaterialImpl.get("minecraft:sheaf_pottery_sherd"); Material SHELTER_POTTERY_SHERD = MaterialImpl.get("minecraft:shelter_pottery_sherd"); @@ -2628,4 +2656,12 @@ interface Materials { Material TRIAL_SPAWNER = MaterialImpl.get("minecraft:trial_spawner"); Material TRIAL_KEY = MaterialImpl.get("minecraft:trial_key"); + + Material OMINOUS_TRIAL_KEY = MaterialImpl.get("minecraft:ominous_trial_key"); + + Material VAULT = MaterialImpl.get("minecraft:vault"); + + Material OMINOUS_BOTTLE = MaterialImpl.get("minecraft:ominous_bottle"); + + Material BREEZE_ROD = MaterialImpl.get("minecraft:breeze_rod"); } diff --git a/src/autogenerated/java/net/minestom/server/particle/Particles.java b/src/autogenerated/java/net/minestom/server/particle/Particles.java index 691280c43..a25a661c0 100644 --- a/src/autogenerated/java/net/minestom/server/particle/Particles.java +++ b/src/autogenerated/java/net/minestom/server/particle/Particles.java @@ -5,8 +5,6 @@ package net.minestom.server.particle; */ @SuppressWarnings("unused") interface Particles { - Particle AMBIENT_ENTITY_EFFECT = ParticleImpl.get("minecraft:ambient_entity_effect"); - Particle ANGRY_VILLAGER = ParticleImpl.get("minecraft:angry_villager"); Particle BLOCK = ParticleImpl.get("minecraft:block"); @@ -55,7 +53,11 @@ interface Particles { Particle GUST = ParticleImpl.get("minecraft:gust"); - Particle GUST_EMITTER = ParticleImpl.get("minecraft:gust_emitter"); + Particle SMALL_GUST = ParticleImpl.get("minecraft:small_gust"); + + Particle GUST_EMITTER_LARGE = ParticleImpl.get("minecraft:gust_emitter_large"); + + Particle GUST_EMITTER_SMALL = ParticleImpl.get("minecraft:gust_emitter_small"); Particle SONIC_BOOM = ParticleImpl.get("minecraft:sonic_boom"); @@ -67,6 +69,8 @@ interface Particles { Particle FLAME = ParticleImpl.get("minecraft:flame"); + Particle INFESTED = ParticleImpl.get("minecraft:infested"); + Particle CHERRY_LEAVES = ParticleImpl.get("minecraft:cherry_leaves"); Particle SCULK_SOUL = ParticleImpl.get("minecraft:sculk_soul"); @@ -95,6 +99,8 @@ interface Particles { Particle ITEM_SLIME = ParticleImpl.get("minecraft:item_slime"); + Particle ITEM_COBWEB = ParticleImpl.get("minecraft:item_cobweb"); + Particle ITEM_SNOWBALL = ParticleImpl.get("minecraft:item_snowball"); Particle LARGE_SMOKE = ParticleImpl.get("minecraft:large_smoke"); @@ -203,7 +209,17 @@ interface Particles { Particle DUST_PLUME = ParticleImpl.get("minecraft:dust_plume"); - Particle GUST_DUST = ParticleImpl.get("minecraft:gust_dust"); - Particle TRIAL_SPAWNER_DETECTION = ParticleImpl.get("minecraft:trial_spawner_detection"); + + Particle TRIAL_SPAWNER_DETECTION_OMINOUS = ParticleImpl.get("minecraft:trial_spawner_detection_ominous"); + + Particle VAULT_CONNECTION = ParticleImpl.get("minecraft:vault_connection"); + + Particle DUST_PILLAR = ParticleImpl.get("minecraft:dust_pillar"); + + Particle OMINOUS_SPAWNING = ParticleImpl.get("minecraft:ominous_spawning"); + + Particle RAID_OMEN = ParticleImpl.get("minecraft:raid_omen"); + + Particle TRIAL_OMEN = ParticleImpl.get("minecraft:trial_omen"); } diff --git a/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java b/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java index 00189dc46..6116a8692 100644 --- a/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java +++ b/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java @@ -70,4 +70,16 @@ interface PotionEffects { PotionEffect HERO_OF_THE_VILLAGE = PotionEffectImpl.get("minecraft:hero_of_the_village"); PotionEffect DARKNESS = PotionEffectImpl.get("minecraft:darkness"); + + PotionEffect TRIAL_OMEN = PotionEffectImpl.get("minecraft:trial_omen"); + + PotionEffect RAID_OMEN = PotionEffectImpl.get("minecraft:raid_omen"); + + PotionEffect WIND_CHARGED = PotionEffectImpl.get("minecraft:wind_charged"); + + PotionEffect WEAVING = PotionEffectImpl.get("minecraft:weaving"); + + PotionEffect OOZING = PotionEffectImpl.get("minecraft:oozing"); + + PotionEffect INFESTED = PotionEffectImpl.get("minecraft:infested"); } diff --git a/src/autogenerated/java/net/minestom/server/potion/PotionTypes.java b/src/autogenerated/java/net/minestom/server/potion/PotionTypes.java index 91430a100..fc078487d 100644 --- a/src/autogenerated/java/net/minestom/server/potion/PotionTypes.java +++ b/src/autogenerated/java/net/minestom/server/potion/PotionTypes.java @@ -5,8 +5,6 @@ package net.minestom.server.potion; */ @SuppressWarnings("unused") interface PotionTypes { - PotionType EMPTY = PotionTypeImpl.get("minecraft:empty"); - PotionType WATER = PotionTypeImpl.get("minecraft:water"); PotionType MUNDANE = PotionTypeImpl.get("minecraft:mundane"); @@ -90,4 +88,12 @@ interface PotionTypes { PotionType SLOW_FALLING = PotionTypeImpl.get("minecraft:slow_falling"); PotionType LONG_SLOW_FALLING = PotionTypeImpl.get("minecraft:long_slow_falling"); + + PotionType WIND_CHARGED = PotionTypeImpl.get("minecraft:wind_charged"); + + PotionType WEAVING = PotionTypeImpl.get("minecraft:weaving"); + + PotionType OOZING = PotionTypeImpl.get("minecraft:oozing"); + + PotionType INFESTED = PotionTypeImpl.get("minecraft:infested"); } diff --git a/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java b/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java index bd1d0287f..61da7da29 100644 --- a/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java +++ b/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java @@ -113,6 +113,32 @@ interface SoundEvents { SoundEvent BLOCK_ANVIL_USE = SoundEventImpl.get("minecraft:block.anvil.use"); + SoundEvent ENTITY_ARMADILLO_EAT = SoundEventImpl.get("minecraft:entity.armadillo.eat"); + + SoundEvent ENTITY_ARMADILLO_HURT = SoundEventImpl.get("minecraft:entity.armadillo.hurt"); + + SoundEvent ENTITY_ARMADILLO_HURT_REDUCED = SoundEventImpl.get("minecraft:entity.armadillo.hurt_reduced"); + + SoundEvent ENTITY_ARMADILLO_AMBIENT = SoundEventImpl.get("minecraft:entity.armadillo.ambient"); + + SoundEvent ENTITY_ARMADILLO_STEP = SoundEventImpl.get("minecraft:entity.armadillo.step"); + + SoundEvent ENTITY_ARMADILLO_DEATH = SoundEventImpl.get("minecraft:entity.armadillo.death"); + + SoundEvent ENTITY_ARMADILLO_ROLL = SoundEventImpl.get("minecraft:entity.armadillo.roll"); + + SoundEvent ENTITY_ARMADILLO_LAND = SoundEventImpl.get("minecraft:entity.armadillo.land"); + + SoundEvent ENTITY_ARMADILLO_SCUTE_DROP = SoundEventImpl.get("minecraft:entity.armadillo.scute_drop"); + + SoundEvent ENTITY_ARMADILLO_UNROLL_FINISH = SoundEventImpl.get("minecraft:entity.armadillo.unroll_finish"); + + SoundEvent ENTITY_ARMADILLO_PEEK = SoundEventImpl.get("minecraft:entity.armadillo.peek"); + + SoundEvent ENTITY_ARMADILLO_UNROLL_START = SoundEventImpl.get("minecraft:entity.armadillo.unroll_start"); + + SoundEvent ENTITY_ARMADILLO_BRUSH = SoundEventImpl.get("minecraft:entity.armadillo.brush"); + SoundEvent ITEM_ARMOR_EQUIP_CHAIN = SoundEventImpl.get("minecraft:item.armor.equip_chain"); SoundEvent ITEM_ARMOR_EQUIP_DIAMOND = SoundEventImpl.get("minecraft:item.armor.equip_diamond"); @@ -131,6 +157,10 @@ interface SoundEvents { SoundEvent ITEM_ARMOR_EQUIP_TURTLE = SoundEventImpl.get("minecraft:item.armor.equip_turtle"); + SoundEvent ITEM_ARMOR_EQUIP_WOLF = SoundEventImpl.get("minecraft:item.armor.equip_wolf"); + + SoundEvent ITEM_ARMOR_UNEQUIP_WOLF = SoundEventImpl.get("minecraft:item.armor.unequip_wolf"); + SoundEvent ENTITY_ARMOR_STAND_BREAK = SoundEventImpl.get("minecraft:entity.armor_stand.break"); SoundEvent ENTITY_ARMOR_STAND_FALL = SoundEventImpl.get("minecraft:entity.armor_stand.fall"); @@ -313,6 +343,16 @@ interface SoundEvents { SoundEvent ENTITY_BOAT_PADDLE_WATER = SoundEventImpl.get("minecraft:entity.boat.paddle_water"); + SoundEvent ENTITY_BOGGED_AMBIENT = SoundEventImpl.get("minecraft:entity.bogged.ambient"); + + SoundEvent ENTITY_BOGGED_DEATH = SoundEventImpl.get("minecraft:entity.bogged.death"); + + SoundEvent ENTITY_BOGGED_HURT = SoundEventImpl.get("minecraft:entity.bogged.hurt"); + + SoundEvent ENTITY_BOGGED_SHEAR = SoundEventImpl.get("minecraft:entity.bogged.shear"); + + SoundEvent ENTITY_BOGGED_STEP = SoundEventImpl.get("minecraft:entity.bogged.step"); + SoundEvent BLOCK_BONE_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.bone_block.break"); SoundEvent BLOCK_BONE_BLOCK_FALL = SoundEventImpl.get("minecraft:block.bone_block.fall"); @@ -337,6 +377,10 @@ interface SoundEvents { SoundEvent ITEM_BOTTLE_FILL_DRAGONBREATH = SoundEventImpl.get("minecraft:item.bottle.fill_dragonbreath"); + SoundEvent ENTITY_BREEZE_CHARGE = SoundEventImpl.get("minecraft:entity.breeze.charge"); + + SoundEvent ENTITY_BREEZE_DEFLECT = SoundEventImpl.get("minecraft:entity.breeze.deflect"); + SoundEvent ENTITY_BREEZE_INHALE = SoundEventImpl.get("minecraft:entity.breeze.inhale"); SoundEvent ENTITY_BREEZE_IDLE_GROUND = SoundEventImpl.get("minecraft:entity.breeze.idle_ground"); @@ -355,6 +399,10 @@ interface SoundEvents { SoundEvent ENTITY_BREEZE_HURT = SoundEventImpl.get("minecraft:entity.breeze.hurt"); + SoundEvent ENTITY_BREEZE_WHIRL = SoundEventImpl.get("minecraft:entity.breeze.whirl"); + + SoundEvent ENTITY_BREEZE_WIND_BURST = SoundEventImpl.get("minecraft:entity.breeze.wind_burst"); + SoundEvent BLOCK_BREWING_STAND_BREW = SoundEventImpl.get("minecraft:block.brewing_stand.brew"); SoundEvent ITEM_BRUSH_BRUSHING_GENERIC = SoundEventImpl.get("minecraft:item.brush.brushing.generic"); @@ -597,6 +645,16 @@ interface SoundEvents { SoundEvent ITEM_CHORUS_FRUIT_TELEPORT = SoundEventImpl.get("minecraft:item.chorus_fruit.teleport"); + SoundEvent BLOCK_COBWEB_BREAK = SoundEventImpl.get("minecraft:block.cobweb.break"); + + SoundEvent BLOCK_COBWEB_STEP = SoundEventImpl.get("minecraft:block.cobweb.step"); + + SoundEvent BLOCK_COBWEB_PLACE = SoundEventImpl.get("minecraft:block.cobweb.place"); + + SoundEvent BLOCK_COBWEB_HIT = SoundEventImpl.get("minecraft:block.cobweb.hit"); + + SoundEvent BLOCK_COBWEB_FALL = SoundEventImpl.get("minecraft:block.cobweb.fall"); + SoundEvent ENTITY_COD_AMBIENT = SoundEventImpl.get("minecraft:entity.cod.ambient"); SoundEvent ENTITY_COD_DEATH = SoundEventImpl.get("minecraft:entity.cod.death"); @@ -801,6 +859,8 @@ interface SoundEvents { SoundEvent ENTITY_DONKEY_HURT = SoundEventImpl.get("minecraft:entity.donkey.hurt"); + SoundEvent ENTITY_DONKEY_JUMP = SoundEventImpl.get("minecraft:entity.donkey.jump"); + SoundEvent BLOCK_DRIPSTONE_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.dripstone_block.break"); SoundEvent BLOCK_DRIPSTONE_BLOCK_STEP = SoundEventImpl.get("minecraft:block.dripstone_block.step"); @@ -1261,6 +1321,16 @@ interface SoundEvents { SoundEvent BLOCK_HANGING_SIGN_PLACE = SoundEventImpl.get("minecraft:block.hanging_sign.place"); + SoundEvent BLOCK_HEAVY_CORE_BREAK = SoundEventImpl.get("minecraft:block.heavy_core.break"); + + SoundEvent BLOCK_HEAVY_CORE_FALL = SoundEventImpl.get("minecraft:block.heavy_core.fall"); + + SoundEvent BLOCK_HEAVY_CORE_HIT = SoundEventImpl.get("minecraft:block.heavy_core.hit"); + + SoundEvent BLOCK_HEAVY_CORE_PLACE = SoundEventImpl.get("minecraft:block.heavy_core.place"); + + SoundEvent BLOCK_HEAVY_CORE_STEP = SoundEventImpl.get("minecraft:block.heavy_core.step"); + SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_STEP = SoundEventImpl.get("minecraft:block.nether_wood_hanging_sign.step"); SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_BREAK = SoundEventImpl.get("minecraft:block.nether_wood_hanging_sign.break"); @@ -1293,10 +1363,20 @@ interface SoundEvents { SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_MOB = SoundEventImpl.get("minecraft:block.trial_spawner.spawn_mob"); + SoundEvent BLOCK_TRIAL_SPAWNER_ABOUT_TO_SPAWN_ITEM = SoundEventImpl.get("minecraft:block.trial_spawner.about_to_spawn_item"); + + SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_ITEM = SoundEventImpl.get("minecraft:block.trial_spawner.spawn_item"); + + SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_ITEM_BEGIN = SoundEventImpl.get("minecraft:block.trial_spawner.spawn_item_begin"); + SoundEvent BLOCK_TRIAL_SPAWNER_DETECT_PLAYER = SoundEventImpl.get("minecraft:block.trial_spawner.detect_player"); + SoundEvent BLOCK_TRIAL_SPAWNER_CHARGE_ACTIVATE = SoundEventImpl.get("minecraft:block.trial_spawner.charge_activate"); + SoundEvent BLOCK_TRIAL_SPAWNER_AMBIENT = SoundEventImpl.get("minecraft:block.trial_spawner.ambient"); + SoundEvent BLOCK_TRIAL_SPAWNER_AMBIENT_CHARGED = SoundEventImpl.get("minecraft:block.trial_spawner.ambient_charged"); + SoundEvent BLOCK_TRIAL_SPAWNER_OPEN_SHUTTER = SoundEventImpl.get("minecraft:block.trial_spawner.open_shutter"); SoundEvent BLOCK_TRIAL_SPAWNER_CLOSE_SHUTTER = SoundEventImpl.get("minecraft:block.trial_spawner.close_shutter"); @@ -1525,6 +1605,12 @@ interface SoundEvents { SoundEvent ITEM_LODESTONE_COMPASS_LOCK = SoundEventImpl.get("minecraft:item.lodestone_compass.lock"); + SoundEvent ITEM_MACE_SMASH_AIR = SoundEventImpl.get("minecraft:item.mace.smash_air"); + + SoundEvent ITEM_MACE_SMASH_GROUND = SoundEventImpl.get("minecraft:item.mace.smash_ground"); + + SoundEvent ITEM_MACE_SMASH_GROUND_HEAVY = SoundEventImpl.get("minecraft:item.mace.smash_ground_heavy"); + SoundEvent ENTITY_MAGMA_CUBE_DEATH = SoundEventImpl.get("minecraft:entity.magma_cube.death"); SoundEvent ENTITY_MAGMA_CUBE_HURT = SoundEventImpl.get("minecraft:entity.magma_cube.hurt"); @@ -1653,6 +1739,8 @@ interface SoundEvents { SoundEvent ENTITY_MULE_HURT = SoundEventImpl.get("minecraft:entity.mule.hurt"); + SoundEvent ENTITY_MULE_JUMP = SoundEventImpl.get("minecraft:entity.mule.jump"); + SoundEvent MUSIC_CREATIVE = SoundEventImpl.get("minecraft:music.creative"); SoundEvent MUSIC_CREDITS = SoundEventImpl.get("minecraft:music.credits"); @@ -1933,6 +2021,8 @@ interface SoundEvents { SoundEvent ENTITY_OCELOT_DEATH = SoundEventImpl.get("minecraft:entity.ocelot.death"); + SoundEvent ITEM_OMINOUS_BOTTLE_DISPOSE = SoundEventImpl.get("minecraft:item.ominous_bottle.dispose"); + SoundEvent ENTITY_PAINTING_BREAK = SoundEventImpl.get("minecraft:entity.painting.break"); SoundEvent ENTITY_PAINTING_PLACE = SoundEventImpl.get("minecraft:entity.painting.place"); @@ -1971,6 +2061,8 @@ interface SoundEvents { SoundEvent ENTITY_PARROT_IMITATE_BLAZE = SoundEventImpl.get("minecraft:entity.parrot.imitate.blaze"); + SoundEvent ENTITY_PARROT_IMITATE_BOGGED = SoundEventImpl.get("minecraft:entity.parrot.imitate.bogged"); + SoundEvent ENTITY_PARROT_IMITATE_BREEZE = SoundEventImpl.get("minecraft:entity.parrot.imitate.breeze"); SoundEvent ENTITY_PARROT_IMITATE_CREEPER = SoundEventImpl.get("minecraft:entity.parrot.imitate.creeper"); @@ -2779,6 +2871,32 @@ interface SoundEvents { SoundEvent UI_TOAST_OUT = SoundEventImpl.get("minecraft:ui.toast.out"); + SoundEvent BLOCK_VAULT_ACTIVATE = SoundEventImpl.get("minecraft:block.vault.activate"); + + SoundEvent BLOCK_VAULT_AMBIENT = SoundEventImpl.get("minecraft:block.vault.ambient"); + + SoundEvent BLOCK_VAULT_BREAK = SoundEventImpl.get("minecraft:block.vault.break"); + + SoundEvent BLOCK_VAULT_CLOSE_SHUTTER = SoundEventImpl.get("minecraft:block.vault.close_shutter"); + + SoundEvent BLOCK_VAULT_DEACTIVATE = SoundEventImpl.get("minecraft:block.vault.deactivate"); + + SoundEvent BLOCK_VAULT_EJECT_ITEM = SoundEventImpl.get("minecraft:block.vault.eject_item"); + + SoundEvent BLOCK_VAULT_FALL = SoundEventImpl.get("minecraft:block.vault.fall"); + + SoundEvent BLOCK_VAULT_HIT = SoundEventImpl.get("minecraft:block.vault.hit"); + + SoundEvent BLOCK_VAULT_INSERT_ITEM = SoundEventImpl.get("minecraft:block.vault.insert_item"); + + SoundEvent BLOCK_VAULT_INSERT_ITEM_FAIL = SoundEventImpl.get("minecraft:block.vault.insert_item_fail"); + + SoundEvent BLOCK_VAULT_OPEN_SHUTTER = SoundEventImpl.get("minecraft:block.vault.open_shutter"); + + SoundEvent BLOCK_VAULT_PLACE = SoundEventImpl.get("minecraft:block.vault.place"); + + SoundEvent BLOCK_VAULT_STEP = SoundEventImpl.get("minecraft:block.vault.step"); + SoundEvent ENTITY_VEX_AMBIENT = SoundEventImpl.get("minecraft:entity.vex.ambient"); SoundEvent ENTITY_VEX_CHARGE = SoundEventImpl.get("minecraft:entity.vex.charge"); @@ -2929,6 +3047,8 @@ interface SoundEvents { SoundEvent BLOCK_WET_SPONGE_BREAK = SoundEventImpl.get("minecraft:block.wet_sponge.break"); + SoundEvent BLOCK_WET_SPONGE_DRIES = SoundEventImpl.get("minecraft:block.wet_sponge.dries"); + SoundEvent BLOCK_WET_SPONGE_FALL = SoundEventImpl.get("minecraft:block.wet_sponge.fall"); SoundEvent BLOCK_WET_SPONGE_HIT = SoundEventImpl.get("minecraft:block.wet_sponge.hit"); @@ -2937,7 +3057,9 @@ interface SoundEvents { SoundEvent BLOCK_WET_SPONGE_STEP = SoundEventImpl.get("minecraft:block.wet_sponge.step"); - SoundEvent ENTITY_GENERIC_WIND_BURST = SoundEventImpl.get("minecraft:entity.generic.wind_burst"); + SoundEvent ENTITY_WIND_CHARGE_WIND_BURST = SoundEventImpl.get("minecraft:entity.wind_charge.wind_burst"); + + SoundEvent ENTITY_WIND_CHARGE_THROW = SoundEventImpl.get("minecraft:entity.wind_charge.throw"); SoundEvent ENTITY_WITCH_AMBIENT = SoundEventImpl.get("minecraft:entity.witch.ambient"); @@ -2971,6 +3093,14 @@ interface SoundEvents { SoundEvent ENTITY_WITHER_SPAWN = SoundEventImpl.get("minecraft:entity.wither.spawn"); + SoundEvent ITEM_WOLF_ARMOR_BREAK = SoundEventImpl.get("minecraft:item.wolf_armor.break"); + + SoundEvent ITEM_WOLF_ARMOR_CRACK = SoundEventImpl.get("minecraft:item.wolf_armor.crack"); + + SoundEvent ITEM_WOLF_ARMOR_DAMAGE = SoundEventImpl.get("minecraft:item.wolf_armor.damage"); + + SoundEvent ITEM_WOLF_ARMOR_REPAIR = SoundEventImpl.get("minecraft:item.wolf_armor.repair"); + SoundEvent ENTITY_WOLF_AMBIENT = SoundEventImpl.get("minecraft:entity.wolf.ambient"); SoundEvent ENTITY_WOLF_DEATH = SoundEventImpl.get("minecraft:entity.wolf.death"); @@ -3082,4 +3212,10 @@ interface SoundEvents { SoundEvent ENTITY_ZOMBIE_VILLAGER_HURT = SoundEventImpl.get("minecraft:entity.zombie_villager.hurt"); SoundEvent ENTITY_ZOMBIE_VILLAGER_STEP = SoundEventImpl.get("minecraft:entity.zombie_villager.step"); + + SoundEvent EVENT_MOB_EFFECT_BAD_OMEN = SoundEventImpl.get("minecraft:event.mob_effect.bad_omen"); + + SoundEvent EVENT_MOB_EFFECT_TRIAL_OMEN = SoundEventImpl.get("minecraft:event.mob_effect.trial_omen"); + + SoundEvent EVENT_MOB_EFFECT_RAID_OMEN = SoundEventImpl.get("minecraft:event.mob_effect.raid_omen"); } diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index d8ffa9602..98eae2cfb 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -44,8 +44,8 @@ public final class MinecraftServer { public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class); - public static final String VERSION_NAME = "1.20.4"; - public static final int PROTOCOL_VERSION = 765; + public static final String VERSION_NAME = "24w14a"; + public static final int PROTOCOL_VERSION = 1073742008; // Threads public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark"; diff --git a/src/main/java/net/minestom/server/attribute/Attribute.java b/src/main/java/net/minestom/server/attribute/Attribute.java deleted file mode 100644 index ed97a493c..000000000 --- a/src/main/java/net/minestom/server/attribute/Attribute.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.minestom.server.attribute; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Represents a {@link net.minestom.server.entity.LivingEntity living entity} attribute. - */ -public record Attribute(String key, float defaultValue, float maxValue) { - private static final Map ATTRIBUTES = new ConcurrentHashMap<>(); - - public static final Attribute MAX_HEALTH = (new Attribute("generic.max_health", 20, 1024)).register(); - public static final Attribute FOLLOW_RANGE = (new Attribute("generic.follow_range", 32, 2048)).register(); - public static final Attribute KNOCKBACK_RESISTANCE = (new Attribute("generic.knockback_resistance", 0, 1)).register(); - public static final Attribute MOVEMENT_SPEED = (new Attribute("generic.movement_speed", 0.25f, 1024)).register(); - public static final Attribute ATTACK_DAMAGE = (new Attribute("generic.attack_damage", 2, 2048)).register(); - public static final Attribute ATTACK_SPEED = (new Attribute("generic.attack_speed", 4, 1024)).register(); - public static final Attribute FLYING_SPEED = (new Attribute("generic.flying_speed", 0.4f, 1024)).register(); - public static final Attribute ARMOR = (new Attribute("generic.armor", 0, 30)).register(); - public static final Attribute ARMOR_TOUGHNESS = (new Attribute("generic.armor_toughness", 0, 20)).register(); - public static final Attribute ATTACK_KNOCKBACK = (new Attribute("generic.attack_knockback", 0, 5)).register(); - public static final Attribute LUCK = (new Attribute("generic.luck", 0, 1024)).register(); - public static final Attribute HORSE_JUMP_STRENGTH = (new Attribute("horse.jump_strength", 0.7f, 2)).register(); - public static final Attribute ZOMBIE_SPAWN_REINFORCEMENTS = (new Attribute("zombie.spawn_reinforcements", 0, 1)).register(); - - public Attribute { - if (defaultValue > maxValue) { - throw new IllegalArgumentException("Default value cannot be greater than the maximum allowed"); - } - } - - /** - * Register this attribute. - * - * @return this attribute - * @see #fromKey(String) - * @see #values() - */ - public @NotNull Attribute register() { - ATTRIBUTES.put(key, this); - return this; - } - - /** - * Retrieves an attribute by its key. - * - * @param key the key of the attribute - * @return the attribute for the key or null if not any - */ - public static @Nullable Attribute fromKey(@NotNull String key) { - return ATTRIBUTES.get(key); - } - - /** - * Retrieves all registered attributes. - * - * @return an array containing all registered attributes - */ - public static @NotNull Collection<@NotNull Attribute> values() { - return ATTRIBUTES.values(); - } -} diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 368f91882..8e3c69c39 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -1,11 +1,11 @@ package net.minestom.server.entity; import net.kyori.adventure.sound.Sound.Source; -import net.minestom.server.attribute.Attribute; -import net.minestom.server.attribute.AttributeInstance; import net.minestom.server.collision.BoundingBox; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.entity.attribute.AttributeInstance; import net.minestom.server.entity.damage.Damage; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.metadata.LivingEntityMeta; @@ -22,7 +22,7 @@ import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.server.LazyPacket; import net.minestom.server.network.packet.server.play.CollectItemPacket; import net.minestom.server.network.packet.server.play.EntityAnimationPacket; -import net.minestom.server.network.packet.server.play.EntityPropertiesPacket; +import net.minestom.server.network.packet.server.play.EntityAttributesPacket; import net.minestom.server.network.packet.server.play.SoundEffectPacket; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.scoreboard.Team; @@ -419,21 +419,21 @@ public class LivingEntity extends Entity implements EquipmentHandler { } /** - * Gets the entity max health from {@link #getAttributeValue(Attribute)} {@link Attribute#MAX_HEALTH}. + * Gets the entity max health from {@link #getAttributeValue(Attribute)} {@link Attribute#GENERIC_MAX_HEALTH}. * * @return the entity max health */ public float getMaxHealth() { - return getAttributeValue(Attribute.MAX_HEALTH); + return (float) getAttributeValue(Attribute.GENERIC_MAX_HEALTH); } /** * Sets the heal of the entity as its max health. *

- * Retrieved from {@link #getAttributeValue(Attribute)} with the attribute {@link Attribute#MAX_HEALTH}. + * Retrieved from {@link #getAttributeValue(Attribute)} with the attribute {@link Attribute#GENERIC_MAX_HEALTH}. */ public void heal() { - setHealth(getAttributeValue(Attribute.MAX_HEALTH)); + setHealth((float) getAttributeValue(Attribute.GENERIC_MAX_HEALTH)); } /** @@ -443,7 +443,7 @@ public class LivingEntity extends Entity implements EquipmentHandler { * @return the attribute instance */ public @NotNull AttributeInstance getAttribute(@NotNull Attribute attribute) { - return attributeModifiers.computeIfAbsent(attribute.key(), + return attributeModifiers.computeIfAbsent(attribute.name(), s -> new AttributeInstance(attribute, this::onAttributeChanged)); } @@ -459,7 +459,7 @@ public class LivingEntity extends Entity implements EquipmentHandler { // connection null during Player initialization (due to #super call) self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY; } - EntityPropertiesPacket propertiesPacket = new EntityPropertiesPacket(getEntityId(), List.of(attributeInstance)); + EntityAttributesPacket propertiesPacket = new EntityAttributesPacket(getEntityId(), List.of(attributeInstance)); if (self) { sendPacketToViewersAndSelf(propertiesPacket); } else { @@ -473,8 +473,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { * @param attribute the attribute value to get * @return the attribute value */ - public float getAttributeValue(@NotNull Attribute attribute) { - AttributeInstance instance = attributeModifiers.get(attribute.key()); + public double getAttributeValue(@NotNull Attribute attribute) { + AttributeInstance instance = attributeModifiers.get(attribute.name()); return (instance != null) ? instance.getValue() : attribute.defaultValue(); } @@ -566,12 +566,12 @@ public class LivingEntity extends Entity implements EquipmentHandler { } /** - * Gets an {@link EntityPropertiesPacket} for this entity with all of its attributes values. + * Gets an {@link EntityAttributesPacket} for this entity with all of its attributes values. * - * @return an {@link EntityPropertiesPacket} linked to this entity + * @return an {@link EntityAttributesPacket} linked to this entity */ - protected @NotNull EntityPropertiesPacket getPropertiesPacket() { - return new EntityPropertiesPacket(getEntityId(), List.copyOf(attributeModifiers.values())); + protected @NotNull EntityAttributesPacket getPropertiesPacket() { + return new EntityAttributesPacket(getEntityId(), List.copyOf(attributeModifiers.values())); } /** @@ -667,7 +667,7 @@ public class LivingEntity extends Entity implements EquipmentHandler { */ @Override public void takeKnockback(float strength, final double x, final double z) { - strength *= 1 - getAttributeValue(Attribute.KNOCKBACK_RESISTANCE); + strength *= (float) (1 - getAttributeValue(Attribute.GENERIC_KNOCKBACK_RESISTANCE)); super.takeKnockback(strength, x, z); } } diff --git a/src/main/java/net/minestom/server/entity/Metadata.java b/src/main/java/net/minestom/server/entity/Metadata.java index f208bf2db..791e8bfaf 100644 --- a/src/main/java/net/minestom/server/entity/Metadata.java +++ b/src/main/java/net/minestom/server/entity/Metadata.java @@ -3,6 +3,7 @@ package net.minestom.server.entity; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; +import net.minestom.server.entity.metadata.animal.ArmadilloMeta; import net.minestom.server.entity.metadata.animal.FrogMeta; import net.minestom.server.entity.metadata.animal.SnifferMeta; import net.minestom.server.entity.metadata.animal.tameable.CatMeta; @@ -19,10 +20,8 @@ import org.jetbrains.annotations.UnknownNullability; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; public final class Metadata { public static Entry Byte(byte value) { @@ -50,11 +49,11 @@ public final class Metadata { } public static Entry OptChat(@Nullable Component value) { - return new MetadataImpl.EntryImpl<>(TYPE_OPTCHAT, value, NetworkBuffer.OPT_CHAT); + return new MetadataImpl.EntryImpl<>(TYPE_OPT_CHAT, value, NetworkBuffer.OPT_CHAT); } - public static Entry Slot(@NotNull ItemStack value) { - return new MetadataImpl.EntryImpl<>(TYPE_SLOT, value, NetworkBuffer.ITEM); + public static Entry ItemStack(@NotNull ItemStack value) { + return new MetadataImpl.EntryImpl<>(TYPE_ITEM_STACK, value, NetworkBuffer.ITEM); } public static Entry Boolean(boolean value) { @@ -65,12 +64,12 @@ public final class Metadata { return new MetadataImpl.EntryImpl<>(TYPE_ROTATION, value, NetworkBuffer.VECTOR3); } - public static Entry Position(@NotNull Point value) { - return new MetadataImpl.EntryImpl<>(TYPE_POSITION, value, NetworkBuffer.BLOCK_POSITION); + public static Entry BlockPosition(@NotNull Point value) { + return new MetadataImpl.EntryImpl<>(TYPE_BLOCK_POSITION, value, NetworkBuffer.BLOCK_POSITION); } - public static Entry OptPosition(@Nullable Point value) { - return new MetadataImpl.EntryImpl<>(TYPE_OPTPOSITION, value, NetworkBuffer.OPT_BLOCK_POSITION); + public static Entry OptBlockPosition(@Nullable Point value) { + return new MetadataImpl.EntryImpl<>(TYPE_OPT_BLOCK_POSITION, value, NetworkBuffer.OPT_BLOCK_POSITION); } public static Entry Direction(@NotNull Direction value) { @@ -78,7 +77,7 @@ public final class Metadata { } public static Entry OptUUID(@Nullable UUID value) { - return new MetadataImpl.EntryImpl<>(TYPE_OPTUUID, value, NetworkBuffer.OPT_UUID); + return new MetadataImpl.EntryImpl<>(TYPE_OPT_UUID, value, NetworkBuffer.OPT_UUID); } public static Entry BlockState(@Nullable Integer value) { @@ -86,22 +85,38 @@ public final class Metadata { } public static Entry OptBlockState(@Nullable Integer value) { - return new MetadataImpl.EntryImpl<>(TYPE_OPTBLOCKSTATE, value, NetworkBuffer.OPT_BLOCK_STATE); + return new MetadataImpl.EntryImpl<>(TYPE_OPT_BLOCKSTATE, value, NetworkBuffer.OPT_BLOCK_STATE); } public static Entry NBT(@NotNull BinaryTag nbt) { return new MetadataImpl.EntryImpl<>(TYPE_NBT, nbt, NetworkBuffer.NBT); } - public static Entry VillagerData(int villagerType, - int villagerProfession, - int level) { + public static Entry Particle(@NotNull Particle particle) { + return new MetadataImpl.EntryImpl<>(TYPE_PARTICLE, particle, NetworkBuffer.PARTICLE); + } + + public static Entry> ParticleList(@NotNull List particles) { + return new MetadataImpl.EntryImpl<>(TYPE_PARTICLE_LIST, particles, new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, List value) { + buffer.writeCollection(NetworkBuffer.PARTICLE, value); + } + + @Override + public List read(@NotNull NetworkBuffer buffer) { + return buffer.readCollection(NetworkBuffer.PARTICLE, Integer.MAX_VALUE); + } + }); + } + + public static Entry VillagerData(int villagerType, int villagerProfession, int level) { return new MetadataImpl.EntryImpl<>(TYPE_VILLAGERDATA, new int[]{villagerType, villagerProfession, level}, NetworkBuffer.VILLAGER_DATA); } public static Entry OptVarInt(@Nullable Integer value) { - return new MetadataImpl.EntryImpl<>(TYPE_OPTVARINT, value, new NetworkBuffer.Type<>() { + return new MetadataImpl.EntryImpl<>(TYPE_OPT_VARINT, value, new NetworkBuffer.Type<>() { @Override public void write(@NotNull NetworkBuffer buffer, Integer value) { buffer.write(NetworkBuffer.VAR_INT, value == null ? 0 : value + 1); @@ -123,18 +138,24 @@ public final class Metadata { return new MetadataImpl.EntryImpl<>(TYPE_CAT_VARIANT, value, NetworkBuffer.CAT_VARIANT); } + // WOLF VARIANT + public static Entry FrogVariant(@NotNull FrogMeta.Variant value) { return new MetadataImpl.EntryImpl<>(TYPE_FROG_VARIANT, value, NetworkBuffer.FROG_VARIANT); } public static Entry PaintingVariant(@NotNull PaintingMeta.Variant value) { - return new MetadataImpl.EntryImpl<>(TYPE_PAINTINGVARIANT, value, NetworkBuffer.PAINTING_VARIANT); + return new MetadataImpl.EntryImpl<>(TYPE_PAINTING_VARIANT, value, NetworkBuffer.PAINTING_VARIANT); } public static Entry SnifferState(@NotNull SnifferMeta.State value) { return new MetadataImpl.EntryImpl<>(TYPE_SNIFFER_STATE, value, NetworkBuffer.SNIFFER_STATE); } + public static Entry ArmadilloState(@NotNull ArmadilloMeta.State value) { + return new MetadataImpl.EntryImpl<>(TYPE_ARMADILLO_STATE, value, NetworkBuffer.ARMADILLO_STATE); + } + public static Entry Vector3(@NotNull Point value) { return new MetadataImpl.EntryImpl<>(TYPE_VECTOR3, value, NetworkBuffer.VECTOR3); } @@ -143,41 +164,46 @@ public final class Metadata { return new MetadataImpl.EntryImpl<>(TYPE_QUATERNION, value, NetworkBuffer.QUATERNION); } - public static Entry Particle(@NotNull Particle particle) { - return new MetadataImpl.EntryImpl<>(TYPE_PARTICLE, particle, NetworkBuffer.PARTICLE); - } + private static final AtomicInteger NEXT_ID = new AtomicInteger(0); - public static final byte TYPE_BYTE = 0; - public static final byte TYPE_VARINT = 1; - public static final byte TYPE_LONG = 2; - public static final byte TYPE_FLOAT = 3; - public static final byte TYPE_STRING = 4; - public static final byte TYPE_CHAT = 5; - public static final byte TYPE_OPTCHAT = 6; - public static final byte TYPE_SLOT = 7; - public static final byte TYPE_BOOLEAN = 8; - public static final byte TYPE_ROTATION = 9; - public static final byte TYPE_POSITION = 10; - public static final byte TYPE_OPTPOSITION = 11; - public static final byte TYPE_DIRECTION = 12; - public static final byte TYPE_OPTUUID = 13; - public static final byte TYPE_BLOCKSTATE = 14; - public static final byte TYPE_OPTBLOCKSTATE = 15; - public static final byte TYPE_NBT = 16; - public static final byte TYPE_PARTICLE = 17; - public static final byte TYPE_VILLAGERDATA = 18; - public static final byte TYPE_OPTVARINT = 19; - public static final byte TYPE_POSE = 20; - public static final byte TYPE_CAT_VARIANT = 21; - public static final byte TYPE_FROG_VARIANT = 22; - public static final byte TYPE_OPTGLOBALPOS = 23; - public static final byte TYPE_PAINTINGVARIANT = 24; - public static final byte TYPE_SNIFFER_STATE = 25; - public static final byte TYPE_VECTOR3 = 26; - public static final byte TYPE_QUATERNION = 27; + public static final byte TYPE_BYTE = nextId(); + public static final byte TYPE_VARINT = nextId(); + public static final byte TYPE_LONG = nextId(); + public static final byte TYPE_FLOAT = nextId(); + public static final byte TYPE_STRING = nextId(); + public static final byte TYPE_CHAT = nextId(); + public static final byte TYPE_OPT_CHAT = nextId(); + public static final byte TYPE_ITEM_STACK = nextId(); + public static final byte TYPE_BOOLEAN = nextId(); + public static final byte TYPE_ROTATION = nextId(); + public static final byte TYPE_BLOCK_POSITION = nextId(); + public static final byte TYPE_OPT_BLOCK_POSITION = nextId(); + public static final byte TYPE_DIRECTION = nextId(); + public static final byte TYPE_OPT_UUID = nextId(); + public static final byte TYPE_BLOCKSTATE = nextId(); + public static final byte TYPE_OPT_BLOCKSTATE = nextId(); + public static final byte TYPE_NBT = nextId(); + public static final byte TYPE_PARTICLE = nextId(); + public static final byte TYPE_PARTICLE_LIST = nextId(); + public static final byte TYPE_VILLAGERDATA = nextId(); + public static final byte TYPE_OPT_VARINT = nextId(); + public static final byte TYPE_POSE = nextId(); + public static final byte TYPE_CAT_VARIANT = nextId(); + public static final byte TYPE_WOLF_VARIANT = nextId(); + public static final byte TYPE_FROG_VARIANT = nextId(); + public static final byte TYPE_OPT_GLOBAL_POSITION = nextId(); // Unused by protocol it seems + public static final byte TYPE_PAINTING_VARIANT = nextId(); + public static final byte TYPE_SNIFFER_STATE = nextId(); + public static final byte TYPE_ARMADILLO_STATE = nextId(); + public static final byte TYPE_VECTOR3 = nextId(); + public static final byte TYPE_QUATERNION = nextId(); // Impl Note: Adding an entry here requires that a default value entry is added in MetadataImpl.EMPTY_VALUES + private static byte nextId() { + return (byte) NEXT_ID.getAndIncrement(); + } + private static final VarHandle NOTIFIED_CHANGES; static { diff --git a/src/main/java/net/minestom/server/entity/MetadataImpl.java b/src/main/java/net/minestom/server/entity/MetadataImpl.java index 8a58a9d4e..ba46d47d3 100644 --- a/src/main/java/net/minestom/server/entity/MetadataImpl.java +++ b/src/main/java/net/minestom/server/entity/MetadataImpl.java @@ -3,6 +3,7 @@ package net.minestom.server.entity; import net.kyori.adventure.nbt.EndBinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.metadata.animal.ArmadilloMeta; import net.minestom.server.entity.metadata.animal.FrogMeta; import net.minestom.server.entity.metadata.animal.SnifferMeta; import net.minestom.server.entity.metadata.animal.tameable.CatMeta; @@ -10,11 +11,14 @@ import net.minestom.server.entity.metadata.other.PaintingMeta; import net.minestom.server.instance.block.Block; import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.particle.Particle; import net.minestom.server.utils.Direction; import net.minestom.server.utils.collection.ObjectArray; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; +import java.util.List; + import static net.minestom.server.entity.Metadata.*; import static net.minestom.server.network.NetworkBuffer.VAR_INT; @@ -28,26 +32,29 @@ final class MetadataImpl { EMPTY_VALUES.set(TYPE_FLOAT, Float(0f)); EMPTY_VALUES.set(TYPE_STRING, String("")); EMPTY_VALUES.set(TYPE_CHAT, Chat(Component.empty())); - EMPTY_VALUES.set(TYPE_OPTCHAT, OptChat(null)); - EMPTY_VALUES.set(TYPE_SLOT, Slot(ItemStack.AIR)); + EMPTY_VALUES.set(TYPE_OPT_CHAT, OptChat(null)); + EMPTY_VALUES.set(TYPE_ITEM_STACK, ItemStack(ItemStack.AIR)); EMPTY_VALUES.set(TYPE_BOOLEAN, Boolean(false)); EMPTY_VALUES.set(TYPE_ROTATION, Rotation(Vec.ZERO)); - EMPTY_VALUES.set(TYPE_POSITION, Position(Vec.ZERO)); - EMPTY_VALUES.set(TYPE_OPTPOSITION, OptPosition(null)); + EMPTY_VALUES.set(TYPE_BLOCK_POSITION, BlockPosition(Vec.ZERO)); + EMPTY_VALUES.set(TYPE_OPT_BLOCK_POSITION, OptBlockPosition(null)); EMPTY_VALUES.set(TYPE_DIRECTION, Direction(Direction.DOWN)); - EMPTY_VALUES.set(TYPE_OPTUUID, OptUUID(null)); + EMPTY_VALUES.set(TYPE_OPT_UUID, OptUUID(null)); EMPTY_VALUES.set(TYPE_BLOCKSTATE, BlockState(Block.AIR.id())); - EMPTY_VALUES.set(TYPE_OPTBLOCKSTATE, OptBlockState(null)); + EMPTY_VALUES.set(TYPE_OPT_BLOCKSTATE, OptBlockState(null)); EMPTY_VALUES.set(TYPE_NBT, NBT(EndBinaryTag.endBinaryTag())); - //EMPTY_VALUES.set(TYPE_PARTICLE -> throw new UnsupportedOperationException(); + EMPTY_VALUES.set(TYPE_PARTICLE, Particle(Particle.DUST)); + EMPTY_VALUES.set(TYPE_PARTICLE_LIST, ParticleList(List.of())); EMPTY_VALUES.set(TYPE_VILLAGERDATA, VillagerData(0, 0, 0)); - EMPTY_VALUES.set(TYPE_OPTVARINT, OptVarInt(null)); + EMPTY_VALUES.set(TYPE_OPT_VARINT, OptVarInt(null)); EMPTY_VALUES.set(TYPE_POSE, Pose(Entity.Pose.STANDING)); EMPTY_VALUES.set(TYPE_CAT_VARIANT, CatVariant(CatMeta.Variant.TABBY)); + // WolfVariant EMPTY_VALUES.set(TYPE_FROG_VARIANT, FrogVariant(FrogMeta.Variant.TEMPERATE)); // OptGlobalPos - EMPTY_VALUES.set(TYPE_PAINTINGVARIANT, PaintingVariant(PaintingMeta.Variant.KEBAB)); + EMPTY_VALUES.set(TYPE_PAINTING_VARIANT, PaintingVariant(PaintingMeta.Variant.KEBAB)); EMPTY_VALUES.set(TYPE_SNIFFER_STATE, SnifferState(SnifferMeta.State.IDLING)); + EMPTY_VALUES.set(TYPE_ARMADILLO_STATE, ArmadilloState(ArmadilloMeta.State.IDLE)); EMPTY_VALUES.set(TYPE_VECTOR3, Vector3(Vec.ZERO)); EMPTY_VALUES.set(TYPE_QUATERNION, Quaternion(new float[]{0, 0, 0, 0})); EMPTY_VALUES.trim(); diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index bd2113b50..7e0f7cc1b 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -25,13 +25,13 @@ import net.minestom.server.advancements.AdvancementTab; import net.minestom.server.adventure.AdventurePacketConvertor; import net.minestom.server.adventure.Localizable; import net.minestom.server.adventure.audience.Audiences; -import net.minestom.server.attribute.Attribute; import net.minestom.server.collision.BoundingBox; import net.minestom.server.command.CommandSender; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.effects.Effects; +import net.minestom.server.entity.attribute.Attribute; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.metadata.LivingEntityMeta; import net.minestom.server.entity.metadata.PlayerMeta; @@ -251,7 +251,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, this.gameMode = GameMode.SURVIVAL; this.dimensionType = DimensionType.OVERWORLD; // Default dimension this.levelFlat = true; - getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.1f); + getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0.1); // FakePlayer init its connection there playerConnectionInit(); @@ -293,8 +293,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, final JoinGamePacket joinGamePacket = new JoinGamePacket( getEntityId(), this.hardcore, List.of(), 0, ServerFlag.CHUNK_VIEW_DISTANCE, ServerFlag.CHUNK_VIEW_DISTANCE, - false, true, false, dimensionType.toString(), spawnInstance.getDimensionName(), - 0, gameMode, null, false, levelFlat, deathLocation, portalCooldown); + false, true, false, dimensionType.getId(), spawnInstance.getDimensionName(), + 0, gameMode, null, false, levelFlat, deathLocation, portalCooldown, true); sendPacket(joinGamePacket); // Difficulty @@ -523,7 +523,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, setOnFire(false); refreshHealth(); - sendPacket(new RespawnPacket(getDimensionType().toString(), instance.getDimensionName(), + sendPacket(new RespawnPacket(getDimensionType().getId(), instance.getDimensionName(), 0, gameMode, gameMode, false, levelFlat, deathLocation, portalCooldown, RespawnPacket.COPY_ALL)); refreshClientStateAfterRespawn(); @@ -1202,7 +1202,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, final PlayerInfoRemovePacket removePlayerPacket = getRemovePlayerToList(); final PlayerInfoUpdatePacket addPlayerPacket = getAddPlayerToList(); - RespawnPacket respawnPacket = new RespawnPacket(getDimensionType().toString(), instance.getDimensionName(), + RespawnPacket respawnPacket = new RespawnPacket(getDimensionType().getId(), instance.getDimensionName(), 0, gameMode, gameMode, false, levelFlat, deathLocation, portalCooldown, RespawnPacket.COPY_ALL); sendPacket(removePlayerPacket); @@ -1653,7 +1653,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, Check.argCondition(instance.getDimensionName().equals(dimensionName), "The dimension needs to be different than the current one!"); this.dimensionType = dimensionType; - sendPacket(new RespawnPacket(dimensionType.toString(), dimensionName, + sendPacket(new RespawnPacket(dimensionType.getId(), dimensionName, 0, gameMode, gameMode, false, levelFlat, deathLocation, portalCooldown, RespawnPacket.COPY_ALL)); refreshClientStateAfterRespawn(); diff --git a/src/main/java/net/minestom/server/entity/attribute/Attribute.java b/src/main/java/net/minestom/server/entity/attribute/Attribute.java new file mode 100644 index 000000000..51e5e4d37 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/attribute/Attribute.java @@ -0,0 +1,59 @@ +package net.minestom.server.entity.attribute; + +import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public sealed interface Attribute extends StaticProtocolObject, Attributes permits AttributeImpl { + + @Contract(pure = true) + @NotNull Registry.AttributeEntry registry(); + + @Override + default @NotNull NamespaceID namespace() { + return registry().namespace(); + } + + @Override + default int id() { + return registry().id(); + } + + default double defaultValue() { + return registry().defaultValue(); + } + + default double minValue() { + return registry().minValue(); + } + + default double maxValue() { + return registry().maxValue(); + } + + default boolean isSynced() { + return registry().clientSync(); + } + + static @NotNull Collection<@NotNull Attribute> values() { + return AttributeImpl.values(); + } + + static @Nullable Attribute fromNamespaceId(@NotNull String namespaceID) { + return AttributeImpl.getSafe(namespaceID); + } + + static @Nullable Attribute fromNamespaceId(@NotNull NamespaceID namespaceID) { + return fromNamespaceId(namespaceID.asString()); + } + + static @Nullable Attribute fromId(int id) { + return AttributeImpl.getId(id); + } + +} diff --git a/src/main/java/net/minestom/server/entity/attribute/AttributeImpl.java b/src/main/java/net/minestom/server/entity/attribute/AttributeImpl.java new file mode 100644 index 000000000..89ba36f58 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/attribute/AttributeImpl.java @@ -0,0 +1,32 @@ +package net.minestom.server.entity.attribute; + +import net.minestom.server.registry.Registry; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +record AttributeImpl(@NotNull Registry.AttributeEntry registry) implements Attribute { + private static final Registry.Container CONTAINER = Registry.createStaticContainer(Registry.Resource.ATTRIBUTES, + (namespace, properties) -> new AttributeImpl(Registry.attribute(namespace, properties))); + + static Attribute get(@NotNull String namespace) { + return CONTAINER.get(namespace); + } + + static Attribute getSafe(@NotNull String namespace) { + return CONTAINER.getSafe(namespace); + } + + static Attribute getId(int id) { + return CONTAINER.getId(id); + } + + static Collection values() { + return CONTAINER.values(); + } + + @Override + public String toString() { + return name(); + } +} diff --git a/src/main/java/net/minestom/server/attribute/AttributeInstance.java b/src/main/java/net/minestom/server/entity/attribute/AttributeInstance.java similarity index 91% rename from src/main/java/net/minestom/server/attribute/AttributeInstance.java rename to src/main/java/net/minestom/server/entity/attribute/AttributeInstance.java index e123792f6..c26416941 100644 --- a/src/main/java/net/minestom/server/attribute/AttributeInstance.java +++ b/src/main/java/net/minestom/server/entity/attribute/AttributeInstance.java @@ -1,4 +1,4 @@ -package net.minestom.server.attribute; +package net.minestom.server.entity.attribute; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -16,8 +16,8 @@ public final class AttributeInstance { private final Attribute attribute; private final Map modifiers = new HashMap<>(); private final Consumer propertyChangeListener; - private float baseValue; - private float cachedValue = 0.0f; + private double baseValue; + private double cachedValue = 0.0f; public AttributeInstance(@NotNull Attribute attribute, @Nullable Consumer listener) { this.attribute = attribute; @@ -39,9 +39,9 @@ public final class AttributeInstance { * The base value of this instance without modifiers * * @return the instance base value - * @see #setBaseValue(float) + * @see #setBaseValue(double) */ - public float getBaseValue() { + public double getBaseValue() { return baseValue; } @@ -51,7 +51,7 @@ public final class AttributeInstance { * @param baseValue the new base value * @see #getBaseValue() */ - public void setBaseValue(float baseValue) { + public void setBaseValue(double baseValue) { if (this.baseValue != baseValue) { this.baseValue = baseValue; refreshCachedValue(); @@ -104,7 +104,7 @@ public final class AttributeInstance { * * @return the attribute value */ - public float getValue() { + public double getValue() { return cachedValue; } @@ -113,13 +113,13 @@ public final class AttributeInstance { */ private void refreshCachedValue() { final Collection modifiers = getModifiers(); - float base = getBaseValue(); + double base = getBaseValue(); for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.ADDITION).toArray(AttributeModifier[]::new)) { base += modifier.getAmount(); } - float result = base; + double result = base; for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.MULTIPLY_BASE).toArray(AttributeModifier[]::new)) { result += (base * modifier.getAmount()); diff --git a/src/main/java/net/minestom/server/attribute/AttributeModifier.java b/src/main/java/net/minestom/server/entity/attribute/AttributeModifier.java similarity index 97% rename from src/main/java/net/minestom/server/attribute/AttributeModifier.java rename to src/main/java/net/minestom/server/entity/attribute/AttributeModifier.java index 143b5ef58..9e3f90c18 100644 --- a/src/main/java/net/minestom/server/attribute/AttributeModifier.java +++ b/src/main/java/net/minestom/server/entity/attribute/AttributeModifier.java @@ -1,4 +1,4 @@ -package net.minestom.server.attribute; +package net.minestom.server.entity.attribute; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/net/minestom/server/attribute/AttributeOperation.java b/src/main/java/net/minestom/server/entity/attribute/AttributeOperation.java similarity index 92% rename from src/main/java/net/minestom/server/attribute/AttributeOperation.java rename to src/main/java/net/minestom/server/entity/attribute/AttributeOperation.java index 33da291c7..acf09ab91 100644 --- a/src/main/java/net/minestom/server/attribute/AttributeOperation.java +++ b/src/main/java/net/minestom/server/entity/attribute/AttributeOperation.java @@ -1,4 +1,4 @@ -package net.minestom.server.attribute; +package net.minestom.server.entity.attribute; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java index 2d2a20cc2..11aaa611a 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageType.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -1,6 +1,6 @@ package net.minestom.server.entity.damage; -import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.registry.Registry; import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; @@ -36,7 +36,7 @@ public sealed interface DamageType extends StaticProtocolObject, DamageTypes per return registry().scaling(); } - CompoundBinaryTag asNBT(); + @NotNull RegistryDataPacket.Entry toRegistryEntry(); static @NotNull Collection<@NotNull DamageType> values() { return DamageTypeImpl.values(); @@ -54,7 +54,7 @@ public sealed interface DamageType extends StaticProtocolObject, DamageTypes per return DamageTypeImpl.getId(id); } - static CompoundBinaryTag getNBT() { - return DamageTypeImpl.getNBT(); + static @NotNull RegistryDataPacket registryDataPacket() { + return DamageTypeImpl.registryDataPacket(); } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java b/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java index b8480e656..4e8777338 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java @@ -1,8 +1,7 @@ package net.minestom.server.entity.damage; -import net.kyori.adventure.nbt.BinaryTagTypes; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.ListBinaryTag; +import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.registry.Registry; import org.jetbrains.annotations.NotNull; @@ -30,13 +29,14 @@ record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements Dama return CONTAINER.getId(id); } + @Override - public CompoundBinaryTag asNBT() { - return CompoundBinaryTag.builder() + public @NotNull RegistryDataPacket.Entry toRegistryEntry() { + return new RegistryDataPacket.Entry(name(), CompoundBinaryTag.builder() .putFloat("exhaustion", registry.exhaustion()) .putString("message_id", registry.messageId()) .putString("scaling", registry.scaling()) - .build(); + .build()); } static Collection values() { @@ -53,24 +53,15 @@ record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements Dama return id; } - private static CompoundBinaryTag lazyNbt = null; + private static RegistryDataPacket lazyRegistryDataPacket = null; - static CompoundBinaryTag getNBT() { - if (lazyNbt == null) { - var entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); - for (var damageType : values()) { - entries.add(CompoundBinaryTag.builder() - .putInt("id", damageType.id()) - .putString("name", damageType.name()) - .put("element", damageType.asNBT()) - .build()); - } - - lazyNbt = CompoundBinaryTag.builder() - .putString("type", "minecraft:damage_type") - .put("value", entries.build()) - .build(); - } - return lazyNbt; + static @NotNull RegistryDataPacket registryDataPacket() { + if (lazyRegistryDataPacket != null) return lazyRegistryDataPacket; + return lazyRegistryDataPacket = new RegistryDataPacket( + "minecraft:damage_type", + values().stream() + .map(DamageType::toRegistryEntry) + .toList() + ); } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java b/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java index 60d7102b2..ee841f83a 100644 --- a/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java @@ -4,9 +4,12 @@ import net.minestom.server.coordinate.Point; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Metadata; import net.minestom.server.entity.Player; +import net.minestom.server.particle.Particle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + public class LivingEntityMeta extends EntityMeta { public static final byte OFFSET = EntityMeta.MAX_OFFSET; public static final byte MAX_OFFSET = OFFSET + 7; @@ -44,58 +47,28 @@ public class LivingEntityMeta extends EntityMeta { setMaskBit(OFFSET, IS_IN_SPIN_ATTACK_BIT, value); } - public float getHealth() { - return super.metadata.getIndex(OFFSET + 1, 1F); + public @NotNull List getEffectParticles() { + return super.metadata.getIndex(OFFSET + 1, List.of()); } - public void setHealth(float value) { - super.metadata.setIndex(OFFSET + 1, Metadata.Float(value)); - } - - public int getPotionEffectColor() { - return super.metadata.getIndex(OFFSET + 2, 0); - } - - public void setPotionEffectColor(int value) { - super.metadata.setIndex(OFFSET + 2, Metadata.VarInt(value)); + public void setEffectParticles(@NotNull List value) { + super.metadata.setIndex(OFFSET + 1, Metadata.ParticleList(value)); } public boolean isPotionEffectAmbient() { - return super.metadata.getIndex(OFFSET + 3, false); + return super.metadata.getIndex(OFFSET + 2, false); } public void setPotionEffectAmbient(boolean value) { - super.metadata.setIndex(OFFSET + 3, Metadata.Boolean(value)); + super.metadata.setIndex(OFFSET + 2, Metadata.Boolean(value)); } public int getArrowCount() { - return super.metadata.getIndex(OFFSET + 4, 0); + return super.metadata.getIndex(OFFSET + 3, 0); } public void setArrowCount(int value) { - super.metadata.setIndex(OFFSET + 4, Metadata.VarInt(value)); - } - - /** - * @deprecated - * This returns the bee stinger count, not the absorption heart count - * Use {@link #getBeeStingerCount()} instead - * @return The number of bee stingers in this entity - */ - @Deprecated - public int getHealthAddedByAbsorption() { - return super.metadata.getIndex(OFFSET + 5, 0); - } - - /** - * @deprecated - * This sets the bee stinger count, not the absorption heart count - * Use {@link #setBeeStingerCount(int)} instead - * @param value The number of bee stingers for this entity to have - */ - @Deprecated - public void setHealthAddedByAbsorption(int value) { - super.metadata.setIndex(OFFSET + 5, Metadata.VarInt(value)); + super.metadata.setIndex(OFFSET + 3, Metadata.VarInt(value)); } /** @@ -103,7 +76,7 @@ public class LivingEntityMeta extends EntityMeta { * @return The amount of bee stingers */ public int getBeeStingerCount() { - return super.metadata.getIndex(OFFSET + 5, 0); + return super.metadata.getIndex(OFFSET + 4, 0); } /** @@ -111,7 +84,15 @@ public class LivingEntityMeta extends EntityMeta { * @param value The amount of bee stingers to set, use 0 to clear all stingers */ public void setBeeStingerCount(int value) { - super.metadata.setIndex(OFFSET + 5, Metadata.VarInt(value)); + super.metadata.setIndex(OFFSET + 4, Metadata.VarInt(value)); + } + + public float getHealth() { + return super.metadata.getIndex(OFFSET + 5, 1F); + } + + public void setHealth(float value) { + super.metadata.setIndex(OFFSET + 5, Metadata.Float(value)); } @Nullable @@ -120,7 +101,7 @@ public class LivingEntityMeta extends EntityMeta { } public void setBedInWhichSleepingPosition(@Nullable Point value) { - super.metadata.setIndex(OFFSET + 6, Metadata.OptPosition(value)); + super.metadata.setIndex(OFFSET + 6, Metadata.OptBlockPosition(value)); } } diff --git a/src/main/java/net/minestom/server/entity/metadata/animal/ArmadilloMeta.java b/src/main/java/net/minestom/server/entity/metadata/animal/ArmadilloMeta.java new file mode 100644 index 000000000..a0807cfb8 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/metadata/animal/ArmadilloMeta.java @@ -0,0 +1,32 @@ +package net.minestom.server.entity.metadata.animal; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Metadata; +import org.jetbrains.annotations.NotNull; + +public class ArmadilloMeta extends AnimalMeta { + public static final byte OFFSET = AnimalMeta.MAX_OFFSET; + public static final byte MAX_OFFSET = OFFSET + 1; + + public ArmadilloMeta(@NotNull Entity entity, @NotNull Metadata metadata) { + super(entity, metadata); + } + + @NotNull + public State getState() { + return super.metadata.getIndex(OFFSET, State.IDLE); + } + + public void setState(@NotNull State value) { + super.metadata.setIndex(OFFSET, Metadata.ArmadilloState(value)); + } + + public enum State { + IDLE, + ROLLING, + SCARED, + UNROLLING; + + private static final State[] VALUES = values(); + } +} diff --git a/src/main/java/net/minestom/server/entity/metadata/animal/TurtleMeta.java b/src/main/java/net/minestom/server/entity/metadata/animal/TurtleMeta.java index 2f12487ae..cd96cc4f2 100644 --- a/src/main/java/net/minestom/server/entity/metadata/animal/TurtleMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/animal/TurtleMeta.java @@ -19,7 +19,7 @@ public class TurtleMeta extends AnimalMeta { } public void setBlockPosition(@NotNull Point value) { - super.metadata.setIndex(OFFSET, Metadata.Position(value)); + super.metadata.setIndex(OFFSET, Metadata.BlockPosition(value)); } public boolean isHasEgg() { @@ -43,7 +43,7 @@ public class TurtleMeta extends AnimalMeta { } public void setTravelPosition(@NotNull Point value) { - super.metadata.setIndex(OFFSET + 3, Metadata.Position(value)); + super.metadata.setIndex(OFFSET + 3, Metadata.BlockPosition(value)); } public boolean isGoingHome() { diff --git a/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java b/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java index ce593bd84..4851ed0cf 100644 --- a/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java @@ -12,6 +12,8 @@ public class WolfMeta extends TameableAnimalMeta { super(entity, metadata); } + //todo variant + public boolean isBegging() { return super.metadata.getIndex(OFFSET, false); } diff --git a/src/main/java/net/minestom/server/entity/metadata/display/ItemDisplayMeta.java b/src/main/java/net/minestom/server/entity/metadata/display/ItemDisplayMeta.java index 5a78687ca..ff5e266fe 100644 --- a/src/main/java/net/minestom/server/entity/metadata/display/ItemDisplayMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/display/ItemDisplayMeta.java @@ -18,7 +18,7 @@ public class ItemDisplayMeta extends AbstractDisplayMeta { } public void setItemStack(@NotNull ItemStack value) { - super.metadata.setIndex(OFFSET, Metadata.Slot(value)); + super.metadata.setIndex(OFFSET, Metadata.ItemStack(value)); } public @NotNull DisplayContext getDisplayContext() { diff --git a/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java b/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java index 2949ec133..8d403468f 100644 --- a/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/item/ItemContainingMeta.java @@ -24,7 +24,7 @@ class ItemContainingMeta extends EntityMeta { } public void setItem(@NotNull ItemStack item) { - super.metadata.setIndex(OFFSET, Metadata.Slot(item)); + super.metadata.setIndex(OFFSET, Metadata.ItemStack(item)); } } diff --git a/src/main/java/net/minestom/server/entity/metadata/monster/skeleton/BoggedMeta.java b/src/main/java/net/minestom/server/entity/metadata/monster/skeleton/BoggedMeta.java new file mode 100644 index 000000000..da5d5791c --- /dev/null +++ b/src/main/java/net/minestom/server/entity/metadata/monster/skeleton/BoggedMeta.java @@ -0,0 +1,14 @@ +package net.minestom.server.entity.metadata.monster.skeleton; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Metadata; +import org.jetbrains.annotations.NotNull; + +public class BoggedMeta extends AbstractSkeletonMeta { + public static final byte OFFSET = AbstractSkeletonMeta.MAX_OFFSET; + public static final byte MAX_OFFSET = OFFSET + 0; + + public BoggedMeta(@NotNull Entity entity, @NotNull Metadata metadata) { + super(entity, metadata); + } +} diff --git a/src/main/java/net/minestom/server/entity/metadata/other/EndCrystalMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/EndCrystalMeta.java index c4889ddaa..7f1e9fce0 100644 --- a/src/main/java/net/minestom/server/entity/metadata/other/EndCrystalMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/other/EndCrystalMeta.java @@ -20,7 +20,7 @@ public class EndCrystalMeta extends EntityMeta { } public void setBeamTarget(@Nullable Point value) { - super.metadata.setIndex(OFFSET, Metadata.OptPosition(value)); + super.metadata.setIndex(OFFSET, Metadata.OptBlockPosition(value)); } public boolean isShowingBottom() { diff --git a/src/main/java/net/minestom/server/entity/metadata/other/FallingBlockMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/FallingBlockMeta.java index f05fb9327..3bb0ce150 100644 --- a/src/main/java/net/minestom/server/entity/metadata/other/FallingBlockMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/other/FallingBlockMeta.java @@ -24,7 +24,7 @@ public class FallingBlockMeta extends EntityMeta implements ObjectDataProvider { } public void setSpawnPosition(Point value) { - super.metadata.setIndex(OFFSET, Metadata.Position(value)); + super.metadata.setIndex(OFFSET, Metadata.BlockPosition(value)); } @NotNull diff --git a/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java index 9fe83fd1e..742e2ffa6 100644 --- a/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/other/ItemFrameMeta.java @@ -25,7 +25,7 @@ public class ItemFrameMeta extends EntityMeta implements ObjectDataProvider { } public void setItem(@NotNull ItemStack value) { - super.metadata.setIndex(OFFSET, Metadata.Slot(value)); + super.metadata.setIndex(OFFSET, Metadata.ItemStack(value)); } @NotNull diff --git a/src/main/java/net/minestom/server/entity/metadata/other/OminousItemSpawnerMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/OminousItemSpawnerMeta.java new file mode 100644 index 000000000..318a88fd2 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/metadata/other/OminousItemSpawnerMeta.java @@ -0,0 +1,25 @@ +package net.minestom.server.entity.metadata.other; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Metadata; +import net.minestom.server.entity.metadata.EntityMeta; +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class OminousItemSpawnerMeta extends EntityMeta { + public static final byte OFFSET = EntityMeta.MAX_OFFSET; + public static final byte MAX_OFFSET = OFFSET + 1; + + public OminousItemSpawnerMeta(@NotNull Entity entity, @NotNull Metadata metadata) { + super(entity, metadata); + } + + public @NotNull ItemStack getItem() { + return super.metadata.getIndex(OFFSET, ItemStack.AIR); + } + + public void setItem(@NotNull ItemStack value) { + super.metadata.setIndex(OFFSET, Metadata.ItemStack(value)); + } + +} diff --git a/src/main/java/net/minestom/server/entity/metadata/projectile/FireworkRocketMeta.java b/src/main/java/net/minestom/server/entity/metadata/projectile/FireworkRocketMeta.java index 65c055236..6c3450d1b 100644 --- a/src/main/java/net/minestom/server/entity/metadata/projectile/FireworkRocketMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/projectile/FireworkRocketMeta.java @@ -3,7 +3,6 @@ package net.minestom.server.entity.metadata.projectile; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Metadata; import net.minestom.server.entity.metadata.EntityMeta; -import net.minestom.server.entity.metadata.projectile.ProjectileMeta; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +23,7 @@ public class FireworkRocketMeta extends EntityMeta implements ProjectileMeta { } public void setFireworkInfo(@NotNull ItemStack value) { - super.metadata.setIndex(OFFSET, Metadata.Slot(value)); + super.metadata.setIndex(OFFSET, Metadata.ItemStack(value)); } @Override diff --git a/src/main/java/net/minestom/server/entity/metadata/water/DolphinMeta.java b/src/main/java/net/minestom/server/entity/metadata/water/DolphinMeta.java index 6dca1c672..b4753d46d 100644 --- a/src/main/java/net/minestom/server/entity/metadata/water/DolphinMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/water/DolphinMeta.java @@ -20,7 +20,7 @@ public class DolphinMeta extends WaterAnimalMeta { } public void setTreasurePosition(@NotNull Point value) { - super.metadata.setIndex(OFFSET, Metadata.Position(value)); + super.metadata.setIndex(OFFSET, Metadata.BlockPosition(value)); } public boolean isHasFish() { diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PFPathingEntity.java b/src/main/java/net/minestom/server/entity/pathfinding/PFPathingEntity.java index a8e6a7422..ac7780e87 100644 --- a/src/main/java/net/minestom/server/entity/pathfinding/PFPathingEntity.java +++ b/src/main/java/net/minestom/server/entity/pathfinding/PFPathingEntity.java @@ -4,11 +4,11 @@ import com.extollit.gaming.ai.path.model.Gravitation; import com.extollit.gaming.ai.path.model.IPathingEntity; import com.extollit.gaming.ai.path.model.Passibility; import com.extollit.linalg.immutable.Vec3d; -import net.minestom.server.attribute.Attribute; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; import net.minestom.server.entity.LivingEntity; +import net.minestom.server.entity.attribute.Attribute; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -34,7 +34,7 @@ public final class PFPathingEntity implements IPathingEntity { this.navigator = navigator; this.entity = navigator.getEntity(); - this.searchRange = getAttributeValue(Attribute.FOLLOW_RANGE); + this.searchRange = (float) getAttributeValue(Attribute.GENERIC_FOLLOW_RANGE); } @Override @@ -138,7 +138,7 @@ public final class PFPathingEntity implements IPathingEntity { return new Capabilities() { @Override public float speed() { - return getAttributeValue(Attribute.MOVEMENT_SPEED); + return (float) getAttributeValue(Attribute.GENERIC_MOVEMENT_SPEED); } @Override @@ -191,7 +191,7 @@ public final class PFPathingEntity implements IPathingEntity { @Override public void moveTo(Vec3d position, Passibility passibility, Gravitation gravitation) { final Point targetPosition = new Vec(position.x, position.y, position.z); - this.navigator.moveTowards(targetPosition, getAttributeValue(Attribute.MOVEMENT_SPEED)); + this.navigator.moveTowards(targetPosition, getAttributeValue(Attribute.GENERIC_MOVEMENT_SPEED)); final double entityY = entity.getPosition().y() + 0.00001D; // After any negative y movement, entities will always be extremely // slightly below floor level. This +0.00001D is here to offset this // error and stop the entity from permanently jumping. @@ -217,7 +217,7 @@ public final class PFPathingEntity implements IPathingEntity { return (float) entity.getBoundingBox().height(); } - private float getAttributeValue(@NotNull Attribute attribute) { + private double getAttributeValue(@NotNull Attribute attribute) { if (entity instanceof LivingEntity) { return ((LivingEntity) entity).getAttributeValue(attribute); } diff --git a/src/main/java/net/minestom/server/item/ItemSerializers.java b/src/main/java/net/minestom/server/item/ItemSerializers.java index c2b6494bc..6764589c6 100644 --- a/src/main/java/net/minestom/server/item/ItemSerializers.java +++ b/src/main/java/net/minestom/server/item/ItemSerializers.java @@ -1,7 +1,7 @@ package net.minestom.server.item; -import net.minestom.server.attribute.Attribute; -import net.minestom.server.attribute.AttributeOperation; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.entity.attribute.AttributeOperation; import net.minestom.server.item.attribute.AttributeSlot; import net.minestom.server.item.attribute.ItemAttribute; import net.minestom.server.tag.Tag; @@ -57,7 +57,7 @@ public final class ItemSerializers { final int operation = reader.getTag(OPERATION); final String name = reader.getTag(NAME); - final Attribute attribute = Attribute.fromKey(attributeName.toLowerCase(Locale.ROOT)); + final Attribute attribute = Attribute.fromNamespaceId(attributeName.toLowerCase(Locale.ROOT)); // Wrong attribute name, stop here if (attribute == null) return null; final AttributeOperation attributeOperation = AttributeOperation.fromId(operation); @@ -79,7 +79,7 @@ public final class ItemSerializers { writer.setTag(ID, value.uuid()); writer.setTag(AMOUNT, value.amount()); writer.setTag(SLOT, value.slot().name().toLowerCase(Locale.ROOT)); - writer.setTag(ATTRIBUTE_NAME, value.attribute().key()); + writer.setTag(ATTRIBUTE_NAME, value.attribute().name()); writer.setTag(OPERATION, value.operation().getId()); writer.setTag(NAME, value.name()); } diff --git a/src/main/java/net/minestom/server/item/Material.java b/src/main/java/net/minestom/server/item/Material.java index efb8eab74..011633e34 100644 --- a/src/main/java/net/minestom/server/item/Material.java +++ b/src/main/java/net/minestom/server/item/Material.java @@ -1,8 +1,8 @@ package net.minestom.server.item; import net.minestom.server.instance.block.Block; -import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -10,6 +10,38 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; +/** + * + * Component notes + * todo delete me + * + * ItemComponent.DYED_ITEM -> record DyedItemComponent.class + * + * itemStack.with(ItemComponent.DYED_ITEM, new DyedItemComponent(Color.RED)) + * itemStack.get(ItemComponent.DYED_ITEM) + * itemStack.getOrDefault(ItemComponent.DYED_ITEM, new DyedItemComponent(Color.RED)) + * + * material.prototype() -> some component list? + * + * + * + * // NEW WIRE FORMAT + * count | varint + * material id | varint + * components | SEE BELOW + * + * DataComponentPatch + * additions | varint + * removals | varint + * for each addition + * varint | data component id + * data component | data component (depends) + * for each removal + * varint | data component id + * + * + */ + public sealed interface Material extends StaticProtocolObject, Materials permits MaterialImpl { /** diff --git a/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java b/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java index 1e14f1999..e914d4eff 100644 --- a/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java +++ b/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java @@ -1,7 +1,7 @@ package net.minestom.server.item.attribute; -import net.minestom.server.attribute.Attribute; -import net.minestom.server.attribute.AttributeOperation; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.entity.attribute.AttributeOperation; import org.jetbrains.annotations.NotNull; import java.util.UUID; diff --git a/src/main/java/net/minestom/server/item/component/CustomData.java b/src/main/java/net/minestom/server/item/component/CustomData.java new file mode 100644 index 000000000..0c00926ea --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/CustomData.java @@ -0,0 +1,23 @@ +package net.minestom.server.item.component; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.tag.Tag; +import org.jetbrains.annotations.NotNull; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +public record CustomData(@NotNull NBTCompound nbt) implements ItemComponent { + static final Tag TAG = Tag.Structure("ab", CustomData.class); + + static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, CustomData value) { + buffer.write(NetworkBuffer.NBT, value.nbt); + } + + @Override + public CustomData read(@NotNull NetworkBuffer buffer) { + return new CustomData((NBTCompound) buffer.read(NetworkBuffer.NBT)); + } + }; + +} diff --git a/src/main/java/net/minestom/server/item/component/ItemComponent.java b/src/main/java/net/minestom/server/item/component/ItemComponent.java new file mode 100644 index 000000000..2cc339d29 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/ItemComponent.java @@ -0,0 +1,20 @@ +package net.minestom.server.item.component; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.tag.Tag; +import org.jetbrains.annotations.NotNull; + +public sealed interface ItemComponent permits CustomData { + + ItemComponent.Type CUSTOM_DATA = new ItemComponent.Type<>("custom_data", CustomData.NETWORK_TYPE, CustomData.TAG); + + record Type( + @NotNull String name, + @NotNull NetworkBuffer.Type network, + @NotNull Tag tag + ) { + + } + + +} diff --git a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java b/src/main/java/net/minestom/server/item/metadata/PotionMeta.java index a4ecfcc2e..00742b003 100644 --- a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PotionMeta.java @@ -13,7 +13,7 @@ import org.jetbrains.annotations.UnknownNullability; import java.util.List; public record PotionMeta(TagReadable readable) implements ItemMetaView { - private static final Tag POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, StaticProtocolObject::name).defaultValue(PotionType.EMPTY); + private static final Tag POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, StaticProtocolObject::name).defaultValue(PotionType.WATER); private static final Tag> CUSTOM_POTION_EFFECTS = Tag.Structure("CustomPotionEffects", new TagSerializer() { @Override public @Nullable CustomPotionEffect read(@NotNull TagReadable reader) { diff --git a/src/main/java/net/minestom/server/listener/preplay/HandshakeListener.java b/src/main/java/net/minestom/server/listener/preplay/HandshakeListener.java index 13ea23024..06ec2f836 100644 --- a/src/main/java/net/minestom/server/listener/preplay/HandshakeListener.java +++ b/src/main/java/net/minestom/server/listener/preplay/HandshakeListener.java @@ -40,8 +40,8 @@ public final class HandshakeListener { public static void listener(@NotNull ClientHandshakePacket packet, @NotNull PlayerConnection connection) { String address = packet.serverAddress(); switch (packet.intent()) { - case 1 -> connection.setConnectionState(ConnectionState.STATUS); - case 2 -> { + case STATUS -> connection.setConnectionState(ConnectionState.STATUS); + case LOGIN -> { connection.setConnectionState(ConnectionState.LOGIN); if (packet.protocolVersion() != MinecraftServer.PROTOCOL_VERSION) { // Incorrect client version @@ -114,6 +114,9 @@ public final class HandshakeListener { } } } + case TRANSFER -> { + throw new UnsupportedOperationException("Transfer intent is not supported in HandshakeListener"); + } default -> { // Unexpected error } diff --git a/src/main/java/net/minestom/server/listener/preplay/LoginListener.java b/src/main/java/net/minestom/server/listener/preplay/LoginListener.java index 15df756e7..55b3308e7 100644 --- a/src/main/java/net/minestom/server/listener/preplay/LoginListener.java +++ b/src/main/java/net/minestom/server/listener/preplay/LoginListener.java @@ -75,7 +75,7 @@ public final class LoginListener { byte[] nonce = new byte[4]; ThreadLocalRandom.current().nextBytes(nonce); socketConnection.setNonce(nonce); - socketConnection.sendPacket(new EncryptionRequestPacket("", publicKey, nonce)); + socketConnection.sendPacket(new EncryptionRequestPacket("", publicKey, nonce, true)); } else { final boolean bungee = BungeeCordProxy.isEnabled(); // Offline diff --git a/src/main/java/net/minestom/server/message/Messenger.java b/src/main/java/net/minestom/server/message/Messenger.java index df3d09acb..fdd6abc18 100644 --- a/src/main/java/net/minestom/server/message/Messenger.java +++ b/src/main/java/net/minestom/server/message/Messenger.java @@ -5,6 +5,7 @@ import net.kyori.adventure.nbt.TagStringIO; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.network.packet.server.play.SystemChatPacket; import net.minestom.server.utils.PacketUtils; import org.jetbrains.annotations.NotNull; @@ -12,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.UUID; @@ -26,44 +28,36 @@ public final class Messenger { private static final UUID NO_SENDER = new UUID(0, 0); private static final SystemChatPacket CANNOT_SEND_PACKET = new SystemChatPacket(CANNOT_SEND_MESSAGE, false); - private static final CompoundBinaryTag CHAT_REGISTRY; + private static final RegistryDataPacket REGISTRY_DATA_PACKET; static { try { - CHAT_REGISTRY = TagStringIO.get().asCompound( - """ - { - "type": "minecraft:chat_type", - "value": [ - { - "name":"minecraft:chat", - "id":1, - "element":{ - "chat":{ - "translation_key":"chat.type.text", - "parameters":[ - "sender", - "content" - ] - }, - "narration":{ - "translation_key":"chat.type.text.narrate", - "parameters":[ - "sender", - "content" - ] - } - } - } ] - }""" - ); + REGISTRY_DATA_PACKET = new RegistryDataPacket( + "minecraft:chat_type", List.of( + new RegistryDataPacket.Entry( + "minecraft:chat", + TagStringIO.get().asCompound( + """ + { + "chat":{ + "translation_key":"chat.type.text", + "parameters":["sender", "content"] + }, + "narration":{ + "translation_key":"chat.type.text.narrate", + "parameters":["sender", "content"] + } + }""" + ) + ) + )); } catch (IOException e) { throw new RuntimeException(e); } } - public static @NotNull CompoundBinaryTag chatRegistry() { - return CHAT_REGISTRY; + public static @NotNull RegistryDataPacket registryDataPacket() { + return REGISTRY_DATA_PACKET; } /** diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index ec78114e9..fb3238dd1 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -1,6 +1,5 @@ package net.minestom.server.network; -import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; @@ -18,7 +17,6 @@ import net.minestom.server.network.packet.server.common.KeepAlivePacket; import net.minestom.server.network.packet.server.common.PluginMessagePacket; import net.minestom.server.network.packet.server.common.TagsPacket; import net.minestom.server.network.packet.server.configuration.FinishConfigurationPacket; -import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.network.packet.server.login.LoginSuccessPacket; import net.minestom.server.network.packet.server.play.StartConfigurationPacket; import net.minestom.server.network.player.PlayerConnection; @@ -33,8 +31,6 @@ import org.jctools.queues.MpscUnboundedArrayQueue; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -282,15 +278,15 @@ public final class ConnectionManager { // Registry data (if it should be sent) if (event.willSendRegistryData()) { - var registryCompound = CompoundBinaryTag.builder() - .put("minecraft:chat_type", Messenger.chatRegistry()) - .put("minecraft:dimension_type", MinecraftServer.getDimensionTypeManager().toNBT()) - .put("minecraft:worldgen/biome", MinecraftServer.getBiomeManager().toNBT()) - .put("minecraft:damage_type", DamageType.getNBT()) -// .put("minecraft:trim_material", MinecraftServer.getTrimManager().getTrimMaterialNBT()) -// .put("minecraft:trim_pattern", MinecraftServer.getTrimManager().getTrimPatternNBT()) - .build(); - player.sendPacket(new RegistryDataPacket(registryCompound)); + + // minecraft:trim_pattern, minecraft:trim_material, minecraft:wolf_variant, and minecraft:banner_pattern. + + player.sendPacket(Messenger.registryDataPacket()); + player.sendPacket(MinecraftServer.getDimensionTypeManager().registryDataPacket()); + player.sendPacket(MinecraftServer.getBiomeManager().registryDataPacket()); + player.sendPacket(DamageType.registryDataPacket()); +// registry.put("minecraft:trim_material", MinecraftServer.getTrimManager().getTrimMaterialNBT()); +// registry.put("minecraft:trim_pattern", MinecraftServer.getTrimManager().getTrimPatternNBT()); player.sendPacket(TagsPacket.DEFAULT_TAGS); } diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index 7f1b9a267..8f074f60c 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; import net.minestom.server.entity.Entity; +import net.minestom.server.entity.metadata.animal.ArmadilloMeta; import net.minestom.server.entity.metadata.animal.FrogMeta; import net.minestom.server.entity.metadata.animal.SnifferMeta; import net.minestom.server.entity.metadata.animal.tameable.CatMeta; @@ -73,6 +74,7 @@ public final class NetworkBuffer { public static final Type FROG_VARIANT = NetworkBufferTypeImpl.fromEnum(FrogMeta.Variant.class); public static final Type PAINTING_VARIANT = NetworkBufferTypeImpl.fromEnum(PaintingMeta.Variant.class); public static final Type SNIFFER_STATE = NetworkBufferTypeImpl.fromEnum(SnifferMeta.State.class); + public static final Type ARMADILLO_STATE = NetworkBufferTypeImpl.fromEnum(ArmadilloMeta.State.class); ByteBuffer nioBuffer; @@ -301,6 +303,11 @@ public final class NetworkBuffer { void write(@NotNull NetworkBuffer writer); } + @FunctionalInterface + public interface Reader { + @NotNull T read(@NotNull NetworkBuffer reader); + } + public static byte[] makeArray(@NotNull Consumer<@NotNull NetworkBuffer> writing) { NetworkBuffer writer = new NetworkBuffer(); writing.accept(writer); diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java index 3a2e2e4d0..6347280d2 100644 --- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java +++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java @@ -399,32 +399,28 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { @Override public void write(@NotNull NetworkBuffer buffer, ItemStack value) { if (value.isAir()) { - buffer.write(BOOLEAN, false); + buffer.write(VAR_INT, 0); // 0 count always return; } - buffer.write(BOOLEAN, true); + buffer.write(VAR_INT, value.amount()); buffer.write(VAR_INT, value.material().id()); - buffer.write(BYTE, (byte) value.amount()); - // Vanilla does not write an empty object, just an end tag. - CompoundBinaryTag nbt = value.meta().toNBT(); - buffer.write(NBT, nbt.size() == 0 ? EndBinaryTag.endBinaryTag() : nbt); + buffer.write(VAR_INT, 0); // Added components + buffer.write(VAR_INT, 0); // Removed components } @Override public ItemStack read(@NotNull NetworkBuffer buffer) { - final boolean present = buffer.read(BOOLEAN); - if (!present) return ItemStack.AIR; + int count = buffer.read(VAR_INT); + if (count <= 0) return ItemStack.AIR; final int id = buffer.read(VAR_INT); final Material material = Material.fromId(id); if (material == null) throw new RuntimeException("Unknown material id: " + id); - final int amount = buffer.read(BYTE); - final BinaryTag nbt = buffer.read(NBT); - if (!(nbt instanceof CompoundBinaryTag compound)) { - return ItemStack.of(material, amount); - } - return ItemStack.fromNBT(material, compound, amount); + buffer.read(VAR_INT); // Added components + buffer.read(VAR_INT); // Removed components + + return ItemStack.fromNBT(material, new NBTCompound(), count); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java b/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java index 80ec4e5c1..91d739fa4 100644 --- a/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java +++ b/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java @@ -3,6 +3,7 @@ package net.minestom.server.network.packet.client; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.common.*; import net.minestom.server.network.packet.client.configuration.ClientFinishConfigurationPacket; +import net.minestom.server.network.packet.client.configuration.ClientSelectKnownPacksPacket; import net.minestom.server.network.packet.client.login.ClientEncryptionResponsePacket; import net.minestom.server.network.packet.client.login.ClientLoginAcknowledgedPacket; import net.minestom.server.network.packet.client.login.ClientLoginPluginResponsePacket; @@ -13,28 +14,26 @@ import net.minestom.server.utils.collection.ObjectArray; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; -import java.util.function.Function; - /** * Contains registered packets and a way to instantiate them. *

- * Packets are registered using {@link #register(int, Function)} and created using {@link #create(int, NetworkBuffer)}. + * Packets are registered using {@link #register(int, NetworkBuffer.Reader)} and created using {@link #create(int, NetworkBuffer)}. */ public sealed class ClientPacketsHandler permits ClientPacketsHandler.Status, ClientPacketsHandler.Login, ClientPacketsHandler.Configuration, ClientPacketsHandler.Play { - private final ObjectArray> suppliers = ObjectArray.singleThread(0x10); + private final ObjectArray> suppliers = ObjectArray.singleThread(0x10); private ClientPacketsHandler() { } - public void register(int id, @NotNull Function<@NotNull NetworkBuffer, @NotNull ClientPacket> packetSupplier) { + public void register(int id, @NotNull NetworkBuffer.Reader packetSupplier) { this.suppliers.set(id, packetSupplier); } public @UnknownNullability ClientPacket create(int packetId, @NotNull NetworkBuffer reader) { - final Function supplier = suppliers.get(packetId); + final NetworkBuffer.Reader supplier = suppliers.get(packetId); if (supplier == null) throw new IllegalStateException("Packet id 0x" + Integer.toHexString(packetId) + " isn't registered!"); - return supplier.apply(reader); + return supplier.read(reader); } public static final class Status extends ClientPacketsHandler { @@ -60,6 +59,7 @@ public sealed class ClientPacketsHandler permits ClientPacketsHandler.Status, Cl register(nextId(), ClientEncryptionResponsePacket::new); register(nextId(), ClientLoginPluginResponsePacket::new); register(nextId(), ClientLoginAcknowledgedPacket::new); + register(nextId(), ClientCookieResponsePacket::new); } } @@ -71,11 +71,13 @@ public sealed class ClientPacketsHandler permits ClientPacketsHandler.Status, Cl public Configuration() { register(nextId(), ClientSettingsPacket::new); + register(nextId(), ClientCookieResponsePacket::new); register(nextId(), ClientPluginMessagePacket::new); register(nextId(), ClientFinishConfigurationPacket::new); register(nextId(), ClientKeepAlivePacket::new); register(nextId(), ClientPongPacket::new); register(nextId(), ClientResourcePackStatusPacket::new); + register(nextId(), ClientSelectKnownPacksPacket::new); } } @@ -93,6 +95,7 @@ public sealed class ClientPacketsHandler permits ClientPacketsHandler.Status, Cl nextId(); // difficulty packet register(nextId(), ClientChatAckPacket::new); register(nextId(), ClientCommandChatPacket::new); + register(nextId(), ClientSignedCommandChatPacket::new); register(nextId(), ClientChatMessagePacket::new); register(nextId(), ClientChatSessionUpdatePacket::new); register(nextId(), ClientChunkBatchReceivedPacket::new); @@ -104,7 +107,9 @@ public sealed class ClientPacketsHandler permits ClientPacketsHandler.Status, Cl register(nextId(), ClientClickWindowPacket::new); register(nextId(), ClientCloseWindowPacket::new); register(nextId(), ClientWindowSlotStatePacket::new); + register(nextId(), ClientCookieResponsePacket::new); register(nextId(), ClientPluginMessagePacket::new); + register(nextId(), ClientDebugSampleSubscriptionPacket::new); register(nextId(), ClientEditBookPacket::new); register(nextId(), ClientQueryEntityNbtPacket::new); register(nextId(), ClientInteractEntityPacket::new); diff --git a/src/main/java/net/minestom/server/network/packet/client/common/ClientCookieResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/common/ClientCookieResponsePacket.java new file mode 100644 index 000000000..25854b758 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/common/ClientCookieResponsePacket.java @@ -0,0 +1,35 @@ +package net.minestom.server.network.packet.client.common; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.client.ClientPacket; +import net.minestom.server.network.packet.server.common.CookieStorePacket; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record ClientCookieResponsePacket( + @NotNull String key, + byte @Nullable [] value +) implements ClientPacket { + + public ClientCookieResponsePacket { + Check.argCondition(value != null && value.length > CookieStorePacket.MAX_VALUE_LENGTH, + "Value is too long: {0} > {1}", value != null ? value.length : 0, CookieStorePacket.MAX_VALUE_LENGTH); + } + + public ClientCookieResponsePacket(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.STRING), reader.readOptional(buffer -> { + int valueLength = buffer.read(NetworkBuffer.VAR_INT); + Check.argCondition(valueLength > CookieStorePacket.MAX_VALUE_LENGTH, + "Value is too long: {0} > {1}", valueLength, CookieStorePacket.MAX_VALUE_LENGTH); + return buffer.readBytes(valueLength); + })); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.STRING, key); + writer.writeOptional(NetworkBuffer.BYTE_ARRAY, value); + } + +} diff --git a/src/main/java/net/minestom/server/network/packet/client/configuration/ClientSelectKnownPacksPacket.java b/src/main/java/net/minestom/server/network/packet/client/configuration/ClientSelectKnownPacksPacket.java new file mode 100644 index 000000000..2e263ea84 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/configuration/ClientSelectKnownPacksPacket.java @@ -0,0 +1,29 @@ +package net.minestom.server.network.packet.client.configuration; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.client.ClientPacket; +import net.minestom.server.network.packet.server.configuration.SelectKnownPacksPacket; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record ClientSelectKnownPacksPacket( + @NotNull List entries +) implements ClientPacket { + private static final int MAX_ENTRIES = 64; + + public ClientSelectKnownPacksPacket { + Check.argCondition(entries.size() > MAX_ENTRIES, "Too many known packs: {0} > {1}", entries.size(), MAX_ENTRIES); + } + + public ClientSelectKnownPacksPacket(@NotNull NetworkBuffer reader) { + this(reader.readCollection(SelectKnownPacksPacket.Entry::new, MAX_ENTRIES)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeCollection(entries); + } + +} diff --git a/src/main/java/net/minestom/server/network/packet/client/handshake/ClientHandshakePacket.java b/src/main/java/net/minestom/server/network/packet/client/handshake/ClientHandshakePacket.java index bd1432190..1517a4d62 100644 --- a/src/main/java/net/minestom/server/network/packet/client/handshake/ClientHandshakePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/handshake/ClientHandshakePacket.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull; import static net.minestom.server.network.NetworkBuffer.*; public record ClientHandshakePacket(int protocolVersion, @NotNull String serverAddress, - int serverPort, int intent) implements ClientPacket { + int serverPort, @NotNull Intent intent) implements ClientPacket { public ClientHandshakePacket { if (serverAddress.length() > getMaxHandshakeLength()) { @@ -18,7 +18,7 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA public ClientHandshakePacket(@NotNull NetworkBuffer reader) { this(reader.read(VAR_INT), reader.read(STRING), - reader.read(UNSIGNED_SHORT), reader.read(VAR_INT)); + reader.read(UNSIGNED_SHORT), Intent.fromId(reader.read(VAR_INT))); } @Override @@ -30,7 +30,7 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA } writer.write(STRING, serverAddress); writer.write(UNSIGNED_SHORT, serverPort); - writer.write(VAR_INT, intent); + writer.write(VAR_INT, intent.id()); } private static int getMaxHandshakeLength() { @@ -38,4 +38,23 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA return BungeeCordProxy.isEnabled() ? (BungeeCordProxy.isBungeeGuardEnabled() ? 2500 : Short.MAX_VALUE) : 255; } + public enum Intent { + STATUS, + LOGIN, + TRANSFER; + + public static @NotNull Intent fromId(int id) { + return switch (id) { + case 1 -> STATUS; + case 2 -> LOGIN; + case 3 -> TRANSFER; + default -> throw new IllegalArgumentException("Unknown connection intent: " + id); + }; + } + + public int id() { + return ordinal() + 1; + } + } + } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java index ef29ff546..66b302b3e 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java @@ -1,35 +1,23 @@ package net.minestom.server.network.packet.client.play; -import net.minestom.server.crypto.ArgumentSignatures; -import net.minestom.server.crypto.LastSeenMessages; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import static net.minestom.server.network.NetworkBuffer.LONG; import static net.minestom.server.network.NetworkBuffer.STRING; -public record ClientCommandChatPacket(@NotNull String message, long timestamp, - long salt, @NotNull ArgumentSignatures signatures, - LastSeenMessages.@NotNull Update lastSeenMessages) implements ClientPacket { +public record ClientCommandChatPacket(@NotNull String message) implements ClientPacket { public ClientCommandChatPacket { - if (message.length() > 256) { - throw new IllegalArgumentException("Message length cannot be greater than 256"); - } + Check.argCondition(message.length() > 256, "Message length cannot be greater than 256"); } public ClientCommandChatPacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), reader.read(LONG), - reader.read(LONG), new ArgumentSignatures(reader), - new LastSeenMessages.Update(reader)); + this(reader.read(STRING)); } @Override public void write(@NotNull NetworkBuffer writer) { writer.write(STRING, message); - writer.write(LONG, timestamp); - writer.write(LONG, salt); - writer.write(signatures); - writer.write(lastSeenMessages); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientDebugSampleSubscriptionPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientDebugSampleSubscriptionPacket.java new file mode 100644 index 000000000..2fd208907 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientDebugSampleSubscriptionPacket.java @@ -0,0 +1,18 @@ +package net.minestom.server.network.packet.client.play; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.client.ClientPacket; +import net.minestom.server.network.packet.server.play.DebugSamplePacket; +import org.jetbrains.annotations.NotNull; + +public record ClientDebugSampleSubscriptionPacket(@NotNull DebugSamplePacket.Type type) implements ClientPacket { + + public ClientDebugSampleSubscriptionPacket(@NotNull NetworkBuffer reader) { + this(reader.readEnum(DebugSamplePacket.Type.class)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeEnum(DebugSamplePacket.Type.class, type); + } +} diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientSignedCommandChatPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientSignedCommandChatPacket.java new file mode 100644 index 000000000..3693b80e8 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientSignedCommandChatPacket.java @@ -0,0 +1,34 @@ +package net.minestom.server.network.packet.client.play; + +import net.minestom.server.crypto.ArgumentSignatures; +import net.minestom.server.crypto.LastSeenMessages; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.client.ClientPacket; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +import static net.minestom.server.network.NetworkBuffer.LONG; +import static net.minestom.server.network.NetworkBuffer.STRING; + +public record ClientSignedCommandChatPacket(@NotNull String message, long timestamp, + long salt, @NotNull ArgumentSignatures signatures, + LastSeenMessages.@NotNull Update lastSeenMessages) implements ClientPacket { + public ClientSignedCommandChatPacket { + Check.argCondition(message.length() > 256, "Message length cannot be greater than 256"); + } + + public ClientSignedCommandChatPacket(@NotNull NetworkBuffer reader) { + this(reader.read(STRING), reader.read(LONG), + reader.read(LONG), new ArgumentSignatures(reader), + new LastSeenMessages.Update(reader)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(STRING, message); + writer.write(LONG, timestamp); + writer.write(LONG, salt); + writer.write(signatures); + writer.write(lastSeenMessages); + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java index c6afcdc87..3390174ff 100644 --- a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java +++ b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java @@ -3,27 +3,36 @@ package net.minestom.server.network.packet.server; import java.util.concurrent.atomic.AtomicInteger; public final class ServerPacketIdentifier { + private static final AtomicInteger STATUS_ID = new AtomicInteger(0); + private static final AtomicInteger LOGIN_ID = new AtomicInteger(0); + private static final AtomicInteger CONFIGURATION_ID = new AtomicInteger(0); private static final AtomicInteger PLAY_ID = new AtomicInteger(0); - public static final int STATUS_RESPONSE = 0x00; - public static final int STATUS_PING_RESPONSE = 0x01; + public static final int STATUS_RESPONSE = nextStatusId(); + public static final int STATUS_PING_RESPONSE = nextStatusId(); - public static final int LOGIN_DISCONNECT = 0x00; - public static final int LOGIN_ENCRYPTION_REQUEST = 0x01; - public static final int LOGIN_SUCCESS = 0x02; - public static final int LOGIN_SET_COMPRESSION = 0x03; - public static final int LOGIN_PLUGIN_REQUEST = 0x04; + public static final int LOGIN_DISCONNECT = nextLoginId(); + public static final int LOGIN_ENCRYPTION_REQUEST = nextLoginId(); + public static final int LOGIN_SUCCESS = nextLoginId(); + public static final int LOGIN_SET_COMPRESSION = nextLoginId(); + public static final int LOGIN_PLUGIN_REQUEST = nextLoginId(); + public static final int LOGIN_COOKIE_REQUEST = nextLoginId(); - public static final int CONFIGURATION_PLUGIN_MESSAGE = 0x00; - public static final int CONFIGURATION_DISCONNECT = 0x01; - public static final int CONFIGURATION_FINISH_CONFIGURATION = 0x02; - public static final int CONFIGURATION_KEEP_ALIVE = 0x03; - public static final int CONFIGURATION_PING = 0x04; - public static final int CONFIGURATION_REGISTRY_DATA = 0x05; - public static final int CONFIGURATION_RESOURCE_PACK_POP_PACKET = 0x06; - public static final int CONFIGURATION_RESOURCE_PACK_PUSH_PACKET = 0x07; - public static final int CONFIGURATION_UPDATE_ENABLED_FEATURES = 0x08; - public static final int CONFIGURATION_TAGS = 0x09; + public static final int CONFIGURATION_COOKIE_REQUEST = nextConfigurationId(); + public static final int CONFIGURATION_PLUGIN_MESSAGE = nextConfigurationId(); + public static final int CONFIGURATION_DISCONNECT = nextConfigurationId(); + public static final int CONFIGURATION_FINISH_CONFIGURATION = nextConfigurationId(); + public static final int CONFIGURATION_KEEP_ALIVE = nextConfigurationId(); + public static final int CONFIGURATION_PING = nextConfigurationId(); + public static final int CONFIGURATION_RESET_CHAT = nextConfigurationId(); + public static final int CONFIGURATION_REGISTRY_DATA = nextConfigurationId(); + public static final int CONFIGURATION_RESOURCE_PACK_POP = nextConfigurationId(); + public static final int CONFIGURATION_RESOURCE_PACK_PUSH = nextConfigurationId(); + public static final int CONFIGURATION_COOKIE_STORE = nextConfigurationId(); + public static final int CONFIGURATION_TRANSFER = nextConfigurationId(); + public static final int CONFIGURATION_UPDATE_ENABLED_FEATURES = nextConfigurationId(); + public static final int CONFIGURATION_TAGS = nextConfigurationId(); + public static final int CONFIGURATION_SELECT_KNOWN_PACKS = nextConfigurationId(); public static final int BUNDLE = nextPlayId(); public static final int SPAWN_ENTITY = nextPlayId(); @@ -47,10 +56,12 @@ public final class ServerPacketIdentifier { public static final int WINDOW_ITEMS = nextPlayId(); public static final int WINDOW_PROPERTY = nextPlayId(); public static final int SET_SLOT = nextPlayId(); + public static final int COOKIE_REQUEST = nextPlayId(); public static final int SET_COOLDOWN = nextPlayId(); public static final int CUSTOM_CHAT_COMPLETIONS = nextPlayId(); public static final int PLUGIN_MESSAGE = nextPlayId(); public static final int DAMAGE_EVENT = nextPlayId(); + public static final int DEBUG_SAMPLE = nextPlayId(); public static final int DELETE_CHAT_MESSAGE = nextPlayId(); public static final int DISCONNECT = nextPlayId(); public static final int DISGUISED_CHAT = nextPlayId(); @@ -130,6 +141,7 @@ public final class ServerPacketIdentifier { public static final int SOUND_EFFECT = nextPlayId(); public static final int START_CONFIGURATION_PACKET = nextPlayId(); public static final int STOP_SOUND = nextPlayId(); + public static final int COOKIE_STORE = nextPlayId(); public static final int SYSTEM_CHAT = nextPlayId(); public static final int PLAYER_LIST_HEADER_AND_FOOTER = nextPlayId(); public static final int NBT_QUERY_RESPONSE = nextPlayId(); @@ -137,11 +149,25 @@ public final class ServerPacketIdentifier { public static final int ENTITY_TELEPORT = nextPlayId(); public static final int TICK_STATE = nextPlayId(); public static final int TICK_STEP = nextPlayId(); + public static final int TRANSFER = nextPlayId(); public static final int ADVANCEMENTS = nextPlayId(); - public static final int ENTITY_PROPERTIES = nextPlayId(); + public static final int ENTITY_ATTRIBUTES = nextPlayId(); public static final int ENTITY_EFFECT = nextPlayId(); public static final int DECLARE_RECIPES = nextPlayId(); public static final int TAGS = nextPlayId(); + public static final int PROJECTILE_POWER = nextPlayId(); + + private static int nextStatusId() { + return STATUS_ID.getAndIncrement(); + } + + private static int nextLoginId() { + return LOGIN_ID.getAndIncrement(); + } + + private static int nextConfigurationId() { + return CONFIGURATION_ID.getAndIncrement(); + } private static int nextPlayId() { return PLAY_ID.getAndIncrement(); diff --git a/src/main/java/net/minestom/server/network/packet/server/common/CookieRequestPacket.java b/src/main/java/net/minestom/server/network/packet/server/common/CookieRequestPacket.java new file mode 100644 index 000000000..853c11b39 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/common/CookieRequestPacket.java @@ -0,0 +1,34 @@ +package net.minestom.server.network.packet.server.common; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; + +public record CookieRequestPacket(@NotNull String key) implements + ServerPacket.Login, ServerPacket.Configuration, ServerPacket.Play { + + public CookieRequestPacket(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.STRING)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.STRING, key); + } + + @Override + public int configurationId() { + return ServerPacketIdentifier.CONFIGURATION_COOKIE_REQUEST; + } + + @Override + public int loginId() { + return ServerPacketIdentifier.LOGIN_COOKIE_REQUEST; + } + + @Override + public int playId() { + return ServerPacketIdentifier.COOKIE_REQUEST; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/common/CookieStorePacket.java b/src/main/java/net/minestom/server/network/packet/server/common/CookieStorePacket.java new file mode 100644 index 000000000..42775547f --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/common/CookieStorePacket.java @@ -0,0 +1,54 @@ +package net.minestom.server.network.packet.server.common; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.utils.NamespaceID; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +public record CookieStorePacket( + @NotNull String key, byte[] value +) implements ServerPacket.Configuration, ServerPacket.Play { + public static final int MAX_VALUE_LENGTH = 5120; + + public CookieStorePacket { + Check.argCondition(value.length > MAX_VALUE_LENGTH, "Cookie value length too long: {0} > {1}", value.length, MAX_VALUE_LENGTH); + } + + public CookieStorePacket(@NotNull NamespaceID key, byte[] value) { + this(key.asString(), value); + } + + public CookieStorePacket(@NotNull NetworkBuffer reader) { + this(read(reader)); + } + + private CookieStorePacket(@NotNull CookieStorePacket other) { + this(other.key, other.value); + } + + private static @NotNull CookieStorePacket read(@NotNull NetworkBuffer reader) { + String key = reader.read(NetworkBuffer.STRING); + int valueLength = reader.read(NetworkBuffer.VAR_INT); + Check.argCondition(valueLength > MAX_VALUE_LENGTH, "Cookie value length too long: {0} > {1}", valueLength, MAX_VALUE_LENGTH); + byte[] value = reader.readBytes(valueLength); + return new CookieStorePacket(key, value); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.STRING, key); + writer.write(NetworkBuffer.BYTE_ARRAY, value); + } + + @Override + public int configurationId() { + return ServerPacketIdentifier.CONFIGURATION_COOKIE_STORE; + } + + @Override + public int playId() { + return ServerPacketIdentifier.COOKIE_STORE; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPopPacket.java b/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPopPacket.java index a6774a3d7..82adfd113 100644 --- a/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPopPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPopPacket.java @@ -20,7 +20,7 @@ public record ResourcePackPopPacket(@Nullable UUID id) implements ServerPacket.C @Override public int configurationId() { - return ServerPacketIdentifier.CONFIGURATION_RESOURCE_PACK_POP_PACKET; + return ServerPacketIdentifier.CONFIGURATION_RESOURCE_PACK_POP; } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPushPacket.java b/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPushPacket.java index a2b2f5e80..b585c2152 100644 --- a/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPushPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/common/ResourcePackPushPacket.java @@ -42,7 +42,7 @@ public record ResourcePackPushPacket( @Override public int configurationId() { - return ServerPacketIdentifier.CONFIGURATION_RESOURCE_PACK_PUSH_PACKET; + return ServerPacketIdentifier.CONFIGURATION_RESOURCE_PACK_PUSH; } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/common/TransferPacket.java b/src/main/java/net/minestom/server/network/packet/server/common/TransferPacket.java new file mode 100644 index 000000000..968e6b725 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/common/TransferPacket.java @@ -0,0 +1,32 @@ +package net.minestom.server.network.packet.server.common; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; + +public record TransferPacket( + @NotNull String host, + int port +) implements ServerPacket.Configuration, ServerPacket.Play { + + public TransferPacket(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.STRING), reader.read(NetworkBuffer.VAR_INT)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.STRING, host); + writer.write(NetworkBuffer.VAR_INT, port); + } + + @Override + public int configurationId() { + return ServerPacketIdentifier.CONFIGURATION_TRANSFER; + } + + @Override + public int playId() { + return ServerPacketIdentifier.TRANSFER; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java index 1d8aa950a..df09adb35 100644 --- a/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java @@ -5,21 +5,47 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +import java.util.List; import static net.minestom.server.network.NetworkBuffer.NBT; +import static net.minestom.server.network.NetworkBuffer.STRING; + +public record RegistryDataPacket( + @NotNull String registryId, + @NotNull List entries +) implements ServerPacket.Configuration { -public record RegistryDataPacket(@NotNull CompoundBinaryTag data) implements ServerPacket.Configuration { public RegistryDataPacket(@NotNull NetworkBuffer buffer) { - this((CompoundBinaryTag) buffer.read(NBT)); + this(buffer.read(STRING), buffer.readCollection(Entry::new, Integer.MAX_VALUE)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(NBT, data); + writer.write(STRING, registryId); + writer.writeCollection(entries); } @Override public int configurationId() { return ServerPacketIdentifier.CONFIGURATION_REGISTRY_DATA; } + + public record Entry( + @NotNull String id, + @Nullable CompoundBinaryTag data + ) implements NetworkBuffer.Writer { + + public Entry(@NotNull NetworkBuffer reader) { + this(reader.read(STRING), (CompoundBinaryTag) reader.readOptional(NBT)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(STRING, id); + writer.writeOptional(NBT, data); + } + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/configuration/ResetChatPacket.java b/src/main/java/net/minestom/server/network/packet/server/configuration/ResetChatPacket.java new file mode 100644 index 000000000..f460192f0 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/configuration/ResetChatPacket.java @@ -0,0 +1,23 @@ +package net.minestom.server.network.packet.server.configuration; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; + +public record ResetChatPacket() implements ServerPacket.Configuration { + + public ResetChatPacket(@NotNull NetworkBuffer reader) { + this(); // No fields + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + // No fields + } + + @Override + public int configurationId() { + return ServerPacketIdentifier.CONFIGURATION_RESET_CHAT; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/configuration/SelectKnownPacksPacket.java b/src/main/java/net/minestom/server/network/packet/server/configuration/SelectKnownPacksPacket.java new file mode 100644 index 000000000..61eead13e --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/configuration/SelectKnownPacksPacket.java @@ -0,0 +1,52 @@ +package net.minestom.server.network.packet.server.configuration; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record SelectKnownPacksPacket( + @NotNull List entries +) implements ServerPacket.Configuration { + private static final int MAX_ENTRIES = 64; + + public SelectKnownPacksPacket { + Check.argCondition(entries.size() > MAX_ENTRIES, "Too many known packs: {0} > {1}", entries.size(), MAX_ENTRIES); + } + + public SelectKnownPacksPacket(@NotNull NetworkBuffer reader) { + this(reader.readCollection(Entry::new, MAX_ENTRIES)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeCollection(entries); + } + + @Override + public int configurationId() { + return ServerPacketIdentifier.CONFIGURATION_SELECT_KNOWN_PACKS; + } + + public record Entry( + @NotNull String namespace, + @NotNull String id, + @NotNull String version + ) implements NetworkBuffer.Writer { + public Entry(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.STRING), + reader.read(NetworkBuffer.STRING), + reader.read(NetworkBuffer.STRING)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.STRING, namespace); + writer.write(NetworkBuffer.STRING, id); + writer.write(NetworkBuffer.STRING, version); + } + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java b/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java index 3a52c731f..99d0dd22f 100644 --- a/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java @@ -5,16 +5,20 @@ import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; -import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; -import static net.minestom.server.network.NetworkBuffer.STRING; +import static net.minestom.server.network.NetworkBuffer.*; + +public record EncryptionRequestPacket( + @NotNull String serverId, + byte @NotNull [] publicKey, + byte @NotNull [] verifyToken, + boolean shouldAuthenticate +) implements ServerPacket.Login { -public record EncryptionRequestPacket(@NotNull String serverId, - byte @NotNull [] publicKey, - byte @NotNull [] verifyToken) implements ServerPacket.Login { public EncryptionRequestPacket(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(BYTE_ARRAY), - reader.read(BYTE_ARRAY)); + reader.read(BYTE_ARRAY), + reader.read(BOOLEAN)); } @Override @@ -22,6 +26,7 @@ public record EncryptionRequestPacket(@NotNull String serverId, writer.write(STRING, serverId); writer.write(BYTE_ARRAY, publicKey); writer.write(BYTE_ARRAY, verifyToken); + writer.write(BOOLEAN, shouldAuthenticate); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/DebugSamplePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/DebugSamplePacket.java new file mode 100644 index 000000000..4d70d0185 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/play/DebugSamplePacket.java @@ -0,0 +1,29 @@ +package net.minestom.server.network.packet.server.play; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; + +public record DebugSamplePacket(long @NotNull [] sample, @NotNull Type type) implements ServerPacket.Play { + + public DebugSamplePacket(@NotNull NetworkBuffer buffer) { + this(buffer.read(NetworkBuffer.LONG_ARRAY), buffer.readEnum(Type.class)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.LONG_ARRAY, sample); + writer.writeEnum(Type.class, type); + } + + @Override + public int playId() { + return ServerPacketIdentifier.DEBUG_SAMPLE; + } + + public enum Type { + TICK_TIME + } + +} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java index d239800df..4306d4f0a 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java @@ -42,8 +42,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem @Override public void write(@NotNull NetworkBuffer writer) { writer.writeCollection(recipes, (bWriter, recipe) -> { - bWriter.write(STRING, recipe.type()); bWriter.write(STRING, recipe.recipeId()); + bWriter.write(STRING, recipe.type()); bWriter.write(recipe); }); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityPropertiesPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityAttributesPacket.java similarity index 70% rename from src/main/java/net/minestom/server/network/packet/server/play/EntityPropertiesPacket.java rename to src/main/java/net/minestom/server/network/packet/server/play/EntityAttributesPacket.java index 32b786eb1..78859e440 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityPropertiesPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityAttributesPacket.java @@ -1,12 +1,13 @@ package net.minestom.server.network.packet.server.play; -import net.minestom.server.attribute.Attribute; -import net.minestom.server.attribute.AttributeInstance; -import net.minestom.server.attribute.AttributeModifier; -import net.minestom.server.attribute.AttributeOperation; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.entity.attribute.AttributeInstance; +import net.minestom.server.entity.attribute.AttributeModifier; +import net.minestom.server.entity.attribute.AttributeOperation; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -14,16 +15,19 @@ import java.util.List; import static net.minestom.server.network.NetworkBuffer.*; -public record EntityPropertiesPacket(int entityId, List properties) implements ServerPacket.Play { +public record EntityAttributesPacket(int entityId, List properties) implements ServerPacket.Play { public static final int MAX_ENTRIES = 1024; - public EntityPropertiesPacket { + public EntityAttributesPacket { properties = List.copyOf(properties); } - public EntityPropertiesPacket(@NotNull NetworkBuffer reader) { + public EntityAttributesPacket(@NotNull NetworkBuffer reader) { this(reader.read(VAR_INT), reader.readCollection(r -> { - final Attribute attribute = Attribute.fromKey(reader.read(STRING)); + int id = reader.read(VAR_INT); + final Attribute attribute = Attribute.fromId(id); + Check.notNull(attribute, "Unknown attribute id: " + id); + final double value = reader.read(DOUBLE); int modifierCount = reader.read(VAR_INT); AttributeInstance instance = new AttributeInstance(attribute, null); @@ -42,8 +46,8 @@ public record EntityPropertiesPacket(int entityId, List prope for (AttributeInstance instance : properties) { final Attribute attribute = instance.getAttribute(); - writer.write(STRING, attribute.key()); - writer.write(DOUBLE, (double) instance.getBaseValue()); + writer.write(VAR_INT, attribute.id()); + writer.write(DOUBLE, instance.getBaseValue()); { Collection modifiers = instance.getModifiers(); @@ -60,6 +64,6 @@ public record EntityPropertiesPacket(int entityId, List prope @Override public int playId() { - return ServerPacketIdentifier.ENTITY_PROPERTIES; + return ServerPacketIdentifier.ENTITY_ATTRIBUTES; } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java index 35e97f6f1..3024a943e 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java @@ -1,27 +1,22 @@ package net.minestom.server.network.packet.server.play; -import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.potion.Potion; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; -public record EntityEffectPacket(int entityId, @NotNull Potion potion, - @Nullable CompoundBinaryTag factorCodec) implements ServerPacket.Play { +public record EntityEffectPacket(int entityId, @NotNull Potion potion) implements ServerPacket.Play { public EntityEffectPacket(@NotNull NetworkBuffer reader) { - this(reader.read(VAR_INT), new Potion(reader), - reader.read(BOOLEAN) ? (CompoundBinaryTag) reader.read(NBT) : null); + this(reader.read(VAR_INT), new Potion(reader)); } @Override public void write(@NotNull NetworkBuffer writer) { writer.write(VAR_INT, entityId); writer.write(potion); - writer.writeOptional(NBT, factorCodec); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java index 900785f41..7bbeb39ac 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java @@ -3,7 +3,6 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; import net.minestom.server.entity.Metadata; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket.ComponentHolding; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; @@ -13,7 +12,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.UnaryOperator; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.BYTE; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; public record EntityMetaDataPacket(int entityId, @NotNull Map> entries) implements ServerPacket.Play, ServerPacket.ComponentHolding { @@ -73,7 +73,7 @@ public record EntityMetaDataPacket(int entityId, if (v instanceof Component c) { var translated = operator.apply(c); - entries.put(key, t == Metadata.TYPE_OPTCHAT ? Metadata.OptChat(translated) : Metadata.Chat(translated)); + entries.put(key, t == Metadata.TYPE_OPT_CHAT ? Metadata.OptChat(translated) : Metadata.Chat(translated)); } else { entries.put(key, value); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/JoinGamePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/JoinGamePacket.java index 36528e190..69644648e 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/JoinGamePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/JoinGamePacket.java @@ -15,9 +15,10 @@ import static net.minestom.server.network.NetworkBuffer.*; public record JoinGamePacket( int entityId, boolean isHardcore, List worlds, int maxPlayers, int viewDistance, int simulationDistance, boolean reducedDebugInfo, boolean enableRespawnScreen, - boolean doLimitedCrafting, - String dimensionType, String world, long hashedSeed, GameMode gameMode, GameMode previousGameMode, - boolean isDebug, boolean isFlat, WorldPos deathLocation, int portalCooldown + boolean doLimitedCrafting, int dimensionType, + String world, long hashedSeed, GameMode gameMode, GameMode previousGameMode, + boolean isDebug, boolean isFlat, @Nullable WorldPos deathLocation, int portalCooldown, + boolean enforcesSecureChat ) implements ServerPacket.Play { public static final int MAX_WORLDS = Short.MAX_VALUE; @@ -36,8 +37,7 @@ public record JoinGamePacket( reader.read(BOOLEAN), reader.read(BOOLEAN), reader.read(BOOLEAN), - - reader.read(STRING), + reader.read(VAR_INT), reader.read(STRING), reader.read(LONG), GameMode.fromId(reader.read(BYTE)), @@ -45,7 +45,8 @@ public record JoinGamePacket( reader.read(BOOLEAN), reader.read(BOOLEAN), reader.read(DEATH_LOCATION), - reader.read(VAR_INT) + reader.read(VAR_INT), + reader.read(BOOLEAN) ); } @@ -60,8 +61,7 @@ public record JoinGamePacket( writer.write(BOOLEAN, reducedDebugInfo); writer.write(BOOLEAN, enableRespawnScreen); writer.write(BOOLEAN, doLimitedCrafting); - - writer.write(STRING, dimensionType); + writer.write(VAR_INT, dimensionType); writer.write(STRING, world); writer.write(LONG, hashedSeed); writer.write(BYTE, gameMode.id()); @@ -74,6 +74,7 @@ public record JoinGamePacket( writer.write(BOOLEAN, isFlat); writer.write(DEATH_LOCATION, deathLocation); writer.write(VAR_INT, portalCooldown); + writer.write(BOOLEAN, enforcesSecureChat); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ProjectilePowerPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ProjectilePowerPacket.java new file mode 100644 index 000000000..e147b15fa --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/play/ProjectilePowerPacket.java @@ -0,0 +1,28 @@ +package net.minestom.server.network.packet.server.play; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; + +public record ProjectilePowerPacket( + int entityId, @NotNull Point power +) implements ServerPacket.Play { + + public ProjectilePowerPacket(@NotNull NetworkBuffer buffer) { + this(buffer.read(NetworkBuffer.VAR_INT), buffer.read(NetworkBuffer.VECTOR3D)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.VAR_INT, entityId); + writer.write(NetworkBuffer.VECTOR3D, power); + } + + @Override + public int playId() { + return ServerPacketIdentifier.PROJECTILE_POWER; + } + +} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/RespawnPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/RespawnPacket.java index 12eb01e4e..4fa239837 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/RespawnPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/RespawnPacket.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; import static net.minestom.server.network.NetworkBuffer.*; public record RespawnPacket( - String dimensionType, String worldName, + int dimensionType, String worldName, long hashedSeed, GameMode gameMode, GameMode previousGameMode, boolean isDebug, boolean isFlat, WorldPos deathLocation, int portalCooldown, int copyData @@ -21,7 +21,7 @@ public record RespawnPacket( public static final int COPY_ALL = COPY_ATTRIBUTES | COPY_METADATA; public RespawnPacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), reader.read(STRING), + this(reader.read(VAR_INT), reader.read(STRING), reader.read(LONG), GameMode.fromId(reader.read(BYTE)), GameMode.fromId(reader.read(BYTE)), reader.read(BOOLEAN), reader.read(BOOLEAN), @@ -31,7 +31,7 @@ public record RespawnPacket( @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(STRING, dimensionType); + writer.write(VAR_INT, dimensionType); writer.write(STRING, worldName); writer.write(LONG, hashedSeed); writer.write(BYTE, gameMode.id()); diff --git a/src/main/java/net/minestom/server/potion/Potion.java b/src/main/java/net/minestom/server/potion/Potion.java index 6ad326aa0..4f367f1e9 100644 --- a/src/main/java/net/minestom/server/potion/Potion.java +++ b/src/main/java/net/minestom/server/potion/Potion.java @@ -48,6 +48,11 @@ public record Potion(@NotNull PotionEffect effect, byte amplifier, */ public static final byte ICON_FLAG = 0x04; + /** + * A flag instructing the client to use its builtin blending effect, only used with the darkness effect currently. + */ + public static final byte BLEND_FLAG = 0x08; + /** * A duration constant which sets a Potion duration to infinite. */ @@ -106,6 +111,10 @@ public record Potion(@NotNull PotionEffect effect, byte amplifier, return (flags & ICON_FLAG) == ICON_FLAG; } + public boolean hasBlend() { + return (flags & BLEND_FLAG) == BLEND_FLAG; + } + /** * Sends a packet that a potion effect has been applied to the entity. *

@@ -114,7 +123,7 @@ public record Potion(@NotNull PotionEffect effect, byte amplifier, * @param entity the entity to add the effect to */ public void sendAddPacket(@NotNull Entity entity) { - entity.sendPacketToViewersAndSelf(new EntityEffectPacket(entity.getEntityId(), this, null)); + entity.sendPacketToViewersAndSelf(new EntityEffectPacket(entity.getEntityId(), this)); } /** diff --git a/src/main/java/net/minestom/server/recipe/RecipeManager.java b/src/main/java/net/minestom/server/recipe/RecipeManager.java index 3dac248fb..80587c262 100644 --- a/src/main/java/net/minestom/server/recipe/RecipeManager.java +++ b/src/main/java/net/minestom/server/recipe/RecipeManager.java @@ -57,7 +57,7 @@ public class RecipeManager { case SMITHING_TRIM -> RecipeConversion.smithingTrim((SmithingTrimRecipe) recipe); }); } - return new DeclareRecipesPacket(entries); + return new DeclareRecipesPacket(List.of()); } } diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 93c247089..14868a24a 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -78,6 +78,11 @@ public final class Registry { return new TrimPatternEntry(namespace, main, null); } + @ApiStatus.Internal + public static AttributeEntry attribute(String namespace, @NotNull Properties main) { + return new AttributeEntry(namespace, main, null); + } + @ApiStatus.Internal public static Map> load(Resource resource) { Map> map = new HashMap<>(); @@ -223,7 +228,8 @@ public final class Registry { FLUID_TAGS("tags/fluid_tags.json"), GAMEPLAY_TAGS("tags/gameplay_tags.json"), ITEM_TAGS("tags/item_tags.json"), - BIOMES("biomes.json"); + BIOMES("biomes.json"), + ATTRIBUTES("attributes.json"); private final String name; @@ -679,6 +685,23 @@ public final class Registry { } } + public record AttributeEntry(NamespaceID namespace, int id, + String translationKey, double defaultValue, + boolean clientSync, + double maxValue, double minValue, + Properties custom) implements Entry { + public AttributeEntry(String namespace, Properties main, Properties custom) { + this(NamespaceID.from(namespace), + main.getInt("id"), + main.getString("translationKey"), + main.getDouble("defaultValue"), + main.getBoolean("clientSync"), + main.getDouble("maxValue"), + main.getDouble("minValue"), + custom); + } + } + public interface Entry { @ApiStatus.Experimental Properties custom(); diff --git a/src/main/java/net/minestom/server/world/DimensionType.java b/src/main/java/net/minestom/server/world/DimensionType.java index c48aed029..7a37771b5 100644 --- a/src/main/java/net/minestom/server/world/DimensionType.java +++ b/src/main/java/net/minestom/server/world/DimensionType.java @@ -1,6 +1,7 @@ package net.minestom.server.world; import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -109,13 +110,8 @@ public class DimensionType { .build(); } - @NotNull - public CompoundBinaryTag toIndexedNBT() { - return CompoundBinaryTag.builder() - .putString("name", name.toString()) - .putInt("id", id) - .put("element", toNBT()) - .build(); + public @NotNull RegistryDataPacket.Entry toRegistryEntry() { + return new RegistryDataPacket.Entry(name.toString(), toNBT()); } @NotNull diff --git a/src/main/java/net/minestom/server/world/DimensionTypeManager.java b/src/main/java/net/minestom/server/world/DimensionTypeManager.java index 4627851f3..181dc5c03 100644 --- a/src/main/java/net/minestom/server/world/DimensionTypeManager.java +++ b/src/main/java/net/minestom/server/world/DimensionTypeManager.java @@ -1,8 +1,8 @@ package net.minestom.server.world; -import net.kyori.adventure.nbt.BinaryTagTypes; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.ListBinaryTag; +import net.minestom.server.network.packet.server.CachedPacket; +import net.minestom.server.network.packet.server.SendablePacket; +import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,6 +18,7 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public final class DimensionTypeManager { + private final CachedPacket registryDataPacket = new CachedPacket(this::createRegistryDataPacket); private final List dimensionTypes = new CopyOnWriteArrayList<>(); public DimensionTypeManager() { @@ -32,6 +33,7 @@ public final class DimensionTypeManager { public void addDimension(@NotNull DimensionType dimensionType) { dimensionType.registered = true; this.dimensionTypes.add(dimensionType); + registryDataPacket.invalidate(); } /** @@ -42,7 +44,9 @@ public final class DimensionTypeManager { */ public boolean removeDimension(@NotNull DimensionType dimensionType) { dimensionType.registered = false; - return dimensionTypes.remove(dimensionType); + boolean removed = dimensionTypes.remove(dimensionType); + if (removed) registryDataPacket.invalidate(); + return removed; } /** @@ -80,20 +84,16 @@ public final class DimensionTypeManager { return Collections.unmodifiableList(dimensionTypes); } - /** - * Creates the {@link CompoundBinaryTag} containing all the registered dimensions. - *

- * Used when a player connects. - * - * @return an nbt compound containing the registered dimensions - */ - public @NotNull CompoundBinaryTag toNBT() { - ListBinaryTag.Builder entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); - for (DimensionType dimensionType : dimensionTypes) - entries.add(dimensionType.toIndexedNBT()); - return CompoundBinaryTag.builder() - .putString("type", "minecraft:dimension_type") - .put("value", entries.build()) - .build(); + public @NotNull SendablePacket registryDataPacket() { + return registryDataPacket; + } + + private @NotNull RegistryDataPacket createRegistryDataPacket() { + return new RegistryDataPacket( + "minecraft:dimension_type", + dimensionTypes.stream() + .map(DimensionType::toRegistryEntry) + .toList() + ); } } diff --git a/src/main/java/net/minestom/server/world/biomes/Biome.java b/src/main/java/net/minestom/server/world/biomes/Biome.java index 0372d6e03..9f43b0cb5 100644 --- a/src/main/java/net/minestom/server/world/biomes/Biome.java +++ b/src/main/java/net/minestom/server/world/biomes/Biome.java @@ -2,6 +2,7 @@ package net.minestom.server.world.biomes; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.coordinate.Point; +import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.registry.ProtocolObject; import net.minestom.server.registry.Registry; import net.minestom.server.utils.NamespaceID; @@ -53,6 +54,10 @@ sealed public interface Biome extends ProtocolObject permits BiomeImpl { } } + default @NotNull RegistryDataPacket.Entry toRegistryEntry() { + return new RegistryDataPacket.Entry(namespace().toString(), toNbt()); + } + default @NotNull CompoundBinaryTag toNbt() { Check.notNull(name(), "The biome namespace cannot be null"); Check.notNull(effects(), "The biome effects cannot be null"); diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java index 9569ca9f6..2a787db0c 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java @@ -1,8 +1,8 @@ package net.minestom.server.world.biomes; -import net.kyori.adventure.nbt.BinaryTagTypes; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.ListBinaryTag; +import net.minestom.server.network.packet.server.CachedPacket; +import net.minestom.server.network.packet.server.SendablePacket; +import net.minestom.server.network.packet.server.configuration.RegistryDataPacket; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; @@ -10,9 +10,10 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CopyOnWriteArrayList; /** @@ -20,12 +21,13 @@ import java.util.concurrent.atomic.AtomicInteger; *

*/ public final class BiomeManager { - private final Map biomes = new ConcurrentHashMap<>(); + private final CachedPacket registryDataPacket = new CachedPacket(this::createRegistryDataPacket); + + private final List biomes = new CopyOnWriteArrayList<>(); private final Map biomesByName = new ConcurrentHashMap<>(); private final Map idMappings = new ConcurrentHashMap<>(); private final AtomicInteger ID_COUNTER = new AtomicInteger(0); - private CompoundBinaryTag nbtCache = null; public BiomeManager() { // Need to register plains for the client to work properly @@ -48,27 +50,28 @@ public final class BiomeManager { public void addBiome(@NotNull Biome biome) { Check.stateCondition(getByName(biome.namespace()) != null, "The biome " + biome.namespace() + " has already been registered"); - var id = ID_COUNTER.getAndIncrement(); - this.biomes.put(id, biome); + var id = this.biomes.size(); + this.biomes.add(biome); this.biomesByName.put(biome.namespace(), biome); this.idMappings.put(biome.namespace(), id); - nbtCache = null; + registryDataPacket.invalidate(); } - /** - * Removes a biome. This does NOT send the new list to players. - * - * @param biome the biome to remove - */ - public void removeBiome(@NotNull Biome biome) { - var id = idMappings.get(biome.namespace()); - if (id != null) { - biomes.remove(id); - biomesByName.remove(biome.namespace()); - idMappings.remove(biome.namespace()); - nbtCache = null; - } - } + //todo supporting remove is probably challenging at best since we no longer send ids explicitly, so you cannot skip an ID. +// /** +// * Removes a biome. This does NOT send the new list to players. +// * +// * @param biome the biome to remove +// */ +// public void removeBiome(@NotNull Biome biome) { +// var id = idMappings.get(biome.namespace()); +// if (id != null) { +// biomes.remove(id.intValue()); +// biomesByName.remove(biome.namespace()); +// idMappings.remove(biome.namespace()); +// registryDataPacket.invalidate(); +// } +// } /** * Returns an immutable copy of the biomes already registered. @@ -76,7 +79,7 @@ public final class BiomeManager { * @return an immutable copy of the biomes already registered */ public Collection unmodifiableCollection() { - return Collections.unmodifiableCollection(biomes.values()); + return Collections.unmodifiableCollection(biomes); } /** @@ -101,24 +104,6 @@ public final class BiomeManager { return getByName(namespace); } - public @NotNull CompoundBinaryTag toNBT() { - if (nbtCache != null) return nbtCache; - - ListBinaryTag.Builder entries = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); - for (Biome biome : biomes.values()) { - entries.add(CompoundBinaryTag.builder() - .putInt("id", getId(biome)) - .putString("name", biome.namespace().toString()) - .put("element", biome.toNbt()) - .build()); - } - nbtCache = CompoundBinaryTag.builder() - .putString("type", "minecraft:worldgen/biome") - .put("value", entries.build()) - .build(); - return nbtCache; - } - /** * Gets the id of a biome. *` @@ -128,4 +113,32 @@ public final class BiomeManager { public int getId(Biome biome) { return idMappings.getOrDefault(biome.namespace(), -1); } +// +// public @NotNull NBTCompound toNBT() { +// if (nbtCache != null) return nbtCache; +// nbtCache = NBT.Compound(Map.of( +// "type", NBT.String("minecraft:worldgen/biome"), +// "value", NBT.List(NBTType.TAG_Compound, biomes.values().stream().map(biome -> { +// return NBT.Compound(Map.of( +// "id", NBT.Int(getId(biome)), +// "name", NBT.String(biome.namespace().toString()), +// "element", biome.toNbt() +// )); +// }).toList()))); +// +// return nbtCache; +// } + + public @NotNull SendablePacket registryDataPacket() { + return registryDataPacket; + } + + private @NotNull RegistryDataPacket createRegistryDataPacket() { + return new RegistryDataPacket( + "minecraft:worldgen/biome", + biomes.stream() + .map(Biome::toRegistryEntry) + .toList() + ); + } } diff --git a/src/test/java/net/minestom/server/entity/EntityMetaIntegrationTest.java b/src/test/java/net/minestom/server/entity/EntityMetaIntegrationTest.java index d4bc0029e..ce434dd34 100644 --- a/src/test/java/net/minestom/server/entity/EntityMetaIntegrationTest.java +++ b/src/test/java/net/minestom/server/entity/EntityMetaIntegrationTest.java @@ -1,10 +1,10 @@ package net.minestom.server.entity; import net.kyori.adventure.text.Component; -import net.minestom.testing.Env; -import net.minestom.testing.EnvTest; import net.minestom.server.coordinate.Pos; import net.minestom.server.network.packet.server.play.EntityMetaDataPacket; +import net.minestom.testing.Env; +import net.minestom.testing.EnvTest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -105,7 +105,7 @@ public class EntityMetaIntegrationTest { //This is first test, and it is not related to "custom name" bug. Therefore, it should work. var packets = incomingPackets.collect(); validMetaDataPackets(packets, entity.getEntityId(), entry -> { - if (entry.type() != Metadata.TYPE_OPTCHAT) return; + if (entry.type() != Metadata.TYPE_OPT_CHAT) return; assertEquals(Component.text("Custom Name"), entry.value()); }); @@ -127,7 +127,7 @@ public class EntityMetaIntegrationTest { //Listen packets to check if entity name is "Custom Name 2". packets = incomingPackets.collect(); validMetaDataPackets(packets, entity.getEntityId(), entry -> { - if (entry.type() != Metadata.TYPE_OPTCHAT) return; + if (entry.type() != Metadata.TYPE_OPT_CHAT) return; assertEquals(Component.text("Custom Name 2"), entry.value()); }); } diff --git a/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java b/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java index 227dc8813..c96fefddb 100644 --- a/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java +++ b/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java @@ -23,7 +23,6 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; -import static net.minestom.server.entity.Player.*; import static org.junit.jupiter.api.Assertions.*; @EnvTest @@ -119,7 +118,7 @@ public class PlayerIntegrationTest { final var packets = List.of( JoinGamePacket.class, ServerDifficultyPacket.class, SpawnPositionPacket.class, - DeclareCommandsPacket.class, EntityPropertiesPacket.class, EntityStatusPacket.class, + DeclareCommandsPacket.class, EntityAttributesPacket.class, EntityStatusPacket.class, UpdateHealthPacket.class, PlayerAbilitiesPacket.class ); final List> trackers = new ArrayList<>(); diff --git a/src/test/java/net/minestom/server/item/ItemAttributeTest.java b/src/test/java/net/minestom/server/item/ItemAttributeTest.java index 4111ba131..f7c8d82a3 100644 --- a/src/test/java/net/minestom/server/item/ItemAttributeTest.java +++ b/src/test/java/net/minestom/server/item/ItemAttributeTest.java @@ -1,13 +1,10 @@ package net.minestom.server.item; import net.minestom.server.attribute.Attribute; -import net.minestom.server.attribute.AttributeOperation; +import net.minestom.server.entity.attribute.AttributeOperation; import net.minestom.server.item.attribute.AttributeSlot; import net.minestom.server.item.attribute.ItemAttribute; import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagWritable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java index 0e5fe3594..4d4df24c6 100644 --- a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java +++ b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java @@ -1,12 +1,8 @@ package net.minestom.server.network; import com.google.gson.JsonObject; - -import java.io.PrintStream; - import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.text.Component; -import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.GameMode; @@ -140,7 +136,7 @@ public class PacketWriteReadTest { SERVER_PACKETS.add(new EntityMetaDataPacket(5, Map.of(1, Metadata.VarInt(5)))); SERVER_PACKETS.add(new EntityPositionAndRotationPacket(5, (short) 0, (short) 0, (short) 0, 45f, 45f, false)); SERVER_PACKETS.add(new EntityPositionPacket(5, (short) 0, (short) 0, (short) 0, true)); - SERVER_PACKETS.add(new EntityPropertiesPacket(5, List.of())); + SERVER_PACKETS.add(new EntityAttributesPacket(5, List.of())); SERVER_PACKETS.add(new EntityRotationPacket(5, 45f, 45f, false)); final PlayerSkin skin = new PlayerSkin("hh", "hh"); From 58367bb6de58314814bca36a857531d55d6918c3 Mon Sep 17 00:00:00 2001 From: mworzala Date: Wed, 10 Apr 2024 00:51:51 -0400 Subject: [PATCH 17/46] fix: oops, health is before potion still --- .../entity/metadata/LivingEntityMeta.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java b/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java index ee841f83a..34caaa496 100644 --- a/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/LivingEntityMeta.java @@ -47,28 +47,36 @@ public class LivingEntityMeta extends EntityMeta { setMaskBit(OFFSET, IS_IN_SPIN_ATTACK_BIT, value); } + public float getHealth() { + return super.metadata.getIndex(OFFSET + 1, 1F); + } + + public void setHealth(float value) { + super.metadata.setIndex(OFFSET + 1, Metadata.Float(value)); + } + public @NotNull List getEffectParticles() { - return super.metadata.getIndex(OFFSET + 1, List.of()); + return super.metadata.getIndex(OFFSET + 2, List.of()); } public void setEffectParticles(@NotNull List value) { - super.metadata.setIndex(OFFSET + 1, Metadata.ParticleList(value)); + super.metadata.setIndex(OFFSET + 2, Metadata.ParticleList(value)); } public boolean isPotionEffectAmbient() { - return super.metadata.getIndex(OFFSET + 2, false); + return super.metadata.getIndex(OFFSET + 3, false); } public void setPotionEffectAmbient(boolean value) { - super.metadata.setIndex(OFFSET + 2, Metadata.Boolean(value)); + super.metadata.setIndex(OFFSET + 3, Metadata.Boolean(value)); } public int getArrowCount() { - return super.metadata.getIndex(OFFSET + 3, 0); + return super.metadata.getIndex(OFFSET + 4, 0); } public void setArrowCount(int value) { - super.metadata.setIndex(OFFSET + 3, Metadata.VarInt(value)); + super.metadata.setIndex(OFFSET + 4, Metadata.VarInt(value)); } /** @@ -76,7 +84,7 @@ public class LivingEntityMeta extends EntityMeta { * @return The amount of bee stingers */ public int getBeeStingerCount() { - return super.metadata.getIndex(OFFSET + 4, 0); + return super.metadata.getIndex(OFFSET + 5, 0); } /** @@ -84,15 +92,7 @@ public class LivingEntityMeta extends EntityMeta { * @param value The amount of bee stingers to set, use 0 to clear all stingers */ public void setBeeStingerCount(int value) { - super.metadata.setIndex(OFFSET + 4, Metadata.VarInt(value)); - } - - public float getHealth() { - return super.metadata.getIndex(OFFSET + 5, 1F); - } - - public void setHealth(float value) { - super.metadata.setIndex(OFFSET + 5, Metadata.Float(value)); + super.metadata.setIndex(OFFSET + 5, Metadata.VarInt(value)); } @Nullable From d6d2e9c3e79d565e1709a9e5cd4c59dd89a584fe Mon Sep 17 00:00:00 2001 From: mworzala Date: Wed, 10 Apr 2024 09:00:31 -0400 Subject: [PATCH 18/46] chore: rebase on adventure-nbt --- .../java/net/minestom/server/item/component/CustomData.java | 6 +++--- .../net/minestom/server/network/NetworkBufferTypeImpl.java | 3 +-- .../packet/server/configuration/RegistryDataPacket.java | 1 - .../java/net/minestom/server/world/biomes/BiomeManager.java | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/minestom/server/item/component/CustomData.java b/src/main/java/net/minestom/server/item/component/CustomData.java index 0c00926ea..012ea1d0f 100644 --- a/src/main/java/net/minestom/server/item/component/CustomData.java +++ b/src/main/java/net/minestom/server/item/component/CustomData.java @@ -1,11 +1,11 @@ package net.minestom.server.item.component; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.tag.Tag; import org.jetbrains.annotations.NotNull; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; -public record CustomData(@NotNull NBTCompound nbt) implements ItemComponent { +public record CustomData(@NotNull CompoundBinaryTag nbt) implements ItemComponent { static final Tag TAG = Tag.Structure("ab", CustomData.class); static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { @@ -16,7 +16,7 @@ public record CustomData(@NotNull NBTCompound nbt) implements ItemComponent { @Override public CustomData read(@NotNull NetworkBuffer buffer) { - return new CustomData((NBTCompound) buffer.read(NetworkBuffer.NBT)); + return new CustomData((CompoundBinaryTag) buffer.read(NetworkBuffer.NBT)); } }; diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java index 6347280d2..0b1fcff0b 100644 --- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java +++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java @@ -2,7 +2,6 @@ package net.minestom.server.network; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.EndBinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; @@ -420,7 +419,7 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { buffer.read(VAR_INT); // Added components buffer.read(VAR_INT); // Removed components - return ItemStack.fromNBT(material, new NBTCompound(), count); + return ItemStack.fromNBT(material, CompoundBinaryTag.empty(), count); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java index df09adb35..cf0fc3eac 100644 --- a/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/configuration/RegistryDataPacket.java @@ -6,7 +6,6 @@ import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.List; diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java index 2a787db0c..b7d39dfe4 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java @@ -14,11 +14,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; - +import java.util.concurrent.atomic.AtomicInteger; /** * Allows servers to register custom dimensions. Also used during player joining to send the list of all existing dimensions. - *

*/ public final class BiomeManager { private final CachedPacket registryDataPacket = new CachedPacket(this::createRegistryDataPacket); From e8d4f79ee3d02df5ab2788cb88f83855d8831ed8 Mon Sep 17 00:00:00 2001 From: mworzala Date: Wed, 10 Apr 2024 21:42:15 -0400 Subject: [PATCH 19/46] feat: first version of a bunch of components --- .../java/net/minestom/server/ServerFlag.java | 1 - .../entity/attribute/AttributeModifier.java | 50 ++-- .../minestom/server/item/ItemHideFlag.java | 1 + .../net/minestom/server/item/ItemMeta.java | 60 ++--- .../minestom/server/item/ItemMetaImpl.java | 190 ++++++++++++-- .../minestom/server/item/ItemMetaView.java | 9 +- .../server/item/ItemMetaViewImpl.java | 16 +- .../minestom/server/item/ItemSerializers.java | 1 + .../net/minestom/server/item/ItemStack.java | 240 ++++++++++-------- .../minestom/server/item/ItemStackImpl.java | 190 +++++++------- .../net/minestom/server/item/ItemTags.java | 33 --- .../net/minestom/server/item/Material.java | 31 ++- .../minestom/server/item/StackingRule.java | 70 ----- .../server/item/attribute/AttributeSlot.java | 5 +- .../server/item/component/AttributeList.java | 101 ++++++++ .../server/item/component/CustomData.java | 20 +- .../server/item/component/DyedItemColor.java | 57 +++++ .../item/component/EnchantmentList.java | 81 ++++++ .../item/component/FireworkExplosion.java | 89 +++++++ .../server/item/component/FireworkList.java | 62 +++++ .../server/item/component/ItemComponent.java | 89 ++++++- .../item/component/ItemComponentImpl.java | 47 ++++ .../item/component/ItemComponentMap.java | 32 +++ .../item/component/ItemComponentPatch.java | 37 +++ .../server/item/component/ItemRarity.java | 47 ++++ .../item/component/LodestoneTracker.java | 53 ++++ .../server/item/component/MapDecorations.java | 44 ++++ .../item/component/MapPostProcessing.java | 21 ++ .../server/item/component/PotDecorations.java | 40 +++ .../item/component/SeededContainerLoot.java | 16 ++ .../server/item/component/Unbreakable.java | 28 ++ .../server/item/firework/FireworkEffect.java | 15 ++ .../item/firework/FireworkEffectType.java | 23 ++ .../server/item/metadata/BundleMeta.java | 34 +-- .../server/item/metadata/CompassMeta.java | 56 ++-- .../server/item/metadata/CrossbowMeta.java | 34 +-- .../item/metadata/EnchantedBookMeta.java | 50 ++-- .../item/metadata/FireworkEffectMeta.java | 27 +- .../server/item/metadata/FireworkMeta.java | 34 +-- .../item/metadata/LeatherArmorMeta.java | 28 +- .../server/item/metadata/MapMeta.java | 18 +- .../server/item/metadata/PlayerHeadMeta.java | 18 +- .../server/item/metadata/PotionMeta.java | 18 +- .../item/metadata/WritableBookMeta.java | 14 +- .../server/item/metadata/WrittenBookMeta.java | 15 +- .../server/item/rule/VanillaStackingRule.java | 40 --- .../server/network/NetworkBuffer.java | 18 +- .../server/network/NetworkBufferTypeImpl.java | 30 ++- .../minestom/server/registry/Registry.java | 120 ++++----- .../net/minestom/server/tag/Serializers.java | 24 +- .../java/net/minestom/server/tag/Tag.java | 1 - .../minestom/server/utils/UniqueIdUtils.java | 28 ++ .../server/utils/nbt/BinaryTagReader.java | 1 - .../server/utils/nbt/BinaryTagSerializer.java | 119 +++++++++ .../server/utils/nbt/BinaryTagWriter.java | 1 - 55 files changed, 1817 insertions(+), 710 deletions(-) delete mode 100644 src/main/java/net/minestom/server/item/ItemTags.java delete mode 100644 src/main/java/net/minestom/server/item/StackingRule.java create mode 100644 src/main/java/net/minestom/server/item/component/AttributeList.java create mode 100644 src/main/java/net/minestom/server/item/component/DyedItemColor.java create mode 100644 src/main/java/net/minestom/server/item/component/EnchantmentList.java create mode 100644 src/main/java/net/minestom/server/item/component/FireworkExplosion.java create mode 100644 src/main/java/net/minestom/server/item/component/FireworkList.java create mode 100644 src/main/java/net/minestom/server/item/component/ItemComponentImpl.java create mode 100644 src/main/java/net/minestom/server/item/component/ItemComponentMap.java create mode 100644 src/main/java/net/minestom/server/item/component/ItemComponentPatch.java create mode 100644 src/main/java/net/minestom/server/item/component/ItemRarity.java create mode 100644 src/main/java/net/minestom/server/item/component/LodestoneTracker.java create mode 100644 src/main/java/net/minestom/server/item/component/MapDecorations.java create mode 100644 src/main/java/net/minestom/server/item/component/MapPostProcessing.java create mode 100644 src/main/java/net/minestom/server/item/component/PotDecorations.java create mode 100644 src/main/java/net/minestom/server/item/component/SeededContainerLoot.java create mode 100644 src/main/java/net/minestom/server/item/component/Unbreakable.java delete mode 100644 src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java create mode 100644 src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java diff --git a/src/main/java/net/minestom/server/ServerFlag.java b/src/main/java/net/minestom/server/ServerFlag.java index cf6cb1749..cb5c70db1 100644 --- a/src/main/java/net/minestom/server/ServerFlag.java +++ b/src/main/java/net/minestom/server/ServerFlag.java @@ -42,7 +42,6 @@ public final class ServerFlag { public static final @NotNull String AUTH_URL = System.getProperty("minestom.auth.url", "https://sessionserver.mojang.com/session/minecraft/hasJoined"); // World - public static final @Nullable String STACKING_RULE = System.getProperty("minestom.stacking-rule"); public static final int WORLD_BORDER_SIZE = Integer.getInteger("minestom.world-border-size", 29999984); // Maps diff --git a/src/main/java/net/minestom/server/entity/attribute/AttributeModifier.java b/src/main/java/net/minestom/server/entity/attribute/AttributeModifier.java index 9e3f90c18..c25827bf9 100644 --- a/src/main/java/net/minestom/server/entity/attribute/AttributeModifier.java +++ b/src/main/java/net/minestom/server/entity/attribute/AttributeModifier.java @@ -1,5 +1,6 @@ package net.minestom.server.entity.attribute; +import net.minestom.server.network.NetworkBuffer; import org.jetbrains.annotations.NotNull; import java.util.UUID; @@ -7,12 +8,12 @@ import java.util.UUID; /** * Represent an attribute modifier. */ -public class AttributeModifier { - - private final double amount; - private final String name; - private final AttributeOperation operation; - private final UUID id; +public record AttributeModifier( + @NotNull UUID id, + @NotNull String name, + double amount, + @NotNull AttributeOperation operation +) implements NetworkBuffer.Writer { /** * Creates a new modifier with a random id. @@ -25,19 +26,17 @@ public class AttributeModifier { this(UUID.randomUUID(), name, amount, operation); } - /** - * Creates a new modifier. - * - * @param id the id of this modifier - * @param name the name of this modifier - * @param amount the value of this modifier - * @param operation the operation to apply this modifier with - */ - public AttributeModifier(@NotNull UUID id, @NotNull String name, double amount, @NotNull AttributeOperation operation) { - this.id = id; - this.name = name; - this.amount = amount; - this.operation = operation; + public AttributeModifier(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.UUID), reader.read(NetworkBuffer.STRING), + reader.read(NetworkBuffer.DOUBLE), reader.readEnum(AttributeOperation.class)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.UUID, id); + writer.write(NetworkBuffer.STRING, name); + writer.write(NetworkBuffer.DOUBLE, amount); + writer.writeEnum(AttributeOperation.class, operation); } /** @@ -45,8 +44,8 @@ public class AttributeModifier { * * @return the id of this modifier */ - @NotNull - public UUID getId() { + @Deprecated + public @NotNull UUID getId() { return id; } @@ -55,8 +54,8 @@ public class AttributeModifier { * * @return the name of this modifier */ - @NotNull - public String getName() { + @Deprecated + public @NotNull String getName() { return name; } @@ -65,6 +64,7 @@ public class AttributeModifier { * * @return the value of this modifier */ + @Deprecated public double getAmount() { return amount; } @@ -74,8 +74,8 @@ public class AttributeModifier { * * @return the operation of this modifier */ - @NotNull - public AttributeOperation getOperation() { + @Deprecated + public @NotNull AttributeOperation getOperation() { return operation; } } diff --git a/src/main/java/net/minestom/server/item/ItemHideFlag.java b/src/main/java/net/minestom/server/item/ItemHideFlag.java index 92087b072..ad1f95781 100644 --- a/src/main/java/net/minestom/server/item/ItemHideFlag.java +++ b/src/main/java/net/minestom/server/item/ItemHideFlag.java @@ -3,6 +3,7 @@ package net.minestom.server.item; /** * Represents a hide flag which can be applied to an {@link ItemStack} using {@link ItemMeta.Builder#hideFlag(int)}. */ +@Deprecated public enum ItemHideFlag { HIDE_ENCHANTS, HIDE_ATTRIBUTES, diff --git a/src/main/java/net/minestom/server/item/ItemMeta.java b/src/main/java/net/minestom/server/item/ItemMeta.java index b0c17517c..8d1d64daa 100644 --- a/src/main/java/net/minestom/server/item/ItemMeta.java +++ b/src/main/java/net/minestom/server/item/ItemMeta.java @@ -4,8 +4,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.instance.block.Block; import net.minestom.server.item.attribute.ItemAttribute; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagReadable; import net.minestom.server.tag.Taggable; @@ -17,8 +16,10 @@ import org.jetbrains.annotations.UnknownNullability; import java.util.*; import java.util.function.Consumer; -public sealed interface ItemMeta extends TagReadable, NetworkBuffer.Writer - permits ItemMetaImpl { +@Deprecated +public sealed interface ItemMeta extends TagReadable permits ItemMetaImpl { + + @NotNull ItemComponentPatch components(); @Override @UnknownNullability T getTag(@NotNull Tag tag); @@ -31,69 +32,48 @@ public sealed interface ItemMeta extends TagReadable, NetworkBuffer.Writer @NotNull String toSNBT(); @Contract(pure = true) - default int getDamage() { - return getTag(ItemTags.DAMAGE); - } + int getDamage(); @Contract(pure = true) - default boolean isUnbreakable() { - return getTag(ItemTags.UNBREAKABLE); - } + boolean isUnbreakable(); @Contract(pure = true) - default int getHideFlag() { - return getTag(ItemTags.HIDE_FLAGS); - } + int getHideFlag(); @Contract(pure = true) - default @Nullable Component getDisplayName() { - return getTag(ItemTags.NAME); - } + @Nullable Component getDisplayName(); @Contract(pure = true) - default @NotNull List<@NotNull Component> getLore() { - return getTag(ItemTags.LORE); - } + @NotNull List<@NotNull Component> getLore(); @Contract(pure = true) - default @NotNull Map getEnchantmentMap() { - return getTag(ItemTags.ENCHANTMENTS); - } + @NotNull Map getEnchantmentMap(); @Contract(pure = true) - default @NotNull List<@NotNull ItemAttribute> getAttributes() { - return getTag(ItemTags.ATTRIBUTES); - } + @NotNull List<@NotNull ItemAttribute> getAttributes(); @Contract(pure = true) - default int getCustomModelData() { - return getTag(ItemTags.CUSTOM_MODEL_DATA); - } + int getCustomModelData(); @Contract(pure = true) - default @NotNull Set<@NotNull String> getCanDestroy() { - return Set.copyOf(getTag(ItemTags.CAN_DESTROY)); - } + @NotNull Set<@NotNull String> getCanDestroy(); @Contract(pure = true) - default boolean canDestroy(@NotNull Block block) { - return getCanDestroy().contains(block.name()); - } + boolean canDestroy(@NotNull Block block); @Contract(pure = true) - default @NotNull Set<@NotNull String> getCanPlaceOn() { - return Set.copyOf(getTag(ItemTags.CAN_PLACE_ON)); - } + @NotNull Set<@NotNull String> getCanPlaceOn(); @Contract(pure = true) - default boolean canPlaceOn(@NotNull Block block) { - return getCanPlaceOn().contains(block.name()); - } + boolean canPlaceOn(@NotNull Block block); + @Deprecated sealed interface Builder extends Taggable permits ItemMetaImpl.Builder, ItemMetaView.Builder { @NotNull ItemMeta build(); + @NotNull ItemComponentPatch.Builder components(); + default @NotNull Builder set(@NotNull Tag tag, @Nullable T value) { setTag(tag, value); return this; diff --git a/src/main/java/net/minestom/server/item/ItemMetaImpl.java b/src/main/java/net/minestom/server/item/ItemMetaImpl.java index 063b40111..e1b7cd50a 100644 --- a/src/main/java/net/minestom/server/item/ItemMetaImpl.java +++ b/src/main/java/net/minestom/server/item/ItemMetaImpl.java @@ -2,36 +2,38 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.TagStringIO; -import net.minestom.server.network.NetworkBuffer; +import net.kyori.adventure.text.Component; +import net.minestom.server.instance.block.Block; +import net.minestom.server.item.attribute.ItemAttribute; +import net.minestom.server.item.component.*; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import java.io.IOException; -import java.util.Objects; +import java.util.*; import java.util.function.Consumer; -import static net.minestom.server.network.NetworkBuffer.NBT; - -record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta { - static final ItemMetaImpl EMPTY = new ItemMetaImpl(TagHandler.newHandler()); +@Deprecated +record ItemMetaImpl(ItemComponentPatch components) implements ItemMeta { @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return tagHandler.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } @Override public @NotNull ItemMeta with(@NotNull Consumer builderConsumer) { - Builder builder = new Builder(tagHandler.copy()); + Builder builder = new Builder(components.builder()); builderConsumer.accept(builder); return builder.build(); } @Override public @NotNull CompoundBinaryTag toNBT() { - return tagHandler.asCompound(); + return components.asCompound(); } @Override @@ -44,20 +46,81 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta { } @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(NBT, toNBT()); + public int getDamage() { + return components.get(ItemComponent.DAMAGE, 0); + } + + @Override + public boolean isUnbreakable() { + return components.has(ItemComponent.UNBREAKABLE); + } + + @Override + public int getHideFlag() { + return 0; + } + + @Override + public @Nullable Component getDisplayName() { + return components.get(ItemComponent.CUSTOM_NAME); + } + + @Override + public @NotNull List<@NotNull Component> getLore() { + return components.get(ItemComponent.LORE, List.of()); + } + + @Override + public @NotNull Map getEnchantmentMap() { + EnchantmentList enchantments = components.get(ItemComponent.ENCHANTMENTS); + if (enchantments == null) return Map.of(); + Map map = new HashMap<>(enchantments.enchantments().size()); + for (Map.Entry entry : enchantments.enchantments().entrySet()) { + map.put(entry.getKey(), entry.getValue().shortValue()); + } + return map; + } + + @Override + public @NotNull List<@NotNull ItemAttribute> getAttributes() { + //todo + } + + @Override + public int getCustomModelData() { + return components.get(ItemComponent.CUSTOM_MODEL_DATA, 0); + } + + @Override + public @NotNull Set<@NotNull String> getCanDestroy() { + //todo + } + + @Override + public boolean canDestroy(@NotNull Block block) { + //todo + } + + @Override + public @NotNull Set<@NotNull String> getCanPlaceOn() { + //todo + } + + @Override + public boolean canPlaceOn(@NotNull Block block) { + //todo } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ItemMetaImpl itemMeta)) return false; - return toNBT().equals(itemMeta.toNBT()); + return components.equals(itemMeta.components); } @Override public int hashCode() { - return Objects.hash(toNBT()); + return Objects.hash(components); } @Override @@ -65,10 +128,107 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta { return toSNBT(); } - record Builder(TagHandler tagHandler) implements ItemMeta.Builder { + static final class Builder implements ItemMeta.Builder { + private final ItemComponentPatch.Builder components; + private TagHandler tagHandler = null; + + Builder(ItemComponentPatch.Builder components) { + this.components = components; + } + + @Override + public ItemMeta.@NotNull Builder damage(int damage) { + components.set(ItemComponent.DAMAGE, damage); + return this; + } + + @Override + public ItemMeta.@NotNull Builder unbreakable(boolean unbreakable) { + if (unbreakable) { + components.set(ItemComponent.UNBREAKABLE, new Unbreakable( + components.get(ItemComponent.UNBREAKABLE, Unbreakable.DEFAULT).showInTooltip())); + } else { + components.remove(ItemComponent.UNBREAKABLE); + } + return this; + } + + @Override + public ItemMeta.@NotNull Builder hideFlag(int hideFlag) { + return this; //todo + } + + @Override + public ItemMeta.@NotNull Builder displayName(@Nullable Component displayName) { + if (displayName == null) { + components.remove(ItemComponent.CUSTOM_NAME); + } else { + components.set(ItemComponent.CUSTOM_NAME, displayName); + } + return this; + } + + @Override + public ItemMeta.@NotNull Builder lore(@NotNull List lore) { + components.set(ItemComponent.LORE, new ArrayList<>(lore)); + return this; + } + + @Override + public ItemMeta.@NotNull Builder enchantments(@NotNull Map enchantments) { + EnchantmentList existing = components.get(ItemComponent.ENCHANTMENTS, EnchantmentList.EMPTY); + Map map = new HashMap<>(enchantments.size()); + for (Map.Entry entry : enchantments.entrySet()) { + map.put(entry.getKey(), (int) entry.getValue()); + } + components.set(ItemComponent.ENCHANTMENTS, new EnchantmentList(map, existing.showInTooltip())); + return this; + } + + @Override + public ItemMeta.@NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) { + components.set(ItemComponent.ENCHANTMENTS, components.get(ItemComponent.ENCHANTMENTS, EnchantmentList.EMPTY) + .with(enchantment, level)); + return this; + } + + @Override + public ItemMeta.@NotNull Builder attributes(@NotNull List<@NotNull ItemAttribute> attributes) { + return this; //todo + } + + @Override + public ItemMeta.@NotNull Builder customModelData(int customModelData) { + components.set(ItemComponent.CUSTOM_MODEL_DATA, customModelData); + return this; + } + + @Override + public ItemMeta.@NotNull Builder canPlaceOn(@NotNull Set<@NotNull Block> blocks) { + //todo + return this; + } + + @Override + public ItemMeta.@NotNull Builder canDestroy(@NotNull Set<@NotNull Block> blocks) { + //todo + return this; + } + + @Override + public @NotNull TagHandler tagHandler() { + this.tagHandler = TagHandler.fromCompound(components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).nbt()); + return tagHandler; + } + @Override public @NotNull ItemMetaImpl build() { - return new ItemMetaImpl(tagHandler.copy()); + if (tagHandler != null) { + // If tagHandler was called then a tag was probably changed so update custom data. + components.set(ItemComponent.CUSTOM_DATA, new CustomData(tagHandler.asCompound())); + } + return new ItemMetaImpl(components.build()); } + } } diff --git a/src/main/java/net/minestom/server/item/ItemMetaView.java b/src/main/java/net/minestom/server/item/ItemMetaView.java index 53232d3f7..75066b9da 100644 --- a/src/main/java/net/minestom/server/item/ItemMetaView.java +++ b/src/main/java/net/minestom/server/item/ItemMetaView.java @@ -1,16 +1,15 @@ package net.minestom.server.item; import net.minestom.server.tag.TagReadable; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -@SuppressWarnings("ALL") -@ApiStatus.Experimental +@Deprecated public interface ItemMetaView extends TagReadable { - @ApiStatus.Experimental + + @Deprecated non-sealed interface Builder extends ItemMeta.Builder { default @NotNull ItemMeta build() { - return new ItemMetaImpl(tagHandler().copy()); + return new ItemMetaImpl(components().build()); } } } diff --git a/src/main/java/net/minestom/server/item/ItemMetaViewImpl.java b/src/main/java/net/minestom/server/item/ItemMetaViewImpl.java index 0ec8d7386..38dd22e4d 100644 --- a/src/main/java/net/minestom/server/item/ItemMetaViewImpl.java +++ b/src/main/java/net/minestom/server/item/ItemMetaViewImpl.java @@ -1,34 +1,34 @@ package net.minestom.server.item; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; +import net.minestom.server.item.component.ItemComponentPatch; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +@Deprecated final class ItemMetaViewImpl { static > Class viewType(Class metaClass) { final Type type = metaClass.getGenericInterfaces()[0]; return (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; } - static > T construct(Class metaClass, TagReadable tagReadable) { + static > T construct(Class metaClass, ItemComponentPatch components) { try { - final Constructor cons = metaClass.getDeclaredConstructor(TagReadable.class); - return cons.newInstance(tagReadable); + final Constructor cons = metaClass.getDeclaredConstructor(ItemComponentPatch.class); + return cons.newInstance(components); } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } - static > V constructBuilder(Class metaClass, TagHandler tagHandler) { + static > V constructBuilder(Class metaClass, ItemComponentPatch.Builder components) { final Class clazz = viewType(metaClass); try { - final Constructor cons = clazz.getDeclaredConstructor(TagHandler.class); - return cons.newInstance(tagHandler); + final Constructor cons = clazz.getDeclaredConstructor(ItemComponentPatch.Builder.class); + return cons.newInstance(components); } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); diff --git a/src/main/java/net/minestom/server/item/ItemSerializers.java b/src/main/java/net/minestom/server/item/ItemSerializers.java index 6764589c6..d28226c86 100644 --- a/src/main/java/net/minestom/server/item/ItemSerializers.java +++ b/src/main/java/net/minestom/server/item/ItemSerializers.java @@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Locale; import java.util.UUID; +@Deprecated @ApiStatus.Internal public final class ItemSerializers { public static final TagSerializer ENCHANTMENT_SERIALIZER = new TagSerializer<>() { diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 9a2f17ae1..26f8e596d 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -6,12 +6,14 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import net.minestom.server.adventure.MinestomAdventure; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentMap; import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagReadable; import net.minestom.server.tag.TagWritable; -import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.*; import java.io.IOException; @@ -26,7 +28,7 @@ import java.util.function.UnaryOperator; *

* An item stack cannot be null, {@link ItemStack#AIR} should be used instead. */ -public sealed interface ItemStack extends TagReadable, HoverEventSource +public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEventSource permits ItemStackImpl { /** * Constant AIR item. Should be used instead of 'null'. @@ -48,12 +50,14 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource> @NotNull T meta(@NotNull Class metaClass); - @Contract(value = "_, -> new", pure = true) @NotNull ItemStack with(@NotNull Consumer<@NotNull Builder> consumer); - @Contract(value = "_, _ -> new", pure = true) - @ApiStatus.Experimental - > @NotNull ItemStack withMeta(@NotNull Class metaType, - @NotNull Consumer consumer); - - @Contract(value = "_ -> new", pure = true) - @NotNull ItemStack withMeta(@NotNull Consumer consumer); - @Contract(value = "_, -> new", pure = true) @NotNull ItemStack withMaterial(@NotNull Material material); @@ -112,44 +100,26 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource @NotNull ItemStack with(@NotNull ItemComponent component, T value); + + @Contract(value = "_, -> new", pure = true) + @NotNull ItemStack without(@NotNull ItemComponent component); + + @Contract(value = "_, _ -> new", pure = true) + default @NotNull ItemStack withTag(@NotNull Tag tag, @Nullable T value) { + return withMeta(builder -> builder.set(tag, value)); + } + + @Override + @Contract(pure = true) + default @UnknownNullability T getTag(@NotNull Tag tag) { + return getOrDefault(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); + } + @Contract(value = "_, -> new", pure = true) @NotNull ItemStack consume(int amount); - @Contract(pure = true) - default @Nullable Component getDisplayName() { - return meta().getDisplayName(); - } - - @Contract(pure = true) - default @NotNull List<@NotNull Component> getLore() { - return meta().getLore(); - } - - @ApiStatus.Experimental - @Contract(value = "_ -> new", pure = true) - @NotNull ItemStack withMeta(@NotNull ItemMeta meta); - - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withDisplayName(@Nullable Component displayName) { - return withMeta(builder -> builder.displayName(displayName)); - } - - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) { - return withDisplayName(componentUnaryOperator.apply(getDisplayName())); - } - - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withLore(@NotNull List lore) { - return withMeta(builder -> builder.lore(lore)); - } - - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) { - return withLore(loreUnaryOperator.apply(getLore())); - } - @Contract(pure = true) default boolean isAir() { return material() == Material.AIR; @@ -158,16 +128,76 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource> @NotNull T meta(@NotNull Class metaClass); + + @Deprecated(forRemoval = true) @Contract(value = "_, _ -> new", pure = true) - default @NotNull ItemStack withTag(@NotNull Tag tag, @Nullable T value) { - return withMeta(builder -> builder.set(tag, value)); + @ApiStatus.Experimental + > @NotNull ItemStack withMeta(@NotNull Class metaType, + @NotNull Consumer consumer); + + @Deprecated(forRemoval = true) + @Contract(value = "_ -> new", pure = true) + @NotNull ItemStack withMeta(@NotNull Consumer consumer); + + @Deprecated(forRemoval = true) + @Contract(pure = true) + default @Nullable Component getDisplayName() { + return meta().getDisplayName(); } - @Override - default @UnknownNullability T getTag(@NotNull Tag tag) { - return meta().getTag(tag); + @Deprecated(forRemoval = true) + @Contract(pure = true) + default @NotNull List<@NotNull Component> getLore() { + return meta().getLore(); } + @Deprecated(forRemoval = true) + @Contract(value = "_ -> new", pure = true) + @NotNull ItemStack withMeta(@NotNull ItemMeta meta); + + @Deprecated(forRemoval = true) + @Contract(value = "_, -> new", pure = true) + default @NotNull ItemStack withDisplayName(@Nullable Component displayName) { + return withMeta(builder -> builder.displayName(displayName)); + } + + @Deprecated(forRemoval = true) + @Contract(value = "_, -> new", pure = true) + default @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) { + return withDisplayName(componentUnaryOperator.apply(getDisplayName())); + } + + @Deprecated(forRemoval = true) + @Contract(value = "_, -> new", pure = true) + default @NotNull ItemStack withLore(@NotNull List lore) { + return withMeta(builder -> builder.lore(lore)); + } + + @Deprecated(forRemoval = true) + @Contract(value = "_, -> new", pure = true) + default @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) { + return withLore(loreUnaryOperator.apply(getLore())); + } + + // END DEPRECATED PRE-COMPONENT METHODS + @Override default @NotNull HoverEvent asHoverEvent(@NotNull UnaryOperator op) { try { @@ -179,56 +209,17 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource consumer); - @Contract(value = "_, _ -> this") - > @NotNull Builder meta(@NotNull Class metaType, - @NotNull Consumer<@NotNull V> itemMetaConsumer); + @NotNull Builder set(@NotNull ItemComponent component, T value); - @Contract(value = "-> new", pure = true) - @NotNull ItemStack build(); + @Contract(value = "_ -> this") + @NotNull Builder remove(@NotNull ItemComponent component); @Contract(value = "_, _ -> this") default @NotNull Builder set(@NotNull Tag tag, @Nullable T value) { @@ -236,16 +227,45 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource consumer); + + @Deprecated(forRemoval = true) + @Contract(value = "_, _ -> this") + > @NotNull Builder meta(@NotNull Class metaType, + @NotNull Consumer<@NotNull V> itemMetaConsumer); + + @Deprecated(forRemoval = true) @Contract(value = "_ -> this") default @NotNull Builder displayName(@Nullable Component displayName) { return meta(builder -> builder.displayName(displayName)); } + @Deprecated(forRemoval = true) @Contract(value = "_ -> this") default @NotNull Builder lore(@NotNull List lore) { return meta(builder -> builder.lore(lore)); } + @Deprecated(forRemoval = true) @Contract(value = "_ -> this") default @NotNull Builder lore(Component... lore) { return meta(builder -> builder.lore(lore)); diff --git a/src/main/java/net/minestom/server/item/ItemStackImpl.java b/src/main/java/net/minestom/server/item/ItemStackImpl.java index a0d75e7ee..2e9f40bdf 100644 --- a/src/main/java/net/minestom/server/item/ItemStackImpl.java +++ b/src/main/java/net/minestom/server/item/ItemStackImpl.java @@ -1,8 +1,9 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.minestom.server.ServerFlag; -import net.minestom.server.item.rule.VanillaStackingRule; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import org.jetbrains.annotations.Contract; @@ -11,34 +12,25 @@ import org.jetbrains.annotations.Nullable; import java.util.function.Consumer; -record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implements ItemStack { - static final @NotNull StackingRule DEFAULT_STACKING_RULE; +record ItemStackImpl(Material material, int amount, ItemComponentPatch components) implements ItemStack { - static { - if (ServerFlag.STACKING_RULE == null) { - DEFAULT_STACKING_RULE = new VanillaStackingRule(); - } else { - try { - DEFAULT_STACKING_RULE = (StackingRule) ClassLoader.getSystemClassLoader() - .loadClass(ServerFlag.STACKING_RULE).getConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException("Could not instantiate default stacking rule", e); - } - } - } - - static ItemStack create(Material material, int amount, ItemMetaImpl meta) { + static ItemStack create(Material material, int amount, ItemComponentPatch components) { if (amount <= 0) return AIR; - return new ItemStackImpl(material, amount, meta); + return new ItemStackImpl(material, amount, components); } static ItemStack create(Material material, int amount) { - return create(material, amount, ItemMetaImpl.EMPTY); + return create(material, amount, ItemComponentPatch.EMPTY); } @Override - public > @NotNull T meta(@NotNull Class metaClass) { - return ItemMetaViewImpl.construct(metaClass, meta); + public @Nullable T get(@NotNull ItemComponent component) { + return components.get(component); + } + + @Override + public boolean has(@NotNull ItemComponent component) { + return components.has(component); } @Override @@ -48,6 +40,65 @@ record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implement return builder.build(); } + @Override + public @NotNull ItemStack withMaterial(@NotNull Material material) { + return new ItemStackImpl(material, amount, components); + } + + @Override + public @NotNull ItemStack withAmount(int amount) { + return create(material, amount, components); + } + + @Override + public @NotNull ItemStack with(@NotNull ItemComponent component, T value) { + return new ItemStackImpl(material, amount, components.with(component, value)); + } + + @Override + public @NotNull ItemStack without(@NotNull ItemComponent component) { + return new ItemStackImpl(material, amount, components.without(component)); + } + + @Override + public @NotNull ItemStack consume(int amount) { + int newAmount = amount() - amount; + if (newAmount <= 0) return AIR; + return withAmount(newAmount); + } + + @Override + public boolean isSimilar(@NotNull ItemStack itemStack) { + return material == itemStack.material() && components.equals(((ItemStackImpl) itemStack).components); + } + + @Override + public @NotNull CompoundBinaryTag toItemNBT() { +// CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder() +// .putString("id", material.name()) +// .putByte("Count", (byte) amount); +// CompoundBinaryTag nbt = meta.toNBT(); +// if (nbt.size() > 0) builder.put("tag", nbt); +// return builder.build(); + //todo + } + + @Contract(value = "-> new", pure = true) + private @NotNull ItemStack.Builder builder() { + return new Builder(material, amount, new ItemMetaImpl.Builder(meta.tagHandler().copy())); + } + + // BEGIN DEPRECATED PRE-COMPONENT METHODS + + @Override public @NotNull ItemMeta meta() { + return new ItemMetaImpl(components); + } + + @Override + public > @NotNull T meta(@NotNull Class metaClass) { + return ItemMetaViewImpl.construct(metaClass, meta); + } + @Override public @NotNull > ItemStack withMeta(@NotNull Class metaType, @NotNull Consumer consumer) { @@ -59,59 +110,20 @@ record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implement return builder().meta(consumer).build(); } - @Override - public @NotNull ItemStack withMaterial(@NotNull Material material) { - return new ItemStackImpl(material, amount, meta); - } - - @Override - public @NotNull ItemStack withAmount(int amount) { - return create(material, amount, meta); - } - - @Override - public @NotNull ItemStack consume(int amount) { - return DEFAULT_STACKING_RULE.apply(this, currentAmount -> currentAmount - amount); - } - @Override public @NotNull ItemStack withMeta(@NotNull ItemMeta meta) { return new ItemStackImpl(material, amount, (ItemMetaImpl) meta); } - @Override - public boolean isSimilar(@NotNull ItemStack itemStack) { - return material == itemStack.material() && meta.equals(itemStack.meta()); - } - - @Override - public @NotNull CompoundBinaryTag toItemNBT() { - CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder() - .putString("id", material.name()) - .putByte("Count", (byte) amount); - CompoundBinaryTag nbt = meta.toNBT(); - if (nbt.size() > 0) builder.put("tag", nbt); - return builder.build(); - } - - @Contract(value = "-> new", pure = true) - private @NotNull ItemStack.Builder builder() { - return new Builder(material, amount, new ItemMetaImpl.Builder(meta.tagHandler().copy())); - } - static final class Builder implements ItemStack.Builder { final Material material; int amount; - ItemMetaImpl.Builder metaBuilder; + ItemComponentPatch.Builder components; - Builder(Material material, int amount, ItemMetaImpl.Builder metaBuilder) { + Builder(Material material, int amount, ItemComponentPatch.Builder components) { this.material = material; this.amount = amount; - this.metaBuilder = metaBuilder; - } - - Builder(Material material, int amount) { - this(material, amount, new ItemMetaImpl.Builder(TagHandler.newHandler())); + this.components = components; } @Override @@ -120,49 +132,57 @@ record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implement return this; } + @Override + public ItemStack.@NotNull Builder set(@NotNull ItemComponent component, T value) { + components.set(component, value); + return this; + } + + @Override + public ItemStack.@NotNull Builder remove(@NotNull ItemComponent component) { + components.remove(component); + return this; + } + + @Override + public void setTag(@NotNull Tag tag, @Nullable T value) { + components.set(ItemComponent.CUSTOM_DATA, components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).withTag(tag, value)); + } + + @Override + public @NotNull ItemStack build() { + return ItemStackImpl.create(material, amount, components.build()); + } + @Override public ItemStack.@NotNull Builder meta(@NotNull TagHandler tagHandler) { - return metaBuilder(new ItemMetaImpl.Builder(tagHandler.copy())); + return meta(tagHandler.asCompound()); } @Override public ItemStack.@NotNull Builder meta(@NotNull CompoundBinaryTag compound) { - return metaBuilder(new ItemMetaImpl.Builder(TagHandler.fromCompound(compound))); + components.set(ItemComponent.CUSTOM_DATA, new CustomData(compound)); + return this; } @Override public ItemStack.@NotNull Builder meta(@NotNull ItemMeta itemMeta) { - final TagHandler tagHandler = ((ItemMetaImpl) itemMeta).tagHandler(); - return metaBuilder(new ItemMetaImpl.Builder(tagHandler.copy())); + this.components = itemMeta.components().builder(); + return this; } @Override public ItemStack.@NotNull Builder meta(@NotNull Consumer consumer) { - consumer.accept(metaBuilder); + consumer.accept(new ItemMetaImpl.Builder(components)); return this; } @Override public > ItemStack.@NotNull Builder meta(@NotNull Class metaType, @NotNull Consumer<@NotNull V> itemMetaConsumer) { - V view = ItemMetaViewImpl.constructBuilder(metaType, metaBuilder.tagHandler()); + V view = ItemMetaViewImpl.constructBuilder(metaType, components); itemMetaConsumer.accept(view); return this; } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - this.metaBuilder.setTag(tag, value); - } - - @Override - public @NotNull ItemStack build() { - return ItemStackImpl.create(material, amount, metaBuilder.build()); - } - - private ItemStack.@NotNull Builder metaBuilder(@NotNull ItemMetaImpl.Builder builder) { - this.metaBuilder = builder; - return this; - } } } diff --git a/src/main/java/net/minestom/server/item/ItemTags.java b/src/main/java/net/minestom/server/item/ItemTags.java deleted file mode 100644 index e59bf0b3c..000000000 --- a/src/main/java/net/minestom/server/item/ItemTags.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.minestom.server.item; - -import net.kyori.adventure.text.Component; -import net.minestom.server.item.attribute.ItemAttribute; -import net.minestom.server.tag.Tag; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static net.minestom.server.item.ItemSerializers.*; - -final class ItemTags { - static final Tag DAMAGE = Tag.Integer("Damage").defaultValue(0); - static final Tag UNBREAKABLE = Tag.Boolean("Unbreakable").defaultValue(false); - static final Tag HIDE_FLAGS = Tag.Integer("HideFlags").defaultValue(0); - static final Tag CUSTOM_MODEL_DATA = Tag.Integer("CustomModelData").defaultValue(0); - static final Tag NAME = Tag.Component("Name").path("display"); - static final Tag> LORE = Tag.Component("Lore").path("display").list().defaultValue(List.of()); - static final Tag> ENCHANTMENTS = Tag.Structure("Enchantments", ENCHANTMENT_SERIALIZER).list().map(enchantmentEntry -> { - Map map = new HashMap<>(); - for (var entry : enchantmentEntry) map.put(entry.enchantment(), entry.level()); - return Map.copyOf(map); - }, o -> { - List entries = new ArrayList<>(); - for (var entry : o.entrySet()) entries.add(new EnchantmentEntry(entry.getKey(), entry.getValue())); - return List.copyOf(entries); - }).defaultValue(Map.of()); - static final Tag> ATTRIBUTES = Tag.Structure("AttributeModifiers", ATTRIBUTE_SERIALIZER).list().defaultValue(List.of()); - static final Tag> CAN_PLACE_ON = Tag.String("CanPlaceOn").list().defaultValue(List.of()); - static final Tag> CAN_DESTROY = Tag.String("CanDestroy").list().defaultValue(List.of()); -} diff --git a/src/main/java/net/minestom/server/item/Material.java b/src/main/java/net/minestom/server/item/Material.java index 011633e34..c0c277b11 100644 --- a/src/main/java/net/minestom/server/item/Material.java +++ b/src/main/java/net/minestom/server/item/Material.java @@ -1,12 +1,15 @@ package net.minestom.server.item; import net.minestom.server.instance.block.Block; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentMap; import net.minestom.server.registry.Registry; import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import java.util.Collection; @@ -62,24 +65,22 @@ public sealed interface Material extends StaticProtocolObject, Materials permits return registry().id(); } - default int maxStackSize() { - return registry().maxStackSize(); - } - - default boolean isFood() { - return registry().isFood(); - } - default boolean isBlock() { return registry().block() != null; } - default Block block() { + default @UnknownNullability Block block() { return registry().block(); } + default @NotNull ItemComponentMap prototype() { + return registry().prototype(); + } + default boolean isArmor() { - return registry().isArmor(); + //todo how does armor work nowadays + return false; +// return registry().isArmor(); } default boolean hasState() { @@ -90,6 +91,16 @@ public sealed interface Material extends StaticProtocolObject, Materials permits } } + @Deprecated(forRemoval = true) + default int maxStackSize() { + return prototype().getOrDefault(ItemComponent.MAX_STACK_SIZE, 64); + } + + @Deprecated(forRemoval = true) + default boolean isFood() { + return prototype().has(ItemComponent.FOOD); + } + static @NotNull Collection<@NotNull Material> values() { return MaterialImpl.values(); } diff --git a/src/main/java/net/minestom/server/item/StackingRule.java b/src/main/java/net/minestom/server/item/StackingRule.java deleted file mode 100644 index 15db1b602..000000000 --- a/src/main/java/net/minestom/server/item/StackingRule.java +++ /dev/null @@ -1,70 +0,0 @@ -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) - * or a complete new one which can be stored in lore, name, etc... - */ -public interface StackingRule { - - static @NotNull StackingRule get() { - return ItemStackImpl.DEFAULT_STACKING_RULE; - } - - /** - * Used to know if two {@link ItemStack} can be stacked together. - * - * @param item1 the first {@link ItemStack} - * @param item2 the second {@link ItemStack} - * @return true if both {@link ItemStack} can be stacked together - * (without taking their amount in consideration) - */ - boolean canBeStacked(@NotNull ItemStack item1, @NotNull ItemStack item2); - - /** - * Used to know if an {@link ItemStack} can have the size {@code newAmount} applied. - * - * @param item the {@link ItemStack} to check - * @param amount the desired new amount - * @return true if {@code item} can have its stack size set to newAmount - */ - boolean canApply(@NotNull ItemStack item, int amount); - - /** - * Changes the size of the {@link ItemStack} to {@code newAmount}. - * At this point we know that the item can have this stack size applied. - * - * @param item the {@link ItemStack} to applies the size to - * @param newAmount the new item size - * @return a new {@link ItemStack item} with the specified amount - */ - @Contract("_, _ -> new") - @NotNull ItemStack apply(@NotNull ItemStack item, int newAmount); - - @Contract("_, _ -> new") - default @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}. - * It is possible to have it stored in its nbt. - * - * @param itemStack the {@link ItemStack} to check the size - * @return the correct size of {@link ItemStack} - */ - int getAmount(@NotNull ItemStack itemStack); - - /** - * Gets the max size of a stack. - * - * @param itemStack the item to get the max size from - * @return the max size of a stack - */ - int getMaxSize(@NotNull ItemStack itemStack); -} diff --git a/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java b/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java index 46d3c9678..249a44cdc 100644 --- a/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java +++ b/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java @@ -1,10 +1,13 @@ package net.minestom.server.item.attribute; public enum AttributeSlot { + ANY, MAINHAND, OFFHAND, FEET, LEGS, CHEST, - HEAD + HEAD, + ARMOR, + BODY, } diff --git a/src/main/java/net/minestom/server/item/component/AttributeList.java b/src/main/java/net/minestom/server/item/component/AttributeList.java new file mode 100644 index 000000000..c436ac4b0 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/AttributeList.java @@ -0,0 +1,101 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.*; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.entity.attribute.AttributeModifier; +import net.minestom.server.entity.attribute.AttributeOperation; +import net.minestom.server.item.attribute.AttributeSlot; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.UniqueIdUtils; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public record AttributeList(@NotNull List modifiers, boolean showInTooltip) { + public static final AttributeList EMPTY = new AttributeList(List.of(), true); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, AttributeList value) { + buffer.writeCollection(value.modifiers); + buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip); + } + + @Override + public AttributeList read(@NotNull NetworkBuffer buffer) { + return new AttributeList(buffer.readCollection(Modifier::new, Short.MAX_VALUE), + buffer.read(NetworkBuffer.BOOLEAN)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull AttributeList value) { + ListBinaryTag.Builder modifiers = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); + for (Modifier modifier : value.modifiers) { + modifiers.add(CompoundBinaryTag.builder() + .putString("type", modifier.attribute.name()) + .putString("slot", modifier.slot.name().toLowerCase(Locale.ROOT)) + .put("uuid", UniqueIdUtils.toNbt(modifier.modifier.id())) + .putString("name", modifier.modifier.name()) + .putDouble("amount", modifier.modifier.amount()) + .putString("operation", modifier.modifier.operation().name().toLowerCase(Locale.ROOT)) + .build()); + } + return CompoundBinaryTag.builder() + .put("modifiers", modifiers.build()) + .putBoolean("show_in_tooltip", value.showInTooltip) + .build(); + } + + @Override + public @NotNull AttributeList read(@NotNull BinaryTag tag) { + boolean showInTooltip = true; + ListBinaryTag modifiersTag; + if (tag instanceof CompoundBinaryTag compound) { + modifiersTag = compound.getList("modifiers", BinaryTagTypes.COMPOUND); + showInTooltip = compound.getBoolean("show_in_tooltip", true); + } else if (tag instanceof ListBinaryTag list) { + modifiersTag = list; + } else return EMPTY; + List modifiers = new ArrayList<>(modifiersTag.size()); + for (BinaryTag modifierTagRaw : modifiersTag) { + if (!(modifierTagRaw instanceof CompoundBinaryTag modifierTag)) continue; + Attribute attribute = Attribute.fromNamespaceId(modifierTag.getString("type")); + if (attribute == null) continue; // Unknown attribute, skip + AttributeSlot slot = AttributeSlot.valueOf(modifierTag.getString("slot").toUpperCase(Locale.ROOT)); + AttributeModifier modifier = new AttributeModifier( + UniqueIdUtils.fromNbt((IntArrayBinaryTag) modifierTag.get("uuid")), + modifierTag.getString("name"), + modifierTag.getDouble("amount"), + AttributeOperation.valueOf(modifierTag.getString("operation").toUpperCase(Locale.ROOT)) + ); + modifiers.add(new Modifier(attribute, modifier, slot)); + } + return new AttributeList(modifiers, showInTooltip); + } + }; + + public record Modifier( + @NotNull Attribute attribute, + @NotNull AttributeModifier modifier, + @NotNull AttributeSlot slot + ) implements NetworkBuffer.Writer { + + public Modifier(@NotNull NetworkBuffer reader) { + this(Attribute.fromId(reader.read(NetworkBuffer.VAR_INT)), + new AttributeModifier(reader), + reader.readEnum(AttributeSlot.class)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.VAR_INT, attribute.id()); + modifier.write(writer); + writer.writeEnum(AttributeSlot.class, slot); + } + } +} diff --git a/src/main/java/net/minestom/server/item/component/CustomData.java b/src/main/java/net/minestom/server/item/component/CustomData.java index 012ea1d0f..20f881da8 100644 --- a/src/main/java/net/minestom/server/item/component/CustomData.java +++ b/src/main/java/net/minestom/server/item/component/CustomData.java @@ -3,10 +3,13 @@ package net.minestom.server.item.component; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.tag.Tag; +import net.minestom.server.tag.TagReadable; +import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; -public record CustomData(@NotNull CompoundBinaryTag nbt) implements ItemComponent { - static final Tag TAG = Tag.Structure("ab", CustomData.class); +public record CustomData(@NotNull CompoundBinaryTag nbt) implements TagReadable { + public static final CustomData EMPTY = new CustomData(CompoundBinaryTag.empty()); static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { @Override @@ -20,4 +23,17 @@ public record CustomData(@NotNull CompoundBinaryTag nbt) implements ItemComponen } }; + static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map(CustomData::new, CustomData::nbt); + + @Override + public @UnknownNullability T getTag(@NotNull Tag tag) { + return tag.read(nbt); + } + + public @NotNull CustomData withTag(@NotNull Tag tag, T value) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + builder.put(nbt); + tag.write(builder, value); + return new CustomData(builder.build()); + } } diff --git a/src/main/java/net/minestom/server/item/component/DyedItemColor.java b/src/main/java/net/minestom/server/item/component/DyedItemColor.java new file mode 100644 index 000000000..49d4b8f28 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/DyedItemColor.java @@ -0,0 +1,57 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.IntBinaryTag; +import net.minestom.server.color.Color; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +public record DyedItemColor(@NotNull Color color, boolean showInTooltip) { + public static DyedItemColor LEATHER = new DyedItemColor(new Color(-6265536), true); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, DyedItemColor value) { + buffer.write(NetworkBuffer.COLOR, value.color); + buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip); + } + + @Override + public DyedItemColor read(@NotNull NetworkBuffer buffer) { + return new DyedItemColor(buffer.read(NetworkBuffer.COLOR), buffer.read(NetworkBuffer.BOOLEAN)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull DyedItemColor value) { + return CompoundBinaryTag.builder() + .putInt("color", value.color.asRGB()) + .putBoolean("show_in_tooltip", value.showInTooltip) + .build(); + } + + @Override + public @NotNull DyedItemColor read(@NotNull BinaryTag tag) { + if (tag instanceof CompoundBinaryTag compoundTag) { + int color = compoundTag.getInt("color"); + boolean showInTooltip = compoundTag.getBoolean("show_in_tooltip", true); + return new DyedItemColor(new Color(color), showInTooltip); + } else if (tag instanceof IntBinaryTag intTag) { + return new DyedItemColor(new Color(intTag.intValue()), true); + } + return new DyedItemColor(new Color(0), false); + } + }; + + public @NotNull DyedItemColor withColor(@NotNull Color color) { + return new DyedItemColor(color, showInTooltip); + } + + public @NotNull DyedItemColor withTooltip(boolean showInTooltip) { + return new DyedItemColor(color, showInTooltip); + } + +} diff --git a/src/main/java/net/minestom/server/item/component/EnchantmentList.java b/src/main/java/net/minestom/server/item/component/EnchantmentList.java new file mode 100644 index 000000000..913213d39 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/EnchantmentList.java @@ -0,0 +1,81 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.item.Enchantment; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public record EnchantmentList(@NotNull Map enchantments, boolean showInTooltip) { + public static final EnchantmentList EMPTY = new EnchantmentList(Map.of(), true); + + static NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, @NotNull EnchantmentList value) { + buffer.write(NetworkBuffer.VAR_INT, value.enchantments.size()); + for (Map.Entry entry : value.enchantments.entrySet()) { + buffer.write(NetworkBuffer.VAR_INT, entry.getKey().id()); + buffer.write(NetworkBuffer.VAR_INT, entry.getValue()); + } + buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip); + } + + @Override + public @NotNull EnchantmentList read(@NotNull NetworkBuffer buffer) { + int size = buffer.read(NetworkBuffer.VAR_INT); + Check.argCondition(size < 0 || size > Short.MAX_VALUE, "Invalid enchantment list size: {0}", size); + Map enchantments = new HashMap<>(size); + for (int i = 0; i < size; i++) { + Enchantment enchantment = Enchantment.fromId(buffer.read(NetworkBuffer.VAR_INT)); + int level = buffer.read(NetworkBuffer.VAR_INT); + enchantments.put(enchantment, level); + } + boolean showInTooltip = buffer.read(NetworkBuffer.BOOLEAN); + return new EnchantmentList(enchantments, showInTooltip); + } + }; + + static BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + // We have two variants of the enchantment list, one with {levels: {...}, show_in_tooltip: boolean} and one with {...}. + CompoundBinaryTag levels = tag.keySet().contains("levels") ? tag.getCompound("levels") : tag; + Map enchantments = new HashMap<>(levels.size()); + for (Map.Entry entry : levels) { + Enchantment enchantment = Enchantment.fromNamespaceId(entry.getKey()); + Check.notNull(enchantment, "Unknown enchantment: {0}", entry.getKey()); + int level = BinaryTagSerializer.INT.read(entry.getValue()); + if (level > 0) enchantments.put(enchantment, level); + } + + // Doesnt matter which variant we chose, the default will work. + boolean showInTooltip = tag.getBoolean("show_in_tooltip", true); + return new EnchantmentList(enchantments, showInTooltip); + }, + value -> { + CompoundBinaryTag.Builder levels = CompoundBinaryTag.builder(); + for (Map.Entry entry : value.enchantments.entrySet()) { + levels.put(entry.getKey().name(), BinaryTagSerializer.INT.write(entry.getValue())); + } + + return CompoundBinaryTag.builder() + .put("levels", levels.build()) + .putBoolean("show_in_tooltip", value.showInTooltip) + .build(); + } + ); + + public EnchantmentList { + enchantments = Map.copyOf(enchantments); + } + + public @NotNull EnchantmentList with(@NotNull Enchantment enchantment, int level) { + Map newEnchantments = new HashMap<>(enchantments); + newEnchantments.put(enchantment, level); + return new EnchantmentList(newEnchantments, showInTooltip); + } +} diff --git a/src/main/java/net/minestom/server/item/component/FireworkExplosion.java b/src/main/java/net/minestom/server/item/component/FireworkExplosion.java new file mode 100644 index 000000000..34357c9e2 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/FireworkExplosion.java @@ -0,0 +1,89 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.color.Color; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public record FireworkExplosion( + @NotNull Shape shape, + @NotNull List colors, + @NotNull List fadeColors, + boolean hasTrail, + boolean hasTwinkle +) { + + public enum Shape { + SMALL_BALL, + LARGE_BALL, + STAR, + CREEPER, + BURST + } + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, FireworkExplosion value) { + buffer.writeEnum(Shape.class, value.shape); + buffer.writeCollection(NetworkBuffer.COLOR, value.colors); + buffer.writeCollection(NetworkBuffer.COLOR, value.fadeColors); + buffer.write(NetworkBuffer.BOOLEAN, value.hasTrail); + buffer.write(NetworkBuffer.BOOLEAN, value.hasTwinkle); + } + + @Override + public FireworkExplosion read(@NotNull NetworkBuffer buffer) { + return new FireworkExplosion( + buffer.readEnum(Shape.class), + buffer.readCollection(NetworkBuffer.COLOR, Short.MAX_VALUE), + buffer.readCollection(NetworkBuffer.COLOR, Short.MAX_VALUE), + buffer.read(NetworkBuffer.BOOLEAN), + buffer.read(NetworkBuffer.BOOLEAN) + ); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + Shape shape = Shape.valueOf(tag.getString("shape").toUpperCase(Locale.ROOT)); + List colors = new ArrayList<>(); + for (int color : tag.getIntArray("colors")) + colors.add(new Color(color)); + List fadeColors = new ArrayList<>(); + for (int fadeColor : tag.getIntArray("fadeColors")) + fadeColors.add(new Color(fadeColor)); + boolean hasTrail = tag.getBoolean("hasTrail"); + boolean hasTwinkle = tag.getBoolean("hasTwinkle"); + return new FireworkExplosion(shape, colors, fadeColors, hasTrail, hasTwinkle); + }, + value -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + builder.putString("shape", value.shape.name().toLowerCase(Locale.ROOT)); + if (!value.colors.isEmpty()) { + int[] colors = new int[value.colors.size()]; + for (int i = 0; i < value.colors.size(); i++) + colors[i] = value.colors.get(i).asRGB(); + builder.putIntArray("colors", colors); + } + if (!value.fadeColors.isEmpty()) { + int[] fadeColors = new int[value.fadeColors.size()]; + for (int i = 0; i < value.fadeColors.size(); i++) + fadeColors[i] = value.fadeColors.get(i).asRGB(); + builder.putIntArray("fadeColors", fadeColors); + } + if (value.hasTrail) builder.putBoolean("hasTrail", value.hasTrail); + if (value.hasTwinkle) builder.putBoolean("hasTwinkle", value.hasTwinkle); + return builder.build(); + } + ); + + public FireworkExplosion { + colors = List.copyOf(colors); + fadeColors = List.copyOf(fadeColors); + } +} diff --git a/src/main/java/net/minestom/server/item/component/FireworkList.java b/src/main/java/net/minestom/server/item/component/FireworkList.java new file mode 100644 index 000000000..470457af1 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/FireworkList.java @@ -0,0 +1,62 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public record FireworkList(byte flightDuration, @NotNull List explosions) { + public static final FireworkList EMPTY = new FireworkList((byte) 0, List.of()); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, FireworkList value) { + buffer.write(NetworkBuffer.BYTE, value.flightDuration); + buffer.writeCollection(FireworkExplosion.NETWORK_TYPE, value.explosions); + } + + @Override + public FireworkList read(@NotNull NetworkBuffer buffer) { + return new FireworkList(buffer.read(NetworkBuffer.BYTE), + buffer.readCollection(FireworkExplosion.NETWORK_TYPE, 256)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + byte flightDuration = tag.getByte("flight_duration"); + ListBinaryTag explosionsTag = tag.getList("explosions", BinaryTagTypes.COMPOUND); + List explosions = new ArrayList<>(explosionsTag.size()); + for (BinaryTag explosionTag : explosionsTag) + explosions.add(FireworkExplosion.NBT_TYPE.read(explosionTag)); + return new FireworkList(flightDuration, explosions); + }, + value -> { + ListBinaryTag.Builder explosionsTag = ListBinaryTag.builder(); + for (FireworkExplosion explosion : value.explosions) + explosionsTag.add(FireworkExplosion.NBT_TYPE.write(explosion)); + return CompoundBinaryTag.builder() + .putByte("flight_duration", value.flightDuration) + .put("explosions", explosionsTag.build()) + .build(); + } + ); + + public FireworkList { + explosions = List.copyOf(explosions); + } + + public @NotNull FireworkList withFlightDuration(byte flightDuration) { + return new FireworkList(flightDuration, explosions); + } + + public @NotNull FireworkList withExplosions(@NotNull List explosions) { + return new FireworkList(flightDuration, explosions); + } +} diff --git a/src/main/java/net/minestom/server/item/component/ItemComponent.java b/src/main/java/net/minestom/server/item/component/ItemComponent.java index 2cc339d29..99f4a4621 100644 --- a/src/main/java/net/minestom/server/item/component/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/component/ItemComponent.java @@ -1,20 +1,93 @@ package net.minestom.server.item.component; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.text.Component; +import net.minestom.server.color.Color; +import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.tag.Tag; +import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.utils.NamespaceID; +import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public sealed interface ItemComponent permits CustomData { +import java.util.List; - ItemComponent.Type CUSTOM_DATA = new ItemComponent.Type<>("custom_data", CustomData.NETWORK_TYPE, CustomData.TAG); +import static net.minestom.server.item.component.ItemComponentImpl.declare; - record Type( - @NotNull String name, - @NotNull NetworkBuffer.Type network, - @NotNull Tag tag - ) { +public sealed interface ItemComponent extends StaticProtocolObject permits ItemComponentImpl { + // Note that even non-networked components are declared here as they still contribute to the component ID counter. + ItemComponent CUSTOM_DATA = declare("custom_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent MAX_STACK_SIZE = declare("max_stack_size", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent MAX_DAMAGE = declare("max_damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent DAMAGE = declare("damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent UNBREAKABLE = declare("unbreakable", Unbreakable.NETWORK_TYPE, Unbreakable.NBT_TYPE); + ItemComponent CUSTOM_NAME = declare("custom_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); + ItemComponent ITEM_NAME = declare("item_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); + ItemComponent> LORE = declare("lore", NetworkBuffer.COMPONENT.list(256), BinaryTagSerializer.JSON_COMPONENT.list()); + ItemComponent RARITY = declare("rarity", ItemRarity.NETWORK_TYPE, ItemRarity.NBT_TYPE); + ItemComponent ENCHANTMENTS = declare("enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); + ItemComponent CAN_PLACE_ON = declare("can_place_on", null, null); //todo + ItemComponent CAN_BREAK = declare("can_break", null, null); //todo + ItemComponent ATTRIBUTE_MODIFIERS = declare("attribute_modifiers", AttributeList.NETWORK_TYPE, AttributeList.NBT_TYPE); + ItemComponent CUSTOM_MODEL_DATA = declare("custom_model_data", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + ItemComponent HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + ItemComponent REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING, null); + ItemComponent ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN); + ItemComponent INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); + ItemComponent FOOD = declare("food", null, null); //todo + ItemComponent FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + ItemComponent TOOL = declare("tool", null, null); //todo + ItemComponent STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); + ItemComponent DYED_COLOR = declare("dyed_color", DyedItemColor.NETWORK_TYPE, DyedItemColor.NBT_TYPE); + ItemComponent MAP_COLOR = declare("map_color", NetworkBuffer.COLOR, BinaryTagSerializer.INT.map(Color::new, Color::asRGB)); + ItemComponent MAP_ID = declare("map_id", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent MAP_DECORATIONS = declare("map_decorations", null, MapDecorations.NBT_TYPE); + ItemComponent MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null); + ItemComponent> CHARGED_PROJECTILES = declare("charged_projectiles", NetworkBuffer.ITEM.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + ItemComponent> BUNDLE_CONTENTS = declare("bundle_contents", NetworkBuffer.ITEM.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + ItemComponent POTION_CONTENTS = declare("potion_contents", null, null); //todo + ItemComponent SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo + ItemComponent WRITABLE_BOOK_CONTENT = declare("writable_book_content", null, null); //todo + ItemComponent WRITTEN_BOOK_CONTENT = declare("written_book_content", null, null); //todo + ItemComponent TRIM = declare("trim", null, null); //todo + ItemComponent DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo + ItemComponent ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent BUCKET_ENTITY_DATA = declare("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent BLOCK_ENTITY_DATA = declare("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent INSTRUMENT = declare("instrument", null, null); //todo + ItemComponent OMINOUS_BOTTLE_AMPLIFIER = declare("ominous_bottle_amplifier", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent> RECIPES = declare("recipes", NetworkBuffer.STRING.list(Short.MAX_VALUE), BinaryTagSerializer.STRING.list()); + ItemComponent LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE); + ItemComponent FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE); + ItemComponent FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE); + ItemComponent PROFILE = declare("profile", null, null); //todo + ItemComponent NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING); + ItemComponent BANNER_PATTERNS = declare("banner_patterns", null, null); //todo + ItemComponent BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity + ItemComponent POT_DECORATIONS = declare("pot_decorations", null, null); //todo + ItemComponent> CONTAINER = declare("container", NetworkBuffer.ITEM.list(256), BinaryTagSerializer.ITEM.list()); + ItemComponent BLOCK_STATE = declare("block_state", null, null); //todo + ItemComponent BEES = declare("bees", null, null); //todo + ItemComponent LOCK = declare("lock", null, BinaryTagSerializer.STRING); + ItemComponent CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE); + + @NotNull T read(@NotNull BinaryTag tag); + + @NotNull T read(@NotNull NetworkBuffer reader); + + static @Nullable ItemComponent fromNamespaceId(@NotNull String namespaceId) { + return ItemComponentImpl.NAMESPACES.get(namespaceId); } + static @Nullable ItemComponent fromNamespaceId(@NotNull NamespaceID namespaceId) { + return fromNamespaceId(namespaceId.asString()); + } + static @Nullable ItemComponent fromId(int id) { + return ItemComponentImpl.IDS.get(id); + } } diff --git a/src/main/java/net/minestom/server/item/component/ItemComponentImpl.java b/src/main/java/net/minestom/server/item/component/ItemComponentImpl.java new file mode 100644 index 000000000..dcbbf3048 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/ItemComponentImpl.java @@ -0,0 +1,47 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.NamespaceID; +import net.minestom.server.utils.collection.ObjectArray; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +record ItemComponentImpl( + int id, + @NotNull NamespaceID namespace, + @Nullable NetworkBuffer.Type network, + @Nullable BinaryTagSerializer nbt +) implements ItemComponent { + static final Map> NAMESPACES = new HashMap<>(32); + static final ObjectArray> IDS = ObjectArray.singleThread(32); + + static ItemComponent declare(@NotNull String name, @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt) { + ItemComponent impl = new ItemComponentImpl<>(NAMESPACES.size(), NamespaceID.from(name), network, nbt); + NAMESPACES.put(impl.name(), impl); + IDS.set(impl.id(), impl); + return impl; + } + + @Override + public @NotNull T read(@NotNull BinaryTag tag) { + Check.notNull(nbt, "{0} cannot be deserialized from NBT", this); + return nbt.read(tag); + } + + @Override + public @NotNull T read(@NotNull NetworkBuffer reader) { + Check.notNull(network, "{0} cannot be deserialized from network", this); + return network.read(reader); + } + + @Override + public String toString() { + return name(); + } +} diff --git a/src/main/java/net/minestom/server/item/component/ItemComponentMap.java b/src/main/java/net/minestom/server/item/component/ItemComponentMap.java new file mode 100644 index 000000000..fff85468e --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/ItemComponentMap.java @@ -0,0 +1,32 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ItemComponentMap { + + static @NotNull Builder builder() { + //todo + } + + boolean has(@NotNull ItemComponent component); + + @Nullable T get(@NotNull ItemComponent component); + + default @NotNull T get(@NotNull ItemComponent component, @NotNull T defaultValue) { + T value = get(component); + return value != null ? value : defaultValue; + } + + @NotNull CompoundBinaryTag asCompound(); + + interface Builder { + + @NotNull Builder set(@NotNull ItemComponent component, @NotNull T value); + + void remove(@NotNull ItemComponent component); + + @NotNull ItemComponentMap build(); + } +} diff --git a/src/main/java/net/minestom/server/item/component/ItemComponentPatch.java b/src/main/java/net/minestom/server/item/component/ItemComponentPatch.java new file mode 100644 index 000000000..48db618bd --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/ItemComponentPatch.java @@ -0,0 +1,37 @@ +package net.minestom.server.item.component; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ItemComponentPatch extends ItemComponentMap { + + static ItemComponentPatch EMPTY = new ItemComponentPatch() { + @Override public boolean has(@NotNull ItemComponent component) { + return false; + } + + @Override public @Nullable T get(@NotNull ItemComponent component) { + return null; + } + }; //todo + + + @NotNull ItemComponentPatch with(@NotNull ItemComponent component, T value); + + @NotNull ItemComponentPatch without(@NotNull ItemComponent component); + + @NotNull Builder builder(); + + interface Builder extends ItemComponentMap { + + @Contract(value = "_, _ -> this", pure = true) + @NotNull Builder set(@NotNull ItemComponent component, @NotNull T value); + + @Contract(value = "_ -> this", pure = true) + @NotNull Builder remove(@NotNull ItemComponent component); + + @Contract(value = "-> new", pure = true) + @NotNull ItemComponentPatch build(); + } +} diff --git a/src/main/java/net/minestom/server/item/component/ItemRarity.java b/src/main/java/net/minestom/server/item/component/ItemRarity.java new file mode 100644 index 000000000..a27e1c1a4 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/ItemRarity.java @@ -0,0 +1,47 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.IntBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; + +public enum ItemRarity { + COMMON, + UNCOMMON, + RARE, + EPIC; + + private static final ItemRarity[] VALUES = values(); + + static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { + @Override + public void write(@NotNull NetworkBuffer buffer, ItemRarity value) { + buffer.writeEnum(ItemRarity.class, value); + } + + @Override + public ItemRarity read(@NotNull NetworkBuffer buffer) { + return buffer.readEnum(ItemRarity.class); + } + }; + + static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull ItemRarity value) { + return IntBinaryTag.intBinaryTag(value.ordinal()); + } + + @Override + public @NotNull ItemRarity read(@NotNull BinaryTag tag) { + return switch (tag) { + case IntBinaryTag intBinaryTag -> VALUES[intBinaryTag.value()]; + case StringBinaryTag stringBinaryTag -> valueOf(stringBinaryTag.value().toUpperCase(Locale.ROOT)); + default -> COMMON; + }; + } + }; +} diff --git a/src/main/java/net/minestom/server/item/component/LodestoneTracker.java b/src/main/java/net/minestom/server/item/component/LodestoneTracker.java new file mode 100644 index 000000000..88dc55d75 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/LodestoneTracker.java @@ -0,0 +1,53 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.minestom.server.coordinate.Point; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +public record LodestoneTracker(@NotNull String dimension, @NotNull Point blockPosition, boolean tracked) { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, @NotNull LodestoneTracker value) { + buffer.write(NetworkBuffer.STRING, value.dimension); + buffer.write(NetworkBuffer.BLOCK_POSITION, value.blockPosition); + buffer.write(NetworkBuffer.BOOLEAN, value.tracked); + } + + @Override + public @NotNull LodestoneTracker read(@NotNull NetworkBuffer buffer) { + return new LodestoneTracker( + buffer.read(NetworkBuffer.STRING), + buffer.read(NetworkBuffer.BLOCK_POSITION), + buffer.read(NetworkBuffer.BOOLEAN) + ); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull LodestoneTracker value) { + throw new UnsupportedOperationException("Not implemented"); //todo + } + + @Override + public @NotNull LodestoneTracker read(@NotNull BinaryTag tag) { + throw new UnsupportedOperationException("Not implemented"); //todo + } + }; + + public @NotNull LodestoneTracker withDimension(@NotNull String dimension) { + return new LodestoneTracker(dimension, blockPosition, tracked); + } + + public @NotNull LodestoneTracker withBlockPosition(@NotNull Point blockPosition) { + return new LodestoneTracker(dimension, blockPosition, tracked); + } + + public @NotNull LodestoneTracker withTracked(boolean tracked) { + return new LodestoneTracker(dimension, blockPosition, tracked); + } + +} diff --git a/src/main/java/net/minestom/server/item/component/MapDecorations.java b/src/main/java/net/minestom/server/item/component/MapDecorations.java new file mode 100644 index 000000000..94f19291b --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/MapDecorations.java @@ -0,0 +1,44 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public record MapDecorations(@NotNull Map decorations) { + + public record Entry(@NotNull String type, double x, double z, float rotation) { + } + + static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + Map map = new HashMap<>(tag.size()); + for (Map.Entry entry : tag) { + if (!(entry instanceof CompoundBinaryTag entryTag)) continue; + map.put(entry.getKey(), new Entry( + entryTag.getString("type"), + entryTag.getDouble("x"), + entryTag.getDouble("z"), + entryTag.getFloat("rotation") + )); + } + return new MapDecorations(map); + }, + decorations -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + for (Map.Entry entry : decorations.decorations.entrySet()) { + CompoundBinaryTag entryTag = CompoundBinaryTag.builder() + .putString("type", entry.getValue().type) + .putDouble("x", entry.getValue().x) + .putDouble("z", entry.getValue().z) + .putFloat("rotation", entry.getValue().rotation) + .build(); + builder.put(entry.getKey(), entryTag); + } + return builder.build(); + } + ); +} diff --git a/src/main/java/net/minestom/server/item/component/MapPostProcessing.java b/src/main/java/net/minestom/server/item/component/MapPostProcessing.java new file mode 100644 index 000000000..624902948 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/MapPostProcessing.java @@ -0,0 +1,21 @@ +package net.minestom.server.item.component; + +import net.minestom.server.network.NetworkBuffer; + +public enum MapPostProcessing { + LOCK, + SCALE; + private static final MapPostProcessing[] VALUES = values(); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(NetworkBuffer buffer, MapPostProcessing value) { + buffer.write(NetworkBuffer.VAR_INT, value.ordinal()); + } + + @Override + public MapPostProcessing read(NetworkBuffer buffer) { + return VALUES[buffer.read(NetworkBuffer.VAR_INT)]; + } + }; +} diff --git a/src/main/java/net/minestom/server/item/component/PotDecorations.java b/src/main/java/net/minestom/server/item/component/PotDecorations.java new file mode 100644 index 000000000..21413c76e --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/PotDecorations.java @@ -0,0 +1,40 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.minestom.server.item.Material; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +public record PotDecorations( + @NotNull Material back, + @NotNull Material left, + @NotNull Material right, + @NotNull Material front +) { + public static final PotDecorations EMPTY = new PotDecorations(Material.AIR, Material.AIR, Material.AIR, Material.AIR); + + public static NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, PotDecorations value) { + + } + + @Override + public PotDecorations read(@NotNull NetworkBuffer buffer) { + return null; + } + }; + + public static BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull PotDecorations value) { + return null; + } + + @Override + public @NotNull PotDecorations read(@NotNull BinaryTag tag) { + return null; + } + }; +} diff --git a/src/main/java/net/minestom/server/item/component/SeededContainerLoot.java b/src/main/java/net/minestom/server/item/component/SeededContainerLoot.java new file mode 100644 index 000000000..cf10f0512 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/SeededContainerLoot.java @@ -0,0 +1,16 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +public record SeededContainerLoot(@NotNull String lootTable, long seed) { + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new SeededContainerLoot(tag.getString("loot_table"), tag.getLong("seed")), + loot -> CompoundBinaryTag.builder() + .putString("loot_table", loot.lootTable) + .putLong("seed", loot.seed) + .build() + ); +} diff --git a/src/main/java/net/minestom/server/item/component/Unbreakable.java b/src/main/java/net/minestom/server/item/component/Unbreakable.java new file mode 100644 index 000000000..dc8b71e2a --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/Unbreakable.java @@ -0,0 +1,28 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +public record Unbreakable(boolean showInTooltip) { + public static final Unbreakable DEFAULT = new Unbreakable(false); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { + @Override + public void write(@NotNull NetworkBuffer buffer, Unbreakable value) { + buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip()); + } + + @Override + public Unbreakable read(@NotNull NetworkBuffer buffer) { + return new Unbreakable(buffer.read(NetworkBuffer.BOOLEAN)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new Unbreakable(tag.getBoolean("showInTooltip", false)), + unbreakable -> CompoundBinaryTag.builder().putBoolean("showInTooltip", unbreakable.showInTooltip()).build() + ); + +} diff --git a/src/main/java/net/minestom/server/item/firework/FireworkEffect.java b/src/main/java/net/minestom/server/item/firework/FireworkEffect.java index e9feaebee..69c40d6d8 100644 --- a/src/main/java/net/minestom/server/item/firework/FireworkEffect.java +++ b/src/main/java/net/minestom/server/item/firework/FireworkEffect.java @@ -2,11 +2,13 @@ package net.minestom.server.item.firework; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.color.Color; +import net.minestom.server.item.component.FireworkExplosion; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +@Deprecated public record FireworkEffect(boolean flicker, boolean trail, @NotNull FireworkEffectType type, @NotNull List colors, @@ -16,6 +18,12 @@ public record FireworkEffect(boolean flicker, boolean trail, fadeColors = List.copyOf(fadeColors); } + public FireworkEffect(@NotNull FireworkExplosion explosion) { + this(explosion.hasTwinkle(), explosion.hasTrail(), + FireworkEffectType.fromExplosionShape(explosion.shape()), + explosion.colors(), explosion.fadeColors()); + } + /** * Retrieves a firework effect from the given {@code compound}. * @@ -52,4 +60,11 @@ public record FireworkEffect(boolean flicker, boolean trail, .putIntArray("FadeColors", fadeColors.stream().mapToInt(Color::asRGB).toArray()) .build(); } + + public @NotNull FireworkExplosion toExplosion() { + return new FireworkExplosion( + type.toExplosionShape(), + colors, fadeColors, + trail, flicker); + } } diff --git a/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java b/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java index 67efbb32c..522c5e32e 100644 --- a/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java +++ b/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java @@ -2,10 +2,13 @@ package net.minestom.server.item.firework; import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap; +import net.minestom.server.item.component.FireworkExplosion; +import org.jetbrains.annotations.NotNull; /** * An enumeration that representing all available firework types. */ +@Deprecated public enum FireworkEffectType { SMALL_BALL((byte) 0), LARGE_BALL((byte) 1), @@ -37,6 +40,16 @@ public enum FireworkEffectType { return BY_ID.get(id); } + public static FireworkEffectType fromExplosionShape(@NotNull FireworkExplosion.Shape shape) { + return switch (shape) { + case SMALL_BALL -> SMALL_BALL; + case LARGE_BALL -> LARGE_BALL; + case STAR -> STAR_SHAPED; + case CREEPER -> CREEPER_SHAPED; + case BURST -> BURST; + }; + } + /** * Retrieves the type of the firework effect. * @@ -45,5 +58,15 @@ public enum FireworkEffectType { public byte getType() { return type; } + + public FireworkExplosion.Shape toExplosionShape() { + return switch (this) { + case SMALL_BALL -> FireworkExplosion.Shape.SMALL_BALL; + case LARGE_BALL -> FireworkExplosion.Shape.LARGE_BALL; + case STAR_SHAPED -> FireworkExplosion.Shape.STAR; + case CREEPER_SHAPED -> FireworkExplosion.Shape.CREEPER; + case BURST -> FireworkExplosion.Shape.BURST; + }; + } } diff --git a/src/main/java/net/minestom/server/item/metadata/BundleMeta.java b/src/main/java/net/minestom/server/item/metadata/BundleMeta.java index 9392d0d47..8e05f04b0 100644 --- a/src/main/java/net/minestom/server/item/metadata/BundleMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/BundleMeta.java @@ -2,51 +2,51 @@ package net.minestom.server.item.metadata; import net.minestom.server.item.ItemMetaView; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; import java.util.ArrayList; import java.util.List; -@ApiStatus.Experimental -public record BundleMeta(TagReadable readable) implements ItemMetaView { - private static final Tag> ITEMS = Tag.ItemStack("Items").list().defaultValue(List.of()); +@Deprecated +public record BundleMeta(ItemComponentPatch components) implements ItemMetaView { public @NotNull List getItems() { - return getTag(ITEMS); + return components.get(ItemComponent.BUNDLE_CONTENTS, List.of()); } @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + @Deprecated + public record Builder(ItemComponentPatch.Builder components) implements ItemMetaView.Builder { public Builder items(@NotNull List items) { - setTag(ITEMS, items); + if (items.isEmpty()) { + components.remove(ItemComponent.BUNDLE_CONTENTS); + } else { + components.set(ItemComponent.BUNDLE_CONTENTS, items); + } return this; } - @ApiStatus.Experimental public Builder addItem(@NotNull ItemStack item) { - var newList = new ArrayList<>(getTag(ITEMS)); + var newList = new ArrayList<>(components.get(ItemComponent.BUNDLE_CONTENTS, List.of())); newList.add(item); return items(newList); } - @ApiStatus.Experimental public Builder removeItem(@NotNull ItemStack item) { - var newList = new ArrayList<>(getTag(ITEMS)); + var newList = new ArrayList<>(components.get(ItemComponent.BUNDLE_CONTENTS, List.of())); newList.remove(item); return items(newList); } + } } diff --git a/src/main/java/net/minestom/server/item/metadata/CompassMeta.java b/src/main/java/net/minestom/server/item/metadata/CompassMeta.java index 24040a879..ea1c40c93 100644 --- a/src/main/java/net/minestom/server/item/metadata/CompassMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/CompassMeta.java @@ -3,66 +3,58 @@ package net.minestom.server.item.metadata; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; import net.minestom.server.item.ItemMetaView; -import net.minestom.server.tag.*; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; +import net.minestom.server.item.component.LodestoneTracker; +import net.minestom.server.tag.Tag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -public record CompassMeta(TagReadable readable) implements ItemMetaView { - private static final Tag LODESTONE_TRACKED = Tag.Boolean("LodestoneTracked").defaultValue(false); - private static final Tag LODESTONE_DIMENSION = Tag.String("LodestoneDimension"); - private static final Tag LODESTONE_POSITION = Tag.Structure("LodestonePos", new TagSerializer<>() { - @Override - public @Nullable Point read(@NotNull TagReadable reader) { - final Integer x = reader.getTag(Tag.Integer("X")); - final Integer y = reader.getTag(Tag.Integer("Y")); - final Integer z = reader.getTag(Tag.Integer("Z")); - if (x == null || y == null || z == null) return null; - return new Vec(x, y, z); - } - - @Override - public void write(@NotNull TagWritable writer, @NotNull Point value) { - writer.setTag(Tag.Integer("X"), value.blockX()); - writer.setTag(Tag.Integer("Y"), value.blockY()); - writer.setTag(Tag.Integer("Z"), value.blockZ()); - } - }); +@Deprecated +public record CompassMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { public boolean isLodestoneTracked() { - return getTag(LODESTONE_TRACKED); + LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER); + return tracker != null && tracker.tracked(); } public @Nullable String getLodestoneDimension() { - return getTag(LODESTONE_DIMENSION); + LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER); + return tracker == null ? null : tracker.dimension(); } public @Nullable Point getLodestonePosition() { - return getTag(LODESTONE_POSITION); + LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER); + return tracker == null ? null : tracker.blockPosition(); } @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + @Deprecated + public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { + // The empty state isnt really valid because the dimension is empty (invalid), but these functions need to set each so its simpler. + private static final LodestoneTracker EMPTY = new LodestoneTracker("", Vec.ZERO, false); public Builder lodestoneTracked(boolean lodestoneTracked) { - setTag(LODESTONE_TRACKED, lodestoneTracked); + LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY); + components.set(ItemComponent.LODESTONE_TRACKER, tracker.withTracked(lodestoneTracked)); return this; } public Builder lodestoneDimension(@Nullable String lodestoneDimension) { - setTag(LODESTONE_DIMENSION, lodestoneDimension); + LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY); + components.set(ItemComponent.LODESTONE_TRACKER, tracker.withDimension(lodestoneDimension)); return this; } public Builder lodestonePosition(@Nullable Point lodestonePosition) { - setTag(LODESTONE_POSITION, lodestonePosition); + LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY); + components.set(ItemComponent.LODESTONE_TRACKER, tracker.withBlockPosition(lodestonePosition)); return this; } } diff --git a/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java b/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java index cfc525f2f..3f4597fdc 100644 --- a/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java @@ -2,48 +2,52 @@ package net.minestom.server.item.metadata; import net.minestom.server.item.ItemMetaView; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; import java.util.List; -public record CrossbowMeta(TagReadable readable) implements ItemMetaView { - private static final Tag> PROJECTILES = Tag.ItemStack("ChargedProjectiles").list().defaultValue(List.of()); - private static final Tag CHARGED = Tag.Boolean("Charged").defaultValue(false); +@Deprecated +public record CrossbowMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { public @NotNull List getProjectiles() { - return getTag(PROJECTILES); + return components.get(ItemComponent.CHARGED_PROJECTILES, List.of()); } public boolean isCharged() { - return getTag(CHARGED); + return components.has(ItemComponent.CHARGED_PROJECTILES); } @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + @Deprecated + public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { public Builder projectile(@NotNull ItemStack projectile) { - setTag(PROJECTILES, List.of(projectile)); + components.set(ItemComponent.CHARGED_PROJECTILES, List.of(projectile)); return this; } public Builder projectiles(@NotNull ItemStack projectile1, @NotNull ItemStack projectile2, @NotNull ItemStack projectile3) { - setTag(PROJECTILES, List.of(projectile1, projectile2, projectile3)); + components.set(ItemComponent.CHARGED_PROJECTILES, List.of(projectile1, projectile2, projectile3)); return this; } public Builder charged(boolean charged) { - setTag(CHARGED, charged); + if (charged) { + // Only reset to empty list if we dont have any projectiles yet, as to not overwrite the call to projectiles() + if (!components.has(ItemComponent.CHARGED_PROJECTILES)) + components.set(ItemComponent.CHARGED_PROJECTILES, List.of()); + } else { + components.remove(ItemComponent.CHARGED_PROJECTILES); + } return this; } } diff --git a/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java b/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java index 5bb733523..f1ea75641 100644 --- a/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java @@ -2,55 +2,49 @@ package net.minestom.server.item.metadata; import net.minestom.server.item.Enchantment; import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.ItemSerializers; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.EnchantmentList; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import static net.minestom.server.item.ItemSerializers.ENCHANTMENT_SERIALIZER; - -public record EnchantedBookMeta(TagReadable readable) implements ItemMetaView { - static final Tag> ENCHANTMENTS = Tag.Structure("StoredEnchantments", ENCHANTMENT_SERIALIZER).list().map(enchantmentEntry -> { - Map map = new HashMap<>(); - for (var entry : enchantmentEntry) map.put(entry.enchantment(), entry.level()); - return Map.copyOf(map); - }, o -> { - List entries = new ArrayList<>(); - for (var entry : o.entrySet()) - entries.add(new ItemSerializers.EnchantmentEntry(entry.getKey(), entry.getValue())); - return List.copyOf(entries); - }).defaultValue(Map.of()); +@Deprecated +public record EnchantedBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { public @NotNull Map getStoredEnchantmentMap() { - return getTag(ENCHANTMENTS); + EnchantmentList value = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY); + Map map = new HashMap<>(); + for (var entry : value.enchantments().entrySet()) + map.put(entry.getKey(), entry.getValue().shortValue()); + return map; } @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + @Deprecated + public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { public @NotNull Builder enchantments(@NotNull Map enchantments) { - setTag(ENCHANTMENTS, Map.copyOf(enchantments)); + Map map = new HashMap<>(); + enchantments.forEach((enchantment, level) -> map.put(enchantment, (int) level)); + // Fetch existing to preserve the showInTooltip value. + EnchantmentList existing = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY); + components.set(ItemComponent.STORED_ENCHANTMENTS, new EnchantmentList(map, existing.showInTooltip())); return this; } public @NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) { - var enchantments = new HashMap<>(getTag(ENCHANTMENTS)); - enchantments.put(enchantment, level); - return enchantments(enchantments); + EnchantmentList value = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY); + components.set(ItemComponent.STORED_ENCHANTMENTS, value.with(enchantment, level)); + return this; } } } diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java index abdd59781..3437f5dd6 100644 --- a/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java @@ -1,35 +1,34 @@ package net.minestom.server.item.metadata; import net.minestom.server.item.ItemMetaView; +import net.minestom.server.item.component.*; import net.minestom.server.item.firework.FireworkEffect; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; -import net.minestom.server.tag.TagSerializer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -public record FireworkEffectMeta(TagReadable readable) implements ItemMetaView { - private static final Tag FIREWORK_EFFECT = Tag.Structure("Explosion", - TagSerializer.fromCompound(FireworkEffect::fromCompound, FireworkEffect::asCompound)); - +@Deprecated +public record FireworkEffectMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { public @Nullable FireworkEffect getFireworkEffect() { - return getTag(FIREWORK_EFFECT); + FireworkExplosion explosion = components.get(ItemComponent.FIREWORK_EXPLOSION); + return explosion == null ? null : new FireworkEffect(explosion); } @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + @Deprecated + public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { public Builder effect(@Nullable FireworkEffect fireworkEffect) { - setTag(FIREWORK_EFFECT, fireworkEffect); + if (fireworkEffect == null) { + components.remove(ItemComponent.FIREWORK_EXPLOSION); + } else { + components.set(ItemComponent.FIREWORK_EXPLOSION, fireworkEffect.toExplosion()); + } return this; } } diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java index 722057271..cb21ccce2 100644 --- a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java @@ -1,48 +1,48 @@ package net.minestom.server.item.metadata; import net.minestom.server.item.ItemMetaView; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.FireworkList; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.item.firework.FireworkEffect; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; -import net.minestom.server.tag.TagSerializer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import java.util.List; -public record FireworkMeta(TagReadable readable) implements ItemMetaView { - private static final Tag> EFFECTS = Tag.Structure("Explosions", - TagSerializer.fromCompound(FireworkEffect::fromCompound, FireworkEffect::asCompound)) - .path("Fireworks").list().defaultValue(List.of()); - private static final Tag FLIGHT_DURATION = Tag.Byte("Flight").path("Fireworks"); +@Deprecated +public record FireworkMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { public @NotNull List getEffects() { - return getTag(EFFECTS); + FireworkList value = components.get(ItemComponent.FIREWORKS); + return value == null ? List.of() : value.explosions().stream().map(FireworkEffect::new).toList(); } public @Nullable Byte getFlightDuration() { - return getTag(FLIGHT_DURATION); + FireworkList value = components.get(ItemComponent.FIREWORKS); + return value == null ? null : value.flightDuration(); } @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + @Deprecated + public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { public Builder effects(List effects) { - setTag(EFFECTS, effects); + FireworkList value = components.get(ItemComponent.FIREWORKS, FireworkList.EMPTY); + components.set(ItemComponent.FIREWORKS, value.withExplosions(effects.stream().map(FireworkEffect::toExplosion).toList())); return this; } public Builder flightDuration(byte flightDuration) { - setTag(FLIGHT_DURATION, flightDuration); + FireworkList value = components.get(ItemComponent.FIREWORKS, FireworkList.EMPTY); + components.set(ItemComponent.FIREWORKS, value.withFlightDuration(flightDuration)); return this; } } diff --git a/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java b/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java index 427a023db..2d885a714 100644 --- a/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java @@ -2,32 +2,38 @@ package net.minestom.server.item.metadata; import net.minestom.server.color.Color; import net.minestom.server.item.ItemMetaView; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.DyedItemColor; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -public record LeatherArmorMeta(TagReadable readable) implements ItemMetaView { - private static final Tag COLOR = Tag.Integer("color").path("display").map(Color::new, Color::asRGB); +@Deprecated +public record LeatherArmorMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { public @Nullable Color getColor() { - return getTag(COLOR); + DyedItemColor value = components.get(ItemComponent.DYED_COLOR); + return value == null ? null : value.color(); } @Override public @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + @Deprecated + public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { public Builder color(@Nullable Color color) { - setTag(COLOR, color); + if (color == null) { + components.remove(ItemComponent.DYED_COLOR); + } else { + DyedItemColor value = components.get(ItemComponent.DYED_COLOR, DyedItemColor.LEATHER); + components.set(ItemComponent.DYED_COLOR, value.withColor(color)); + } return this; } } diff --git a/src/main/java/net/minestom/server/item/metadata/MapMeta.java b/src/main/java/net/minestom/server/item/metadata/MapMeta.java index 9a21425b4..ca70fc0c1 100644 --- a/src/main/java/net/minestom/server/item/metadata/MapMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/MapMeta.java @@ -2,14 +2,21 @@ package net.minestom.server.item.metadata; import net.minestom.server.color.Color; import net.minestom.server.item.ItemMetaView; -import net.minestom.server.tag.*; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentMap; +import net.minestom.server.item.component.ItemComponentPatch; +import net.minestom.server.tag.Tag; +import net.minestom.server.tag.TagReadable; +import net.minestom.server.tag.TagSerializer; +import net.minestom.server.tag.TagWritable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import java.util.List; -public record MapMeta(TagReadable readable) implements ItemMetaView { +public record MapMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { private static final Tag MAP_ID = Tag.Integer("map").defaultValue(0); private static final Tag MAP_SCALE_DIRECTION = Tag.Integer("map_scale_direction").defaultValue(0); private static final Tag> DECORATIONS = Tag.Structure("Decorations", new TagSerializer() { @@ -53,13 +60,10 @@ public record MapMeta(TagReadable readable) implements ItemMetaView @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { public Builder mapId(int value) { setTag(MAP_ID, value); diff --git a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java index 40b4ca98d..9e4cd9b75 100644 --- a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java @@ -6,7 +6,14 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.ListBinaryTag; import net.minestom.server.entity.PlayerSkin; import net.minestom.server.item.ItemMetaView; -import net.minestom.server.tag.*; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentMap; +import net.minestom.server.item.component.ItemComponentPatch; +import net.minestom.server.tag.Tag; +import net.minestom.server.tag.TagReadable; +import net.minestom.server.tag.TagSerializer; +import net.minestom.server.tag.TagWritable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; @@ -15,7 +22,7 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView { +public record PlayerHeadMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { public static final Tag SKULL_OWNER = Tag.UUID("Id").path("SkullOwner"); public static final Tag SKIN = Tag.Structure("Properties", new TagSerializer() { private static final Tag TEXTURES = Tag.NBT("textures"); @@ -50,13 +57,10 @@ public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { public Builder skullOwner(@Nullable UUID skullOwner) { setTag(SKULL_OWNER, skullOwner); diff --git a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java b/src/main/java/net/minestom/server/item/metadata/PotionMeta.java index 00742b003..f37c24769 100644 --- a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PotionMeta.java @@ -2,17 +2,24 @@ package net.minestom.server.item.metadata; import net.minestom.server.color.Color; import net.minestom.server.item.ItemMetaView; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentMap; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.potion.CustomPotionEffect; import net.minestom.server.potion.PotionType; import net.minestom.server.registry.StaticProtocolObject; -import net.minestom.server.tag.*; +import net.minestom.server.tag.Tag; +import net.minestom.server.tag.TagReadable; +import net.minestom.server.tag.TagSerializer; +import net.minestom.server.tag.TagWritable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import java.util.List; -public record PotionMeta(TagReadable readable) implements ItemMetaView { +public record PotionMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { private static final Tag POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, StaticProtocolObject::name).defaultValue(PotionType.WATER); private static final Tag> CUSTOM_POTION_EFFECTS = Tag.Structure("CustomPotionEffects", new TagSerializer() { @Override @@ -55,13 +62,10 @@ public record PotionMeta(TagReadable readable) implements ItemMetaView @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { public Builder potionType(@NotNull PotionType potionType) { setTag(POTION_TYPE, potionType); diff --git a/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java b/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java index e90c6de5c..61ce4833e 100644 --- a/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java @@ -3,15 +3,16 @@ package net.minestom.server.item.metadata; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.minestom.server.item.ItemMetaView; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnknownNullability; import java.util.List; -public record WritableBookMeta(TagReadable readable) implements ItemMetaView { +public record WritableBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { private static final Tag> PAGES = Tag.String("pages") .map(s -> LegacyComponentSerializer.legacySection().deserialize(s), textComponent -> LegacyComponentSerializer.legacySection().serialize(textComponent)) @@ -23,13 +24,10 @@ public record WritableBookMeta(TagReadable readable) implements ItemMetaView @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { public Builder pages(@NotNull List<@NotNull Component> pages) { setTag(PAGES, pages); diff --git a/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java b/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java index 20be04fc3..b4508b84a 100644 --- a/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java @@ -4,9 +4,11 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.minestom.server.item.ItemMetaView; +import net.minestom.server.item.component.CustomData; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentMap; +import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.TagReadable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; @@ -14,7 +16,7 @@ import org.jetbrains.annotations.UnknownNullability; import java.util.Arrays; import java.util.List; -public record WrittenBookMeta(TagReadable readable) implements ItemMetaView { +public record WrittenBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { private static final Tag RESOLVED = Tag.Boolean("resolved").defaultValue(false); private static final Tag GENERATION = Tag.Integer("resolved").map(integer -> WrittenBookGeneration.values()[integer], Enum::ordinal); private static final Tag AUTHOR = Tag.String("author"); @@ -45,17 +47,14 @@ public record WrittenBookMeta(TagReadable readable) implements ItemMetaView @UnknownNullability T getTag(@NotNull Tag tag) { - return readable.getTag(tag); + return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); } public enum WrittenBookGeneration { ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, TATTERED } - public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder { - public Builder() { - this(TagHandler.newHandler()); - } + public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { public Builder resolved(boolean resolved) { setTag(RESOLVED, resolved); diff --git a/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java b/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java deleted file mode 100644 index 496d23bb4..000000000 --- a/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.minestom.server.item.rule; - -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.StackingRule; -import net.minestom.server.utils.MathUtils; -import org.jetbrains.annotations.NotNull; - -public final class VanillaStackingRule implements StackingRule { - - @Override - public boolean canBeStacked(@NotNull ItemStack item1, @NotNull ItemStack item2) { - return item1.isSimilar(item2); - } - - @Override - public boolean canApply(@NotNull ItemStack item, int newAmount) { - return MathUtils.isBetween(newAmount, 0, getMaxSize(item)); - } - - @Override - public @NotNull ItemStack apply(@NotNull ItemStack item, int amount) { - return amount > 0 ? item.withAmount(amount) : ItemStack.AIR; - } - - @Override - public int getAmount(@NotNull ItemStack itemStack) { - return itemStack.amount(); - } - - @Override - public int getMaxSize(@NotNull ItemStack itemStack) { - return itemStack.material().maxStackSize(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - return obj != null && getClass() == obj.getClass(); - } -} diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index 8f074f60c..e28920b44 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -2,6 +2,7 @@ package net.minestom.server.network; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; +import net.minestom.server.color.Color; import net.minestom.server.coordinate.Point; import net.minestom.server.entity.Entity; import net.minestom.server.entity.metadata.animal.ArmadilloMeta; @@ -29,6 +30,7 @@ import java.util.function.Function; @ApiStatus.Experimental public final class NetworkBuffer { + public static final Type NOTHING = new NetworkBufferTypeImpl.NothingType(); public static final Type BOOLEAN = new NetworkBufferTypeImpl.BooleanType(); public static final Type BYTE = new NetworkBufferTypeImpl.ByteType(); public static final Type SHORT = new NetworkBufferTypeImpl.ShortType(); @@ -76,6 +78,7 @@ public final class NetworkBuffer { public static final Type SNIFFER_STATE = NetworkBufferTypeImpl.fromEnum(SnifferMeta.State.class); public static final Type ARMADILLO_STATE = NetworkBufferTypeImpl.fromEnum(ArmadilloMeta.State.class); + public static final Type COLOR = new NetworkBufferTypeImpl.ColorType(); ByteBuffer nioBuffer; final boolean resizable; @@ -294,8 +297,21 @@ public final class NetworkBuffer { public interface Type { void write(@NotNull NetworkBuffer buffer, T value); - T read(@NotNull NetworkBuffer buffer); + + default @NotNull Type> list(int maxSize) { + return new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, List value) { + buffer.writeCollection(Type.this, value); + } + + @Override + public List read(@NotNull NetworkBuffer buffer) { + return buffer.readCollection(Type.this, maxSize); + } + }; + } } @FunctionalInterface diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java index 0b1fcff0b..0bd43c218 100644 --- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java +++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java @@ -5,10 +5,12 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; +import net.minestom.server.color.Color; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.item.component.ItemComponent; import net.minestom.server.network.packet.server.play.data.WorldPos; import net.minestom.server.particle.Particle; import net.minestom.server.particle.data.ParticleData; @@ -27,6 +29,17 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { int SEGMENT_BITS = 0x7F; int CONTINUE_BIT = 0x80; + record NothingType() implements NetworkBufferTypeImpl { + @Override + public void write(@NotNull NetworkBuffer buffer, Void value) { + } + + @Override + public Void read(@NotNull NetworkBuffer buffer) { + return null; + } + } + record BooleanType() implements NetworkBufferTypeImpl { @Override public void write(@NotNull NetworkBuffer buffer, Boolean value) { @@ -403,8 +416,11 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { } buffer.write(VAR_INT, value.amount()); buffer.write(VAR_INT, value.material().id()); - buffer.write(VAR_INT, 0); // Added components + buffer.write(VAR_INT, 1); // Added components buffer.write(VAR_INT, 0); // Removed components + var component = ItemComponent.MAX_STACK_SIZE; + buffer.write(VAR_INT, component.id()); + buffer.write(VAR_INT, 16); } @Override @@ -600,6 +616,18 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { } } + record ColorType() implements NetworkBufferTypeImpl { + @Override + public void write(@NotNull NetworkBuffer buffer, Color value) { + buffer.write(NetworkBuffer.INT, value.asRGB()); + } + + @Override + public Color read(@NotNull NetworkBuffer buffer) { + return new Color(buffer.read(NetworkBuffer.INT)); + } + } + static > NetworkBufferTypeImpl fromEnum(Class enumClass) { return new NetworkBufferTypeImpl<>() { @Override diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 14868a24a..bf5e7f335 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -9,20 +9,19 @@ import net.minestom.server.collision.BoundingBox; import net.minestom.server.collision.CollisionUtils; import net.minestom.server.collision.Shape; import net.minestom.server.entity.EntitySpawnType; -import net.minestom.server.entity.EntityType; -import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.instance.block.Block; import net.minestom.server.item.Material; +import net.minestom.server.item.component.ItemComponent; +import net.minestom.server.item.component.ItemComponentMap; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.collection.ObjectArray; +import net.minestom.server.utils.nbt.BinaryTagReader; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -475,12 +474,11 @@ public final class Registry { private final NamespaceID namespace; private final int id; private final String translationKey; - private final int maxStackSize; - private final int maxDamage; - private final boolean isFood; private final Supplier blockSupplier; - private final EquipmentSlot equipmentSlot; - private final EntityType entityType; + private final ItemComponentMap prototype; + +// private final EquipmentSlot equipmentSlot; //todo +// private final EntityType entityType; //todo private final Properties custom; private MaterialEntry(String namespace, Properties main, Properties custom) { @@ -488,35 +486,47 @@ public final class Registry { this.namespace = NamespaceID.from(namespace); this.id = main.getInt("id"); this.translationKey = main.getString("translationKey"); - this.maxStackSize = main.getInt("maxStackSize", 64); - this.maxDamage = main.getInt("maxDamage", 0); - this.isFood = main.getBoolean("edible", false); { final String blockNamespace = main.getString("correspondingBlock", null); this.blockSupplier = blockNamespace != null ? () -> Block.fromNamespaceId(blockNamespace) : () -> null; } - { - final Properties armorProperties = main.section("armorProperties"); - if (armorProperties != null) { - switch (armorProperties.getString("slot")) { - case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS; - case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS; - case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE; - case "head" -> this.equipmentSlot = EquipmentSlot.HELMET; - default -> this.equipmentSlot = null; - } - } else { - this.equipmentSlot = null; - } - } - { - final Properties spawnEggProperties = main.section("spawnEggProperties"); - if (spawnEggProperties != null) { - this.entityType = EntityType.fromNamespaceId(spawnEggProperties.getString("entityType")); - } else { - this.entityType = null; + try { + ItemComponentMap.Builder builder = ItemComponentMap.builder(); + for (Map.Entry entry : main.section("components")) { + //noinspection unchecked + ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); + Check.notNull(component, "Unknown component: " + entry.getKey()); + + byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue()); + BinaryTagReader reader = new BinaryTagReader(new DataInputStream(new ByteArrayInputStream(rawValue))); + builder.set(component, component.read(reader.readNameless())); } + this.prototype = builder.build(); + } catch (IOException e) { + throw new RuntimeException("failed to parse material registry: " + namespace, e); } +// { +// final Properties armorProperties = main.section("armorProperties"); +// if (armorProperties != null) { +// switch (armorProperties.getString("slot")) { +// case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS; +// case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS; +// case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE; +// case "head" -> this.equipmentSlot = EquipmentSlot.HELMET; +// default -> this.equipmentSlot = null; +// } +// } else { +// this.equipmentSlot = null; +// } +// } +// { +// final Properties spawnEggProperties = main.section("spawnEggProperties"); +// if (spawnEggProperties != null) { +// this.entityType = EntityType.fromNamespaceId(spawnEggProperties.getString("entityType")); +// } else { +// this.entityType = null; +// } +// } } public @NotNull NamespaceID namespace() { @@ -527,41 +537,33 @@ public final class Registry { return id; } - public String translationKey() { + public @NotNull String translationKey() { return translationKey; } - public int maxStackSize() { - return maxStackSize; - } - - public int maxDamage() { - return maxDamage; - } - - public boolean isFood() { - return isFood; - } - public @Nullable Block block() { return blockSupplier.get(); } - public boolean isArmor() { - return equipmentSlot != null; + public @NotNull ItemComponentMap prototype() { + return prototype; } - public @Nullable EquipmentSlot equipmentSlot() { - return equipmentSlot; - } - - /** - * Gets the entity type this item can spawn. Only present for spawn eggs (e.g. wolf spawn egg, skeleton spawn egg) - * @return The entity type it can spawn, or null if it is not a spawn egg - */ - public @Nullable EntityType spawnEntityType() { - return entityType; - } + // public boolean isArmor() { +// return equipmentSlot != null; +// } +// +// public @Nullable EquipmentSlot equipmentSlot() { +// return equipmentSlot; +// } +// +// /** +// * Gets the entity type this item can spawn. Only present for spawn eggs (e.g. wolf spawn egg, skeleton spawn egg) +// * @return The entity type it can spawn, or null if it is not a spawn egg +// */ +// public @Nullable EntityType spawnEntityType() { +// return entityType; +// } @Override public Properties custom() { diff --git a/src/main/java/net/minestom/server/tag/Serializers.java b/src/main/java/net/minestom/server/tag/Serializers.java index 8a37efc5e..f26ff1155 100644 --- a/src/main/java/net/minestom/server/tag/Serializers.java +++ b/src/main/java/net/minestom/server/tag/Serializers.java @@ -5,8 +5,8 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.ServerFlag; import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.UniqueIdUtils; -import java.util.UUID; import java.util.function.Function; /** @@ -23,9 +23,7 @@ final class Serializers { static final Entry STRING = new Entry<>(BinaryTagTypes.STRING, StringBinaryTag::value, StringBinaryTag::stringBinaryTag); static final Entry NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity()); - static final Entry UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, - intArray -> intArrayToUuid(intArray.value()), - uuid -> IntArrayBinaryTag.intArrayBinaryTag(uuidToIntArray(uuid))); + static final Entry UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, UniqueIdUtils::fromNbt, UniqueIdUtils::toNbt); static final Entry ITEM = new Entry<>(BinaryTagTypes.COMPOUND, ItemStack::fromItemNBT, ItemStack::toItemNBT); static final Entry COMPONENT = new Entry<>(BinaryTagTypes.STRING, input -> GsonComponentSerializer.gson().deserialize(input.value()), component -> StringBinaryTag.stringBinaryTag(GsonComponentSerializer.gson().serialize(component))); @@ -59,22 +57,4 @@ final class Serializers { return writer.apply(value); } } - - private static int[] uuidToIntArray(UUID uuid) { - final long uuidMost = uuid.getMostSignificantBits(); - final long uuidLeast = uuid.getLeastSignificantBits(); - return new int[]{ - (int) (uuidMost >> 32), - (int) uuidMost, - (int) (uuidLeast >> 32), - (int) uuidLeast - }; - } - - private static UUID intArrayToUuid(int[] array) { - final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL; - final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL; - - return new UUID(uuidMost, uuidLeast); - } } diff --git a/src/main/java/net/minestom/server/tag/Tag.java b/src/main/java/net/minestom/server/tag/Tag.java index a6a448e28..0ac6bee99 100644 --- a/src/main/java/net/minestom/server/tag/Tag.java +++ b/src/main/java/net/minestom/server/tag/Tag.java @@ -263,7 +263,6 @@ public class Tag { return tag(key, Serializers.STRING); } - @ApiStatus.Experimental public static @NotNull Tag UUID(@NotNull String key) { return tag(key, Serializers.UUID); } diff --git a/src/main/java/net/minestom/server/utils/UniqueIdUtils.java b/src/main/java/net/minestom/server/utils/UniqueIdUtils.java index 242a6a1b5..8d04807af 100644 --- a/src/main/java/net/minestom/server/utils/UniqueIdUtils.java +++ b/src/main/java/net/minestom/server/utils/UniqueIdUtils.java @@ -1,6 +1,8 @@ package net.minestom.server.utils; +import net.kyori.adventure.nbt.IntArrayBinaryTag; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import java.util.UUID; import java.util.regex.Pattern; @@ -21,4 +23,30 @@ public final class UniqueIdUtils { public static boolean isUniqueId(String input) { return input.matches(UNIQUE_ID_PATTERN.pattern()); } + + public static @NotNull UUID fromNbt(@NotNull IntArrayBinaryTag tag) { + return intArrayToUuid(tag.value()); + } + + public static @NotNull IntArrayBinaryTag toNbt(@NotNull UUID uuid) { + return IntArrayBinaryTag.intArrayBinaryTag(uuidToIntArray(uuid)); + } + + public static int[] uuidToIntArray(UUID uuid) { + final long uuidMost = uuid.getMostSignificantBits(); + final long uuidLeast = uuid.getLeastSignificantBits(); + return new int[]{ + (int) (uuidMost >> 32), + (int) uuidMost, + (int) (uuidLeast >> 32), + (int) uuidLeast + }; + } + + public static UUID intArrayToUuid(int[] array) { + final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL; + final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL; + + return new UUID(uuidMost, uuidLeast); + } } diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java index 8a68021ad..41b73393c 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagReader.java @@ -33,5 +33,4 @@ public class BinaryTagReader { String name = input.readUTF(); return Map.entry(name, type.read(input)); } - } diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java new file mode 100644 index 000000000..6e0fdcb14 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java @@ -0,0 +1,119 @@ +package net.minestom.server.utils.nbt; + +import net.kyori.adventure.nbt.*; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public interface BinaryTagSerializer { + + BinaryTagSerializer NOTHING = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull Void value) { + return EndBinaryTag.endBinaryTag(); + } + + @Override + public @NotNull Void read(@NotNull BinaryTag tag) { + return null; + } + }; + + BinaryTagSerializer BYTE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull Byte value) { + return ByteBinaryTag.byteBinaryTag(value); + } + + @Override + public @NotNull Byte read(@NotNull BinaryTag tag) { + return tag instanceof ByteBinaryTag byteBinaryTag ? byteBinaryTag.value() : 0; + } + }; + + BinaryTagSerializer BOOLEAN = BYTE.map(b -> b != 0, b -> (byte) (b ? 1 : 0)); + + BinaryTagSerializer INT = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull Integer value) { + return IntBinaryTag.intBinaryTag(value); + } + + @Override + public @NotNull Integer read(@NotNull BinaryTag tag) { + return tag instanceof IntBinaryTag intBinaryTag ? intBinaryTag.value() : 0; + } + }; + + BinaryTagSerializer STRING = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull String value) { + return StringBinaryTag.stringBinaryTag(value); + } + + @Override + public @NotNull String read(@NotNull BinaryTag tag) { + return tag instanceof StringBinaryTag stringBinaryTag ? stringBinaryTag.value() : ""; + } + }; + + BinaryTagSerializer COMPOUND = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull CompoundBinaryTag value) { + return value; + } + + @Override + public @NotNull CompoundBinaryTag read(@NotNull BinaryTag tag) { + return tag instanceof CompoundBinaryTag compoundBinaryTag ? compoundBinaryTag : CompoundBinaryTag.empty(); + } + }; + + BinaryTagSerializer JSON_COMPONENT = STRING.map( + s -> GsonComponentSerializer.gson().deserialize(s), + c -> GsonComponentSerializer.gson().serialize(c) + ); + BinaryTagSerializer ITEM = COMPOUND.map(ItemStack::fromItemNBT, ItemStack::toItemNBT); + + @NotNull BinaryTag write(@NotNull T value); + @NotNull T read(@NotNull BinaryTag tag); + + default BinaryTagSerializer map(@NotNull Function to, @NotNull Function from) { + return new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull S value) { + return BinaryTagSerializer.this.write(from.apply(value)); + } + + @Override + public @NotNull S read(@NotNull BinaryTag tag) { + return to.apply(BinaryTagSerializer.this.read(tag)); + } + }; + } + + default BinaryTagSerializer> list() { + return new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull List value) { + ListBinaryTag.Builder builder = ListBinaryTag.builder(); + for (T t : value) builder.add(BinaryTagSerializer.this.write(t)); + return builder.build(); + } + + @Override + public @NotNull List read(@NotNull BinaryTag tag) { + if (!(tag instanceof ListBinaryTag listBinaryTag)) return List.of(); + List list = new ArrayList<>(); + for (BinaryTag element : listBinaryTag) + list.add(BinaryTagSerializer.this.read(element)); + return list; + } + }; + } +} diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java index 7690d4de0..0d17ea8e1 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagWriter.java @@ -36,5 +36,4 @@ public class BinaryTagWriter { output.writeUTF(name); type.write(tag, output); } - } From ac18b3fb382880d81694340fbaf84513b3622249 Mon Sep 17 00:00:00 2001 From: mworzala Date: Thu, 11 Apr 2024 12:49:57 -0400 Subject: [PATCH 20/46] chore: delete all old metadata classes for now, may return compatibility later --- .../net/minestom/server/MinecraftServer.java | 4 +- .../item/{component => }/ItemComponent.java | 6 +- .../{component => }/ItemComponentImpl.java | 2 +- .../{component => }/ItemComponentMap.java | 5 +- .../{component => }/ItemComponentPatch.java | 21 +- .../server/item/ItemComponentPatchImpl.java | 4 + .../minestom/server/item/ItemHideFlag.java | 21 -- .../net/minestom/server/item/ItemMeta.java | 166 ------------- .../minestom/server/item/ItemMetaImpl.java | 234 ------------------ .../minestom/server/item/ItemMetaView.java | 15 -- .../server/item/ItemMetaViewImpl.java | 37 --- .../minestom/server/item/ItemSerializers.java | 88 ------- .../net/minestom/server/item/ItemStack.java | 143 +---------- .../minestom/server/item/ItemStackImpl.java | 70 +----- .../net/minestom/server/item/Material.java | 2 - .../server/item/component/CustomData.java | 4 +- .../item/component/EnchantmentList.java | 4 +- .../server/item/component/ItemRarity.java | 4 +- .../server/item/component/MapDecorations.java | 2 +- .../server/item/firework/FireworkEffect.java | 70 ------ .../item/firework/FireworkEffectType.java | 72 ------ .../server/item/metadata/BundleMeta.java | 52 ---- .../server/item/metadata/CompassMeta.java | 61 ----- .../server/item/metadata/CrossbowMeta.java | 54 ---- .../item/metadata/EnchantedBookMeta.java | 50 ---- .../item/metadata/FireworkEffectMeta.java | 35 --- .../server/item/metadata/FireworkMeta.java | 49 ---- .../item/metadata/LeatherArmorMeta.java | 40 --- .../server/item/metadata/MapMeta.java | 91 ------- .../server/item/metadata/PlayerHeadMeta.java | 75 ------ .../server/item/metadata/PotionMeta.java | 85 ------- .../item/metadata/WritableBookMeta.java | 37 --- .../server/item/metadata/WrittenBookMeta.java | 96 ------- .../server/network/NetworkBufferTypeImpl.java | 2 +- .../minestom/server/registry/Registry.java | 4 +- 35 files changed, 48 insertions(+), 1657 deletions(-) rename src/main/java/net/minestom/server/item/{component => }/ItemComponent.java (97%) rename src/main/java/net/minestom/server/item/{component => }/ItemComponentImpl.java (97%) rename src/main/java/net/minestom/server/item/{component => }/ItemComponentMap.java (83%) rename src/main/java/net/minestom/server/item/{component => }/ItemComponentPatch.java (57%) create mode 100644 src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java delete mode 100644 src/main/java/net/minestom/server/item/ItemHideFlag.java delete mode 100644 src/main/java/net/minestom/server/item/ItemMeta.java delete mode 100644 src/main/java/net/minestom/server/item/ItemMetaImpl.java delete mode 100644 src/main/java/net/minestom/server/item/ItemMetaView.java delete mode 100644 src/main/java/net/minestom/server/item/ItemMetaViewImpl.java delete mode 100644 src/main/java/net/minestom/server/item/ItemSerializers.java delete mode 100644 src/main/java/net/minestom/server/item/firework/FireworkEffect.java delete mode 100644 src/main/java/net/minestom/server/item/firework/FireworkEffectType.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/BundleMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/CompassMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/FireworkMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/MapMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/PotionMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java delete mode 100644 src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 98eae2cfb..f619b94eb 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -44,8 +44,8 @@ public final class MinecraftServer { public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class); - public static final String VERSION_NAME = "24w14a"; - public static final int PROTOCOL_VERSION = 1073742008; + public static final String VERSION_NAME = "1.20.5-pre1"; + public static final int PROTOCOL_VERSION = 1073742009; // Threads public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark"; diff --git a/src/main/java/net/minestom/server/item/component/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java similarity index 97% rename from src/main/java/net/minestom/server/item/component/ItemComponent.java rename to src/main/java/net/minestom/server/item/ItemComponent.java index 99f4a4621..8f0d6b231 100644 --- a/src/main/java/net/minestom/server/item/component/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -1,9 +1,9 @@ -package net.minestom.server.item.component; +package net.minestom.server.item; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.color.Color; -import net.minestom.server.item.ItemStack; +import net.minestom.server.item.component.*; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; @@ -13,7 +13,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; -import static net.minestom.server.item.component.ItemComponentImpl.declare; +import static net.minestom.server.item.ItemComponentImpl.declare; public sealed interface ItemComponent extends StaticProtocolObject permits ItemComponentImpl { // Note that even non-networked components are declared here as they still contribute to the component ID counter. diff --git a/src/main/java/net/minestom/server/item/component/ItemComponentImpl.java b/src/main/java/net/minestom/server/item/ItemComponentImpl.java similarity index 97% rename from src/main/java/net/minestom/server/item/component/ItemComponentImpl.java rename to src/main/java/net/minestom/server/item/ItemComponentImpl.java index dcbbf3048..3f45c08b1 100644 --- a/src/main/java/net/minestom/server/item/component/ItemComponentImpl.java +++ b/src/main/java/net/minestom/server/item/ItemComponentImpl.java @@ -1,4 +1,4 @@ -package net.minestom.server.item.component; +package net.minestom.server.item; import net.kyori.adventure.nbt.BinaryTag; import net.minestom.server.network.NetworkBuffer; diff --git a/src/main/java/net/minestom/server/item/component/ItemComponentMap.java b/src/main/java/net/minestom/server/item/ItemComponentMap.java similarity index 83% rename from src/main/java/net/minestom/server/item/component/ItemComponentMap.java rename to src/main/java/net/minestom/server/item/ItemComponentMap.java index fff85468e..124baece9 100644 --- a/src/main/java/net/minestom/server/item/component/ItemComponentMap.java +++ b/src/main/java/net/minestom/server/item/ItemComponentMap.java @@ -1,6 +1,5 @@ -package net.minestom.server.item.component; +package net.minestom.server.item; -import net.kyori.adventure.nbt.CompoundBinaryTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -19,8 +18,6 @@ public interface ItemComponentMap { return value != null ? value : defaultValue; } - @NotNull CompoundBinaryTag asCompound(); - interface Builder { @NotNull Builder set(@NotNull ItemComponent component, @NotNull T value); diff --git a/src/main/java/net/minestom/server/item/component/ItemComponentPatch.java b/src/main/java/net/minestom/server/item/ItemComponentPatch.java similarity index 57% rename from src/main/java/net/minestom/server/item/component/ItemComponentPatch.java rename to src/main/java/net/minestom/server/item/ItemComponentPatch.java index 48db618bd..d148f8350 100644 --- a/src/main/java/net/minestom/server/item/component/ItemComponentPatch.java +++ b/src/main/java/net/minestom/server/item/ItemComponentPatch.java @@ -1,28 +1,19 @@ -package net.minestom.server.item.component; +package net.minestom.server.item; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -public interface ItemComponentPatch extends ItemComponentMap { - - static ItemComponentPatch EMPTY = new ItemComponentPatch() { - @Override public boolean has(@NotNull ItemComponent component) { - return false; - } - - @Override public @Nullable T get(@NotNull ItemComponent component) { - return null; - } - }; //todo +public sealed interface ItemComponentPatch extends ItemComponentMap permits ItemComponentPatchImpl { + @NotNull NetworkBuffer.Type NETWORK_TYPE = null; + @NotNull BinaryTagSerializer NBT_TYPE = null; @NotNull ItemComponentPatch with(@NotNull ItemComponent component, T value); @NotNull ItemComponentPatch without(@NotNull ItemComponent component); - @NotNull Builder builder(); - interface Builder extends ItemComponentMap { @Contract(value = "_, _ -> this", pure = true) diff --git a/src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java b/src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java new file mode 100644 index 000000000..21664c6d8 --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java @@ -0,0 +1,4 @@ +package net.minestom.server.item; + +final class ItemComponentPatchImpl implements ItemComponentPatch { +} diff --git a/src/main/java/net/minestom/server/item/ItemHideFlag.java b/src/main/java/net/minestom/server/item/ItemHideFlag.java deleted file mode 100644 index ad1f95781..000000000 --- a/src/main/java/net/minestom/server/item/ItemHideFlag.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.minestom.server.item; - -/** - * Represents a hide flag which can be applied to an {@link ItemStack} using {@link ItemMeta.Builder#hideFlag(int)}. - */ -@Deprecated -public enum ItemHideFlag { - HIDE_ENCHANTS, - HIDE_ATTRIBUTES, - HIDE_UNBREAKABLE, - HIDE_DESTROYS, - HIDE_PLACED_ON, - HIDE_POTION_EFFECTS, - HIDE_DYE; - - private final int bitFieldPart = 1 << this.ordinal(); - - public int getBitFieldPart() { - return bitFieldPart; - } -} diff --git a/src/main/java/net/minestom/server/item/ItemMeta.java b/src/main/java/net/minestom/server/item/ItemMeta.java deleted file mode 100644 index 8d1d64daa..000000000 --- a/src/main/java/net/minestom/server/item/ItemMeta.java +++ /dev/null @@ -1,166 +0,0 @@ -package net.minestom.server.item; - -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.text.Component; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.attribute.ItemAttribute; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagReadable; -import net.minestom.server.tag.Taggable; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.*; -import java.util.function.Consumer; - -@Deprecated -public sealed interface ItemMeta extends TagReadable permits ItemMetaImpl { - - @NotNull ItemComponentPatch components(); - - @Override - @UnknownNullability T getTag(@NotNull Tag tag); - - @Contract(value = "_, -> new", pure = true) - @NotNull ItemMeta with(@NotNull Consumer<@NotNull Builder> builderConsumer); - - @NotNull CompoundBinaryTag toNBT(); - - @NotNull String toSNBT(); - - @Contract(pure = true) - int getDamage(); - - @Contract(pure = true) - boolean isUnbreakable(); - - @Contract(pure = true) - int getHideFlag(); - - @Contract(pure = true) - @Nullable Component getDisplayName(); - - @Contract(pure = true) - @NotNull List<@NotNull Component> getLore(); - - @Contract(pure = true) - @NotNull Map getEnchantmentMap(); - - @Contract(pure = true) - @NotNull List<@NotNull ItemAttribute> getAttributes(); - - @Contract(pure = true) - int getCustomModelData(); - - @Contract(pure = true) - @NotNull Set<@NotNull String> getCanDestroy(); - - @Contract(pure = true) - boolean canDestroy(@NotNull Block block); - - @Contract(pure = true) - @NotNull Set<@NotNull String> getCanPlaceOn(); - - @Contract(pure = true) - boolean canPlaceOn(@NotNull Block block); - - @Deprecated - sealed interface Builder extends Taggable - permits ItemMetaImpl.Builder, ItemMetaView.Builder { - @NotNull ItemMeta build(); - - @NotNull ItemComponentPatch.Builder components(); - - default @NotNull Builder set(@NotNull Tag tag, @Nullable T value) { - setTag(tag, value); - return this; - } - - @Contract("_ -> this") - default @NotNull Builder damage(int damage) { - return set(ItemTags.DAMAGE, damage); - } - - @Contract("_ -> this") - default @NotNull Builder unbreakable(boolean unbreakable) { - return set(ItemTags.UNBREAKABLE, unbreakable); - } - - @Contract("_ -> this") - default @NotNull Builder hideFlag(int hideFlag) { - return set(ItemTags.HIDE_FLAGS, hideFlag); - } - - @Contract("_ -> this") - default @NotNull Builder hideFlag(@NotNull ItemHideFlag... hideFlags) { - int result = 0; - for (ItemHideFlag hideFlag : hideFlags) result |= hideFlag.getBitFieldPart(); - return hideFlag(result); - } - - @Contract("_ -> this") - default @NotNull Builder displayName(@Nullable Component displayName) { - return set(ItemTags.NAME, displayName); - } - - @Contract("_ -> this") - default @NotNull Builder lore(@NotNull List lore) { - return set(ItemTags.LORE, lore.isEmpty() ? null : List.class.cast(lore)); - } - - @Contract("_ -> this") - default @NotNull Builder lore(Component... lore) { - return lore(Arrays.asList(lore)); - } - - @Contract("_ -> this") - default @NotNull Builder enchantments(@NotNull Map enchantments) { - return set(ItemTags.ENCHANTMENTS, Map.copyOf(enchantments)); - } - - @Contract("_, _ -> this") - default @NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) { - var enchantments = new HashMap<>(getTag(ItemTags.ENCHANTMENTS)); - enchantments.put(enchantment, level); - return enchantments(enchantments); - } - - @Contract("-> this") - default @NotNull Builder clearEnchantment() { - return enchantments(Map.of()); - } - - @Contract("_ -> this") - default @NotNull Builder attributes(@NotNull List<@NotNull ItemAttribute> attributes) { - return set(ItemTags.ATTRIBUTES, attributes.isEmpty() ? null : attributes); - } - - @Contract("_ -> this") - default @NotNull Builder customModelData(int customModelData) { - return set(ItemTags.CUSTOM_MODEL_DATA, customModelData); - } - - @Contract("_ -> this") - default @NotNull Builder canPlaceOn(@NotNull Set<@NotNull Block> blocks) { - return set(ItemTags.CAN_PLACE_ON, blocks.stream().map(StaticProtocolObject::name).toList()); - } - - @Contract("_ -> this") - default @NotNull Builder canPlaceOn(@NotNull Block... blocks) { - return canPlaceOn(Set.of(blocks)); - } - - @Contract("_ -> this") - default @NotNull Builder canDestroy(@NotNull Set<@NotNull Block> blocks) { - return set(ItemTags.CAN_DESTROY, blocks.stream().map(StaticProtocolObject::name).toList()); - } - - @Contract("_ -> this") - default @NotNull Builder canDestroy(@NotNull Block... blocks) { - return canDestroy(Set.of(blocks)); - } - } -} diff --git a/src/main/java/net/minestom/server/item/ItemMetaImpl.java b/src/main/java/net/minestom/server/item/ItemMetaImpl.java deleted file mode 100644 index e1b7cd50a..000000000 --- a/src/main/java/net/minestom/server/item/ItemMetaImpl.java +++ /dev/null @@ -1,234 +0,0 @@ -package net.minestom.server.item; - -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.TagStringIO; -import net.kyori.adventure.text.Component; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.attribute.ItemAttribute; -import net.minestom.server.item.component.*; -import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.io.IOException; -import java.util.*; -import java.util.function.Consumer; - -@Deprecated -record ItemMetaImpl(ItemComponentPatch components) implements ItemMeta { - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Override - public @NotNull ItemMeta with(@NotNull Consumer builderConsumer) { - Builder builder = new Builder(components.builder()); - builderConsumer.accept(builder); - return builder.build(); - } - - @Override - public @NotNull CompoundBinaryTag toNBT() { - return components.asCompound(); - } - - @Override - public @NotNull String toSNBT() { - try { - return TagStringIO.get().asString(toNBT()); - } catch (IOException e) { - throw new RuntimeException("Failed to convert to SNBT", e); - } - } - - @Override - public int getDamage() { - return components.get(ItemComponent.DAMAGE, 0); - } - - @Override - public boolean isUnbreakable() { - return components.has(ItemComponent.UNBREAKABLE); - } - - @Override - public int getHideFlag() { - return 0; - } - - @Override - public @Nullable Component getDisplayName() { - return components.get(ItemComponent.CUSTOM_NAME); - } - - @Override - public @NotNull List<@NotNull Component> getLore() { - return components.get(ItemComponent.LORE, List.of()); - } - - @Override - public @NotNull Map getEnchantmentMap() { - EnchantmentList enchantments = components.get(ItemComponent.ENCHANTMENTS); - if (enchantments == null) return Map.of(); - Map map = new HashMap<>(enchantments.enchantments().size()); - for (Map.Entry entry : enchantments.enchantments().entrySet()) { - map.put(entry.getKey(), entry.getValue().shortValue()); - } - return map; - } - - @Override - public @NotNull List<@NotNull ItemAttribute> getAttributes() { - //todo - } - - @Override - public int getCustomModelData() { - return components.get(ItemComponent.CUSTOM_MODEL_DATA, 0); - } - - @Override - public @NotNull Set<@NotNull String> getCanDestroy() { - //todo - } - - @Override - public boolean canDestroy(@NotNull Block block) { - //todo - } - - @Override - public @NotNull Set<@NotNull String> getCanPlaceOn() { - //todo - } - - @Override - public boolean canPlaceOn(@NotNull Block block) { - //todo - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ItemMetaImpl itemMeta)) return false; - return components.equals(itemMeta.components); - } - - @Override - public int hashCode() { - return Objects.hash(components); - } - - @Override - public String toString() { - return toSNBT(); - } - - static final class Builder implements ItemMeta.Builder { - private final ItemComponentPatch.Builder components; - private TagHandler tagHandler = null; - - Builder(ItemComponentPatch.Builder components) { - this.components = components; - } - - @Override - public ItemMeta.@NotNull Builder damage(int damage) { - components.set(ItemComponent.DAMAGE, damage); - return this; - } - - @Override - public ItemMeta.@NotNull Builder unbreakable(boolean unbreakable) { - if (unbreakable) { - components.set(ItemComponent.UNBREAKABLE, new Unbreakable( - components.get(ItemComponent.UNBREAKABLE, Unbreakable.DEFAULT).showInTooltip())); - } else { - components.remove(ItemComponent.UNBREAKABLE); - } - return this; - } - - @Override - public ItemMeta.@NotNull Builder hideFlag(int hideFlag) { - return this; //todo - } - - @Override - public ItemMeta.@NotNull Builder displayName(@Nullable Component displayName) { - if (displayName == null) { - components.remove(ItemComponent.CUSTOM_NAME); - } else { - components.set(ItemComponent.CUSTOM_NAME, displayName); - } - return this; - } - - @Override - public ItemMeta.@NotNull Builder lore(@NotNull List lore) { - components.set(ItemComponent.LORE, new ArrayList<>(lore)); - return this; - } - - @Override - public ItemMeta.@NotNull Builder enchantments(@NotNull Map enchantments) { - EnchantmentList existing = components.get(ItemComponent.ENCHANTMENTS, EnchantmentList.EMPTY); - Map map = new HashMap<>(enchantments.size()); - for (Map.Entry entry : enchantments.entrySet()) { - map.put(entry.getKey(), (int) entry.getValue()); - } - components.set(ItemComponent.ENCHANTMENTS, new EnchantmentList(map, existing.showInTooltip())); - return this; - } - - @Override - public ItemMeta.@NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) { - components.set(ItemComponent.ENCHANTMENTS, components.get(ItemComponent.ENCHANTMENTS, EnchantmentList.EMPTY) - .with(enchantment, level)); - return this; - } - - @Override - public ItemMeta.@NotNull Builder attributes(@NotNull List<@NotNull ItemAttribute> attributes) { - return this; //todo - } - - @Override - public ItemMeta.@NotNull Builder customModelData(int customModelData) { - components.set(ItemComponent.CUSTOM_MODEL_DATA, customModelData); - return this; - } - - @Override - public ItemMeta.@NotNull Builder canPlaceOn(@NotNull Set<@NotNull Block> blocks) { - //todo - return this; - } - - @Override - public ItemMeta.@NotNull Builder canDestroy(@NotNull Set<@NotNull Block> blocks) { - //todo - return this; - } - - @Override - public @NotNull TagHandler tagHandler() { - this.tagHandler = TagHandler.fromCompound(components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).nbt()); - return tagHandler; - } - - @Override - public @NotNull ItemMetaImpl build() { - if (tagHandler != null) { - // If tagHandler was called then a tag was probably changed so update custom data. - components.set(ItemComponent.CUSTOM_DATA, new CustomData(tagHandler.asCompound())); - } - return new ItemMetaImpl(components.build()); - } - - } -} diff --git a/src/main/java/net/minestom/server/item/ItemMetaView.java b/src/main/java/net/minestom/server/item/ItemMetaView.java deleted file mode 100644 index 75066b9da..000000000 --- a/src/main/java/net/minestom/server/item/ItemMetaView.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.minestom.server.item; - -import net.minestom.server.tag.TagReadable; -import org.jetbrains.annotations.NotNull; - -@Deprecated -public interface ItemMetaView extends TagReadable { - - @Deprecated - non-sealed interface Builder extends ItemMeta.Builder { - default @NotNull ItemMeta build() { - return new ItemMetaImpl(components().build()); - } - } -} diff --git a/src/main/java/net/minestom/server/item/ItemMetaViewImpl.java b/src/main/java/net/minestom/server/item/ItemMetaViewImpl.java deleted file mode 100644 index 38dd22e4d..000000000 --- a/src/main/java/net/minestom/server/item/ItemMetaViewImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.minestom.server.item; - -import net.minestom.server.item.component.ItemComponentPatch; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -@Deprecated -final class ItemMetaViewImpl { - static > Class viewType(Class metaClass) { - final Type type = metaClass.getGenericInterfaces()[0]; - return (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; - } - - static > T construct(Class metaClass, ItemComponentPatch components) { - try { - final Constructor cons = metaClass.getDeclaredConstructor(ItemComponentPatch.class); - return cons.newInstance(components); - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | - IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - static > V constructBuilder(Class metaClass, ItemComponentPatch.Builder components) { - final Class clazz = viewType(metaClass); - try { - final Constructor cons = clazz.getDeclaredConstructor(ItemComponentPatch.Builder.class); - return cons.newInstance(components); - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | - IllegalAccessException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/net/minestom/server/item/ItemSerializers.java b/src/main/java/net/minestom/server/item/ItemSerializers.java deleted file mode 100644 index d28226c86..000000000 --- a/src/main/java/net/minestom/server/item/ItemSerializers.java +++ /dev/null @@ -1,88 +0,0 @@ -package net.minestom.server.item; - -import net.minestom.server.entity.attribute.Attribute; -import net.minestom.server.entity.attribute.AttributeOperation; -import net.minestom.server.item.attribute.AttributeSlot; -import net.minestom.server.item.attribute.ItemAttribute; -import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagReadable; -import net.minestom.server.tag.TagSerializer; -import net.minestom.server.tag.TagWritable; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Locale; -import java.util.UUID; - -@Deprecated -@ApiStatus.Internal -public final class ItemSerializers { - public static final TagSerializer ENCHANTMENT_SERIALIZER = new TagSerializer<>() { - static final Tag LEVEL = Tag.Short("lvl"); - static final Tag ID = Tag.String("id"); - - @Override - public @Nullable EnchantmentEntry read(@NotNull TagReadable reader) { - final String id = reader.getTag(ID); - final Short level = reader.getTag(LEVEL); - if (id == null || level == null) return null; - final Enchantment enchantment = Enchantment.fromNamespaceId(id); - return new EnchantmentEntry(enchantment, level); - } - - @Override - public void write(@NotNull TagWritable writer, @NotNull EnchantmentEntry value) { - writer.setTag(ID, value.enchantment.name()); - writer.setTag(LEVEL, value.level); - } - }; - - public record EnchantmentEntry(Enchantment enchantment, short level) { - } - - static final TagSerializer ATTRIBUTE_SERIALIZER = new TagSerializer<>() { - static final Tag ID = Tag.UUID("UUID"); - static final Tag AMOUNT = Tag.Double("Amount"); - static final Tag SLOT = Tag.String("Slot").defaultValue("mainhand"); - static final Tag ATTRIBUTE_NAME = Tag.String("AttributeName"); - static final Tag OPERATION = Tag.Integer("Operation"); - static final Tag NAME = Tag.String("Name"); - - @Override - public @Nullable ItemAttribute read(@NotNull TagReadable reader) { - final UUID uuid = reader.getTag(ID); - final double amount = reader.getTag(AMOUNT); - final String slot = reader.getTag(SLOT); - final String attributeName = reader.getTag(ATTRIBUTE_NAME); - final int operation = reader.getTag(OPERATION); - final String name = reader.getTag(NAME); - - final Attribute attribute = Attribute.fromNamespaceId(attributeName.toLowerCase(Locale.ROOT)); - // Wrong attribute name, stop here - if (attribute == null) return null; - final AttributeOperation attributeOperation = AttributeOperation.fromId(operation); - // Wrong attribute operation, stop here - if (attributeOperation == null) return null; - - // Find slot, default to the main hand if the nbt tag is invalid - AttributeSlot attributeSlot; - try { - attributeSlot = AttributeSlot.valueOf(slot.toUpperCase()); - } catch (IllegalArgumentException e) { - attributeSlot = AttributeSlot.MAINHAND; - } - return new ItemAttribute(uuid, name, attribute, attributeOperation, amount, attributeSlot); - } - - @Override - public void write(@NotNull TagWritable writer, @NotNull ItemAttribute value) { - writer.setTag(ID, value.uuid()); - writer.setTag(AMOUNT, value.amount()); - writer.setTag(SLOT, value.slot().name().toLowerCase(Locale.ROOT)); - writer.setTag(ATTRIBUTE_NAME, value.attribute().name()); - writer.setTag(OPERATION, value.operation().getId()); - writer.setTag(NAME, value.name()); - } - }; -} diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 26f8e596d..aac727772 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -1,23 +1,20 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.api.BinaryTagHolder; -import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; -import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.item.component.CustomData; import net.minestom.server.item.component.ItemComponent; import net.minestom.server.item.component.ItemComponentMap; import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagReadable; import net.minestom.server.tag.TagWritable; -import org.jetbrains.annotations.*; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; -import java.io.IOException; -import java.util.List; import java.util.function.Consumer; import java.util.function.IntUnaryOperator; import java.util.function.UnaryOperator; @@ -30,6 +27,7 @@ import java.util.function.UnaryOperator; */ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEventSource permits ItemStackImpl { + /** * Constant AIR item. Should be used instead of 'null'. */ @@ -50,19 +48,6 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv return of(material, 1); } - @Deprecated(forRemoval = true) - @Contract(value = "_, _, _ -> new", pure = true) - static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable CompoundBinaryTag nbtCompound, int amount) { - if (nbtCompound == null) return of(material, amount); - return builder(material).amount(amount).meta(nbtCompound).build(); - } - - @Deprecated(forRemoval = true) - @Contract(value = "_, _ -> new", pure = true) - static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable CompoundBinaryTag nbtCompound) { - return fromNBT(material, nbtCompound, 1); - } - /** * Converts this item to an NBT tag containing the id (material), count (amount), and tag (meta). * @@ -135,78 +120,16 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv */ @NotNull CompoundBinaryTag toItemNBT(); - // BEGIN DEPRECATED PRE-COMPONENT METHODS - - @Deprecated(forRemoval = true) - @Contract(pure = true) - @NotNull ItemMeta meta(); - - @Deprecated(forRemoval = true) - @Contract(pure = true) - @ApiStatus.Experimental - > @NotNull T meta(@NotNull Class metaClass); - - @Deprecated(forRemoval = true) - @Contract(value = "_, _ -> new", pure = true) - @ApiStatus.Experimental - > @NotNull ItemStack withMeta(@NotNull Class metaType, - @NotNull Consumer consumer); - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> new", pure = true) - @NotNull ItemStack withMeta(@NotNull Consumer consumer); - - @Deprecated(forRemoval = true) - @Contract(pure = true) - default @Nullable Component getDisplayName() { - return meta().getDisplayName(); - } - - @Deprecated(forRemoval = true) - @Contract(pure = true) - default @NotNull List<@NotNull Component> getLore() { - return meta().getLore(); - } - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> new", pure = true) - @NotNull ItemStack withMeta(@NotNull ItemMeta meta); - - @Deprecated(forRemoval = true) - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withDisplayName(@Nullable Component displayName) { - return withMeta(builder -> builder.displayName(displayName)); - } - - @Deprecated(forRemoval = true) - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) { - return withDisplayName(componentUnaryOperator.apply(getDisplayName())); - } - - @Deprecated(forRemoval = true) - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withLore(@NotNull List lore) { - return withMeta(builder -> builder.lore(lore)); - } - - @Deprecated(forRemoval = true) - @Contract(value = "_, -> new", pure = true) - default @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) { - return withLore(loreUnaryOperator.apply(getLore())); - } - - // END DEPRECATED PRE-COMPONENT METHODS - @Override default @NotNull HoverEvent asHoverEvent(@NotNull UnaryOperator op) { - try { - final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC); - return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.showItem(material(), amount(), tagHolder))); - } catch (IOException e) { - //todo(matt): revisit, - throw new RuntimeException(e); - } + //todo +// try { +// final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC); +// return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.showItem(material(), amount(), tagHolder))); +// } catch (IOException e) { +// //todo(matt): revisit, +// throw new RuntimeException(e); +// } } sealed interface Builder extends TagWritable @@ -230,45 +153,5 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @Contract(value = "-> new", pure = true) @NotNull ItemStack build(); - // BEGIN DEPRECATED PRE-COMPONENT METHODS - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> this") - @NotNull Builder meta(@NotNull TagHandler tagHandler); - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> this") - @NotNull Builder meta(@NotNull CompoundBinaryTag compound); - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> this") - @NotNull Builder meta(@NotNull ItemMeta itemMeta); - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> this") - @NotNull Builder meta(@NotNull Consumer consumer); - - @Deprecated(forRemoval = true) - @Contract(value = "_, _ -> this") - > @NotNull Builder meta(@NotNull Class metaType, - @NotNull Consumer<@NotNull V> itemMetaConsumer); - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> this") - default @NotNull Builder displayName(@Nullable Component displayName) { - return meta(builder -> builder.displayName(displayName)); - } - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> this") - default @NotNull Builder lore(@NotNull List lore) { - return meta(builder -> builder.lore(lore)); - } - - @Deprecated(forRemoval = true) - @Contract(value = "_ -> this") - default @NotNull Builder lore(Component... lore) { - return meta(builder -> builder.lore(lore)); - } } } diff --git a/src/main/java/net/minestom/server/item/ItemStackImpl.java b/src/main/java/net/minestom/server/item/ItemStackImpl.java index 2e9f40bdf..b5ba800e7 100644 --- a/src/main/java/net/minestom/server/item/ItemStackImpl.java +++ b/src/main/java/net/minestom/server/item/ItemStackImpl.java @@ -2,10 +2,7 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagHandler; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -20,7 +17,7 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component } static ItemStack create(Material material, int amount) { - return create(material, amount, ItemComponentPatch.EMPTY); + return create(material, amount, ItemComponentPatch.builder(material).build()); } @Override @@ -85,34 +82,7 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component @Contract(value = "-> new", pure = true) private @NotNull ItemStack.Builder builder() { - return new Builder(material, amount, new ItemMetaImpl.Builder(meta.tagHandler().copy())); - } - - // BEGIN DEPRECATED PRE-COMPONENT METHODS - - @Override public @NotNull ItemMeta meta() { - return new ItemMetaImpl(components); - } - - @Override - public > @NotNull T meta(@NotNull Class metaClass) { - return ItemMetaViewImpl.construct(metaClass, meta); - } - - @Override - public @NotNull > ItemStack withMeta(@NotNull Class metaType, - @NotNull Consumer consumer) { - return builder().meta(metaType, consumer).build(); - } - - @Override - public @NotNull ItemStack withMeta(@NotNull Consumer consumer) { - return builder().meta(consumer).build(); - } - - @Override - public @NotNull ItemStack withMeta(@NotNull ItemMeta meta) { - return new ItemStackImpl(material, amount, (ItemMetaImpl) meta); + return new Builder(material, amount, components.builder()); } static final class Builder implements ItemStack.Builder { @@ -126,6 +96,12 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component this.components = components; } + Builder(Material material, int amount) { + this.material = material; + this.amount = amount; + this.components = ItemComponentPatch.builder(material); + } + @Override public ItemStack.@NotNull Builder amount(int amount) { this.amount = amount; @@ -154,35 +130,5 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component return ItemStackImpl.create(material, amount, components.build()); } - @Override - public ItemStack.@NotNull Builder meta(@NotNull TagHandler tagHandler) { - return meta(tagHandler.asCompound()); - } - - @Override - public ItemStack.@NotNull Builder meta(@NotNull CompoundBinaryTag compound) { - components.set(ItemComponent.CUSTOM_DATA, new CustomData(compound)); - return this; - } - - @Override - public ItemStack.@NotNull Builder meta(@NotNull ItemMeta itemMeta) { - this.components = itemMeta.components().builder(); - return this; - } - - @Override - public ItemStack.@NotNull Builder meta(@NotNull Consumer consumer) { - consumer.accept(new ItemMetaImpl.Builder(components)); - return this; - } - - @Override - public > ItemStack.@NotNull Builder meta(@NotNull Class metaType, - @NotNull Consumer<@NotNull V> itemMetaConsumer) { - V view = ItemMetaViewImpl.constructBuilder(metaType, components); - itemMetaConsumer.accept(view); - return this; - } } } diff --git a/src/main/java/net/minestom/server/item/Material.java b/src/main/java/net/minestom/server/item/Material.java index c0c277b11..e389136c6 100644 --- a/src/main/java/net/minestom/server/item/Material.java +++ b/src/main/java/net/minestom/server/item/Material.java @@ -1,8 +1,6 @@ package net.minestom.server.item; import net.minestom.server.instance.block.Block; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentMap; import net.minestom.server.registry.Registry; import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; diff --git a/src/main/java/net/minestom/server/item/component/CustomData.java b/src/main/java/net/minestom/server/item/component/CustomData.java index 20f881da8..37b7505b2 100644 --- a/src/main/java/net/minestom/server/item/component/CustomData.java +++ b/src/main/java/net/minestom/server/item/component/CustomData.java @@ -11,7 +11,7 @@ import org.jetbrains.annotations.UnknownNullability; public record CustomData(@NotNull CompoundBinaryTag nbt) implements TagReadable { public static final CustomData EMPTY = new CustomData(CompoundBinaryTag.empty()); - static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { @Override public void write(@NotNull NetworkBuffer buffer, CustomData value) { buffer.write(NetworkBuffer.NBT, value.nbt); @@ -23,7 +23,7 @@ public record CustomData(@NotNull CompoundBinaryTag nbt) implements TagReadable } }; - static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map(CustomData::new, CustomData::nbt); + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map(CustomData::new, CustomData::nbt); @Override public @UnknownNullability T getTag(@NotNull Tag tag) { diff --git a/src/main/java/net/minestom/server/item/component/EnchantmentList.java b/src/main/java/net/minestom/server/item/component/EnchantmentList.java index 913213d39..ce8ee3309 100644 --- a/src/main/java/net/minestom/server/item/component/EnchantmentList.java +++ b/src/main/java/net/minestom/server/item/component/EnchantmentList.java @@ -14,7 +14,7 @@ import java.util.Map; public record EnchantmentList(@NotNull Map enchantments, boolean showInTooltip) { public static final EnchantmentList EMPTY = new EnchantmentList(Map.of(), true); - static NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + public static NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { @Override public void write(@NotNull NetworkBuffer buffer, @NotNull EnchantmentList value) { buffer.write(NetworkBuffer.VAR_INT, value.enchantments.size()); @@ -40,7 +40,7 @@ public record EnchantmentList(@NotNull Map enchantments, b } }; - static BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + public static BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( tag -> { // We have two variants of the enchantment list, one with {levels: {...}, show_in_tooltip: boolean} and one with {...}. CompoundBinaryTag levels = tag.keySet().contains("levels") ? tag.getCompound("levels") : tag; diff --git a/src/main/java/net/minestom/server/item/component/ItemRarity.java b/src/main/java/net/minestom/server/item/component/ItemRarity.java index a27e1c1a4..a88ae4744 100644 --- a/src/main/java/net/minestom/server/item/component/ItemRarity.java +++ b/src/main/java/net/minestom/server/item/component/ItemRarity.java @@ -17,7 +17,7 @@ public enum ItemRarity { private static final ItemRarity[] VALUES = values(); - static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { @Override public void write(@NotNull NetworkBuffer buffer, ItemRarity value) { buffer.writeEnum(ItemRarity.class, value); @@ -29,7 +29,7 @@ public enum ItemRarity { } }; - static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { @Override public @NotNull BinaryTag write(@NotNull ItemRarity value) { return IntBinaryTag.intBinaryTag(value.ordinal()); diff --git a/src/main/java/net/minestom/server/item/component/MapDecorations.java b/src/main/java/net/minestom/server/item/component/MapDecorations.java index 94f19291b..e94f2725a 100644 --- a/src/main/java/net/minestom/server/item/component/MapDecorations.java +++ b/src/main/java/net/minestom/server/item/component/MapDecorations.java @@ -13,7 +13,7 @@ public record MapDecorations(@NotNull Map decorations) { public record Entry(@NotNull String type, double x, double z, float rotation) { } - static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( tag -> { Map map = new HashMap<>(tag.size()); for (Map.Entry entry : tag) { diff --git a/src/main/java/net/minestom/server/item/firework/FireworkEffect.java b/src/main/java/net/minestom/server/item/firework/FireworkEffect.java deleted file mode 100644 index 69c40d6d8..000000000 --- a/src/main/java/net/minestom/server/item/firework/FireworkEffect.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.minestom.server.item.firework; - -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.minestom.server.color.Color; -import net.minestom.server.item.component.FireworkExplosion; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -@Deprecated -public record FireworkEffect(boolean flicker, boolean trail, - @NotNull FireworkEffectType type, - @NotNull List colors, - @NotNull List fadeColors) { - public FireworkEffect { - colors = List.copyOf(colors); - fadeColors = List.copyOf(fadeColors); - } - - public FireworkEffect(@NotNull FireworkExplosion explosion) { - this(explosion.hasTwinkle(), explosion.hasTrail(), - FireworkEffectType.fromExplosionShape(explosion.shape()), - explosion.colors(), explosion.fadeColors()); - } - - /** - * Retrieves a firework effect from the given {@code compound}. - * - * @param compound The NBT connection, which should be a fireworks effect. - * @return A new created firework effect. - */ - public static @NotNull FireworkEffect fromCompound(@NotNull CompoundBinaryTag compound) { - List primaryColor = new ArrayList<>(); - List secondaryColor = new ArrayList<>(); - - for (int rgb : compound.getIntArray("Colors")) - primaryColor.add(new Color(rgb)); - for (int rgb : compound.getIntArray("FadeColors")) - secondaryColor.add(new Color(rgb)); - - boolean flicker = compound.getBoolean("Flicker"); - boolean trail = compound.getBoolean("Trail"); - FireworkEffectType type = FireworkEffectType.byId(compound.getByte("Type")); - - return new FireworkEffect(flicker, trail, type, primaryColor, secondaryColor); - } - - /** - * Retrieves the {@link FireworkEffect} as a {@link CompoundBinaryTag}. - * - * @return The firework effect as a nbt compound. - */ - public @NotNull CompoundBinaryTag asCompound() { - return CompoundBinaryTag.builder() - .putBoolean("Flicker", flicker) - .putBoolean("Trail", trail) - .putByte("Type", (byte) type.getType()) - .putIntArray("Colors", colors.stream().mapToInt(Color::asRGB).toArray()) - .putIntArray("FadeColors", fadeColors.stream().mapToInt(Color::asRGB).toArray()) - .build(); - } - - public @NotNull FireworkExplosion toExplosion() { - return new FireworkExplosion( - type.toExplosionShape(), - colors, fadeColors, - trail, flicker); - } -} diff --git a/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java b/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java deleted file mode 100644 index 522c5e32e..000000000 --- a/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.minestom.server.item.firework; - -import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; -import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap; -import net.minestom.server.item.component.FireworkExplosion; -import org.jetbrains.annotations.NotNull; - -/** - * An enumeration that representing all available firework types. - */ -@Deprecated -public enum FireworkEffectType { - SMALL_BALL((byte) 0), - LARGE_BALL((byte) 1), - STAR_SHAPED((byte) 2), - CREEPER_SHAPED((byte) 3), - BURST((byte) 4); - - private static final Byte2ObjectMap BY_ID = new Byte2ObjectOpenHashMap<>(); - - static { - for (FireworkEffectType value : values()) { - BY_ID.put(value.type, value); - } - } - - private final byte type; - - FireworkEffectType(byte type) { - this.type = type; - } - - /** - * Retrieves a {@link FireworkEffectType} by the given {@code id}. - * - * @param id The identifier of the firework effect type. - * @return A firework effect type or {@code null}. - */ - public static FireworkEffectType byId(byte id) { - return BY_ID.get(id); - } - - public static FireworkEffectType fromExplosionShape(@NotNull FireworkExplosion.Shape shape) { - return switch (shape) { - case SMALL_BALL -> SMALL_BALL; - case LARGE_BALL -> LARGE_BALL; - case STAR -> STAR_SHAPED; - case CREEPER -> CREEPER_SHAPED; - case BURST -> BURST; - }; - } - - /** - * Retrieves the type of the firework effect. - * - * @return The type of the firework effect as a byte. - */ - public byte getType() { - return type; - } - - public FireworkExplosion.Shape toExplosionShape() { - return switch (this) { - case SMALL_BALL -> FireworkExplosion.Shape.SMALL_BALL; - case LARGE_BALL -> FireworkExplosion.Shape.LARGE_BALL; - case STAR_SHAPED -> FireworkExplosion.Shape.STAR; - case CREEPER_SHAPED -> FireworkExplosion.Shape.CREEPER; - case BURST -> FireworkExplosion.Shape.BURST; - }; - } -} - diff --git a/src/main/java/net/minestom/server/item/metadata/BundleMeta.java b/src/main/java/net/minestom/server/item/metadata/BundleMeta.java deleted file mode 100644 index 8e05f04b0..000000000 --- a/src/main/java/net/minestom/server/item/metadata/BundleMeta.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.ArrayList; -import java.util.List; - -@Deprecated -public record BundleMeta(ItemComponentPatch components) implements ItemMetaView { - - public @NotNull List getItems() { - return components.get(ItemComponent.BUNDLE_CONTENTS, List.of()); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Deprecated - public record Builder(ItemComponentPatch.Builder components) implements ItemMetaView.Builder { - - public Builder items(@NotNull List items) { - if (items.isEmpty()) { - components.remove(ItemComponent.BUNDLE_CONTENTS); - } else { - components.set(ItemComponent.BUNDLE_CONTENTS, items); - } - return this; - } - - public Builder addItem(@NotNull ItemStack item) { - var newList = new ArrayList<>(components.get(ItemComponent.BUNDLE_CONTENTS, List.of())); - newList.add(item); - return items(newList); - } - - public Builder removeItem(@NotNull ItemStack item) { - var newList = new ArrayList<>(components.get(ItemComponent.BUNDLE_CONTENTS, List.of())); - newList.remove(item); - return items(newList); - } - - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/CompassMeta.java b/src/main/java/net/minestom/server/item/metadata/CompassMeta.java deleted file mode 100644 index ea1c40c93..000000000 --- a/src/main/java/net/minestom/server/item/metadata/CompassMeta.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Vec; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.item.component.LodestoneTracker; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -@Deprecated -public record CompassMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - - public boolean isLodestoneTracked() { - LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER); - return tracker != null && tracker.tracked(); - } - - public @Nullable String getLodestoneDimension() { - LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER); - return tracker == null ? null : tracker.dimension(); - } - - public @Nullable Point getLodestonePosition() { - LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER); - return tracker == null ? null : tracker.blockPosition(); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Deprecated - public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { - // The empty state isnt really valid because the dimension is empty (invalid), but these functions need to set each so its simpler. - private static final LodestoneTracker EMPTY = new LodestoneTracker("", Vec.ZERO, false); - - public Builder lodestoneTracked(boolean lodestoneTracked) { - LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY); - components.set(ItemComponent.LODESTONE_TRACKER, tracker.withTracked(lodestoneTracked)); - return this; - } - - public Builder lodestoneDimension(@Nullable String lodestoneDimension) { - LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY); - components.set(ItemComponent.LODESTONE_TRACKER, tracker.withDimension(lodestoneDimension)); - return this; - } - - public Builder lodestonePosition(@Nullable Point lodestonePosition) { - LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY); - components.set(ItemComponent.LODESTONE_TRACKER, tracker.withBlockPosition(lodestonePosition)); - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java b/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java deleted file mode 100644 index 3f4597fdc..000000000 --- a/src/main/java/net/minestom/server/item/metadata/CrossbowMeta.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.List; - -@Deprecated -public record CrossbowMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - - public @NotNull List getProjectiles() { - return components.get(ItemComponent.CHARGED_PROJECTILES, List.of()); - } - - public boolean isCharged() { - return components.has(ItemComponent.CHARGED_PROJECTILES); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Deprecated - public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { - - public Builder projectile(@NotNull ItemStack projectile) { - components.set(ItemComponent.CHARGED_PROJECTILES, List.of(projectile)); - return this; - } - - public Builder projectiles(@NotNull ItemStack projectile1, @NotNull ItemStack projectile2, @NotNull ItemStack projectile3) { - components.set(ItemComponent.CHARGED_PROJECTILES, List.of(projectile1, projectile2, projectile3)); - return this; - } - - public Builder charged(boolean charged) { - if (charged) { - // Only reset to empty list if we dont have any projectiles yet, as to not overwrite the call to projectiles() - if (!components.has(ItemComponent.CHARGED_PROJECTILES)) - components.set(ItemComponent.CHARGED_PROJECTILES, List.of()); - } else { - components.remove(ItemComponent.CHARGED_PROJECTILES); - } - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java b/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java deleted file mode 100644 index f1ea75641..000000000 --- a/src/main/java/net/minestom/server/item/metadata/EnchantedBookMeta.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.item.Enchantment; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.EnchantmentList; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.HashMap; -import java.util.Map; - -@Deprecated -public record EnchantedBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - - public @NotNull Map getStoredEnchantmentMap() { - EnchantmentList value = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY); - Map map = new HashMap<>(); - for (var entry : value.enchantments().entrySet()) - map.put(entry.getKey(), entry.getValue().shortValue()); - return map; - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Deprecated - public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { - - public @NotNull Builder enchantments(@NotNull Map enchantments) { - Map map = new HashMap<>(); - enchantments.forEach((enchantment, level) -> map.put(enchantment, (int) level)); - // Fetch existing to preserve the showInTooltip value. - EnchantmentList existing = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY); - components.set(ItemComponent.STORED_ENCHANTMENTS, new EnchantmentList(map, existing.showInTooltip())); - return this; - } - - public @NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) { - EnchantmentList value = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY); - components.set(ItemComponent.STORED_ENCHANTMENTS, value.with(enchantment, level)); - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java deleted file mode 100644 index 3437f5dd6..000000000 --- a/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.*; -import net.minestom.server.item.firework.FireworkEffect; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -@Deprecated -public record FireworkEffectMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - public @Nullable FireworkEffect getFireworkEffect() { - FireworkExplosion explosion = components.get(ItemComponent.FIREWORK_EXPLOSION); - return explosion == null ? null : new FireworkEffect(explosion); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Deprecated - public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { - - public Builder effect(@Nullable FireworkEffect fireworkEffect) { - if (fireworkEffect == null) { - components.remove(ItemComponent.FIREWORK_EXPLOSION); - } else { - components.set(ItemComponent.FIREWORK_EXPLOSION, fireworkEffect.toExplosion()); - } - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java deleted file mode 100644 index cb21ccce2..000000000 --- a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.FireworkList; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.item.firework.FireworkEffect; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.List; - -@Deprecated -public record FireworkMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - - public @NotNull List getEffects() { - FireworkList value = components.get(ItemComponent.FIREWORKS); - return value == null ? List.of() : value.explosions().stream().map(FireworkEffect::new).toList(); - } - - public @Nullable Byte getFlightDuration() { - FireworkList value = components.get(ItemComponent.FIREWORKS); - return value == null ? null : value.flightDuration(); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Deprecated - public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { - - public Builder effects(List effects) { - FireworkList value = components.get(ItemComponent.FIREWORKS, FireworkList.EMPTY); - components.set(ItemComponent.FIREWORKS, value.withExplosions(effects.stream().map(FireworkEffect::toExplosion).toList())); - return this; - } - - public Builder flightDuration(byte flightDuration) { - FireworkList value = components.get(ItemComponent.FIREWORKS, FireworkList.EMPTY); - components.set(ItemComponent.FIREWORKS, value.withFlightDuration(flightDuration)); - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java b/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java deleted file mode 100644 index 2d885a714..000000000 --- a/src/main/java/net/minestom/server/item/metadata/LeatherArmorMeta.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.color.Color; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.DyedItemColor; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -@Deprecated -public record LeatherArmorMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - - public @Nullable Color getColor() { - DyedItemColor value = components.get(ItemComponent.DYED_COLOR); - return value == null ? null : value.color(); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - @Deprecated - public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { - - public Builder color(@Nullable Color color) { - if (color == null) { - components.remove(ItemComponent.DYED_COLOR); - } else { - DyedItemColor value = components.get(ItemComponent.DYED_COLOR, DyedItemColor.LEATHER); - components.set(ItemComponent.DYED_COLOR, value.withColor(color)); - } - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/MapMeta.java b/src/main/java/net/minestom/server/item/metadata/MapMeta.java deleted file mode 100644 index ca70fc0c1..000000000 --- a/src/main/java/net/minestom/server/item/metadata/MapMeta.java +++ /dev/null @@ -1,91 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.color.Color; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentMap; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagReadable; -import net.minestom.server.tag.TagSerializer; -import net.minestom.server.tag.TagWritable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.List; - -public record MapMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - private static final Tag MAP_ID = Tag.Integer("map").defaultValue(0); - private static final Tag MAP_SCALE_DIRECTION = Tag.Integer("map_scale_direction").defaultValue(0); - private static final Tag> DECORATIONS = Tag.Structure("Decorations", new TagSerializer() { - @Override - public @Nullable Decoration read(@NotNull TagReadable reader) { - final String id = reader.getTag(Tag.String("id")); - final Byte type = reader.getTag(Tag.Byte("type")); - final Byte x = reader.getTag(Tag.Byte("x")); - final Byte z = reader.getTag(Tag.Byte("z")); - final Double rot = reader.getTag(Tag.Double("rot")); - if (id == null || type == null || x == null || z == null || rot == null) return null; - return new Decoration(id, type, x, z, rot); - } - - @Override - public void write(@NotNull TagWritable writer, @NotNull Decoration value) { - writer.setTag(Tag.String("id"), value.id); - writer.setTag(Tag.Byte("type"), value.type); - writer.setTag(Tag.Byte("x"), value.x); - writer.setTag(Tag.Byte("z"), value.z); - writer.setTag(Tag.Double("rot"), value.rotation); - } - }).list().defaultValue(List.of()); - private static final Tag MAP_COLOR = Tag.Integer("MapColor").path("display").map(Color::new, Color::asRGB); - - public int getMapId() { - return getTag(MAP_ID); - } - - public int getMapScaleDirection() { - return getTag(MAP_SCALE_DIRECTION); - } - - public List getDecorations() { - return getTag(DECORATIONS); - } - - public @NotNull Color getMapColor() { - return getTag(MAP_COLOR); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { - - public Builder mapId(int value) { - setTag(MAP_ID, value); - return this; - } - - public Builder mapScaleDirection(int value) { - setTag(MAP_SCALE_DIRECTION, value); - return this; - } - - public Builder decorations(List value) { - setTag(DECORATIONS, value); - return this; - } - - public Builder mapColor(Color value) { - setTag(MAP_COLOR, value); - return this; - } - } - - public record Decoration(String id, byte type, byte x, byte z, double rotation) { - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java deleted file mode 100644 index 9e4cd9b75..000000000 --- a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.kyori.adventure.nbt.BinaryTag; -import net.kyori.adventure.nbt.BinaryTagTypes; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.ListBinaryTag; -import net.minestom.server.entity.PlayerSkin; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentMap; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagReadable; -import net.minestom.server.tag.TagSerializer; -import net.minestom.server.tag.TagWritable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -public record PlayerHeadMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - public static final Tag SKULL_OWNER = Tag.UUID("Id").path("SkullOwner"); - public static final Tag SKIN = Tag.Structure("Properties", new TagSerializer() { - private static final Tag TEXTURES = Tag.NBT("textures"); - - @Override - public @Nullable PlayerSkin read(@NotNull TagReadable reader) { - final BinaryTag result = reader.getTag(TEXTURES); - if (!(result instanceof ListBinaryTag textures)) return null; - final CompoundBinaryTag texture = textures.getCompound(0); - final String value = texture.getString("Value"); - final String signature = texture.getString("Signature"); - return new PlayerSkin(value, signature); - } - - @Override - public void write(@NotNull TagWritable writer, @NotNull PlayerSkin playerSkin) { - final String value = Objects.requireNonNullElse(playerSkin.textures(), ""); - final String signature = Objects.requireNonNullElse(playerSkin.signature(), ""); - writer.setTag(TEXTURES, ListBinaryTag.listBinaryTag(BinaryTagTypes.COMPOUND, List.of( - CompoundBinaryTag.builder().putString("Value", value).putString("Signature", signature).build() - ))); - } - }).path("SkullOwner"); - - public @Nullable UUID getSkullOwner() { - return getTag(SKULL_OWNER); - } - - public @Nullable PlayerSkin getPlayerSkin() { - return getTag(SKIN); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { - - public Builder skullOwner(@Nullable UUID skullOwner) { - setTag(SKULL_OWNER, skullOwner); - return this; - } - - public Builder playerSkin(@Nullable PlayerSkin playerSkin) { - setTag(SKIN, playerSkin); - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java b/src/main/java/net/minestom/server/item/metadata/PotionMeta.java deleted file mode 100644 index f37c24769..000000000 --- a/src/main/java/net/minestom/server/item/metadata/PotionMeta.java +++ /dev/null @@ -1,85 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.minestom.server.color.Color; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentMap; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.potion.CustomPotionEffect; -import net.minestom.server.potion.PotionType; -import net.minestom.server.registry.StaticProtocolObject; -import net.minestom.server.tag.Tag; -import net.minestom.server.tag.TagReadable; -import net.minestom.server.tag.TagSerializer; -import net.minestom.server.tag.TagWritable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.List; - -public record PotionMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - private static final Tag POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, StaticProtocolObject::name).defaultValue(PotionType.WATER); - private static final Tag> CUSTOM_POTION_EFFECTS = Tag.Structure("CustomPotionEffects", new TagSerializer() { - @Override - public @Nullable CustomPotionEffect read(@NotNull TagReadable reader) { - final Byte id = reader.getTag(Tag.Byte("Id")); - final Byte amplifier = reader.getTag(Tag.Byte("Amplifier")); - final Integer duration = reader.getTag(Tag.Integer("Duration")); - final Boolean ambient = reader.getTag(Tag.Boolean("Ambient")); - final Boolean showParticles = reader.getTag(Tag.Boolean("ShowParticles")); - final Boolean showIcon = reader.getTag(Tag.Boolean("ShowIcon")); - if (id == null || amplifier == null || duration == null || ambient == null || showParticles == null || showIcon == null) { - return null; - } - return new CustomPotionEffect(id, amplifier, duration, ambient, showParticles, showIcon); - } - - @Override - public void write(@NotNull TagWritable writer, @NotNull CustomPotionEffect value) { - writer.setTag(Tag.Byte("Id"), value.id()); - writer.setTag(Tag.Byte("Amplifier"), value.amplifier()); - writer.setTag(Tag.Integer("Duration"), value.duration()); - writer.setTag(Tag.Boolean("Ambient"), value.isAmbient()); - writer.setTag(Tag.Boolean("ShowParticles"), value.showParticles()); - writer.setTag(Tag.Boolean("ShowIcon"), value.showIcon()); - } - }).list().defaultValue(List.of()); - private static final Tag CUSTOM_POTION_COLOR = Tag.Integer("CustomPotionColor").path("display").map(Color::new, Color::asRGB); - - public @NotNull PotionType getPotionType() { - return getTag(POTION_TYPE); - } - - public @NotNull List getCustomPotionEffects() { - return getTag(CUSTOM_POTION_EFFECTS); - } - - public @Nullable Color getColor() { - return getTag(CUSTOM_POTION_COLOR); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { - - public Builder potionType(@NotNull PotionType potionType) { - setTag(POTION_TYPE, potionType); - return this; - } - - public Builder effects(@NotNull List customPotionEffects) { - setTag(CUSTOM_POTION_EFFECTS, customPotionEffects); - return this; - } - - public Builder color(@NotNull Color color) { - setTag(CUSTOM_POTION_COLOR, color); - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java b/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java deleted file mode 100644 index 61ce4833e..000000000 --- a/src/main/java/net/minestom/server/item/metadata/WritableBookMeta.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.List; - -public record WritableBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - private static final Tag> PAGES = Tag.String("pages") - .map(s -> LegacyComponentSerializer.legacySection().deserialize(s), - textComponent -> LegacyComponentSerializer.legacySection().serialize(textComponent)) - .list().defaultValue(List.of()); - - public @NotNull List<@NotNull Component> getPages() { - return getTag(PAGES); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder { - - public Builder pages(@NotNull List<@NotNull Component> pages) { - setTag(PAGES, pages); - return this; - } - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java b/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java deleted file mode 100644 index b4508b84a..000000000 --- a/src/main/java/net/minestom/server/item/metadata/WrittenBookMeta.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.minestom.server.item.metadata; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import net.minestom.server.item.ItemMetaView; -import net.minestom.server.item.component.CustomData; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentMap; -import net.minestom.server.item.component.ItemComponentPatch; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.Arrays; -import java.util.List; - -public record WrittenBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView { - private static final Tag RESOLVED = Tag.Boolean("resolved").defaultValue(false); - private static final Tag GENERATION = Tag.Integer("resolved").map(integer -> WrittenBookGeneration.values()[integer], Enum::ordinal); - private static final Tag AUTHOR = Tag.String("author"); - private static final Tag TITLE = Tag.String("title"); - private static final Tag> PAGES = Tag.String("pages") - .map(GsonComponentSerializer.gson()::deserialize, GsonComponentSerializer.gson()::serialize) - .list().defaultValue(List.of()); - - public boolean isResolved() { - return getTag(RESOLVED); - } - - public @Nullable WrittenBookGeneration getGeneration() { - return getTag(GENERATION); - } - - public @Nullable String getAuthor() { - return getTag(AUTHOR); - } - - public @Nullable String getTitle() { - return getTag(TITLE); - } - - public @NotNull List<@NotNull Component> getPages() { - return getTag(PAGES); - } - - @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); - } - - public enum WrittenBookGeneration { - ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, TATTERED - } - - public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder { - - public Builder resolved(boolean resolved) { - setTag(RESOLVED, resolved); - return this; - } - - public Builder generation(@Nullable WrittenBookGeneration generation) { - setTag(GENERATION, generation); - return this; - } - - public Builder author(@Nullable String author) { - setTag(AUTHOR, author); - return this; - } - - public Builder author(@Nullable Component author) { - return author(author != null ? LegacyComponentSerializer.legacySection().serialize(author) : null); - } - - public Builder title(@Nullable String title) { - setTag(TITLE, title); - return this; - } - - public Builder title(@Nullable Component title) { - return title(title != null ? LegacyComponentSerializer.legacySection().serialize(title) : null); - } - - public Builder pages(@NotNull List<@NotNull Component> pages) { - setTag(PAGES, pages); - return this; - } - - public Builder pages(Component... pages) { - return pages(Arrays.asList(pages)); - } - } -} diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java index 0bd43c218..804171ac6 100644 --- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java +++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java @@ -8,9 +8,9 @@ import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; import net.minestom.server.color.Color; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -import net.minestom.server.item.component.ItemComponent; import net.minestom.server.network.packet.server.play.data.WorldPos; import net.minestom.server.particle.Particle; import net.minestom.server.particle.data.ParticleData; diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index bf5e7f335..2cf0dcfeb 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -10,9 +10,9 @@ import net.minestom.server.collision.CollisionUtils; import net.minestom.server.collision.Shape; import net.minestom.server.entity.EntitySpawnType; import net.minestom.server.instance.block.Block; +import net.minestom.server.item.ItemComponent; +import net.minestom.server.item.ItemComponentMap; import net.minestom.server.item.Material; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentMap; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.collection.ObjectArray; import net.minestom.server.utils.nbt.BinaryTagReader; From 690f828c3ce7a6bf38ca370737b2b062c8766ca1 Mon Sep 17 00:00:00 2001 From: mworzala Date: Fri, 12 Apr 2024 09:28:07 -0400 Subject: [PATCH 21/46] chore: more components --- .../net/minestom/server/entity/Player.java | 14 +- .../minestom/server/item/ItemComponent.java | 7 +- .../server/item/ItemComponentMap.java | 12 -- .../server/item/ItemComponentPatch.java | 133 +++++++++++++++++- .../net/minestom/server/item/ItemStack.java | 5 +- .../minestom/server/item/ItemStackImpl.java | 31 +++- .../server/item/book/FilteredText.java | 60 ++++++++ .../item/component/WritableBookContent.java | 57 ++++++++ .../item/component/WrittenBookContent.java | 72 ++++++++++ .../server/utils/nbt/BinaryTagUtil.java | 5 + 10 files changed, 367 insertions(+), 29 deletions(-) create mode 100644 src/main/java/net/minestom/server/item/book/FilteredText.java create mode 100644 src/main/java/net/minestom/server/item/component/WritableBookContent.java create mode 100644 src/main/java/net/minestom/server/item/component/WrittenBookContent.java diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 7e0f7cc1b..d1cd1045e 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -18,6 +18,7 @@ import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent.ShowEntity; import net.kyori.adventure.text.event.HoverEventSource; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.title.TitlePart; import net.minestom.server.MinecraftServer; import net.minestom.server.ServerFlag; @@ -48,10 +49,11 @@ import net.minestom.server.instance.Instance; import net.minestom.server.inventory.Inventory; import net.minestom.server.instance.block.Block; import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.item.ItemComponent; import net.minestom.server.inventory.click.Click; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -import net.minestom.server.item.metadata.WrittenBookMeta; +import net.minestom.server.item.component.WrittenBookContent; import net.minestom.server.listener.manager.PacketListenerManager; import net.minestom.server.message.ChatMessageType; import net.minestom.server.message.ChatPosition; @@ -1004,13 +1006,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable, closeInventory(); } + // TODO: when adventure updates, delete this + String title = PlainTextComponentSerializer.plainText().serialize(book.title()); + String author = PlainTextComponentSerializer.plainText().serialize(book.author()); final ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK) - .meta(WrittenBookMeta.class, builder -> builder.resolved(false) - .generation(WrittenBookMeta.WrittenBookGeneration.ORIGINAL) - .author(book.author()) - .title(book.title()) - .pages(book.pages())) + .set(ItemComponent.WRITTEN_BOOK_CONTENT, new WrittenBookContent(book.pages(), title, author, 0, false)) .build(); + // Set book in offhand sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFF_HAND_SLOT, writtenBook)); // Open the book diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index 8f0d6b231..eef69da27 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -51,8 +51,8 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent> BUNDLE_CONTENTS = declare("bundle_contents", NetworkBuffer.ITEM.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); ItemComponent POTION_CONTENTS = declare("potion_contents", null, null); //todo ItemComponent SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo - ItemComponent WRITABLE_BOOK_CONTENT = declare("writable_book_content", null, null); //todo - ItemComponent WRITTEN_BOOK_CONTENT = declare("written_book_content", null, null); //todo + ItemComponent WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE); + ItemComponent WRITTEN_BOOK_CONTENT = declare("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE); ItemComponent TRIM = declare("trim", null, null); //todo ItemComponent DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo ItemComponent ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); @@ -76,8 +76,11 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE); @NotNull T read(@NotNull BinaryTag tag); + @NotNull BinaryTag write(@NotNull T value); @NotNull T read(@NotNull NetworkBuffer reader); + void write(@NotNull NetworkBuffer writer, @NotNull T value); + static @Nullable ItemComponent fromNamespaceId(@NotNull String namespaceId) { return ItemComponentImpl.NAMESPACES.get(namespaceId); diff --git a/src/main/java/net/minestom/server/item/ItemComponentMap.java b/src/main/java/net/minestom/server/item/ItemComponentMap.java index 124baece9..3e3b9d011 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentMap.java +++ b/src/main/java/net/minestom/server/item/ItemComponentMap.java @@ -5,10 +5,6 @@ import org.jetbrains.annotations.Nullable; public interface ItemComponentMap { - static @NotNull Builder builder() { - //todo - } - boolean has(@NotNull ItemComponent component); @Nullable T get(@NotNull ItemComponent component); @@ -18,12 +14,4 @@ public interface ItemComponentMap { return value != null ? value : defaultValue; } - interface Builder { - - @NotNull Builder set(@NotNull ItemComponent component, @NotNull T value); - - void remove(@NotNull ItemComponent component); - - @NotNull ItemComponentMap build(); - } } diff --git a/src/main/java/net/minestom/server/item/ItemComponentPatch.java b/src/main/java/net/minestom/server/item/ItemComponentPatch.java index d148f8350..85b618dfc 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentPatch.java +++ b/src/main/java/net/minestom/server/item/ItemComponentPatch.java @@ -1,18 +1,141 @@ package net.minestom.server.item; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.nbt.BinaryTagSerializer; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public sealed interface ItemComponentPatch extends ItemComponentMap permits ItemComponentPatchImpl { +import java.util.Map; - @NotNull NetworkBuffer.Type NETWORK_TYPE = null; - @NotNull BinaryTagSerializer NBT_TYPE = null; +/** + *

Holds the altered components of an itemstack.

+ * + *

The inner map contains the value for added components, null for removed components, and no entry for unmodified components.

+ * + * @param patch + */ +record ItemComponentPatch(@NotNull Int2ObjectMap patch) { + private static final char REMOVAL_PREFIX = '!'; - @NotNull ItemComponentPatch with(@NotNull ItemComponent component, T value); + public static final ItemComponentPatch EMPTY = new ItemComponentPatch(new Int2ObjectArrayMap<>(0)); - @NotNull ItemComponentPatch without(@NotNull ItemComponent component); + public static final @NotNull NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, ItemComponentPatch value) { + int added = 0; + for (Object o : value.patch.values()) { + if (o != null) added++; + } + + buffer.write(NetworkBuffer.VAR_INT, added); + buffer.write(NetworkBuffer.VAR_INT, value.patch.size() - added); + for (Int2ObjectMap.Entry entry : value.patch.int2ObjectEntrySet()) { + if (entry.getValue() != null) { + buffer.write(NetworkBuffer.VAR_INT, entry.getIntKey()); + //noinspection unchecked + ItemComponent type = (ItemComponent) ItemComponent.fromId(entry.getIntKey()); + assert type != null; + type.write(entry.getValue()); + } + } + for (Int2ObjectMap.Entry entry : value.patch.int2ObjectEntrySet()) { + if (entry.getValue() == null) { + buffer.write(NetworkBuffer.VAR_INT, entry.getIntKey()); + } + } + } + + @Override + public ItemComponentPatch read(@NotNull NetworkBuffer buffer) { + int added = buffer.read(NetworkBuffer.VAR_INT); + int removed = buffer.read(NetworkBuffer.VAR_INT); + Int2ObjectMap patch = new Int2ObjectArrayMap<>(added + removed); + for (int i = 0; i < added; i++) { + int id = buffer.read(NetworkBuffer.VAR_INT); + //noinspection unchecked + ItemComponent type = (ItemComponent) ItemComponent.fromId(id); + Check.notNull(type, "Unknown item component id: {0}", id); + patch.put(id, type.read(buffer)); + } + for (int i = 0; i < removed; i++) { + int id = buffer.read(NetworkBuffer.VAR_INT); + patch.put(id, null); + } + return new ItemComponentPatch(patch); + } + }; + public static final @NotNull BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + if (tag.size() == 0) return EMPTY; + Int2ObjectMap patch = new Int2ObjectArrayMap<>(tag.size()); + for (Map.Entry entry : tag) { + String key = entry.getKey(); + boolean remove = false; + if (!key.isEmpty() && key.charAt(0) == REMOVAL_PREFIX) { + key = key.substring(1); + remove = true; + } + ItemComponent type = ItemComponent.fromNamespaceId(key); + Check.notNull(type, "Unknown item component: {0}", key); + if (remove) { + patch.put(type.id(), null); + } else { + Object value = type.read(entry.getValue()); + patch.put(type.id(), value); + } + } + return new ItemComponentPatch(patch); + }, + patch -> { + if (patch.patch.isEmpty()) return CompoundBinaryTag.empty(); + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + for (Int2ObjectMap.Entry entry : patch.patch.int2ObjectEntrySet()) { + //noinspection unchecked + ItemComponent type = (ItemComponent) ItemComponent.fromId(entry.getIntKey()); + Check.notNull(type, "Unknown item component id: {0}", entry.getIntKey()); + if (entry.getValue() == null) { + builder.put(REMOVAL_PREFIX + type.name(), CompoundBinaryTag.empty()); + } else { + builder.put(type.name(), type.write(entry.getValue())); + } + } + return builder.build(); + } + ); + + public boolean has(@NotNull ItemComponentMap prototype, @NotNull ItemComponent component) { + if (patch.containsKey(component.id())) { + return patch.get(component.id()) != null; + } else { + return prototype.has(component); + } + } + + public @Nullable T get(@NotNull ItemComponentMap prototype, @NotNull ItemComponent component) { + if (patch.containsKey(component.id())) { + return (T) patch.get(component.id()); + } else { + return prototype.get(component); + } + } + + public @NotNull ItemComponentPatch with(@NotNull ItemComponent component, @NotNull T value) { + Int2ObjectMap newPatch = new Int2ObjectArrayMap<>(patch); + newPatch.put(component.id(), value); + return new ItemComponentPatch(newPatch); + } + + public @NotNull ItemComponentPatch without(@NotNull ItemComponent component) { + Int2ObjectMap newPatch = new Int2ObjectArrayMap<>(patch); + newPatch.put(component.id(), null); + return new ItemComponentPatch(newPatch); + } interface Builder extends ItemComponentMap { diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index aac727772..d901c971b 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import net.minestom.server.item.component.CustomData; +import net.minestom.server.network.NetworkBuffer; import net.minestom.server.item.component.ItemComponent; import net.minestom.server.item.component.ItemComponentMap; import net.minestom.server.inventory.ContainerInventory; @@ -33,6 +34,8 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv */ @NotNull ItemStack AIR = ItemStack.of(Material.AIR); + @NotNull NetworkBuffer.Type NETWORK_TYPE = ItemStackImpl.NETWORK_TYPE; + @Contract(value = "_ -> new", pure = true) static @NotNull Builder builder(@NotNull Material material) { return new ItemStackImpl.Builder(material, 1); @@ -93,7 +96,7 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @Contract(value = "_, _ -> new", pure = true) default @NotNull ItemStack withTag(@NotNull Tag tag, @Nullable T value) { - return withMeta(builder -> builder.set(tag, value)); + return with(ItemComponent.CUSTOM_DATA, get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).withTag(tag, value)); } @Override diff --git a/src/main/java/net/minestom/server/item/ItemStackImpl.java b/src/main/java/net/minestom/server/item/ItemStackImpl.java index b5ba800e7..4574d5de6 100644 --- a/src/main/java/net/minestom/server/item/ItemStackImpl.java +++ b/src/main/java/net/minestom/server/item/ItemStackImpl.java @@ -2,7 +2,10 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.item.component.CustomData; +import net.minestom.server.network.NetworkBuffer; import net.minestom.server.tag.Tag; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,23 +14,26 @@ import java.util.function.Consumer; record ItemStackImpl(Material material, int amount, ItemComponentPatch components) implements ItemStack { + static final NetworkBuffer.Type NETWORK_TYPE = null; + static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map(ItemStackImpl::fromCompound, ItemStackImpl::toCompound); + static ItemStack create(Material material, int amount, ItemComponentPatch components) { if (amount <= 0) return AIR; return new ItemStackImpl(material, amount, components); } static ItemStack create(Material material, int amount) { - return create(material, amount, ItemComponentPatch.builder(material).build()); + return create(material, amount, ItemComponentPatch.EMPTY); } @Override public @Nullable T get(@NotNull ItemComponent component) { - return components.get(component); + return components.get(material.prototype(), component); } @Override public boolean has(@NotNull ItemComponent component) { - return components.has(component); + return components.has(material.prototype(), component); } @Override @@ -71,6 +77,8 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component @Override public @NotNull CompoundBinaryTag toItemNBT() { + return (CompoundBinaryTag) NBT_TYPE.write(this); + // CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder() // .putString("id", material.name()) // .putByte("Count", (byte) amount); @@ -85,6 +93,23 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component return new Builder(material, amount, components.builder()); } + private static @NotNull ItemStack fromCompound(@NotNull CompoundBinaryTag tag) { + String id = tag.getString("id"); + Material material = Material.fromNamespaceId(id); + Check.notNull(material, "Unknown material: {0}", id); + int count = tag.getInt("count", 1); + ItemComponentPatch patch = ItemComponentPatch.NBT_TYPE.read(tag.getCompound("components")); + return new ItemStackImpl(material, count, patch); + } + + private static @NotNull CompoundBinaryTag toCompound(@NotNull ItemStack itemStack) { + return CompoundBinaryTag.builder() + .putString("id", itemStack.material().name()) + .putInt("count", itemStack.amount()) + .put("components", ItemComponentPatch.NBT_TYPE.write(((ItemStackImpl) itemStack).components)) + .build(); + } + static final class Builder implements ItemStack.Builder { final Material material; int amount; diff --git a/src/main/java/net/minestom/server/item/book/FilteredText.java b/src/main/java/net/minestom/server/item/book/FilteredText.java new file mode 100644 index 000000000..38c0ad757 --- /dev/null +++ b/src/main/java/net/minestom/server/item/book/FilteredText.java @@ -0,0 +1,60 @@ +package net.minestom.server.item.book; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.text.Component; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record FilteredText(@NotNull T text, @Nullable T filtered) { + + public static @NotNull NetworkBuffer.Type> STRING_NETWORK_TYPE = createNetworkType(NetworkBuffer.STRING); + public static @NotNull BinaryTagSerializer> STRING_NBT_TYPE = createNbtType(BinaryTagSerializer.STRING); + + public static @NotNull NetworkBuffer.Type> COMPONENT_NETWORK_TYPE = createNetworkType(NetworkBuffer.COMPONENT); + public static @NotNull BinaryTagSerializer> COMPONENT_NBT_TYPE = createNbtType(BinaryTagSerializer.JSON_COMPONENT); + + private static NetworkBuffer.@NotNull Type> createNetworkType(@NotNull NetworkBuffer.Type inner) { + return new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, FilteredText value) { + buffer.write(inner, value.text); + buffer.writeOptional(inner, value.filtered); + } + + @Override + public FilteredText read(@NotNull NetworkBuffer buffer) { + return new FilteredText<>(buffer.read(inner), buffer.readOptional(inner)); + } + }; + } + + private static @NotNull BinaryTagSerializer> createNbtType(@NotNull BinaryTagSerializer inner) { + return new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull FilteredText value) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + builder.put("text", inner.write(value.text)); + if (value.filtered != null) { + builder.put("filtered", inner.write(value.filtered)); + } + return builder.build(); + } + + @Override + public @NotNull FilteredText read(@NotNull BinaryTag tag) { + if (tag instanceof CompoundBinaryTag compound) { + BinaryTag textTag = compound.get("text"); + if (textTag != null) { + BinaryTag filteredTag = compound.get("filtered"); + T filtered = filteredTag == null ? null : inner.read(filteredTag); + return new FilteredText<>(inner.read(textTag), filtered); + } + } + return new FilteredText<>(inner.read(tag), null); + } + }; + } +} diff --git a/src/main/java/net/minestom/server/item/component/WritableBookContent.java b/src/main/java/net/minestom/server/item/component/WritableBookContent.java new file mode 100644 index 000000000..b416f20cc --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/WritableBookContent.java @@ -0,0 +1,57 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.minestom.server.item.book.FilteredText; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public record WritableBookContent(@NotNull List> pages) { + public static final WritableBookContent EMPTY = new WritableBookContent(List.of()); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, WritableBookContent value) { + buffer.writeCollection(FilteredText.STRING_NETWORK_TYPE, value.pages); + } + + @Override + public WritableBookContent read(@NotNull NetworkBuffer buffer) { + return new WritableBookContent(buffer.readCollection(FilteredText.STRING_NETWORK_TYPE, 100)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull WritableBookContent value) { + ListBinaryTag.Builder pages = ListBinaryTag.builder(); + for (FilteredText page : value.pages) { + pages.add(FilteredText.STRING_NBT_TYPE.write(page)); + } + return CompoundBinaryTag.builder().put("pages", pages.build()).build(); + } + + @Override + public @NotNull WritableBookContent read(@NotNull BinaryTag tag) { + if (!(tag instanceof CompoundBinaryTag compound)) return EMPTY; + ListBinaryTag pagesTag = compound.getList("pages"); + if (pagesTag.size() == 0) return EMPTY; + + List> pages = new ArrayList<>(pagesTag.size()); + for (BinaryTag pageTag : pagesTag) { + pages.add(FilteredText.STRING_NBT_TYPE.read(pageTag)); + } + return new WritableBookContent(pages); + } + }; + + public WritableBookContent { + pages = List.copyOf(pages); + } + +} diff --git a/src/main/java/net/minestom/server/item/component/WrittenBookContent.java b/src/main/java/net/minestom/server/item/component/WrittenBookContent.java new file mode 100644 index 000000000..5ab694a26 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/WrittenBookContent.java @@ -0,0 +1,72 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.text.Component; +import net.minestom.server.item.book.FilteredText; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record WrittenBookContent(@NotNull List> pages, @NotNull FilteredText title, @NotNull String author, int generation, boolean resolved) { + public static final WrittenBookContent EMPTY = new WrittenBookContent(List.of(), new FilteredText<>("", null), "", 0, true); + + public static final @NotNull NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, WrittenBookContent value) { + buffer.write(FilteredText.STRING_NETWORK_TYPE, value.title); + buffer.write(NetworkBuffer.STRING, value.author); + buffer.write(NetworkBuffer.VAR_INT, value.generation); + buffer.writeCollection(FilteredText.COMPONENT_NETWORK_TYPE, value.pages); + buffer.write(NetworkBuffer.BOOLEAN, value.resolved); + } + + @Override + public WrittenBookContent read(@NotNull NetworkBuffer buffer) { + FilteredText title = buffer.read(FilteredText.STRING_NETWORK_TYPE); + String author = buffer.read(NetworkBuffer.STRING); + int generation = buffer.read(NetworkBuffer.VAR_INT); + List> pages = buffer.readCollection(FilteredText.COMPONENT_NETWORK_TYPE, 100); + boolean resolved = buffer.read(NetworkBuffer.BOOLEAN); + return new WrittenBookContent(pages, title, author, generation, resolved); + } + }; + + public static final @NotNull BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + compound -> { + ListBinaryTag pagesTag = compound.getList("pages"); + List> pages = pagesTag.stream() + .map(FilteredText.COMPONENT_NBT_TYPE::read) + .toList(); + FilteredText title = FilteredText.STRING_NBT_TYPE.read(compound.get("title")); + String author = compound.getString("author"); + int generation = compound.getInt("generation"); + boolean resolved = compound.getBoolean("resolved"); + return new WrittenBookContent(pages, title, author, generation, resolved); + }, + value -> { + ListBinaryTag.Builder pagesTag = ListBinaryTag.builder(); + for (FilteredText page : value.pages) { + pagesTag.add(FilteredText.COMPONENT_NBT_TYPE.write(page)); + } + return CompoundBinaryTag.builder() + .put("pages", pagesTag.build()) + .put("title", FilteredText.STRING_NBT_TYPE.write(value.title)) + .putString("author", value.author) + .putInt("generation", value.generation) + .putBoolean("resolved", value.resolved) + .build(); + } + ); + + public WrittenBookContent { + pages = List.copyOf(pages); + } + + public WrittenBookContent(@NotNull List pages, @NotNull String title, @NotNull String author, int generation, boolean resolved) { + this(pages.stream().map(page -> new FilteredText<>(page, null)).toList(), new FilteredText<>(title, null), author, generation, resolved); + } +} diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java index d61c1074f..d61f07235 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java @@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.*; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; @ApiStatus.Internal public final class BinaryTagUtil { @@ -54,6 +55,10 @@ public final class BinaryTagUtil { } } + public static @Nullable String getStringOrNull(@NotNull CompoundBinaryTag tag, @NotNull String key) { + return tag.keySet().contains(key) ? tag.getString(key) : null; + } + private BinaryTagUtil() { } } From a9e056a119dbf9588c54b7722ee4feea7e211a44 Mon Sep 17 00:00:00 2001 From: mworzala Date: Fri, 12 Apr 2024 11:00:16 -0400 Subject: [PATCH 22/46] chore: more components --- .../src/main/java/net/minestom/demo/Main.java | 9 +++ .../java/net/minestom/demo/PlayerInit.java | 18 ++--- .../minecraft/ArgumentItemStack.java | 3 +- .../minestom/server/entity/EquipmentSlot.java | 11 --- .../minestom/server/entity/ItemEntity.java | 11 ++- .../net/minestom/server/entity/Metadata.java | 2 +- .../net/minestom/server/entity/Player.java | 8 +- .../entity/attribute/AttributeInstance.java | 2 +- .../entity/attribute/AttributeOperation.java | 4 +- .../server/instance/InstanceContainer.java | 2 +- .../block/rule/BlockPlacementRule.java | 4 +- .../inventory/click/ClickProcessors.java | 8 +- .../minestom/server/item/ItemComponent.java | 6 +- .../server/item/ItemComponentImpl.java | 12 +++ .../server/item/ItemComponentMap.java | 14 ++++ .../server/item/ItemComponentMapImpl.java | 47 ++++++++++++ .../server/item/ItemComponentPatch.java | 34 +++++++-- .../server/item/ItemComponentPatchImpl.java | 4 - .../net/minestom/server/item/ItemStack.java | 28 +++---- .../minestom/server/item/ItemStackImpl.java | 37 ++++++---- .../net/minestom/server/item/Material.java | 20 +---- .../listener/BlockPlacementListener.java | 6 +- .../listener/PlayerDiggingListener.java | 7 +- .../server/listener/UseItemListener.java | 3 +- .../server/network/NetworkBuffer.java | 2 - .../server/network/NetworkBufferTypeImpl.java | 36 --------- .../client/play/ClientClickWindowPacket.java | 8 +- .../ClientCreativeInventoryActionPacket.java | 5 +- .../server/play/AdvancementsPacket.java | 5 +- .../server/play/DeclareRecipesPacket.java | 36 ++++----- .../server/play/EntityEquipmentPacket.java | 13 ++-- .../packet/server/play/ExplosionPacket.java | 3 +- .../packet/server/play/SetSlotPacket.java | 23 +++--- .../packet/server/play/TradeListPacket.java | 10 +-- .../packet/server/play/WindowItemsPacket.java | 39 +++++----- .../particle/data/ItemParticleData.java | 4 +- .../minestom/server/registry/Registry.java | 74 ++++++++++--------- .../server/utils/binary/BinaryReader.java | 2 +- .../server/utils/binary/BinaryWriter.java | 2 +- .../server/world/biomes/BiomeParticle.java | 7 +- 40 files changed, 313 insertions(+), 256 deletions(-) create mode 100644 src/main/java/net/minestom/server/item/ItemComponentMapImpl.java delete mode 100644 src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index f8ddbe0b6..c2af8d309 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -31,6 +31,15 @@ import java.util.List; public class Main { public static void main(String[] args) { + try { + + Class.forName("net.minestom.server.item.ItemComponent"); + Class.forName("net.minestom.server.item.ItemStack"); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Failed to load Minestom classes, make sure you are using the correct dependencies"); + return; + } System.setProperty("minestom.experiment.pose-updates", "true"); MinecraftServer.setCompressionThreshold(0); diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 417fa6276..5ecced05e 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -28,9 +28,9 @@ import net.minestom.server.instance.LightingChunk; import net.minestom.server.instance.block.Block; import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -import net.minestom.server.item.metadata.BundleMeta; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.TickMonitor; import net.minestom.server.utils.MathUtils; @@ -38,8 +38,8 @@ import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; import java.time.Duration; +import java.util.List; import java.util.Random; -import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; @@ -100,17 +100,17 @@ public class PlayerInit { player.setPermissionLevel(4); ItemStack itemStack = ItemStack.builder(Material.STONE) .amount(64) - .meta(itemMetaBuilder -> - itemMetaBuilder.canPlaceOn(Set.of(Block.STONE)) - .canDestroy(Set.of(Block.DIAMOND_ORE))) +// .meta(itemMetaBuilder -> +// itemMetaBuilder.canPlaceOn(Set.of(Block.STONE)) +// .canDestroy(Set.of(Block.DIAMOND_ORE))) .build(); player.getInventory().addItemStack(itemStack); ItemStack bundle = ItemStack.builder(Material.BUNDLE) - .meta(BundleMeta.class, bundleMetaBuilder -> { - bundleMetaBuilder.addItem(ItemStack.of(Material.DIAMOND, 5)); - bundleMetaBuilder.addItem(ItemStack.of(Material.RABBIT_FOOT, 5)); - }) + .set(ItemComponent.BUNDLE_CONTENTS, List.of( + ItemStack.of(Material.DIAMOND, 5), + ItemStack.of(Material.RABBIT_FOOT, 5) + )) .build(); player.getInventory().addItemStack(bundle); diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java index 18e5ef25e..aa74f7796 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentItemStack.java @@ -71,7 +71,8 @@ public class ArgumentItemStack extends Argument { throw new ArgumentSyntaxException("Item NBT is invalid", input, INVALID_NBT); } - return ItemStack.fromNBT(material, compound); +// return ItemStack.fromNBT(material, compound); //todo + return ItemStack.of(material); } } diff --git a/src/main/java/net/minestom/server/entity/EquipmentSlot.java b/src/main/java/net/minestom/server/entity/EquipmentSlot.java index 1ec135bcb..dcddbd71e 100644 --- a/src/main/java/net/minestom/server/entity/EquipmentSlot.java +++ b/src/main/java/net/minestom/server/entity/EquipmentSlot.java @@ -1,6 +1,5 @@ package net.minestom.server.entity; -import net.minestom.server.item.attribute.AttributeSlot; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; @@ -40,14 +39,4 @@ public enum EquipmentSlot { return ARMORS; } - public static @NotNull EquipmentSlot fromAttributeSlot(@NotNull AttributeSlot attributeSlot) { - return switch (attributeSlot) { - case MAINHAND -> MAIN_HAND; - case OFFHAND -> OFF_HAND; - case FEET -> BOOTS; - case LEGS -> LEGGINGS; - case CHEST -> CHESTPLATE; - case HEAD -> HELMET; - }; - } } diff --git a/src/main/java/net/minestom/server/entity/ItemEntity.java b/src/main/java/net/minestom/server/entity/ItemEntity.java index f0ff33d7b..9339a23c7 100644 --- a/src/main/java/net/minestom/server/entity/ItemEntity.java +++ b/src/main/java/net/minestom/server/entity/ItemEntity.java @@ -5,7 +5,7 @@ import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.entity.EntityItemMergeEvent; import net.minestom.server.instance.EntityTracker; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.StackingRule; +import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.time.Cooldown; import net.minestom.server.utils.time.TimeUnit; import org.jetbrains.annotations.NotNull; @@ -77,13 +77,12 @@ public class ItemEntity extends Entity { if (getDistanceSquared(itemEntity) > mergeRange * mergeRange) return; final ItemStack itemStackEntity = itemEntity.getItemStack(); - final StackingRule stackingRule = StackingRule.get(); - final boolean canStack = stackingRule.canBeStacked(itemStack, itemStackEntity); + final boolean canStack = itemStack.isSimilar(itemStackEntity); if (!canStack) return; - final int totalAmount = stackingRule.getAmount(itemStack) + stackingRule.getAmount(itemStackEntity); - if (!stackingRule.canApply(itemStack, totalAmount)) return; - final ItemStack result = stackingRule.apply(itemStack, totalAmount); + final int totalAmount = itemStack.amount() + itemStackEntity.amount(); + if (!MathUtils.isBetween(totalAmount, 0, itemStack.maxStackSize())) return; + final ItemStack result = itemStack.withAmount(totalAmount); EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity, result); EventDispatcher.callCancellable(entityItemMergeEvent, () -> { setItemStack(entityItemMergeEvent.getResult()); diff --git a/src/main/java/net/minestom/server/entity/Metadata.java b/src/main/java/net/minestom/server/entity/Metadata.java index 791e8bfaf..ebb7b1138 100644 --- a/src/main/java/net/minestom/server/entity/Metadata.java +++ b/src/main/java/net/minestom/server/entity/Metadata.java @@ -53,7 +53,7 @@ public final class Metadata { } public static Entry ItemStack(@NotNull ItemStack value) { - return new MetadataImpl.EntryImpl<>(TYPE_ITEM_STACK, value, NetworkBuffer.ITEM); + return new MetadataImpl.EntryImpl<>(TYPE_ITEM_STACK, value, ItemStack.NETWORK_TYPE); } public static Entry Boolean(boolean value) { diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index d1cd1045e..64f8413c5 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -46,11 +46,11 @@ import net.minestom.server.event.player.*; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.EntityTracker; import net.minestom.server.instance.Instance; -import net.minestom.server.inventory.Inventory; import net.minestom.server.instance.block.Block; +import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.PlayerInventory; -import net.minestom.server.item.ItemComponent; import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.item.component.WrittenBookContent; @@ -447,7 +447,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, refreshActiveHand(false, isOffHand, false); final ItemStack foodItem = itemUpdateStateEvent.getItemStack(); - final boolean isFood = foodItem.material().isFood(); + final boolean isFood = foodItem.has(ItemComponent.FOOD); if (isFood) { PlayerEatEvent playerEatEvent = new PlayerEatEvent(this, foodItem, eatingHand); @@ -2200,7 +2200,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, return null; final ItemStack updatedItem = getItemInHand(hand); - final boolean isFood = updatedItem.material().isFood(); + final boolean isFood = updatedItem.has(ItemComponent.FOOD); if (isFood && !allowFood) return null; diff --git a/src/main/java/net/minestom/server/entity/attribute/AttributeInstance.java b/src/main/java/net/minestom/server/entity/attribute/AttributeInstance.java index c26416941..c3adb948c 100644 --- a/src/main/java/net/minestom/server/entity/attribute/AttributeInstance.java +++ b/src/main/java/net/minestom/server/entity/attribute/AttributeInstance.java @@ -115,7 +115,7 @@ public final class AttributeInstance { final Collection modifiers = getModifiers(); double base = getBaseValue(); - for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.ADDITION).toArray(AttributeModifier[]::new)) { + for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.ADD_VALUE).toArray(AttributeModifier[]::new)) { base += modifier.getAmount(); } diff --git a/src/main/java/net/minestom/server/entity/attribute/AttributeOperation.java b/src/main/java/net/minestom/server/entity/attribute/AttributeOperation.java index acf09ab91..2a66e7d04 100644 --- a/src/main/java/net/minestom/server/entity/attribute/AttributeOperation.java +++ b/src/main/java/net/minestom/server/entity/attribute/AttributeOperation.java @@ -3,11 +3,11 @@ package net.minestom.server.entity.attribute; import org.jetbrains.annotations.Nullable; public enum AttributeOperation { - ADDITION(0), + ADD_VALUE(0), MULTIPLY_BASE(1), MULTIPLY_TOTAL(2); - private static final AttributeOperation[] VALUES = new AttributeOperation[]{ADDITION, MULTIPLY_BASE, MULTIPLY_TOTAL}; + private static final AttributeOperation[] VALUES = new AttributeOperation[]{ADD_VALUE, MULTIPLY_BASE, MULTIPLY_TOTAL}; private final int id; AttributeOperation(int id) { diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index b62a9c1c7..3c516a8ab 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -158,7 +158,7 @@ public class InstanceContainer extends Instance { this, block, pp.getBlockFace(), blockPosition, new Vec(pp.getCursorX(), pp.getCursorY(), pp.getCursorZ()), pp.getPlayer().getPosition(), - pp.getPlayer().getItemInHand(pp.getHand()).meta(), + pp.getPlayer().getItemInHand(pp.getHand()), pp.getPlayer().isSneaking() ); } else { diff --git a/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java index 68d027514..a2eb98107 100644 --- a/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java +++ b/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java @@ -4,7 +4,7 @@ import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockFace; -import net.minestom.server.item.ItemMeta; +import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,7 +61,7 @@ public abstract class BlockPlacementRule { @NotNull Point placePosition, @Nullable Point cursorPosition, @Nullable Pos playerPosition, - @Nullable ItemMeta usedItemMeta, + @Nullable ItemStack usedItemStack, boolean isPlayerShifting ) { } diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index 5d77f98e4..f34121662 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -10,7 +10,6 @@ import net.minestom.server.inventory.click.Click.Change.Main; import net.minestom.server.inventory.click.Click.Change.Player; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -import net.minestom.server.item.StackingRule; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import org.jetbrains.annotations.NotNull; @@ -29,7 +28,6 @@ import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; * Provides standard implementations of most click functions. */ public final class ClickProcessors { - private static final @NotNull StackingRule RULE = StackingRule.get(); public static @NotNull List leftClick(int slot, @NotNull Click.Getter getter) { final ItemStack cursor = getter.cursor(); @@ -38,7 +36,7 @@ public final class ClickProcessors { final TransactionOperator.Entry pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor); if (pair != null) { // Stackable items, combine their counts return List.of(new Main(slot, pair.left()), new Cursor(pair.right())); - } else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them + } else if (!cursor.isSimilar(clickedItem)) { // If they're unstackable, switch them return List.of(new Main(slot, cursor), new Cursor(clickedItem)); } else { return List.of(); @@ -172,14 +170,14 @@ public final class ClickProcessors { case Click.Info.Double(int slot) -> doubleClick(doubleClickSlots.getList(getter, getter.get(slot), slot), getter); case Click.Info.LeftDrag(List slots) -> { - int cursorAmount = RULE.getAmount(getter.cursor()); + int cursorAmount = getter.cursor().amount(); int amount = (int) Math.floor(cursorAmount / (double) slots.size()); yield dragClick(amount, slots, getter); } case Click.Info.RightDrag(List slots) -> dragClick(1, slots, getter); case Click.Info.MiddleDrag(List slots) -> middleDragClick(slots, getter); case Click.Info.DropSlot(int slot, boolean all) -> - dropFromSlot(slot, all ? RULE.getAmount(getter.get(slot)) : 1, getter); + dropFromSlot(slot, all ? getter.get(slot).amount() : 1, getter); case Click.Info.LeftDropCursor() -> dropFromCursor(getter.cursor().amount(), getter); case Click.Info.RightDropCursor() -> dropFromCursor(1, getter); case Click.Info.MiddleDropCursor() -> List.of(); diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index eef69da27..efc579be6 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -47,8 +47,8 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent MAP_ID = declare("map_id", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); ItemComponent MAP_DECORATIONS = declare("map_decorations", null, MapDecorations.NBT_TYPE); ItemComponent MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null); - ItemComponent> CHARGED_PROJECTILES = declare("charged_projectiles", NetworkBuffer.ITEM.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); - ItemComponent> BUNDLE_CONTENTS = declare("bundle_contents", NetworkBuffer.ITEM.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + ItemComponent> CHARGED_PROJECTILES = declare("charged_projectiles", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + ItemComponent> BUNDLE_CONTENTS = declare("bundle_contents", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); ItemComponent POTION_CONTENTS = declare("potion_contents", null, null); //todo ItemComponent SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo ItemComponent WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE); @@ -69,7 +69,7 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent BANNER_PATTERNS = declare("banner_patterns", null, null); //todo ItemComponent BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity ItemComponent POT_DECORATIONS = declare("pot_decorations", null, null); //todo - ItemComponent> CONTAINER = declare("container", NetworkBuffer.ITEM.list(256), BinaryTagSerializer.ITEM.list()); + ItemComponent> CONTAINER = declare("container", ItemStack.NETWORK_TYPE.list(256), BinaryTagSerializer.ITEM.list()); ItemComponent BLOCK_STATE = declare("block_state", null, null); //todo ItemComponent BEES = declare("bees", null, null); //todo ItemComponent LOCK = declare("lock", null, BinaryTagSerializer.STRING); diff --git a/src/main/java/net/minestom/server/item/ItemComponentImpl.java b/src/main/java/net/minestom/server/item/ItemComponentImpl.java index 3f45c08b1..4850b4692 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentImpl.java +++ b/src/main/java/net/minestom/server/item/ItemComponentImpl.java @@ -34,12 +34,24 @@ record ItemComponentImpl( return nbt.read(tag); } + @Override + public @NotNull BinaryTag write(@NotNull T value) { + Check.notNull(nbt, "{0} cannot be serialized to NBT", this); + return nbt.write(value); + } + @Override public @NotNull T read(@NotNull NetworkBuffer reader) { Check.notNull(network, "{0} cannot be deserialized from network", this); return network.read(reader); } + @Override + public void write(@NotNull NetworkBuffer writer, @NotNull T value) { + Check.notNull(network, "{0} cannot be serialized to network", this); + network.write(writer, value); + } + @Override public String toString() { return name(); diff --git a/src/main/java/net/minestom/server/item/ItemComponentMap.java b/src/main/java/net/minestom/server/item/ItemComponentMap.java index 3e3b9d011..0944c3382 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentMap.java +++ b/src/main/java/net/minestom/server/item/ItemComponentMap.java @@ -1,10 +1,15 @@ package net.minestom.server.item; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public interface ItemComponentMap { + static @NotNull ItemComponentMap.Builder builder() { + return new ItemComponentMapImpl.BuilderImpl(new Int2ObjectArrayMap<>()); + } + boolean has(@NotNull ItemComponent component); @Nullable T get(@NotNull ItemComponent component); @@ -14,4 +19,13 @@ public interface ItemComponentMap { return value != null ? value : defaultValue; } + interface Builder extends ItemComponentMap { + + @NotNull Builder set(@NotNull ItemComponent component, @Nullable Object value); + + @NotNull Builder remove(@NotNull ItemComponent component); + + @NotNull ItemComponentMap build(); + } + } diff --git a/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java b/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java new file mode 100644 index 000000000..42365e07a --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java @@ -0,0 +1,47 @@ +package net.minestom.server.item; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record ItemComponentMapImpl(@NotNull Int2ObjectMap components) implements ItemComponentMap { + @Override + public boolean has(@NotNull ItemComponent component) { + return components.get(component.id()) != null; + } + + @Override + public @Nullable T get(@NotNull ItemComponent component) { + return (T) components.get(component.id()); + } + + public record BuilderImpl(@NotNull Int2ObjectMap components) implements ItemComponentMap.Builder { + + @Override + public boolean has(@NotNull ItemComponent component) { + return components.get(component.id()) != null; + } + + @Override + public @Nullable T get(@NotNull ItemComponent component) { + return (T) components.get(component.id()); + } + + @Override + public @NotNull Builder set(@NotNull ItemComponent component, @Nullable Object value) { + components.put(component.id(), value); + return this; + } + + @Override + public @NotNull Builder remove(@NotNull ItemComponent component) { + components.remove(component.id()); + return this; + } + + @Override + public @NotNull ItemComponentMap build() { + return new ItemComponentMapImpl(components); + } + } +} diff --git a/src/main/java/net/minestom/server/item/ItemComponentPatch.java b/src/main/java/net/minestom/server/item/ItemComponentPatch.java index 85b618dfc..0a07a0fca 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentPatch.java +++ b/src/main/java/net/minestom/server/item/ItemComponentPatch.java @@ -7,7 +7,6 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.nbt.BinaryTagSerializer; import net.minestom.server.utils.validate.Check; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -137,15 +136,34 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { return new ItemComponentPatch(newPatch); } - interface Builder extends ItemComponentMap { + public @NotNull Builder builder() { + return new Builder(new Int2ObjectArrayMap<>(patch)); + } - @Contract(value = "_, _ -> this", pure = true) - @NotNull Builder set(@NotNull ItemComponent component, @NotNull T value); + record Builder(@NotNull Int2ObjectMap patch) implements ItemComponentMap { - @Contract(value = "_ -> this", pure = true) - @NotNull Builder remove(@NotNull ItemComponent component); + @Override + public boolean has(@NotNull ItemComponent component) { + return patch.get(component.id()) != null; + } - @Contract(value = "-> new", pure = true) - @NotNull ItemComponentPatch build(); + @Override + public @Nullable T get(@NotNull ItemComponent component) { + return (T) patch.get(component.id()); + } + + public ItemComponentPatch.@NotNull Builder set(@NotNull ItemComponent component, @NotNull T value) { + patch.put(component.id(), value); + return this; + } + + public ItemComponentPatch.@NotNull Builder remove(@NotNull ItemComponent component) { + patch.put(component.id(), null); + return this; + } + + public @NotNull ItemComponentPatch build() { + return new ItemComponentPatch(patch); + } } } diff --git a/src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java b/src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java deleted file mode 100644 index 21664c6d8..000000000 --- a/src/main/java/net/minestom/server/item/ItemComponentPatchImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.minestom.server.item; - -final class ItemComponentPatchImpl implements ItemComponentPatch { -} diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index d901c971b..bbedc7d2f 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -3,11 +3,9 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; +import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.item.component.CustomData; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.item.component.ItemComponent; -import net.minestom.server.item.component.ItemComponentMap; -import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagReadable; import net.minestom.server.tag.TagWritable; @@ -57,15 +55,7 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv * @param nbtCompound The nbt representation of the item */ static @NotNull ItemStack fromItemNBT(@NotNull CompoundBinaryTag nbtCompound) { -// String id = nbtCompound.getString("id"); -// Check.notNull(id, "Item NBT must contain an id field."); -// Material material = Material.fromNamespaceId(id); -// Check.notNull(material, "Unknown material: {0}", id); -// -// Byte amount = nbtCompound.getByte("Count"); -// if (amount == null) amount = 1; -// final CompoundBinaryTag tag = nbtCompound.getCompound("tag"); -// return tag != null ? fromNBT(material, tag, amount) : of(material, amount); + return ItemStackImpl.NBT_TYPE.read(nbtCompound); } @Contract(pure = true) @@ -91,6 +81,12 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @Contract(value = "_, _ -> new", pure = true) @NotNull ItemStack with(@NotNull ItemComponent component, T value); + default @NotNull ItemStack with(@NotNull ItemComponent component, @NotNull UnaryOperator operator) { + T value = get(component); + if (value == null) return this; + return with(component, operator.apply(value)); + } + @Contract(value = "_, -> new", pure = true) @NotNull ItemStack without(@NotNull ItemComponent component); @@ -102,7 +98,12 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @Override @Contract(pure = true) default @UnknownNullability T getTag(@NotNull Tag tag) { - return getOrDefault(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); + return get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag); + } + + @Contract(pure = true) + default int maxStackSize() { + return get(ItemComponent.MAX_STACK_SIZE, 64); } @Contract(value = "_, -> new", pure = true) @@ -133,6 +134,7 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv // //todo(matt): revisit, // throw new RuntimeException(e); // } + throw new UnsupportedOperationException("todo"); } sealed interface Builder extends TagWritable diff --git a/src/main/java/net/minestom/server/item/ItemStackImpl.java b/src/main/java/net/minestom/server/item/ItemStackImpl.java index 4574d5de6..a0361b94b 100644 --- a/src/main/java/net/minestom/server/item/ItemStackImpl.java +++ b/src/main/java/net/minestom/server/item/ItemStackImpl.java @@ -1,5 +1,6 @@ package net.minestom.server.item; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.item.component.CustomData; import net.minestom.server.network.NetworkBuffer; @@ -14,7 +15,22 @@ import java.util.function.Consumer; record ItemStackImpl(Material material, int amount, ItemComponentPatch components) implements ItemStack { - static final NetworkBuffer.Type NETWORK_TYPE = null; + static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, ItemStack value) { + buffer.write(NetworkBuffer.VAR_INT, value.material().id()); + buffer.write(NetworkBuffer.VAR_INT, value.amount()); + buffer.write(ItemComponentPatch.NETWORK_TYPE, ((ItemStackImpl) value).components); + } + + @Override + public ItemStack read(@NotNull NetworkBuffer buffer) { + Material material = Material.fromId(buffer.read(NetworkBuffer.VAR_INT)); + int amount = buffer.read(NetworkBuffer.VAR_INT); + ItemComponentPatch components = buffer.read(ItemComponentPatch.NETWORK_TYPE); + return new ItemStackImpl(material, amount, components); + } + }; static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map(ItemStackImpl::fromCompound, ItemStackImpl::toCompound); static ItemStack create(Material material, int amount, ItemComponentPatch components) { @@ -26,6 +42,10 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component return create(material, amount, ItemComponentPatch.EMPTY); } + public ItemStackImpl { + Check.notNull(material, "Material cannot be null"); + } + @Override public @Nullable T get(@NotNull ItemComponent component) { return components.get(material.prototype(), component); @@ -50,6 +70,7 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component @Override public @NotNull ItemStack withAmount(int amount) { + if (amount <= 0) return ItemStack.AIR; return create(material, amount, components); } @@ -65,9 +86,7 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component @Override public @NotNull ItemStack consume(int amount) { - int newAmount = amount() - amount; - if (newAmount <= 0) return AIR; - return withAmount(newAmount); + return withAmount(amount() - amount); } @Override @@ -78,14 +97,6 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component @Override public @NotNull CompoundBinaryTag toItemNBT() { return (CompoundBinaryTag) NBT_TYPE.write(this); - -// CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder() -// .putString("id", material.name()) -// .putByte("Count", (byte) amount); -// CompoundBinaryTag nbt = meta.toNBT(); -// if (nbt.size() > 0) builder.put("tag", nbt); -// return builder.build(); - //todo } @Contract(value = "-> new", pure = true) @@ -124,7 +135,7 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component Builder(Material material, int amount) { this.material = material; this.amount = amount; - this.components = ItemComponentPatch.builder(material); + this.components = new ItemComponentPatch.Builder(new Int2ObjectArrayMap<>()); } @Override diff --git a/src/main/java/net/minestom/server/item/Material.java b/src/main/java/net/minestom/server/item/Material.java index e389136c6..6863f54a0 100644 --- a/src/main/java/net/minestom/server/item/Material.java +++ b/src/main/java/net/minestom/server/item/Material.java @@ -76,27 +76,11 @@ public sealed interface Material extends StaticProtocolObject, Materials permits } default boolean isArmor() { - //todo how does armor work nowadays - return false; -// return registry().isArmor(); + return registry().isArmor(); } - default boolean hasState() { - if (this == BOW || this == TRIDENT || this == CROSSBOW || this == SHIELD) { - return true; - } else { - return isFood(); - } - } - - @Deprecated(forRemoval = true) default int maxStackSize() { - return prototype().getOrDefault(ItemComponent.MAX_STACK_SIZE, 64); - } - - @Deprecated(forRemoval = true) - default boolean isFood() { - return prototype().has(ItemComponent.FOOD); + return prototype().get(ItemComponent.MAX_STACK_SIZE, 64); } static @NotNull Collection<@NotNull Material> values() { diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 9da15ee9a..54984f5e0 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -27,8 +27,6 @@ import net.minestom.server.network.packet.server.play.BlockChangePacket; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.validate.Check; -import java.util.concurrent.atomic.AtomicBoolean; - public class BlockPlacementListener { private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); @@ -89,7 +87,9 @@ public class BlockPlacementListener { canPlaceBlock = false; // Spectators can't place blocks } else if (player.getGameMode() == GameMode.ADVENTURE) { //Check if the block can be placed on the block - canPlaceBlock = usedItem.meta().canPlaceOn(interactedBlock); +// canPlaceBlock = usedItem.meta().canPlaceOn(interactedBlock); + canPlaceBlock = false; + //todo } diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index 729832020..d5a5ece37 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -115,9 +115,10 @@ public final class PlayerDiggingListener { } else if (player.getGameMode() == GameMode.ADVENTURE) { // Check if the item can break the block with the current item final ItemStack itemInMainHand = player.getItemInMainHand(); - if (!itemInMainHand.meta().canDestroy(block)) { - return true; - } + //todo +// if (!itemInMainHand.meta().canDestroy(block)) { +// return true; +// } } return false; } diff --git a/src/main/java/net/minestom/server/listener/UseItemListener.java b/src/main/java/net/minestom/server/listener/UseItemListener.java index 93b6443e2..c35efed6f 100644 --- a/src/main/java/net/minestom/server/listener/UseItemListener.java +++ b/src/main/java/net/minestom/server/listener/UseItemListener.java @@ -7,6 +7,7 @@ import net.minestom.server.event.player.PlayerItemAnimationEvent; import net.minestom.server.event.player.PlayerPreEatEvent; import net.minestom.server.event.player.PlayerUseItemEvent; import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.network.packet.client.play.ClientUseItemPacket; @@ -51,7 +52,7 @@ public class UseItemListener { itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.SHIELD; } else if (material == Material.TRIDENT) { itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.TRIDENT; - } else if (material.isFood()) { + } else if (itemStack.has(ItemComponent.FOOD)) { itemAnimationType = PlayerItemAnimationEvent.ItemAnimationType.EAT; // Eating code, contains the eating time customisation diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index e28920b44..af5a48c9e 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -10,7 +10,6 @@ import net.minestom.server.entity.metadata.animal.FrogMeta; import net.minestom.server.entity.metadata.animal.SnifferMeta; import net.minestom.server.entity.metadata.animal.tameable.CatMeta; import net.minestom.server.entity.metadata.other.PaintingMeta; -import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.data.WorldPos; import net.minestom.server.particle.Particle; import net.minestom.server.utils.Direction; @@ -48,7 +47,6 @@ public final class NetworkBuffer { public static final Type COMPONENT = new NetworkBufferTypeImpl.ComponentType(); public static final Type JSON_COMPONENT = new NetworkBufferTypeImpl.JsonComponentType(); public static final Type UUID = new NetworkBufferTypeImpl.UUIDType(); - public static final Type ITEM = new NetworkBufferTypeImpl.ItemType(); public static final Type BYTE_ARRAY = new NetworkBufferTypeImpl.ByteArrayType(); public static final Type LONG_ARRAY = new NetworkBufferTypeImpl.LongArrayType(); diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java index 804171ac6..b1fc188f7 100644 --- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java +++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java @@ -1,16 +1,12 @@ package net.minestom.server.network; import net.kyori.adventure.nbt.BinaryTag; -import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; import net.minestom.server.color.Color; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; -import net.minestom.server.item.ItemComponent; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; import net.minestom.server.network.packet.server.play.data.WorldPos; import net.minestom.server.particle.Particle; import net.minestom.server.particle.data.ParticleData; @@ -407,38 +403,6 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { } } - record ItemType() implements NetworkBufferTypeImpl { - @Override - public void write(@NotNull NetworkBuffer buffer, ItemStack value) { - if (value.isAir()) { - buffer.write(VAR_INT, 0); // 0 count always - return; - } - buffer.write(VAR_INT, value.amount()); - buffer.write(VAR_INT, value.material().id()); - buffer.write(VAR_INT, 1); // Added components - buffer.write(VAR_INT, 0); // Removed components - var component = ItemComponent.MAX_STACK_SIZE; - buffer.write(VAR_INT, component.id()); - buffer.write(VAR_INT, 16); - } - - @Override - public ItemStack read(@NotNull NetworkBuffer buffer) { - int count = buffer.read(VAR_INT); - if (count <= 0) return ItemStack.AIR; - - final int id = buffer.read(VAR_INT); - final Material material = Material.fromId(id); - if (material == null) throw new RuntimeException("Unknown material id: " + id); - - buffer.read(VAR_INT); // Added components - buffer.read(VAR_INT); // Removed components - - return ItemStack.fromNBT(material, CompoundBinaryTag.empty(), count); - } - } - record ByteArrayType() implements NetworkBufferTypeImpl { @Override public void write(@NotNull NetworkBuffer buffer, byte[] value) { diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java index 1e1e64c33..595bfc27e 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientClickWindowPacket.java @@ -22,7 +22,7 @@ public record ClientClickWindowPacket(byte windowId, int stateId, public ClientClickWindowPacket(@NotNull NetworkBuffer reader) { this(reader.read(BYTE), reader.read(VAR_INT), reader.read(SHORT), reader.read(BYTE), reader.readEnum(ClickType.class), - reader.readCollection(ChangedSlot::new, MAX_CHANGED_SLOTS), reader.read(ITEM)); + reader.readCollection(ChangedSlot::new, MAX_CHANGED_SLOTS), reader.read(ItemStack.NETWORK_TYPE)); } @Override @@ -33,18 +33,18 @@ public record ClientClickWindowPacket(byte windowId, int stateId, writer.write(BYTE, button); writer.write(VAR_INT, clickType.ordinal()); writer.writeCollection(changedSlots); - writer.write(ITEM, clickedItem); + writer.write(ItemStack.NETWORK_TYPE, clickedItem); } public record ChangedSlot(short slot, @NotNull ItemStack item) implements NetworkBuffer.Writer { public ChangedSlot(@NotNull NetworkBuffer reader) { - this(reader.read(SHORT), reader.read(ITEM)); + this(reader.read(SHORT), reader.read(ItemStack.NETWORK_TYPE)); } @Override public void write(@NotNull NetworkBuffer writer) { writer.write(SHORT, slot); - writer.write(ITEM, item); + writer.write(ItemStack.NETWORK_TYPE, item); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java index f1c382d6f..794029111 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientCreativeInventoryActionPacket.java @@ -5,17 +5,16 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; import org.jetbrains.annotations.NotNull; -import static net.minestom.server.network.NetworkBuffer.ITEM; import static net.minestom.server.network.NetworkBuffer.SHORT; public record ClientCreativeInventoryActionPacket(short slot, @NotNull ItemStack item) implements ClientPacket { public ClientCreativeInventoryActionPacket(@NotNull NetworkBuffer reader) { - this(reader.read(SHORT), reader.read(ITEM)); + this(reader.read(SHORT), reader.read(ItemStack.NETWORK_TYPE)); } @Override public void write(@NotNull NetworkBuffer writer) { writer.write(SHORT, slot); - writer.write(ITEM, item); + writer.write(ItemStack.NETWORK_TYPE, item); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java index e8b4870ae..1461344f4 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java @@ -5,7 +5,6 @@ import net.minestom.server.advancements.FrameType; import net.minestom.server.adventure.ComponentHolder; import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket.ComponentHolding; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; @@ -164,7 +163,7 @@ public record AdvancementsPacket(boolean reset, @NotNull List recipes) implem private DeclaredShapelessCraftingRecipe(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(STRING), reader.readEnum(RecipeCategory.Crafting.class), - reader.readCollection(Ingredient::new, MAX_INGREDIENTS), reader.read(ITEM)); + reader.readCollection(Ingredient::new, MAX_INGREDIENTS), reader.read(ItemStack.NETWORK_TYPE)); } @Override @@ -78,7 +78,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem writer.write(STRING, group); writer.writeEnum(RecipeCategory.Crafting.class, crafting); writer.writeCollection(ingredients); - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); } @Override @@ -114,7 +114,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem for (int slot = 0; slot < width * height; slot++) { ingredients.add(new Ingredient(reader)); } - ItemStack result = reader.read(ITEM); + ItemStack result = reader.read(ItemStack.NETWORK_TYPE); boolean showNotification = reader.read(BOOLEAN); return new DeclaredShapedCraftingRecipe(recipeId, group, category, width, height, ingredients, result, showNotification); } @@ -128,7 +128,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem for (Ingredient ingredient : ingredients) { ingredient.write(writer); } - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); writer.write(BOOLEAN, showNotification); } @@ -145,7 +145,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem public DeclaredSmeltingRecipe(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(STRING), reader.readEnum(RecipeCategory.Cooking.class), - new Ingredient(reader), reader.read(ITEM), + new Ingredient(reader), reader.read(ItemStack.NETWORK_TYPE), reader.read(FLOAT), reader.read(VAR_INT)); } @@ -154,7 +154,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem writer.write(STRING, group); writer.writeEnum(RecipeCategory.Cooking.class, category); writer.write(ingredient); - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); writer.write(FLOAT, experience); writer.write(VAR_INT, cookingTime); } @@ -172,7 +172,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem public DeclaredBlastingRecipe(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(STRING), reader.readEnum(RecipeCategory.Cooking.class), - new Ingredient(reader), reader.read(ITEM), + new Ingredient(reader), reader.read(ItemStack.NETWORK_TYPE), reader.read(FLOAT), reader.read(VAR_INT)); } @@ -181,7 +181,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem writer.write(STRING, group); writer.writeEnum(RecipeCategory.Cooking.class, category); writer.write(ingredient); - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); writer.write(FLOAT, experience); writer.write(VAR_INT, cookingTime); } @@ -199,7 +199,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem public DeclaredSmokingRecipe(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(STRING), reader.readEnum(RecipeCategory.Cooking.class), - new Ingredient(reader), reader.read(ITEM), + new Ingredient(reader), reader.read(ItemStack.NETWORK_TYPE), reader.read(FLOAT), reader.read(VAR_INT)); } @@ -208,7 +208,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem writer.write(STRING, group); writer.writeEnum(RecipeCategory.Cooking.class, category); writer.write(ingredient); - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); writer.write(FLOAT, experience); writer.write(VAR_INT, cookingTime); } @@ -226,7 +226,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem public DeclaredCampfireCookingRecipe(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(STRING), reader.readEnum(RecipeCategory.Cooking.class), - new Ingredient(reader), reader.read(ITEM), + new Ingredient(reader), reader.read(ItemStack.NETWORK_TYPE), reader.read(FLOAT), reader.read(VAR_INT)); } @@ -235,7 +235,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem writer.write(STRING, group); writer.writeEnum(RecipeCategory.Cooking.class, category); writer.write(ingredient); - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); writer.write(FLOAT, experience); writer.write(VAR_INT, cookingTime); } @@ -250,14 +250,14 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem Ingredient ingredient, ItemStack result) implements DeclaredRecipe { public DeclaredStonecutterRecipe(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(STRING), - new Ingredient(reader), reader.read(ITEM)); + new Ingredient(reader), reader.read(ItemStack.NETWORK_TYPE)); } @Override public void write(@NotNull NetworkBuffer writer) { writer.write(STRING, group); writer.write(ingredient); - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); } @Override @@ -270,7 +270,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem Ingredient base, Ingredient addition, ItemStack result) implements DeclaredRecipe { public DeclaredSmithingTransformRecipe(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), new Ingredient(reader), new Ingredient(reader), new Ingredient(reader), reader.read(ITEM)); + this(reader.read(STRING), new Ingredient(reader), new Ingredient(reader), new Ingredient(reader), reader.read(ItemStack.NETWORK_TYPE)); } @Override @@ -278,7 +278,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem writer.write(template); writer.write(base); writer.write(addition); - writer.write(ITEM, result); + writer.write(ItemStack.NETWORK_TYPE, result); } @Override @@ -312,11 +312,11 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } public Ingredient(@NotNull NetworkBuffer reader) { - this(reader.readCollection(ITEM, MAX_INGREDIENTS)); + this(reader.readCollection(ItemStack.NETWORK_TYPE, MAX_INGREDIENTS)); } public void write(@NotNull NetworkBuffer writer) { - writer.writeCollection(ITEM, items); + writer.writeCollection(ItemStack.NETWORK_TYPE, items); } } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java index 9715976bf..a46d13d77 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java @@ -2,9 +2,9 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; import net.minestom.server.entity.EquipmentSlot; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket.ComponentHolding; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; @@ -16,7 +16,8 @@ import java.util.Objects; import java.util.function.UnaryOperator; import java.util.stream.Collectors; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.BYTE; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; public record EntityEquipmentPacket(int entityId, @NotNull Map equipments) implements ServerPacket.Play, ServerPacket.ComponentHolding { @@ -39,7 +40,7 @@ public record EntityEquipmentPacket(int entityId, byte slotEnum = (byte) entry.getKey().ordinal(); if (!last) slotEnum |= 0x80; writer.write(BYTE, slotEnum); - writer.write(ITEM, entry.getValue()); + writer.write(ItemStack.NETWORK_TYPE, entry.getValue()); } } @@ -52,7 +53,7 @@ public record EntityEquipmentPacket(int entityId, public @NotNull Collection components() { return this.equipments.values() .stream() - .map(ItemStack::getDisplayName) + .map(item -> item.get(ItemComponent.CUSTOM_NAME)) .filter(Objects::nonNull) .collect(Collectors.toList()); } @@ -60,7 +61,7 @@ public record EntityEquipmentPacket(int entityId, @Override public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { final var map = new EnumMap(EquipmentSlot.class); - this.equipments.forEach((key, value) -> map.put(key, value.withDisplayName(operator))); + this.equipments.forEach((key, value) -> map.put(key, value.with(ItemComponent.CUSTOM_NAME, operator))); return new EntityEquipmentPacket(this.entityId, map); } @@ -70,7 +71,7 @@ public record EntityEquipmentPacket(int entityId, byte slot; do { slot = reader.read(BYTE); - equipments.put(EquipmentSlot.values()[slot & 0x7F], reader.read(ITEM)); + equipments.put(EquipmentSlot.values()[slot & 0x7F], reader.read(ItemStack.NETWORK_TYPE)); } while ((slot & 0x80) == 0x80); return equipments; } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java index 70c15f6d8..798ded661 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java @@ -1,5 +1,6 @@ package net.minestom.server.network.packet.server.play; +import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -53,7 +54,7 @@ public record ExplosionPacket(double x, double y, double z, float radius, return writer.toByteArray(); } else if (particle.equals(Particle.ITEM)) { - writer.writeItemStack(reader.read(ITEM)); + writer.writeItemStack(reader.read(ItemStack.NETWORK_TYPE)); } else if (particle.equals(Particle.DUST_COLOR_TRANSITION)) { for (int i = 0; i < 7; i++) writer.writeFloat(reader.read(FLOAT)); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java index 1be1adafa..8ddf7bbc2 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java @@ -1,9 +1,9 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket.ComponentHolding; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; @@ -19,7 +19,7 @@ public record SetSlotPacket(byte windowId, int stateId, short slot, @NotNull ItemStack itemStack) implements ServerPacket.Play, ServerPacket.ComponentHolding { public SetSlotPacket(@NotNull NetworkBuffer reader) { this(reader.read(BYTE), reader.read(VAR_INT), reader.read(SHORT), - reader.read(ITEM)); + reader.read(ItemStack.NETWORK_TYPE)); } @Override @@ -27,7 +27,7 @@ public record SetSlotPacket(byte windowId, int stateId, short slot, writer.write(BYTE, windowId); writer.write(VAR_INT, stateId); writer.write(SHORT, slot); - writer.write(ITEM, itemStack); + writer.write(ItemStack.NETWORK_TYPE, itemStack); } @Override @@ -37,20 +37,21 @@ public record SetSlotPacket(byte windowId, int stateId, short slot, @Override public @NotNull Collection components() { - final var components = new ArrayList<>(this.itemStack.getLore()); - final var displayname = this.itemStack.getDisplayName(); + final var components = new ArrayList<>(this.itemStack.get(ItemComponent.LORE, List.of())); + final var displayname = this.itemStack.get(ItemComponent.CUSTOM_NAME); if (displayname != null) components.add(displayname); - return List.copyOf(components); } @Override public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { - return new SetSlotPacket(this.windowId, this.stateId, this.slot, this.itemStack.withDisplayName(operator).withLore(lines -> { - final var translatedComponents = new ArrayList(); - lines.forEach(component -> translatedComponents.add(operator.apply(component))); - return translatedComponents; - })); + return new SetSlotPacket(this.windowId, this.stateId, this.slot, this.itemStack + .with(ItemComponent.CUSTOM_NAME, operator) + .with(ItemComponent.LORE, (UnaryOperator>) lines -> { + final var translatedComponents = new ArrayList(); + lines.forEach(component -> translatedComponents.add(operator.apply(component))); + return translatedComponents; + })); } /** diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java index baffa039a..c21acc41b 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TradeListPacket.java @@ -45,17 +45,17 @@ public record TradeListPacket(int windowId, @NotNull List trades, int tradeUsesNumber, int maxTradeUsesNumber, int exp, int specialPrice, float priceMultiplier, int demand) implements NetworkBuffer.Writer { public Trade(@NotNull NetworkBuffer reader) { - this(reader.read(ITEM), reader.read(ITEM), - reader.readOptional(ITEM), reader.read(BOOLEAN), + this(reader.read(ItemStack.NETWORK_TYPE), reader.read(ItemStack.NETWORK_TYPE), + reader.readOptional(ItemStack.NETWORK_TYPE), reader.read(BOOLEAN), reader.read(INT), reader.read(INT), reader.read(INT), reader.read(INT), reader.read(FLOAT), reader.read(INT)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(ITEM, inputItem1); - writer.write(ITEM, result); - writer.writeOptional(ITEM, inputItem2); + writer.write(ItemStack.NETWORK_TYPE, inputItem1); + writer.write(ItemStack.NETWORK_TYPE, result); + writer.writeOptional(ItemStack.NETWORK_TYPE, inputItem2); writer.write(BOOLEAN, tradeDisabled); writer.write(INT, tradeUsesNumber); writer.write(INT, maxTradeUsesNumber); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java index 21073cbfd..a015c42bd 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java @@ -1,9 +1,9 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket.ComponentHolding; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; @@ -13,7 +13,8 @@ import java.util.Collection; import java.util.List; import java.util.function.UnaryOperator; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.BYTE; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; public record WindowItemsPacket(byte windowId, int stateId, @NotNull List items, @NotNull ItemStack carriedItem) implements ServerPacket.Play, ServerPacket.ComponentHolding { @@ -24,16 +25,16 @@ public record WindowItemsPacket(byte windowId, int stateId, @NotNull List(); list.forEach(itemStack -> { - components.addAll(itemStack.getLore()); + components.addAll(itemStack.get(ItemComponent.LORE, List.of())); - final var displayName = itemStack.getDisplayName(); + final var displayName = itemStack.get(ItemComponent.CUSTOM_NAME); if (displayName == null) return; components.add(displayName); @@ -62,19 +63,21 @@ public record WindowItemsPacket(byte windowId, int stateId, @NotNull List operator) { + UnaryOperator> loreOperator = lines -> { + final var translatedComponents = new ArrayList(); + lines.forEach(component -> translatedComponents.add(operator.apply(component))); + return translatedComponents; + }; return new WindowItemsPacket( this.windowId, this.stateId, - this.items.stream().map(stack -> stack.withDisplayName(operator).withLore(lines -> { - final var translatedComponents = new ArrayList(); - lines.forEach(component -> translatedComponents.add(operator.apply(component))); - return translatedComponents; - })).toList(), - this.carriedItem.withDisplayName(operator).withLore(lines -> { - final var translatedComponents = new ArrayList(); - lines.forEach(component -> translatedComponents.add(operator.apply(component))); - return translatedComponents; - }) + this.items.stream().map(stack -> stack + .with(ItemComponent.CUSTOM_NAME, operator) + .with(ItemComponent.LORE, loreOperator)) + .toList(), + this.carriedItem + .with(ItemComponent.CUSTOM_NAME, operator) + .with(ItemComponent.LORE, loreOperator) ); } } diff --git a/src/main/java/net/minestom/server/particle/data/ItemParticleData.java b/src/main/java/net/minestom/server/particle/data/ItemParticleData.java index 58ddfb0d3..7f11bb2a3 100644 --- a/src/main/java/net/minestom/server/particle/data/ItemParticleData.java +++ b/src/main/java/net/minestom/server/particle/data/ItemParticleData.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull; public record ItemParticleData(ItemStack item) implements ParticleData { ItemParticleData(NetworkBuffer reader) { - this(reader.read(NetworkBuffer.ITEM)); + this(reader.read(ItemStack.NETWORK_TYPE)); } ItemParticleData() { @@ -17,7 +17,7 @@ public record ItemParticleData(ItemStack item) implements ParticleData { @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(NetworkBuffer.ITEM, item); + writer.write(ItemStack.NETWORK_TYPE, item); } @Override diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 2cf0dcfeb..742c9bbf3 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -9,6 +9,7 @@ import net.minestom.server.collision.BoundingBox; import net.minestom.server.collision.CollisionUtils; import net.minestom.server.collision.Shape; import net.minestom.server.entity.EntitySpawnType; +import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.instance.block.Block; import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemComponentMap; @@ -477,8 +478,8 @@ public final class Registry { private final Supplier blockSupplier; private final ItemComponentMap prototype; -// private final EquipmentSlot equipmentSlot; //todo -// private final EntityType entityType; //todo + private final EquipmentSlot equipmentSlot; + // private final EntityType entityType; //todo private final Properties custom; private MaterialEntry(String namespace, Properties main, Properties custom) { @@ -493,32 +494,38 @@ public final class Registry { try { ItemComponentMap.Builder builder = ItemComponentMap.builder(); for (Map.Entry entry : main.section("components")) { - //noinspection unchecked - ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); - Check.notNull(component, "Unknown component: " + entry.getKey()); + try { + //noinspection unchecked + ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); + Check.notNull(component, "Unknown component: " + entry.getKey()); - byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue()); - BinaryTagReader reader = new BinaryTagReader(new DataInputStream(new ByteArrayInputStream(rawValue))); - builder.set(component, component.read(reader.readNameless())); + byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue()); + BinaryTagReader reader = new BinaryTagReader(new DataInputStream(new ByteArrayInputStream(rawValue))); + + //todo remove this try/catch, just so i dont need to impl all comps yet + builder.set(component, component.read(reader.readNameless())); + } catch (NullPointerException e) { + System.out.println(e.getMessage()); + } } this.prototype = builder.build(); } catch (IOException e) { throw new RuntimeException("failed to parse material registry: " + namespace, e); } -// { -// final Properties armorProperties = main.section("armorProperties"); -// if (armorProperties != null) { -// switch (armorProperties.getString("slot")) { -// case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS; -// case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS; -// case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE; -// case "head" -> this.equipmentSlot = EquipmentSlot.HELMET; -// default -> this.equipmentSlot = null; -// } -// } else { -// this.equipmentSlot = null; -// } -// } + { + final Properties armorProperties = main.section("armorProperties"); + if (armorProperties != null) { + switch (armorProperties.getString("slot")) { + case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS; + case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS; + case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE; + case "head" -> this.equipmentSlot = EquipmentSlot.HELMET; + default -> this.equipmentSlot = null; + } + } else { + this.equipmentSlot = null; + } + } // { // final Properties spawnEggProperties = main.section("spawnEggProperties"); // if (spawnEggProperties != null) { @@ -549,14 +556,14 @@ public final class Registry { return prototype; } - // public boolean isArmor() { -// return equipmentSlot != null; -// } -// -// public @Nullable EquipmentSlot equipmentSlot() { -// return equipmentSlot; -// } -// + public boolean isArmor() { + return equipmentSlot != null; + } + + public @Nullable EquipmentSlot equipmentSlot() { + return equipmentSlot; + } + // /** // * Gets the entity type this item can spawn. Only present for spawn eggs (e.g. wolf spawn egg, skeleton spawn egg) // * @return The entity type it can spawn, or null if it is not a spawn egg @@ -612,11 +619,12 @@ public final class Registry { custom); } } + public record TrimMaterialEntry(@NotNull NamespaceID namespace, @NotNull String assetName, @NotNull Material ingredient, float itemModelIndex, - @NotNull Map overrideArmorMaterials, + @NotNull Map overrideArmorMaterials, @NotNull Component description, Properties custom) implements Entry { public TrimMaterialEntry(@NotNull String namespace, @NotNull Properties main, Properties custom) { @@ -625,7 +633,7 @@ public final class Registry { main.getString("asset_name"), Objects.requireNonNull(Material.fromNamespaceId(main.getString("ingredient"))), (float) main.getDouble("item_model_index"), - Objects.requireNonNullElse(main.section("override_armor_materials"),new PropertiesMap(Map.of())) + Objects.requireNonNullElse(main.section("override_armor_materials"), new PropertiesMap(Map.of())) .asMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> (String) entry.getValue())), JSONComponentSerializer.json().deserialize(main.section("description").toString()), custom @@ -803,7 +811,7 @@ public final class Registry { public String toString() { AtomicReference string = new AtomicReference<>("{ "); this.map.forEach((s, object) -> string.set(string.get() + " , " + "\"" + s + "\"" + " : " + "\"" + object.toString() + "\"")); - return string.updateAndGet(s -> s.replaceFirst(" , ","") + "}"); + return string.updateAndGet(s -> s.replaceFirst(" , ", "") + "}"); } } diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java index 5fbcb3b6d..dc85307a1 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java @@ -187,7 +187,7 @@ public class BinaryReader extends InputStream { } public ItemStack readItemStack() { - return buffer.read(ITEM); + return buffer.read(ItemStack.NETWORK_TYPE); } public Component readComponent(int maxLength) { diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java index 7a0eb0116..037c12fd4 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java @@ -164,7 +164,7 @@ public class BinaryWriter extends OutputStream { } public void writeItemStack(@NotNull ItemStack itemStack) { - this.buffer.write(ITEM, itemStack); + this.buffer.write(ItemStack.NETWORK_TYPE, itemStack); } public void writeNBT(@NotNull String name, @NotNull BinaryTag tag) { diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java b/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java index 42fdd44dc..9025f5dc5 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeParticle.java @@ -58,9 +58,10 @@ public record BiomeParticle(float probability, Option option) { @Override public CompoundBinaryTag toNbt() { - //todo test count might be wrong type - return item.meta().toNBT() - .putString("type", type); + //todo +// return item.meta().toNBT() +// .putString("type", type); + return CompoundBinaryTag.empty(); } } From f1f1246230cd2547cf3e9d3a50664207f2be7321 Mon Sep 17 00:00:00 2001 From: mworzala Date: Sat, 13 Apr 2024 00:15:06 -0400 Subject: [PATCH 23/46] chore: trying to fix nightmare loop --- .../src/main/java/net/minestom/demo/Main.java | 9 +- .../minestom/server/item/ItemComponent.java | 140 ++++++++---------- .../server/item/ItemComponentMap.java | 10 +- .../server/item/ItemComponentMapImpl.java | 12 +- .../server/item/ItemComponentPatch.java | 24 +-- .../server/item/ItemComponentType.java | 30 ++++ ...ntImpl.java => ItemComponentTypeImpl.java} | 12 +- .../net/minestom/server/item/ItemStack.java | 10 +- .../minestom/server/item/ItemStackImpl.java | 12 +- .../minestom/server/registry/Registry.java | 10 +- 10 files changed, 141 insertions(+), 128 deletions(-) create mode 100644 src/main/java/net/minestom/server/item/ItemComponentType.java rename src/main/java/net/minestom/server/item/{ItemComponentImpl.java => ItemComponentTypeImpl.java} (75%) diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index c2af8d309..3d54eabb8 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -15,6 +15,7 @@ import net.minestom.server.event.server.ServerListPingEvent; import net.minestom.server.extras.lan.OpenToLAN; import net.minestom.server.extras.lan.OpenToLANConfig; import net.minestom.server.instance.block.BlockManager; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.network.packet.server.play.DeclareRecipesPacket; @@ -32,13 +33,9 @@ public class Main { public static void main(String[] args) { try { - - Class.forName("net.minestom.server.item.ItemComponent"); - Class.forName("net.minestom.server.item.ItemStack"); + Class.forName(ItemComponent.class.getName()); } catch (Exception e) { - e.printStackTrace(); - System.err.println("Failed to load Minestom classes, make sure you are using the correct dependencies"); - return; + throw new RuntimeException(e); } System.setProperty("minestom.experiment.pose-updates", "true"); diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index efc579be6..ba4881ac3 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -1,96 +1,74 @@ package net.minestom.server.item; -import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.color.Color; import net.minestom.server.item.component.*; import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.registry.StaticProtocolObject; -import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.nbt.BinaryTagSerializer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.List; -import static net.minestom.server.item.ItemComponentImpl.declare; +import static net.minestom.server.item.ItemComponentTypeImpl.declare; -public sealed interface ItemComponent extends StaticProtocolObject permits ItemComponentImpl { +public final class ItemComponent { // Note that even non-networked components are declared here as they still contribute to the component ID counter. - ItemComponent CUSTOM_DATA = declare("custom_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - ItemComponent MAX_STACK_SIZE = declare("max_stack_size", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent MAX_DAMAGE = declare("max_damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent DAMAGE = declare("damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent UNBREAKABLE = declare("unbreakable", Unbreakable.NETWORK_TYPE, Unbreakable.NBT_TYPE); - ItemComponent CUSTOM_NAME = declare("custom_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); - ItemComponent ITEM_NAME = declare("item_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); - ItemComponent> LORE = declare("lore", NetworkBuffer.COMPONENT.list(256), BinaryTagSerializer.JSON_COMPONENT.list()); - ItemComponent RARITY = declare("rarity", ItemRarity.NETWORK_TYPE, ItemRarity.NBT_TYPE); - ItemComponent ENCHANTMENTS = declare("enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); - ItemComponent CAN_PLACE_ON = declare("can_place_on", null, null); //todo - ItemComponent CAN_BREAK = declare("can_break", null, null); //todo - ItemComponent ATTRIBUTE_MODIFIERS = declare("attribute_modifiers", AttributeList.NETWORK_TYPE, AttributeList.NBT_TYPE); - ItemComponent CUSTOM_MODEL_DATA = declare("custom_model_data", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); - ItemComponent HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); - ItemComponent REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING, null); - ItemComponent ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN); - ItemComponent INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); - ItemComponent FOOD = declare("food", null, null); //todo - ItemComponent FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); - ItemComponent TOOL = declare("tool", null, null); //todo - ItemComponent STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); - ItemComponent DYED_COLOR = declare("dyed_color", DyedItemColor.NETWORK_TYPE, DyedItemColor.NBT_TYPE); - ItemComponent MAP_COLOR = declare("map_color", NetworkBuffer.COLOR, BinaryTagSerializer.INT.map(Color::new, Color::asRGB)); - ItemComponent MAP_ID = declare("map_id", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent MAP_DECORATIONS = declare("map_decorations", null, MapDecorations.NBT_TYPE); - ItemComponent MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null); - ItemComponent> CHARGED_PROJECTILES = declare("charged_projectiles", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); - ItemComponent> BUNDLE_CONTENTS = declare("bundle_contents", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); - ItemComponent POTION_CONTENTS = declare("potion_contents", null, null); //todo - ItemComponent SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo - ItemComponent WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE); - ItemComponent WRITTEN_BOOK_CONTENT = declare("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE); - ItemComponent TRIM = declare("trim", null, null); //todo - ItemComponent DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo - ItemComponent ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - ItemComponent BUCKET_ENTITY_DATA = declare("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - ItemComponent BLOCK_ENTITY_DATA = declare("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - ItemComponent INSTRUMENT = declare("instrument", null, null); //todo - ItemComponent OMINOUS_BOTTLE_AMPLIFIER = declare("ominous_bottle_amplifier", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent> RECIPES = declare("recipes", NetworkBuffer.STRING.list(Short.MAX_VALUE), BinaryTagSerializer.STRING.list()); - ItemComponent LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE); - ItemComponent FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE); - ItemComponent FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE); - ItemComponent PROFILE = declare("profile", null, null); //todo - ItemComponent NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING); - ItemComponent BANNER_PATTERNS = declare("banner_patterns", null, null); //todo - ItemComponent BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity - ItemComponent POT_DECORATIONS = declare("pot_decorations", null, null); //todo - ItemComponent> CONTAINER = declare("container", ItemStack.NETWORK_TYPE.list(256), BinaryTagSerializer.ITEM.list()); - ItemComponent BLOCK_STATE = declare("block_state", null, null); //todo - ItemComponent BEES = declare("bees", null, null); //todo - ItemComponent LOCK = declare("lock", null, BinaryTagSerializer.STRING); - ItemComponent CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE); + public static final ItemComponentType CUSTOM_DATA = declare("custom_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + public static final ItemComponentType MAX_STACK_SIZE = declare("max_stack_size", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + public static final ItemComponentType MAX_DAMAGE = declare("max_damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + public static final ItemComponentType DAMAGE = declare("damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + public static final ItemComponentType UNBREAKABLE = declare("unbreakable", Unbreakable.NETWORK_TYPE, Unbreakable.NBT_TYPE); + public static final ItemComponentType CUSTOM_NAME = declare("custom_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); + public static final ItemComponentType ITEM_NAME = declare("item_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); + public static final ItemComponentType> LORE = declare("lore", NetworkBuffer.COMPONENT.list(256), BinaryTagSerializer.JSON_COMPONENT.list()); + public static final ItemComponentType RARITY = declare("rarity", ItemRarity.NETWORK_TYPE, ItemRarity.NBT_TYPE); + public static final ItemComponentType ENCHANTMENTS = declare("enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); + public static final ItemComponentType CAN_PLACE_ON = declare("can_place_on", null, null); //todo + public static final ItemComponentType CAN_BREAK = declare("can_break", null, null); //todo + public static final ItemComponentType ATTRIBUTE_MODIFIERS = declare("attribute_modifiers", AttributeList.NETWORK_TYPE, AttributeList.NBT_TYPE); + public static final ItemComponentType CUSTOM_MODEL_DATA = declare("custom_model_data", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + public static final ItemComponentType HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + public static final ItemComponentType HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + public static final ItemComponentType REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + public static final ItemComponentType CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING, null); + public static final ItemComponentType ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN); + public static final ItemComponentType INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); + public static final ItemComponentType FOOD = declare("food", null, null); //todo + public static final ItemComponentType FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + public static final ItemComponentType TOOL = declare("tool", null, null); //todo + public static final ItemComponentType STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); + public static final ItemComponentType DYED_COLOR = declare("dyed_color", DyedItemColor.NETWORK_TYPE, DyedItemColor.NBT_TYPE); + public static final ItemComponentType MAP_COLOR = declare("map_color", NetworkBuffer.COLOR, BinaryTagSerializer.INT.map(Color::new, Color::asRGB)); + public static final ItemComponentType MAP_ID = declare("map_id", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + public static final ItemComponentType MAP_DECORATIONS = declare("map_decorations", null, MapDecorations.NBT_TYPE); + public static final ItemComponentType MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null); + public static final ItemComponentType> CHARGED_PROJECTILES = declare("charged_projectiles", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + public static final ItemComponentType> BUNDLE_CONTENTS = declare("bundle_contents", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + public static final ItemComponentType POTION_CONTENTS = declare("potion_contents", null, null); //todo + public static final ItemComponentType SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo + public static final ItemComponentType WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE); + public static final ItemComponentType WRITTEN_BOOK_CONTENT = declare("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE); + public static final ItemComponentType TRIM = declare("trim", null, null); //todo + public static final ItemComponentType DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo + public static final ItemComponentType ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + public static final ItemComponentType BUCKET_ENTITY_DATA = declare("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + public static final ItemComponentType BLOCK_ENTITY_DATA = declare("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + public static final ItemComponentType INSTRUMENT = declare("instrument", null, null); //todo + public static final ItemComponentType OMINOUS_BOTTLE_AMPLIFIER = declare("ominous_bottle_amplifier", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + public static final ItemComponentType> RECIPES = declare("recipes", NetworkBuffer.STRING.list(Short.MAX_VALUE), BinaryTagSerializer.STRING.list()); + public static final ItemComponentType LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE); + public static final ItemComponentType FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE); + public static final ItemComponentType FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE); + public static final ItemComponentType PROFILE = declare("profile", null, null); //todo + public static final ItemComponentType NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING); + public static final ItemComponentType BANNER_PATTERNS = declare("banner_patterns", null, null); //todo + public static final ItemComponentType BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity + public static final ItemComponentType POT_DECORATIONS = declare("pot_decorations", null, null); //todo + public static final ItemComponentType> CONTAINER = declare("container", ItemStack.NETWORK_TYPE.list(256), BinaryTagSerializer.ITEM.list()); + public static final ItemComponentType BLOCK_STATE = declare("block_state", null, null); //todo + public static final ItemComponentType BEES = declare("bees", null, null); //todo + public static final ItemComponentType LOCK = declare("lock", null, BinaryTagSerializer.STRING); + public static final ItemComponentType CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE); - @NotNull T read(@NotNull BinaryTag tag); - @NotNull BinaryTag write(@NotNull T value); - - @NotNull T read(@NotNull NetworkBuffer reader); - void write(@NotNull NetworkBuffer writer, @NotNull T value); - - - static @Nullable ItemComponent fromNamespaceId(@NotNull String namespaceId) { - return ItemComponentImpl.NAMESPACES.get(namespaceId); - } - - static @Nullable ItemComponent fromNamespaceId(@NotNull NamespaceID namespaceId) { - return fromNamespaceId(namespaceId.asString()); - } - - static @Nullable ItemComponent fromId(int id) { - return ItemComponentImpl.IDS.get(id); - } + private ItemComponent() {} } diff --git a/src/main/java/net/minestom/server/item/ItemComponentMap.java b/src/main/java/net/minestom/server/item/ItemComponentMap.java index 0944c3382..7488e8497 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentMap.java +++ b/src/main/java/net/minestom/server/item/ItemComponentMap.java @@ -10,20 +10,20 @@ public interface ItemComponentMap { return new ItemComponentMapImpl.BuilderImpl(new Int2ObjectArrayMap<>()); } - boolean has(@NotNull ItemComponent component); + boolean has(@NotNull ItemComponentType component); - @Nullable T get(@NotNull ItemComponent component); + @Nullable T get(@NotNull ItemComponentType component); - default @NotNull T get(@NotNull ItemComponent component, @NotNull T defaultValue) { + default @NotNull T get(@NotNull ItemComponentType component, @NotNull T defaultValue) { T value = get(component); return value != null ? value : defaultValue; } interface Builder extends ItemComponentMap { - @NotNull Builder set(@NotNull ItemComponent component, @Nullable Object value); + @NotNull Builder set(@NotNull ItemComponentType component, @Nullable Object value); - @NotNull Builder remove(@NotNull ItemComponent component); + @NotNull Builder remove(@NotNull ItemComponentType component); @NotNull ItemComponentMap build(); } diff --git a/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java b/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java index 42365e07a..7211f118b 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java +++ b/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java @@ -6,35 +6,35 @@ import org.jetbrains.annotations.Nullable; public record ItemComponentMapImpl(@NotNull Int2ObjectMap components) implements ItemComponentMap { @Override - public boolean has(@NotNull ItemComponent component) { + public boolean has(@NotNull ItemComponentType component) { return components.get(component.id()) != null; } @Override - public @Nullable T get(@NotNull ItemComponent component) { + public @Nullable T get(@NotNull ItemComponentType component) { return (T) components.get(component.id()); } public record BuilderImpl(@NotNull Int2ObjectMap components) implements ItemComponentMap.Builder { @Override - public boolean has(@NotNull ItemComponent component) { + public boolean has(@NotNull ItemComponentType component) { return components.get(component.id()) != null; } @Override - public @Nullable T get(@NotNull ItemComponent component) { + public @Nullable T get(@NotNull ItemComponentType component) { return (T) components.get(component.id()); } @Override - public @NotNull Builder set(@NotNull ItemComponent component, @Nullable Object value) { + public @NotNull Builder set(@NotNull ItemComponentType component, @Nullable Object value) { components.put(component.id(), value); return this; } @Override - public @NotNull Builder remove(@NotNull ItemComponent component) { + public @NotNull Builder remove(@NotNull ItemComponentType component) { components.remove(component.id()); return this; } diff --git a/src/main/java/net/minestom/server/item/ItemComponentPatch.java b/src/main/java/net/minestom/server/item/ItemComponentPatch.java index 0a07a0fca..5504d5ae3 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentPatch.java +++ b/src/main/java/net/minestom/server/item/ItemComponentPatch.java @@ -38,7 +38,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { if (entry.getValue() != null) { buffer.write(NetworkBuffer.VAR_INT, entry.getIntKey()); //noinspection unchecked - ItemComponent type = (ItemComponent) ItemComponent.fromId(entry.getIntKey()); + ItemComponentType type = (ItemComponentType) ItemComponentType.fromId(entry.getIntKey()); assert type != null; type.write(entry.getValue()); } @@ -58,7 +58,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { for (int i = 0; i < added; i++) { int id = buffer.read(NetworkBuffer.VAR_INT); //noinspection unchecked - ItemComponent type = (ItemComponent) ItemComponent.fromId(id); + ItemComponentType type = (ItemComponentType) ItemComponentType.fromId(id); Check.notNull(type, "Unknown item component id: {0}", id); patch.put(id, type.read(buffer)); } @@ -80,7 +80,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { key = key.substring(1); remove = true; } - ItemComponent type = ItemComponent.fromNamespaceId(key); + ItemComponentType type = ItemComponentType.fromNamespaceId(key); Check.notNull(type, "Unknown item component: {0}", key); if (remove) { patch.put(type.id(), null); @@ -96,7 +96,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); for (Int2ObjectMap.Entry entry : patch.patch.int2ObjectEntrySet()) { //noinspection unchecked - ItemComponent type = (ItemComponent) ItemComponent.fromId(entry.getIntKey()); + ItemComponentType type = (ItemComponentType) ItemComponentType.fromId(entry.getIntKey()); Check.notNull(type, "Unknown item component id: {0}", entry.getIntKey()); if (entry.getValue() == null) { builder.put(REMOVAL_PREFIX + type.name(), CompoundBinaryTag.empty()); @@ -108,7 +108,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { } ); - public boolean has(@NotNull ItemComponentMap prototype, @NotNull ItemComponent component) { + public boolean has(@NotNull ItemComponentMap prototype, @NotNull ItemComponentType component) { if (patch.containsKey(component.id())) { return patch.get(component.id()) != null; } else { @@ -116,7 +116,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { } } - public @Nullable T get(@NotNull ItemComponentMap prototype, @NotNull ItemComponent component) { + public @Nullable T get(@NotNull ItemComponentMap prototype, @NotNull ItemComponentType component) { if (patch.containsKey(component.id())) { return (T) patch.get(component.id()); } else { @@ -124,13 +124,13 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { } } - public @NotNull ItemComponentPatch with(@NotNull ItemComponent component, @NotNull T value) { + public @NotNull ItemComponentPatch with(@NotNull ItemComponentType component, @NotNull T value) { Int2ObjectMap newPatch = new Int2ObjectArrayMap<>(patch); newPatch.put(component.id(), value); return new ItemComponentPatch(newPatch); } - public @NotNull ItemComponentPatch without(@NotNull ItemComponent component) { + public @NotNull ItemComponentPatch without(@NotNull ItemComponentType component) { Int2ObjectMap newPatch = new Int2ObjectArrayMap<>(patch); newPatch.put(component.id(), null); return new ItemComponentPatch(newPatch); @@ -143,21 +143,21 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { record Builder(@NotNull Int2ObjectMap patch) implements ItemComponentMap { @Override - public boolean has(@NotNull ItemComponent component) { + public boolean has(@NotNull ItemComponentType component) { return patch.get(component.id()) != null; } @Override - public @Nullable T get(@NotNull ItemComponent component) { + public @Nullable T get(@NotNull ItemComponentType component) { return (T) patch.get(component.id()); } - public ItemComponentPatch.@NotNull Builder set(@NotNull ItemComponent component, @NotNull T value) { + public ItemComponentPatch.@NotNull Builder set(@NotNull ItemComponentType component, @NotNull T value) { patch.put(component.id(), value); return this; } - public ItemComponentPatch.@NotNull Builder remove(@NotNull ItemComponent component) { + public ItemComponentPatch.@NotNull Builder remove(@NotNull ItemComponentType component) { patch.put(component.id(), null); return this; } diff --git a/src/main/java/net/minestom/server/item/ItemComponentType.java b/src/main/java/net/minestom/server/item/ItemComponentType.java new file mode 100644 index 000000000..46e8a952e --- /dev/null +++ b/src/main/java/net/minestom/server/item/ItemComponentType.java @@ -0,0 +1,30 @@ +package net.minestom.server.item; + +import net.kyori.adventure.nbt.BinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public sealed interface ItemComponentType extends StaticProtocolObject permits ItemComponentTypeImpl { + + @NotNull T read(@NotNull BinaryTag tag); + @NotNull BinaryTag write(@NotNull T value); + + @NotNull T read(@NotNull NetworkBuffer reader); + void write(@NotNull NetworkBuffer writer, @NotNull T value); + + + static @Nullable ItemComponentType fromNamespaceId(@NotNull String namespaceId) { + return ItemComponentTypeImpl.NAMESPACES.get(namespaceId); + } + + static @Nullable ItemComponentType fromNamespaceId(@NotNull NamespaceID namespaceId) { + return fromNamespaceId(namespaceId.asString()); + } + + static @Nullable ItemComponentType fromId(int id) { + return ItemComponentTypeImpl.IDS.get(id); + } +} diff --git a/src/main/java/net/minestom/server/item/ItemComponentImpl.java b/src/main/java/net/minestom/server/item/ItemComponentTypeImpl.java similarity index 75% rename from src/main/java/net/minestom/server/item/ItemComponentImpl.java rename to src/main/java/net/minestom/server/item/ItemComponentTypeImpl.java index 4850b4692..00c8852af 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentImpl.java +++ b/src/main/java/net/minestom/server/item/ItemComponentTypeImpl.java @@ -12,17 +12,17 @@ import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.Map; -record ItemComponentImpl( +record ItemComponentTypeImpl( int id, @NotNull NamespaceID namespace, @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt -) implements ItemComponent { - static final Map> NAMESPACES = new HashMap<>(32); - static final ObjectArray> IDS = ObjectArray.singleThread(32); +) implements ItemComponentType { + static final Map> NAMESPACES = new HashMap<>(32); + static final ObjectArray> IDS = ObjectArray.singleThread(32); - static ItemComponent declare(@NotNull String name, @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt) { - ItemComponent impl = new ItemComponentImpl<>(NAMESPACES.size(), NamespaceID.from(name), network, nbt); + static ItemComponentType declare(@NotNull String name, @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt) { + ItemComponentType impl = new ItemComponentTypeImpl<>(NAMESPACES.size(), NamespaceID.from(name), network, nbt); NAMESPACES.put(impl.name(), impl); IDS.set(impl.id(), impl); return impl; diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index bbedc7d2f..824b0eadb 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -79,16 +79,16 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv } @Contract(value = "_, _ -> new", pure = true) - @NotNull ItemStack with(@NotNull ItemComponent component, T value); + @NotNull ItemStack with(@NotNull ItemComponentType component, T value); - default @NotNull ItemStack with(@NotNull ItemComponent component, @NotNull UnaryOperator operator) { + default @NotNull ItemStack with(@NotNull ItemComponentType component, @NotNull UnaryOperator operator) { T value = get(component); if (value == null) return this; return with(component, operator.apply(value)); } @Contract(value = "_, -> new", pure = true) - @NotNull ItemStack without(@NotNull ItemComponent component); + @NotNull ItemStack without(@NotNull ItemComponentType component); @Contract(value = "_, _ -> new", pure = true) default @NotNull ItemStack withTag(@NotNull Tag tag, @Nullable T value) { @@ -144,10 +144,10 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @NotNull Builder amount(int amount); @Contract(value = "_, _ -> this") - @NotNull Builder set(@NotNull ItemComponent component, T value); + @NotNull Builder set(@NotNull ItemComponentType component, T value); @Contract(value = "_ -> this") - @NotNull Builder remove(@NotNull ItemComponent component); + @NotNull Builder remove(@NotNull ItemComponentType component); @Contract(value = "_, _ -> this") default @NotNull Builder set(@NotNull Tag tag, @Nullable T value) { diff --git a/src/main/java/net/minestom/server/item/ItemStackImpl.java b/src/main/java/net/minestom/server/item/ItemStackImpl.java index a0361b94b..ed58bc00f 100644 --- a/src/main/java/net/minestom/server/item/ItemStackImpl.java +++ b/src/main/java/net/minestom/server/item/ItemStackImpl.java @@ -47,12 +47,12 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component } @Override - public @Nullable T get(@NotNull ItemComponent component) { + public @Nullable T get(@NotNull ItemComponentType component) { return components.get(material.prototype(), component); } @Override - public boolean has(@NotNull ItemComponent component) { + public boolean has(@NotNull ItemComponentType component) { return components.has(material.prototype(), component); } @@ -75,12 +75,12 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component } @Override - public @NotNull ItemStack with(@NotNull ItemComponent component, T value) { + public @NotNull ItemStack with(@NotNull ItemComponentType component, T value) { return new ItemStackImpl(material, amount, components.with(component, value)); } @Override - public @NotNull ItemStack without(@NotNull ItemComponent component) { + public @NotNull ItemStack without(@NotNull ItemComponentType component) { return new ItemStackImpl(material, amount, components.without(component)); } @@ -145,13 +145,13 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component } @Override - public ItemStack.@NotNull Builder set(@NotNull ItemComponent component, T value) { + public ItemStack.@NotNull Builder set(@NotNull ItemComponentType component, T value) { components.set(component, value); return this; } @Override - public ItemStack.@NotNull Builder remove(@NotNull ItemComponent component) { + public ItemStack.@NotNull Builder remove(@NotNull ItemComponentType component) { components.remove(component); return this; } diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 742c9bbf3..d2a2bc90c 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -13,6 +13,7 @@ import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.instance.block.Block; import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemComponentMap; +import net.minestom.server.item.ItemComponentType; import net.minestom.server.item.Material; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.collection.ObjectArray; @@ -492,11 +493,18 @@ public final class Registry { this.blockSupplier = blockNamespace != null ? () -> Block.fromNamespaceId(blockNamespace) : () -> null; } try { + + try { + Class.forName(ItemComponent.class.getName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + ItemComponentMap.Builder builder = ItemComponentMap.builder(); for (Map.Entry entry : main.section("components")) { try { //noinspection unchecked - ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); + ItemComponentType component = (ItemComponentType) ItemComponentType.fromNamespaceId(entry.getKey()); Check.notNull(component, "Unknown component: " + entry.getKey()); byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue()); From c873d72f6400fe85dacee0d4a96b6ea05789afbb Mon Sep 17 00:00:00 2001 From: mworzala Date: Sat, 13 Apr 2024 10:03:36 -0400 Subject: [PATCH 24/46] feat: functional components, but at what cost --- .../java/net/minestom/demo/PlayerInit.java | 9 +++ .../nbt/NbtComponentSerializerImpl.java | 18 ++++-- .../net/minestom/server/entity/Player.java | 1 + .../minestom/server/item/ItemComponent.java | 3 +- .../server/item/ItemComponentPatch.java | 2 +- .../minestom/server/item/ItemStackImpl.java | 10 +++- .../server/network/NetworkBuffer.java | 4 +- .../server/network/NetworkBufferTypeImpl.java | 6 +- .../minestom/server/registry/Registry.java | 57 +++++++++---------- .../java/net/minestom/server/utils/Unit.java | 7 +++ 10 files changed, 73 insertions(+), 44 deletions(-) create mode 100644 src/main/java/net/minestom/server/utils/Unit.java diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 5ecced05e..73827f0ff 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -34,6 +34,7 @@ import net.minestom.server.item.Material; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.TickMonitor; import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.Unit; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; @@ -114,6 +115,14 @@ public class PlayerInit { .build(); player.getInventory().addItemStack(bundle); + try { + player.getInventory().addItemStack(ItemStack.builder(Material.STICK) + .set(ItemComponent.CREATIVE_SLOT_LOCK, Unit.INSTANCE) + .build()); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (event.isFirstSpawn()) { Notification notification = new Notification( Component.text("Welcome!"), diff --git a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java index b459b05da..7e36842e6 100644 --- a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java +++ b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java @@ -36,12 +36,18 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { // DESERIALIZATION private @NotNull Component deserializeAnyComponent(@NotNull BinaryTag nbt) { - if (nbt instanceof CompoundBinaryTag compound) { - return deserializeComponent(compound); - } else { - //todo raw string + list - throw new UnsupportedOperationException("Unknown NBT type: " + nbt.getClass().getName()); - } + return switch (nbt) { + case CompoundBinaryTag compound -> deserializeComponent(compound); + case StringBinaryTag string -> Component.text(string.value()); + case ListBinaryTag list -> { + var builder = Component.text(); + for (var element : list) { + builder.append(deserializeAnyComponent(element)); + } + yield builder.build(); + } + default -> throw new UnsupportedOperationException("Unknown NBT type: " + nbt.getClass().getName()); + }; } private @NotNull Component deserializeComponent(@NotNull CompoundBinaryTag compound) { diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 64f8413c5..5cf16854f 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -744,6 +744,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // Load the nearby chunks and queue them to be sent to them ChunkUtils.forChunksInRange(spawnPosition, settings.getEffectiveViewDistance(), chunkAdder); + sendPendingChunks(); // Send available first chunk immediately to prevent falling through the floor } synchronizePositionAfterTeleport(spawnPosition, 0); // So the player doesn't get stuck diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index ba4881ac3..a33ad89c8 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -4,6 +4,7 @@ import net.kyori.adventure.text.Component; import net.minestom.server.color.Color; import net.minestom.server.item.component.*; import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.Unit; import net.minestom.server.utils.nbt.BinaryTagSerializer; import java.util.List; @@ -30,7 +31,7 @@ public final class ItemComponent { public static final ItemComponentType HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); public static final ItemComponentType HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); public static final ItemComponentType REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING, null); + public static final ItemComponentType CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING_V2, null); public static final ItemComponentType ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN); public static final ItemComponentType INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); public static final ItemComponentType FOOD = declare("food", null, null); //todo diff --git a/src/main/java/net/minestom/server/item/ItemComponentPatch.java b/src/main/java/net/minestom/server/item/ItemComponentPatch.java index 5504d5ae3..91454db89 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentPatch.java +++ b/src/main/java/net/minestom/server/item/ItemComponentPatch.java @@ -40,7 +40,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { //noinspection unchecked ItemComponentType type = (ItemComponentType) ItemComponentType.fromId(entry.getIntKey()); assert type != null; - type.write(entry.getValue()); + type.write(buffer, entry.getValue()); } } for (Int2ObjectMap.Entry entry : value.patch.int2ObjectEntrySet()) { diff --git a/src/main/java/net/minestom/server/item/ItemStackImpl.java b/src/main/java/net/minestom/server/item/ItemStackImpl.java index ed58bc00f..6a45b978e 100644 --- a/src/main/java/net/minestom/server/item/ItemStackImpl.java +++ b/src/main/java/net/minestom/server/item/ItemStackImpl.java @@ -18,15 +18,21 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { @Override public void write(@NotNull NetworkBuffer buffer, ItemStack value) { - buffer.write(NetworkBuffer.VAR_INT, value.material().id()); + if (value.isAir()) { + buffer.write(NetworkBuffer.VAR_INT, 0); + return; + } + buffer.write(NetworkBuffer.VAR_INT, value.amount()); + buffer.write(NetworkBuffer.VAR_INT, value.material().id()); buffer.write(ItemComponentPatch.NETWORK_TYPE, ((ItemStackImpl) value).components); } @Override public ItemStack read(@NotNull NetworkBuffer buffer) { - Material material = Material.fromId(buffer.read(NetworkBuffer.VAR_INT)); int amount = buffer.read(NetworkBuffer.VAR_INT); + if (amount <= 0) return ItemStack.AIR; + Material material = Material.fromId(buffer.read(NetworkBuffer.VAR_INT)); ItemComponentPatch components = buffer.read(ItemComponentPatch.NETWORK_TYPE); return new ItemStackImpl(material, amount, components); } diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index af5a48c9e..9b57daa99 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -13,6 +13,7 @@ import net.minestom.server.entity.metadata.other.PaintingMeta; import net.minestom.server.network.packet.server.play.data.WorldPos; import net.minestom.server.particle.Particle; import net.minestom.server.utils.Direction; +import net.minestom.server.utils.Unit; import net.minestom.server.utils.nbt.BinaryTagReader; import net.minestom.server.utils.nbt.BinaryTagWriter; import net.minestom.server.utils.validate.Check; @@ -29,7 +30,8 @@ import java.util.function.Function; @ApiStatus.Experimental public final class NetworkBuffer { - public static final Type NOTHING = new NetworkBufferTypeImpl.NothingType(); + public static final Type NOTHING = new NetworkBufferTypeImpl.NothingType<>(); + public static final Type NOTHING_V2 = new NetworkBufferTypeImpl.NothingType<>(); public static final Type BOOLEAN = new NetworkBufferTypeImpl.BooleanType(); public static final Type BYTE = new NetworkBufferTypeImpl.ByteType(); public static final Type SHORT = new NetworkBufferTypeImpl.ShortType(); diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java index b1fc188f7..1f19df0d6 100644 --- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java +++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java @@ -25,13 +25,13 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type { int SEGMENT_BITS = 0x7F; int CONTINUE_BIT = 0x80; - record NothingType() implements NetworkBufferTypeImpl { + record NothingType() implements NetworkBufferTypeImpl { @Override - public void write(@NotNull NetworkBuffer buffer, Void value) { + public void write(@NotNull NetworkBuffer buffer, T value) { } @Override - public Void read(@NotNull NetworkBuffer buffer) { + public T read(@NotNull NetworkBuffer buffer) { return null; } } diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index d2a2bc90c..2d2654231 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -11,7 +11,6 @@ import net.minestom.server.collision.Shape; import net.minestom.server.entity.EntitySpawnType; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemComponentMap; import net.minestom.server.item.ItemComponentType; import net.minestom.server.item.Material; @@ -474,16 +473,18 @@ public final class Registry { public static final class MaterialEntry implements Entry { private final NamespaceID namespace; + private final Properties main; private final int id; private final String translationKey; private final Supplier blockSupplier; - private final ItemComponentMap prototype; + private ItemComponentMap prototype; private final EquipmentSlot equipmentSlot; // private final EntityType entityType; //todo private final Properties custom; private MaterialEntry(String namespace, Properties main, Properties custom) { + this.main = main; this.custom = custom; this.namespace = NamespaceID.from(namespace); this.id = main.getInt("id"); @@ -492,34 +493,6 @@ public final class Registry { final String blockNamespace = main.getString("correspondingBlock", null); this.blockSupplier = blockNamespace != null ? () -> Block.fromNamespaceId(blockNamespace) : () -> null; } - try { - - try { - Class.forName(ItemComponent.class.getName()); - } catch (Exception e) { - throw new RuntimeException(e); - } - - ItemComponentMap.Builder builder = ItemComponentMap.builder(); - for (Map.Entry entry : main.section("components")) { - try { - //noinspection unchecked - ItemComponentType component = (ItemComponentType) ItemComponentType.fromNamespaceId(entry.getKey()); - Check.notNull(component, "Unknown component: " + entry.getKey()); - - byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue()); - BinaryTagReader reader = new BinaryTagReader(new DataInputStream(new ByteArrayInputStream(rawValue))); - - //todo remove this try/catch, just so i dont need to impl all comps yet - builder.set(component, component.read(reader.readNameless())); - } catch (NullPointerException e) { - System.out.println(e.getMessage()); - } - } - this.prototype = builder.build(); - } catch (IOException e) { - throw new RuntimeException("failed to parse material registry: " + namespace, e); - } { final Properties armorProperties = main.section("armorProperties"); if (armorProperties != null) { @@ -561,6 +534,30 @@ public final class Registry { } public @NotNull ItemComponentMap prototype() { + if (prototype == null) { + try { + ItemComponentMap.Builder builder = ItemComponentMap.builder(); + for (Map.Entry entry : main.section("components")) { + try { + //noinspection unchecked + ItemComponentType component = (ItemComponentType) ItemComponentType.fromNamespaceId(entry.getKey()); + Check.notNull(component, "Unknown component: " + entry.getKey()); + + byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue()); + BinaryTagReader reader = new BinaryTagReader(new DataInputStream(new ByteArrayInputStream(rawValue))); + + //todo remove this try/catch, just so i dont need to impl all comps yet + builder.set(component, component.read(reader.readNameless())); + } catch (NullPointerException e) { + System.out.println(e.getMessage()); + } + } + this.prototype = builder.build(); + } catch (IOException e) { + throw new RuntimeException("failed to parse material registry: " + namespace, e); + } + } + return prototype; } diff --git a/src/main/java/net/minestom/server/utils/Unit.java b/src/main/java/net/minestom/server/utils/Unit.java new file mode 100644 index 000000000..3a2902c53 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/Unit.java @@ -0,0 +1,7 @@ +package net.minestom.server.utils; + +public final class Unit { + //todo would rather just support void + + public static final Unit INSTANCE = new Unit(); +} From 3fdd9ab9bb69db1ff648d1c542a9d8a4547231f8 Mon Sep 17 00:00:00 2001 From: mworzala Date: Wed, 17 Apr 2024 12:39:49 -0400 Subject: [PATCH 25/46] chore: cleanup itemcomponenttype, update to datagen changes --- gradle/libs.versions.toml | 2 +- .../kyori/adventure/nbt/TagStringIOExt.java | 42 +++++ .../net/minestom/server/entity/Entity.java | 4 +- .../minestom/server/entity/LivingEntity.java | 11 +- .../minestom/server/item/ItemComponent.java | 140 +++++++++------- ...ntTypeImpl.java => ItemComponentImpl.java} | 12 +- .../server/item/ItemComponentMap.java | 10 +- .../server/item/ItemComponentMapImpl.java | 12 +- .../server/item/ItemComponentPatch.java | 24 +-- .../server/item/ItemComponentType.java | 30 ---- .../net/minestom/server/item/ItemStack.java | 10 +- .../minestom/server/item/ItemStackImpl.java | 12 +- .../minestom/server/registry/Registry.java | 150 ++++++++++++------ .../server/utils/nbt/BinaryTagUtil.java | 5 - 14 files changed, 271 insertions(+), 193 deletions(-) create mode 100644 src/main/java/net/kyori/adventure/nbt/TagStringIOExt.java rename src/main/java/net/minestom/server/item/{ItemComponentTypeImpl.java => ItemComponentImpl.java} (75%) delete mode 100644 src/main/java/net/minestom/server/item/ItemComponentType.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd19340c0..1bda5c0f8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ metadata.format.version = "1.1" [versions] # Important dependencies -data = "24w13a-dev" +data = "1.20.5-pre4-dev" adventure = "4.16.0" kotlin = "1.7.22" dependencyGetter = "v1.0.1" diff --git a/src/main/java/net/kyori/adventure/nbt/TagStringIOExt.java b/src/main/java/net/kyori/adventure/nbt/TagStringIOExt.java new file mode 100644 index 000000000..de973e3f8 --- /dev/null +++ b/src/main/java/net/kyori/adventure/nbt/TagStringIOExt.java @@ -0,0 +1,42 @@ +package net.kyori.adventure.nbt; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +// Based on net.kyori.adventure.nbt.TagStringIO licensed under the MIT license. +// https://github.com/KyoriPowered/adventure/blob/main/4/nbt/src/main/java/net/kyori/adventure/nbt/TagStringIO.java +public final class TagStringIOExt { + + public static @NotNull String writeTag(@NotNull BinaryTag tag) { + return writeTag(tag, ""); + } + + public static @NotNull String writeTag(@NotNull BinaryTag input, @NotNull String indent) { + final StringBuilder sb = new StringBuilder(); + try (final TagStringWriter emit = new TagStringWriter(sb, indent)) { + emit.writeTag(input); + } catch (IOException e) { + // The IOException comes from Writer#close(), but we are passing a StringBuilder which + // is not a writer and does not need to be closed so will not throw. + throw new RuntimeException(e); + } + return sb.toString(); + } + + public static @NotNull BinaryTag readTag(@NotNull String input) throws IOException { + try { + final CharBuffer buffer = new CharBuffer(input); + final TagStringReader parser = new TagStringReader(buffer); + final BinaryTag tag = parser.tag(); + if (buffer.skipWhitespace().hasMore()) { + throw new IOException("Document had trailing content after first tag"); + } + return tag; + } catch (final StringTagParseException ex) { + throw new IOException(ex); + } + } + + private TagStringIOExt() {} +} diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 6d3c83d7d..2d8685941 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -1385,13 +1385,11 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev /** * Gets the entity eye height. - *

- * Default to {@link BoundingBox#height()}x0.85 * * @return the entity eye height */ public double getEyeHeight() { - return getPose() == Pose.SLEEPING ? 0.2 : (boundingBox.height() * 0.85); + return getPose() == Pose.SLEEPING ? 0.2 : entityType.registry().eyeHeight(); } /** diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 8e3c69c39..e35d81cc7 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -399,7 +399,7 @@ public class LivingEntity extends Entity implements EquipmentHandler { * @param health the new entity health */ public void setHealth(float health) { - this.health = Math.min(health, getMaxHealth()); + this.health = Math.min(health, (float) getAttributeValue(Attribute.GENERIC_MAX_HEALTH)); if (this.health <= 0 && !isDead) { kill(); } @@ -418,15 +418,6 @@ public class LivingEntity extends Entity implements EquipmentHandler { return lastDamage; } - /** - * Gets the entity max health from {@link #getAttributeValue(Attribute)} {@link Attribute#GENERIC_MAX_HEALTH}. - * - * @return the entity max health - */ - public float getMaxHealth() { - return (float) getAttributeValue(Attribute.GENERIC_MAX_HEALTH); - } - /** * Sets the heal of the entity as its max health. *

diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index a33ad89c8..5c4da54bc 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -1,75 +1,97 @@ package net.minestom.server.item; +import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.color.Color; import net.minestom.server.item.component.*; import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.Unit; import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; -import static net.minestom.server.item.ItemComponentTypeImpl.declare; +import static net.minestom.server.item.ItemComponentImpl.declare; -public final class ItemComponent { +public sealed interface ItemComponent extends StaticProtocolObject permits ItemComponentImpl { // Note that even non-networked components are declared here as they still contribute to the component ID counter. - public static final ItemComponentType CUSTOM_DATA = declare("custom_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - public static final ItemComponentType MAX_STACK_SIZE = declare("max_stack_size", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType MAX_DAMAGE = declare("max_damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType DAMAGE = declare("damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType UNBREAKABLE = declare("unbreakable", Unbreakable.NETWORK_TYPE, Unbreakable.NBT_TYPE); - public static final ItemComponentType CUSTOM_NAME = declare("custom_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); - public static final ItemComponentType ITEM_NAME = declare("item_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); - public static final ItemComponentType> LORE = declare("lore", NetworkBuffer.COMPONENT.list(256), BinaryTagSerializer.JSON_COMPONENT.list()); - public static final ItemComponentType RARITY = declare("rarity", ItemRarity.NETWORK_TYPE, ItemRarity.NBT_TYPE); - public static final ItemComponentType ENCHANTMENTS = declare("enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); - public static final ItemComponentType CAN_PLACE_ON = declare("can_place_on", null, null); //todo - public static final ItemComponentType CAN_BREAK = declare("can_break", null, null); //todo - public static final ItemComponentType ATTRIBUTE_MODIFIERS = declare("attribute_modifiers", AttributeList.NETWORK_TYPE, AttributeList.NBT_TYPE); - public static final ItemComponentType CUSTOM_MODEL_DATA = declare("custom_model_data", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); - public static final ItemComponentType HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); - public static final ItemComponentType REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING_V2, null); - public static final ItemComponentType ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN); - public static final ItemComponentType INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); - public static final ItemComponentType FOOD = declare("food", null, null); //todo - public static final ItemComponentType FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); - public static final ItemComponentType TOOL = declare("tool", null, null); //todo - public static final ItemComponentType STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); - public static final ItemComponentType DYED_COLOR = declare("dyed_color", DyedItemColor.NETWORK_TYPE, DyedItemColor.NBT_TYPE); - public static final ItemComponentType MAP_COLOR = declare("map_color", NetworkBuffer.COLOR, BinaryTagSerializer.INT.map(Color::new, Color::asRGB)); - public static final ItemComponentType MAP_ID = declare("map_id", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType MAP_DECORATIONS = declare("map_decorations", null, MapDecorations.NBT_TYPE); - public static final ItemComponentType MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null); - public static final ItemComponentType> CHARGED_PROJECTILES = declare("charged_projectiles", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); - public static final ItemComponentType> BUNDLE_CONTENTS = declare("bundle_contents", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); - public static final ItemComponentType POTION_CONTENTS = declare("potion_contents", null, null); //todo - public static final ItemComponentType SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo - public static final ItemComponentType WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE); - public static final ItemComponentType WRITTEN_BOOK_CONTENT = declare("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE); - public static final ItemComponentType TRIM = declare("trim", null, null); //todo - public static final ItemComponentType DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo - public static final ItemComponentType ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - public static final ItemComponentType BUCKET_ENTITY_DATA = declare("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - public static final ItemComponentType BLOCK_ENTITY_DATA = declare("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - public static final ItemComponentType INSTRUMENT = declare("instrument", null, null); //todo - public static final ItemComponentType OMINOUS_BOTTLE_AMPLIFIER = declare("ominous_bottle_amplifier", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - public static final ItemComponentType> RECIPES = declare("recipes", NetworkBuffer.STRING.list(Short.MAX_VALUE), BinaryTagSerializer.STRING.list()); - public static final ItemComponentType LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE); - public static final ItemComponentType FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE); - public static final ItemComponentType FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE); - public static final ItemComponentType PROFILE = declare("profile", null, null); //todo - public static final ItemComponentType NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING); - public static final ItemComponentType BANNER_PATTERNS = declare("banner_patterns", null, null); //todo - public static final ItemComponentType BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity - public static final ItemComponentType POT_DECORATIONS = declare("pot_decorations", null, null); //todo - public static final ItemComponentType> CONTAINER = declare("container", ItemStack.NETWORK_TYPE.list(256), BinaryTagSerializer.ITEM.list()); - public static final ItemComponentType BLOCK_STATE = declare("block_state", null, null); //todo - public static final ItemComponentType BEES = declare("bees", null, null); //todo - public static final ItemComponentType LOCK = declare("lock", null, BinaryTagSerializer.STRING); - public static final ItemComponentType CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE); + ItemComponent CUSTOM_DATA = declare("custom_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent MAX_STACK_SIZE = declare("max_stack_size", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent MAX_DAMAGE = declare("max_damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent DAMAGE = declare("damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent UNBREAKABLE = declare("unbreakable", Unbreakable.NETWORK_TYPE, Unbreakable.NBT_TYPE); + ItemComponent CUSTOM_NAME = declare("custom_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); + ItemComponent ITEM_NAME = declare("item_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT); + ItemComponent> LORE = declare("lore", NetworkBuffer.COMPONENT.list(256), BinaryTagSerializer.JSON_COMPONENT.list()); + ItemComponent RARITY = declare("rarity", ItemRarity.NETWORK_TYPE, ItemRarity.NBT_TYPE); + ItemComponent ENCHANTMENTS = declare("enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); + ItemComponent CAN_PLACE_ON = declare("can_place_on", null, null); //todo + ItemComponent CAN_BREAK = declare("can_break", null, null); //todo + ItemComponent ATTRIBUTE_MODIFIERS = declare("attribute_modifiers", AttributeList.NETWORK_TYPE, AttributeList.NBT_TYPE); + ItemComponent CUSTOM_MODEL_DATA = declare("custom_model_data", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + ItemComponent HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + ItemComponent REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING_V2, null); + ItemComponent ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN); + ItemComponent INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); + ItemComponent FOOD = declare("food", null, null); //todo + ItemComponent FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); + ItemComponent TOOL = declare("tool", null, null); //todo + ItemComponent STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); + ItemComponent DYED_COLOR = declare("dyed_color", DyedItemColor.NETWORK_TYPE, DyedItemColor.NBT_TYPE); + ItemComponent MAP_COLOR = declare("map_color", NetworkBuffer.COLOR, BinaryTagSerializer.INT.map(Color::new, Color::asRGB)); + ItemComponent MAP_ID = declare("map_id", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent MAP_DECORATIONS = declare("map_decorations", null, MapDecorations.NBT_TYPE); + ItemComponent MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null); + ItemComponent> CHARGED_PROJECTILES = declare("charged_projectiles", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + ItemComponent> BUNDLE_CONTENTS = declare("bundle_contents", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); + ItemComponent POTION_CONTENTS = declare("potion_contents", null, null); //todo + ItemComponent SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo + ItemComponent WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE); + ItemComponent WRITTEN_BOOK_CONTENT = declare("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE); + ItemComponent TRIM = declare("trim", null, null); //todo + ItemComponent DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo + ItemComponent ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent BUCKET_ENTITY_DATA = declare("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent BLOCK_ENTITY_DATA = declare("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); + ItemComponent INSTRUMENT = declare("instrument", null, null); //todo + ItemComponent OMINOUS_BOTTLE_AMPLIFIER = declare("ominous_bottle_amplifier", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); + ItemComponent> RECIPES = declare("recipes", NetworkBuffer.STRING.list(Short.MAX_VALUE), BinaryTagSerializer.STRING.list()); + ItemComponent LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE); + ItemComponent FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE); + ItemComponent FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE); + ItemComponent PROFILE = declare("profile", null, null); //todo + ItemComponent NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING); + ItemComponent BANNER_PATTERNS = declare("banner_patterns", null, null); //todo + ItemComponent BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity + ItemComponent POT_DECORATIONS = declare("pot_decorations", null, null); //todo + ItemComponent> CONTAINER = declare("container", ItemStack.NETWORK_TYPE.list(256), BinaryTagSerializer.ITEM.list()); + ItemComponent BLOCK_STATE = declare("block_state", null, null); //todo + ItemComponent BEES = declare("bees", null, null); //todo + ItemComponent LOCK = declare("lock", null, BinaryTagSerializer.STRING); + ItemComponent CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE); - private ItemComponent() {} + @NotNull T read(@NotNull BinaryTag tag); + @NotNull BinaryTag write(@NotNull T value); + + @NotNull T read(@NotNull NetworkBuffer reader); + void write(@NotNull NetworkBuffer writer, @NotNull T value); + + + static @Nullable ItemComponent fromNamespaceId(@NotNull String namespaceId) { + return ItemComponentImpl.NAMESPACES.get(namespaceId); + } + + static @Nullable ItemComponent fromNamespaceId(@NotNull NamespaceID namespaceId) { + return fromNamespaceId(namespaceId.asString()); + } + + static @Nullable ItemComponent fromId(int id) { + return ItemComponentImpl.IDS.get(id); + } } diff --git a/src/main/java/net/minestom/server/item/ItemComponentTypeImpl.java b/src/main/java/net/minestom/server/item/ItemComponentImpl.java similarity index 75% rename from src/main/java/net/minestom/server/item/ItemComponentTypeImpl.java rename to src/main/java/net/minestom/server/item/ItemComponentImpl.java index 00c8852af..4850b4692 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentTypeImpl.java +++ b/src/main/java/net/minestom/server/item/ItemComponentImpl.java @@ -12,17 +12,17 @@ import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.Map; -record ItemComponentTypeImpl( +record ItemComponentImpl( int id, @NotNull NamespaceID namespace, @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt -) implements ItemComponentType { - static final Map> NAMESPACES = new HashMap<>(32); - static final ObjectArray> IDS = ObjectArray.singleThread(32); +) implements ItemComponent { + static final Map> NAMESPACES = new HashMap<>(32); + static final ObjectArray> IDS = ObjectArray.singleThread(32); - static ItemComponentType declare(@NotNull String name, @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt) { - ItemComponentType impl = new ItemComponentTypeImpl<>(NAMESPACES.size(), NamespaceID.from(name), network, nbt); + static ItemComponent declare(@NotNull String name, @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt) { + ItemComponent impl = new ItemComponentImpl<>(NAMESPACES.size(), NamespaceID.from(name), network, nbt); NAMESPACES.put(impl.name(), impl); IDS.set(impl.id(), impl); return impl; diff --git a/src/main/java/net/minestom/server/item/ItemComponentMap.java b/src/main/java/net/minestom/server/item/ItemComponentMap.java index 7488e8497..0944c3382 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentMap.java +++ b/src/main/java/net/minestom/server/item/ItemComponentMap.java @@ -10,20 +10,20 @@ public interface ItemComponentMap { return new ItemComponentMapImpl.BuilderImpl(new Int2ObjectArrayMap<>()); } - boolean has(@NotNull ItemComponentType component); + boolean has(@NotNull ItemComponent component); - @Nullable T get(@NotNull ItemComponentType component); + @Nullable T get(@NotNull ItemComponent component); - default @NotNull T get(@NotNull ItemComponentType component, @NotNull T defaultValue) { + default @NotNull T get(@NotNull ItemComponent component, @NotNull T defaultValue) { T value = get(component); return value != null ? value : defaultValue; } interface Builder extends ItemComponentMap { - @NotNull Builder set(@NotNull ItemComponentType component, @Nullable Object value); + @NotNull Builder set(@NotNull ItemComponent component, @Nullable Object value); - @NotNull Builder remove(@NotNull ItemComponentType component); + @NotNull Builder remove(@NotNull ItemComponent component); @NotNull ItemComponentMap build(); } diff --git a/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java b/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java index 7211f118b..42365e07a 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java +++ b/src/main/java/net/minestom/server/item/ItemComponentMapImpl.java @@ -6,35 +6,35 @@ import org.jetbrains.annotations.Nullable; public record ItemComponentMapImpl(@NotNull Int2ObjectMap components) implements ItemComponentMap { @Override - public boolean has(@NotNull ItemComponentType component) { + public boolean has(@NotNull ItemComponent component) { return components.get(component.id()) != null; } @Override - public @Nullable T get(@NotNull ItemComponentType component) { + public @Nullable T get(@NotNull ItemComponent component) { return (T) components.get(component.id()); } public record BuilderImpl(@NotNull Int2ObjectMap components) implements ItemComponentMap.Builder { @Override - public boolean has(@NotNull ItemComponentType component) { + public boolean has(@NotNull ItemComponent component) { return components.get(component.id()) != null; } @Override - public @Nullable T get(@NotNull ItemComponentType component) { + public @Nullable T get(@NotNull ItemComponent component) { return (T) components.get(component.id()); } @Override - public @NotNull Builder set(@NotNull ItemComponentType component, @Nullable Object value) { + public @NotNull Builder set(@NotNull ItemComponent component, @Nullable Object value) { components.put(component.id(), value); return this; } @Override - public @NotNull Builder remove(@NotNull ItemComponentType component) { + public @NotNull Builder remove(@NotNull ItemComponent component) { components.remove(component.id()); return this; } diff --git a/src/main/java/net/minestom/server/item/ItemComponentPatch.java b/src/main/java/net/minestom/server/item/ItemComponentPatch.java index 91454db89..27fb99e3e 100644 --- a/src/main/java/net/minestom/server/item/ItemComponentPatch.java +++ b/src/main/java/net/minestom/server/item/ItemComponentPatch.java @@ -38,7 +38,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { if (entry.getValue() != null) { buffer.write(NetworkBuffer.VAR_INT, entry.getIntKey()); //noinspection unchecked - ItemComponentType type = (ItemComponentType) ItemComponentType.fromId(entry.getIntKey()); + ItemComponent type = (ItemComponent) ItemComponent.fromId(entry.getIntKey()); assert type != null; type.write(buffer, entry.getValue()); } @@ -58,7 +58,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { for (int i = 0; i < added; i++) { int id = buffer.read(NetworkBuffer.VAR_INT); //noinspection unchecked - ItemComponentType type = (ItemComponentType) ItemComponentType.fromId(id); + ItemComponent type = (ItemComponent) ItemComponent.fromId(id); Check.notNull(type, "Unknown item component id: {0}", id); patch.put(id, type.read(buffer)); } @@ -80,7 +80,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { key = key.substring(1); remove = true; } - ItemComponentType type = ItemComponentType.fromNamespaceId(key); + ItemComponent type = ItemComponent.fromNamespaceId(key); Check.notNull(type, "Unknown item component: {0}", key); if (remove) { patch.put(type.id(), null); @@ -96,7 +96,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); for (Int2ObjectMap.Entry entry : patch.patch.int2ObjectEntrySet()) { //noinspection unchecked - ItemComponentType type = (ItemComponentType) ItemComponentType.fromId(entry.getIntKey()); + ItemComponent type = (ItemComponent) ItemComponent.fromId(entry.getIntKey()); Check.notNull(type, "Unknown item component id: {0}", entry.getIntKey()); if (entry.getValue() == null) { builder.put(REMOVAL_PREFIX + type.name(), CompoundBinaryTag.empty()); @@ -108,7 +108,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { } ); - public boolean has(@NotNull ItemComponentMap prototype, @NotNull ItemComponentType component) { + public boolean has(@NotNull ItemComponentMap prototype, @NotNull ItemComponent component) { if (patch.containsKey(component.id())) { return patch.get(component.id()) != null; } else { @@ -116,7 +116,7 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { } } - public @Nullable T get(@NotNull ItemComponentMap prototype, @NotNull ItemComponentType component) { + public @Nullable T get(@NotNull ItemComponentMap prototype, @NotNull ItemComponent component) { if (patch.containsKey(component.id())) { return (T) patch.get(component.id()); } else { @@ -124,13 +124,13 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { } } - public @NotNull ItemComponentPatch with(@NotNull ItemComponentType component, @NotNull T value) { + public @NotNull ItemComponentPatch with(@NotNull ItemComponent component, @NotNull T value) { Int2ObjectMap newPatch = new Int2ObjectArrayMap<>(patch); newPatch.put(component.id(), value); return new ItemComponentPatch(newPatch); } - public @NotNull ItemComponentPatch without(@NotNull ItemComponentType component) { + public @NotNull ItemComponentPatch without(@NotNull ItemComponent component) { Int2ObjectMap newPatch = new Int2ObjectArrayMap<>(patch); newPatch.put(component.id(), null); return new ItemComponentPatch(newPatch); @@ -143,21 +143,21 @@ record ItemComponentPatch(@NotNull Int2ObjectMap patch) { record Builder(@NotNull Int2ObjectMap patch) implements ItemComponentMap { @Override - public boolean has(@NotNull ItemComponentType component) { + public boolean has(@NotNull ItemComponent component) { return patch.get(component.id()) != null; } @Override - public @Nullable T get(@NotNull ItemComponentType component) { + public @Nullable T get(@NotNull ItemComponent component) { return (T) patch.get(component.id()); } - public ItemComponentPatch.@NotNull Builder set(@NotNull ItemComponentType component, @NotNull T value) { + public ItemComponentPatch.@NotNull Builder set(@NotNull ItemComponent component, @NotNull T value) { patch.put(component.id(), value); return this; } - public ItemComponentPatch.@NotNull Builder remove(@NotNull ItemComponentType component) { + public ItemComponentPatch.@NotNull Builder remove(@NotNull ItemComponent component) { patch.put(component.id(), null); return this; } diff --git a/src/main/java/net/minestom/server/item/ItemComponentType.java b/src/main/java/net/minestom/server/item/ItemComponentType.java deleted file mode 100644 index 46e8a952e..000000000 --- a/src/main/java/net/minestom/server/item/ItemComponentType.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.minestom.server.item; - -import net.kyori.adventure.nbt.BinaryTag; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.registry.StaticProtocolObject; -import net.minestom.server.utils.NamespaceID; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public sealed interface ItemComponentType extends StaticProtocolObject permits ItemComponentTypeImpl { - - @NotNull T read(@NotNull BinaryTag tag); - @NotNull BinaryTag write(@NotNull T value); - - @NotNull T read(@NotNull NetworkBuffer reader); - void write(@NotNull NetworkBuffer writer, @NotNull T value); - - - static @Nullable ItemComponentType fromNamespaceId(@NotNull String namespaceId) { - return ItemComponentTypeImpl.NAMESPACES.get(namespaceId); - } - - static @Nullable ItemComponentType fromNamespaceId(@NotNull NamespaceID namespaceId) { - return fromNamespaceId(namespaceId.asString()); - } - - static @Nullable ItemComponentType fromId(int id) { - return ItemComponentTypeImpl.IDS.get(id); - } -} diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 824b0eadb..bbedc7d2f 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -79,16 +79,16 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv } @Contract(value = "_, _ -> new", pure = true) - @NotNull ItemStack with(@NotNull ItemComponentType component, T value); + @NotNull ItemStack with(@NotNull ItemComponent component, T value); - default @NotNull ItemStack with(@NotNull ItemComponentType component, @NotNull UnaryOperator operator) { + default @NotNull ItemStack with(@NotNull ItemComponent component, @NotNull UnaryOperator operator) { T value = get(component); if (value == null) return this; return with(component, operator.apply(value)); } @Contract(value = "_, -> new", pure = true) - @NotNull ItemStack without(@NotNull ItemComponentType component); + @NotNull ItemStack without(@NotNull ItemComponent component); @Contract(value = "_, _ -> new", pure = true) default @NotNull ItemStack withTag(@NotNull Tag tag, @Nullable T value) { @@ -144,10 +144,10 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @NotNull Builder amount(int amount); @Contract(value = "_, _ -> this") - @NotNull Builder set(@NotNull ItemComponentType component, T value); + @NotNull Builder set(@NotNull ItemComponent component, T value); @Contract(value = "_ -> this") - @NotNull Builder remove(@NotNull ItemComponentType component); + @NotNull Builder remove(@NotNull ItemComponent component); @Contract(value = "_, _ -> this") default @NotNull Builder set(@NotNull Tag tag, @Nullable T value) { diff --git a/src/main/java/net/minestom/server/item/ItemStackImpl.java b/src/main/java/net/minestom/server/item/ItemStackImpl.java index 6a45b978e..413178804 100644 --- a/src/main/java/net/minestom/server/item/ItemStackImpl.java +++ b/src/main/java/net/minestom/server/item/ItemStackImpl.java @@ -53,12 +53,12 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component } @Override - public @Nullable T get(@NotNull ItemComponentType component) { + public @Nullable T get(@NotNull ItemComponent component) { return components.get(material.prototype(), component); } @Override - public boolean has(@NotNull ItemComponentType component) { + public boolean has(@NotNull ItemComponent component) { return components.has(material.prototype(), component); } @@ -81,12 +81,12 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component } @Override - public @NotNull ItemStack with(@NotNull ItemComponentType component, T value) { + public @NotNull ItemStack with(@NotNull ItemComponent component, T value) { return new ItemStackImpl(material, amount, components.with(component, value)); } @Override - public @NotNull ItemStack without(@NotNull ItemComponentType component) { + public @NotNull ItemStack without(@NotNull ItemComponent component) { return new ItemStackImpl(material, amount, components.without(component)); } @@ -151,13 +151,13 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component } @Override - public ItemStack.@NotNull Builder set(@NotNull ItemComponentType component, T value) { + public ItemStack.@NotNull Builder set(@NotNull ItemComponent component, T value) { components.set(component, value); return this; } @Override - public ItemStack.@NotNull Builder remove(@NotNull ItemComponentType component) { + public ItemStack.@NotNull Builder remove(@NotNull ItemComponent component) { components.remove(component); return this; } diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 2d2654231..31d37b2c3 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -2,6 +2,8 @@ package net.minestom.server.registry; import com.google.gson.ToNumberPolicy; import com.google.gson.stream.JsonReader; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.TagStringIOExt; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; import net.minestom.server.MinecraftServer; @@ -9,20 +11,22 @@ import net.minestom.server.collision.BoundingBox; import net.minestom.server.collision.CollisionUtils; import net.minestom.server.collision.Shape; import net.minestom.server.entity.EntitySpawnType; +import net.minestom.server.entity.EntityType; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.instance.block.Block; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemComponentMap; -import net.minestom.server.item.ItemComponentType; import net.minestom.server.item.Material; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.collection.ObjectArray; -import net.minestom.server.utils.nbt.BinaryTagReader; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -480,7 +484,7 @@ public final class Registry { private ItemComponentMap prototype; private final EquipmentSlot equipmentSlot; - // private final EntityType entityType; //todo + private final EntityType entityType; private final Properties custom; private MaterialEntry(String namespace, Properties main, Properties custom) { @@ -507,14 +511,14 @@ public final class Registry { this.equipmentSlot = null; } } -// { -// final Properties spawnEggProperties = main.section("spawnEggProperties"); -// if (spawnEggProperties != null) { -// this.entityType = EntityType.fromNamespaceId(spawnEggProperties.getString("entityType")); -// } else { -// this.entityType = null; -// } -// } + { + final Properties spawnEggProperties = main.section("spawnEggProperties"); + if (spawnEggProperties != null) { + this.entityType = EntityType.fromNamespaceId(spawnEggProperties.getString("entityType")); + } else { + this.entityType = null; + } + } } public @NotNull NamespaceID namespace() { @@ -540,14 +544,12 @@ public final class Registry { for (Map.Entry entry : main.section("components")) { try { //noinspection unchecked - ItemComponentType component = (ItemComponentType) ItemComponentType.fromNamespaceId(entry.getKey()); + ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); Check.notNull(component, "Unknown component: " + entry.getKey()); - byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue()); - BinaryTagReader reader = new BinaryTagReader(new DataInputStream(new ByteArrayInputStream(rawValue))); - + BinaryTag tag = TagStringIOExt.readTag((String) entry.getValue()); + builder.set(component, component.read(tag)); //todo remove this try/catch, just so i dont need to impl all comps yet - builder.set(component, component.read(reader.readNameless())); } catch (NullPointerException e) { System.out.println(e.getMessage()); } @@ -569,13 +571,14 @@ public final class Registry { return equipmentSlot; } -// /** -// * Gets the entity type this item can spawn. Only present for spawn eggs (e.g. wolf spawn egg, skeleton spawn egg) -// * @return The entity type it can spawn, or null if it is not a spawn egg -// */ -// public @Nullable EntityType spawnEntityType() { -// return entityType; -// } + /** + * Gets the entity type this item can spawn. Only present for spawn eggs (e.g. wolf spawn egg, skeleton spawn egg) + * + * @return The entity type it can spawn, or null if it is not a spawn egg + */ + public @Nullable EntityType spawnEntityType() { + return entityType; + } @Override public Properties custom() { @@ -583,28 +586,85 @@ public final class Registry { } } - public record EntityEntry(NamespaceID namespace, int id, - String translationKey, - double width, double height, - double drag, double acceleration, - EntitySpawnType spawnType, - BoundingBox boundingBox, - Properties custom) implements Entry { + public static final class EntityEntry implements Entry { + private final NamespaceID namespace; + private final int id; + private final String translationKey; + private final double drag; + private final double acceleration; + private final EntitySpawnType spawnType; + private final double width; + private final double height; + private final double eyeHeight; + private final BoundingBox boundingBox; + private final Properties custom; + public EntityEntry(String namespace, Properties main, Properties custom) { - this(NamespaceID.from(namespace), - main.getInt("id"), - main.getString("translationKey"), - main.getDouble("width"), - main.getDouble("height"), - main.getDouble("drag", 0.02), - main.getDouble("acceleration", 0.08), - EntitySpawnType.valueOf(main.getString("packetType").toUpperCase(Locale.ROOT)), - new BoundingBox( - main.getDouble("width"), - main.getDouble("height"), - main.getDouble("width")), - custom - ); + this.namespace = NamespaceID.from(namespace); + this.id = main.getInt("id"); + this.translationKey = main.getString("translationKey"); + this.drag = main.getDouble("drag", 0.02); + this.acceleration = main.getDouble("acceleration", 0.08); + this.spawnType = EntitySpawnType.valueOf(main.getString("packetType").toUpperCase(Locale.ROOT)); + + // Dimensions + this.width = main.getDouble("width"); + this.height = main.getDouble("height"); + this.eyeHeight = main.getDouble("eyeHeight"); + this.boundingBox = new BoundingBox(this.width, this.height, this.width); + + // Attachments + Properties attachments = main.section("attachments"); + if (attachments != null) { + //todo + } + + this.custom = custom; + } + + public @NotNull NamespaceID namespace() { + return namespace; + } + + public int id() { + return id; + } + + public String translationKey() { + return translationKey; + } + + public double drag() { + return drag; + } + + public double acceleration() { + return acceleration; + } + + public @NotNull EntitySpawnType spawnType() { + return spawnType; + } + + public double width() { + return width; + } + + public double height() { + return height; + } + + public double eyeHeight() { + return eyeHeight; + } + + public @NotNull BoundingBox boundingBox() { + return boundingBox; + } + + @Override + public Properties custom() { + return custom; } } diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java index d61f07235..d61c1074f 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagUtil.java @@ -4,7 +4,6 @@ import net.kyori.adventure.nbt.*; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; @ApiStatus.Internal public final class BinaryTagUtil { @@ -55,10 +54,6 @@ public final class BinaryTagUtil { } } - public static @Nullable String getStringOrNull(@NotNull CompoundBinaryTag tag, @NotNull String key) { - return tag.keySet().contains(key) ? tag.getString(key) : null; - } - private BinaryTagUtil() { } } From 2b974d95f9aee679223f4219f529fc21c49e4964 Mon Sep 17 00:00:00 2001 From: mworzala Date: Thu, 18 Apr 2024 06:18:28 -0400 Subject: [PATCH 26/46] feat: more components --- build.gradle.kts | 3 +- .../java/net/minestom/codegen/Generators.java | 2 +- .../java/net/minestom/demo/PlayerInit.java | 13 +- .../net/minestom/server/color/DyeColor.java | 5 + .../item/{ => enchant}/Enchantments.java | 2 +- .../minestom/server/potion/PotionEffects.java | 2 +- .../registry/ArgumentEnchantment.java | 2 +- .../net/minestom/server/entity/Player.java | 6 +- .../type/EnchantmentTableInventory.java | 2 +- .../minestom/server/item/ItemComponent.java | 24 ++-- .../net/minestom/server/item/Material.java | 5 + .../minestom/server/item/MaterialImpl.java | 5 + .../server/item/armor/TrimMaterial.java | 9 ++ .../server/item/armor/TrimMaterialImpl.java | 10 ++ .../server/item/armor/TrimPattern.java | 9 ++ .../server/item/armor/TrimPatternImpl.java | 8 ++ .../server/item/component/ArmorTrim.java | 43 +++++++ .../minestom/server/item/component/Bee.java | 33 +++++ .../item/component/DebugStickState.java | 38 ++++++ .../item/component/EnchantmentList.java | 2 +- .../minestom/server/item/component/Food.java | 85 +++++++++++++ .../server/item/component/ItemBlockState.java | 74 +++++++++++ .../server/item/component/ItemRarity.java | 35 +----- .../server/item/component/PotDecorations.java | 46 ++++--- .../server/item/component/PotionContents.java | 83 ++++++++++++ .../item/component/SuspiciousStewEffects.java | 49 ++++++++ .../item/{ => enchant}/Enchantment.java | 4 +- .../item/{ => enchant}/EnchantmentImpl.java | 2 +- .../listener/BlockPlacementListener.java | 6 +- .../server/network/NetworkBuffer.java | 18 +++ .../server/potion/CustomPotionEffect.java | 119 +++++++++++++++++- .../minestom/server/potion/PotionEffect.java | 5 +- .../server/potion/PotionEffectImpl.java | 3 + .../server/utils/nbt/BinaryTagSerializer.java | 45 ++++++- .../server/command/ArgumentTypeTest.java | 3 +- .../command/CommandSyntaxSingleTest.java | 5 +- .../minestom/server/item/ItemEnchantTest.java | 4 +- .../net/minestom/server/item/ItemTest.java | 1 + 38 files changed, 708 insertions(+), 102 deletions(-) rename src/autogenerated/java/net/minestom/server/item/{ => enchant}/Enchantments.java (98%) create mode 100644 src/main/java/net/minestom/server/item/component/ArmorTrim.java create mode 100644 src/main/java/net/minestom/server/item/component/Bee.java create mode 100644 src/main/java/net/minestom/server/item/component/DebugStickState.java create mode 100644 src/main/java/net/minestom/server/item/component/Food.java create mode 100644 src/main/java/net/minestom/server/item/component/ItemBlockState.java create mode 100644 src/main/java/net/minestom/server/item/component/PotionContents.java create mode 100644 src/main/java/net/minestom/server/item/component/SuspiciousStewEffects.java rename src/main/java/net/minestom/server/item/{ => enchant}/Enchantment.java (96%) rename src/main/java/net/minestom/server/item/{ => enchant}/EnchantmentImpl.java (95%) diff --git a/build.gradle.kts b/build.gradle.kts index f8259daf4..9bcb30de8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,8 +37,7 @@ allprojects { withSourcesJar() withJavadocJar() - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + toolchain.languageVersion = JavaLanguageVersion.of(21) } tasks.withType { diff --git a/code-generators/src/main/java/net/minestom/codegen/Generators.java b/code-generators/src/main/java/net/minestom/codegen/Generators.java index 26688c331..47f2e8c6b 100644 --- a/code-generators/src/main/java/net/minestom/codegen/Generators.java +++ b/code-generators/src/main/java/net/minestom/codegen/Generators.java @@ -26,7 +26,7 @@ public class Generators { generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "Materials"); generator.generate(resource("entities.json"), "net.minestom.server.entity", "EntityType", "EntityTypeImpl", "EntityTypes"); generator.generate(resource("biomes.json"), "net.minestom.server.world.biomes", "Biome", "BiomeImpl", "Biomes"); - generator.generate(resource("enchantments.json"), "net.minestom.server.item", "Enchantment", "EnchantmentImpl", "Enchantments"); + generator.generate(resource("enchantments.json"), "net.minestom.server.item.enchant", "Enchantment", "EnchantmentImpl", "Enchantments"); generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffects"); generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypes"); generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "Particles"); diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 73827f0ff..089d3d3c5 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -31,15 +31,16 @@ import net.minestom.server.inventory.InventoryType; import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.item.component.ItemBlockState; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.TickMonitor; import net.minestom.server.utils.MathUtils; -import net.minestom.server.utils.Unit; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; @@ -115,13 +116,9 @@ public class PlayerInit { .build(); player.getInventory().addItemStack(bundle); - try { - player.getInventory().addItemStack(ItemStack.builder(Material.STICK) - .set(ItemComponent.CREATIVE_SLOT_LOCK, Unit.INSTANCE) - .build()); - } catch (Exception e) { - throw new RuntimeException(e); - } + player.getInventory().addItemStack(ItemStack.builder(Material.STONE_STAIRS) + .set(ItemComponent.BLOCK_STATE, new ItemBlockState(Map.of("facing", "west", "half", "top"))) + .build()); if (event.isFirstSpawn()) { Notification notification = new Notification( diff --git a/src/autogenerated/java/net/minestom/server/color/DyeColor.java b/src/autogenerated/java/net/minestom/server/color/DyeColor.java index 092b0580e..d3de026e4 100644 --- a/src/autogenerated/java/net/minestom/server/color/DyeColor.java +++ b/src/autogenerated/java/net/minestom/server/color/DyeColor.java @@ -1,6 +1,8 @@ package net.minestom.server.color; import net.kyori.adventure.util.RGBLike; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.NotNull; /** @@ -39,6 +41,9 @@ public enum DyeColor implements RGBLike { BLACK(new Color(0x1d1d21), new Color(0x0), new Color(0x1e1b1b), 29); + public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.fromEnum(DyeColor.class); + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.fromEnumStringable(DyeColor.class); + private final Color textureDiffuseColor; private final Color textColor; diff --git a/src/autogenerated/java/net/minestom/server/item/Enchantments.java b/src/autogenerated/java/net/minestom/server/item/enchant/Enchantments.java similarity index 98% rename from src/autogenerated/java/net/minestom/server/item/Enchantments.java rename to src/autogenerated/java/net/minestom/server/item/enchant/Enchantments.java index 814705b2d..ff95445ae 100644 --- a/src/autogenerated/java/net/minestom/server/item/Enchantments.java +++ b/src/autogenerated/java/net/minestom/server/item/enchant/Enchantments.java @@ -1,4 +1,4 @@ -package net.minestom.server.item; +package net.minestom.server.item.enchant; /** * Code autogenerated, do not edit! diff --git a/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java b/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java index 6116a8692..9f0e2324e 100644 --- a/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java +++ b/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java @@ -49,7 +49,7 @@ interface PotionEffects { PotionEffect ABSORPTION = PotionEffectImpl.get("minecraft:absorption"); - PotionEffect SATURATION = PotionEffectImpl.get("minecraft:saturation"); + PotionEffect SATURATION = PotionEffectImpl.get("minecraft:saturationModifier"); PotionEffect GLOWING = PotionEffectImpl.get("minecraft:glowing"); diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentEnchantment.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentEnchantment.java index 0ca7404c1..aee915c3d 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentEnchantment.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentEnchantment.java @@ -1,6 +1,6 @@ package net.minestom.server.command.builder.arguments.minecraft.registry; -import net.minestom.server.item.Enchantment; +import net.minestom.server.item.enchant.Enchantment; import org.jetbrains.annotations.NotNull; /** diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 5cf16854f..08d5f9c5c 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1098,14 +1098,14 @@ public class Player extends LivingEntity implements CommandSender, Localizable, } /** - * Sets and refresh client food saturation. + * Sets and refresh client food saturationModifier. * - * @param foodSaturation the food saturation + * @param foodSaturation the food saturationModifier * @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 20 */ public void setFoodSaturation(float foodSaturation) { Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 20), - "Food saturation has to be between 0 and 20"); + "Food saturationModifier has to be between 0 and 20"); this.foodSaturation = foodSaturation; sendPacket(new UpdateHealthPacket(getHealth(), food, foodSaturation)); } diff --git a/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java b/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java index 3d0ca7473..1983d81cd 100644 --- a/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/EnchantmentTableInventory.java @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component; import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryProperty; import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.Enchantment; +import net.minestom.server.item.enchant.Enchantment; import org.jetbrains.annotations.NotNull; public class EnchantmentTableInventory extends ContainerInventory { diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index 5c4da54bc..3ca20ef8c 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -3,11 +3,11 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.text.Component; import net.minestom.server.color.Color; +import net.minestom.server.color.DyeColor; import net.minestom.server.item.component.*; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; -import net.minestom.server.utils.Unit; import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,10 +36,10 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); ItemComponent HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); ItemComponent REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); - ItemComponent CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING_V2, null); + ItemComponent CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING, null); ItemComponent ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN); ItemComponent INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); - ItemComponent FOOD = declare("food", null, null); //todo + ItemComponent FOOD = declare("food", Food.NETWORK_TYPE, Food.NBT_TYPE); ItemComponent FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); ItemComponent TOOL = declare("tool", null, null); //todo ItemComponent STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); @@ -50,16 +50,16 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null); ItemComponent> CHARGED_PROJECTILES = declare("charged_projectiles", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); ItemComponent> BUNDLE_CONTENTS = declare("bundle_contents", ItemStack.NETWORK_TYPE.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list()); - ItemComponent POTION_CONTENTS = declare("potion_contents", null, null); //todo - ItemComponent SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo + ItemComponent POTION_CONTENTS = declare("potion_contents", PotionContents.NETWORK_TYPE, PotionContents.NBT_TYPE); + ItemComponent SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", SuspiciousStewEffects.NETWORK_TYPE, SuspiciousStewEffects.NBT_TYPE); ItemComponent WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE); ItemComponent WRITTEN_BOOK_CONTENT = declare("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE); - ItemComponent TRIM = declare("trim", null, null); //todo - ItemComponent DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo + ItemComponent TRIM = declare("trim", ArmorTrim.NETWORK_TYPE, ArmorTrim.NBT_TYPE); + ItemComponent DEBUG_STICK_STATE = declare("debug_stick_state", null, DebugStickState.NBT_TYPE); ItemComponent ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); ItemComponent BUCKET_ENTITY_DATA = declare("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); ItemComponent BLOCK_ENTITY_DATA = declare("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); - ItemComponent INSTRUMENT = declare("instrument", null, null); //todo + ItemComponent INSTRUMENT = declare("instrument", NetworkBuffer.STRING, BinaryTagSerializer.STRING); ItemComponent OMINOUS_BOTTLE_AMPLIFIER = declare("ominous_bottle_amplifier", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); ItemComponent> RECIPES = declare("recipes", NetworkBuffer.STRING.list(Short.MAX_VALUE), BinaryTagSerializer.STRING.list()); ItemComponent LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE); @@ -68,11 +68,11 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent PROFILE = declare("profile", null, null); //todo ItemComponent NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING); ItemComponent BANNER_PATTERNS = declare("banner_patterns", null, null); //todo - ItemComponent BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity - ItemComponent POT_DECORATIONS = declare("pot_decorations", null, null); //todo + ItemComponent BASE_COLOR = declare("base_color", DyeColor.NETWORK_TYPE, DyeColor.NBT_TYPE); + ItemComponent POT_DECORATIONS = declare("pot_decorations", PotDecorations.NETWORK_TYPE, PotDecorations.NBT_TYPE); ItemComponent> CONTAINER = declare("container", ItemStack.NETWORK_TYPE.list(256), BinaryTagSerializer.ITEM.list()); - ItemComponent BLOCK_STATE = declare("block_state", null, null); //todo - ItemComponent BEES = declare("bees", null, null); //todo + ItemComponent BLOCK_STATE = declare("block_state", ItemBlockState.NETWORK_TYPE, ItemBlockState.NBT_TYPE); + ItemComponent> BEES = declare("bees", Bee.NETWORK_TYPE.list(Short.MAX_VALUE), Bee.NBT_TYPE.list()); ItemComponent LOCK = declare("lock", null, BinaryTagSerializer.STRING); ItemComponent CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE); diff --git a/src/main/java/net/minestom/server/item/Material.java b/src/main/java/net/minestom/server/item/Material.java index 6863f54a0..900a3c8b0 100644 --- a/src/main/java/net/minestom/server/item/Material.java +++ b/src/main/java/net/minestom/server/item/Material.java @@ -1,9 +1,11 @@ package net.minestom.server.item; import net.minestom.server.instance.block.Block; +import net.minestom.server.network.NetworkBuffer; import net.minestom.server.registry.Registry; import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; +import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,6 +47,9 @@ import java.util.Collection; public sealed interface Material extends StaticProtocolObject, Materials permits MaterialImpl { + NetworkBuffer.Type NETWORK_TYPE = MaterialImpl.NETWORK_TYPE; + BinaryTagSerializer NBT_TYPE = MaterialImpl.NBT_TYPE; + /** * Returns the material registry. * diff --git a/src/main/java/net/minestom/server/item/MaterialImpl.java b/src/main/java/net/minestom/server/item/MaterialImpl.java index 38c1c8236..3ec4837b2 100644 --- a/src/main/java/net/minestom/server/item/MaterialImpl.java +++ b/src/main/java/net/minestom/server/item/MaterialImpl.java @@ -1,6 +1,8 @@ package net.minestom.server.item; +import net.minestom.server.network.NetworkBuffer; import net.minestom.server.registry.Registry; +import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -9,6 +11,9 @@ record MaterialImpl(Registry.MaterialEntry registry) implements Material { private static final Registry.Container CONTAINER = Registry.createStaticContainer(Registry.Resource.ITEMS, (namespace, properties) -> new MaterialImpl(Registry.material(namespace, properties))); + static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.VAR_INT.map(MaterialImpl::getId, Material::id); + static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.STRING.map(MaterialImpl::getSafe, Material::name); + static Material get(@NotNull String namespace) { return CONTAINER.get(namespace); } diff --git a/src/main/java/net/minestom/server/item/armor/TrimMaterial.java b/src/main/java/net/minestom/server/item/armor/TrimMaterial.java index 0bf3bb3b4..57cb5044a 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimMaterial.java +++ b/src/main/java/net/minestom/server/item/armor/TrimMaterial.java @@ -8,6 +8,7 @@ import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Map; @@ -52,6 +53,14 @@ public interface TrimMaterial extends StaticProtocolObject { ); } + static @Nullable TrimMaterial fromId(int id) { + return TrimMaterialImpl.fromId(id); + } + + static @Nullable TrimMaterial fromNamespaceId(@NotNull String id) { + return TrimMaterialImpl.fromNamespaceId(id); + } + static Collection values() { return TrimMaterialImpl.values(); } diff --git a/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java b/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java index 2af492f28..cc0e62d60 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java +++ b/src/main/java/net/minestom/server/item/armor/TrimMaterialImpl.java @@ -4,6 +4,8 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.StringBinaryTag; import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer; import net.minestom.server.registry.Registry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Map; @@ -27,6 +29,14 @@ record TrimMaterialImpl(Registry.TrimMaterialEntry registry, int id) implements return CONTAINER.get(namespace); } + static @Nullable TrimMaterial fromId(int id) { + return CONTAINER.getId(id); + } + + static @Nullable TrimMaterial fromNamespaceId(@NotNull String id) { + return CONTAINER.get(id); + } + static Collection values() { return CONTAINER.values(); } diff --git a/src/main/java/net/minestom/server/item/armor/TrimPattern.java b/src/main/java/net/minestom/server/item/armor/TrimPattern.java index 7b824ba0d..30968417c 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimPattern.java +++ b/src/main/java/net/minestom/server/item/armor/TrimPattern.java @@ -8,6 +8,7 @@ import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -33,6 +34,14 @@ public interface TrimPattern extends StaticProtocolObject { ); } + static @Nullable TrimPattern fromId(int id) { + return TrimPatternImpl.fromId(id); + } + + static @Nullable TrimPattern fromNamespaceId(@NotNull String id) { + return TrimPatternImpl.fromNamespaceId(id); + } + static Collection values() { return TrimPatternImpl.values(); } diff --git a/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java b/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java index 9d4df9ed0..5de89da66 100644 --- a/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java +++ b/src/main/java/net/minestom/server/item/armor/TrimPatternImpl.java @@ -20,6 +20,14 @@ record TrimPatternImpl(Registry.TrimPatternEntry registry, int id) implements Tr this(registry, i.getAndIncrement()); } + public static TrimPattern fromId(int id) { + return CONTAINER.getId(id); + } + + public static TrimPattern fromNamespaceId(String namespace) { + return CONTAINER.getSafe(namespace); + } + public static TrimPattern get(String namespace) { return CONTAINER.get(namespace); } diff --git a/src/main/java/net/minestom/server/item/component/ArmorTrim.java b/src/main/java/net/minestom/server/item/component/ArmorTrim.java new file mode 100644 index 000000000..6b0c3afed --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/ArmorTrim.java @@ -0,0 +1,43 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.item.armor.TrimMaterial; +import net.minestom.server.item.armor.TrimPattern; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public record ArmorTrim(@NotNull TrimMaterial material, @NotNull TrimPattern pattern, boolean showInTooltip) { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, ArmorTrim value) { + buffer.write(NetworkBuffer.VAR_INT, value.material.id()); + buffer.write(NetworkBuffer.VAR_INT, value.pattern.id()); + buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip); + } + + @Override + public ArmorTrim read(@NotNull NetworkBuffer buffer) { + TrimMaterial material = Objects.requireNonNull(TrimMaterial.fromId(buffer.read(NetworkBuffer.VAR_INT)), "unknown trim material"); + TrimPattern pattern = Objects.requireNonNull(TrimPattern.fromId(buffer.read(NetworkBuffer.VAR_INT)), "unknown trim pattern"); + return new ArmorTrim(material, pattern, buffer.read(NetworkBuffer.BOOLEAN)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + TrimMaterial material = Objects.requireNonNull(TrimMaterial.fromNamespaceId(tag.getString("material")), "unknown trim material"); + TrimPattern pattern = Objects.requireNonNull(TrimPattern.fromNamespaceId(tag.getString("pattern")), "unknown trim pattern"); + boolean showInTooltip = tag.getBoolean("show_in_tooltip", true); + return new ArmorTrim(material, pattern, showInTooltip); + }, + value -> CompoundBinaryTag.builder() + .putString("material", value.material.name()) + .putString("pattern", value.pattern.name()) + .putBoolean("show_in_tooltip", value.showInTooltip) + .build() + ); +} diff --git a/src/main/java/net/minestom/server/item/component/Bee.java b/src/main/java/net/minestom/server/item/component/Bee.java new file mode 100644 index 000000000..0bc705576 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/Bee.java @@ -0,0 +1,33 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +public record Bee(@NotNull CustomData entityData, int ticksInHive, int minTicksInHive) { + + public static @NotNull NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { + @Override + public void write(@NotNull NetworkBuffer buffer, Bee value) { + buffer.write(CustomData.NETWORK_TYPE, value.entityData); + buffer.write(NetworkBuffer.VAR_INT, value.ticksInHive); + buffer.write(NetworkBuffer.VAR_INT, value.minTicksInHive); + } + + @Override + public Bee read(@NotNull NetworkBuffer buffer) { + return new Bee(buffer.read(CustomData.NETWORK_TYPE), buffer.read(NetworkBuffer.VAR_INT), buffer.read(NetworkBuffer.VAR_INT)); + } + }; + public static @NotNull BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new Bee(CustomData.NBT_TYPE.read(tag.getCompound("entity_data")), + tag.getInt("ticks_in_hive"), + tag.getInt("min_ticks_in_hive")), + value -> CompoundBinaryTag.builder() + .put("entity_data", CustomData.NBT_TYPE.write(value.entityData)) + .putInt("ticks_in_hive", value.ticksInHive) + .putInt("min_ticks_in_hive", value.minTicksInHive) + .build() + ); +} diff --git a/src/main/java/net/minestom/server/item/component/DebugStickState.java b/src/main/java/net/minestom/server/item/component/DebugStickState.java new file mode 100644 index 000000000..10ca2e0ca --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/DebugStickState.java @@ -0,0 +1,38 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public record DebugStickState(@NotNull Map state) { + public static final DebugStickState EMPTY = new DebugStickState(Map.of()); + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + Map state = new HashMap<>(); + for (Map.Entry entry : tag) { + if (!(entry.getValue() instanceof StringBinaryTag property)) + continue; + state.put(entry.getKey(), property.value()); + } + return new DebugStickState(state); + }, + state -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + for (Map.Entry entry : state.state().entrySet()) { + builder.put(entry.getKey(), StringBinaryTag.stringBinaryTag(entry.getValue())); + } + return builder.build(); + } + ); + + public DebugStickState { + state = Map.copyOf(state); + } + +} diff --git a/src/main/java/net/minestom/server/item/component/EnchantmentList.java b/src/main/java/net/minestom/server/item/component/EnchantmentList.java index ce8ee3309..461e4c01f 100644 --- a/src/main/java/net/minestom/server/item/component/EnchantmentList.java +++ b/src/main/java/net/minestom/server/item/component/EnchantmentList.java @@ -2,7 +2,7 @@ package net.minestom.server.item.component; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.minestom.server.item.Enchantment; +import net.minestom.server.item.enchant.Enchantment; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.nbt.BinaryTagSerializer; import net.minestom.server.utils.validate.Check; diff --git a/src/main/java/net/minestom/server/item/component/Food.java b/src/main/java/net/minestom/server/item/component/Food.java new file mode 100644 index 000000000..29394e3a0 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/Food.java @@ -0,0 +1,85 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.ServerFlag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.potion.CustomPotionEffect; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record Food(int nutrition, float saturationModifier, boolean canAlwaysEat, float eatSeconds, @NotNull List effects) { + public static final float DEFAULT_EAT_SECONDS = 1.6f; + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, Food value) { + buffer.write(NetworkBuffer.VAR_INT, value.nutrition); + buffer.write(NetworkBuffer.FLOAT, value.saturationModifier); + buffer.write(NetworkBuffer.BOOLEAN, value.canAlwaysEat); + buffer.write(NetworkBuffer.FLOAT, value.eatSeconds); + buffer.writeCollection(EffectChance.NETWORK_TYPE, value.effects); + } + + @Override + public Food read(@NotNull NetworkBuffer buffer) { + return new Food( + buffer.read(NetworkBuffer.VAR_INT), + buffer.read(NetworkBuffer.FLOAT), + buffer.read(NetworkBuffer.BOOLEAN), + buffer.read(NetworkBuffer.FLOAT), + buffer.readCollection(EffectChance.NETWORK_TYPE, Short.MAX_VALUE) + ); + } + }; + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new Food( + tag.getInt("nutrition"), + tag.getFloat("saturation_modifier"), + tag.getBoolean("can_always_eat"), + tag.getFloat("eat_seconds", DEFAULT_EAT_SECONDS), + EffectChance.NBT_LIST_TYPE.read(tag.getList("effects", BinaryTagTypes.COMPOUND))), + value -> CompoundBinaryTag.builder() + .putInt("nutrition", value.nutrition) + .putFloat("saturationModifier", value.saturationModifier) + .putBoolean("canAlwaysEat", value.canAlwaysEat) + .putFloat("eatSeconds", value.eatSeconds) + .put("effects", EffectChance.NBT_LIST_TYPE.write(value.effects)) + .build() + ); + + public Food { + effects = List.copyOf(effects); + } + + public int eatDurationTicks() { + return (int) (eatSeconds * ServerFlag.SERVER_TICKS_PER_SECOND); + } + + public record EffectChance(@NotNull CustomPotionEffect effect, float probability) { + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, EffectChance value) { + + } + + @Override + public EffectChance read(@NotNull NetworkBuffer buffer) { + return null; + } + }; + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new EffectChance( + CustomPotionEffect.NBT_TYPE.read(tag.getCompound("effect")), + tag.getFloat("probability", 1f)), + value -> CompoundBinaryTag.builder() + .put("effect", CustomPotionEffect.NBT_TYPE.write(value.effect())) + .putFloat("probability", value.probability) + .build() + ); + public static final BinaryTagSerializer> NBT_LIST_TYPE = NBT_TYPE.list(); + } + +} diff --git a/src/main/java/net/minestom/server/item/component/ItemBlockState.java b/src/main/java/net/minestom/server/item/component/ItemBlockState.java new file mode 100644 index 000000000..ae403be3a --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/ItemBlockState.java @@ -0,0 +1,74 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.minestom.server.instance.block.Block; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public record ItemBlockState(@NotNull Map properties) { + public static final ItemBlockState EMPTY = new ItemBlockState(Map.of()); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, ItemBlockState value) { + buffer.write(NetworkBuffer.VAR_INT, value.properties.size()); + for (Map.Entry entry : value.properties.entrySet()) { + buffer.write(NetworkBuffer.STRING, entry.getKey()); + buffer.write(NetworkBuffer.STRING, entry.getValue()); + } + } + + @Override + public ItemBlockState read(@NotNull NetworkBuffer buffer) { + int size = buffer.read(NetworkBuffer.VAR_INT); + Map properties = new HashMap<>(size); + for (int i = 0; i < size; i++) { + properties.put(buffer.read(NetworkBuffer.STRING), buffer.read(NetworkBuffer.STRING)); + } + return new ItemBlockState(properties); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + Map properties = new HashMap<>(tag.size()); + for (Map.Entry entry : tag) { + if (!(entry.getValue() instanceof StringBinaryTag str)) continue; + properties.put(entry.getKey(), str.value()); + } + return new ItemBlockState(properties); + }, + value -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + for (Map.Entry entry : value.properties.entrySet()) { + builder.put(entry.getKey(), StringBinaryTag.stringBinaryTag(entry.getValue())); + } + return builder.build(); + } + ); + + public ItemBlockState { + properties = Map.copyOf(properties); + } + + public @NotNull ItemBlockState with(@NotNull String key, @NotNull String value) { + Map newProperties = new HashMap<>(properties); + newProperties.put(key, value); + return new ItemBlockState(newProperties); + } + + public @NotNull Block apply(@NotNull Block block) { + for (Map.Entry entry : properties.entrySet()) { + if (block.getProperty(entry.getKey()) == null) + continue; // Ignore properties not present on this block + block = block.withProperty(entry.getKey(), entry.getValue()); + } + return block; + } +} diff --git a/src/main/java/net/minestom/server/item/component/ItemRarity.java b/src/main/java/net/minestom/server/item/component/ItemRarity.java index a88ae4744..16cc1a498 100644 --- a/src/main/java/net/minestom/server/item/component/ItemRarity.java +++ b/src/main/java/net/minestom/server/item/component/ItemRarity.java @@ -1,13 +1,7 @@ package net.minestom.server.item.component; -import net.kyori.adventure.nbt.BinaryTag; -import net.kyori.adventure.nbt.IntBinaryTag; -import net.kyori.adventure.nbt.StringBinaryTag; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.nbt.BinaryTagSerializer; -import org.jetbrains.annotations.NotNull; - -import java.util.Locale; public enum ItemRarity { COMMON, @@ -17,31 +11,6 @@ public enum ItemRarity { private static final ItemRarity[] VALUES = values(); - public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { - @Override - public void write(@NotNull NetworkBuffer buffer, ItemRarity value) { - buffer.writeEnum(ItemRarity.class, value); - } - - @Override - public ItemRarity read(@NotNull NetworkBuffer buffer) { - return buffer.readEnum(ItemRarity.class); - } - }; - - public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { - @Override - public @NotNull BinaryTag write(@NotNull ItemRarity value) { - return IntBinaryTag.intBinaryTag(value.ordinal()); - } - - @Override - public @NotNull ItemRarity read(@NotNull BinaryTag tag) { - return switch (tag) { - case IntBinaryTag intBinaryTag -> VALUES[intBinaryTag.value()]; - case StringBinaryTag stringBinaryTag -> valueOf(stringBinaryTag.value().toUpperCase(Locale.ROOT)); - default -> COMMON; - }; - } - }; + public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.fromEnum(ItemRarity.class); + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.fromEnumStringable(ItemRarity.class); } diff --git a/src/main/java/net/minestom/server/item/component/PotDecorations.java b/src/main/java/net/minestom/server/item/component/PotDecorations.java index 21413c76e..35fd2302a 100644 --- a/src/main/java/net/minestom/server/item/component/PotDecorations.java +++ b/src/main/java/net/minestom/server/item/component/PotDecorations.java @@ -6,35 +6,45 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.NotNull; +import java.util.List; + public record PotDecorations( @NotNull Material back, @NotNull Material left, @NotNull Material right, @NotNull Material front ) { - public static final PotDecorations EMPTY = new PotDecorations(Material.AIR, Material.AIR, Material.AIR, Material.AIR); - - public static NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { - @Override - public void write(@NotNull NetworkBuffer buffer, PotDecorations value) { + public static final @NotNull Material DEFAULT_ITEM = Material.BRICK; + public static final PotDecorations EMPTY = new PotDecorations(DEFAULT_ITEM, DEFAULT_ITEM, DEFAULT_ITEM, DEFAULT_ITEM); + public static NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { + @Override public void write(@NotNull NetworkBuffer buffer, PotDecorations value) { + Material.NETWORK_TYPE.list(4).map(PotDecorations::new, PotDecorations::asList).write(buffer, value); } - @Override - public PotDecorations read(@NotNull NetworkBuffer buffer) { - return null; + @Override public PotDecorations read(@NotNull NetworkBuffer buffer) { + return Material.NETWORK_TYPE.list(4).map(PotDecorations::new, PotDecorations::asList).read(buffer); + } + }; + public static BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer() { + @Override public @NotNull BinaryTag write(@NotNull PotDecorations value) { + return Material.NBT_TYPE.list().map(PotDecorations::new, PotDecorations::asList).write(value); + } + + @Override public @NotNull PotDecorations read(@NotNull BinaryTag tag) { + return Material.NBT_TYPE.list().map(PotDecorations::new, PotDecorations::asList).read(tag); } }; - public static BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { - @Override - public @NotNull BinaryTag write(@NotNull PotDecorations value) { - return null; - } + public PotDecorations(@NotNull List list) { + this(getOrAir(list, 0), getOrAir(list, 1), getOrAir(list, 2), getOrAir(list, 3)); + } - @Override - public @NotNull PotDecorations read(@NotNull BinaryTag tag) { - return null; - } - }; + public @NotNull List asList() { + return List.of(back, left, right, front); + } + + private static @NotNull Material getOrAir(@NotNull List list, int index) { + return index < list.size() ? list.get(index) : Material.BRICK; + } } diff --git a/src/main/java/net/minestom/server/item/component/PotionContents.java b/src/main/java/net/minestom/server/item/component/PotionContents.java new file mode 100644 index 000000000..36e04df96 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/PotionContents.java @@ -0,0 +1,83 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.*; +import net.minestom.server.color.Color; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.potion.CustomPotionEffect; +import net.minestom.server.potion.PotionEffect; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public record PotionContents( + @Nullable PotionEffect potion, + @Nullable Color customColor, + @NotNull List customEffects +) { + public static final PotionContents EMPTY = new PotionContents(null, null, List.of()); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, PotionContents value) { + buffer.writeOptional(PotionEffect.NETWORK_TYPE, value.potion); + buffer.writeOptional(NetworkBuffer.COLOR, value.customColor); + buffer.writeCollection(CustomPotionEffect.NETWORK_TYPE, value.customEffects); + } + + @Override + public PotionContents read(@NotNull NetworkBuffer buffer) { + return new PotionContents( + buffer.readOptional(PotionEffect.NETWORK_TYPE), + buffer.readOptional(NetworkBuffer.COLOR), + buffer.readCollection(CustomPotionEffect.NETWORK_TYPE, Short.MAX_VALUE) + ); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull PotionContents value) { + return null; + } + + @Override + public @NotNull PotionContents read(@NotNull BinaryTag tag) { + // Can be a string with just a potion effect id + if (tag instanceof StringBinaryTag string) { + return new PotionContents(PotionEffect.fromNamespaceId(string.value()), null, List.of()); + } + + // Otherwise must be a compound + if (!(tag instanceof CompoundBinaryTag compound)) { + return EMPTY; + } + + PotionEffect potion = null; + if (compound.get("potion") instanceof StringBinaryTag potionTag) + potion = PotionEffect.fromNamespaceId(potionTag.value()); + + Color customColor = null; + if (compound.get("custom_color") instanceof IntBinaryTag colorTag) { + customColor = new Color(colorTag.value()); + } + + List customEffects = new ArrayList<>(); + ListBinaryTag customEffectsTag = compound.getList("custom_effects", BinaryTagTypes.COMPOUND); + for (BinaryTag customEffectTag : customEffectsTag) { + if (!(customEffectTag instanceof CompoundBinaryTag customEffectCompound)) { + continue; + } + customEffects.add(CustomPotionEffect.NBT_TYPE.read(customEffectCompound)); + } + + return new PotionContents(potion, customColor, customEffects); + } + }; + + public PotionContents { + customEffects = List.copyOf(customEffects); + } +} diff --git a/src/main/java/net/minestom/server/item/component/SuspiciousStewEffects.java b/src/main/java/net/minestom/server/item/component/SuspiciousStewEffects.java new file mode 100644 index 000000000..b5027c2b2 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/SuspiciousStewEffects.java @@ -0,0 +1,49 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.potion.PotionEffect; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record SuspiciousStewEffects(@NotNull List effects) { + public static final int DEFAULT_DURATION = 160; + + public static final NetworkBuffer.Type NETWORK_TYPE = Effect.NETWORK_TYPE.list(Short.MAX_VALUE).map(SuspiciousStewEffects::new, SuspiciousStewEffects::effects); + public static final BinaryTagSerializer NBT_TYPE = Effect.NBT_TYPE.list().map(SuspiciousStewEffects::new, SuspiciousStewEffects::effects); + + public SuspiciousStewEffects { + effects = List.copyOf(effects); + } + + public record Effect(@NotNull PotionEffect id, int durationTicks) { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, Effect value) { + buffer.write(PotionEffect.NETWORK_TYPE, value.id); + buffer.write(NetworkBuffer.VAR_INT, value.durationTicks); + } + + @Override + public Effect read(@NotNull NetworkBuffer buffer) { + return new Effect(buffer.read(PotionEffect.NETWORK_TYPE), buffer.read(NetworkBuffer.VAR_INT)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new Effect(PotionEffect.fromNamespaceId(tag.getString("id")), + tag.getInt("duration", DEFAULT_DURATION)), + value -> CompoundBinaryTag.builder() + .putString("id", value.id.name()) + .putInt("duration", value.durationTicks) + .build() + ); + + public Effect(@NotNull PotionEffect id) { + this(id, DEFAULT_DURATION); + } + } +} diff --git a/src/main/java/net/minestom/server/item/Enchantment.java b/src/main/java/net/minestom/server/item/enchant/Enchantment.java similarity index 96% rename from src/main/java/net/minestom/server/item/Enchantment.java rename to src/main/java/net/minestom/server/item/enchant/Enchantment.java index 39e27ac66..8a43f9129 100644 --- a/src/main/java/net/minestom/server/item/Enchantment.java +++ b/src/main/java/net/minestom/server/item/enchant/Enchantment.java @@ -1,7 +1,7 @@ -package net.minestom.server.item; +package net.minestom.server.item.enchant; -import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/net/minestom/server/item/EnchantmentImpl.java b/src/main/java/net/minestom/server/item/enchant/EnchantmentImpl.java similarity index 95% rename from src/main/java/net/minestom/server/item/EnchantmentImpl.java rename to src/main/java/net/minestom/server/item/enchant/EnchantmentImpl.java index 4a82d4efc..295c7594a 100644 --- a/src/main/java/net/minestom/server/item/EnchantmentImpl.java +++ b/src/main/java/net/minestom/server/item/enchant/EnchantmentImpl.java @@ -1,4 +1,4 @@ -package net.minestom.server.item; +package net.minestom.server.item.enchant; import net.minestom.server.registry.Registry; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 54984f5e0..eab194532 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -19,8 +19,10 @@ import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.rule.BlockPlacementRule; import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.item.component.ItemBlockState; import net.minestom.server.network.packet.client.play.ClientPlayerBlockPlacementPacket; import net.minestom.server.network.packet.server.play.AcknowledgeBlockChangePacket; import net.minestom.server.network.packet.server.play.BlockChangePacket; @@ -139,7 +141,9 @@ public class BlockPlacementListener { return; } - final Block placedBlock = useMaterial.block(); + final ItemBlockState blockState = usedItem.get(ItemComponent.BLOCK_STATE, ItemBlockState.EMPTY); + final Block placedBlock = blockState.apply(useMaterial.block()); + Entity collisionEntity = CollisionUtils.canPlaceBlockAt(instance, placementPosition, placedBlock); if (collisionEntity != null) { // If a player is trying to place a block on themselves, the client will send a block change but will not set the block on the client diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index 9b57daa99..09d2ab544 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -80,6 +80,10 @@ public final class NetworkBuffer { public static final Type COLOR = new NetworkBufferTypeImpl.ColorType(); + public static > Type fromEnum(@NotNull Class enumClass) { + return NetworkBufferTypeImpl.fromEnum(enumClass); + } + ByteBuffer nioBuffer; final boolean resizable; int writeIndex; @@ -299,6 +303,20 @@ public final class NetworkBuffer { void write(@NotNull NetworkBuffer buffer, T value); T read(@NotNull NetworkBuffer buffer); + default @NotNull Type map(@NotNull Function to, @NotNull Function from) { + return new Type() { + @Override + public void write(@NotNull NetworkBuffer buffer, S value) { + Type.this.write(buffer, from.apply(value)); + } + + @Override + public S read(@NotNull NetworkBuffer buffer) { + return to.apply(Type.this.read(buffer)); + } + }; + } + default @NotNull Type> list(int maxSize) { return new NetworkBuffer.Type<>() { @Override diff --git a/src/main/java/net/minestom/server/potion/CustomPotionEffect.java b/src/main/java/net/minestom/server/potion/CustomPotionEffect.java index e9ecad664..82a15f851 100644 --- a/src/main/java/net/minestom/server/potion/CustomPotionEffect.java +++ b/src/main/java/net/minestom/server/potion/CustomPotionEffect.java @@ -1,9 +1,120 @@ package net.minestom.server.potion; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** - * Represents a custom effect in {@link net.minestom.server.item.metadata.PotionMeta}. + * Represents a custom effect in {@link net.minestom.server.item.ItemComponent#POTION_CONTENTS}. */ -public record CustomPotionEffect(byte id, byte amplifier, int duration, - boolean isAmbient, boolean showParticles, - boolean showIcon) { +public record CustomPotionEffect(@NotNull PotionEffect id, @NotNull Settings settings) { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, CustomPotionEffect value) { + buffer.write(PotionEffect.NETWORK_TYPE, value.id); + buffer.write(Settings.NETWORK_TYPE, value.settings); + } + + @Override + public CustomPotionEffect read(@NotNull NetworkBuffer buffer) { + return new CustomPotionEffect(buffer.read(PotionEffect.NETWORK_TYPE), buffer.read(Settings.NETWORK_TYPE)); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new CustomPotionEffect( + PotionEffect.fromNamespaceId(tag.getString("id")), + Settings.NBT_TYPE.read(tag)), + value -> CompoundBinaryTag.builder() + .putString("id", value.id.name()) + .put((CompoundBinaryTag) Settings.NBT_TYPE.write(value.settings)) + .build() + ); + + public CustomPotionEffect(@NotNull PotionEffect id, byte amplifier, int duration, boolean isAmbient, boolean showParticles, boolean showIcon) { + this(id, new Settings(amplifier, duration, isAmbient, showParticles, showIcon, null)); + } + + public byte amplifier() { + return settings.amplifier; + } + + public int duration() { + return settings.duration; + } + + public boolean isAmbient() { + return settings.isAmbient; + } + + public boolean showParticles() { + return settings.showParticles; + } + + public boolean showIcon() { + return settings.showIcon; + } + + public record Settings( + byte amplifier, int duration, + boolean isAmbient, boolean showParticles, boolean showIcon, + @Nullable Settings hiddenEffect + ) { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, Settings value) { + buffer.write(NetworkBuffer.VAR_INT, (int) value.amplifier); + buffer.write(NetworkBuffer.VAR_INT, value.duration); + buffer.write(NetworkBuffer.BOOLEAN, value.isAmbient); + buffer.write(NetworkBuffer.BOOLEAN, value.showParticles); + buffer.write(NetworkBuffer.BOOLEAN, value.showIcon); + buffer.writeOptional(NETWORK_TYPE, value.hiddenEffect); + } + + @Override + public Settings read(@NotNull NetworkBuffer buffer) { + return new Settings( + buffer.read(NetworkBuffer.VAR_INT).byteValue(), + buffer.read(NetworkBuffer.VAR_INT), + buffer.read(NetworkBuffer.BOOLEAN), + buffer.read(NetworkBuffer.BOOLEAN), + buffer.read(NetworkBuffer.BOOLEAN), + buffer.readOptional(NETWORK_TYPE) + ); + } + }; + + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.recursive(self -> BinaryTagSerializer.COMPOUND.map( + tag -> { + byte amplifier = tag.getByte("amplifier"); + int duration = tag.getInt("duration"); + boolean ambient = tag.getBoolean("ambient"); + boolean showParticles = tag.getBoolean("show_particles", true); + boolean showIcon = tag.getBoolean("show_icon", showParticles); + Settings hiddenEffect = null; + if (tag.get("hidden_effect") instanceof CompoundBinaryTag hiddenEffectTag) { + hiddenEffect = self.read(hiddenEffectTag); + } + return new Settings(amplifier, duration, ambient, showParticles, showIcon, hiddenEffect); + }, + value -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder() + .putByte("amplifier", value.amplifier) + .putInt("duration", value.duration) + .putBoolean("ambient", value.isAmbient) + .putBoolean("show_particles", value.showParticles) + .putBoolean("show_icon", value.showIcon); + if (value.hiddenEffect != null) { + builder.put("hidden_effect", self.write(value.hiddenEffect)); + } + return builder.build(); + } + )); + + } + } diff --git a/src/main/java/net/minestom/server/potion/PotionEffect.java b/src/main/java/net/minestom/server/potion/PotionEffect.java index 0fa9778ce..8bb957e0c 100644 --- a/src/main/java/net/minestom/server/potion/PotionEffect.java +++ b/src/main/java/net/minestom/server/potion/PotionEffect.java @@ -1,7 +1,8 @@ package net.minestom.server.potion; -import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.network.NetworkBuffer; import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -11,6 +12,8 @@ import java.util.Collection; public sealed interface PotionEffect extends StaticProtocolObject, PotionEffects permits PotionEffectImpl { + NetworkBuffer.Type NETWORK_TYPE = PotionEffectImpl.NETWORK_TYPE; + @Contract(pure = true) @NotNull Registry.PotionEffectEntry registry(); diff --git a/src/main/java/net/minestom/server/potion/PotionEffectImpl.java b/src/main/java/net/minestom/server/potion/PotionEffectImpl.java index 0e90e62d1..eafbd5ebf 100644 --- a/src/main/java/net/minestom/server/potion/PotionEffectImpl.java +++ b/src/main/java/net/minestom/server/potion/PotionEffectImpl.java @@ -1,5 +1,6 @@ package net.minestom.server.potion; +import net.minestom.server.network.NetworkBuffer; import net.minestom.server.registry.Registry; import org.jetbrains.annotations.NotNull; @@ -9,6 +10,8 @@ record PotionEffectImpl(Registry.PotionEffectEntry registry) implements PotionEf private static final Registry.Container CONTAINER = Registry.createStaticContainer(Registry.Resource.POTION_EFFECTS, (namespace, properties) -> new PotionEffectImpl(Registry.potionEffect(namespace, properties))); + public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.VAR_INT.map(PotionEffectImpl::getId, PotionEffect::id); + static PotionEffect get(@NotNull String namespace) { return CONTAINER.get(namespace); } diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java index 6e0fdcb14..6e0f8fd3c 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java @@ -6,12 +6,53 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; public interface BinaryTagSerializer { + static @NotNull BinaryTagSerializer recursive(@NotNull Function, BinaryTagSerializer> self) { + return new BinaryTagSerializer<>() { + private BinaryTagSerializer serializer = null; + + @Override + public @NotNull BinaryTag write(@NotNull T value) { + return serializer().write(value); + } + + @Override + public @NotNull T read(@NotNull BinaryTag tag) { + return serializer().read(tag); + } + + private BinaryTagSerializer serializer() { + if (serializer == null) serializer = self.apply(this); + return serializer; + } + }; + } + + static > @NotNull BinaryTagSerializer fromEnumStringable(@NotNull Class enumClass) { + final E[] values = enumClass.getEnumConstants(); + final Map nameMap = Arrays.stream(values).collect(Collectors.toMap(e -> e.name().toLowerCase(Locale.ROOT), Function.identity())); + return new BinaryTagSerializer() { + @Override + public @NotNull BinaryTag write(@NotNull E value) { + return StringBinaryTag.stringBinaryTag(value.name().toLowerCase(Locale.ROOT)); + } + + @Override + public @NotNull E read(@NotNull BinaryTag tag) { + return switch (tag) { + case IntBinaryTag intBinaryTag -> values[intBinaryTag.value()]; + case StringBinaryTag string -> nameMap.getOrDefault(string.value().toLowerCase(Locale.ROOT), values[0]); + default -> values[0]; + }; + } + }; + } + BinaryTagSerializer NOTHING = new BinaryTagSerializer<>() { @Override public @NotNull BinaryTag write(@NotNull Void value) { diff --git a/src/test/java/net/minestom/server/command/ArgumentTypeTest.java b/src/test/java/net/minestom/server/command/ArgumentTypeTest.java index edc08e4f4..d014694f3 100644 --- a/src/test/java/net/minestom/server/command/ArgumentTypeTest.java +++ b/src/test/java/net/minestom/server/command/ArgumentTypeTest.java @@ -12,11 +12,10 @@ import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.EntityType; import net.minestom.server.instance.block.Block; -import net.minestom.server.item.Enchantment; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.item.enchant.Enchantment; import net.minestom.server.particle.Particle; -import net.minestom.server.potion.PotionEffect; import net.minestom.server.tag.Tag; import net.minestom.server.utils.location.RelativeVec; import net.minestom.server.utils.math.FloatRange; diff --git a/src/test/java/net/minestom/server/command/CommandSyntaxSingleTest.java b/src/test/java/net/minestom/server/command/CommandSyntaxSingleTest.java index 381eb2c0c..d2baf997e 100644 --- a/src/test/java/net/minestom/server/command/CommandSyntaxSingleTest.java +++ b/src/test/java/net/minestom/server/command/CommandSyntaxSingleTest.java @@ -4,16 +4,13 @@ import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.instance.block.Block; -import net.minestom.server.item.Enchantment; +import net.minestom.server.item.enchant.Enchantment; import org.junit.jupiter.api.Test; -import java.lang.String; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import static net.minestom.server.command.builder.arguments.ArgumentType.Integer; -import static net.minestom.server.command.builder.arguments.ArgumentType.String; import static net.minestom.server.command.builder.arguments.ArgumentType.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; diff --git a/src/test/java/net/minestom/server/item/ItemEnchantTest.java b/src/test/java/net/minestom/server/item/ItemEnchantTest.java index 46f818bcb..db702681b 100644 --- a/src/test/java/net/minestom/server/item/ItemEnchantTest.java +++ b/src/test/java/net/minestom/server/item/ItemEnchantTest.java @@ -1,12 +1,10 @@ package net.minestom.server.item; +import net.minestom.server.item.enchant.Enchantment; import org.junit.jupiter.api.Test; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - public class ItemEnchantTest { @Test diff --git a/src/test/java/net/minestom/server/item/ItemTest.java b/src/test/java/net/minestom/server/item/ItemTest.java index 90e4b6c7e..2bdb517b3 100644 --- a/src/test/java/net/minestom/server/item/ItemTest.java +++ b/src/test/java/net/minestom/server/item/ItemTest.java @@ -3,6 +3,7 @@ package net.minestom.server.item; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.entity.EntityType; +import net.minestom.server.item.enchant.Enchantment; import org.jglrxavpok.hephaistos.nbt.NBT; import org.junit.jupiter.api.Test; From 4e6c15189c944e9d237a112ad4551ec54022ed80 Mon Sep 17 00:00:00 2001 From: mworzala Date: Thu, 18 Apr 2024 08:41:25 -0400 Subject: [PATCH 27/46] feat: even more components (only 2 missing ones) --- .../src/main/java/net/minestom/demo/Main.java | 1 + .../java/net/minestom/demo/PlayerInit.java | 8 +- .../minestom/demo/commands/PotionCommand.java | 23 +++ .../server/collision/BlockCollision.java | 2 +- .../net/minestom/server/entity/Player.java | 2 +- .../minestom/server/gamedata/tags/Tag.java | 17 ++- .../server/instance/ChunkGenerator.java | 38 ----- .../ChunkGeneratorCompatibilityLayer.java | 42 ------ .../server/instance/ChunkPopulator.java | 10 -- .../minestom/server/instance/Instance.java | 13 +- .../block/predicate/BlockPredicate.java | 88 ++++++++++++ .../block/predicate/BlockTypeFilter.java | 128 +++++++++++++++++ .../block/predicate/PropertiesPredicate.java | 132 ++++++++++++++++++ .../server/inventory/TransactionType.java | 2 +- .../minestom/server/item/ItemComponent.java | 6 +- .../item/component/BlockPredicates.java | 85 +++++++++++ .../minestom/server/item/component/Tool.java | 97 +++++++++++++ .../listener/BlockPlacementListener.java | 8 +- .../listener/PlayerDiggingListener.java | 8 +- .../minestom/server/registry/Registry.java | 15 +- .../server/utils/nbt/BinaryTagSerializer.java | 33 +++++ .../server/item/ItemMetaWrittenBookTest.java | 3 +- 22 files changed, 625 insertions(+), 136 deletions(-) create mode 100644 demo/src/main/java/net/minestom/demo/commands/PotionCommand.java delete mode 100644 src/main/java/net/minestom/server/instance/ChunkGenerator.java delete mode 100644 src/main/java/net/minestom/server/instance/ChunkGeneratorCompatibilityLayer.java delete mode 100644 src/main/java/net/minestom/server/instance/ChunkPopulator.java create mode 100644 src/main/java/net/minestom/server/instance/block/predicate/BlockPredicate.java create mode 100644 src/main/java/net/minestom/server/instance/block/predicate/BlockTypeFilter.java create mode 100644 src/main/java/net/minestom/server/instance/block/predicate/PropertiesPredicate.java create mode 100644 src/main/java/net/minestom/server/item/component/BlockPredicates.java create mode 100644 src/main/java/net/minestom/server/item/component/Tool.java diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 3d54eabb8..1d5d22bb6 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -82,6 +82,7 @@ public class Main { commandManager.register(new RelightCommand()); commandManager.register(new KillCommand()); commandManager.register(new WeatherCommand()); + commandManager.register(new PotionCommand()); commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED))); diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 089d3d3c5..4c6af5a52 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -26,11 +26,14 @@ import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.LightingChunk; import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.predicate.BlockPredicate; +import net.minestom.server.instance.block.predicate.BlockTypeFilter; import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.inventory.InventoryType; import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.item.component.BlockPredicates; import net.minestom.server.item.component.ItemBlockState; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.TickMonitor; @@ -102,9 +105,8 @@ public class PlayerInit { player.setPermissionLevel(4); ItemStack itemStack = ItemStack.builder(Material.STONE) .amount(64) -// .meta(itemMetaBuilder -> -// itemMetaBuilder.canPlaceOn(Set.of(Block.STONE)) -// .canDestroy(Set.of(Block.DIAMOND_ORE))) + .set(ItemComponent.CAN_PLACE_ON, new BlockPredicates(new BlockPredicate(new BlockTypeFilter.Blocks(Block.STONE), null, null))) + .set(ItemComponent.CAN_BREAK, new BlockPredicates(new BlockPredicate(new BlockTypeFilter.Blocks(Block.DIAMOND_ORE), null, null))) .build(); player.getInventory().addItemStack(itemStack); diff --git a/demo/src/main/java/net/minestom/demo/commands/PotionCommand.java b/demo/src/main/java/net/minestom/demo/commands/PotionCommand.java new file mode 100644 index 000000000..bda604f41 --- /dev/null +++ b/demo/src/main/java/net/minestom/demo/commands/PotionCommand.java @@ -0,0 +1,23 @@ +package net.minestom.demo.commands; + +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.Argument; +import net.minestom.server.command.builder.arguments.ArgumentType; +import org.jetbrains.annotations.NotNull; + +public class PotionCommand extends Command { + private final Argument potionArg = ArgumentType.Resource("potion", "minecraft:potion"); + + public PotionCommand() { + super("potion"); + + addSyntax(this::potionCommand, potionArg); + } + + private void potionCommand(@NotNull CommandSender sender, @NotNull CommandContext context) { + final String potion = context.get(potionArg); + sender.sendMessage("Potion: " + potion); + } +} diff --git a/src/main/java/net/minestom/server/collision/BlockCollision.java b/src/main/java/net/minestom/server/collision/BlockCollision.java index 3d3595421..df11fb6f6 100644 --- a/src/main/java/net/minestom/server/collision/BlockCollision.java +++ b/src/main/java/net/minestom/server/collision/BlockCollision.java @@ -298,7 +298,7 @@ final class BlockCollision { // don't fall out of if statement, we could end up redundantly grabbing a block, and we only need to // collision check against the current shape since the below shape isn't tall if (belowShape.relativeEnd().y() > 1) { - // we should always check both shapes, so no short-circuit here, to handle cases where the bounding box + // we should always check both shapes, so no short-circuit here, to handle properties where the bounding box // hits the current solid but misses the tall solid return belowShape.intersectBoxSwept(entityPosition, entityVelocity, belowPos, boundingBox, finalResult) | (currentCollidable && currentShape.intersectBoxSwept(entityPosition, entityVelocity, currentPos, boundingBox, finalResult)); diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 08d5f9c5c..cfb16705f 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1826,7 +1826,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, /** * Used to synchronize player position with viewers on spawn or after {@link Entity#teleport(Pos, long[], int)} - * in cases where a {@link PlayerPositionAndLookPacket} is required + * in properties where a {@link PlayerPositionAndLookPacket} is required * * @param position the position used by {@link PlayerPositionAndLookPacket} * this may not be the same as the {@link Entity#position} diff --git a/src/main/java/net/minestom/server/gamedata/tags/Tag.java b/src/main/java/net/minestom/server/gamedata/tags/Tag.java index 273f2be07..56ae4771b 100644 --- a/src/main/java/net/minestom/server/gamedata/tags/Tag.java +++ b/src/main/java/net/minestom/server/gamedata/tags/Tag.java @@ -3,6 +3,7 @@ package net.minestom.server.gamedata.tags; import net.minestom.server.entity.EntityType; import net.minestom.server.instance.block.Block; import net.minestom.server.item.Material; +import net.minestom.server.registry.ProtocolObject; import net.minestom.server.registry.Registries; import net.minestom.server.registry.Registry; import net.minestom.server.utils.NamespaceID; @@ -19,14 +20,14 @@ import java.util.function.Function; * Represents a group of items, blocks, fluids, entity types or function. * Immutable by design */ -public final class Tag { +public final class Tag implements ProtocolObject { private final NamespaceID name; private final Set values; /** * Creates a new empty tag. This does not cache the tag. */ - public Tag(NamespaceID name) { + public Tag(@NotNull NamespaceID name) { this.name = name; this.values = new HashSet<>(); } @@ -34,7 +35,7 @@ public final class Tag { /** * Creates a new tag with the given values. This does not cache the tag. */ - public Tag(NamespaceID name, Set values) { + public Tag(@NotNull NamespaceID name, @NotNull Set values) { this.name = name; this.values = new HashSet<>(values); } @@ -45,7 +46,7 @@ public final class Tag { * @param id the id to check against * @return 'true' iif this tag contains the given id */ - public boolean contains(NamespaceID id) { + public boolean contains(@NotNull NamespaceID id) { return values.contains(id); } @@ -54,13 +55,19 @@ public final class Tag { * * @return immutable set of values present in this tag */ - public Set getValues() { + public @NotNull Set getValues() { return Collections.unmodifiableSet(values); } + @Override + public @NotNull NamespaceID namespace() { + return name; + } + /** * Returns the name of this tag */ + @Deprecated public NamespaceID getName() { return name; } diff --git a/src/main/java/net/minestom/server/instance/ChunkGenerator.java b/src/main/java/net/minestom/server/instance/ChunkGenerator.java deleted file mode 100644 index ab8725e04..000000000 --- a/src/main/java/net/minestom/server/instance/ChunkGenerator.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.minestom.server.instance; - -import net.minestom.server.instance.batch.ChunkBatch; -import net.minestom.server.instance.block.Block; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -/** - * Responsible for the {@link Chunk} generation, can be set using {@link Instance#setChunkGenerator(ChunkGenerator)}. - *

- * Called if the instance {@link IChunkLoader} hasn't been able to load the chunk. - * @deprecated Replaced by {@link net.minestom.server.instance.generator.Generator} - */ -@Deprecated -public interface ChunkGenerator { - - /** - * Called when the blocks in the {@link Chunk} should be set using {@link ChunkBatch#setBlock(int, int, int, Block)} - * or similar. - *

- * WARNING: all positions are chunk-based (0-15). - * - * @param batch the {@link ChunkBatch} which will be flush after the generation - * @param chunkX the chunk X - * @param chunkZ the chunk Z - */ - void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ); - - /** - * Gets all the {@link ChunkPopulator} of this generator. - * - * @return a {@link List} of {@link ChunkPopulator}, can be null or empty - */ - @Nullable - List getPopulators(); -} diff --git a/src/main/java/net/minestom/server/instance/ChunkGeneratorCompatibilityLayer.java b/src/main/java/net/minestom/server/instance/ChunkGeneratorCompatibilityLayer.java deleted file mode 100644 index 06166c41d..000000000 --- a/src/main/java/net/minestom/server/instance/ChunkGeneratorCompatibilityLayer.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.minestom.server.instance; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.batch.ChunkBatch; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.generator.GenerationUnit; -import net.minestom.server.instance.generator.Generator; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * Provides full compatibility for the deprecated {@link ChunkGenerator} - */ -@SuppressWarnings("deprecation") -record ChunkGeneratorCompatibilityLayer(@NotNull ChunkGenerator chunkGenerator) implements Generator { - @Override - public void generate(@NotNull GenerationUnit unit) { - if (!(unit instanceof GeneratorImpl.UnitImpl impl) || - !(impl.modifier() instanceof GeneratorImpl.AreaModifierImpl modifier && modifier.chunk() != null)) { - throw new IllegalArgumentException("Invalid unit"); - } - - final int startY = unit.absoluteStart().blockY(); - ChunkBatch batch = new ChunkBatch() { - @Override - public void setBlock(int x, int y, int z, @NotNull Block block) { - unit.modifier().setRelative(x, y - startY, z, block); - } - }; - final Point start = unit.absoluteStart(); - chunkGenerator.generateChunkData(batch, start.chunkX(), start.chunkZ()); - - final List populators = chunkGenerator.getPopulators(); - final boolean hasPopulator = populators != null && !populators.isEmpty(); - if (hasPopulator) { - for (ChunkPopulator chunkPopulator : populators) { - chunkPopulator.populateChunk(batch, modifier.chunk()); - } - } - } -} diff --git a/src/main/java/net/minestom/server/instance/ChunkPopulator.java b/src/main/java/net/minestom/server/instance/ChunkPopulator.java deleted file mode 100644 index b6130c499..000000000 --- a/src/main/java/net/minestom/server/instance/ChunkPopulator.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.minestom.server.instance; - -import net.minestom.server.instance.batch.ChunkBatch; - -@Deprecated -public interface ChunkPopulator { - - void populateChunk(ChunkBatch batch, Chunk chunk); - -} diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 7d959aa25..6299bec25 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -205,7 +205,7 @@ public abstract class Instance implements Block.Getter, Block.Setter, public abstract boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace, boolean doBlockUpdates); /** - * Forces the generation of a {@link Chunk}, even if no file and {@link ChunkGenerator} are defined. + * Forces the generation of a {@link Chunk}, even if no file and {@link Generator} are defined. * * @param chunkX the chunk X * @param chunkZ the chunk Z @@ -318,17 +318,6 @@ public abstract class Instance implements Block.Getter, Block.Setter, */ public abstract @NotNull CompletableFuture saveChunksToStorage(); - /** - * Changes the instance {@link ChunkGenerator}. - * - * @param chunkGenerator the new {@link ChunkGenerator} of the instance - * @deprecated Use {@link #setGenerator(Generator)} - */ - @Deprecated - public void setChunkGenerator(@Nullable ChunkGenerator chunkGenerator) { - setGenerator(chunkGenerator != null ? new ChunkGeneratorCompatibilityLayer(chunkGenerator) : null); - } - public abstract void setChunkSupplier(@NotNull ChunkSupplier chunkSupplier); /** diff --git a/src/main/java/net/minestom/server/instance/block/predicate/BlockPredicate.java b/src/main/java/net/minestom/server/instance/block/predicate/BlockPredicate.java new file mode 100644 index 000000000..a78fa2e06 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/block/predicate/BlockPredicate.java @@ -0,0 +1,88 @@ +package net.minestom.server.instance.block.predicate; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.instance.block.Block; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Predicate; + +public record BlockPredicate( + @Nullable BlockTypeFilter blocks, + @Nullable PropertiesPredicate state, + @Nullable CompoundBinaryTag nbt +) implements Predicate { + /** + * Matches all blocks. + */ + public static final BlockPredicate ALL = new BlockPredicate(null, null, null); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, BlockPredicate value) { + buffer.writeOptional(BlockTypeFilter.NETWORK_TYPE, value.blocks); + buffer.writeOptional(PropertiesPredicate.NETWORK_TYPE, value.state); + buffer.writeOptional(NetworkBuffer.NBT, value.nbt); + } + + @Override + public BlockPredicate read(@NotNull NetworkBuffer buffer) { + return new BlockPredicate( + buffer.readOptional(BlockTypeFilter.NETWORK_TYPE), + buffer.readOptional(PropertiesPredicate.NETWORK_TYPE), + (CompoundBinaryTag) buffer.readOptional(NetworkBuffer.NBT) + ); + } + }; + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull BlockPredicate value) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + if (value.blocks != null) + builder.put("blocks", BlockTypeFilter.NBT_TYPE.write(value.blocks)); + if (value.state != null) + builder.put("state", PropertiesPredicate.NBT_TYPE.write(value.state)); + if (value.nbt != null) + builder.put("nbt", value.nbt); + return builder.build(); + } + + @Override + public @NotNull BlockPredicate read(@NotNull BinaryTag tag) { + if (!(tag instanceof CompoundBinaryTag compound)) return BlockPredicate.ALL; + + BinaryTag entry; + BlockTypeFilter blocks = null; + if ((entry = compound.get("blocks")) != null) + blocks = BlockTypeFilter.NBT_TYPE.read(entry); + PropertiesPredicate state = null; + if ((entry = compound.get("state")) != null) + state = PropertiesPredicate.NBT_TYPE.read(entry); + CompoundBinaryTag nbt = null; + if ((entry = compound.get("nbt")) != null) + nbt = BinaryTagSerializer.COMPOUND_COERCED.read(entry); + return new BlockPredicate(blocks, state, nbt); + } + }; + + public BlockPredicate(@NotNull BlockTypeFilter blocks) { + this(blocks, null, null); + } + + public BlockPredicate(@NotNull PropertiesPredicate state) { + this(null, state, null); + } + + public BlockPredicate(@NotNull CompoundBinaryTag nbt) { + this(null, null, nbt); + } + + @Override + public boolean test(@NotNull Block block) { + throw new UnsupportedOperationException("not implemented"); + } + +} diff --git a/src/main/java/net/minestom/server/instance/block/predicate/BlockTypeFilter.java b/src/main/java/net/minestom/server/instance/block/predicate/BlockTypeFilter.java new file mode 100644 index 000000000..acb8b1c99 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/block/predicate/BlockTypeFilter.java @@ -0,0 +1,128 @@ +package net.minestom.server.instance.block.predicate; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.minestom.server.MinecraftServer; +import net.minestom.server.gamedata.tags.TagManager; +import net.minestom.server.instance.block.Block; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +public sealed interface BlockTypeFilter extends Predicate permits BlockTypeFilter.Blocks, BlockTypeFilter.Tag { + + record Blocks(@NotNull List blocks) implements BlockTypeFilter { + public Blocks { + blocks = List.copyOf(blocks); + } + + public Blocks(@NotNull Block... blocks) { + this(List.of(blocks)); + } + + @Override + public boolean test(@NotNull Block block) { + final int blockId = block.id(); + for (Block b : blocks) { + if (blockId == b.id()) { + return true; + } + } + return false; + } + } + + record Tag(@NotNull net.minestom.server.gamedata.tags.Tag tag) implements BlockTypeFilter { + private static final TagManager TAG_MANAGER = Objects.requireNonNull(MinecraftServer.getTagManager()); + + public Tag(@NotNull String namespaceId) { + this(Objects.requireNonNull(TAG_MANAGER.getTag(net.minestom.server.gamedata.tags.Tag.BasicType.BLOCKS, namespaceId), + "No such block tag: " + namespaceId)); + } + + @Override + public boolean test(Block block) { + return tag.contains(block.namespace()); + } + } + + NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, BlockTypeFilter value) { + switch (value) { + case Blocks blocks -> { + buffer.write(NetworkBuffer.VAR_INT, blocks.blocks.size() + 1); + for (Block block : blocks.blocks) { + buffer.write(NetworkBuffer.VAR_INT, block.id()); + } + } + case Tag tag -> { + buffer.write(NetworkBuffer.VAR_INT, 0); + buffer.write(NetworkBuffer.STRING, tag.tag.name()); + } + } + } + + @Override + public BlockTypeFilter read(@NotNull NetworkBuffer buffer) { + final int count = buffer.read(NetworkBuffer.VAR_INT) - 1; + if (count == -1) { + return new Tag(buffer.read(NetworkBuffer.STRING)); + } else { + final List blocks = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + blocks.add(Block.fromBlockId(buffer.read(NetworkBuffer.VAR_INT))); + } + return new Blocks(blocks); + } + } + }; + + BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull BlockTypeFilter value) { + return switch (value) { + case Blocks blocks -> { + ListBinaryTag.Builder builder = ListBinaryTag.builder(BinaryTagTypes.STRING); + for (Block block : blocks.blocks) { + builder.add(StringBinaryTag.stringBinaryTag(block.name())); + } + yield builder.build(); + } + case Tag tag -> StringBinaryTag.stringBinaryTag("#" + tag.tag.name()); + }; + } + + @Override + public @NotNull BlockTypeFilter read(@NotNull BinaryTag tag) { + return switch (tag) { + case ListBinaryTag list -> { + final List blocks = new ArrayList<>(list.size()); + for (BinaryTag binaryTag : list) { + if (!(binaryTag instanceof StringBinaryTag string)) continue; + blocks.add(Objects.requireNonNull(Block.fromNamespaceId(string.value()))); + } + yield new Blocks(blocks); + } + case StringBinaryTag string -> { + // Could be a tag or a block name depending if it starts with a # + final String value = string.value(); + if (value.startsWith("#")) { + yield new Tag(value.substring(1)); + } else { + yield new Blocks(Objects.requireNonNull(Block.fromNamespaceId(value))); + } + } + default -> throw new IllegalArgumentException("Invalid tag type: " + tag.type()); + }; + } + }; + +} diff --git a/src/main/java/net/minestom/server/instance/block/predicate/PropertiesPredicate.java b/src/main/java/net/minestom/server/instance/block/predicate/PropertiesPredicate.java new file mode 100644 index 000000000..3c3716910 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/block/predicate/PropertiesPredicate.java @@ -0,0 +1,132 @@ +package net.minestom.server.instance.block.predicate; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public record PropertiesPredicate(@NotNull Map properties) { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, PropertiesPredicate value) { + buffer.write(NetworkBuffer.VAR_INT, value.properties.size()); + for (Map.Entry entry : value.properties.entrySet()) { + buffer.write(NetworkBuffer.STRING, entry.getKey()); + buffer.write(ValuePredicate.NETWORK_TYPE, entry.getValue()); + } + } + + @Override + public PropertiesPredicate read(@NotNull NetworkBuffer buffer) { + int size = buffer.read(NetworkBuffer.VAR_INT); + Map properties = new HashMap<>(size); + for (int i = 0; i < size; i++) { + properties.put(buffer.read(NetworkBuffer.STRING), buffer.read(ValuePredicate.NETWORK_TYPE)); + } + return new PropertiesPredicate(properties); + } + }; + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> { + Map properties = new HashMap<>(); + for (Map.Entry entry : tag) { + properties.put(entry.getKey(), ValuePredicate.NBT_TYPE.read(entry.getValue())); + } + return new PropertiesPredicate(properties); + }, + value -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + for (Map.Entry entry : value.properties.entrySet()) { + builder.put(entry.getKey(), ValuePredicate.NBT_TYPE.write(entry.getValue())); + } + return builder.build(); + } + ); + + public PropertiesPredicate { + properties = Map.copyOf(properties); + } + + public sealed interface ValuePredicate permits ValuePredicate.Exact, ValuePredicate.Range { + + record Exact(@Nullable String value) implements ValuePredicate { + + public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.STRING.map(Exact::new, Exact::value); + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.STRING.map(Exact::new, Exact::value); + + } + + record Range(@Nullable String min, @Nullable String max) implements ValuePredicate { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, Range value) { + buffer.writeOptional(NetworkBuffer.STRING, value.min); + buffer.writeOptional(NetworkBuffer.STRING, value.max); + } + + @Override + public Range read(@NotNull NetworkBuffer buffer) { + return new Range(buffer.readOptional(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.STRING)); + } + }; + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new Range( + tag.get("min") instanceof StringBinaryTag string ? string.value() : null, + tag.get("max") instanceof StringBinaryTag string ? string.value() : null), + value -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + if (value.min != null) builder.putString("min", value.min); + if (value.max != null) builder.putString("max", value.max); + return builder.build(); + } + ); + } + + NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, ValuePredicate value) { + switch (value) { + case Exact exact -> { + buffer.write(NetworkBuffer.BOOLEAN, true); + buffer.write(Exact.NETWORK_TYPE, exact); + } + case Range range -> { + buffer.write(NetworkBuffer.BOOLEAN, false); + buffer.write(Range.NETWORK_TYPE, range); + } + } + } + + @Override + public ValuePredicate read(@NotNull NetworkBuffer buffer) { + return buffer.read(NetworkBuffer.BOOLEAN) ? buffer.read(Exact.NETWORK_TYPE) : buffer.read(Range.NETWORK_TYPE); + } + }; + BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull ValuePredicate value) { + return switch (value) { + case Exact exact -> Exact.NBT_TYPE.write(exact); + case Range range -> Range.NBT_TYPE.write(range); + }; + } + + @Override + public @NotNull ValuePredicate read(@NotNull BinaryTag tag) { + if (tag instanceof StringBinaryTag) { + return Exact.NBT_TYPE.read(tag); + } else { + return Range.NBT_TYPE.read(tag); + } + } + }; + } +} diff --git a/src/main/java/net/minestom/server/inventory/TransactionType.java b/src/main/java/net/minestom/server/inventory/TransactionType.java index 44f14a342..3c553ecfa 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionType.java +++ b/src/main/java/net/minestom/server/inventory/TransactionType.java @@ -35,7 +35,7 @@ public interface TransactionType extends BiFunction<@NotNull ItemStack, @NotNull /** * Joins two transaction types consecutively. - * This will use the same getter in both cases, so ensure that any potential overlap between the transaction types + * This will use the same getter in both properties, so ensure that any potential overlap between the transaction types * will not result in unexpected behaviour (e.g. item duping). */ static @NotNull TransactionType join(@NotNull TransactionType first, @NotNull TransactionType second) { diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index 3ca20ef8c..182d38c70 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -29,8 +29,8 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent> LORE = declare("lore", NetworkBuffer.COMPONENT.list(256), BinaryTagSerializer.JSON_COMPONENT.list()); ItemComponent RARITY = declare("rarity", ItemRarity.NETWORK_TYPE, ItemRarity.NBT_TYPE); ItemComponent ENCHANTMENTS = declare("enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); - ItemComponent CAN_PLACE_ON = declare("can_place_on", null, null); //todo - ItemComponent CAN_BREAK = declare("can_break", null, null); //todo + ItemComponent CAN_PLACE_ON = declare("can_place_on", BlockPredicates.NETWORK_TYPE, BlockPredicates.NBT_TYPE); + ItemComponent CAN_BREAK = declare("can_break", BlockPredicates.NETWORK_TYPE, BlockPredicates.NBT_TYPE); ItemComponent ATTRIBUTE_MODIFIERS = declare("attribute_modifiers", AttributeList.NETWORK_TYPE, AttributeList.NBT_TYPE); ItemComponent CUSTOM_MODEL_DATA = declare("custom_model_data", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); ItemComponent HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); @@ -41,7 +41,7 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING); ItemComponent FOOD = declare("food", Food.NETWORK_TYPE, Food.NBT_TYPE); ItemComponent FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING); - ItemComponent TOOL = declare("tool", null, null); //todo + ItemComponent TOOL = declare("tool", Tool.NETWORK_TYPE, Tool.NBT_TYPE); ItemComponent STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE); ItemComponent DYED_COLOR = declare("dyed_color", DyedItemColor.NETWORK_TYPE, DyedItemColor.NBT_TYPE); ItemComponent MAP_COLOR = declare("map_color", NetworkBuffer.COLOR, BinaryTagSerializer.INT.map(Color::new, Color::asRGB)); diff --git a/src/main/java/net/minestom/server/item/component/BlockPredicates.java b/src/main/java/net/minestom/server/item/component/BlockPredicates.java new file mode 100644 index 000000000..73a2f7132 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/BlockPredicates.java @@ -0,0 +1,85 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.predicate.BlockPredicate; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.function.Predicate; + +public record BlockPredicates(@NotNull List predicates, boolean showInTooltip) implements Predicate { + /** + * Will never match any block. + */ + public static final BlockPredicates NEVER = new BlockPredicates(List.of(), false); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + private static final NetworkBuffer.Type> PREDICATE_LIST_TYPE = BlockPredicate.NETWORK_TYPE.list(Short.MAX_VALUE); + + @Override + public void write(@NotNull NetworkBuffer buffer, BlockPredicates value) { + buffer.write(PREDICATE_LIST_TYPE, value.predicates); + buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip); + } + + @Override + public BlockPredicates read(@NotNull NetworkBuffer buffer) { + return new BlockPredicates(buffer.read(PREDICATE_LIST_TYPE), buffer.read(NetworkBuffer.BOOLEAN)); + } + }; + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + private static final BinaryTagSerializer> PREDICATES_LIST_TYPE = BlockPredicate.NBT_TYPE.list(); + + @Override + public @NotNull BinaryTag write(@NotNull BlockPredicates value) { + return CompoundBinaryTag.builder() + .put("predicates", PREDICATES_LIST_TYPE.write(value.predicates)) + .putBoolean("show_in_tooltip", value.showInTooltip) + .build(); + } + + @Override + public @NotNull BlockPredicates read(@NotNull BinaryTag tag) { + if (!(tag instanceof CompoundBinaryTag compound)) return BlockPredicates.NEVER; + + List predicates; + BinaryTag predicatesTag = compound.get("predicates"); + if (predicatesTag != null) { + predicates = PREDICATES_LIST_TYPE.read(predicatesTag); + } else { + // Try to read as a single predicate + predicates = List.of(BlockPredicate.NBT_TYPE.read(tag)); + } + + // This default is fine in either case because the single predicate shouldnt have this key anyway. + boolean showInTooltip = compound.getBoolean("show_in_tooltip", true); + return new BlockPredicates(predicates, showInTooltip); + } + }; + + public BlockPredicates { + predicates = List.copyOf(predicates); + } + + public BlockPredicates(@NotNull BlockPredicate predicate) { + this(List.of(predicate), true); + } + + public BlockPredicates(@NotNull BlockPredicate predicate, boolean showInTooltip) { + this(List.of(predicate), showInTooltip); + } + + @Override + public boolean test(Block block) { + for (BlockPredicate predicate : predicates) { + if (predicate.test(block)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/net/minestom/server/item/component/Tool.java b/src/main/java/net/minestom/server/item/component/Tool.java new file mode 100644 index 000000000..756571972 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/Tool.java @@ -0,0 +1,97 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.ByteBinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.FloatBinaryTag; +import net.minestom.server.instance.block.predicate.BlockTypeFilter; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public record Tool(@NotNull List rules, float defaultMiningSpeed, int damagePerBlock) { + public static final float DEFAULT_MINING_SPEED = 1.0f; + public static final int DEFAULT_DAMAGE_PER_BLOCK = 1; + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + private static final NetworkBuffer.Type> RULE_LIST_TYPE = Rule.NETWORK_TYPE.list(Short.MAX_VALUE); + + @Override + public void write(@NotNull NetworkBuffer buffer, Tool value) { + RULE_LIST_TYPE.write(buffer, value.rules()); + buffer.write(NetworkBuffer.FLOAT, value.defaultMiningSpeed()); + buffer.write(NetworkBuffer.VAR_INT, value.damagePerBlock()); + } + + @Override + public Tool read(@NotNull NetworkBuffer buffer) { + return new Tool( + RULE_LIST_TYPE.read(buffer), + buffer.read(NetworkBuffer.FLOAT), + buffer.read(NetworkBuffer.VAR_INT) + ); + } + }; + public static final BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer<>() { + private static final BinaryTagSerializer> RULE_LIST_TYPE = Rule.NBT_TYPE.list(); + + @Override + public @NotNull BinaryTag write(@NotNull Tool value) { + return CompoundBinaryTag.builder() + .put("rules", RULE_LIST_TYPE.write(value.rules())) + .putFloat("default_mining_speed", value.defaultMiningSpeed()) + .putInt("damage_per_block", value.damagePerBlock()) + .build(); + } + + @Override + public @NotNull Tool read(@NotNull BinaryTag tag) { + if (!(tag instanceof CompoundBinaryTag compound)) + throw new IllegalArgumentException("Expected a compound tag, got " + tag.type()); + return new Tool( + RULE_LIST_TYPE.read(Objects.requireNonNull(compound.get("rules"))), + compound.getFloat("default_mining_speed", DEFAULT_MINING_SPEED), + compound.getInt("damage_per_block", DEFAULT_DAMAGE_PER_BLOCK) + ); + } + }; + + public record Rule(@NotNull BlockTypeFilter blocks, @Nullable Float speed, @Nullable Boolean correctForDrops) { + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, Rule value) { + buffer.write(BlockTypeFilter.NETWORK_TYPE, value.blocks()); + buffer.writeOptional(NetworkBuffer.FLOAT, value.speed()); + buffer.writeOptional(NetworkBuffer.BOOLEAN, value.correctForDrops()); + } + + @Override + public Rule read(@NotNull NetworkBuffer buffer) { + return new Rule( + buffer.read(BlockTypeFilter.NETWORK_TYPE), + buffer.readOptional(NetworkBuffer.FLOAT), + buffer.readOptional(NetworkBuffer.BOOLEAN) + ); + } + }; + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new Rule( + BlockTypeFilter.NBT_TYPE.read(Objects.requireNonNull(tag.get("blocks"))), + tag.get("speed") instanceof FloatBinaryTag speed ? speed.floatValue() : null, + tag.get("correctForDrops") instanceof ByteBinaryTag correctForDrops ? correctForDrops.value() != 0 : null + ), + rule -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + builder.put("blocks", BlockTypeFilter.NBT_TYPE.write(rule.blocks())); + if (rule.speed() != null) builder.putFloat("speed", rule.speed()); + if (rule.correctForDrops() != null) builder.putBoolean("correct_for_drops", rule.correctForDrops()); + return builder.build(); + } + ); + } +} diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index eab194532..8ae86fcfd 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -22,6 +22,7 @@ import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.item.component.BlockPredicates; import net.minestom.server.item.component.ItemBlockState; import net.minestom.server.network.packet.client.play.ClientPlayerBlockPlacementPacket; import net.minestom.server.network.packet.server.play.AcknowledgeBlockChangePacket; @@ -89,9 +90,8 @@ public class BlockPlacementListener { canPlaceBlock = false; // Spectators can't place blocks } else if (player.getGameMode() == GameMode.ADVENTURE) { //Check if the block can be placed on the block -// canPlaceBlock = usedItem.meta().canPlaceOn(interactedBlock); - canPlaceBlock = false; - //todo + BlockPredicates placePredicate = usedItem.get(ItemComponent.CAN_PLACE_ON, BlockPredicates.NEVER); + canPlaceBlock = placePredicate.test(interactedBlock); } @@ -149,7 +149,7 @@ public class BlockPlacementListener { // If a player is trying to place a block on themselves, the client will send a block change but will not set the block on the client // For this reason, the block doesn't need to be updated for the client - // Client also doesn't predict placement of blocks on entities, but we need to refresh for cases where bounding boxes on the server don't match the client + // Client also doesn't predict placement of blocks on entities, but we need to refresh for properties where bounding boxes on the server don't match the client if (collisionEntity != player) refresh(player, chunk); diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index d5a5ece37..9bf3984fe 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -15,7 +15,9 @@ import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockFace; import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemComponent; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.component.BlockPredicates; import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket; import net.minestom.server.network.packet.server.play.AcknowledgeBlockChangePacket; import net.minestom.server.network.packet.server.play.BlockEntityDataPacket; @@ -115,10 +117,8 @@ public final class PlayerDiggingListener { } else if (player.getGameMode() == GameMode.ADVENTURE) { // Check if the item can break the block with the current item final ItemStack itemInMainHand = player.getItemInMainHand(); - //todo -// if (!itemInMainHand.meta().canDestroy(block)) { -// return true; -// } + final BlockPredicates breakPredicate = itemInMainHand.get(ItemComponent.CAN_BREAK, BlockPredicates.NEVER); + return !breakPredicate.test(block); } return false; } diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 31d37b2c3..ae1de1f43 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -542,17 +542,12 @@ public final class Registry { try { ItemComponentMap.Builder builder = ItemComponentMap.builder(); for (Map.Entry entry : main.section("components")) { - try { - //noinspection unchecked - ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); - Check.notNull(component, "Unknown component: " + entry.getKey()); + //noinspection unchecked + ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); + Check.notNull(component, "Unknown component: " + entry.getKey()); - BinaryTag tag = TagStringIOExt.readTag((String) entry.getValue()); - builder.set(component, component.read(tag)); - //todo remove this try/catch, just so i dont need to impl all comps yet - } catch (NullPointerException e) { - System.out.println(e.getMessage()); - } + BinaryTag tag = TagStringIOExt.readTag((String) entry.getValue()); + builder.set(component, component.read(tag)); } this.prototype = builder.build(); } catch (IOException e) { diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java index 6e0f8fd3c..2f3d612ad 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java @@ -6,6 +6,7 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.item.ItemStack; import org.jetbrains.annotations.NotNull; +import java.io.IOException; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -33,6 +34,37 @@ public interface BinaryTagSerializer { }; } + static @NotNull BinaryTagSerializer coerced(@NotNull BinaryTagType type) { + return new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(@NotNull T value) { + return value; + } + + @Override + public @NotNull T read(@NotNull BinaryTag tag) { + if (tag.type() == type) { + //noinspection unchecked + return (T) tag; + } + + if (tag instanceof StringBinaryTag string) { + try { + tag = TagStringIOExt.readTag(string.value()); + if (tag.type() == type) { + //noinspection unchecked + return (T) tag; + } + } catch (IOException e) { + // Ignored, we'll throw a more useful exception below + } + } + + throw new IllegalArgumentException("Expected " + type + " but got " + tag); + } + }; + } + static > @NotNull BinaryTagSerializer fromEnumStringable(@NotNull Class enumClass) { final E[] values = enumClass.getEnumConstants(); final Map nameMap = Arrays.stream(values).collect(Collectors.toMap(e -> e.name().toLowerCase(Locale.ROOT), Function.identity())); @@ -114,6 +146,7 @@ public interface BinaryTagSerializer { return tag instanceof CompoundBinaryTag compoundBinaryTag ? compoundBinaryTag : CompoundBinaryTag.empty(); } }; + BinaryTagSerializer COMPOUND_COERCED = coerced(BinaryTagTypes.COMPOUND); BinaryTagSerializer JSON_COMPONENT = STRING.map( s -> GsonComponentSerializer.gson().deserialize(s), diff --git a/src/test/java/net/minestom/server/item/ItemMetaWrittenBookTest.java b/src/test/java/net/minestom/server/item/ItemMetaWrittenBookTest.java index f8eb46b2f..0878637dd 100644 --- a/src/test/java/net/minestom/server/item/ItemMetaWrittenBookTest.java +++ b/src/test/java/net/minestom/server/item/ItemMetaWrittenBookTest.java @@ -15,7 +15,6 @@ import java.io.StringReader; import java.util.ArrayList; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; public class ItemMetaWrittenBookTest { @@ -150,7 +149,7 @@ public class ItemMetaWrittenBookTest { } } - // TODO: Compare to vanilla snbt. This depends on a modern serializer that defaults to legacy in some cases. + // TODO: Compare to vanilla snbt. This depends on a modern serializer that defaults to legacy in some properties. // @Test // public void compareToVanilla() { // String vanillaSNBT = """ From f9c835ed6c732a45f70d8b2818f2613c3d3a809c Mon Sep 17 00:00:00 2001 From: mworzala Date: Thu, 18 Apr 2024 10:51:19 -0400 Subject: [PATCH 28/46] feat: first draft of cookies --- .../src/main/java/net/minestom/demo/Main.java | 1 + .../minestom/demo/commands/CookieCommand.java | 63 +++++++++++++++++++ .../minestom/server/item/ItemComponent.java | 1 + .../listener/common/CookieListener.java | 14 +++++ .../manager/PacketListenerManager.java | 8 +-- .../network/player/PlayerConnection.java | 27 ++++++++ .../minestom/server/registry/Registry.java | 2 +- 7 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 demo/src/main/java/net/minestom/demo/commands/CookieCommand.java create mode 100644 src/main/java/net/minestom/server/listener/common/CookieListener.java diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 1d5d22bb6..c5915e408 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -83,6 +83,7 @@ public class Main { commandManager.register(new KillCommand()); commandManager.register(new WeatherCommand()); commandManager.register(new PotionCommand()); + commandManager.register(new CookieCommand()); commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED))); diff --git a/demo/src/main/java/net/minestom/demo/commands/CookieCommand.java b/demo/src/main/java/net/minestom/demo/commands/CookieCommand.java new file mode 100644 index 000000000..eb71677a3 --- /dev/null +++ b/demo/src/main/java/net/minestom/demo/commands/CookieCommand.java @@ -0,0 +1,63 @@ +package net.minestom.demo.commands; + +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.Argument; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class CookieCommand extends Command { + public CookieCommand() { + super("cookie"); + + addSubcommand(new Store()); + addSubcommand(new Fetch()); + } + + public static class Store extends Command { + private final Argument keyArg = ArgumentType.ResourceLocation("key"); + private final Argument valueArg = ArgumentType.StringArray("value"); + + public Store() { + super("store"); + + addSyntax(this::store, keyArg, valueArg); + } + + private void store(@NotNull CommandSender sender, @NotNull CommandContext context) { + if (!(sender instanceof Player player)) return; + + String key = context.get(keyArg); + byte[] value = String.join(" ", context.get(valueArg)).getBytes(); + + player.getPlayerConnection().storeCookie(key, value); + player.sendMessage(key + " stored"); + } + } + + public static class Fetch extends Command { + private final Argument keyArg = ArgumentType.ResourceLocation("key"); + + public Fetch() { + super("fetch"); + + addSyntax(this::fetch, keyArg); + } + + private void fetch(@NotNull CommandSender sender, @NotNull CommandContext context) { + if (!(sender instanceof Player player)) return; + + String key = context.get(keyArg); + + player.getPlayerConnection().fetchCookie(key).thenAccept(value -> { + if (value == null) { + player.sendMessage(key + ": null"); + } else { + player.sendMessage(key + ": " + new String(value)); + } + }); + } + } +} diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index 182d38c70..631d25521 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -18,6 +18,7 @@ import static net.minestom.server.item.ItemComponentImpl.declare; public sealed interface ItemComponent extends StaticProtocolObject permits ItemComponentImpl { // Note that even non-networked components are declared here as they still contribute to the component ID counter. + // The order in this file determines the component protocol IDs, so it is important to match the client. ItemComponent CUSTOM_DATA = declare("custom_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE); ItemComponent MAX_STACK_SIZE = declare("max_stack_size", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT); diff --git a/src/main/java/net/minestom/server/listener/common/CookieListener.java b/src/main/java/net/minestom/server/listener/common/CookieListener.java new file mode 100644 index 000000000..c1fccd5ae --- /dev/null +++ b/src/main/java/net/minestom/server/listener/common/CookieListener.java @@ -0,0 +1,14 @@ +package net.minestom.server.listener.common; + +import net.minestom.server.network.packet.client.common.ClientCookieResponsePacket; +import net.minestom.server.network.player.PlayerConnection; +import org.jetbrains.annotations.NotNull; + +public final class CookieListener { + + public static void handleCookieResponse(@NotNull ClientCookieResponsePacket packet, @NotNull PlayerConnection connection) { + connection.receiveCookieResponse(packet.key(), packet.value()); + } + + private CookieListener() {} +} diff --git a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java index 5e15289ed..740f6a5ff 100644 --- a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java +++ b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java @@ -4,10 +4,7 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.player.PlayerPacketEvent; import net.minestom.server.listener.*; -import net.minestom.server.listener.common.KeepAliveListener; -import net.minestom.server.listener.common.PluginMessageListener; -import net.minestom.server.listener.common.ResourcePackListener; -import net.minestom.server.listener.common.SettingsListener; +import net.minestom.server.listener.common.*; import net.minestom.server.listener.preplay.ConfigListener; import net.minestom.server.listener.preplay.HandshakeListener; import net.minestom.server.listener.preplay.LoginListener; @@ -51,6 +48,7 @@ public final class PacketListenerManager { setListener(ConnectionState.LOGIN, ClientEncryptionResponsePacket.class, LoginListener::loginEncryptionResponseListener); setListener(ConnectionState.LOGIN, ClientLoginPluginResponsePacket.class, LoginListener::loginPluginResponseListener); setListener(ConnectionState.LOGIN, ClientLoginAcknowledgedPacket.class, LoginListener::loginAckListener); + setListener(ConnectionState.LOGIN, ClientCookieResponsePacket.class, CookieListener::handleCookieResponse); setConfigurationListener(ClientSettingsPacket.class, SettingsListener::listener); setConfigurationListener(ClientPluginMessagePacket.class, PluginMessageListener::listener); @@ -58,6 +56,7 @@ public final class PacketListenerManager { setConfigurationListener(ClientPongPacket.class, (packet, player) -> {/* empty */}); setConfigurationListener(ClientResourcePackStatusPacket.class, ResourcePackListener::listener); setConfigurationListener(ClientFinishConfigurationPacket.class, ConfigListener::finishConfigListener); + setListener(ConnectionState.CONFIGURATION, ClientCookieResponsePacket.class, CookieListener::handleCookieResponse); setPlayListener(ClientKeepAlivePacket.class, KeepAliveListener::listener); setPlayListener(ClientCommandChatPacket.class, ChatMessageListener::commandChatListener); @@ -96,6 +95,7 @@ public final class PacketListenerManager { setPlayListener(ClientChatSessionUpdatePacket.class, (packet, player) -> {/* empty */}); setPlayListener(ClientChunkBatchReceivedPacket.class, ChunkBatchListener::batchReceivedListener); setPlayListener(ClientPingRequestPacket.class, PlayPingListener::requestListener); + setListener(ConnectionState.PLAY, ClientCookieResponsePacket.class, CookieListener::handleCookieResponse); } /** diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index 4d2dc7e7b..b7daf0cca 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -6,7 +6,10 @@ import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.server.SendablePacket; +import net.minestom.server.network.packet.server.common.CookieRequestPacket; +import net.minestom.server.network.packet.server.common.CookieStorePacket; import net.minestom.server.network.plugin.LoginPluginMessageProcessor; +import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,7 +17,10 @@ import org.jetbrains.annotations.Nullable; import java.net.SocketAddress; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; /** * A PlayerConnection is an object needed for all created {@link Player}. @@ -28,6 +34,8 @@ public abstract class PlayerConnection { private LoginPluginMessageProcessor loginPluginMessageProcessor = new LoginPluginMessageProcessor(this); + private final Map> pendingCookieRequests = new ConcurrentHashMap<>(); + public PlayerConnection() { this.online = true; this.connectionState = ConnectionState.HANDSHAKE; @@ -168,6 +176,25 @@ public abstract class PlayerConnection { this.playerPublicKey = playerPublicKey; } + public void storeCookie(@NotNull String key, byte @NotNull [] data) { + sendPacket(new CookieStorePacket(key, data)); + } + + public CompletableFuture fetchCookie(@NotNull String key) { + CompletableFuture future = new CompletableFuture<>(); + pendingCookieRequests.put(NamespaceID.from(key), future); + sendPacket(new CookieRequestPacket(key)); + return future; + } + + @ApiStatus.Internal + public void receiveCookieResponse(@NotNull String key, byte @Nullable [] data) { + CompletableFuture future = pendingCookieRequests.remove(NamespaceID.from(key)); + if (future != null) { + future.complete(data); + } + } + /** * Gets the login plugin message processor, only available during the login state. */ diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index ae1de1f43..2c908aaea 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -544,7 +544,7 @@ public final class Registry { for (Map.Entry entry : main.section("components")) { //noinspection unchecked ItemComponent component = (ItemComponent) ItemComponent.fromNamespaceId(entry.getKey()); - Check.notNull(component, "Unknown component: " + entry.getKey()); + Check.notNull(component, "Unknown component {0} in {1}", entry.getKey(), namespace); BinaryTag tag = TagStringIOExt.readTag((String) entry.getValue()); builder.set(component, component.read(tag)); From 49f8ae1223f8f301a12cf8cd8a484efb4faed682 Mon Sep 17 00:00:00 2001 From: mworzala Date: Fri, 19 Apr 2024 07:53:32 -0400 Subject: [PATCH 29/46] feat: simplify sound events, fix update explosion packet --- .../java/net/minestom/codegen/Generators.java | 2 +- .../codegen/color/DyeColorGenerator.java | 11 + .../java/net/minestom/demo/PlayerInit.java | 24 + .../net/minestom/server/color/DyeColor.java | 1 + .../minestom/server/potion/PotionEffects.java | 2 +- .../minestom/server/sound/SoundEvents.java | 3214 ++++++++--------- .../adventure/AdventurePacketConvertor.java | 27 +- .../minestom/server/entity/LivingEntity.java | 3 +- .../net/minestom/server/instance/Chunk.java | 9 +- .../server/instance/IChunkLoader.java | 2 +- .../minestom/server/item/ItemComponent.java | 2 +- .../server/item/component/HeadProfile.java | 87 + .../server/network/NetworkBuffer.java | 19 + .../server/play/EntitySoundEffectPacket.java | 59 +- .../packet/server/play/ExplosionPacket.java | 20 +- .../packet/server/play/SoundEffectPacket.java | 61 +- .../server/sound/BuiltinSoundEvent.java | 58 + .../server/sound/CustomSoundEvent.java | 8 + .../net/minestom/server/sound/SoundEvent.java | 48 +- .../minestom/server/sound/SoundEventImpl.java | 33 - .../server/utils/nbt/BinaryTagSerializer.java | 16 + 21 files changed, 1913 insertions(+), 1793 deletions(-) create mode 100644 src/main/java/net/minestom/server/item/component/HeadProfile.java create mode 100644 src/main/java/net/minestom/server/sound/BuiltinSoundEvent.java create mode 100644 src/main/java/net/minestom/server/sound/CustomSoundEvent.java delete mode 100644 src/main/java/net/minestom/server/sound/SoundEventImpl.java diff --git a/code-generators/src/main/java/net/minestom/codegen/Generators.java b/code-generators/src/main/java/net/minestom/codegen/Generators.java index 47f2e8c6b..f9bf04092 100644 --- a/code-generators/src/main/java/net/minestom/codegen/Generators.java +++ b/code-generators/src/main/java/net/minestom/codegen/Generators.java @@ -30,7 +30,7 @@ public class Generators { generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffects"); generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypes"); generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "Particles"); - generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "SoundEventImpl", "SoundEvents"); + generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "BuiltinSoundEvent", "SoundEvents"); generator.generate(resource("custom_statistics.json"), "net.minestom.server.statistic", "StatisticType", "StatisticTypeImpl", "StatisticTypes"); generator.generate(resource("damage_types.json"), "net.minestom.server.entity.damage", "DamageType", "DamageTypeImpl", "DamageTypes"); generator.generate(resource("trim_materials.json"), "net.minestom.server.item.armor", "TrimMaterial", "TrimMaterialImpl", "TrimMaterials"); diff --git a/code-generators/src/main/java/net/minestom/codegen/color/DyeColorGenerator.java b/code-generators/src/main/java/net/minestom/codegen/color/DyeColorGenerator.java index 6e2721a27..dce68d71a 100644 --- a/code-generators/src/main/java/net/minestom/codegen/color/DyeColorGenerator.java +++ b/code-generators/src/main/java/net/minestom/codegen/color/DyeColorGenerator.java @@ -49,9 +49,20 @@ public class DyeColorGenerator extends MinestomCodeGenerator { .addSuperinterface(ClassName.get("net.kyori.adventure.util", "RGBLike")) .addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName()); + ClassName networkBufferCN = ClassName.get("net.minestom.server.network", "NetworkBuffer"); + ParameterizedTypeName networkBufferTypeCN = ParameterizedTypeName.get(networkBufferCN.nestedClass("Type"), dyeColorCN); + ClassName binaryTagSerializerCN = ClassName.get("net.minestom.server.utils.nbt", "BinaryTagSerializer"); + ParameterizedTypeName binaryTagSerializerTypeCN = ParameterizedTypeName.get(binaryTagSerializerCN, dyeColorCN); + // Fields dyeColorEnum.addFields( List.of( + FieldSpec.builder(networkBufferTypeCN, "NETWORK_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("$T.fromEnum($T.class)", networkBufferCN, dyeColorCN) + .build(), + FieldSpec.builder(binaryTagSerializerTypeCN, "NBT_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("$T.fromEnumStringable($T.class)", binaryTagSerializerCN, dyeColorCN) + .build(), FieldSpec.builder(colorCN, "textureDiffuseColor", Modifier.PRIVATE, Modifier.FINAL).build(), FieldSpec.builder(colorCN, "textColor", Modifier.PRIVATE, Modifier.FINAL).build(), FieldSpec.builder(colorCN, "fireworkColor", Modifier.PRIVATE, Modifier.FINAL).build(), diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 4c6af5a52..fb8714b03 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -7,6 +7,7 @@ import net.minestom.server.advancements.notifications.Notification; import net.minestom.server.advancements.notifications.NotificationCenter; import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.adventure.audience.Audiences; +import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; @@ -37,9 +38,16 @@ import net.minestom.server.item.component.BlockPredicates; import net.minestom.server.item.component.ItemBlockState; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.TickMonitor; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.play.ExplosionPacket; +import net.minestom.server.particle.Particle; +import net.minestom.server.particle.data.BlockParticleData; +import net.minestom.server.sound.SoundEvent; import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; +import org.jetbrains.annotations.NotNull; import java.time.Duration; import java.util.List; @@ -87,6 +95,8 @@ public class PlayerInit { itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5)); Vec velocity = playerPos.direction().mul(6); itemEntity.setVelocity(velocity); + + player.sendPacket(makeExplosion(playerPos, velocity)); }) .addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername())) .addListener(AsyncPlayerConfigurationEvent.class, event -> { @@ -164,6 +174,20 @@ public class PlayerInit { event.getInstance().setBlock(event.getBlockPosition(), block); }); + private static final byte[] AIR_BLOCK_PARTICLE = NetworkBuffer.makeArray(new BlockParticleData(Block.AIR)::write); + + private static @NotNull ExplosionPacket makeExplosion(@NotNull Point position, @NotNull Vec motion) { + return new ExplosionPacket( + position.x(), position.y(), position.z(), + 0, new byte[0], + (float) motion.x(), (float) motion.y(), (float) motion.z(), + ExplosionPacket.BlockInteraction.KEEP, + Particle.BLOCK.id(), AIR_BLOCK_PARTICLE, + Particle.BLOCK.id(), AIR_BLOCK_PARTICLE, + SoundEvent.of(NamespaceID.from("not.a.real.sound"), 0f) + ); + } + static { InstanceManager instanceManager = MinecraftServer.getInstanceManager(); diff --git a/src/autogenerated/java/net/minestom/server/color/DyeColor.java b/src/autogenerated/java/net/minestom/server/color/DyeColor.java index d3de026e4..9b668face 100644 --- a/src/autogenerated/java/net/minestom/server/color/DyeColor.java +++ b/src/autogenerated/java/net/minestom/server/color/DyeColor.java @@ -42,6 +42,7 @@ public enum DyeColor implements RGBLike { BLACK(new Color(0x1d1d21), new Color(0x0), new Color(0x1e1b1b), 29); public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.fromEnum(DyeColor.class); + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.fromEnumStringable(DyeColor.class); private final Color textureDiffuseColor; diff --git a/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java b/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java index 9f0e2324e..6116a8692 100644 --- a/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java +++ b/src/autogenerated/java/net/minestom/server/potion/PotionEffects.java @@ -49,7 +49,7 @@ interface PotionEffects { PotionEffect ABSORPTION = PotionEffectImpl.get("minecraft:absorption"); - PotionEffect SATURATION = PotionEffectImpl.get("minecraft:saturationModifier"); + PotionEffect SATURATION = PotionEffectImpl.get("minecraft:saturation"); PotionEffect GLOWING = PotionEffectImpl.get("minecraft:glowing"); diff --git a/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java b/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java index 61da7da29..0947fdba6 100644 --- a/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java +++ b/src/autogenerated/java/net/minestom/server/sound/SoundEvents.java @@ -5,3217 +5,3217 @@ package net.minestom.server.sound; */ @SuppressWarnings("unused") interface SoundEvents { - SoundEvent ENTITY_ALLAY_AMBIENT_WITH_ITEM = SoundEventImpl.get("minecraft:entity.allay.ambient_with_item"); + SoundEvent ENTITY_ALLAY_AMBIENT_WITH_ITEM = BuiltinSoundEvent.get("minecraft:entity.allay.ambient_with_item"); - SoundEvent ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM = SoundEventImpl.get("minecraft:entity.allay.ambient_without_item"); + SoundEvent ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM = BuiltinSoundEvent.get("minecraft:entity.allay.ambient_without_item"); - SoundEvent ENTITY_ALLAY_DEATH = SoundEventImpl.get("minecraft:entity.allay.death"); + SoundEvent ENTITY_ALLAY_DEATH = BuiltinSoundEvent.get("minecraft:entity.allay.death"); - SoundEvent ENTITY_ALLAY_HURT = SoundEventImpl.get("minecraft:entity.allay.hurt"); + SoundEvent ENTITY_ALLAY_HURT = BuiltinSoundEvent.get("minecraft:entity.allay.hurt"); - SoundEvent ENTITY_ALLAY_ITEM_GIVEN = SoundEventImpl.get("minecraft:entity.allay.item_given"); + SoundEvent ENTITY_ALLAY_ITEM_GIVEN = BuiltinSoundEvent.get("minecraft:entity.allay.item_given"); - SoundEvent ENTITY_ALLAY_ITEM_TAKEN = SoundEventImpl.get("minecraft:entity.allay.item_taken"); + SoundEvent ENTITY_ALLAY_ITEM_TAKEN = BuiltinSoundEvent.get("minecraft:entity.allay.item_taken"); - SoundEvent ENTITY_ALLAY_ITEM_THROWN = SoundEventImpl.get("minecraft:entity.allay.item_thrown"); + SoundEvent ENTITY_ALLAY_ITEM_THROWN = BuiltinSoundEvent.get("minecraft:entity.allay.item_thrown"); - SoundEvent AMBIENT_CAVE = SoundEventImpl.get("minecraft:ambient.cave"); + SoundEvent AMBIENT_CAVE = BuiltinSoundEvent.get("minecraft:ambient.cave"); - SoundEvent AMBIENT_BASALT_DELTAS_ADDITIONS = SoundEventImpl.get("minecraft:ambient.basalt_deltas.additions"); + SoundEvent AMBIENT_BASALT_DELTAS_ADDITIONS = BuiltinSoundEvent.get("minecraft:ambient.basalt_deltas.additions"); - SoundEvent AMBIENT_BASALT_DELTAS_LOOP = SoundEventImpl.get("minecraft:ambient.basalt_deltas.loop"); + SoundEvent AMBIENT_BASALT_DELTAS_LOOP = BuiltinSoundEvent.get("minecraft:ambient.basalt_deltas.loop"); - SoundEvent AMBIENT_BASALT_DELTAS_MOOD = SoundEventImpl.get("minecraft:ambient.basalt_deltas.mood"); + SoundEvent AMBIENT_BASALT_DELTAS_MOOD = BuiltinSoundEvent.get("minecraft:ambient.basalt_deltas.mood"); - SoundEvent AMBIENT_CRIMSON_FOREST_ADDITIONS = SoundEventImpl.get("minecraft:ambient.crimson_forest.additions"); + SoundEvent AMBIENT_CRIMSON_FOREST_ADDITIONS = BuiltinSoundEvent.get("minecraft:ambient.crimson_forest.additions"); - SoundEvent AMBIENT_CRIMSON_FOREST_LOOP = SoundEventImpl.get("minecraft:ambient.crimson_forest.loop"); + SoundEvent AMBIENT_CRIMSON_FOREST_LOOP = BuiltinSoundEvent.get("minecraft:ambient.crimson_forest.loop"); - SoundEvent AMBIENT_CRIMSON_FOREST_MOOD = SoundEventImpl.get("minecraft:ambient.crimson_forest.mood"); + SoundEvent AMBIENT_CRIMSON_FOREST_MOOD = BuiltinSoundEvent.get("minecraft:ambient.crimson_forest.mood"); - SoundEvent AMBIENT_NETHER_WASTES_ADDITIONS = SoundEventImpl.get("minecraft:ambient.nether_wastes.additions"); + SoundEvent AMBIENT_NETHER_WASTES_ADDITIONS = BuiltinSoundEvent.get("minecraft:ambient.nether_wastes.additions"); - SoundEvent AMBIENT_NETHER_WASTES_LOOP = SoundEventImpl.get("minecraft:ambient.nether_wastes.loop"); + SoundEvent AMBIENT_NETHER_WASTES_LOOP = BuiltinSoundEvent.get("minecraft:ambient.nether_wastes.loop"); - SoundEvent AMBIENT_NETHER_WASTES_MOOD = SoundEventImpl.get("minecraft:ambient.nether_wastes.mood"); + SoundEvent AMBIENT_NETHER_WASTES_MOOD = BuiltinSoundEvent.get("minecraft:ambient.nether_wastes.mood"); - SoundEvent AMBIENT_SOUL_SAND_VALLEY_ADDITIONS = SoundEventImpl.get("minecraft:ambient.soul_sand_valley.additions"); + SoundEvent AMBIENT_SOUL_SAND_VALLEY_ADDITIONS = BuiltinSoundEvent.get("minecraft:ambient.soul_sand_valley.additions"); - SoundEvent AMBIENT_SOUL_SAND_VALLEY_LOOP = SoundEventImpl.get("minecraft:ambient.soul_sand_valley.loop"); + SoundEvent AMBIENT_SOUL_SAND_VALLEY_LOOP = BuiltinSoundEvent.get("minecraft:ambient.soul_sand_valley.loop"); - SoundEvent AMBIENT_SOUL_SAND_VALLEY_MOOD = SoundEventImpl.get("minecraft:ambient.soul_sand_valley.mood"); + SoundEvent AMBIENT_SOUL_SAND_VALLEY_MOOD = BuiltinSoundEvent.get("minecraft:ambient.soul_sand_valley.mood"); - SoundEvent AMBIENT_WARPED_FOREST_ADDITIONS = SoundEventImpl.get("minecraft:ambient.warped_forest.additions"); + SoundEvent AMBIENT_WARPED_FOREST_ADDITIONS = BuiltinSoundEvent.get("minecraft:ambient.warped_forest.additions"); - SoundEvent AMBIENT_WARPED_FOREST_LOOP = SoundEventImpl.get("minecraft:ambient.warped_forest.loop"); + SoundEvent AMBIENT_WARPED_FOREST_LOOP = BuiltinSoundEvent.get("minecraft:ambient.warped_forest.loop"); - SoundEvent AMBIENT_WARPED_FOREST_MOOD = SoundEventImpl.get("minecraft:ambient.warped_forest.mood"); + SoundEvent AMBIENT_WARPED_FOREST_MOOD = BuiltinSoundEvent.get("minecraft:ambient.warped_forest.mood"); - SoundEvent AMBIENT_UNDERWATER_ENTER = SoundEventImpl.get("minecraft:ambient.underwater.enter"); + SoundEvent AMBIENT_UNDERWATER_ENTER = BuiltinSoundEvent.get("minecraft:ambient.underwater.enter"); - SoundEvent AMBIENT_UNDERWATER_EXIT = SoundEventImpl.get("minecraft:ambient.underwater.exit"); + SoundEvent AMBIENT_UNDERWATER_EXIT = BuiltinSoundEvent.get("minecraft:ambient.underwater.exit"); - SoundEvent AMBIENT_UNDERWATER_LOOP = SoundEventImpl.get("minecraft:ambient.underwater.loop"); + SoundEvent AMBIENT_UNDERWATER_LOOP = BuiltinSoundEvent.get("minecraft:ambient.underwater.loop"); - SoundEvent AMBIENT_UNDERWATER_LOOP_ADDITIONS = SoundEventImpl.get("minecraft:ambient.underwater.loop.additions"); + SoundEvent AMBIENT_UNDERWATER_LOOP_ADDITIONS = BuiltinSoundEvent.get("minecraft:ambient.underwater.loop.additions"); - SoundEvent AMBIENT_UNDERWATER_LOOP_ADDITIONS_RARE = SoundEventImpl.get("minecraft:ambient.underwater.loop.additions.rare"); + SoundEvent AMBIENT_UNDERWATER_LOOP_ADDITIONS_RARE = BuiltinSoundEvent.get("minecraft:ambient.underwater.loop.additions.rare"); - SoundEvent AMBIENT_UNDERWATER_LOOP_ADDITIONS_ULTRA_RARE = SoundEventImpl.get("minecraft:ambient.underwater.loop.additions.ultra_rare"); + SoundEvent AMBIENT_UNDERWATER_LOOP_ADDITIONS_ULTRA_RARE = BuiltinSoundEvent.get("minecraft:ambient.underwater.loop.additions.ultra_rare"); - SoundEvent BLOCK_AMETHYST_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.amethyst_block.break"); + SoundEvent BLOCK_AMETHYST_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.amethyst_block.break"); - SoundEvent BLOCK_AMETHYST_BLOCK_CHIME = SoundEventImpl.get("minecraft:block.amethyst_block.chime"); + SoundEvent BLOCK_AMETHYST_BLOCK_CHIME = BuiltinSoundEvent.get("minecraft:block.amethyst_block.chime"); - SoundEvent BLOCK_AMETHYST_BLOCK_FALL = SoundEventImpl.get("minecraft:block.amethyst_block.fall"); + SoundEvent BLOCK_AMETHYST_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.amethyst_block.fall"); - SoundEvent BLOCK_AMETHYST_BLOCK_HIT = SoundEventImpl.get("minecraft:block.amethyst_block.hit"); + SoundEvent BLOCK_AMETHYST_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.amethyst_block.hit"); - SoundEvent BLOCK_AMETHYST_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.amethyst_block.place"); + SoundEvent BLOCK_AMETHYST_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.amethyst_block.place"); - SoundEvent BLOCK_AMETHYST_BLOCK_RESONATE = SoundEventImpl.get("minecraft:block.amethyst_block.resonate"); + SoundEvent BLOCK_AMETHYST_BLOCK_RESONATE = BuiltinSoundEvent.get("minecraft:block.amethyst_block.resonate"); - SoundEvent BLOCK_AMETHYST_BLOCK_STEP = SoundEventImpl.get("minecraft:block.amethyst_block.step"); + SoundEvent BLOCK_AMETHYST_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.amethyst_block.step"); - SoundEvent BLOCK_AMETHYST_CLUSTER_BREAK = SoundEventImpl.get("minecraft:block.amethyst_cluster.break"); + SoundEvent BLOCK_AMETHYST_CLUSTER_BREAK = BuiltinSoundEvent.get("minecraft:block.amethyst_cluster.break"); - SoundEvent BLOCK_AMETHYST_CLUSTER_FALL = SoundEventImpl.get("minecraft:block.amethyst_cluster.fall"); + SoundEvent BLOCK_AMETHYST_CLUSTER_FALL = BuiltinSoundEvent.get("minecraft:block.amethyst_cluster.fall"); - SoundEvent BLOCK_AMETHYST_CLUSTER_HIT = SoundEventImpl.get("minecraft:block.amethyst_cluster.hit"); + SoundEvent BLOCK_AMETHYST_CLUSTER_HIT = BuiltinSoundEvent.get("minecraft:block.amethyst_cluster.hit"); - SoundEvent BLOCK_AMETHYST_CLUSTER_PLACE = SoundEventImpl.get("minecraft:block.amethyst_cluster.place"); + SoundEvent BLOCK_AMETHYST_CLUSTER_PLACE = BuiltinSoundEvent.get("minecraft:block.amethyst_cluster.place"); - SoundEvent BLOCK_AMETHYST_CLUSTER_STEP = SoundEventImpl.get("minecraft:block.amethyst_cluster.step"); + SoundEvent BLOCK_AMETHYST_CLUSTER_STEP = BuiltinSoundEvent.get("minecraft:block.amethyst_cluster.step"); - SoundEvent BLOCK_ANCIENT_DEBRIS_BREAK = SoundEventImpl.get("minecraft:block.ancient_debris.break"); + SoundEvent BLOCK_ANCIENT_DEBRIS_BREAK = BuiltinSoundEvent.get("minecraft:block.ancient_debris.break"); - SoundEvent BLOCK_ANCIENT_DEBRIS_STEP = SoundEventImpl.get("minecraft:block.ancient_debris.step"); + SoundEvent BLOCK_ANCIENT_DEBRIS_STEP = BuiltinSoundEvent.get("minecraft:block.ancient_debris.step"); - SoundEvent BLOCK_ANCIENT_DEBRIS_PLACE = SoundEventImpl.get("minecraft:block.ancient_debris.place"); + SoundEvent BLOCK_ANCIENT_DEBRIS_PLACE = BuiltinSoundEvent.get("minecraft:block.ancient_debris.place"); - SoundEvent BLOCK_ANCIENT_DEBRIS_HIT = SoundEventImpl.get("minecraft:block.ancient_debris.hit"); + SoundEvent BLOCK_ANCIENT_DEBRIS_HIT = BuiltinSoundEvent.get("minecraft:block.ancient_debris.hit"); - SoundEvent BLOCK_ANCIENT_DEBRIS_FALL = SoundEventImpl.get("minecraft:block.ancient_debris.fall"); + SoundEvent BLOCK_ANCIENT_DEBRIS_FALL = BuiltinSoundEvent.get("minecraft:block.ancient_debris.fall"); - SoundEvent BLOCK_ANVIL_BREAK = SoundEventImpl.get("minecraft:block.anvil.break"); + SoundEvent BLOCK_ANVIL_BREAK = BuiltinSoundEvent.get("minecraft:block.anvil.break"); - SoundEvent BLOCK_ANVIL_DESTROY = SoundEventImpl.get("minecraft:block.anvil.destroy"); + SoundEvent BLOCK_ANVIL_DESTROY = BuiltinSoundEvent.get("minecraft:block.anvil.destroy"); - SoundEvent BLOCK_ANVIL_FALL = SoundEventImpl.get("minecraft:block.anvil.fall"); + SoundEvent BLOCK_ANVIL_FALL = BuiltinSoundEvent.get("minecraft:block.anvil.fall"); - SoundEvent BLOCK_ANVIL_HIT = SoundEventImpl.get("minecraft:block.anvil.hit"); + SoundEvent BLOCK_ANVIL_HIT = BuiltinSoundEvent.get("minecraft:block.anvil.hit"); - SoundEvent BLOCK_ANVIL_LAND = SoundEventImpl.get("minecraft:block.anvil.land"); + SoundEvent BLOCK_ANVIL_LAND = BuiltinSoundEvent.get("minecraft:block.anvil.land"); - SoundEvent BLOCK_ANVIL_PLACE = SoundEventImpl.get("minecraft:block.anvil.place"); + SoundEvent BLOCK_ANVIL_PLACE = BuiltinSoundEvent.get("minecraft:block.anvil.place"); - SoundEvent BLOCK_ANVIL_STEP = SoundEventImpl.get("minecraft:block.anvil.step"); + SoundEvent BLOCK_ANVIL_STEP = BuiltinSoundEvent.get("minecraft:block.anvil.step"); - SoundEvent BLOCK_ANVIL_USE = SoundEventImpl.get("minecraft:block.anvil.use"); + SoundEvent BLOCK_ANVIL_USE = BuiltinSoundEvent.get("minecraft:block.anvil.use"); - SoundEvent ENTITY_ARMADILLO_EAT = SoundEventImpl.get("minecraft:entity.armadillo.eat"); + SoundEvent ENTITY_ARMADILLO_EAT = BuiltinSoundEvent.get("minecraft:entity.armadillo.eat"); - SoundEvent ENTITY_ARMADILLO_HURT = SoundEventImpl.get("minecraft:entity.armadillo.hurt"); + SoundEvent ENTITY_ARMADILLO_HURT = BuiltinSoundEvent.get("minecraft:entity.armadillo.hurt"); - SoundEvent ENTITY_ARMADILLO_HURT_REDUCED = SoundEventImpl.get("minecraft:entity.armadillo.hurt_reduced"); + SoundEvent ENTITY_ARMADILLO_HURT_REDUCED = BuiltinSoundEvent.get("minecraft:entity.armadillo.hurt_reduced"); - SoundEvent ENTITY_ARMADILLO_AMBIENT = SoundEventImpl.get("minecraft:entity.armadillo.ambient"); + SoundEvent ENTITY_ARMADILLO_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.armadillo.ambient"); - SoundEvent ENTITY_ARMADILLO_STEP = SoundEventImpl.get("minecraft:entity.armadillo.step"); + SoundEvent ENTITY_ARMADILLO_STEP = BuiltinSoundEvent.get("minecraft:entity.armadillo.step"); - SoundEvent ENTITY_ARMADILLO_DEATH = SoundEventImpl.get("minecraft:entity.armadillo.death"); + SoundEvent ENTITY_ARMADILLO_DEATH = BuiltinSoundEvent.get("minecraft:entity.armadillo.death"); - SoundEvent ENTITY_ARMADILLO_ROLL = SoundEventImpl.get("minecraft:entity.armadillo.roll"); + SoundEvent ENTITY_ARMADILLO_ROLL = BuiltinSoundEvent.get("minecraft:entity.armadillo.roll"); - SoundEvent ENTITY_ARMADILLO_LAND = SoundEventImpl.get("minecraft:entity.armadillo.land"); + SoundEvent ENTITY_ARMADILLO_LAND = BuiltinSoundEvent.get("minecraft:entity.armadillo.land"); - SoundEvent ENTITY_ARMADILLO_SCUTE_DROP = SoundEventImpl.get("minecraft:entity.armadillo.scute_drop"); + SoundEvent ENTITY_ARMADILLO_SCUTE_DROP = BuiltinSoundEvent.get("minecraft:entity.armadillo.scute_drop"); - SoundEvent ENTITY_ARMADILLO_UNROLL_FINISH = SoundEventImpl.get("minecraft:entity.armadillo.unroll_finish"); + SoundEvent ENTITY_ARMADILLO_UNROLL_FINISH = BuiltinSoundEvent.get("minecraft:entity.armadillo.unroll_finish"); - SoundEvent ENTITY_ARMADILLO_PEEK = SoundEventImpl.get("minecraft:entity.armadillo.peek"); + SoundEvent ENTITY_ARMADILLO_PEEK = BuiltinSoundEvent.get("minecraft:entity.armadillo.peek"); - SoundEvent ENTITY_ARMADILLO_UNROLL_START = SoundEventImpl.get("minecraft:entity.armadillo.unroll_start"); + SoundEvent ENTITY_ARMADILLO_UNROLL_START = BuiltinSoundEvent.get("minecraft:entity.armadillo.unroll_start"); - SoundEvent ENTITY_ARMADILLO_BRUSH = SoundEventImpl.get("minecraft:entity.armadillo.brush"); + SoundEvent ENTITY_ARMADILLO_BRUSH = BuiltinSoundEvent.get("minecraft:entity.armadillo.brush"); - SoundEvent ITEM_ARMOR_EQUIP_CHAIN = SoundEventImpl.get("minecraft:item.armor.equip_chain"); + SoundEvent ITEM_ARMOR_EQUIP_CHAIN = BuiltinSoundEvent.get("minecraft:item.armor.equip_chain"); - SoundEvent ITEM_ARMOR_EQUIP_DIAMOND = SoundEventImpl.get("minecraft:item.armor.equip_diamond"); + SoundEvent ITEM_ARMOR_EQUIP_DIAMOND = BuiltinSoundEvent.get("minecraft:item.armor.equip_diamond"); - SoundEvent ITEM_ARMOR_EQUIP_ELYTRA = SoundEventImpl.get("minecraft:item.armor.equip_elytra"); + SoundEvent ITEM_ARMOR_EQUIP_ELYTRA = BuiltinSoundEvent.get("minecraft:item.armor.equip_elytra"); - SoundEvent ITEM_ARMOR_EQUIP_GENERIC = SoundEventImpl.get("minecraft:item.armor.equip_generic"); + SoundEvent ITEM_ARMOR_EQUIP_GENERIC = BuiltinSoundEvent.get("minecraft:item.armor.equip_generic"); - SoundEvent ITEM_ARMOR_EQUIP_GOLD = SoundEventImpl.get("minecraft:item.armor.equip_gold"); + SoundEvent ITEM_ARMOR_EQUIP_GOLD = BuiltinSoundEvent.get("minecraft:item.armor.equip_gold"); - SoundEvent ITEM_ARMOR_EQUIP_IRON = SoundEventImpl.get("minecraft:item.armor.equip_iron"); + SoundEvent ITEM_ARMOR_EQUIP_IRON = BuiltinSoundEvent.get("minecraft:item.armor.equip_iron"); - SoundEvent ITEM_ARMOR_EQUIP_LEATHER = SoundEventImpl.get("minecraft:item.armor.equip_leather"); + SoundEvent ITEM_ARMOR_EQUIP_LEATHER = BuiltinSoundEvent.get("minecraft:item.armor.equip_leather"); - SoundEvent ITEM_ARMOR_EQUIP_NETHERITE = SoundEventImpl.get("minecraft:item.armor.equip_netherite"); + SoundEvent ITEM_ARMOR_EQUIP_NETHERITE = BuiltinSoundEvent.get("minecraft:item.armor.equip_netherite"); - SoundEvent ITEM_ARMOR_EQUIP_TURTLE = SoundEventImpl.get("minecraft:item.armor.equip_turtle"); + SoundEvent ITEM_ARMOR_EQUIP_TURTLE = BuiltinSoundEvent.get("minecraft:item.armor.equip_turtle"); - SoundEvent ITEM_ARMOR_EQUIP_WOLF = SoundEventImpl.get("minecraft:item.armor.equip_wolf"); + SoundEvent ITEM_ARMOR_EQUIP_WOLF = BuiltinSoundEvent.get("minecraft:item.armor.equip_wolf"); - SoundEvent ITEM_ARMOR_UNEQUIP_WOLF = SoundEventImpl.get("minecraft:item.armor.unequip_wolf"); + SoundEvent ITEM_ARMOR_UNEQUIP_WOLF = BuiltinSoundEvent.get("minecraft:item.armor.unequip_wolf"); - SoundEvent ENTITY_ARMOR_STAND_BREAK = SoundEventImpl.get("minecraft:entity.armor_stand.break"); + SoundEvent ENTITY_ARMOR_STAND_BREAK = BuiltinSoundEvent.get("minecraft:entity.armor_stand.break"); - SoundEvent ENTITY_ARMOR_STAND_FALL = SoundEventImpl.get("minecraft:entity.armor_stand.fall"); + SoundEvent ENTITY_ARMOR_STAND_FALL = BuiltinSoundEvent.get("minecraft:entity.armor_stand.fall"); - SoundEvent ENTITY_ARMOR_STAND_HIT = SoundEventImpl.get("minecraft:entity.armor_stand.hit"); + SoundEvent ENTITY_ARMOR_STAND_HIT = BuiltinSoundEvent.get("minecraft:entity.armor_stand.hit"); - SoundEvent ENTITY_ARMOR_STAND_PLACE = SoundEventImpl.get("minecraft:entity.armor_stand.place"); + SoundEvent ENTITY_ARMOR_STAND_PLACE = BuiltinSoundEvent.get("minecraft:entity.armor_stand.place"); - SoundEvent ENTITY_ARROW_HIT = SoundEventImpl.get("minecraft:entity.arrow.hit"); + SoundEvent ENTITY_ARROW_HIT = BuiltinSoundEvent.get("minecraft:entity.arrow.hit"); - SoundEvent ENTITY_ARROW_HIT_PLAYER = SoundEventImpl.get("minecraft:entity.arrow.hit_player"); + SoundEvent ENTITY_ARROW_HIT_PLAYER = BuiltinSoundEvent.get("minecraft:entity.arrow.hit_player"); - SoundEvent ENTITY_ARROW_SHOOT = SoundEventImpl.get("minecraft:entity.arrow.shoot"); + SoundEvent ENTITY_ARROW_SHOOT = BuiltinSoundEvent.get("minecraft:entity.arrow.shoot"); - SoundEvent ITEM_AXE_STRIP = SoundEventImpl.get("minecraft:item.axe.strip"); + SoundEvent ITEM_AXE_STRIP = BuiltinSoundEvent.get("minecraft:item.axe.strip"); - SoundEvent ITEM_AXE_SCRAPE = SoundEventImpl.get("minecraft:item.axe.scrape"); + SoundEvent ITEM_AXE_SCRAPE = BuiltinSoundEvent.get("minecraft:item.axe.scrape"); - SoundEvent ITEM_AXE_WAX_OFF = SoundEventImpl.get("minecraft:item.axe.wax_off"); + SoundEvent ITEM_AXE_WAX_OFF = BuiltinSoundEvent.get("minecraft:item.axe.wax_off"); - SoundEvent ENTITY_AXOLOTL_ATTACK = SoundEventImpl.get("minecraft:entity.axolotl.attack"); + SoundEvent ENTITY_AXOLOTL_ATTACK = BuiltinSoundEvent.get("minecraft:entity.axolotl.attack"); - SoundEvent ENTITY_AXOLOTL_DEATH = SoundEventImpl.get("minecraft:entity.axolotl.death"); + SoundEvent ENTITY_AXOLOTL_DEATH = BuiltinSoundEvent.get("minecraft:entity.axolotl.death"); - SoundEvent ENTITY_AXOLOTL_HURT = SoundEventImpl.get("minecraft:entity.axolotl.hurt"); + SoundEvent ENTITY_AXOLOTL_HURT = BuiltinSoundEvent.get("minecraft:entity.axolotl.hurt"); - SoundEvent ENTITY_AXOLOTL_IDLE_AIR = SoundEventImpl.get("minecraft:entity.axolotl.idle_air"); + SoundEvent ENTITY_AXOLOTL_IDLE_AIR = BuiltinSoundEvent.get("minecraft:entity.axolotl.idle_air"); - SoundEvent ENTITY_AXOLOTL_IDLE_WATER = SoundEventImpl.get("minecraft:entity.axolotl.idle_water"); + SoundEvent ENTITY_AXOLOTL_IDLE_WATER = BuiltinSoundEvent.get("minecraft:entity.axolotl.idle_water"); - SoundEvent ENTITY_AXOLOTL_SPLASH = SoundEventImpl.get("minecraft:entity.axolotl.splash"); + SoundEvent ENTITY_AXOLOTL_SPLASH = BuiltinSoundEvent.get("minecraft:entity.axolotl.splash"); - SoundEvent ENTITY_AXOLOTL_SWIM = SoundEventImpl.get("minecraft:entity.axolotl.swim"); + SoundEvent ENTITY_AXOLOTL_SWIM = BuiltinSoundEvent.get("minecraft:entity.axolotl.swim"); - SoundEvent BLOCK_AZALEA_BREAK = SoundEventImpl.get("minecraft:block.azalea.break"); + SoundEvent BLOCK_AZALEA_BREAK = BuiltinSoundEvent.get("minecraft:block.azalea.break"); - SoundEvent BLOCK_AZALEA_FALL = SoundEventImpl.get("minecraft:block.azalea.fall"); + SoundEvent BLOCK_AZALEA_FALL = BuiltinSoundEvent.get("minecraft:block.azalea.fall"); - SoundEvent BLOCK_AZALEA_HIT = SoundEventImpl.get("minecraft:block.azalea.hit"); + SoundEvent BLOCK_AZALEA_HIT = BuiltinSoundEvent.get("minecraft:block.azalea.hit"); - SoundEvent BLOCK_AZALEA_PLACE = SoundEventImpl.get("minecraft:block.azalea.place"); + SoundEvent BLOCK_AZALEA_PLACE = BuiltinSoundEvent.get("minecraft:block.azalea.place"); - SoundEvent BLOCK_AZALEA_STEP = SoundEventImpl.get("minecraft:block.azalea.step"); + SoundEvent BLOCK_AZALEA_STEP = BuiltinSoundEvent.get("minecraft:block.azalea.step"); - SoundEvent BLOCK_AZALEA_LEAVES_BREAK = SoundEventImpl.get("minecraft:block.azalea_leaves.break"); + SoundEvent BLOCK_AZALEA_LEAVES_BREAK = BuiltinSoundEvent.get("minecraft:block.azalea_leaves.break"); - SoundEvent BLOCK_AZALEA_LEAVES_FALL = SoundEventImpl.get("minecraft:block.azalea_leaves.fall"); + SoundEvent BLOCK_AZALEA_LEAVES_FALL = BuiltinSoundEvent.get("minecraft:block.azalea_leaves.fall"); - SoundEvent BLOCK_AZALEA_LEAVES_HIT = SoundEventImpl.get("minecraft:block.azalea_leaves.hit"); + SoundEvent BLOCK_AZALEA_LEAVES_HIT = BuiltinSoundEvent.get("minecraft:block.azalea_leaves.hit"); - SoundEvent BLOCK_AZALEA_LEAVES_PLACE = SoundEventImpl.get("minecraft:block.azalea_leaves.place"); + SoundEvent BLOCK_AZALEA_LEAVES_PLACE = BuiltinSoundEvent.get("minecraft:block.azalea_leaves.place"); - SoundEvent BLOCK_AZALEA_LEAVES_STEP = SoundEventImpl.get("minecraft:block.azalea_leaves.step"); + SoundEvent BLOCK_AZALEA_LEAVES_STEP = BuiltinSoundEvent.get("minecraft:block.azalea_leaves.step"); - SoundEvent BLOCK_BAMBOO_BREAK = SoundEventImpl.get("minecraft:block.bamboo.break"); + SoundEvent BLOCK_BAMBOO_BREAK = BuiltinSoundEvent.get("minecraft:block.bamboo.break"); - SoundEvent BLOCK_BAMBOO_FALL = SoundEventImpl.get("minecraft:block.bamboo.fall"); + SoundEvent BLOCK_BAMBOO_FALL = BuiltinSoundEvent.get("minecraft:block.bamboo.fall"); - SoundEvent BLOCK_BAMBOO_HIT = SoundEventImpl.get("minecraft:block.bamboo.hit"); + SoundEvent BLOCK_BAMBOO_HIT = BuiltinSoundEvent.get("minecraft:block.bamboo.hit"); - SoundEvent BLOCK_BAMBOO_PLACE = SoundEventImpl.get("minecraft:block.bamboo.place"); + SoundEvent BLOCK_BAMBOO_PLACE = BuiltinSoundEvent.get("minecraft:block.bamboo.place"); - SoundEvent BLOCK_BAMBOO_STEP = SoundEventImpl.get("minecraft:block.bamboo.step"); + SoundEvent BLOCK_BAMBOO_STEP = BuiltinSoundEvent.get("minecraft:block.bamboo.step"); - SoundEvent BLOCK_BAMBOO_SAPLING_BREAK = SoundEventImpl.get("minecraft:block.bamboo_sapling.break"); + SoundEvent BLOCK_BAMBOO_SAPLING_BREAK = BuiltinSoundEvent.get("minecraft:block.bamboo_sapling.break"); - SoundEvent BLOCK_BAMBOO_SAPLING_HIT = SoundEventImpl.get("minecraft:block.bamboo_sapling.hit"); + SoundEvent BLOCK_BAMBOO_SAPLING_HIT = BuiltinSoundEvent.get("minecraft:block.bamboo_sapling.hit"); - SoundEvent BLOCK_BAMBOO_SAPLING_PLACE = SoundEventImpl.get("minecraft:block.bamboo_sapling.place"); + SoundEvent BLOCK_BAMBOO_SAPLING_PLACE = BuiltinSoundEvent.get("minecraft:block.bamboo_sapling.place"); - SoundEvent BLOCK_BAMBOO_WOOD_BREAK = SoundEventImpl.get("minecraft:block.bamboo_wood.break"); + SoundEvent BLOCK_BAMBOO_WOOD_BREAK = BuiltinSoundEvent.get("minecraft:block.bamboo_wood.break"); - SoundEvent BLOCK_BAMBOO_WOOD_FALL = SoundEventImpl.get("minecraft:block.bamboo_wood.fall"); + SoundEvent BLOCK_BAMBOO_WOOD_FALL = BuiltinSoundEvent.get("minecraft:block.bamboo_wood.fall"); - SoundEvent BLOCK_BAMBOO_WOOD_HIT = SoundEventImpl.get("minecraft:block.bamboo_wood.hit"); + SoundEvent BLOCK_BAMBOO_WOOD_HIT = BuiltinSoundEvent.get("minecraft:block.bamboo_wood.hit"); - SoundEvent BLOCK_BAMBOO_WOOD_PLACE = SoundEventImpl.get("minecraft:block.bamboo_wood.place"); + SoundEvent BLOCK_BAMBOO_WOOD_PLACE = BuiltinSoundEvent.get("minecraft:block.bamboo_wood.place"); - SoundEvent BLOCK_BAMBOO_WOOD_STEP = SoundEventImpl.get("minecraft:block.bamboo_wood.step"); + SoundEvent BLOCK_BAMBOO_WOOD_STEP = BuiltinSoundEvent.get("minecraft:block.bamboo_wood.step"); - SoundEvent BLOCK_BAMBOO_WOOD_DOOR_CLOSE = SoundEventImpl.get("minecraft:block.bamboo_wood_door.close"); + SoundEvent BLOCK_BAMBOO_WOOD_DOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_door.close"); - SoundEvent BLOCK_BAMBOO_WOOD_DOOR_OPEN = SoundEventImpl.get("minecraft:block.bamboo_wood_door.open"); + SoundEvent BLOCK_BAMBOO_WOOD_DOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_door.open"); - SoundEvent BLOCK_BAMBOO_WOOD_TRAPDOOR_CLOSE = SoundEventImpl.get("minecraft:block.bamboo_wood_trapdoor.close"); + SoundEvent BLOCK_BAMBOO_WOOD_TRAPDOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_trapdoor.close"); - SoundEvent BLOCK_BAMBOO_WOOD_TRAPDOOR_OPEN = SoundEventImpl.get("minecraft:block.bamboo_wood_trapdoor.open"); + SoundEvent BLOCK_BAMBOO_WOOD_TRAPDOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_trapdoor.open"); - SoundEvent BLOCK_BAMBOO_WOOD_BUTTON_CLICK_OFF = SoundEventImpl.get("minecraft:block.bamboo_wood_button.click_off"); + SoundEvent BLOCK_BAMBOO_WOOD_BUTTON_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_button.click_off"); - SoundEvent BLOCK_BAMBOO_WOOD_BUTTON_CLICK_ON = SoundEventImpl.get("minecraft:block.bamboo_wood_button.click_on"); + SoundEvent BLOCK_BAMBOO_WOOD_BUTTON_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_button.click_on"); - SoundEvent BLOCK_BAMBOO_WOOD_PRESSURE_PLATE_CLICK_OFF = SoundEventImpl.get("minecraft:block.bamboo_wood_pressure_plate.click_off"); + SoundEvent BLOCK_BAMBOO_WOOD_PRESSURE_PLATE_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_pressure_plate.click_off"); - SoundEvent BLOCK_BAMBOO_WOOD_PRESSURE_PLATE_CLICK_ON = SoundEventImpl.get("minecraft:block.bamboo_wood_pressure_plate.click_on"); + SoundEvent BLOCK_BAMBOO_WOOD_PRESSURE_PLATE_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_pressure_plate.click_on"); - SoundEvent BLOCK_BAMBOO_WOOD_FENCE_GATE_CLOSE = SoundEventImpl.get("minecraft:block.bamboo_wood_fence_gate.close"); + SoundEvent BLOCK_BAMBOO_WOOD_FENCE_GATE_CLOSE = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_fence_gate.close"); - SoundEvent BLOCK_BAMBOO_WOOD_FENCE_GATE_OPEN = SoundEventImpl.get("minecraft:block.bamboo_wood_fence_gate.open"); + SoundEvent BLOCK_BAMBOO_WOOD_FENCE_GATE_OPEN = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_fence_gate.open"); - SoundEvent BLOCK_BARREL_CLOSE = SoundEventImpl.get("minecraft:block.barrel.close"); + SoundEvent BLOCK_BARREL_CLOSE = BuiltinSoundEvent.get("minecraft:block.barrel.close"); - SoundEvent BLOCK_BARREL_OPEN = SoundEventImpl.get("minecraft:block.barrel.open"); + SoundEvent BLOCK_BARREL_OPEN = BuiltinSoundEvent.get("minecraft:block.barrel.open"); - SoundEvent BLOCK_BASALT_BREAK = SoundEventImpl.get("minecraft:block.basalt.break"); + SoundEvent BLOCK_BASALT_BREAK = BuiltinSoundEvent.get("minecraft:block.basalt.break"); - SoundEvent BLOCK_BASALT_STEP = SoundEventImpl.get("minecraft:block.basalt.step"); + SoundEvent BLOCK_BASALT_STEP = BuiltinSoundEvent.get("minecraft:block.basalt.step"); - SoundEvent BLOCK_BASALT_PLACE = SoundEventImpl.get("minecraft:block.basalt.place"); + SoundEvent BLOCK_BASALT_PLACE = BuiltinSoundEvent.get("minecraft:block.basalt.place"); - SoundEvent BLOCK_BASALT_HIT = SoundEventImpl.get("minecraft:block.basalt.hit"); + SoundEvent BLOCK_BASALT_HIT = BuiltinSoundEvent.get("minecraft:block.basalt.hit"); - SoundEvent BLOCK_BASALT_FALL = SoundEventImpl.get("minecraft:block.basalt.fall"); + SoundEvent BLOCK_BASALT_FALL = BuiltinSoundEvent.get("minecraft:block.basalt.fall"); - SoundEvent ENTITY_BAT_AMBIENT = SoundEventImpl.get("minecraft:entity.bat.ambient"); + SoundEvent ENTITY_BAT_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.bat.ambient"); - SoundEvent ENTITY_BAT_DEATH = SoundEventImpl.get("minecraft:entity.bat.death"); + SoundEvent ENTITY_BAT_DEATH = BuiltinSoundEvent.get("minecraft:entity.bat.death"); - SoundEvent ENTITY_BAT_HURT = SoundEventImpl.get("minecraft:entity.bat.hurt"); + SoundEvent ENTITY_BAT_HURT = BuiltinSoundEvent.get("minecraft:entity.bat.hurt"); - SoundEvent ENTITY_BAT_LOOP = SoundEventImpl.get("minecraft:entity.bat.loop"); + SoundEvent ENTITY_BAT_LOOP = BuiltinSoundEvent.get("minecraft:entity.bat.loop"); - SoundEvent ENTITY_BAT_TAKEOFF = SoundEventImpl.get("minecraft:entity.bat.takeoff"); + SoundEvent ENTITY_BAT_TAKEOFF = BuiltinSoundEvent.get("minecraft:entity.bat.takeoff"); - SoundEvent BLOCK_BEACON_ACTIVATE = SoundEventImpl.get("minecraft:block.beacon.activate"); + SoundEvent BLOCK_BEACON_ACTIVATE = BuiltinSoundEvent.get("minecraft:block.beacon.activate"); - SoundEvent BLOCK_BEACON_AMBIENT = SoundEventImpl.get("minecraft:block.beacon.ambient"); + SoundEvent BLOCK_BEACON_AMBIENT = BuiltinSoundEvent.get("minecraft:block.beacon.ambient"); - SoundEvent BLOCK_BEACON_DEACTIVATE = SoundEventImpl.get("minecraft:block.beacon.deactivate"); + SoundEvent BLOCK_BEACON_DEACTIVATE = BuiltinSoundEvent.get("minecraft:block.beacon.deactivate"); - SoundEvent BLOCK_BEACON_POWER_SELECT = SoundEventImpl.get("minecraft:block.beacon.power_select"); + SoundEvent BLOCK_BEACON_POWER_SELECT = BuiltinSoundEvent.get("minecraft:block.beacon.power_select"); - SoundEvent ENTITY_BEE_DEATH = SoundEventImpl.get("minecraft:entity.bee.death"); + SoundEvent ENTITY_BEE_DEATH = BuiltinSoundEvent.get("minecraft:entity.bee.death"); - SoundEvent ENTITY_BEE_HURT = SoundEventImpl.get("minecraft:entity.bee.hurt"); + SoundEvent ENTITY_BEE_HURT = BuiltinSoundEvent.get("minecraft:entity.bee.hurt"); - SoundEvent ENTITY_BEE_LOOP_AGGRESSIVE = SoundEventImpl.get("minecraft:entity.bee.loop_aggressive"); + SoundEvent ENTITY_BEE_LOOP_AGGRESSIVE = BuiltinSoundEvent.get("minecraft:entity.bee.loop_aggressive"); - SoundEvent ENTITY_BEE_LOOP = SoundEventImpl.get("minecraft:entity.bee.loop"); + SoundEvent ENTITY_BEE_LOOP = BuiltinSoundEvent.get("minecraft:entity.bee.loop"); - SoundEvent ENTITY_BEE_STING = SoundEventImpl.get("minecraft:entity.bee.sting"); + SoundEvent ENTITY_BEE_STING = BuiltinSoundEvent.get("minecraft:entity.bee.sting"); - SoundEvent ENTITY_BEE_POLLINATE = SoundEventImpl.get("minecraft:entity.bee.pollinate"); + SoundEvent ENTITY_BEE_POLLINATE = BuiltinSoundEvent.get("minecraft:entity.bee.pollinate"); - SoundEvent BLOCK_BEEHIVE_DRIP = SoundEventImpl.get("minecraft:block.beehive.drip"); + SoundEvent BLOCK_BEEHIVE_DRIP = BuiltinSoundEvent.get("minecraft:block.beehive.drip"); - SoundEvent BLOCK_BEEHIVE_ENTER = SoundEventImpl.get("minecraft:block.beehive.enter"); + SoundEvent BLOCK_BEEHIVE_ENTER = BuiltinSoundEvent.get("minecraft:block.beehive.enter"); - SoundEvent BLOCK_BEEHIVE_EXIT = SoundEventImpl.get("minecraft:block.beehive.exit"); + SoundEvent BLOCK_BEEHIVE_EXIT = BuiltinSoundEvent.get("minecraft:block.beehive.exit"); - SoundEvent BLOCK_BEEHIVE_SHEAR = SoundEventImpl.get("minecraft:block.beehive.shear"); + SoundEvent BLOCK_BEEHIVE_SHEAR = BuiltinSoundEvent.get("minecraft:block.beehive.shear"); - SoundEvent BLOCK_BEEHIVE_WORK = SoundEventImpl.get("minecraft:block.beehive.work"); + SoundEvent BLOCK_BEEHIVE_WORK = BuiltinSoundEvent.get("minecraft:block.beehive.work"); - SoundEvent BLOCK_BELL_USE = SoundEventImpl.get("minecraft:block.bell.use"); + SoundEvent BLOCK_BELL_USE = BuiltinSoundEvent.get("minecraft:block.bell.use"); - SoundEvent BLOCK_BELL_RESONATE = SoundEventImpl.get("minecraft:block.bell.resonate"); + SoundEvent BLOCK_BELL_RESONATE = BuiltinSoundEvent.get("minecraft:block.bell.resonate"); - SoundEvent BLOCK_BIG_DRIPLEAF_BREAK = SoundEventImpl.get("minecraft:block.big_dripleaf.break"); + SoundEvent BLOCK_BIG_DRIPLEAF_BREAK = BuiltinSoundEvent.get("minecraft:block.big_dripleaf.break"); - SoundEvent BLOCK_BIG_DRIPLEAF_FALL = SoundEventImpl.get("minecraft:block.big_dripleaf.fall"); + SoundEvent BLOCK_BIG_DRIPLEAF_FALL = BuiltinSoundEvent.get("minecraft:block.big_dripleaf.fall"); - SoundEvent BLOCK_BIG_DRIPLEAF_HIT = SoundEventImpl.get("minecraft:block.big_dripleaf.hit"); + SoundEvent BLOCK_BIG_DRIPLEAF_HIT = BuiltinSoundEvent.get("minecraft:block.big_dripleaf.hit"); - SoundEvent BLOCK_BIG_DRIPLEAF_PLACE = SoundEventImpl.get("minecraft:block.big_dripleaf.place"); + SoundEvent BLOCK_BIG_DRIPLEAF_PLACE = BuiltinSoundEvent.get("minecraft:block.big_dripleaf.place"); - SoundEvent BLOCK_BIG_DRIPLEAF_STEP = SoundEventImpl.get("minecraft:block.big_dripleaf.step"); + SoundEvent BLOCK_BIG_DRIPLEAF_STEP = BuiltinSoundEvent.get("minecraft:block.big_dripleaf.step"); - SoundEvent ENTITY_BLAZE_AMBIENT = SoundEventImpl.get("minecraft:entity.blaze.ambient"); + SoundEvent ENTITY_BLAZE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.blaze.ambient"); - SoundEvent ENTITY_BLAZE_BURN = SoundEventImpl.get("minecraft:entity.blaze.burn"); + SoundEvent ENTITY_BLAZE_BURN = BuiltinSoundEvent.get("minecraft:entity.blaze.burn"); - SoundEvent ENTITY_BLAZE_DEATH = SoundEventImpl.get("minecraft:entity.blaze.death"); + SoundEvent ENTITY_BLAZE_DEATH = BuiltinSoundEvent.get("minecraft:entity.blaze.death"); - SoundEvent ENTITY_BLAZE_HURT = SoundEventImpl.get("minecraft:entity.blaze.hurt"); + SoundEvent ENTITY_BLAZE_HURT = BuiltinSoundEvent.get("minecraft:entity.blaze.hurt"); - SoundEvent ENTITY_BLAZE_SHOOT = SoundEventImpl.get("minecraft:entity.blaze.shoot"); + SoundEvent ENTITY_BLAZE_SHOOT = BuiltinSoundEvent.get("minecraft:entity.blaze.shoot"); - SoundEvent ENTITY_BOAT_PADDLE_LAND = SoundEventImpl.get("minecraft:entity.boat.paddle_land"); + SoundEvent ENTITY_BOAT_PADDLE_LAND = BuiltinSoundEvent.get("minecraft:entity.boat.paddle_land"); - SoundEvent ENTITY_BOAT_PADDLE_WATER = SoundEventImpl.get("minecraft:entity.boat.paddle_water"); + SoundEvent ENTITY_BOAT_PADDLE_WATER = BuiltinSoundEvent.get("minecraft:entity.boat.paddle_water"); - SoundEvent ENTITY_BOGGED_AMBIENT = SoundEventImpl.get("minecraft:entity.bogged.ambient"); + SoundEvent ENTITY_BOGGED_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.bogged.ambient"); - SoundEvent ENTITY_BOGGED_DEATH = SoundEventImpl.get("minecraft:entity.bogged.death"); + SoundEvent ENTITY_BOGGED_DEATH = BuiltinSoundEvent.get("minecraft:entity.bogged.death"); - SoundEvent ENTITY_BOGGED_HURT = SoundEventImpl.get("minecraft:entity.bogged.hurt"); + SoundEvent ENTITY_BOGGED_HURT = BuiltinSoundEvent.get("minecraft:entity.bogged.hurt"); - SoundEvent ENTITY_BOGGED_SHEAR = SoundEventImpl.get("minecraft:entity.bogged.shear"); + SoundEvent ENTITY_BOGGED_SHEAR = BuiltinSoundEvent.get("minecraft:entity.bogged.shear"); - SoundEvent ENTITY_BOGGED_STEP = SoundEventImpl.get("minecraft:entity.bogged.step"); + SoundEvent ENTITY_BOGGED_STEP = BuiltinSoundEvent.get("minecraft:entity.bogged.step"); - SoundEvent BLOCK_BONE_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.bone_block.break"); + SoundEvent BLOCK_BONE_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.bone_block.break"); - SoundEvent BLOCK_BONE_BLOCK_FALL = SoundEventImpl.get("minecraft:block.bone_block.fall"); + SoundEvent BLOCK_BONE_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.bone_block.fall"); - SoundEvent BLOCK_BONE_BLOCK_HIT = SoundEventImpl.get("minecraft:block.bone_block.hit"); + SoundEvent BLOCK_BONE_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.bone_block.hit"); - SoundEvent BLOCK_BONE_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.bone_block.place"); + SoundEvent BLOCK_BONE_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.bone_block.place"); - SoundEvent BLOCK_BONE_BLOCK_STEP = SoundEventImpl.get("minecraft:block.bone_block.step"); + SoundEvent BLOCK_BONE_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.bone_block.step"); - SoundEvent ITEM_BONE_MEAL_USE = SoundEventImpl.get("minecraft:item.bone_meal.use"); + SoundEvent ITEM_BONE_MEAL_USE = BuiltinSoundEvent.get("minecraft:item.bone_meal.use"); - SoundEvent ITEM_BOOK_PAGE_TURN = SoundEventImpl.get("minecraft:item.book.page_turn"); + SoundEvent ITEM_BOOK_PAGE_TURN = BuiltinSoundEvent.get("minecraft:item.book.page_turn"); - SoundEvent ITEM_BOOK_PUT = SoundEventImpl.get("minecraft:item.book.put"); + SoundEvent ITEM_BOOK_PUT = BuiltinSoundEvent.get("minecraft:item.book.put"); - SoundEvent BLOCK_BLASTFURNACE_FIRE_CRACKLE = SoundEventImpl.get("minecraft:block.blastfurnace.fire_crackle"); + SoundEvent BLOCK_BLASTFURNACE_FIRE_CRACKLE = BuiltinSoundEvent.get("minecraft:block.blastfurnace.fire_crackle"); - SoundEvent ITEM_BOTTLE_EMPTY = SoundEventImpl.get("minecraft:item.bottle.empty"); + SoundEvent ITEM_BOTTLE_EMPTY = BuiltinSoundEvent.get("minecraft:item.bottle.empty"); - SoundEvent ITEM_BOTTLE_FILL = SoundEventImpl.get("minecraft:item.bottle.fill"); + SoundEvent ITEM_BOTTLE_FILL = BuiltinSoundEvent.get("minecraft:item.bottle.fill"); - SoundEvent ITEM_BOTTLE_FILL_DRAGONBREATH = SoundEventImpl.get("minecraft:item.bottle.fill_dragonbreath"); + SoundEvent ITEM_BOTTLE_FILL_DRAGONBREATH = BuiltinSoundEvent.get("minecraft:item.bottle.fill_dragonbreath"); - SoundEvent ENTITY_BREEZE_CHARGE = SoundEventImpl.get("minecraft:entity.breeze.charge"); + SoundEvent ENTITY_BREEZE_CHARGE = BuiltinSoundEvent.get("minecraft:entity.breeze.charge"); - SoundEvent ENTITY_BREEZE_DEFLECT = SoundEventImpl.get("minecraft:entity.breeze.deflect"); + SoundEvent ENTITY_BREEZE_DEFLECT = BuiltinSoundEvent.get("minecraft:entity.breeze.deflect"); - SoundEvent ENTITY_BREEZE_INHALE = SoundEventImpl.get("minecraft:entity.breeze.inhale"); + SoundEvent ENTITY_BREEZE_INHALE = BuiltinSoundEvent.get("minecraft:entity.breeze.inhale"); - SoundEvent ENTITY_BREEZE_IDLE_GROUND = SoundEventImpl.get("minecraft:entity.breeze.idle_ground"); + SoundEvent ENTITY_BREEZE_IDLE_GROUND = BuiltinSoundEvent.get("minecraft:entity.breeze.idle_ground"); - SoundEvent ENTITY_BREEZE_IDLE_AIR = SoundEventImpl.get("minecraft:entity.breeze.idle_air"); + SoundEvent ENTITY_BREEZE_IDLE_AIR = BuiltinSoundEvent.get("minecraft:entity.breeze.idle_air"); - SoundEvent ENTITY_BREEZE_SHOOT = SoundEventImpl.get("minecraft:entity.breeze.shoot"); + SoundEvent ENTITY_BREEZE_SHOOT = BuiltinSoundEvent.get("minecraft:entity.breeze.shoot"); - SoundEvent ENTITY_BREEZE_JUMP = SoundEventImpl.get("minecraft:entity.breeze.jump"); + SoundEvent ENTITY_BREEZE_JUMP = BuiltinSoundEvent.get("minecraft:entity.breeze.jump"); - SoundEvent ENTITY_BREEZE_LAND = SoundEventImpl.get("minecraft:entity.breeze.land"); + SoundEvent ENTITY_BREEZE_LAND = BuiltinSoundEvent.get("minecraft:entity.breeze.land"); - SoundEvent ENTITY_BREEZE_SLIDE = SoundEventImpl.get("minecraft:entity.breeze.slide"); + SoundEvent ENTITY_BREEZE_SLIDE = BuiltinSoundEvent.get("minecraft:entity.breeze.slide"); - SoundEvent ENTITY_BREEZE_DEATH = SoundEventImpl.get("minecraft:entity.breeze.death"); + SoundEvent ENTITY_BREEZE_DEATH = BuiltinSoundEvent.get("minecraft:entity.breeze.death"); - SoundEvent ENTITY_BREEZE_HURT = SoundEventImpl.get("minecraft:entity.breeze.hurt"); + SoundEvent ENTITY_BREEZE_HURT = BuiltinSoundEvent.get("minecraft:entity.breeze.hurt"); - SoundEvent ENTITY_BREEZE_WHIRL = SoundEventImpl.get("minecraft:entity.breeze.whirl"); + SoundEvent ENTITY_BREEZE_WHIRL = BuiltinSoundEvent.get("minecraft:entity.breeze.whirl"); - SoundEvent ENTITY_BREEZE_WIND_BURST = SoundEventImpl.get("minecraft:entity.breeze.wind_burst"); + SoundEvent ENTITY_BREEZE_WIND_BURST = BuiltinSoundEvent.get("minecraft:entity.breeze.wind_burst"); - SoundEvent BLOCK_BREWING_STAND_BREW = SoundEventImpl.get("minecraft:block.brewing_stand.brew"); + SoundEvent BLOCK_BREWING_STAND_BREW = BuiltinSoundEvent.get("minecraft:block.brewing_stand.brew"); - SoundEvent ITEM_BRUSH_BRUSHING_GENERIC = SoundEventImpl.get("minecraft:item.brush.brushing.generic"); + SoundEvent ITEM_BRUSH_BRUSHING_GENERIC = BuiltinSoundEvent.get("minecraft:item.brush.brushing.generic"); - SoundEvent ITEM_BRUSH_BRUSHING_SAND = SoundEventImpl.get("minecraft:item.brush.brushing.sand"); + SoundEvent ITEM_BRUSH_BRUSHING_SAND = BuiltinSoundEvent.get("minecraft:item.brush.brushing.sand"); - SoundEvent ITEM_BRUSH_BRUSHING_GRAVEL = SoundEventImpl.get("minecraft:item.brush.brushing.gravel"); + SoundEvent ITEM_BRUSH_BRUSHING_GRAVEL = BuiltinSoundEvent.get("minecraft:item.brush.brushing.gravel"); - SoundEvent ITEM_BRUSH_BRUSHING_SAND_COMPLETE = SoundEventImpl.get("minecraft:item.brush.brushing.sand.complete"); + SoundEvent ITEM_BRUSH_BRUSHING_SAND_COMPLETE = BuiltinSoundEvent.get("minecraft:item.brush.brushing.sand.complete"); - SoundEvent ITEM_BRUSH_BRUSHING_GRAVEL_COMPLETE = SoundEventImpl.get("minecraft:item.brush.brushing.gravel.complete"); + SoundEvent ITEM_BRUSH_BRUSHING_GRAVEL_COMPLETE = BuiltinSoundEvent.get("minecraft:item.brush.brushing.gravel.complete"); - SoundEvent BLOCK_BUBBLE_COLUMN_BUBBLE_POP = SoundEventImpl.get("minecraft:block.bubble_column.bubble_pop"); + SoundEvent BLOCK_BUBBLE_COLUMN_BUBBLE_POP = BuiltinSoundEvent.get("minecraft:block.bubble_column.bubble_pop"); - SoundEvent BLOCK_BUBBLE_COLUMN_UPWARDS_AMBIENT = SoundEventImpl.get("minecraft:block.bubble_column.upwards_ambient"); + SoundEvent BLOCK_BUBBLE_COLUMN_UPWARDS_AMBIENT = BuiltinSoundEvent.get("minecraft:block.bubble_column.upwards_ambient"); - SoundEvent BLOCK_BUBBLE_COLUMN_UPWARDS_INSIDE = SoundEventImpl.get("minecraft:block.bubble_column.upwards_inside"); + SoundEvent BLOCK_BUBBLE_COLUMN_UPWARDS_INSIDE = BuiltinSoundEvent.get("minecraft:block.bubble_column.upwards_inside"); - SoundEvent BLOCK_BUBBLE_COLUMN_WHIRLPOOL_AMBIENT = SoundEventImpl.get("minecraft:block.bubble_column.whirlpool_ambient"); + SoundEvent BLOCK_BUBBLE_COLUMN_WHIRLPOOL_AMBIENT = BuiltinSoundEvent.get("minecraft:block.bubble_column.whirlpool_ambient"); - SoundEvent BLOCK_BUBBLE_COLUMN_WHIRLPOOL_INSIDE = SoundEventImpl.get("minecraft:block.bubble_column.whirlpool_inside"); + SoundEvent BLOCK_BUBBLE_COLUMN_WHIRLPOOL_INSIDE = BuiltinSoundEvent.get("minecraft:block.bubble_column.whirlpool_inside"); - SoundEvent ITEM_BUCKET_EMPTY = SoundEventImpl.get("minecraft:item.bucket.empty"); + SoundEvent ITEM_BUCKET_EMPTY = BuiltinSoundEvent.get("minecraft:item.bucket.empty"); - SoundEvent ITEM_BUCKET_EMPTY_AXOLOTL = SoundEventImpl.get("minecraft:item.bucket.empty_axolotl"); + SoundEvent ITEM_BUCKET_EMPTY_AXOLOTL = BuiltinSoundEvent.get("minecraft:item.bucket.empty_axolotl"); - SoundEvent ITEM_BUCKET_EMPTY_FISH = SoundEventImpl.get("minecraft:item.bucket.empty_fish"); + SoundEvent ITEM_BUCKET_EMPTY_FISH = BuiltinSoundEvent.get("minecraft:item.bucket.empty_fish"); - SoundEvent ITEM_BUCKET_EMPTY_LAVA = SoundEventImpl.get("minecraft:item.bucket.empty_lava"); + SoundEvent ITEM_BUCKET_EMPTY_LAVA = BuiltinSoundEvent.get("minecraft:item.bucket.empty_lava"); - SoundEvent ITEM_BUCKET_EMPTY_POWDER_SNOW = SoundEventImpl.get("minecraft:item.bucket.empty_powder_snow"); + SoundEvent ITEM_BUCKET_EMPTY_POWDER_SNOW = BuiltinSoundEvent.get("minecraft:item.bucket.empty_powder_snow"); - SoundEvent ITEM_BUCKET_EMPTY_TADPOLE = SoundEventImpl.get("minecraft:item.bucket.empty_tadpole"); + SoundEvent ITEM_BUCKET_EMPTY_TADPOLE = BuiltinSoundEvent.get("minecraft:item.bucket.empty_tadpole"); - SoundEvent ITEM_BUCKET_FILL = SoundEventImpl.get("minecraft:item.bucket.fill"); + SoundEvent ITEM_BUCKET_FILL = BuiltinSoundEvent.get("minecraft:item.bucket.fill"); - SoundEvent ITEM_BUCKET_FILL_AXOLOTL = SoundEventImpl.get("minecraft:item.bucket.fill_axolotl"); + SoundEvent ITEM_BUCKET_FILL_AXOLOTL = BuiltinSoundEvent.get("minecraft:item.bucket.fill_axolotl"); - SoundEvent ITEM_BUCKET_FILL_FISH = SoundEventImpl.get("minecraft:item.bucket.fill_fish"); + SoundEvent ITEM_BUCKET_FILL_FISH = BuiltinSoundEvent.get("minecraft:item.bucket.fill_fish"); - SoundEvent ITEM_BUCKET_FILL_LAVA = SoundEventImpl.get("minecraft:item.bucket.fill_lava"); + SoundEvent ITEM_BUCKET_FILL_LAVA = BuiltinSoundEvent.get("minecraft:item.bucket.fill_lava"); - SoundEvent ITEM_BUCKET_FILL_POWDER_SNOW = SoundEventImpl.get("minecraft:item.bucket.fill_powder_snow"); + SoundEvent ITEM_BUCKET_FILL_POWDER_SNOW = BuiltinSoundEvent.get("minecraft:item.bucket.fill_powder_snow"); - SoundEvent ITEM_BUCKET_FILL_TADPOLE = SoundEventImpl.get("minecraft:item.bucket.fill_tadpole"); + SoundEvent ITEM_BUCKET_FILL_TADPOLE = BuiltinSoundEvent.get("minecraft:item.bucket.fill_tadpole"); - SoundEvent ITEM_BUNDLE_DROP_CONTENTS = SoundEventImpl.get("minecraft:item.bundle.drop_contents"); + SoundEvent ITEM_BUNDLE_DROP_CONTENTS = BuiltinSoundEvent.get("minecraft:item.bundle.drop_contents"); - SoundEvent ITEM_BUNDLE_INSERT = SoundEventImpl.get("minecraft:item.bundle.insert"); + SoundEvent ITEM_BUNDLE_INSERT = BuiltinSoundEvent.get("minecraft:item.bundle.insert"); - SoundEvent ITEM_BUNDLE_REMOVE_ONE = SoundEventImpl.get("minecraft:item.bundle.remove_one"); + SoundEvent ITEM_BUNDLE_REMOVE_ONE = BuiltinSoundEvent.get("minecraft:item.bundle.remove_one"); - SoundEvent BLOCK_CAKE_ADD_CANDLE = SoundEventImpl.get("minecraft:block.cake.add_candle"); + SoundEvent BLOCK_CAKE_ADD_CANDLE = BuiltinSoundEvent.get("minecraft:block.cake.add_candle"); - SoundEvent BLOCK_CALCITE_BREAK = SoundEventImpl.get("minecraft:block.calcite.break"); + SoundEvent BLOCK_CALCITE_BREAK = BuiltinSoundEvent.get("minecraft:block.calcite.break"); - SoundEvent BLOCK_CALCITE_STEP = SoundEventImpl.get("minecraft:block.calcite.step"); + SoundEvent BLOCK_CALCITE_STEP = BuiltinSoundEvent.get("minecraft:block.calcite.step"); - SoundEvent BLOCK_CALCITE_PLACE = SoundEventImpl.get("minecraft:block.calcite.place"); + SoundEvent BLOCK_CALCITE_PLACE = BuiltinSoundEvent.get("minecraft:block.calcite.place"); - SoundEvent BLOCK_CALCITE_HIT = SoundEventImpl.get("minecraft:block.calcite.hit"); + SoundEvent BLOCK_CALCITE_HIT = BuiltinSoundEvent.get("minecraft:block.calcite.hit"); - SoundEvent BLOCK_CALCITE_FALL = SoundEventImpl.get("minecraft:block.calcite.fall"); + SoundEvent BLOCK_CALCITE_FALL = BuiltinSoundEvent.get("minecraft:block.calcite.fall"); - SoundEvent ENTITY_CAMEL_AMBIENT = SoundEventImpl.get("minecraft:entity.camel.ambient"); + SoundEvent ENTITY_CAMEL_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.camel.ambient"); - SoundEvent ENTITY_CAMEL_DASH = SoundEventImpl.get("minecraft:entity.camel.dash"); + SoundEvent ENTITY_CAMEL_DASH = BuiltinSoundEvent.get("minecraft:entity.camel.dash"); - SoundEvent ENTITY_CAMEL_DASH_READY = SoundEventImpl.get("minecraft:entity.camel.dash_ready"); + SoundEvent ENTITY_CAMEL_DASH_READY = BuiltinSoundEvent.get("minecraft:entity.camel.dash_ready"); - SoundEvent ENTITY_CAMEL_DEATH = SoundEventImpl.get("minecraft:entity.camel.death"); + SoundEvent ENTITY_CAMEL_DEATH = BuiltinSoundEvent.get("minecraft:entity.camel.death"); - SoundEvent ENTITY_CAMEL_EAT = SoundEventImpl.get("minecraft:entity.camel.eat"); + SoundEvent ENTITY_CAMEL_EAT = BuiltinSoundEvent.get("minecraft:entity.camel.eat"); - SoundEvent ENTITY_CAMEL_HURT = SoundEventImpl.get("minecraft:entity.camel.hurt"); + SoundEvent ENTITY_CAMEL_HURT = BuiltinSoundEvent.get("minecraft:entity.camel.hurt"); - SoundEvent ENTITY_CAMEL_SADDLE = SoundEventImpl.get("minecraft:entity.camel.saddle"); + SoundEvent ENTITY_CAMEL_SADDLE = BuiltinSoundEvent.get("minecraft:entity.camel.saddle"); - SoundEvent ENTITY_CAMEL_SIT = SoundEventImpl.get("minecraft:entity.camel.sit"); + SoundEvent ENTITY_CAMEL_SIT = BuiltinSoundEvent.get("minecraft:entity.camel.sit"); - SoundEvent ENTITY_CAMEL_STAND = SoundEventImpl.get("minecraft:entity.camel.stand"); + SoundEvent ENTITY_CAMEL_STAND = BuiltinSoundEvent.get("minecraft:entity.camel.stand"); - SoundEvent ENTITY_CAMEL_STEP = SoundEventImpl.get("minecraft:entity.camel.step"); + SoundEvent ENTITY_CAMEL_STEP = BuiltinSoundEvent.get("minecraft:entity.camel.step"); - SoundEvent ENTITY_CAMEL_STEP_SAND = SoundEventImpl.get("minecraft:entity.camel.step_sand"); + SoundEvent ENTITY_CAMEL_STEP_SAND = BuiltinSoundEvent.get("minecraft:entity.camel.step_sand"); - SoundEvent BLOCK_CAMPFIRE_CRACKLE = SoundEventImpl.get("minecraft:block.campfire.crackle"); + SoundEvent BLOCK_CAMPFIRE_CRACKLE = BuiltinSoundEvent.get("minecraft:block.campfire.crackle"); - SoundEvent BLOCK_CANDLE_AMBIENT = SoundEventImpl.get("minecraft:block.candle.ambient"); + SoundEvent BLOCK_CANDLE_AMBIENT = BuiltinSoundEvent.get("minecraft:block.candle.ambient"); - SoundEvent BLOCK_CANDLE_BREAK = SoundEventImpl.get("minecraft:block.candle.break"); + SoundEvent BLOCK_CANDLE_BREAK = BuiltinSoundEvent.get("minecraft:block.candle.break"); - SoundEvent BLOCK_CANDLE_EXTINGUISH = SoundEventImpl.get("minecraft:block.candle.extinguish"); + SoundEvent BLOCK_CANDLE_EXTINGUISH = BuiltinSoundEvent.get("minecraft:block.candle.extinguish"); - SoundEvent BLOCK_CANDLE_FALL = SoundEventImpl.get("minecraft:block.candle.fall"); + SoundEvent BLOCK_CANDLE_FALL = BuiltinSoundEvent.get("minecraft:block.candle.fall"); - SoundEvent BLOCK_CANDLE_HIT = SoundEventImpl.get("minecraft:block.candle.hit"); + SoundEvent BLOCK_CANDLE_HIT = BuiltinSoundEvent.get("minecraft:block.candle.hit"); - SoundEvent BLOCK_CANDLE_PLACE = SoundEventImpl.get("minecraft:block.candle.place"); + SoundEvent BLOCK_CANDLE_PLACE = BuiltinSoundEvent.get("minecraft:block.candle.place"); - SoundEvent BLOCK_CANDLE_STEP = SoundEventImpl.get("minecraft:block.candle.step"); + SoundEvent BLOCK_CANDLE_STEP = BuiltinSoundEvent.get("minecraft:block.candle.step"); - SoundEvent ENTITY_CAT_AMBIENT = SoundEventImpl.get("minecraft:entity.cat.ambient"); + SoundEvent ENTITY_CAT_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.cat.ambient"); - SoundEvent ENTITY_CAT_STRAY_AMBIENT = SoundEventImpl.get("minecraft:entity.cat.stray_ambient"); + SoundEvent ENTITY_CAT_STRAY_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.cat.stray_ambient"); - SoundEvent ENTITY_CAT_DEATH = SoundEventImpl.get("minecraft:entity.cat.death"); + SoundEvent ENTITY_CAT_DEATH = BuiltinSoundEvent.get("minecraft:entity.cat.death"); - SoundEvent ENTITY_CAT_EAT = SoundEventImpl.get("minecraft:entity.cat.eat"); + SoundEvent ENTITY_CAT_EAT = BuiltinSoundEvent.get("minecraft:entity.cat.eat"); - SoundEvent ENTITY_CAT_HISS = SoundEventImpl.get("minecraft:entity.cat.hiss"); + SoundEvent ENTITY_CAT_HISS = BuiltinSoundEvent.get("minecraft:entity.cat.hiss"); - SoundEvent ENTITY_CAT_BEG_FOR_FOOD = SoundEventImpl.get("minecraft:entity.cat.beg_for_food"); + SoundEvent ENTITY_CAT_BEG_FOR_FOOD = BuiltinSoundEvent.get("minecraft:entity.cat.beg_for_food"); - SoundEvent ENTITY_CAT_HURT = SoundEventImpl.get("minecraft:entity.cat.hurt"); + SoundEvent ENTITY_CAT_HURT = BuiltinSoundEvent.get("minecraft:entity.cat.hurt"); - SoundEvent ENTITY_CAT_PURR = SoundEventImpl.get("minecraft:entity.cat.purr"); + SoundEvent ENTITY_CAT_PURR = BuiltinSoundEvent.get("minecraft:entity.cat.purr"); - SoundEvent ENTITY_CAT_PURREOW = SoundEventImpl.get("minecraft:entity.cat.purreow"); + SoundEvent ENTITY_CAT_PURREOW = BuiltinSoundEvent.get("minecraft:entity.cat.purreow"); - SoundEvent BLOCK_CAVE_VINES_BREAK = SoundEventImpl.get("minecraft:block.cave_vines.break"); + SoundEvent BLOCK_CAVE_VINES_BREAK = BuiltinSoundEvent.get("minecraft:block.cave_vines.break"); - SoundEvent BLOCK_CAVE_VINES_FALL = SoundEventImpl.get("minecraft:block.cave_vines.fall"); + SoundEvent BLOCK_CAVE_VINES_FALL = BuiltinSoundEvent.get("minecraft:block.cave_vines.fall"); - SoundEvent BLOCK_CAVE_VINES_HIT = SoundEventImpl.get("minecraft:block.cave_vines.hit"); + SoundEvent BLOCK_CAVE_VINES_HIT = BuiltinSoundEvent.get("minecraft:block.cave_vines.hit"); - SoundEvent BLOCK_CAVE_VINES_PLACE = SoundEventImpl.get("minecraft:block.cave_vines.place"); + SoundEvent BLOCK_CAVE_VINES_PLACE = BuiltinSoundEvent.get("minecraft:block.cave_vines.place"); - SoundEvent BLOCK_CAVE_VINES_STEP = SoundEventImpl.get("minecraft:block.cave_vines.step"); + SoundEvent BLOCK_CAVE_VINES_STEP = BuiltinSoundEvent.get("minecraft:block.cave_vines.step"); - SoundEvent BLOCK_CAVE_VINES_PICK_BERRIES = SoundEventImpl.get("minecraft:block.cave_vines.pick_berries"); + SoundEvent BLOCK_CAVE_VINES_PICK_BERRIES = BuiltinSoundEvent.get("minecraft:block.cave_vines.pick_berries"); - SoundEvent BLOCK_CHAIN_BREAK = SoundEventImpl.get("minecraft:block.chain.break"); + SoundEvent BLOCK_CHAIN_BREAK = BuiltinSoundEvent.get("minecraft:block.chain.break"); - SoundEvent BLOCK_CHAIN_FALL = SoundEventImpl.get("minecraft:block.chain.fall"); + SoundEvent BLOCK_CHAIN_FALL = BuiltinSoundEvent.get("minecraft:block.chain.fall"); - SoundEvent BLOCK_CHAIN_HIT = SoundEventImpl.get("minecraft:block.chain.hit"); + SoundEvent BLOCK_CHAIN_HIT = BuiltinSoundEvent.get("minecraft:block.chain.hit"); - SoundEvent BLOCK_CHAIN_PLACE = SoundEventImpl.get("minecraft:block.chain.place"); + SoundEvent BLOCK_CHAIN_PLACE = BuiltinSoundEvent.get("minecraft:block.chain.place"); - SoundEvent BLOCK_CHAIN_STEP = SoundEventImpl.get("minecraft:block.chain.step"); + SoundEvent BLOCK_CHAIN_STEP = BuiltinSoundEvent.get("minecraft:block.chain.step"); - SoundEvent BLOCK_CHERRY_WOOD_BREAK = SoundEventImpl.get("minecraft:block.cherry_wood.break"); + SoundEvent BLOCK_CHERRY_WOOD_BREAK = BuiltinSoundEvent.get("minecraft:block.cherry_wood.break"); - SoundEvent BLOCK_CHERRY_WOOD_FALL = SoundEventImpl.get("minecraft:block.cherry_wood.fall"); + SoundEvent BLOCK_CHERRY_WOOD_FALL = BuiltinSoundEvent.get("minecraft:block.cherry_wood.fall"); - SoundEvent BLOCK_CHERRY_WOOD_HIT = SoundEventImpl.get("minecraft:block.cherry_wood.hit"); + SoundEvent BLOCK_CHERRY_WOOD_HIT = BuiltinSoundEvent.get("minecraft:block.cherry_wood.hit"); - SoundEvent BLOCK_CHERRY_WOOD_PLACE = SoundEventImpl.get("minecraft:block.cherry_wood.place"); + SoundEvent BLOCK_CHERRY_WOOD_PLACE = BuiltinSoundEvent.get("minecraft:block.cherry_wood.place"); - SoundEvent BLOCK_CHERRY_WOOD_STEP = SoundEventImpl.get("minecraft:block.cherry_wood.step"); + SoundEvent BLOCK_CHERRY_WOOD_STEP = BuiltinSoundEvent.get("minecraft:block.cherry_wood.step"); - SoundEvent BLOCK_CHERRY_SAPLING_BREAK = SoundEventImpl.get("minecraft:block.cherry_sapling.break"); + SoundEvent BLOCK_CHERRY_SAPLING_BREAK = BuiltinSoundEvent.get("minecraft:block.cherry_sapling.break"); - SoundEvent BLOCK_CHERRY_SAPLING_FALL = SoundEventImpl.get("minecraft:block.cherry_sapling.fall"); + SoundEvent BLOCK_CHERRY_SAPLING_FALL = BuiltinSoundEvent.get("minecraft:block.cherry_sapling.fall"); - SoundEvent BLOCK_CHERRY_SAPLING_HIT = SoundEventImpl.get("minecraft:block.cherry_sapling.hit"); + SoundEvent BLOCK_CHERRY_SAPLING_HIT = BuiltinSoundEvent.get("minecraft:block.cherry_sapling.hit"); - SoundEvent BLOCK_CHERRY_SAPLING_PLACE = SoundEventImpl.get("minecraft:block.cherry_sapling.place"); + SoundEvent BLOCK_CHERRY_SAPLING_PLACE = BuiltinSoundEvent.get("minecraft:block.cherry_sapling.place"); - SoundEvent BLOCK_CHERRY_SAPLING_STEP = SoundEventImpl.get("minecraft:block.cherry_sapling.step"); + SoundEvent BLOCK_CHERRY_SAPLING_STEP = BuiltinSoundEvent.get("minecraft:block.cherry_sapling.step"); - SoundEvent BLOCK_CHERRY_LEAVES_BREAK = SoundEventImpl.get("minecraft:block.cherry_leaves.break"); + SoundEvent BLOCK_CHERRY_LEAVES_BREAK = BuiltinSoundEvent.get("minecraft:block.cherry_leaves.break"); - SoundEvent BLOCK_CHERRY_LEAVES_FALL = SoundEventImpl.get("minecraft:block.cherry_leaves.fall"); + SoundEvent BLOCK_CHERRY_LEAVES_FALL = BuiltinSoundEvent.get("minecraft:block.cherry_leaves.fall"); - SoundEvent BLOCK_CHERRY_LEAVES_HIT = SoundEventImpl.get("minecraft:block.cherry_leaves.hit"); + SoundEvent BLOCK_CHERRY_LEAVES_HIT = BuiltinSoundEvent.get("minecraft:block.cherry_leaves.hit"); - SoundEvent BLOCK_CHERRY_LEAVES_PLACE = SoundEventImpl.get("minecraft:block.cherry_leaves.place"); + SoundEvent BLOCK_CHERRY_LEAVES_PLACE = BuiltinSoundEvent.get("minecraft:block.cherry_leaves.place"); - SoundEvent BLOCK_CHERRY_LEAVES_STEP = SoundEventImpl.get("minecraft:block.cherry_leaves.step"); + SoundEvent BLOCK_CHERRY_LEAVES_STEP = BuiltinSoundEvent.get("minecraft:block.cherry_leaves.step"); - SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_STEP = SoundEventImpl.get("minecraft:block.cherry_wood_hanging_sign.step"); + SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_STEP = BuiltinSoundEvent.get("minecraft:block.cherry_wood_hanging_sign.step"); - SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_BREAK = SoundEventImpl.get("minecraft:block.cherry_wood_hanging_sign.break"); + SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_BREAK = BuiltinSoundEvent.get("minecraft:block.cherry_wood_hanging_sign.break"); - SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_FALL = SoundEventImpl.get("minecraft:block.cherry_wood_hanging_sign.fall"); + SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_FALL = BuiltinSoundEvent.get("minecraft:block.cherry_wood_hanging_sign.fall"); - SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_HIT = SoundEventImpl.get("minecraft:block.cherry_wood_hanging_sign.hit"); + SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_HIT = BuiltinSoundEvent.get("minecraft:block.cherry_wood_hanging_sign.hit"); - SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_PLACE = SoundEventImpl.get("minecraft:block.cherry_wood_hanging_sign.place"); + SoundEvent BLOCK_CHERRY_WOOD_HANGING_SIGN_PLACE = BuiltinSoundEvent.get("minecraft:block.cherry_wood_hanging_sign.place"); - SoundEvent BLOCK_CHERRY_WOOD_DOOR_CLOSE = SoundEventImpl.get("minecraft:block.cherry_wood_door.close"); + SoundEvent BLOCK_CHERRY_WOOD_DOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.cherry_wood_door.close"); - SoundEvent BLOCK_CHERRY_WOOD_DOOR_OPEN = SoundEventImpl.get("minecraft:block.cherry_wood_door.open"); + SoundEvent BLOCK_CHERRY_WOOD_DOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.cherry_wood_door.open"); - SoundEvent BLOCK_CHERRY_WOOD_TRAPDOOR_CLOSE = SoundEventImpl.get("minecraft:block.cherry_wood_trapdoor.close"); + SoundEvent BLOCK_CHERRY_WOOD_TRAPDOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.cherry_wood_trapdoor.close"); - SoundEvent BLOCK_CHERRY_WOOD_TRAPDOOR_OPEN = SoundEventImpl.get("minecraft:block.cherry_wood_trapdoor.open"); + SoundEvent BLOCK_CHERRY_WOOD_TRAPDOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.cherry_wood_trapdoor.open"); - SoundEvent BLOCK_CHERRY_WOOD_BUTTON_CLICK_OFF = SoundEventImpl.get("minecraft:block.cherry_wood_button.click_off"); + SoundEvent BLOCK_CHERRY_WOOD_BUTTON_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.cherry_wood_button.click_off"); - SoundEvent BLOCK_CHERRY_WOOD_BUTTON_CLICK_ON = SoundEventImpl.get("minecraft:block.cherry_wood_button.click_on"); + SoundEvent BLOCK_CHERRY_WOOD_BUTTON_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.cherry_wood_button.click_on"); - SoundEvent BLOCK_CHERRY_WOOD_PRESSURE_PLATE_CLICK_OFF = SoundEventImpl.get("minecraft:block.cherry_wood_pressure_plate.click_off"); + SoundEvent BLOCK_CHERRY_WOOD_PRESSURE_PLATE_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.cherry_wood_pressure_plate.click_off"); - SoundEvent BLOCK_CHERRY_WOOD_PRESSURE_PLATE_CLICK_ON = SoundEventImpl.get("minecraft:block.cherry_wood_pressure_plate.click_on"); + SoundEvent BLOCK_CHERRY_WOOD_PRESSURE_PLATE_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.cherry_wood_pressure_plate.click_on"); - SoundEvent BLOCK_CHERRY_WOOD_FENCE_GATE_CLOSE = SoundEventImpl.get("minecraft:block.cherry_wood_fence_gate.close"); + SoundEvent BLOCK_CHERRY_WOOD_FENCE_GATE_CLOSE = BuiltinSoundEvent.get("minecraft:block.cherry_wood_fence_gate.close"); - SoundEvent BLOCK_CHERRY_WOOD_FENCE_GATE_OPEN = SoundEventImpl.get("minecraft:block.cherry_wood_fence_gate.open"); + SoundEvent BLOCK_CHERRY_WOOD_FENCE_GATE_OPEN = BuiltinSoundEvent.get("minecraft:block.cherry_wood_fence_gate.open"); - SoundEvent BLOCK_CHEST_CLOSE = SoundEventImpl.get("minecraft:block.chest.close"); + SoundEvent BLOCK_CHEST_CLOSE = BuiltinSoundEvent.get("minecraft:block.chest.close"); - SoundEvent BLOCK_CHEST_LOCKED = SoundEventImpl.get("minecraft:block.chest.locked"); + SoundEvent BLOCK_CHEST_LOCKED = BuiltinSoundEvent.get("minecraft:block.chest.locked"); - SoundEvent BLOCK_CHEST_OPEN = SoundEventImpl.get("minecraft:block.chest.open"); + SoundEvent BLOCK_CHEST_OPEN = BuiltinSoundEvent.get("minecraft:block.chest.open"); - SoundEvent ENTITY_CHICKEN_AMBIENT = SoundEventImpl.get("minecraft:entity.chicken.ambient"); + SoundEvent ENTITY_CHICKEN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.chicken.ambient"); - SoundEvent ENTITY_CHICKEN_DEATH = SoundEventImpl.get("minecraft:entity.chicken.death"); + SoundEvent ENTITY_CHICKEN_DEATH = BuiltinSoundEvent.get("minecraft:entity.chicken.death"); - SoundEvent ENTITY_CHICKEN_EGG = SoundEventImpl.get("minecraft:entity.chicken.egg"); + SoundEvent ENTITY_CHICKEN_EGG = BuiltinSoundEvent.get("minecraft:entity.chicken.egg"); - SoundEvent ENTITY_CHICKEN_HURT = SoundEventImpl.get("minecraft:entity.chicken.hurt"); + SoundEvent ENTITY_CHICKEN_HURT = BuiltinSoundEvent.get("minecraft:entity.chicken.hurt"); - SoundEvent ENTITY_CHICKEN_STEP = SoundEventImpl.get("minecraft:entity.chicken.step"); + SoundEvent ENTITY_CHICKEN_STEP = BuiltinSoundEvent.get("minecraft:entity.chicken.step"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_BREAK = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.break"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_BREAK = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.break"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_FALL = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.fall"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_FALL = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.fall"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_HIT = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.hit"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_HIT = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.hit"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_INSERT = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.insert"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_INSERT = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.insert"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_INSERT_ENCHANTED = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.insert.enchanted"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_INSERT_ENCHANTED = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.insert.enchanted"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_STEP = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.step"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_STEP = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.step"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_PICKUP = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.pickup"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_PICKUP = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.pickup"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_PICKUP_ENCHANTED = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.pickup.enchanted"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_PICKUP_ENCHANTED = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.pickup.enchanted"); - SoundEvent BLOCK_CHISELED_BOOKSHELF_PLACE = SoundEventImpl.get("minecraft:block.chiseled_bookshelf.place"); + SoundEvent BLOCK_CHISELED_BOOKSHELF_PLACE = BuiltinSoundEvent.get("minecraft:block.chiseled_bookshelf.place"); - SoundEvent BLOCK_CHORUS_FLOWER_DEATH = SoundEventImpl.get("minecraft:block.chorus_flower.death"); + SoundEvent BLOCK_CHORUS_FLOWER_DEATH = BuiltinSoundEvent.get("minecraft:block.chorus_flower.death"); - SoundEvent BLOCK_CHORUS_FLOWER_GROW = SoundEventImpl.get("minecraft:block.chorus_flower.grow"); + SoundEvent BLOCK_CHORUS_FLOWER_GROW = BuiltinSoundEvent.get("minecraft:block.chorus_flower.grow"); - SoundEvent ITEM_CHORUS_FRUIT_TELEPORT = SoundEventImpl.get("minecraft:item.chorus_fruit.teleport"); + SoundEvent ITEM_CHORUS_FRUIT_TELEPORT = BuiltinSoundEvent.get("minecraft:item.chorus_fruit.teleport"); - SoundEvent BLOCK_COBWEB_BREAK = SoundEventImpl.get("minecraft:block.cobweb.break"); + SoundEvent BLOCK_COBWEB_BREAK = BuiltinSoundEvent.get("minecraft:block.cobweb.break"); - SoundEvent BLOCK_COBWEB_STEP = SoundEventImpl.get("minecraft:block.cobweb.step"); + SoundEvent BLOCK_COBWEB_STEP = BuiltinSoundEvent.get("minecraft:block.cobweb.step"); - SoundEvent BLOCK_COBWEB_PLACE = SoundEventImpl.get("minecraft:block.cobweb.place"); + SoundEvent BLOCK_COBWEB_PLACE = BuiltinSoundEvent.get("minecraft:block.cobweb.place"); - SoundEvent BLOCK_COBWEB_HIT = SoundEventImpl.get("minecraft:block.cobweb.hit"); + SoundEvent BLOCK_COBWEB_HIT = BuiltinSoundEvent.get("minecraft:block.cobweb.hit"); - SoundEvent BLOCK_COBWEB_FALL = SoundEventImpl.get("minecraft:block.cobweb.fall"); + SoundEvent BLOCK_COBWEB_FALL = BuiltinSoundEvent.get("minecraft:block.cobweb.fall"); - SoundEvent ENTITY_COD_AMBIENT = SoundEventImpl.get("minecraft:entity.cod.ambient"); + SoundEvent ENTITY_COD_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.cod.ambient"); - SoundEvent ENTITY_COD_DEATH = SoundEventImpl.get("minecraft:entity.cod.death"); + SoundEvent ENTITY_COD_DEATH = BuiltinSoundEvent.get("minecraft:entity.cod.death"); - SoundEvent ENTITY_COD_FLOP = SoundEventImpl.get("minecraft:entity.cod.flop"); + SoundEvent ENTITY_COD_FLOP = BuiltinSoundEvent.get("minecraft:entity.cod.flop"); - SoundEvent ENTITY_COD_HURT = SoundEventImpl.get("minecraft:entity.cod.hurt"); + SoundEvent ENTITY_COD_HURT = BuiltinSoundEvent.get("minecraft:entity.cod.hurt"); - SoundEvent BLOCK_COMPARATOR_CLICK = SoundEventImpl.get("minecraft:block.comparator.click"); + SoundEvent BLOCK_COMPARATOR_CLICK = BuiltinSoundEvent.get("minecraft:block.comparator.click"); - SoundEvent BLOCK_COMPOSTER_EMPTY = SoundEventImpl.get("minecraft:block.composter.empty"); + SoundEvent BLOCK_COMPOSTER_EMPTY = BuiltinSoundEvent.get("minecraft:block.composter.empty"); - SoundEvent BLOCK_COMPOSTER_FILL = SoundEventImpl.get("minecraft:block.composter.fill"); + SoundEvent BLOCK_COMPOSTER_FILL = BuiltinSoundEvent.get("minecraft:block.composter.fill"); - SoundEvent BLOCK_COMPOSTER_FILL_SUCCESS = SoundEventImpl.get("minecraft:block.composter.fill_success"); + SoundEvent BLOCK_COMPOSTER_FILL_SUCCESS = BuiltinSoundEvent.get("minecraft:block.composter.fill_success"); - SoundEvent BLOCK_COMPOSTER_READY = SoundEventImpl.get("minecraft:block.composter.ready"); + SoundEvent BLOCK_COMPOSTER_READY = BuiltinSoundEvent.get("minecraft:block.composter.ready"); - SoundEvent BLOCK_CONDUIT_ACTIVATE = SoundEventImpl.get("minecraft:block.conduit.activate"); + SoundEvent BLOCK_CONDUIT_ACTIVATE = BuiltinSoundEvent.get("minecraft:block.conduit.activate"); - SoundEvent BLOCK_CONDUIT_AMBIENT = SoundEventImpl.get("minecraft:block.conduit.ambient"); + SoundEvent BLOCK_CONDUIT_AMBIENT = BuiltinSoundEvent.get("minecraft:block.conduit.ambient"); - SoundEvent BLOCK_CONDUIT_AMBIENT_SHORT = SoundEventImpl.get("minecraft:block.conduit.ambient.short"); + SoundEvent BLOCK_CONDUIT_AMBIENT_SHORT = BuiltinSoundEvent.get("minecraft:block.conduit.ambient.short"); - SoundEvent BLOCK_CONDUIT_ATTACK_TARGET = SoundEventImpl.get("minecraft:block.conduit.attack.target"); + SoundEvent BLOCK_CONDUIT_ATTACK_TARGET = BuiltinSoundEvent.get("minecraft:block.conduit.attack.target"); - SoundEvent BLOCK_CONDUIT_DEACTIVATE = SoundEventImpl.get("minecraft:block.conduit.deactivate"); + SoundEvent BLOCK_CONDUIT_DEACTIVATE = BuiltinSoundEvent.get("minecraft:block.conduit.deactivate"); - SoundEvent BLOCK_COPPER_BULB_BREAK = SoundEventImpl.get("minecraft:block.copper_bulb.break"); + SoundEvent BLOCK_COPPER_BULB_BREAK = BuiltinSoundEvent.get("minecraft:block.copper_bulb.break"); - SoundEvent BLOCK_COPPER_BULB_STEP = SoundEventImpl.get("minecraft:block.copper_bulb.step"); + SoundEvent BLOCK_COPPER_BULB_STEP = BuiltinSoundEvent.get("minecraft:block.copper_bulb.step"); - SoundEvent BLOCK_COPPER_BULB_PLACE = SoundEventImpl.get("minecraft:block.copper_bulb.place"); + SoundEvent BLOCK_COPPER_BULB_PLACE = BuiltinSoundEvent.get("minecraft:block.copper_bulb.place"); - SoundEvent BLOCK_COPPER_BULB_HIT = SoundEventImpl.get("minecraft:block.copper_bulb.hit"); + SoundEvent BLOCK_COPPER_BULB_HIT = BuiltinSoundEvent.get("minecraft:block.copper_bulb.hit"); - SoundEvent BLOCK_COPPER_BULB_FALL = SoundEventImpl.get("minecraft:block.copper_bulb.fall"); + SoundEvent BLOCK_COPPER_BULB_FALL = BuiltinSoundEvent.get("minecraft:block.copper_bulb.fall"); - SoundEvent BLOCK_COPPER_BULB_TURN_ON = SoundEventImpl.get("minecraft:block.copper_bulb.turn_on"); + SoundEvent BLOCK_COPPER_BULB_TURN_ON = BuiltinSoundEvent.get("minecraft:block.copper_bulb.turn_on"); - SoundEvent BLOCK_COPPER_BULB_TURN_OFF = SoundEventImpl.get("minecraft:block.copper_bulb.turn_off"); + SoundEvent BLOCK_COPPER_BULB_TURN_OFF = BuiltinSoundEvent.get("minecraft:block.copper_bulb.turn_off"); - SoundEvent BLOCK_COPPER_BREAK = SoundEventImpl.get("minecraft:block.copper.break"); + SoundEvent BLOCK_COPPER_BREAK = BuiltinSoundEvent.get("minecraft:block.copper.break"); - SoundEvent BLOCK_COPPER_STEP = SoundEventImpl.get("minecraft:block.copper.step"); + SoundEvent BLOCK_COPPER_STEP = BuiltinSoundEvent.get("minecraft:block.copper.step"); - SoundEvent BLOCK_COPPER_PLACE = SoundEventImpl.get("minecraft:block.copper.place"); + SoundEvent BLOCK_COPPER_PLACE = BuiltinSoundEvent.get("minecraft:block.copper.place"); - SoundEvent BLOCK_COPPER_HIT = SoundEventImpl.get("minecraft:block.copper.hit"); + SoundEvent BLOCK_COPPER_HIT = BuiltinSoundEvent.get("minecraft:block.copper.hit"); - SoundEvent BLOCK_COPPER_FALL = SoundEventImpl.get("minecraft:block.copper.fall"); + SoundEvent BLOCK_COPPER_FALL = BuiltinSoundEvent.get("minecraft:block.copper.fall"); - SoundEvent BLOCK_COPPER_DOOR_CLOSE = SoundEventImpl.get("minecraft:block.copper_door.close"); + SoundEvent BLOCK_COPPER_DOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.copper_door.close"); - SoundEvent BLOCK_COPPER_DOOR_OPEN = SoundEventImpl.get("minecraft:block.copper_door.open"); + SoundEvent BLOCK_COPPER_DOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.copper_door.open"); - SoundEvent BLOCK_COPPER_GRATE_BREAK = SoundEventImpl.get("minecraft:block.copper_grate.break"); + SoundEvent BLOCK_COPPER_GRATE_BREAK = BuiltinSoundEvent.get("minecraft:block.copper_grate.break"); - SoundEvent BLOCK_COPPER_GRATE_STEP = SoundEventImpl.get("minecraft:block.copper_grate.step"); + SoundEvent BLOCK_COPPER_GRATE_STEP = BuiltinSoundEvent.get("minecraft:block.copper_grate.step"); - SoundEvent BLOCK_COPPER_GRATE_PLACE = SoundEventImpl.get("minecraft:block.copper_grate.place"); + SoundEvent BLOCK_COPPER_GRATE_PLACE = BuiltinSoundEvent.get("minecraft:block.copper_grate.place"); - SoundEvent BLOCK_COPPER_GRATE_HIT = SoundEventImpl.get("minecraft:block.copper_grate.hit"); + SoundEvent BLOCK_COPPER_GRATE_HIT = BuiltinSoundEvent.get("minecraft:block.copper_grate.hit"); - SoundEvent BLOCK_COPPER_GRATE_FALL = SoundEventImpl.get("minecraft:block.copper_grate.fall"); + SoundEvent BLOCK_COPPER_GRATE_FALL = BuiltinSoundEvent.get("minecraft:block.copper_grate.fall"); - SoundEvent BLOCK_COPPER_TRAPDOOR_CLOSE = SoundEventImpl.get("minecraft:block.copper_trapdoor.close"); + SoundEvent BLOCK_COPPER_TRAPDOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.copper_trapdoor.close"); - SoundEvent BLOCK_COPPER_TRAPDOOR_OPEN = SoundEventImpl.get("minecraft:block.copper_trapdoor.open"); + SoundEvent BLOCK_COPPER_TRAPDOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.copper_trapdoor.open"); - SoundEvent BLOCK_CORAL_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.coral_block.break"); + SoundEvent BLOCK_CORAL_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.coral_block.break"); - SoundEvent BLOCK_CORAL_BLOCK_FALL = SoundEventImpl.get("minecraft:block.coral_block.fall"); + SoundEvent BLOCK_CORAL_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.coral_block.fall"); - SoundEvent BLOCK_CORAL_BLOCK_HIT = SoundEventImpl.get("minecraft:block.coral_block.hit"); + SoundEvent BLOCK_CORAL_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.coral_block.hit"); - SoundEvent BLOCK_CORAL_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.coral_block.place"); + SoundEvent BLOCK_CORAL_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.coral_block.place"); - SoundEvent BLOCK_CORAL_BLOCK_STEP = SoundEventImpl.get("minecraft:block.coral_block.step"); + SoundEvent BLOCK_CORAL_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.coral_block.step"); - SoundEvent ENTITY_COW_AMBIENT = SoundEventImpl.get("minecraft:entity.cow.ambient"); + SoundEvent ENTITY_COW_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.cow.ambient"); - SoundEvent ENTITY_COW_DEATH = SoundEventImpl.get("minecraft:entity.cow.death"); + SoundEvent ENTITY_COW_DEATH = BuiltinSoundEvent.get("minecraft:entity.cow.death"); - SoundEvent ENTITY_COW_HURT = SoundEventImpl.get("minecraft:entity.cow.hurt"); + SoundEvent ENTITY_COW_HURT = BuiltinSoundEvent.get("minecraft:entity.cow.hurt"); - SoundEvent ENTITY_COW_MILK = SoundEventImpl.get("minecraft:entity.cow.milk"); + SoundEvent ENTITY_COW_MILK = BuiltinSoundEvent.get("minecraft:entity.cow.milk"); - SoundEvent ENTITY_COW_STEP = SoundEventImpl.get("minecraft:entity.cow.step"); + SoundEvent ENTITY_COW_STEP = BuiltinSoundEvent.get("minecraft:entity.cow.step"); - SoundEvent BLOCK_CRAFTER_CRAFT = SoundEventImpl.get("minecraft:block.crafter.craft"); + SoundEvent BLOCK_CRAFTER_CRAFT = BuiltinSoundEvent.get("minecraft:block.crafter.craft"); - SoundEvent BLOCK_CRAFTER_FAIL = SoundEventImpl.get("minecraft:block.crafter.fail"); + SoundEvent BLOCK_CRAFTER_FAIL = BuiltinSoundEvent.get("minecraft:block.crafter.fail"); - SoundEvent ENTITY_CREEPER_DEATH = SoundEventImpl.get("minecraft:entity.creeper.death"); + SoundEvent ENTITY_CREEPER_DEATH = BuiltinSoundEvent.get("minecraft:entity.creeper.death"); - SoundEvent ENTITY_CREEPER_HURT = SoundEventImpl.get("minecraft:entity.creeper.hurt"); + SoundEvent ENTITY_CREEPER_HURT = BuiltinSoundEvent.get("minecraft:entity.creeper.hurt"); - SoundEvent ENTITY_CREEPER_PRIMED = SoundEventImpl.get("minecraft:entity.creeper.primed"); + SoundEvent ENTITY_CREEPER_PRIMED = BuiltinSoundEvent.get("minecraft:entity.creeper.primed"); - SoundEvent BLOCK_CROP_BREAK = SoundEventImpl.get("minecraft:block.crop.break"); + SoundEvent BLOCK_CROP_BREAK = BuiltinSoundEvent.get("minecraft:block.crop.break"); - SoundEvent ITEM_CROP_PLANT = SoundEventImpl.get("minecraft:item.crop.plant"); + SoundEvent ITEM_CROP_PLANT = BuiltinSoundEvent.get("minecraft:item.crop.plant"); - SoundEvent ITEM_CROSSBOW_HIT = SoundEventImpl.get("minecraft:item.crossbow.hit"); + SoundEvent ITEM_CROSSBOW_HIT = BuiltinSoundEvent.get("minecraft:item.crossbow.hit"); - SoundEvent ITEM_CROSSBOW_LOADING_END = SoundEventImpl.get("minecraft:item.crossbow.loading_end"); + SoundEvent ITEM_CROSSBOW_LOADING_END = BuiltinSoundEvent.get("minecraft:item.crossbow.loading_end"); - SoundEvent ITEM_CROSSBOW_LOADING_MIDDLE = SoundEventImpl.get("minecraft:item.crossbow.loading_middle"); + SoundEvent ITEM_CROSSBOW_LOADING_MIDDLE = BuiltinSoundEvent.get("minecraft:item.crossbow.loading_middle"); - SoundEvent ITEM_CROSSBOW_LOADING_START = SoundEventImpl.get("minecraft:item.crossbow.loading_start"); + SoundEvent ITEM_CROSSBOW_LOADING_START = BuiltinSoundEvent.get("minecraft:item.crossbow.loading_start"); - SoundEvent ITEM_CROSSBOW_QUICK_CHARGE_1 = SoundEventImpl.get("minecraft:item.crossbow.quick_charge_1"); + SoundEvent ITEM_CROSSBOW_QUICK_CHARGE_1 = BuiltinSoundEvent.get("minecraft:item.crossbow.quick_charge_1"); - SoundEvent ITEM_CROSSBOW_QUICK_CHARGE_2 = SoundEventImpl.get("minecraft:item.crossbow.quick_charge_2"); + SoundEvent ITEM_CROSSBOW_QUICK_CHARGE_2 = BuiltinSoundEvent.get("minecraft:item.crossbow.quick_charge_2"); - SoundEvent ITEM_CROSSBOW_QUICK_CHARGE_3 = SoundEventImpl.get("minecraft:item.crossbow.quick_charge_3"); + SoundEvent ITEM_CROSSBOW_QUICK_CHARGE_3 = BuiltinSoundEvent.get("minecraft:item.crossbow.quick_charge_3"); - SoundEvent ITEM_CROSSBOW_SHOOT = SoundEventImpl.get("minecraft:item.crossbow.shoot"); + SoundEvent ITEM_CROSSBOW_SHOOT = BuiltinSoundEvent.get("minecraft:item.crossbow.shoot"); - SoundEvent BLOCK_DECORATED_POT_BREAK = SoundEventImpl.get("minecraft:block.decorated_pot.break"); + SoundEvent BLOCK_DECORATED_POT_BREAK = BuiltinSoundEvent.get("minecraft:block.decorated_pot.break"); - SoundEvent BLOCK_DECORATED_POT_FALL = SoundEventImpl.get("minecraft:block.decorated_pot.fall"); + SoundEvent BLOCK_DECORATED_POT_FALL = BuiltinSoundEvent.get("minecraft:block.decorated_pot.fall"); - SoundEvent BLOCK_DECORATED_POT_HIT = SoundEventImpl.get("minecraft:block.decorated_pot.hit"); + SoundEvent BLOCK_DECORATED_POT_HIT = BuiltinSoundEvent.get("minecraft:block.decorated_pot.hit"); - SoundEvent BLOCK_DECORATED_POT_INSERT = SoundEventImpl.get("minecraft:block.decorated_pot.insert"); + SoundEvent BLOCK_DECORATED_POT_INSERT = BuiltinSoundEvent.get("minecraft:block.decorated_pot.insert"); - SoundEvent BLOCK_DECORATED_POT_INSERT_FAIL = SoundEventImpl.get("minecraft:block.decorated_pot.insert_fail"); + SoundEvent BLOCK_DECORATED_POT_INSERT_FAIL = BuiltinSoundEvent.get("minecraft:block.decorated_pot.insert_fail"); - SoundEvent BLOCK_DECORATED_POT_STEP = SoundEventImpl.get("minecraft:block.decorated_pot.step"); + SoundEvent BLOCK_DECORATED_POT_STEP = BuiltinSoundEvent.get("minecraft:block.decorated_pot.step"); - SoundEvent BLOCK_DECORATED_POT_PLACE = SoundEventImpl.get("minecraft:block.decorated_pot.place"); + SoundEvent BLOCK_DECORATED_POT_PLACE = BuiltinSoundEvent.get("minecraft:block.decorated_pot.place"); - SoundEvent BLOCK_DECORATED_POT_SHATTER = SoundEventImpl.get("minecraft:block.decorated_pot.shatter"); + SoundEvent BLOCK_DECORATED_POT_SHATTER = BuiltinSoundEvent.get("minecraft:block.decorated_pot.shatter"); - SoundEvent BLOCK_DEEPSLATE_BRICKS_BREAK = SoundEventImpl.get("minecraft:block.deepslate_bricks.break"); + SoundEvent BLOCK_DEEPSLATE_BRICKS_BREAK = BuiltinSoundEvent.get("minecraft:block.deepslate_bricks.break"); - SoundEvent BLOCK_DEEPSLATE_BRICKS_FALL = SoundEventImpl.get("minecraft:block.deepslate_bricks.fall"); + SoundEvent BLOCK_DEEPSLATE_BRICKS_FALL = BuiltinSoundEvent.get("minecraft:block.deepslate_bricks.fall"); - SoundEvent BLOCK_DEEPSLATE_BRICKS_HIT = SoundEventImpl.get("minecraft:block.deepslate_bricks.hit"); + SoundEvent BLOCK_DEEPSLATE_BRICKS_HIT = BuiltinSoundEvent.get("minecraft:block.deepslate_bricks.hit"); - SoundEvent BLOCK_DEEPSLATE_BRICKS_PLACE = SoundEventImpl.get("minecraft:block.deepslate_bricks.place"); + SoundEvent BLOCK_DEEPSLATE_BRICKS_PLACE = BuiltinSoundEvent.get("minecraft:block.deepslate_bricks.place"); - SoundEvent BLOCK_DEEPSLATE_BRICKS_STEP = SoundEventImpl.get("minecraft:block.deepslate_bricks.step"); + SoundEvent BLOCK_DEEPSLATE_BRICKS_STEP = BuiltinSoundEvent.get("minecraft:block.deepslate_bricks.step"); - SoundEvent BLOCK_DEEPSLATE_BREAK = SoundEventImpl.get("minecraft:block.deepslate.break"); + SoundEvent BLOCK_DEEPSLATE_BREAK = BuiltinSoundEvent.get("minecraft:block.deepslate.break"); - SoundEvent BLOCK_DEEPSLATE_FALL = SoundEventImpl.get("minecraft:block.deepslate.fall"); + SoundEvent BLOCK_DEEPSLATE_FALL = BuiltinSoundEvent.get("minecraft:block.deepslate.fall"); - SoundEvent BLOCK_DEEPSLATE_HIT = SoundEventImpl.get("minecraft:block.deepslate.hit"); + SoundEvent BLOCK_DEEPSLATE_HIT = BuiltinSoundEvent.get("minecraft:block.deepslate.hit"); - SoundEvent BLOCK_DEEPSLATE_PLACE = SoundEventImpl.get("minecraft:block.deepslate.place"); + SoundEvent BLOCK_DEEPSLATE_PLACE = BuiltinSoundEvent.get("minecraft:block.deepslate.place"); - SoundEvent BLOCK_DEEPSLATE_STEP = SoundEventImpl.get("minecraft:block.deepslate.step"); + SoundEvent BLOCK_DEEPSLATE_STEP = BuiltinSoundEvent.get("minecraft:block.deepslate.step"); - SoundEvent BLOCK_DEEPSLATE_TILES_BREAK = SoundEventImpl.get("minecraft:block.deepslate_tiles.break"); + SoundEvent BLOCK_DEEPSLATE_TILES_BREAK = BuiltinSoundEvent.get("minecraft:block.deepslate_tiles.break"); - SoundEvent BLOCK_DEEPSLATE_TILES_FALL = SoundEventImpl.get("minecraft:block.deepslate_tiles.fall"); + SoundEvent BLOCK_DEEPSLATE_TILES_FALL = BuiltinSoundEvent.get("minecraft:block.deepslate_tiles.fall"); - SoundEvent BLOCK_DEEPSLATE_TILES_HIT = SoundEventImpl.get("minecraft:block.deepslate_tiles.hit"); + SoundEvent BLOCK_DEEPSLATE_TILES_HIT = BuiltinSoundEvent.get("minecraft:block.deepslate_tiles.hit"); - SoundEvent BLOCK_DEEPSLATE_TILES_PLACE = SoundEventImpl.get("minecraft:block.deepslate_tiles.place"); + SoundEvent BLOCK_DEEPSLATE_TILES_PLACE = BuiltinSoundEvent.get("minecraft:block.deepslate_tiles.place"); - SoundEvent BLOCK_DEEPSLATE_TILES_STEP = SoundEventImpl.get("minecraft:block.deepslate_tiles.step"); + SoundEvent BLOCK_DEEPSLATE_TILES_STEP = BuiltinSoundEvent.get("minecraft:block.deepslate_tiles.step"); - SoundEvent BLOCK_DISPENSER_DISPENSE = SoundEventImpl.get("minecraft:block.dispenser.dispense"); + SoundEvent BLOCK_DISPENSER_DISPENSE = BuiltinSoundEvent.get("minecraft:block.dispenser.dispense"); - SoundEvent BLOCK_DISPENSER_FAIL = SoundEventImpl.get("minecraft:block.dispenser.fail"); + SoundEvent BLOCK_DISPENSER_FAIL = BuiltinSoundEvent.get("minecraft:block.dispenser.fail"); - SoundEvent BLOCK_DISPENSER_LAUNCH = SoundEventImpl.get("minecraft:block.dispenser.launch"); + SoundEvent BLOCK_DISPENSER_LAUNCH = BuiltinSoundEvent.get("minecraft:block.dispenser.launch"); - SoundEvent ENTITY_DOLPHIN_AMBIENT = SoundEventImpl.get("minecraft:entity.dolphin.ambient"); + SoundEvent ENTITY_DOLPHIN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.dolphin.ambient"); - SoundEvent ENTITY_DOLPHIN_AMBIENT_WATER = SoundEventImpl.get("minecraft:entity.dolphin.ambient_water"); + SoundEvent ENTITY_DOLPHIN_AMBIENT_WATER = BuiltinSoundEvent.get("minecraft:entity.dolphin.ambient_water"); - SoundEvent ENTITY_DOLPHIN_ATTACK = SoundEventImpl.get("minecraft:entity.dolphin.attack"); + SoundEvent ENTITY_DOLPHIN_ATTACK = BuiltinSoundEvent.get("minecraft:entity.dolphin.attack"); - SoundEvent ENTITY_DOLPHIN_DEATH = SoundEventImpl.get("minecraft:entity.dolphin.death"); + SoundEvent ENTITY_DOLPHIN_DEATH = BuiltinSoundEvent.get("minecraft:entity.dolphin.death"); - SoundEvent ENTITY_DOLPHIN_EAT = SoundEventImpl.get("minecraft:entity.dolphin.eat"); + SoundEvent ENTITY_DOLPHIN_EAT = BuiltinSoundEvent.get("minecraft:entity.dolphin.eat"); - SoundEvent ENTITY_DOLPHIN_HURT = SoundEventImpl.get("minecraft:entity.dolphin.hurt"); + SoundEvent ENTITY_DOLPHIN_HURT = BuiltinSoundEvent.get("minecraft:entity.dolphin.hurt"); - SoundEvent ENTITY_DOLPHIN_JUMP = SoundEventImpl.get("minecraft:entity.dolphin.jump"); + SoundEvent ENTITY_DOLPHIN_JUMP = BuiltinSoundEvent.get("minecraft:entity.dolphin.jump"); - SoundEvent ENTITY_DOLPHIN_PLAY = SoundEventImpl.get("minecraft:entity.dolphin.play"); + SoundEvent ENTITY_DOLPHIN_PLAY = BuiltinSoundEvent.get("minecraft:entity.dolphin.play"); - SoundEvent ENTITY_DOLPHIN_SPLASH = SoundEventImpl.get("minecraft:entity.dolphin.splash"); + SoundEvent ENTITY_DOLPHIN_SPLASH = BuiltinSoundEvent.get("minecraft:entity.dolphin.splash"); - SoundEvent ENTITY_DOLPHIN_SWIM = SoundEventImpl.get("minecraft:entity.dolphin.swim"); + SoundEvent ENTITY_DOLPHIN_SWIM = BuiltinSoundEvent.get("minecraft:entity.dolphin.swim"); - SoundEvent ENTITY_DONKEY_AMBIENT = SoundEventImpl.get("minecraft:entity.donkey.ambient"); + SoundEvent ENTITY_DONKEY_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.donkey.ambient"); - SoundEvent ENTITY_DONKEY_ANGRY = SoundEventImpl.get("minecraft:entity.donkey.angry"); + SoundEvent ENTITY_DONKEY_ANGRY = BuiltinSoundEvent.get("minecraft:entity.donkey.angry"); - SoundEvent ENTITY_DONKEY_CHEST = SoundEventImpl.get("minecraft:entity.donkey.chest"); + SoundEvent ENTITY_DONKEY_CHEST = BuiltinSoundEvent.get("minecraft:entity.donkey.chest"); - SoundEvent ENTITY_DONKEY_DEATH = SoundEventImpl.get("minecraft:entity.donkey.death"); + SoundEvent ENTITY_DONKEY_DEATH = BuiltinSoundEvent.get("minecraft:entity.donkey.death"); - SoundEvent ENTITY_DONKEY_EAT = SoundEventImpl.get("minecraft:entity.donkey.eat"); + SoundEvent ENTITY_DONKEY_EAT = BuiltinSoundEvent.get("minecraft:entity.donkey.eat"); - SoundEvent ENTITY_DONKEY_HURT = SoundEventImpl.get("minecraft:entity.donkey.hurt"); + SoundEvent ENTITY_DONKEY_HURT = BuiltinSoundEvent.get("minecraft:entity.donkey.hurt"); - SoundEvent ENTITY_DONKEY_JUMP = SoundEventImpl.get("minecraft:entity.donkey.jump"); + SoundEvent ENTITY_DONKEY_JUMP = BuiltinSoundEvent.get("minecraft:entity.donkey.jump"); - SoundEvent BLOCK_DRIPSTONE_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.dripstone_block.break"); + SoundEvent BLOCK_DRIPSTONE_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.dripstone_block.break"); - SoundEvent BLOCK_DRIPSTONE_BLOCK_STEP = SoundEventImpl.get("minecraft:block.dripstone_block.step"); + SoundEvent BLOCK_DRIPSTONE_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.dripstone_block.step"); - SoundEvent BLOCK_DRIPSTONE_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.dripstone_block.place"); + SoundEvent BLOCK_DRIPSTONE_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.dripstone_block.place"); - SoundEvent BLOCK_DRIPSTONE_BLOCK_HIT = SoundEventImpl.get("minecraft:block.dripstone_block.hit"); + SoundEvent BLOCK_DRIPSTONE_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.dripstone_block.hit"); - SoundEvent BLOCK_DRIPSTONE_BLOCK_FALL = SoundEventImpl.get("minecraft:block.dripstone_block.fall"); + SoundEvent BLOCK_DRIPSTONE_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.dripstone_block.fall"); - SoundEvent BLOCK_POINTED_DRIPSTONE_BREAK = SoundEventImpl.get("minecraft:block.pointed_dripstone.break"); + SoundEvent BLOCK_POINTED_DRIPSTONE_BREAK = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.break"); - SoundEvent BLOCK_POINTED_DRIPSTONE_STEP = SoundEventImpl.get("minecraft:block.pointed_dripstone.step"); + SoundEvent BLOCK_POINTED_DRIPSTONE_STEP = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.step"); - SoundEvent BLOCK_POINTED_DRIPSTONE_PLACE = SoundEventImpl.get("minecraft:block.pointed_dripstone.place"); + SoundEvent BLOCK_POINTED_DRIPSTONE_PLACE = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.place"); - SoundEvent BLOCK_POINTED_DRIPSTONE_HIT = SoundEventImpl.get("minecraft:block.pointed_dripstone.hit"); + SoundEvent BLOCK_POINTED_DRIPSTONE_HIT = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.hit"); - SoundEvent BLOCK_POINTED_DRIPSTONE_FALL = SoundEventImpl.get("minecraft:block.pointed_dripstone.fall"); + SoundEvent BLOCK_POINTED_DRIPSTONE_FALL = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.fall"); - SoundEvent BLOCK_POINTED_DRIPSTONE_LAND = SoundEventImpl.get("minecraft:block.pointed_dripstone.land"); + SoundEvent BLOCK_POINTED_DRIPSTONE_LAND = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.land"); - SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_LAVA = SoundEventImpl.get("minecraft:block.pointed_dripstone.drip_lava"); + SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_LAVA = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.drip_lava"); - SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_WATER = SoundEventImpl.get("minecraft:block.pointed_dripstone.drip_water"); + SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_WATER = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.drip_water"); - SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_LAVA_INTO_CAULDRON = SoundEventImpl.get("minecraft:block.pointed_dripstone.drip_lava_into_cauldron"); + SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_LAVA_INTO_CAULDRON = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.drip_lava_into_cauldron"); - SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_WATER_INTO_CAULDRON = SoundEventImpl.get("minecraft:block.pointed_dripstone.drip_water_into_cauldron"); + SoundEvent BLOCK_POINTED_DRIPSTONE_DRIP_WATER_INTO_CAULDRON = BuiltinSoundEvent.get("minecraft:block.pointed_dripstone.drip_water_into_cauldron"); - SoundEvent BLOCK_BIG_DRIPLEAF_TILT_DOWN = SoundEventImpl.get("minecraft:block.big_dripleaf.tilt_down"); + SoundEvent BLOCK_BIG_DRIPLEAF_TILT_DOWN = BuiltinSoundEvent.get("minecraft:block.big_dripleaf.tilt_down"); - SoundEvent BLOCK_BIG_DRIPLEAF_TILT_UP = SoundEventImpl.get("minecraft:block.big_dripleaf.tilt_up"); + SoundEvent BLOCK_BIG_DRIPLEAF_TILT_UP = BuiltinSoundEvent.get("minecraft:block.big_dripleaf.tilt_up"); - SoundEvent ENTITY_DROWNED_AMBIENT = SoundEventImpl.get("minecraft:entity.drowned.ambient"); + SoundEvent ENTITY_DROWNED_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.drowned.ambient"); - SoundEvent ENTITY_DROWNED_AMBIENT_WATER = SoundEventImpl.get("minecraft:entity.drowned.ambient_water"); + SoundEvent ENTITY_DROWNED_AMBIENT_WATER = BuiltinSoundEvent.get("minecraft:entity.drowned.ambient_water"); - SoundEvent ENTITY_DROWNED_DEATH = SoundEventImpl.get("minecraft:entity.drowned.death"); + SoundEvent ENTITY_DROWNED_DEATH = BuiltinSoundEvent.get("minecraft:entity.drowned.death"); - SoundEvent ENTITY_DROWNED_DEATH_WATER = SoundEventImpl.get("minecraft:entity.drowned.death_water"); + SoundEvent ENTITY_DROWNED_DEATH_WATER = BuiltinSoundEvent.get("minecraft:entity.drowned.death_water"); - SoundEvent ENTITY_DROWNED_HURT = SoundEventImpl.get("minecraft:entity.drowned.hurt"); + SoundEvent ENTITY_DROWNED_HURT = BuiltinSoundEvent.get("minecraft:entity.drowned.hurt"); - SoundEvent ENTITY_DROWNED_HURT_WATER = SoundEventImpl.get("minecraft:entity.drowned.hurt_water"); + SoundEvent ENTITY_DROWNED_HURT_WATER = BuiltinSoundEvent.get("minecraft:entity.drowned.hurt_water"); - SoundEvent ENTITY_DROWNED_SHOOT = SoundEventImpl.get("minecraft:entity.drowned.shoot"); + SoundEvent ENTITY_DROWNED_SHOOT = BuiltinSoundEvent.get("minecraft:entity.drowned.shoot"); - SoundEvent ENTITY_DROWNED_STEP = SoundEventImpl.get("minecraft:entity.drowned.step"); + SoundEvent ENTITY_DROWNED_STEP = BuiltinSoundEvent.get("minecraft:entity.drowned.step"); - SoundEvent ENTITY_DROWNED_SWIM = SoundEventImpl.get("minecraft:entity.drowned.swim"); + SoundEvent ENTITY_DROWNED_SWIM = BuiltinSoundEvent.get("minecraft:entity.drowned.swim"); - SoundEvent ITEM_DYE_USE = SoundEventImpl.get("minecraft:item.dye.use"); + SoundEvent ITEM_DYE_USE = BuiltinSoundEvent.get("minecraft:item.dye.use"); - SoundEvent ENTITY_EGG_THROW = SoundEventImpl.get("minecraft:entity.egg.throw"); + SoundEvent ENTITY_EGG_THROW = BuiltinSoundEvent.get("minecraft:entity.egg.throw"); - SoundEvent ENTITY_ELDER_GUARDIAN_AMBIENT = SoundEventImpl.get("minecraft:entity.elder_guardian.ambient"); + SoundEvent ENTITY_ELDER_GUARDIAN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.ambient"); - SoundEvent ENTITY_ELDER_GUARDIAN_AMBIENT_LAND = SoundEventImpl.get("minecraft:entity.elder_guardian.ambient_land"); + SoundEvent ENTITY_ELDER_GUARDIAN_AMBIENT_LAND = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.ambient_land"); - SoundEvent ENTITY_ELDER_GUARDIAN_CURSE = SoundEventImpl.get("minecraft:entity.elder_guardian.curse"); + SoundEvent ENTITY_ELDER_GUARDIAN_CURSE = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.curse"); - SoundEvent ENTITY_ELDER_GUARDIAN_DEATH = SoundEventImpl.get("minecraft:entity.elder_guardian.death"); + SoundEvent ENTITY_ELDER_GUARDIAN_DEATH = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.death"); - SoundEvent ENTITY_ELDER_GUARDIAN_DEATH_LAND = SoundEventImpl.get("minecraft:entity.elder_guardian.death_land"); + SoundEvent ENTITY_ELDER_GUARDIAN_DEATH_LAND = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.death_land"); - SoundEvent ENTITY_ELDER_GUARDIAN_FLOP = SoundEventImpl.get("minecraft:entity.elder_guardian.flop"); + SoundEvent ENTITY_ELDER_GUARDIAN_FLOP = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.flop"); - SoundEvent ENTITY_ELDER_GUARDIAN_HURT = SoundEventImpl.get("minecraft:entity.elder_guardian.hurt"); + SoundEvent ENTITY_ELDER_GUARDIAN_HURT = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.hurt"); - SoundEvent ENTITY_ELDER_GUARDIAN_HURT_LAND = SoundEventImpl.get("minecraft:entity.elder_guardian.hurt_land"); + SoundEvent ENTITY_ELDER_GUARDIAN_HURT_LAND = BuiltinSoundEvent.get("minecraft:entity.elder_guardian.hurt_land"); - SoundEvent ITEM_ELYTRA_FLYING = SoundEventImpl.get("minecraft:item.elytra.flying"); + SoundEvent ITEM_ELYTRA_FLYING = BuiltinSoundEvent.get("minecraft:item.elytra.flying"); - SoundEvent BLOCK_ENCHANTMENT_TABLE_USE = SoundEventImpl.get("minecraft:block.enchantment_table.use"); + SoundEvent BLOCK_ENCHANTMENT_TABLE_USE = BuiltinSoundEvent.get("minecraft:block.enchantment_table.use"); - SoundEvent BLOCK_ENDER_CHEST_CLOSE = SoundEventImpl.get("minecraft:block.ender_chest.close"); + SoundEvent BLOCK_ENDER_CHEST_CLOSE = BuiltinSoundEvent.get("minecraft:block.ender_chest.close"); - SoundEvent BLOCK_ENDER_CHEST_OPEN = SoundEventImpl.get("minecraft:block.ender_chest.open"); + SoundEvent BLOCK_ENDER_CHEST_OPEN = BuiltinSoundEvent.get("minecraft:block.ender_chest.open"); - SoundEvent ENTITY_ENDER_DRAGON_AMBIENT = SoundEventImpl.get("minecraft:entity.ender_dragon.ambient"); + SoundEvent ENTITY_ENDER_DRAGON_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.ender_dragon.ambient"); - SoundEvent ENTITY_ENDER_DRAGON_DEATH = SoundEventImpl.get("minecraft:entity.ender_dragon.death"); + SoundEvent ENTITY_ENDER_DRAGON_DEATH = BuiltinSoundEvent.get("minecraft:entity.ender_dragon.death"); - SoundEvent ENTITY_DRAGON_FIREBALL_EXPLODE = SoundEventImpl.get("minecraft:entity.dragon_fireball.explode"); + SoundEvent ENTITY_DRAGON_FIREBALL_EXPLODE = BuiltinSoundEvent.get("minecraft:entity.dragon_fireball.explode"); - SoundEvent ENTITY_ENDER_DRAGON_FLAP = SoundEventImpl.get("minecraft:entity.ender_dragon.flap"); + SoundEvent ENTITY_ENDER_DRAGON_FLAP = BuiltinSoundEvent.get("minecraft:entity.ender_dragon.flap"); - SoundEvent ENTITY_ENDER_DRAGON_GROWL = SoundEventImpl.get("minecraft:entity.ender_dragon.growl"); + SoundEvent ENTITY_ENDER_DRAGON_GROWL = BuiltinSoundEvent.get("minecraft:entity.ender_dragon.growl"); - SoundEvent ENTITY_ENDER_DRAGON_HURT = SoundEventImpl.get("minecraft:entity.ender_dragon.hurt"); + SoundEvent ENTITY_ENDER_DRAGON_HURT = BuiltinSoundEvent.get("minecraft:entity.ender_dragon.hurt"); - SoundEvent ENTITY_ENDER_DRAGON_SHOOT = SoundEventImpl.get("minecraft:entity.ender_dragon.shoot"); + SoundEvent ENTITY_ENDER_DRAGON_SHOOT = BuiltinSoundEvent.get("minecraft:entity.ender_dragon.shoot"); - SoundEvent ENTITY_ENDER_EYE_DEATH = SoundEventImpl.get("minecraft:entity.ender_eye.death"); + SoundEvent ENTITY_ENDER_EYE_DEATH = BuiltinSoundEvent.get("minecraft:entity.ender_eye.death"); - SoundEvent ENTITY_ENDER_EYE_LAUNCH = SoundEventImpl.get("minecraft:entity.ender_eye.launch"); + SoundEvent ENTITY_ENDER_EYE_LAUNCH = BuiltinSoundEvent.get("minecraft:entity.ender_eye.launch"); - SoundEvent ENTITY_ENDERMAN_AMBIENT = SoundEventImpl.get("minecraft:entity.enderman.ambient"); + SoundEvent ENTITY_ENDERMAN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.enderman.ambient"); - SoundEvent ENTITY_ENDERMAN_DEATH = SoundEventImpl.get("minecraft:entity.enderman.death"); + SoundEvent ENTITY_ENDERMAN_DEATH = BuiltinSoundEvent.get("minecraft:entity.enderman.death"); - SoundEvent ENTITY_ENDERMAN_HURT = SoundEventImpl.get("minecraft:entity.enderman.hurt"); + SoundEvent ENTITY_ENDERMAN_HURT = BuiltinSoundEvent.get("minecraft:entity.enderman.hurt"); - SoundEvent ENTITY_ENDERMAN_SCREAM = SoundEventImpl.get("minecraft:entity.enderman.scream"); + SoundEvent ENTITY_ENDERMAN_SCREAM = BuiltinSoundEvent.get("minecraft:entity.enderman.scream"); - SoundEvent ENTITY_ENDERMAN_STARE = SoundEventImpl.get("minecraft:entity.enderman.stare"); + SoundEvent ENTITY_ENDERMAN_STARE = BuiltinSoundEvent.get("minecraft:entity.enderman.stare"); - SoundEvent ENTITY_ENDERMAN_TELEPORT = SoundEventImpl.get("minecraft:entity.enderman.teleport"); + SoundEvent ENTITY_ENDERMAN_TELEPORT = BuiltinSoundEvent.get("minecraft:entity.enderman.teleport"); - SoundEvent ENTITY_ENDERMITE_AMBIENT = SoundEventImpl.get("minecraft:entity.endermite.ambient"); + SoundEvent ENTITY_ENDERMITE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.endermite.ambient"); - SoundEvent ENTITY_ENDERMITE_DEATH = SoundEventImpl.get("minecraft:entity.endermite.death"); + SoundEvent ENTITY_ENDERMITE_DEATH = BuiltinSoundEvent.get("minecraft:entity.endermite.death"); - SoundEvent ENTITY_ENDERMITE_HURT = SoundEventImpl.get("minecraft:entity.endermite.hurt"); + SoundEvent ENTITY_ENDERMITE_HURT = BuiltinSoundEvent.get("minecraft:entity.endermite.hurt"); - SoundEvent ENTITY_ENDERMITE_STEP = SoundEventImpl.get("minecraft:entity.endermite.step"); + SoundEvent ENTITY_ENDERMITE_STEP = BuiltinSoundEvent.get("minecraft:entity.endermite.step"); - SoundEvent ENTITY_ENDER_PEARL_THROW = SoundEventImpl.get("minecraft:entity.ender_pearl.throw"); + SoundEvent ENTITY_ENDER_PEARL_THROW = BuiltinSoundEvent.get("minecraft:entity.ender_pearl.throw"); - SoundEvent BLOCK_END_GATEWAY_SPAWN = SoundEventImpl.get("minecraft:block.end_gateway.spawn"); + SoundEvent BLOCK_END_GATEWAY_SPAWN = BuiltinSoundEvent.get("minecraft:block.end_gateway.spawn"); - SoundEvent BLOCK_END_PORTAL_FRAME_FILL = SoundEventImpl.get("minecraft:block.end_portal_frame.fill"); + SoundEvent BLOCK_END_PORTAL_FRAME_FILL = BuiltinSoundEvent.get("minecraft:block.end_portal_frame.fill"); - SoundEvent BLOCK_END_PORTAL_SPAWN = SoundEventImpl.get("minecraft:block.end_portal.spawn"); + SoundEvent BLOCK_END_PORTAL_SPAWN = BuiltinSoundEvent.get("minecraft:block.end_portal.spawn"); - SoundEvent ENTITY_EVOKER_AMBIENT = SoundEventImpl.get("minecraft:entity.evoker.ambient"); + SoundEvent ENTITY_EVOKER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.evoker.ambient"); - SoundEvent ENTITY_EVOKER_CAST_SPELL = SoundEventImpl.get("minecraft:entity.evoker.cast_spell"); + SoundEvent ENTITY_EVOKER_CAST_SPELL = BuiltinSoundEvent.get("minecraft:entity.evoker.cast_spell"); - SoundEvent ENTITY_EVOKER_CELEBRATE = SoundEventImpl.get("minecraft:entity.evoker.celebrate"); + SoundEvent ENTITY_EVOKER_CELEBRATE = BuiltinSoundEvent.get("minecraft:entity.evoker.celebrate"); - SoundEvent ENTITY_EVOKER_DEATH = SoundEventImpl.get("minecraft:entity.evoker.death"); + SoundEvent ENTITY_EVOKER_DEATH = BuiltinSoundEvent.get("minecraft:entity.evoker.death"); - SoundEvent ENTITY_EVOKER_FANGS_ATTACK = SoundEventImpl.get("minecraft:entity.evoker_fangs.attack"); + SoundEvent ENTITY_EVOKER_FANGS_ATTACK = BuiltinSoundEvent.get("minecraft:entity.evoker_fangs.attack"); - SoundEvent ENTITY_EVOKER_HURT = SoundEventImpl.get("minecraft:entity.evoker.hurt"); + SoundEvent ENTITY_EVOKER_HURT = BuiltinSoundEvent.get("minecraft:entity.evoker.hurt"); - SoundEvent ENTITY_EVOKER_PREPARE_ATTACK = SoundEventImpl.get("minecraft:entity.evoker.prepare_attack"); + SoundEvent ENTITY_EVOKER_PREPARE_ATTACK = BuiltinSoundEvent.get("minecraft:entity.evoker.prepare_attack"); - SoundEvent ENTITY_EVOKER_PREPARE_SUMMON = SoundEventImpl.get("minecraft:entity.evoker.prepare_summon"); + SoundEvent ENTITY_EVOKER_PREPARE_SUMMON = BuiltinSoundEvent.get("minecraft:entity.evoker.prepare_summon"); - SoundEvent ENTITY_EVOKER_PREPARE_WOLOLO = SoundEventImpl.get("minecraft:entity.evoker.prepare_wololo"); + SoundEvent ENTITY_EVOKER_PREPARE_WOLOLO = BuiltinSoundEvent.get("minecraft:entity.evoker.prepare_wololo"); - SoundEvent ENTITY_EXPERIENCE_BOTTLE_THROW = SoundEventImpl.get("minecraft:entity.experience_bottle.throw"); + SoundEvent ENTITY_EXPERIENCE_BOTTLE_THROW = BuiltinSoundEvent.get("minecraft:entity.experience_bottle.throw"); - SoundEvent ENTITY_EXPERIENCE_ORB_PICKUP = SoundEventImpl.get("minecraft:entity.experience_orb.pickup"); + SoundEvent ENTITY_EXPERIENCE_ORB_PICKUP = BuiltinSoundEvent.get("minecraft:entity.experience_orb.pickup"); - SoundEvent BLOCK_FENCE_GATE_CLOSE = SoundEventImpl.get("minecraft:block.fence_gate.close"); + SoundEvent BLOCK_FENCE_GATE_CLOSE = BuiltinSoundEvent.get("minecraft:block.fence_gate.close"); - SoundEvent BLOCK_FENCE_GATE_OPEN = SoundEventImpl.get("minecraft:block.fence_gate.open"); + SoundEvent BLOCK_FENCE_GATE_OPEN = BuiltinSoundEvent.get("minecraft:block.fence_gate.open"); - SoundEvent ITEM_FIRECHARGE_USE = SoundEventImpl.get("minecraft:item.firecharge.use"); + SoundEvent ITEM_FIRECHARGE_USE = BuiltinSoundEvent.get("minecraft:item.firecharge.use"); - SoundEvent ENTITY_FIREWORK_ROCKET_BLAST = SoundEventImpl.get("minecraft:entity.firework_rocket.blast"); + SoundEvent ENTITY_FIREWORK_ROCKET_BLAST = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.blast"); - SoundEvent ENTITY_FIREWORK_ROCKET_BLAST_FAR = SoundEventImpl.get("minecraft:entity.firework_rocket.blast_far"); + SoundEvent ENTITY_FIREWORK_ROCKET_BLAST_FAR = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.blast_far"); - SoundEvent ENTITY_FIREWORK_ROCKET_LARGE_BLAST = SoundEventImpl.get("minecraft:entity.firework_rocket.large_blast"); + SoundEvent ENTITY_FIREWORK_ROCKET_LARGE_BLAST = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.large_blast"); - SoundEvent ENTITY_FIREWORK_ROCKET_LARGE_BLAST_FAR = SoundEventImpl.get("minecraft:entity.firework_rocket.large_blast_far"); + SoundEvent ENTITY_FIREWORK_ROCKET_LARGE_BLAST_FAR = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.large_blast_far"); - SoundEvent ENTITY_FIREWORK_ROCKET_LAUNCH = SoundEventImpl.get("minecraft:entity.firework_rocket.launch"); + SoundEvent ENTITY_FIREWORK_ROCKET_LAUNCH = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.launch"); - SoundEvent ENTITY_FIREWORK_ROCKET_SHOOT = SoundEventImpl.get("minecraft:entity.firework_rocket.shoot"); + SoundEvent ENTITY_FIREWORK_ROCKET_SHOOT = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.shoot"); - SoundEvent ENTITY_FIREWORK_ROCKET_TWINKLE = SoundEventImpl.get("minecraft:entity.firework_rocket.twinkle"); + SoundEvent ENTITY_FIREWORK_ROCKET_TWINKLE = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.twinkle"); - SoundEvent ENTITY_FIREWORK_ROCKET_TWINKLE_FAR = SoundEventImpl.get("minecraft:entity.firework_rocket.twinkle_far"); + SoundEvent ENTITY_FIREWORK_ROCKET_TWINKLE_FAR = BuiltinSoundEvent.get("minecraft:entity.firework_rocket.twinkle_far"); - SoundEvent BLOCK_FIRE_AMBIENT = SoundEventImpl.get("minecraft:block.fire.ambient"); + SoundEvent BLOCK_FIRE_AMBIENT = BuiltinSoundEvent.get("minecraft:block.fire.ambient"); - SoundEvent BLOCK_FIRE_EXTINGUISH = SoundEventImpl.get("minecraft:block.fire.extinguish"); + SoundEvent BLOCK_FIRE_EXTINGUISH = BuiltinSoundEvent.get("minecraft:block.fire.extinguish"); - SoundEvent ENTITY_FISH_SWIM = SoundEventImpl.get("minecraft:entity.fish.swim"); + SoundEvent ENTITY_FISH_SWIM = BuiltinSoundEvent.get("minecraft:entity.fish.swim"); - SoundEvent ENTITY_FISHING_BOBBER_RETRIEVE = SoundEventImpl.get("minecraft:entity.fishing_bobber.retrieve"); + SoundEvent ENTITY_FISHING_BOBBER_RETRIEVE = BuiltinSoundEvent.get("minecraft:entity.fishing_bobber.retrieve"); - SoundEvent ENTITY_FISHING_BOBBER_SPLASH = SoundEventImpl.get("minecraft:entity.fishing_bobber.splash"); + SoundEvent ENTITY_FISHING_BOBBER_SPLASH = BuiltinSoundEvent.get("minecraft:entity.fishing_bobber.splash"); - SoundEvent ENTITY_FISHING_BOBBER_THROW = SoundEventImpl.get("minecraft:entity.fishing_bobber.throw"); + SoundEvent ENTITY_FISHING_BOBBER_THROW = BuiltinSoundEvent.get("minecraft:entity.fishing_bobber.throw"); - SoundEvent ITEM_FLINTANDSTEEL_USE = SoundEventImpl.get("minecraft:item.flintandsteel.use"); + SoundEvent ITEM_FLINTANDSTEEL_USE = BuiltinSoundEvent.get("minecraft:item.flintandsteel.use"); - SoundEvent BLOCK_FLOWERING_AZALEA_BREAK = SoundEventImpl.get("minecraft:block.flowering_azalea.break"); + SoundEvent BLOCK_FLOWERING_AZALEA_BREAK = BuiltinSoundEvent.get("minecraft:block.flowering_azalea.break"); - SoundEvent BLOCK_FLOWERING_AZALEA_FALL = SoundEventImpl.get("minecraft:block.flowering_azalea.fall"); + SoundEvent BLOCK_FLOWERING_AZALEA_FALL = BuiltinSoundEvent.get("minecraft:block.flowering_azalea.fall"); - SoundEvent BLOCK_FLOWERING_AZALEA_HIT = SoundEventImpl.get("minecraft:block.flowering_azalea.hit"); + SoundEvent BLOCK_FLOWERING_AZALEA_HIT = BuiltinSoundEvent.get("minecraft:block.flowering_azalea.hit"); - SoundEvent BLOCK_FLOWERING_AZALEA_PLACE = SoundEventImpl.get("minecraft:block.flowering_azalea.place"); + SoundEvent BLOCK_FLOWERING_AZALEA_PLACE = BuiltinSoundEvent.get("minecraft:block.flowering_azalea.place"); - SoundEvent BLOCK_FLOWERING_AZALEA_STEP = SoundEventImpl.get("minecraft:block.flowering_azalea.step"); + SoundEvent BLOCK_FLOWERING_AZALEA_STEP = BuiltinSoundEvent.get("minecraft:block.flowering_azalea.step"); - SoundEvent ENTITY_FOX_AGGRO = SoundEventImpl.get("minecraft:entity.fox.aggro"); + SoundEvent ENTITY_FOX_AGGRO = BuiltinSoundEvent.get("minecraft:entity.fox.aggro"); - SoundEvent ENTITY_FOX_AMBIENT = SoundEventImpl.get("minecraft:entity.fox.ambient"); + SoundEvent ENTITY_FOX_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.fox.ambient"); - SoundEvent ENTITY_FOX_BITE = SoundEventImpl.get("minecraft:entity.fox.bite"); + SoundEvent ENTITY_FOX_BITE = BuiltinSoundEvent.get("minecraft:entity.fox.bite"); - SoundEvent ENTITY_FOX_DEATH = SoundEventImpl.get("minecraft:entity.fox.death"); + SoundEvent ENTITY_FOX_DEATH = BuiltinSoundEvent.get("minecraft:entity.fox.death"); - SoundEvent ENTITY_FOX_EAT = SoundEventImpl.get("minecraft:entity.fox.eat"); + SoundEvent ENTITY_FOX_EAT = BuiltinSoundEvent.get("minecraft:entity.fox.eat"); - SoundEvent ENTITY_FOX_HURT = SoundEventImpl.get("minecraft:entity.fox.hurt"); + SoundEvent ENTITY_FOX_HURT = BuiltinSoundEvent.get("minecraft:entity.fox.hurt"); - SoundEvent ENTITY_FOX_SCREECH = SoundEventImpl.get("minecraft:entity.fox.screech"); + SoundEvent ENTITY_FOX_SCREECH = BuiltinSoundEvent.get("minecraft:entity.fox.screech"); - SoundEvent ENTITY_FOX_SLEEP = SoundEventImpl.get("minecraft:entity.fox.sleep"); + SoundEvent ENTITY_FOX_SLEEP = BuiltinSoundEvent.get("minecraft:entity.fox.sleep"); - SoundEvent ENTITY_FOX_SNIFF = SoundEventImpl.get("minecraft:entity.fox.sniff"); + SoundEvent ENTITY_FOX_SNIFF = BuiltinSoundEvent.get("minecraft:entity.fox.sniff"); - SoundEvent ENTITY_FOX_SPIT = SoundEventImpl.get("minecraft:entity.fox.spit"); + SoundEvent ENTITY_FOX_SPIT = BuiltinSoundEvent.get("minecraft:entity.fox.spit"); - SoundEvent ENTITY_FOX_TELEPORT = SoundEventImpl.get("minecraft:entity.fox.teleport"); + SoundEvent ENTITY_FOX_TELEPORT = BuiltinSoundEvent.get("minecraft:entity.fox.teleport"); - SoundEvent BLOCK_SUSPICIOUS_SAND_BREAK = SoundEventImpl.get("minecraft:block.suspicious_sand.break"); + SoundEvent BLOCK_SUSPICIOUS_SAND_BREAK = BuiltinSoundEvent.get("minecraft:block.suspicious_sand.break"); - SoundEvent BLOCK_SUSPICIOUS_SAND_STEP = SoundEventImpl.get("minecraft:block.suspicious_sand.step"); + SoundEvent BLOCK_SUSPICIOUS_SAND_STEP = BuiltinSoundEvent.get("minecraft:block.suspicious_sand.step"); - SoundEvent BLOCK_SUSPICIOUS_SAND_PLACE = SoundEventImpl.get("minecraft:block.suspicious_sand.place"); + SoundEvent BLOCK_SUSPICIOUS_SAND_PLACE = BuiltinSoundEvent.get("minecraft:block.suspicious_sand.place"); - SoundEvent BLOCK_SUSPICIOUS_SAND_HIT = SoundEventImpl.get("minecraft:block.suspicious_sand.hit"); + SoundEvent BLOCK_SUSPICIOUS_SAND_HIT = BuiltinSoundEvent.get("minecraft:block.suspicious_sand.hit"); - SoundEvent BLOCK_SUSPICIOUS_SAND_FALL = SoundEventImpl.get("minecraft:block.suspicious_sand.fall"); + SoundEvent BLOCK_SUSPICIOUS_SAND_FALL = BuiltinSoundEvent.get("minecraft:block.suspicious_sand.fall"); - SoundEvent BLOCK_SUSPICIOUS_GRAVEL_BREAK = SoundEventImpl.get("minecraft:block.suspicious_gravel.break"); + SoundEvent BLOCK_SUSPICIOUS_GRAVEL_BREAK = BuiltinSoundEvent.get("minecraft:block.suspicious_gravel.break"); - SoundEvent BLOCK_SUSPICIOUS_GRAVEL_STEP = SoundEventImpl.get("minecraft:block.suspicious_gravel.step"); + SoundEvent BLOCK_SUSPICIOUS_GRAVEL_STEP = BuiltinSoundEvent.get("minecraft:block.suspicious_gravel.step"); - SoundEvent BLOCK_SUSPICIOUS_GRAVEL_PLACE = SoundEventImpl.get("minecraft:block.suspicious_gravel.place"); + SoundEvent BLOCK_SUSPICIOUS_GRAVEL_PLACE = BuiltinSoundEvent.get("minecraft:block.suspicious_gravel.place"); - SoundEvent BLOCK_SUSPICIOUS_GRAVEL_HIT = SoundEventImpl.get("minecraft:block.suspicious_gravel.hit"); + SoundEvent BLOCK_SUSPICIOUS_GRAVEL_HIT = BuiltinSoundEvent.get("minecraft:block.suspicious_gravel.hit"); - SoundEvent BLOCK_SUSPICIOUS_GRAVEL_FALL = SoundEventImpl.get("minecraft:block.suspicious_gravel.fall"); + SoundEvent BLOCK_SUSPICIOUS_GRAVEL_FALL = BuiltinSoundEvent.get("minecraft:block.suspicious_gravel.fall"); - SoundEvent BLOCK_FROGLIGHT_BREAK = SoundEventImpl.get("minecraft:block.froglight.break"); + SoundEvent BLOCK_FROGLIGHT_BREAK = BuiltinSoundEvent.get("minecraft:block.froglight.break"); - SoundEvent BLOCK_FROGLIGHT_FALL = SoundEventImpl.get("minecraft:block.froglight.fall"); + SoundEvent BLOCK_FROGLIGHT_FALL = BuiltinSoundEvent.get("minecraft:block.froglight.fall"); - SoundEvent BLOCK_FROGLIGHT_HIT = SoundEventImpl.get("minecraft:block.froglight.hit"); + SoundEvent BLOCK_FROGLIGHT_HIT = BuiltinSoundEvent.get("minecraft:block.froglight.hit"); - SoundEvent BLOCK_FROGLIGHT_PLACE = SoundEventImpl.get("minecraft:block.froglight.place"); + SoundEvent BLOCK_FROGLIGHT_PLACE = BuiltinSoundEvent.get("minecraft:block.froglight.place"); - SoundEvent BLOCK_FROGLIGHT_STEP = SoundEventImpl.get("minecraft:block.froglight.step"); + SoundEvent BLOCK_FROGLIGHT_STEP = BuiltinSoundEvent.get("minecraft:block.froglight.step"); - SoundEvent BLOCK_FROGSPAWN_STEP = SoundEventImpl.get("minecraft:block.frogspawn.step"); + SoundEvent BLOCK_FROGSPAWN_STEP = BuiltinSoundEvent.get("minecraft:block.frogspawn.step"); - SoundEvent BLOCK_FROGSPAWN_BREAK = SoundEventImpl.get("minecraft:block.frogspawn.break"); + SoundEvent BLOCK_FROGSPAWN_BREAK = BuiltinSoundEvent.get("minecraft:block.frogspawn.break"); - SoundEvent BLOCK_FROGSPAWN_FALL = SoundEventImpl.get("minecraft:block.frogspawn.fall"); + SoundEvent BLOCK_FROGSPAWN_FALL = BuiltinSoundEvent.get("minecraft:block.frogspawn.fall"); - SoundEvent BLOCK_FROGSPAWN_HATCH = SoundEventImpl.get("minecraft:block.frogspawn.hatch"); + SoundEvent BLOCK_FROGSPAWN_HATCH = BuiltinSoundEvent.get("minecraft:block.frogspawn.hatch"); - SoundEvent BLOCK_FROGSPAWN_HIT = SoundEventImpl.get("minecraft:block.frogspawn.hit"); + SoundEvent BLOCK_FROGSPAWN_HIT = BuiltinSoundEvent.get("minecraft:block.frogspawn.hit"); - SoundEvent BLOCK_FROGSPAWN_PLACE = SoundEventImpl.get("minecraft:block.frogspawn.place"); + SoundEvent BLOCK_FROGSPAWN_PLACE = BuiltinSoundEvent.get("minecraft:block.frogspawn.place"); - SoundEvent ENTITY_FROG_AMBIENT = SoundEventImpl.get("minecraft:entity.frog.ambient"); + SoundEvent ENTITY_FROG_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.frog.ambient"); - SoundEvent ENTITY_FROG_DEATH = SoundEventImpl.get("minecraft:entity.frog.death"); + SoundEvent ENTITY_FROG_DEATH = BuiltinSoundEvent.get("minecraft:entity.frog.death"); - SoundEvent ENTITY_FROG_EAT = SoundEventImpl.get("minecraft:entity.frog.eat"); + SoundEvent ENTITY_FROG_EAT = BuiltinSoundEvent.get("minecraft:entity.frog.eat"); - SoundEvent ENTITY_FROG_HURT = SoundEventImpl.get("minecraft:entity.frog.hurt"); + SoundEvent ENTITY_FROG_HURT = BuiltinSoundEvent.get("minecraft:entity.frog.hurt"); - SoundEvent ENTITY_FROG_LAY_SPAWN = SoundEventImpl.get("minecraft:entity.frog.lay_spawn"); + SoundEvent ENTITY_FROG_LAY_SPAWN = BuiltinSoundEvent.get("minecraft:entity.frog.lay_spawn"); - SoundEvent ENTITY_FROG_LONG_JUMP = SoundEventImpl.get("minecraft:entity.frog.long_jump"); + SoundEvent ENTITY_FROG_LONG_JUMP = BuiltinSoundEvent.get("minecraft:entity.frog.long_jump"); - SoundEvent ENTITY_FROG_STEP = SoundEventImpl.get("minecraft:entity.frog.step"); + SoundEvent ENTITY_FROG_STEP = BuiltinSoundEvent.get("minecraft:entity.frog.step"); - SoundEvent ENTITY_FROG_TONGUE = SoundEventImpl.get("minecraft:entity.frog.tongue"); + SoundEvent ENTITY_FROG_TONGUE = BuiltinSoundEvent.get("minecraft:entity.frog.tongue"); - SoundEvent BLOCK_ROOTS_BREAK = SoundEventImpl.get("minecraft:block.roots.break"); + SoundEvent BLOCK_ROOTS_BREAK = BuiltinSoundEvent.get("minecraft:block.roots.break"); - SoundEvent BLOCK_ROOTS_STEP = SoundEventImpl.get("minecraft:block.roots.step"); + SoundEvent BLOCK_ROOTS_STEP = BuiltinSoundEvent.get("minecraft:block.roots.step"); - SoundEvent BLOCK_ROOTS_PLACE = SoundEventImpl.get("minecraft:block.roots.place"); + SoundEvent BLOCK_ROOTS_PLACE = BuiltinSoundEvent.get("minecraft:block.roots.place"); - SoundEvent BLOCK_ROOTS_HIT = SoundEventImpl.get("minecraft:block.roots.hit"); + SoundEvent BLOCK_ROOTS_HIT = BuiltinSoundEvent.get("minecraft:block.roots.hit"); - SoundEvent BLOCK_ROOTS_FALL = SoundEventImpl.get("minecraft:block.roots.fall"); + SoundEvent BLOCK_ROOTS_FALL = BuiltinSoundEvent.get("minecraft:block.roots.fall"); - SoundEvent BLOCK_FURNACE_FIRE_CRACKLE = SoundEventImpl.get("minecraft:block.furnace.fire_crackle"); + SoundEvent BLOCK_FURNACE_FIRE_CRACKLE = BuiltinSoundEvent.get("minecraft:block.furnace.fire_crackle"); - SoundEvent ENTITY_GENERIC_BIG_FALL = SoundEventImpl.get("minecraft:entity.generic.big_fall"); + SoundEvent ENTITY_GENERIC_BIG_FALL = BuiltinSoundEvent.get("minecraft:entity.generic.big_fall"); - SoundEvent ENTITY_GENERIC_BURN = SoundEventImpl.get("minecraft:entity.generic.burn"); + SoundEvent ENTITY_GENERIC_BURN = BuiltinSoundEvent.get("minecraft:entity.generic.burn"); - SoundEvent ENTITY_GENERIC_DEATH = SoundEventImpl.get("minecraft:entity.generic.death"); + SoundEvent ENTITY_GENERIC_DEATH = BuiltinSoundEvent.get("minecraft:entity.generic.death"); - SoundEvent ENTITY_GENERIC_DRINK = SoundEventImpl.get("minecraft:entity.generic.drink"); + SoundEvent ENTITY_GENERIC_DRINK = BuiltinSoundEvent.get("minecraft:entity.generic.drink"); - SoundEvent ENTITY_GENERIC_EAT = SoundEventImpl.get("minecraft:entity.generic.eat"); + SoundEvent ENTITY_GENERIC_EAT = BuiltinSoundEvent.get("minecraft:entity.generic.eat"); - SoundEvent ENTITY_GENERIC_EXPLODE = SoundEventImpl.get("minecraft:entity.generic.explode"); + SoundEvent ENTITY_GENERIC_EXPLODE = BuiltinSoundEvent.get("minecraft:entity.generic.explode"); - SoundEvent ENTITY_GENERIC_EXTINGUISH_FIRE = SoundEventImpl.get("minecraft:entity.generic.extinguish_fire"); + SoundEvent ENTITY_GENERIC_EXTINGUISH_FIRE = BuiltinSoundEvent.get("minecraft:entity.generic.extinguish_fire"); - SoundEvent ENTITY_GENERIC_HURT = SoundEventImpl.get("minecraft:entity.generic.hurt"); + SoundEvent ENTITY_GENERIC_HURT = BuiltinSoundEvent.get("minecraft:entity.generic.hurt"); - SoundEvent ENTITY_GENERIC_SMALL_FALL = SoundEventImpl.get("minecraft:entity.generic.small_fall"); + SoundEvent ENTITY_GENERIC_SMALL_FALL = BuiltinSoundEvent.get("minecraft:entity.generic.small_fall"); - SoundEvent ENTITY_GENERIC_SPLASH = SoundEventImpl.get("minecraft:entity.generic.splash"); + SoundEvent ENTITY_GENERIC_SPLASH = BuiltinSoundEvent.get("minecraft:entity.generic.splash"); - SoundEvent ENTITY_GENERIC_SWIM = SoundEventImpl.get("minecraft:entity.generic.swim"); + SoundEvent ENTITY_GENERIC_SWIM = BuiltinSoundEvent.get("minecraft:entity.generic.swim"); - SoundEvent ENTITY_GHAST_AMBIENT = SoundEventImpl.get("minecraft:entity.ghast.ambient"); + SoundEvent ENTITY_GHAST_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.ghast.ambient"); - SoundEvent ENTITY_GHAST_DEATH = SoundEventImpl.get("minecraft:entity.ghast.death"); + SoundEvent ENTITY_GHAST_DEATH = BuiltinSoundEvent.get("minecraft:entity.ghast.death"); - SoundEvent ENTITY_GHAST_HURT = SoundEventImpl.get("minecraft:entity.ghast.hurt"); + SoundEvent ENTITY_GHAST_HURT = BuiltinSoundEvent.get("minecraft:entity.ghast.hurt"); - SoundEvent ENTITY_GHAST_SCREAM = SoundEventImpl.get("minecraft:entity.ghast.scream"); + SoundEvent ENTITY_GHAST_SCREAM = BuiltinSoundEvent.get("minecraft:entity.ghast.scream"); - SoundEvent ENTITY_GHAST_SHOOT = SoundEventImpl.get("minecraft:entity.ghast.shoot"); + SoundEvent ENTITY_GHAST_SHOOT = BuiltinSoundEvent.get("minecraft:entity.ghast.shoot"); - SoundEvent ENTITY_GHAST_WARN = SoundEventImpl.get("minecraft:entity.ghast.warn"); + SoundEvent ENTITY_GHAST_WARN = BuiltinSoundEvent.get("minecraft:entity.ghast.warn"); - SoundEvent BLOCK_GILDED_BLACKSTONE_BREAK = SoundEventImpl.get("minecraft:block.gilded_blackstone.break"); + SoundEvent BLOCK_GILDED_BLACKSTONE_BREAK = BuiltinSoundEvent.get("minecraft:block.gilded_blackstone.break"); - SoundEvent BLOCK_GILDED_BLACKSTONE_FALL = SoundEventImpl.get("minecraft:block.gilded_blackstone.fall"); + SoundEvent BLOCK_GILDED_BLACKSTONE_FALL = BuiltinSoundEvent.get("minecraft:block.gilded_blackstone.fall"); - SoundEvent BLOCK_GILDED_BLACKSTONE_HIT = SoundEventImpl.get("minecraft:block.gilded_blackstone.hit"); + SoundEvent BLOCK_GILDED_BLACKSTONE_HIT = BuiltinSoundEvent.get("minecraft:block.gilded_blackstone.hit"); - SoundEvent BLOCK_GILDED_BLACKSTONE_PLACE = SoundEventImpl.get("minecraft:block.gilded_blackstone.place"); + SoundEvent BLOCK_GILDED_BLACKSTONE_PLACE = BuiltinSoundEvent.get("minecraft:block.gilded_blackstone.place"); - SoundEvent BLOCK_GILDED_BLACKSTONE_STEP = SoundEventImpl.get("minecraft:block.gilded_blackstone.step"); + SoundEvent BLOCK_GILDED_BLACKSTONE_STEP = BuiltinSoundEvent.get("minecraft:block.gilded_blackstone.step"); - SoundEvent BLOCK_GLASS_BREAK = SoundEventImpl.get("minecraft:block.glass.break"); + SoundEvent BLOCK_GLASS_BREAK = BuiltinSoundEvent.get("minecraft:block.glass.break"); - SoundEvent BLOCK_GLASS_FALL = SoundEventImpl.get("minecraft:block.glass.fall"); + SoundEvent BLOCK_GLASS_FALL = BuiltinSoundEvent.get("minecraft:block.glass.fall"); - SoundEvent BLOCK_GLASS_HIT = SoundEventImpl.get("minecraft:block.glass.hit"); + SoundEvent BLOCK_GLASS_HIT = BuiltinSoundEvent.get("minecraft:block.glass.hit"); - SoundEvent BLOCK_GLASS_PLACE = SoundEventImpl.get("minecraft:block.glass.place"); + SoundEvent BLOCK_GLASS_PLACE = BuiltinSoundEvent.get("minecraft:block.glass.place"); - SoundEvent BLOCK_GLASS_STEP = SoundEventImpl.get("minecraft:block.glass.step"); + SoundEvent BLOCK_GLASS_STEP = BuiltinSoundEvent.get("minecraft:block.glass.step"); - SoundEvent ITEM_GLOW_INK_SAC_USE = SoundEventImpl.get("minecraft:item.glow_ink_sac.use"); + SoundEvent ITEM_GLOW_INK_SAC_USE = BuiltinSoundEvent.get("minecraft:item.glow_ink_sac.use"); - SoundEvent ENTITY_GLOW_ITEM_FRAME_ADD_ITEM = SoundEventImpl.get("minecraft:entity.glow_item_frame.add_item"); + SoundEvent ENTITY_GLOW_ITEM_FRAME_ADD_ITEM = BuiltinSoundEvent.get("minecraft:entity.glow_item_frame.add_item"); - SoundEvent ENTITY_GLOW_ITEM_FRAME_BREAK = SoundEventImpl.get("minecraft:entity.glow_item_frame.break"); + SoundEvent ENTITY_GLOW_ITEM_FRAME_BREAK = BuiltinSoundEvent.get("minecraft:entity.glow_item_frame.break"); - SoundEvent ENTITY_GLOW_ITEM_FRAME_PLACE = SoundEventImpl.get("minecraft:entity.glow_item_frame.place"); + SoundEvent ENTITY_GLOW_ITEM_FRAME_PLACE = BuiltinSoundEvent.get("minecraft:entity.glow_item_frame.place"); - SoundEvent ENTITY_GLOW_ITEM_FRAME_REMOVE_ITEM = SoundEventImpl.get("minecraft:entity.glow_item_frame.remove_item"); + SoundEvent ENTITY_GLOW_ITEM_FRAME_REMOVE_ITEM = BuiltinSoundEvent.get("minecraft:entity.glow_item_frame.remove_item"); - SoundEvent ENTITY_GLOW_ITEM_FRAME_ROTATE_ITEM = SoundEventImpl.get("minecraft:entity.glow_item_frame.rotate_item"); + SoundEvent ENTITY_GLOW_ITEM_FRAME_ROTATE_ITEM = BuiltinSoundEvent.get("minecraft:entity.glow_item_frame.rotate_item"); - SoundEvent ENTITY_GLOW_SQUID_AMBIENT = SoundEventImpl.get("minecraft:entity.glow_squid.ambient"); + SoundEvent ENTITY_GLOW_SQUID_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.glow_squid.ambient"); - SoundEvent ENTITY_GLOW_SQUID_DEATH = SoundEventImpl.get("minecraft:entity.glow_squid.death"); + SoundEvent ENTITY_GLOW_SQUID_DEATH = BuiltinSoundEvent.get("minecraft:entity.glow_squid.death"); - SoundEvent ENTITY_GLOW_SQUID_HURT = SoundEventImpl.get("minecraft:entity.glow_squid.hurt"); + SoundEvent ENTITY_GLOW_SQUID_HURT = BuiltinSoundEvent.get("minecraft:entity.glow_squid.hurt"); - SoundEvent ENTITY_GLOW_SQUID_SQUIRT = SoundEventImpl.get("minecraft:entity.glow_squid.squirt"); + SoundEvent ENTITY_GLOW_SQUID_SQUIRT = BuiltinSoundEvent.get("minecraft:entity.glow_squid.squirt"); - SoundEvent ENTITY_GOAT_AMBIENT = SoundEventImpl.get("minecraft:entity.goat.ambient"); + SoundEvent ENTITY_GOAT_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.goat.ambient"); - SoundEvent ENTITY_GOAT_DEATH = SoundEventImpl.get("minecraft:entity.goat.death"); + SoundEvent ENTITY_GOAT_DEATH = BuiltinSoundEvent.get("minecraft:entity.goat.death"); - SoundEvent ENTITY_GOAT_EAT = SoundEventImpl.get("minecraft:entity.goat.eat"); + SoundEvent ENTITY_GOAT_EAT = BuiltinSoundEvent.get("minecraft:entity.goat.eat"); - SoundEvent ENTITY_GOAT_HURT = SoundEventImpl.get("minecraft:entity.goat.hurt"); + SoundEvent ENTITY_GOAT_HURT = BuiltinSoundEvent.get("minecraft:entity.goat.hurt"); - SoundEvent ENTITY_GOAT_LONG_JUMP = SoundEventImpl.get("minecraft:entity.goat.long_jump"); + SoundEvent ENTITY_GOAT_LONG_JUMP = BuiltinSoundEvent.get("minecraft:entity.goat.long_jump"); - SoundEvent ENTITY_GOAT_MILK = SoundEventImpl.get("minecraft:entity.goat.milk"); + SoundEvent ENTITY_GOAT_MILK = BuiltinSoundEvent.get("minecraft:entity.goat.milk"); - SoundEvent ENTITY_GOAT_PREPARE_RAM = SoundEventImpl.get("minecraft:entity.goat.prepare_ram"); + SoundEvent ENTITY_GOAT_PREPARE_RAM = BuiltinSoundEvent.get("minecraft:entity.goat.prepare_ram"); - SoundEvent ENTITY_GOAT_RAM_IMPACT = SoundEventImpl.get("minecraft:entity.goat.ram_impact"); + SoundEvent ENTITY_GOAT_RAM_IMPACT = BuiltinSoundEvent.get("minecraft:entity.goat.ram_impact"); - SoundEvent ENTITY_GOAT_HORN_BREAK = SoundEventImpl.get("minecraft:entity.goat.horn_break"); + SoundEvent ENTITY_GOAT_HORN_BREAK = BuiltinSoundEvent.get("minecraft:entity.goat.horn_break"); - SoundEvent ITEM_GOAT_HORN_PLAY = SoundEventImpl.get("minecraft:item.goat_horn.play"); + SoundEvent ITEM_GOAT_HORN_PLAY = BuiltinSoundEvent.get("minecraft:item.goat_horn.play"); - SoundEvent ENTITY_GOAT_SCREAMING_AMBIENT = SoundEventImpl.get("minecraft:entity.goat.screaming.ambient"); + SoundEvent ENTITY_GOAT_SCREAMING_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.ambient"); - SoundEvent ENTITY_GOAT_SCREAMING_DEATH = SoundEventImpl.get("minecraft:entity.goat.screaming.death"); + SoundEvent ENTITY_GOAT_SCREAMING_DEATH = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.death"); - SoundEvent ENTITY_GOAT_SCREAMING_EAT = SoundEventImpl.get("minecraft:entity.goat.screaming.eat"); + SoundEvent ENTITY_GOAT_SCREAMING_EAT = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.eat"); - SoundEvent ENTITY_GOAT_SCREAMING_HURT = SoundEventImpl.get("minecraft:entity.goat.screaming.hurt"); + SoundEvent ENTITY_GOAT_SCREAMING_HURT = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.hurt"); - SoundEvent ENTITY_GOAT_SCREAMING_LONG_JUMP = SoundEventImpl.get("minecraft:entity.goat.screaming.long_jump"); + SoundEvent ENTITY_GOAT_SCREAMING_LONG_JUMP = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.long_jump"); - SoundEvent ENTITY_GOAT_SCREAMING_MILK = SoundEventImpl.get("minecraft:entity.goat.screaming.milk"); + SoundEvent ENTITY_GOAT_SCREAMING_MILK = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.milk"); - SoundEvent ENTITY_GOAT_SCREAMING_PREPARE_RAM = SoundEventImpl.get("minecraft:entity.goat.screaming.prepare_ram"); + SoundEvent ENTITY_GOAT_SCREAMING_PREPARE_RAM = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.prepare_ram"); - SoundEvent ENTITY_GOAT_SCREAMING_RAM_IMPACT = SoundEventImpl.get("minecraft:entity.goat.screaming.ram_impact"); + SoundEvent ENTITY_GOAT_SCREAMING_RAM_IMPACT = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.ram_impact"); - SoundEvent ENTITY_GOAT_SCREAMING_HORN_BREAK = SoundEventImpl.get("minecraft:entity.goat.screaming.horn_break"); + SoundEvent ENTITY_GOAT_SCREAMING_HORN_BREAK = BuiltinSoundEvent.get("minecraft:entity.goat.screaming.horn_break"); - SoundEvent ENTITY_GOAT_STEP = SoundEventImpl.get("minecraft:entity.goat.step"); + SoundEvent ENTITY_GOAT_STEP = BuiltinSoundEvent.get("minecraft:entity.goat.step"); - SoundEvent BLOCK_GRASS_BREAK = SoundEventImpl.get("minecraft:block.grass.break"); + SoundEvent BLOCK_GRASS_BREAK = BuiltinSoundEvent.get("minecraft:block.grass.break"); - SoundEvent BLOCK_GRASS_FALL = SoundEventImpl.get("minecraft:block.grass.fall"); + SoundEvent BLOCK_GRASS_FALL = BuiltinSoundEvent.get("minecraft:block.grass.fall"); - SoundEvent BLOCK_GRASS_HIT = SoundEventImpl.get("minecraft:block.grass.hit"); + SoundEvent BLOCK_GRASS_HIT = BuiltinSoundEvent.get("minecraft:block.grass.hit"); - SoundEvent BLOCK_GRASS_PLACE = SoundEventImpl.get("minecraft:block.grass.place"); + SoundEvent BLOCK_GRASS_PLACE = BuiltinSoundEvent.get("minecraft:block.grass.place"); - SoundEvent BLOCK_GRASS_STEP = SoundEventImpl.get("minecraft:block.grass.step"); + SoundEvent BLOCK_GRASS_STEP = BuiltinSoundEvent.get("minecraft:block.grass.step"); - SoundEvent BLOCK_GRAVEL_BREAK = SoundEventImpl.get("minecraft:block.gravel.break"); + SoundEvent BLOCK_GRAVEL_BREAK = BuiltinSoundEvent.get("minecraft:block.gravel.break"); - SoundEvent BLOCK_GRAVEL_FALL = SoundEventImpl.get("minecraft:block.gravel.fall"); + SoundEvent BLOCK_GRAVEL_FALL = BuiltinSoundEvent.get("minecraft:block.gravel.fall"); - SoundEvent BLOCK_GRAVEL_HIT = SoundEventImpl.get("minecraft:block.gravel.hit"); + SoundEvent BLOCK_GRAVEL_HIT = BuiltinSoundEvent.get("minecraft:block.gravel.hit"); - SoundEvent BLOCK_GRAVEL_PLACE = SoundEventImpl.get("minecraft:block.gravel.place"); + SoundEvent BLOCK_GRAVEL_PLACE = BuiltinSoundEvent.get("minecraft:block.gravel.place"); - SoundEvent BLOCK_GRAVEL_STEP = SoundEventImpl.get("minecraft:block.gravel.step"); + SoundEvent BLOCK_GRAVEL_STEP = BuiltinSoundEvent.get("minecraft:block.gravel.step"); - SoundEvent BLOCK_GRINDSTONE_USE = SoundEventImpl.get("minecraft:block.grindstone.use"); + SoundEvent BLOCK_GRINDSTONE_USE = BuiltinSoundEvent.get("minecraft:block.grindstone.use"); - SoundEvent BLOCK_GROWING_PLANT_CROP = SoundEventImpl.get("minecraft:block.growing_plant.crop"); + SoundEvent BLOCK_GROWING_PLANT_CROP = BuiltinSoundEvent.get("minecraft:block.growing_plant.crop"); - SoundEvent ENTITY_GUARDIAN_AMBIENT = SoundEventImpl.get("minecraft:entity.guardian.ambient"); + SoundEvent ENTITY_GUARDIAN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.guardian.ambient"); - SoundEvent ENTITY_GUARDIAN_AMBIENT_LAND = SoundEventImpl.get("minecraft:entity.guardian.ambient_land"); + SoundEvent ENTITY_GUARDIAN_AMBIENT_LAND = BuiltinSoundEvent.get("minecraft:entity.guardian.ambient_land"); - SoundEvent ENTITY_GUARDIAN_ATTACK = SoundEventImpl.get("minecraft:entity.guardian.attack"); + SoundEvent ENTITY_GUARDIAN_ATTACK = BuiltinSoundEvent.get("minecraft:entity.guardian.attack"); - SoundEvent ENTITY_GUARDIAN_DEATH = SoundEventImpl.get("minecraft:entity.guardian.death"); + SoundEvent ENTITY_GUARDIAN_DEATH = BuiltinSoundEvent.get("minecraft:entity.guardian.death"); - SoundEvent ENTITY_GUARDIAN_DEATH_LAND = SoundEventImpl.get("minecraft:entity.guardian.death_land"); + SoundEvent ENTITY_GUARDIAN_DEATH_LAND = BuiltinSoundEvent.get("minecraft:entity.guardian.death_land"); - SoundEvent ENTITY_GUARDIAN_FLOP = SoundEventImpl.get("minecraft:entity.guardian.flop"); + SoundEvent ENTITY_GUARDIAN_FLOP = BuiltinSoundEvent.get("minecraft:entity.guardian.flop"); - SoundEvent ENTITY_GUARDIAN_HURT = SoundEventImpl.get("minecraft:entity.guardian.hurt"); + SoundEvent ENTITY_GUARDIAN_HURT = BuiltinSoundEvent.get("minecraft:entity.guardian.hurt"); - SoundEvent ENTITY_GUARDIAN_HURT_LAND = SoundEventImpl.get("minecraft:entity.guardian.hurt_land"); + SoundEvent ENTITY_GUARDIAN_HURT_LAND = BuiltinSoundEvent.get("minecraft:entity.guardian.hurt_land"); - SoundEvent BLOCK_HANGING_ROOTS_BREAK = SoundEventImpl.get("minecraft:block.hanging_roots.break"); + SoundEvent BLOCK_HANGING_ROOTS_BREAK = BuiltinSoundEvent.get("minecraft:block.hanging_roots.break"); - SoundEvent BLOCK_HANGING_ROOTS_FALL = SoundEventImpl.get("minecraft:block.hanging_roots.fall"); + SoundEvent BLOCK_HANGING_ROOTS_FALL = BuiltinSoundEvent.get("minecraft:block.hanging_roots.fall"); - SoundEvent BLOCK_HANGING_ROOTS_HIT = SoundEventImpl.get("minecraft:block.hanging_roots.hit"); + SoundEvent BLOCK_HANGING_ROOTS_HIT = BuiltinSoundEvent.get("minecraft:block.hanging_roots.hit"); - SoundEvent BLOCK_HANGING_ROOTS_PLACE = SoundEventImpl.get("minecraft:block.hanging_roots.place"); + SoundEvent BLOCK_HANGING_ROOTS_PLACE = BuiltinSoundEvent.get("minecraft:block.hanging_roots.place"); - SoundEvent BLOCK_HANGING_ROOTS_STEP = SoundEventImpl.get("minecraft:block.hanging_roots.step"); + SoundEvent BLOCK_HANGING_ROOTS_STEP = BuiltinSoundEvent.get("minecraft:block.hanging_roots.step"); - SoundEvent BLOCK_HANGING_SIGN_STEP = SoundEventImpl.get("minecraft:block.hanging_sign.step"); + SoundEvent BLOCK_HANGING_SIGN_STEP = BuiltinSoundEvent.get("minecraft:block.hanging_sign.step"); - SoundEvent BLOCK_HANGING_SIGN_BREAK = SoundEventImpl.get("minecraft:block.hanging_sign.break"); + SoundEvent BLOCK_HANGING_SIGN_BREAK = BuiltinSoundEvent.get("minecraft:block.hanging_sign.break"); - SoundEvent BLOCK_HANGING_SIGN_FALL = SoundEventImpl.get("minecraft:block.hanging_sign.fall"); + SoundEvent BLOCK_HANGING_SIGN_FALL = BuiltinSoundEvent.get("minecraft:block.hanging_sign.fall"); - SoundEvent BLOCK_HANGING_SIGN_HIT = SoundEventImpl.get("minecraft:block.hanging_sign.hit"); + SoundEvent BLOCK_HANGING_SIGN_HIT = BuiltinSoundEvent.get("minecraft:block.hanging_sign.hit"); - SoundEvent BLOCK_HANGING_SIGN_PLACE = SoundEventImpl.get("minecraft:block.hanging_sign.place"); + SoundEvent BLOCK_HANGING_SIGN_PLACE = BuiltinSoundEvent.get("minecraft:block.hanging_sign.place"); - SoundEvent BLOCK_HEAVY_CORE_BREAK = SoundEventImpl.get("minecraft:block.heavy_core.break"); + SoundEvent BLOCK_HEAVY_CORE_BREAK = BuiltinSoundEvent.get("minecraft:block.heavy_core.break"); - SoundEvent BLOCK_HEAVY_CORE_FALL = SoundEventImpl.get("minecraft:block.heavy_core.fall"); + SoundEvent BLOCK_HEAVY_CORE_FALL = BuiltinSoundEvent.get("minecraft:block.heavy_core.fall"); - SoundEvent BLOCK_HEAVY_CORE_HIT = SoundEventImpl.get("minecraft:block.heavy_core.hit"); + SoundEvent BLOCK_HEAVY_CORE_HIT = BuiltinSoundEvent.get("minecraft:block.heavy_core.hit"); - SoundEvent BLOCK_HEAVY_CORE_PLACE = SoundEventImpl.get("minecraft:block.heavy_core.place"); + SoundEvent BLOCK_HEAVY_CORE_PLACE = BuiltinSoundEvent.get("minecraft:block.heavy_core.place"); - SoundEvent BLOCK_HEAVY_CORE_STEP = SoundEventImpl.get("minecraft:block.heavy_core.step"); + SoundEvent BLOCK_HEAVY_CORE_STEP = BuiltinSoundEvent.get("minecraft:block.heavy_core.step"); - SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_STEP = SoundEventImpl.get("minecraft:block.nether_wood_hanging_sign.step"); + SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_STEP = BuiltinSoundEvent.get("minecraft:block.nether_wood_hanging_sign.step"); - SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_BREAK = SoundEventImpl.get("minecraft:block.nether_wood_hanging_sign.break"); + SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_BREAK = BuiltinSoundEvent.get("minecraft:block.nether_wood_hanging_sign.break"); - SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_FALL = SoundEventImpl.get("minecraft:block.nether_wood_hanging_sign.fall"); + SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_FALL = BuiltinSoundEvent.get("minecraft:block.nether_wood_hanging_sign.fall"); - SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_HIT = SoundEventImpl.get("minecraft:block.nether_wood_hanging_sign.hit"); + SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_HIT = BuiltinSoundEvent.get("minecraft:block.nether_wood_hanging_sign.hit"); - SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_PLACE = SoundEventImpl.get("minecraft:block.nether_wood_hanging_sign.place"); + SoundEvent BLOCK_NETHER_WOOD_HANGING_SIGN_PLACE = BuiltinSoundEvent.get("minecraft:block.nether_wood_hanging_sign.place"); - SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_STEP = SoundEventImpl.get("minecraft:block.bamboo_wood_hanging_sign.step"); + SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_STEP = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_hanging_sign.step"); - SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_BREAK = SoundEventImpl.get("minecraft:block.bamboo_wood_hanging_sign.break"); + SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_BREAK = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_hanging_sign.break"); - SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_FALL = SoundEventImpl.get("minecraft:block.bamboo_wood_hanging_sign.fall"); + SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_FALL = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_hanging_sign.fall"); - SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_HIT = SoundEventImpl.get("minecraft:block.bamboo_wood_hanging_sign.hit"); + SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_HIT = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_hanging_sign.hit"); - SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_PLACE = SoundEventImpl.get("minecraft:block.bamboo_wood_hanging_sign.place"); + SoundEvent BLOCK_BAMBOO_WOOD_HANGING_SIGN_PLACE = BuiltinSoundEvent.get("minecraft:block.bamboo_wood_hanging_sign.place"); - SoundEvent BLOCK_TRIAL_SPAWNER_BREAK = SoundEventImpl.get("minecraft:block.trial_spawner.break"); + SoundEvent BLOCK_TRIAL_SPAWNER_BREAK = BuiltinSoundEvent.get("minecraft:block.trial_spawner.break"); - SoundEvent BLOCK_TRIAL_SPAWNER_STEP = SoundEventImpl.get("minecraft:block.trial_spawner.step"); + SoundEvent BLOCK_TRIAL_SPAWNER_STEP = BuiltinSoundEvent.get("minecraft:block.trial_spawner.step"); - SoundEvent BLOCK_TRIAL_SPAWNER_PLACE = SoundEventImpl.get("minecraft:block.trial_spawner.place"); + SoundEvent BLOCK_TRIAL_SPAWNER_PLACE = BuiltinSoundEvent.get("minecraft:block.trial_spawner.place"); - SoundEvent BLOCK_TRIAL_SPAWNER_HIT = SoundEventImpl.get("minecraft:block.trial_spawner.hit"); + SoundEvent BLOCK_TRIAL_SPAWNER_HIT = BuiltinSoundEvent.get("minecraft:block.trial_spawner.hit"); - SoundEvent BLOCK_TRIAL_SPAWNER_FALL = SoundEventImpl.get("minecraft:block.trial_spawner.fall"); + SoundEvent BLOCK_TRIAL_SPAWNER_FALL = BuiltinSoundEvent.get("minecraft:block.trial_spawner.fall"); - SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_MOB = SoundEventImpl.get("minecraft:block.trial_spawner.spawn_mob"); + SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_MOB = BuiltinSoundEvent.get("minecraft:block.trial_spawner.spawn_mob"); - SoundEvent BLOCK_TRIAL_SPAWNER_ABOUT_TO_SPAWN_ITEM = SoundEventImpl.get("minecraft:block.trial_spawner.about_to_spawn_item"); + SoundEvent BLOCK_TRIAL_SPAWNER_ABOUT_TO_SPAWN_ITEM = BuiltinSoundEvent.get("minecraft:block.trial_spawner.about_to_spawn_item"); - SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_ITEM = SoundEventImpl.get("minecraft:block.trial_spawner.spawn_item"); + SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_ITEM = BuiltinSoundEvent.get("minecraft:block.trial_spawner.spawn_item"); - SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_ITEM_BEGIN = SoundEventImpl.get("minecraft:block.trial_spawner.spawn_item_begin"); + SoundEvent BLOCK_TRIAL_SPAWNER_SPAWN_ITEM_BEGIN = BuiltinSoundEvent.get("minecraft:block.trial_spawner.spawn_item_begin"); - SoundEvent BLOCK_TRIAL_SPAWNER_DETECT_PLAYER = SoundEventImpl.get("minecraft:block.trial_spawner.detect_player"); + SoundEvent BLOCK_TRIAL_SPAWNER_DETECT_PLAYER = BuiltinSoundEvent.get("minecraft:block.trial_spawner.detect_player"); - SoundEvent BLOCK_TRIAL_SPAWNER_CHARGE_ACTIVATE = SoundEventImpl.get("minecraft:block.trial_spawner.charge_activate"); + SoundEvent BLOCK_TRIAL_SPAWNER_CHARGE_ACTIVATE = BuiltinSoundEvent.get("minecraft:block.trial_spawner.charge_activate"); - SoundEvent BLOCK_TRIAL_SPAWNER_AMBIENT = SoundEventImpl.get("minecraft:block.trial_spawner.ambient"); + SoundEvent BLOCK_TRIAL_SPAWNER_AMBIENT = BuiltinSoundEvent.get("minecraft:block.trial_spawner.ambient"); - SoundEvent BLOCK_TRIAL_SPAWNER_AMBIENT_CHARGED = SoundEventImpl.get("minecraft:block.trial_spawner.ambient_charged"); + SoundEvent BLOCK_TRIAL_SPAWNER_AMBIENT_CHARGED = BuiltinSoundEvent.get("minecraft:block.trial_spawner.ambient_charged"); - SoundEvent BLOCK_TRIAL_SPAWNER_OPEN_SHUTTER = SoundEventImpl.get("minecraft:block.trial_spawner.open_shutter"); + SoundEvent BLOCK_TRIAL_SPAWNER_OPEN_SHUTTER = BuiltinSoundEvent.get("minecraft:block.trial_spawner.open_shutter"); - SoundEvent BLOCK_TRIAL_SPAWNER_CLOSE_SHUTTER = SoundEventImpl.get("minecraft:block.trial_spawner.close_shutter"); + SoundEvent BLOCK_TRIAL_SPAWNER_CLOSE_SHUTTER = BuiltinSoundEvent.get("minecraft:block.trial_spawner.close_shutter"); - SoundEvent BLOCK_TRIAL_SPAWNER_EJECT_ITEM = SoundEventImpl.get("minecraft:block.trial_spawner.eject_item"); + SoundEvent BLOCK_TRIAL_SPAWNER_EJECT_ITEM = BuiltinSoundEvent.get("minecraft:block.trial_spawner.eject_item"); - SoundEvent ITEM_HOE_TILL = SoundEventImpl.get("minecraft:item.hoe.till"); + SoundEvent ITEM_HOE_TILL = BuiltinSoundEvent.get("minecraft:item.hoe.till"); - SoundEvent ENTITY_HOGLIN_AMBIENT = SoundEventImpl.get("minecraft:entity.hoglin.ambient"); + SoundEvent ENTITY_HOGLIN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.hoglin.ambient"); - SoundEvent ENTITY_HOGLIN_ANGRY = SoundEventImpl.get("minecraft:entity.hoglin.angry"); + SoundEvent ENTITY_HOGLIN_ANGRY = BuiltinSoundEvent.get("minecraft:entity.hoglin.angry"); - SoundEvent ENTITY_HOGLIN_ATTACK = SoundEventImpl.get("minecraft:entity.hoglin.attack"); + SoundEvent ENTITY_HOGLIN_ATTACK = BuiltinSoundEvent.get("minecraft:entity.hoglin.attack"); - SoundEvent ENTITY_HOGLIN_CONVERTED_TO_ZOMBIFIED = SoundEventImpl.get("minecraft:entity.hoglin.converted_to_zombified"); + SoundEvent ENTITY_HOGLIN_CONVERTED_TO_ZOMBIFIED = BuiltinSoundEvent.get("minecraft:entity.hoglin.converted_to_zombified"); - SoundEvent ENTITY_HOGLIN_DEATH = SoundEventImpl.get("minecraft:entity.hoglin.death"); + SoundEvent ENTITY_HOGLIN_DEATH = BuiltinSoundEvent.get("minecraft:entity.hoglin.death"); - SoundEvent ENTITY_HOGLIN_HURT = SoundEventImpl.get("minecraft:entity.hoglin.hurt"); + SoundEvent ENTITY_HOGLIN_HURT = BuiltinSoundEvent.get("minecraft:entity.hoglin.hurt"); - SoundEvent ENTITY_HOGLIN_RETREAT = SoundEventImpl.get("minecraft:entity.hoglin.retreat"); + SoundEvent ENTITY_HOGLIN_RETREAT = BuiltinSoundEvent.get("minecraft:entity.hoglin.retreat"); - SoundEvent ENTITY_HOGLIN_STEP = SoundEventImpl.get("minecraft:entity.hoglin.step"); + SoundEvent ENTITY_HOGLIN_STEP = BuiltinSoundEvent.get("minecraft:entity.hoglin.step"); - SoundEvent BLOCK_HONEY_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.honey_block.break"); + SoundEvent BLOCK_HONEY_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.honey_block.break"); - SoundEvent BLOCK_HONEY_BLOCK_FALL = SoundEventImpl.get("minecraft:block.honey_block.fall"); + SoundEvent BLOCK_HONEY_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.honey_block.fall"); - SoundEvent BLOCK_HONEY_BLOCK_HIT = SoundEventImpl.get("minecraft:block.honey_block.hit"); + SoundEvent BLOCK_HONEY_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.honey_block.hit"); - SoundEvent BLOCK_HONEY_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.honey_block.place"); + SoundEvent BLOCK_HONEY_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.honey_block.place"); - SoundEvent BLOCK_HONEY_BLOCK_SLIDE = SoundEventImpl.get("minecraft:block.honey_block.slide"); + SoundEvent BLOCK_HONEY_BLOCK_SLIDE = BuiltinSoundEvent.get("minecraft:block.honey_block.slide"); - SoundEvent BLOCK_HONEY_BLOCK_STEP = SoundEventImpl.get("minecraft:block.honey_block.step"); + SoundEvent BLOCK_HONEY_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.honey_block.step"); - SoundEvent ITEM_HONEYCOMB_WAX_ON = SoundEventImpl.get("minecraft:item.honeycomb.wax_on"); + SoundEvent ITEM_HONEYCOMB_WAX_ON = BuiltinSoundEvent.get("minecraft:item.honeycomb.wax_on"); - SoundEvent ITEM_HONEY_BOTTLE_DRINK = SoundEventImpl.get("minecraft:item.honey_bottle.drink"); + SoundEvent ITEM_HONEY_BOTTLE_DRINK = BuiltinSoundEvent.get("minecraft:item.honey_bottle.drink"); - SoundEvent ITEM_GOAT_HORN_SOUND_0 = SoundEventImpl.get("minecraft:item.goat_horn.sound.0"); + SoundEvent ITEM_GOAT_HORN_SOUND_0 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.0"); - SoundEvent ITEM_GOAT_HORN_SOUND_1 = SoundEventImpl.get("minecraft:item.goat_horn.sound.1"); + SoundEvent ITEM_GOAT_HORN_SOUND_1 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.1"); - SoundEvent ITEM_GOAT_HORN_SOUND_2 = SoundEventImpl.get("minecraft:item.goat_horn.sound.2"); + SoundEvent ITEM_GOAT_HORN_SOUND_2 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.2"); - SoundEvent ITEM_GOAT_HORN_SOUND_3 = SoundEventImpl.get("minecraft:item.goat_horn.sound.3"); + SoundEvent ITEM_GOAT_HORN_SOUND_3 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.3"); - SoundEvent ITEM_GOAT_HORN_SOUND_4 = SoundEventImpl.get("minecraft:item.goat_horn.sound.4"); + SoundEvent ITEM_GOAT_HORN_SOUND_4 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.4"); - SoundEvent ITEM_GOAT_HORN_SOUND_5 = SoundEventImpl.get("minecraft:item.goat_horn.sound.5"); + SoundEvent ITEM_GOAT_HORN_SOUND_5 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.5"); - SoundEvent ITEM_GOAT_HORN_SOUND_6 = SoundEventImpl.get("minecraft:item.goat_horn.sound.6"); + SoundEvent ITEM_GOAT_HORN_SOUND_6 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.6"); - SoundEvent ITEM_GOAT_HORN_SOUND_7 = SoundEventImpl.get("minecraft:item.goat_horn.sound.7"); + SoundEvent ITEM_GOAT_HORN_SOUND_7 = BuiltinSoundEvent.get("minecraft:item.goat_horn.sound.7"); - SoundEvent ENTITY_HORSE_AMBIENT = SoundEventImpl.get("minecraft:entity.horse.ambient"); + SoundEvent ENTITY_HORSE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.horse.ambient"); - SoundEvent ENTITY_HORSE_ANGRY = SoundEventImpl.get("minecraft:entity.horse.angry"); + SoundEvent ENTITY_HORSE_ANGRY = BuiltinSoundEvent.get("minecraft:entity.horse.angry"); - SoundEvent ENTITY_HORSE_ARMOR = SoundEventImpl.get("minecraft:entity.horse.armor"); + SoundEvent ENTITY_HORSE_ARMOR = BuiltinSoundEvent.get("minecraft:entity.horse.armor"); - SoundEvent ENTITY_HORSE_BREATHE = SoundEventImpl.get("minecraft:entity.horse.breathe"); + SoundEvent ENTITY_HORSE_BREATHE = BuiltinSoundEvent.get("minecraft:entity.horse.breathe"); - SoundEvent ENTITY_HORSE_DEATH = SoundEventImpl.get("minecraft:entity.horse.death"); + SoundEvent ENTITY_HORSE_DEATH = BuiltinSoundEvent.get("minecraft:entity.horse.death"); - SoundEvent ENTITY_HORSE_EAT = SoundEventImpl.get("minecraft:entity.horse.eat"); + SoundEvent ENTITY_HORSE_EAT = BuiltinSoundEvent.get("minecraft:entity.horse.eat"); - SoundEvent ENTITY_HORSE_GALLOP = SoundEventImpl.get("minecraft:entity.horse.gallop"); + SoundEvent ENTITY_HORSE_GALLOP = BuiltinSoundEvent.get("minecraft:entity.horse.gallop"); - SoundEvent ENTITY_HORSE_HURT = SoundEventImpl.get("minecraft:entity.horse.hurt"); + SoundEvent ENTITY_HORSE_HURT = BuiltinSoundEvent.get("minecraft:entity.horse.hurt"); - SoundEvent ENTITY_HORSE_JUMP = SoundEventImpl.get("minecraft:entity.horse.jump"); + SoundEvent ENTITY_HORSE_JUMP = BuiltinSoundEvent.get("minecraft:entity.horse.jump"); - SoundEvent ENTITY_HORSE_LAND = SoundEventImpl.get("minecraft:entity.horse.land"); + SoundEvent ENTITY_HORSE_LAND = BuiltinSoundEvent.get("minecraft:entity.horse.land"); - SoundEvent ENTITY_HORSE_SADDLE = SoundEventImpl.get("minecraft:entity.horse.saddle"); + SoundEvent ENTITY_HORSE_SADDLE = BuiltinSoundEvent.get("minecraft:entity.horse.saddle"); - SoundEvent ENTITY_HORSE_STEP = SoundEventImpl.get("minecraft:entity.horse.step"); + SoundEvent ENTITY_HORSE_STEP = BuiltinSoundEvent.get("minecraft:entity.horse.step"); - SoundEvent ENTITY_HORSE_STEP_WOOD = SoundEventImpl.get("minecraft:entity.horse.step_wood"); + SoundEvent ENTITY_HORSE_STEP_WOOD = BuiltinSoundEvent.get("minecraft:entity.horse.step_wood"); - SoundEvent ENTITY_HOSTILE_BIG_FALL = SoundEventImpl.get("minecraft:entity.hostile.big_fall"); + SoundEvent ENTITY_HOSTILE_BIG_FALL = BuiltinSoundEvent.get("minecraft:entity.hostile.big_fall"); - SoundEvent ENTITY_HOSTILE_DEATH = SoundEventImpl.get("minecraft:entity.hostile.death"); + SoundEvent ENTITY_HOSTILE_DEATH = BuiltinSoundEvent.get("minecraft:entity.hostile.death"); - SoundEvent ENTITY_HOSTILE_HURT = SoundEventImpl.get("minecraft:entity.hostile.hurt"); + SoundEvent ENTITY_HOSTILE_HURT = BuiltinSoundEvent.get("minecraft:entity.hostile.hurt"); - SoundEvent ENTITY_HOSTILE_SMALL_FALL = SoundEventImpl.get("minecraft:entity.hostile.small_fall"); + SoundEvent ENTITY_HOSTILE_SMALL_FALL = BuiltinSoundEvent.get("minecraft:entity.hostile.small_fall"); - SoundEvent ENTITY_HOSTILE_SPLASH = SoundEventImpl.get("minecraft:entity.hostile.splash"); + SoundEvent ENTITY_HOSTILE_SPLASH = BuiltinSoundEvent.get("minecraft:entity.hostile.splash"); - SoundEvent ENTITY_HOSTILE_SWIM = SoundEventImpl.get("minecraft:entity.hostile.swim"); + SoundEvent ENTITY_HOSTILE_SWIM = BuiltinSoundEvent.get("minecraft:entity.hostile.swim"); - SoundEvent ENTITY_HUSK_AMBIENT = SoundEventImpl.get("minecraft:entity.husk.ambient"); + SoundEvent ENTITY_HUSK_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.husk.ambient"); - SoundEvent ENTITY_HUSK_CONVERTED_TO_ZOMBIE = SoundEventImpl.get("minecraft:entity.husk.converted_to_zombie"); + SoundEvent ENTITY_HUSK_CONVERTED_TO_ZOMBIE = BuiltinSoundEvent.get("minecraft:entity.husk.converted_to_zombie"); - SoundEvent ENTITY_HUSK_DEATH = SoundEventImpl.get("minecraft:entity.husk.death"); + SoundEvent ENTITY_HUSK_DEATH = BuiltinSoundEvent.get("minecraft:entity.husk.death"); - SoundEvent ENTITY_HUSK_HURT = SoundEventImpl.get("minecraft:entity.husk.hurt"); + SoundEvent ENTITY_HUSK_HURT = BuiltinSoundEvent.get("minecraft:entity.husk.hurt"); - SoundEvent ENTITY_HUSK_STEP = SoundEventImpl.get("minecraft:entity.husk.step"); + SoundEvent ENTITY_HUSK_STEP = BuiltinSoundEvent.get("minecraft:entity.husk.step"); - SoundEvent ENTITY_ILLUSIONER_AMBIENT = SoundEventImpl.get("minecraft:entity.illusioner.ambient"); + SoundEvent ENTITY_ILLUSIONER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.illusioner.ambient"); - SoundEvent ENTITY_ILLUSIONER_CAST_SPELL = SoundEventImpl.get("minecraft:entity.illusioner.cast_spell"); + SoundEvent ENTITY_ILLUSIONER_CAST_SPELL = BuiltinSoundEvent.get("minecraft:entity.illusioner.cast_spell"); - SoundEvent ENTITY_ILLUSIONER_DEATH = SoundEventImpl.get("minecraft:entity.illusioner.death"); + SoundEvent ENTITY_ILLUSIONER_DEATH = BuiltinSoundEvent.get("minecraft:entity.illusioner.death"); - SoundEvent ENTITY_ILLUSIONER_HURT = SoundEventImpl.get("minecraft:entity.illusioner.hurt"); + SoundEvent ENTITY_ILLUSIONER_HURT = BuiltinSoundEvent.get("minecraft:entity.illusioner.hurt"); - SoundEvent ENTITY_ILLUSIONER_MIRROR_MOVE = SoundEventImpl.get("minecraft:entity.illusioner.mirror_move"); + SoundEvent ENTITY_ILLUSIONER_MIRROR_MOVE = BuiltinSoundEvent.get("minecraft:entity.illusioner.mirror_move"); - SoundEvent ENTITY_ILLUSIONER_PREPARE_BLINDNESS = SoundEventImpl.get("minecraft:entity.illusioner.prepare_blindness"); + SoundEvent ENTITY_ILLUSIONER_PREPARE_BLINDNESS = BuiltinSoundEvent.get("minecraft:entity.illusioner.prepare_blindness"); - SoundEvent ENTITY_ILLUSIONER_PREPARE_MIRROR = SoundEventImpl.get("minecraft:entity.illusioner.prepare_mirror"); + SoundEvent ENTITY_ILLUSIONER_PREPARE_MIRROR = BuiltinSoundEvent.get("minecraft:entity.illusioner.prepare_mirror"); - SoundEvent ITEM_INK_SAC_USE = SoundEventImpl.get("minecraft:item.ink_sac.use"); + SoundEvent ITEM_INK_SAC_USE = BuiltinSoundEvent.get("minecraft:item.ink_sac.use"); - SoundEvent BLOCK_IRON_DOOR_CLOSE = SoundEventImpl.get("minecraft:block.iron_door.close"); + SoundEvent BLOCK_IRON_DOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.iron_door.close"); - SoundEvent BLOCK_IRON_DOOR_OPEN = SoundEventImpl.get("minecraft:block.iron_door.open"); + SoundEvent BLOCK_IRON_DOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.iron_door.open"); - SoundEvent ENTITY_IRON_GOLEM_ATTACK = SoundEventImpl.get("minecraft:entity.iron_golem.attack"); + SoundEvent ENTITY_IRON_GOLEM_ATTACK = BuiltinSoundEvent.get("minecraft:entity.iron_golem.attack"); - SoundEvent ENTITY_IRON_GOLEM_DAMAGE = SoundEventImpl.get("minecraft:entity.iron_golem.damage"); + SoundEvent ENTITY_IRON_GOLEM_DAMAGE = BuiltinSoundEvent.get("minecraft:entity.iron_golem.damage"); - SoundEvent ENTITY_IRON_GOLEM_DEATH = SoundEventImpl.get("minecraft:entity.iron_golem.death"); + SoundEvent ENTITY_IRON_GOLEM_DEATH = BuiltinSoundEvent.get("minecraft:entity.iron_golem.death"); - SoundEvent ENTITY_IRON_GOLEM_HURT = SoundEventImpl.get("minecraft:entity.iron_golem.hurt"); + SoundEvent ENTITY_IRON_GOLEM_HURT = BuiltinSoundEvent.get("minecraft:entity.iron_golem.hurt"); - SoundEvent ENTITY_IRON_GOLEM_REPAIR = SoundEventImpl.get("minecraft:entity.iron_golem.repair"); + SoundEvent ENTITY_IRON_GOLEM_REPAIR = BuiltinSoundEvent.get("minecraft:entity.iron_golem.repair"); - SoundEvent ENTITY_IRON_GOLEM_STEP = SoundEventImpl.get("minecraft:entity.iron_golem.step"); + SoundEvent ENTITY_IRON_GOLEM_STEP = BuiltinSoundEvent.get("minecraft:entity.iron_golem.step"); - SoundEvent BLOCK_IRON_TRAPDOOR_CLOSE = SoundEventImpl.get("minecraft:block.iron_trapdoor.close"); + SoundEvent BLOCK_IRON_TRAPDOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.iron_trapdoor.close"); - SoundEvent BLOCK_IRON_TRAPDOOR_OPEN = SoundEventImpl.get("minecraft:block.iron_trapdoor.open"); + SoundEvent BLOCK_IRON_TRAPDOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.iron_trapdoor.open"); - SoundEvent ENTITY_ITEM_FRAME_ADD_ITEM = SoundEventImpl.get("minecraft:entity.item_frame.add_item"); + SoundEvent ENTITY_ITEM_FRAME_ADD_ITEM = BuiltinSoundEvent.get("minecraft:entity.item_frame.add_item"); - SoundEvent ENTITY_ITEM_FRAME_BREAK = SoundEventImpl.get("minecraft:entity.item_frame.break"); + SoundEvent ENTITY_ITEM_FRAME_BREAK = BuiltinSoundEvent.get("minecraft:entity.item_frame.break"); - SoundEvent ENTITY_ITEM_FRAME_PLACE = SoundEventImpl.get("minecraft:entity.item_frame.place"); + SoundEvent ENTITY_ITEM_FRAME_PLACE = BuiltinSoundEvent.get("minecraft:entity.item_frame.place"); - SoundEvent ENTITY_ITEM_FRAME_REMOVE_ITEM = SoundEventImpl.get("minecraft:entity.item_frame.remove_item"); + SoundEvent ENTITY_ITEM_FRAME_REMOVE_ITEM = BuiltinSoundEvent.get("minecraft:entity.item_frame.remove_item"); - SoundEvent ENTITY_ITEM_FRAME_ROTATE_ITEM = SoundEventImpl.get("minecraft:entity.item_frame.rotate_item"); + SoundEvent ENTITY_ITEM_FRAME_ROTATE_ITEM = BuiltinSoundEvent.get("minecraft:entity.item_frame.rotate_item"); - SoundEvent ENTITY_ITEM_BREAK = SoundEventImpl.get("minecraft:entity.item.break"); + SoundEvent ENTITY_ITEM_BREAK = BuiltinSoundEvent.get("minecraft:entity.item.break"); - SoundEvent ENTITY_ITEM_PICKUP = SoundEventImpl.get("minecraft:entity.item.pickup"); + SoundEvent ENTITY_ITEM_PICKUP = BuiltinSoundEvent.get("minecraft:entity.item.pickup"); - SoundEvent BLOCK_LADDER_BREAK = SoundEventImpl.get("minecraft:block.ladder.break"); + SoundEvent BLOCK_LADDER_BREAK = BuiltinSoundEvent.get("minecraft:block.ladder.break"); - SoundEvent BLOCK_LADDER_FALL = SoundEventImpl.get("minecraft:block.ladder.fall"); + SoundEvent BLOCK_LADDER_FALL = BuiltinSoundEvent.get("minecraft:block.ladder.fall"); - SoundEvent BLOCK_LADDER_HIT = SoundEventImpl.get("minecraft:block.ladder.hit"); + SoundEvent BLOCK_LADDER_HIT = BuiltinSoundEvent.get("minecraft:block.ladder.hit"); - SoundEvent BLOCK_LADDER_PLACE = SoundEventImpl.get("minecraft:block.ladder.place"); + SoundEvent BLOCK_LADDER_PLACE = BuiltinSoundEvent.get("minecraft:block.ladder.place"); - SoundEvent BLOCK_LADDER_STEP = SoundEventImpl.get("minecraft:block.ladder.step"); + SoundEvent BLOCK_LADDER_STEP = BuiltinSoundEvent.get("minecraft:block.ladder.step"); - SoundEvent BLOCK_LANTERN_BREAK = SoundEventImpl.get("minecraft:block.lantern.break"); + SoundEvent BLOCK_LANTERN_BREAK = BuiltinSoundEvent.get("minecraft:block.lantern.break"); - SoundEvent BLOCK_LANTERN_FALL = SoundEventImpl.get("minecraft:block.lantern.fall"); + SoundEvent BLOCK_LANTERN_FALL = BuiltinSoundEvent.get("minecraft:block.lantern.fall"); - SoundEvent BLOCK_LANTERN_HIT = SoundEventImpl.get("minecraft:block.lantern.hit"); + SoundEvent BLOCK_LANTERN_HIT = BuiltinSoundEvent.get("minecraft:block.lantern.hit"); - SoundEvent BLOCK_LANTERN_PLACE = SoundEventImpl.get("minecraft:block.lantern.place"); + SoundEvent BLOCK_LANTERN_PLACE = BuiltinSoundEvent.get("minecraft:block.lantern.place"); - SoundEvent BLOCK_LANTERN_STEP = SoundEventImpl.get("minecraft:block.lantern.step"); + SoundEvent BLOCK_LANTERN_STEP = BuiltinSoundEvent.get("minecraft:block.lantern.step"); - SoundEvent BLOCK_LARGE_AMETHYST_BUD_BREAK = SoundEventImpl.get("minecraft:block.large_amethyst_bud.break"); + SoundEvent BLOCK_LARGE_AMETHYST_BUD_BREAK = BuiltinSoundEvent.get("minecraft:block.large_amethyst_bud.break"); - SoundEvent BLOCK_LARGE_AMETHYST_BUD_PLACE = SoundEventImpl.get("minecraft:block.large_amethyst_bud.place"); + SoundEvent BLOCK_LARGE_AMETHYST_BUD_PLACE = BuiltinSoundEvent.get("minecraft:block.large_amethyst_bud.place"); - SoundEvent BLOCK_LAVA_AMBIENT = SoundEventImpl.get("minecraft:block.lava.ambient"); + SoundEvent BLOCK_LAVA_AMBIENT = BuiltinSoundEvent.get("minecraft:block.lava.ambient"); - SoundEvent BLOCK_LAVA_EXTINGUISH = SoundEventImpl.get("minecraft:block.lava.extinguish"); + SoundEvent BLOCK_LAVA_EXTINGUISH = BuiltinSoundEvent.get("minecraft:block.lava.extinguish"); - SoundEvent BLOCK_LAVA_POP = SoundEventImpl.get("minecraft:block.lava.pop"); + SoundEvent BLOCK_LAVA_POP = BuiltinSoundEvent.get("minecraft:block.lava.pop"); - SoundEvent ENTITY_LEASH_KNOT_BREAK = SoundEventImpl.get("minecraft:entity.leash_knot.break"); + SoundEvent ENTITY_LEASH_KNOT_BREAK = BuiltinSoundEvent.get("minecraft:entity.leash_knot.break"); - SoundEvent ENTITY_LEASH_KNOT_PLACE = SoundEventImpl.get("minecraft:entity.leash_knot.place"); + SoundEvent ENTITY_LEASH_KNOT_PLACE = BuiltinSoundEvent.get("minecraft:entity.leash_knot.place"); - SoundEvent BLOCK_LEVER_CLICK = SoundEventImpl.get("minecraft:block.lever.click"); + SoundEvent BLOCK_LEVER_CLICK = BuiltinSoundEvent.get("minecraft:block.lever.click"); - SoundEvent ENTITY_LIGHTNING_BOLT_IMPACT = SoundEventImpl.get("minecraft:entity.lightning_bolt.impact"); + SoundEvent ENTITY_LIGHTNING_BOLT_IMPACT = BuiltinSoundEvent.get("minecraft:entity.lightning_bolt.impact"); - SoundEvent ENTITY_LIGHTNING_BOLT_THUNDER = SoundEventImpl.get("minecraft:entity.lightning_bolt.thunder"); + SoundEvent ENTITY_LIGHTNING_BOLT_THUNDER = BuiltinSoundEvent.get("minecraft:entity.lightning_bolt.thunder"); - SoundEvent ENTITY_LINGERING_POTION_THROW = SoundEventImpl.get("minecraft:entity.lingering_potion.throw"); + SoundEvent ENTITY_LINGERING_POTION_THROW = BuiltinSoundEvent.get("minecraft:entity.lingering_potion.throw"); - SoundEvent ENTITY_LLAMA_AMBIENT = SoundEventImpl.get("minecraft:entity.llama.ambient"); + SoundEvent ENTITY_LLAMA_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.llama.ambient"); - SoundEvent ENTITY_LLAMA_ANGRY = SoundEventImpl.get("minecraft:entity.llama.angry"); + SoundEvent ENTITY_LLAMA_ANGRY = BuiltinSoundEvent.get("minecraft:entity.llama.angry"); - SoundEvent ENTITY_LLAMA_CHEST = SoundEventImpl.get("minecraft:entity.llama.chest"); + SoundEvent ENTITY_LLAMA_CHEST = BuiltinSoundEvent.get("minecraft:entity.llama.chest"); - SoundEvent ENTITY_LLAMA_DEATH = SoundEventImpl.get("minecraft:entity.llama.death"); + SoundEvent ENTITY_LLAMA_DEATH = BuiltinSoundEvent.get("minecraft:entity.llama.death"); - SoundEvent ENTITY_LLAMA_EAT = SoundEventImpl.get("minecraft:entity.llama.eat"); + SoundEvent ENTITY_LLAMA_EAT = BuiltinSoundEvent.get("minecraft:entity.llama.eat"); - SoundEvent ENTITY_LLAMA_HURT = SoundEventImpl.get("minecraft:entity.llama.hurt"); + SoundEvent ENTITY_LLAMA_HURT = BuiltinSoundEvent.get("minecraft:entity.llama.hurt"); - SoundEvent ENTITY_LLAMA_SPIT = SoundEventImpl.get("minecraft:entity.llama.spit"); + SoundEvent ENTITY_LLAMA_SPIT = BuiltinSoundEvent.get("minecraft:entity.llama.spit"); - SoundEvent ENTITY_LLAMA_STEP = SoundEventImpl.get("minecraft:entity.llama.step"); + SoundEvent ENTITY_LLAMA_STEP = BuiltinSoundEvent.get("minecraft:entity.llama.step"); - SoundEvent ENTITY_LLAMA_SWAG = SoundEventImpl.get("minecraft:entity.llama.swag"); + SoundEvent ENTITY_LLAMA_SWAG = BuiltinSoundEvent.get("minecraft:entity.llama.swag"); - SoundEvent ENTITY_MAGMA_CUBE_DEATH_SMALL = SoundEventImpl.get("minecraft:entity.magma_cube.death_small"); + SoundEvent ENTITY_MAGMA_CUBE_DEATH_SMALL = BuiltinSoundEvent.get("minecraft:entity.magma_cube.death_small"); - SoundEvent BLOCK_LODESTONE_BREAK = SoundEventImpl.get("minecraft:block.lodestone.break"); + SoundEvent BLOCK_LODESTONE_BREAK = BuiltinSoundEvent.get("minecraft:block.lodestone.break"); - SoundEvent BLOCK_LODESTONE_STEP = SoundEventImpl.get("minecraft:block.lodestone.step"); + SoundEvent BLOCK_LODESTONE_STEP = BuiltinSoundEvent.get("minecraft:block.lodestone.step"); - SoundEvent BLOCK_LODESTONE_PLACE = SoundEventImpl.get("minecraft:block.lodestone.place"); + SoundEvent BLOCK_LODESTONE_PLACE = BuiltinSoundEvent.get("minecraft:block.lodestone.place"); - SoundEvent BLOCK_LODESTONE_HIT = SoundEventImpl.get("minecraft:block.lodestone.hit"); + SoundEvent BLOCK_LODESTONE_HIT = BuiltinSoundEvent.get("minecraft:block.lodestone.hit"); - SoundEvent BLOCK_LODESTONE_FALL = SoundEventImpl.get("minecraft:block.lodestone.fall"); + SoundEvent BLOCK_LODESTONE_FALL = BuiltinSoundEvent.get("minecraft:block.lodestone.fall"); - SoundEvent ITEM_LODESTONE_COMPASS_LOCK = SoundEventImpl.get("minecraft:item.lodestone_compass.lock"); + SoundEvent ITEM_LODESTONE_COMPASS_LOCK = BuiltinSoundEvent.get("minecraft:item.lodestone_compass.lock"); - SoundEvent ITEM_MACE_SMASH_AIR = SoundEventImpl.get("minecraft:item.mace.smash_air"); + SoundEvent ITEM_MACE_SMASH_AIR = BuiltinSoundEvent.get("minecraft:item.mace.smash_air"); - SoundEvent ITEM_MACE_SMASH_GROUND = SoundEventImpl.get("minecraft:item.mace.smash_ground"); + SoundEvent ITEM_MACE_SMASH_GROUND = BuiltinSoundEvent.get("minecraft:item.mace.smash_ground"); - SoundEvent ITEM_MACE_SMASH_GROUND_HEAVY = SoundEventImpl.get("minecraft:item.mace.smash_ground_heavy"); + SoundEvent ITEM_MACE_SMASH_GROUND_HEAVY = BuiltinSoundEvent.get("minecraft:item.mace.smash_ground_heavy"); - SoundEvent ENTITY_MAGMA_CUBE_DEATH = SoundEventImpl.get("minecraft:entity.magma_cube.death"); + SoundEvent ENTITY_MAGMA_CUBE_DEATH = BuiltinSoundEvent.get("minecraft:entity.magma_cube.death"); - SoundEvent ENTITY_MAGMA_CUBE_HURT = SoundEventImpl.get("minecraft:entity.magma_cube.hurt"); + SoundEvent ENTITY_MAGMA_CUBE_HURT = BuiltinSoundEvent.get("minecraft:entity.magma_cube.hurt"); - SoundEvent ENTITY_MAGMA_CUBE_HURT_SMALL = SoundEventImpl.get("minecraft:entity.magma_cube.hurt_small"); + SoundEvent ENTITY_MAGMA_CUBE_HURT_SMALL = BuiltinSoundEvent.get("minecraft:entity.magma_cube.hurt_small"); - SoundEvent ENTITY_MAGMA_CUBE_JUMP = SoundEventImpl.get("minecraft:entity.magma_cube.jump"); + SoundEvent ENTITY_MAGMA_CUBE_JUMP = BuiltinSoundEvent.get("minecraft:entity.magma_cube.jump"); - SoundEvent ENTITY_MAGMA_CUBE_SQUISH = SoundEventImpl.get("minecraft:entity.magma_cube.squish"); + SoundEvent ENTITY_MAGMA_CUBE_SQUISH = BuiltinSoundEvent.get("minecraft:entity.magma_cube.squish"); - SoundEvent ENTITY_MAGMA_CUBE_SQUISH_SMALL = SoundEventImpl.get("minecraft:entity.magma_cube.squish_small"); + SoundEvent ENTITY_MAGMA_CUBE_SQUISH_SMALL = BuiltinSoundEvent.get("minecraft:entity.magma_cube.squish_small"); - SoundEvent BLOCK_MANGROVE_ROOTS_BREAK = SoundEventImpl.get("minecraft:block.mangrove_roots.break"); + SoundEvent BLOCK_MANGROVE_ROOTS_BREAK = BuiltinSoundEvent.get("minecraft:block.mangrove_roots.break"); - SoundEvent BLOCK_MANGROVE_ROOTS_FALL = SoundEventImpl.get("minecraft:block.mangrove_roots.fall"); + SoundEvent BLOCK_MANGROVE_ROOTS_FALL = BuiltinSoundEvent.get("minecraft:block.mangrove_roots.fall"); - SoundEvent BLOCK_MANGROVE_ROOTS_HIT = SoundEventImpl.get("minecraft:block.mangrove_roots.hit"); + SoundEvent BLOCK_MANGROVE_ROOTS_HIT = BuiltinSoundEvent.get("minecraft:block.mangrove_roots.hit"); - SoundEvent BLOCK_MANGROVE_ROOTS_PLACE = SoundEventImpl.get("minecraft:block.mangrove_roots.place"); + SoundEvent BLOCK_MANGROVE_ROOTS_PLACE = BuiltinSoundEvent.get("minecraft:block.mangrove_roots.place"); - SoundEvent BLOCK_MANGROVE_ROOTS_STEP = SoundEventImpl.get("minecraft:block.mangrove_roots.step"); + SoundEvent BLOCK_MANGROVE_ROOTS_STEP = BuiltinSoundEvent.get("minecraft:block.mangrove_roots.step"); - SoundEvent BLOCK_MEDIUM_AMETHYST_BUD_BREAK = SoundEventImpl.get("minecraft:block.medium_amethyst_bud.break"); + SoundEvent BLOCK_MEDIUM_AMETHYST_BUD_BREAK = BuiltinSoundEvent.get("minecraft:block.medium_amethyst_bud.break"); - SoundEvent BLOCK_MEDIUM_AMETHYST_BUD_PLACE = SoundEventImpl.get("minecraft:block.medium_amethyst_bud.place"); + SoundEvent BLOCK_MEDIUM_AMETHYST_BUD_PLACE = BuiltinSoundEvent.get("minecraft:block.medium_amethyst_bud.place"); - SoundEvent BLOCK_METAL_BREAK = SoundEventImpl.get("minecraft:block.metal.break"); + SoundEvent BLOCK_METAL_BREAK = BuiltinSoundEvent.get("minecraft:block.metal.break"); - SoundEvent BLOCK_METAL_FALL = SoundEventImpl.get("minecraft:block.metal.fall"); + SoundEvent BLOCK_METAL_FALL = BuiltinSoundEvent.get("minecraft:block.metal.fall"); - SoundEvent BLOCK_METAL_HIT = SoundEventImpl.get("minecraft:block.metal.hit"); + SoundEvent BLOCK_METAL_HIT = BuiltinSoundEvent.get("minecraft:block.metal.hit"); - SoundEvent BLOCK_METAL_PLACE = SoundEventImpl.get("minecraft:block.metal.place"); + SoundEvent BLOCK_METAL_PLACE = BuiltinSoundEvent.get("minecraft:block.metal.place"); - SoundEvent BLOCK_METAL_PRESSURE_PLATE_CLICK_OFF = SoundEventImpl.get("minecraft:block.metal_pressure_plate.click_off"); + SoundEvent BLOCK_METAL_PRESSURE_PLATE_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.metal_pressure_plate.click_off"); - SoundEvent BLOCK_METAL_PRESSURE_PLATE_CLICK_ON = SoundEventImpl.get("minecraft:block.metal_pressure_plate.click_on"); + SoundEvent BLOCK_METAL_PRESSURE_PLATE_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.metal_pressure_plate.click_on"); - SoundEvent BLOCK_METAL_STEP = SoundEventImpl.get("minecraft:block.metal.step"); + SoundEvent BLOCK_METAL_STEP = BuiltinSoundEvent.get("minecraft:block.metal.step"); - SoundEvent ENTITY_MINECART_INSIDE_UNDERWATER = SoundEventImpl.get("minecraft:entity.minecart.inside.underwater"); + SoundEvent ENTITY_MINECART_INSIDE_UNDERWATER = BuiltinSoundEvent.get("minecraft:entity.minecart.inside.underwater"); - SoundEvent ENTITY_MINECART_INSIDE = SoundEventImpl.get("minecraft:entity.minecart.inside"); + SoundEvent ENTITY_MINECART_INSIDE = BuiltinSoundEvent.get("minecraft:entity.minecart.inside"); - SoundEvent ENTITY_MINECART_RIDING = SoundEventImpl.get("minecraft:entity.minecart.riding"); + SoundEvent ENTITY_MINECART_RIDING = BuiltinSoundEvent.get("minecraft:entity.minecart.riding"); - SoundEvent ENTITY_MOOSHROOM_CONVERT = SoundEventImpl.get("minecraft:entity.mooshroom.convert"); + SoundEvent ENTITY_MOOSHROOM_CONVERT = BuiltinSoundEvent.get("minecraft:entity.mooshroom.convert"); - SoundEvent ENTITY_MOOSHROOM_EAT = SoundEventImpl.get("minecraft:entity.mooshroom.eat"); + SoundEvent ENTITY_MOOSHROOM_EAT = BuiltinSoundEvent.get("minecraft:entity.mooshroom.eat"); - SoundEvent ENTITY_MOOSHROOM_MILK = SoundEventImpl.get("minecraft:entity.mooshroom.milk"); + SoundEvent ENTITY_MOOSHROOM_MILK = BuiltinSoundEvent.get("minecraft:entity.mooshroom.milk"); - SoundEvent ENTITY_MOOSHROOM_SUSPICIOUS_MILK = SoundEventImpl.get("minecraft:entity.mooshroom.suspicious_milk"); + SoundEvent ENTITY_MOOSHROOM_SUSPICIOUS_MILK = BuiltinSoundEvent.get("minecraft:entity.mooshroom.suspicious_milk"); - SoundEvent ENTITY_MOOSHROOM_SHEAR = SoundEventImpl.get("minecraft:entity.mooshroom.shear"); + SoundEvent ENTITY_MOOSHROOM_SHEAR = BuiltinSoundEvent.get("minecraft:entity.mooshroom.shear"); - SoundEvent BLOCK_MOSS_CARPET_BREAK = SoundEventImpl.get("minecraft:block.moss_carpet.break"); + SoundEvent BLOCK_MOSS_CARPET_BREAK = BuiltinSoundEvent.get("minecraft:block.moss_carpet.break"); - SoundEvent BLOCK_MOSS_CARPET_FALL = SoundEventImpl.get("minecraft:block.moss_carpet.fall"); + SoundEvent BLOCK_MOSS_CARPET_FALL = BuiltinSoundEvent.get("minecraft:block.moss_carpet.fall"); - SoundEvent BLOCK_MOSS_CARPET_HIT = SoundEventImpl.get("minecraft:block.moss_carpet.hit"); + SoundEvent BLOCK_MOSS_CARPET_HIT = BuiltinSoundEvent.get("minecraft:block.moss_carpet.hit"); - SoundEvent BLOCK_MOSS_CARPET_PLACE = SoundEventImpl.get("minecraft:block.moss_carpet.place"); + SoundEvent BLOCK_MOSS_CARPET_PLACE = BuiltinSoundEvent.get("minecraft:block.moss_carpet.place"); - SoundEvent BLOCK_MOSS_CARPET_STEP = SoundEventImpl.get("minecraft:block.moss_carpet.step"); + SoundEvent BLOCK_MOSS_CARPET_STEP = BuiltinSoundEvent.get("minecraft:block.moss_carpet.step"); - SoundEvent BLOCK_PINK_PETALS_BREAK = SoundEventImpl.get("minecraft:block.pink_petals.break"); + SoundEvent BLOCK_PINK_PETALS_BREAK = BuiltinSoundEvent.get("minecraft:block.pink_petals.break"); - SoundEvent BLOCK_PINK_PETALS_FALL = SoundEventImpl.get("minecraft:block.pink_petals.fall"); + SoundEvent BLOCK_PINK_PETALS_FALL = BuiltinSoundEvent.get("minecraft:block.pink_petals.fall"); - SoundEvent BLOCK_PINK_PETALS_HIT = SoundEventImpl.get("minecraft:block.pink_petals.hit"); + SoundEvent BLOCK_PINK_PETALS_HIT = BuiltinSoundEvent.get("minecraft:block.pink_petals.hit"); - SoundEvent BLOCK_PINK_PETALS_PLACE = SoundEventImpl.get("minecraft:block.pink_petals.place"); + SoundEvent BLOCK_PINK_PETALS_PLACE = BuiltinSoundEvent.get("minecraft:block.pink_petals.place"); - SoundEvent BLOCK_PINK_PETALS_STEP = SoundEventImpl.get("minecraft:block.pink_petals.step"); + SoundEvent BLOCK_PINK_PETALS_STEP = BuiltinSoundEvent.get("minecraft:block.pink_petals.step"); - SoundEvent BLOCK_MOSS_BREAK = SoundEventImpl.get("minecraft:block.moss.break"); + SoundEvent BLOCK_MOSS_BREAK = BuiltinSoundEvent.get("minecraft:block.moss.break"); - SoundEvent BLOCK_MOSS_FALL = SoundEventImpl.get("minecraft:block.moss.fall"); + SoundEvent BLOCK_MOSS_FALL = BuiltinSoundEvent.get("minecraft:block.moss.fall"); - SoundEvent BLOCK_MOSS_HIT = SoundEventImpl.get("minecraft:block.moss.hit"); + SoundEvent BLOCK_MOSS_HIT = BuiltinSoundEvent.get("minecraft:block.moss.hit"); - SoundEvent BLOCK_MOSS_PLACE = SoundEventImpl.get("minecraft:block.moss.place"); + SoundEvent BLOCK_MOSS_PLACE = BuiltinSoundEvent.get("minecraft:block.moss.place"); - SoundEvent BLOCK_MOSS_STEP = SoundEventImpl.get("minecraft:block.moss.step"); + SoundEvent BLOCK_MOSS_STEP = BuiltinSoundEvent.get("minecraft:block.moss.step"); - SoundEvent BLOCK_MUD_BREAK = SoundEventImpl.get("minecraft:block.mud.break"); + SoundEvent BLOCK_MUD_BREAK = BuiltinSoundEvent.get("minecraft:block.mud.break"); - SoundEvent BLOCK_MUD_FALL = SoundEventImpl.get("minecraft:block.mud.fall"); + SoundEvent BLOCK_MUD_FALL = BuiltinSoundEvent.get("minecraft:block.mud.fall"); - SoundEvent BLOCK_MUD_HIT = SoundEventImpl.get("minecraft:block.mud.hit"); + SoundEvent BLOCK_MUD_HIT = BuiltinSoundEvent.get("minecraft:block.mud.hit"); - SoundEvent BLOCK_MUD_PLACE = SoundEventImpl.get("minecraft:block.mud.place"); + SoundEvent BLOCK_MUD_PLACE = BuiltinSoundEvent.get("minecraft:block.mud.place"); - SoundEvent BLOCK_MUD_STEP = SoundEventImpl.get("minecraft:block.mud.step"); + SoundEvent BLOCK_MUD_STEP = BuiltinSoundEvent.get("minecraft:block.mud.step"); - SoundEvent BLOCK_MUD_BRICKS_BREAK = SoundEventImpl.get("minecraft:block.mud_bricks.break"); + SoundEvent BLOCK_MUD_BRICKS_BREAK = BuiltinSoundEvent.get("minecraft:block.mud_bricks.break"); - SoundEvent BLOCK_MUD_BRICKS_FALL = SoundEventImpl.get("minecraft:block.mud_bricks.fall"); + SoundEvent BLOCK_MUD_BRICKS_FALL = BuiltinSoundEvent.get("minecraft:block.mud_bricks.fall"); - SoundEvent BLOCK_MUD_BRICKS_HIT = SoundEventImpl.get("minecraft:block.mud_bricks.hit"); + SoundEvent BLOCK_MUD_BRICKS_HIT = BuiltinSoundEvent.get("minecraft:block.mud_bricks.hit"); - SoundEvent BLOCK_MUD_BRICKS_PLACE = SoundEventImpl.get("minecraft:block.mud_bricks.place"); + SoundEvent BLOCK_MUD_BRICKS_PLACE = BuiltinSoundEvent.get("minecraft:block.mud_bricks.place"); - SoundEvent BLOCK_MUD_BRICKS_STEP = SoundEventImpl.get("minecraft:block.mud_bricks.step"); + SoundEvent BLOCK_MUD_BRICKS_STEP = BuiltinSoundEvent.get("minecraft:block.mud_bricks.step"); - SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_BREAK = SoundEventImpl.get("minecraft:block.muddy_mangrove_roots.break"); + SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_BREAK = BuiltinSoundEvent.get("minecraft:block.muddy_mangrove_roots.break"); - SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_FALL = SoundEventImpl.get("minecraft:block.muddy_mangrove_roots.fall"); + SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_FALL = BuiltinSoundEvent.get("minecraft:block.muddy_mangrove_roots.fall"); - SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_HIT = SoundEventImpl.get("minecraft:block.muddy_mangrove_roots.hit"); + SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_HIT = BuiltinSoundEvent.get("minecraft:block.muddy_mangrove_roots.hit"); - SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_PLACE = SoundEventImpl.get("minecraft:block.muddy_mangrove_roots.place"); + SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_PLACE = BuiltinSoundEvent.get("minecraft:block.muddy_mangrove_roots.place"); - SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_STEP = SoundEventImpl.get("minecraft:block.muddy_mangrove_roots.step"); + SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_STEP = BuiltinSoundEvent.get("minecraft:block.muddy_mangrove_roots.step"); - SoundEvent ENTITY_MULE_AMBIENT = SoundEventImpl.get("minecraft:entity.mule.ambient"); + SoundEvent ENTITY_MULE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.mule.ambient"); - SoundEvent ENTITY_MULE_ANGRY = SoundEventImpl.get("minecraft:entity.mule.angry"); + SoundEvent ENTITY_MULE_ANGRY = BuiltinSoundEvent.get("minecraft:entity.mule.angry"); - SoundEvent ENTITY_MULE_CHEST = SoundEventImpl.get("minecraft:entity.mule.chest"); + SoundEvent ENTITY_MULE_CHEST = BuiltinSoundEvent.get("minecraft:entity.mule.chest"); - SoundEvent ENTITY_MULE_DEATH = SoundEventImpl.get("minecraft:entity.mule.death"); + SoundEvent ENTITY_MULE_DEATH = BuiltinSoundEvent.get("minecraft:entity.mule.death"); - SoundEvent ENTITY_MULE_EAT = SoundEventImpl.get("minecraft:entity.mule.eat"); + SoundEvent ENTITY_MULE_EAT = BuiltinSoundEvent.get("minecraft:entity.mule.eat"); - SoundEvent ENTITY_MULE_HURT = SoundEventImpl.get("minecraft:entity.mule.hurt"); + SoundEvent ENTITY_MULE_HURT = BuiltinSoundEvent.get("minecraft:entity.mule.hurt"); - SoundEvent ENTITY_MULE_JUMP = SoundEventImpl.get("minecraft:entity.mule.jump"); + SoundEvent ENTITY_MULE_JUMP = BuiltinSoundEvent.get("minecraft:entity.mule.jump"); - SoundEvent MUSIC_CREATIVE = SoundEventImpl.get("minecraft:music.creative"); + SoundEvent MUSIC_CREATIVE = BuiltinSoundEvent.get("minecraft:music.creative"); - SoundEvent MUSIC_CREDITS = SoundEventImpl.get("minecraft:music.credits"); + SoundEvent MUSIC_CREDITS = BuiltinSoundEvent.get("minecraft:music.credits"); - SoundEvent MUSIC_DISC_5 = SoundEventImpl.get("minecraft:music_disc.5"); + SoundEvent MUSIC_DISC_5 = BuiltinSoundEvent.get("minecraft:music_disc.5"); - SoundEvent MUSIC_DISC_11 = SoundEventImpl.get("minecraft:music_disc.11"); + SoundEvent MUSIC_DISC_11 = BuiltinSoundEvent.get("minecraft:music_disc.11"); - SoundEvent MUSIC_DISC_13 = SoundEventImpl.get("minecraft:music_disc.13"); + SoundEvent MUSIC_DISC_13 = BuiltinSoundEvent.get("minecraft:music_disc.13"); - SoundEvent MUSIC_DISC_BLOCKS = SoundEventImpl.get("minecraft:music_disc.blocks"); + SoundEvent MUSIC_DISC_BLOCKS = BuiltinSoundEvent.get("minecraft:music_disc.blocks"); - SoundEvent MUSIC_DISC_CAT = SoundEventImpl.get("minecraft:music_disc.cat"); + SoundEvent MUSIC_DISC_CAT = BuiltinSoundEvent.get("minecraft:music_disc.cat"); - SoundEvent MUSIC_DISC_CHIRP = SoundEventImpl.get("minecraft:music_disc.chirp"); + SoundEvent MUSIC_DISC_CHIRP = BuiltinSoundEvent.get("minecraft:music_disc.chirp"); - SoundEvent MUSIC_DISC_FAR = SoundEventImpl.get("minecraft:music_disc.far"); + SoundEvent MUSIC_DISC_FAR = BuiltinSoundEvent.get("minecraft:music_disc.far"); - SoundEvent MUSIC_DISC_MALL = SoundEventImpl.get("minecraft:music_disc.mall"); + SoundEvent MUSIC_DISC_MALL = BuiltinSoundEvent.get("minecraft:music_disc.mall"); - SoundEvent MUSIC_DISC_MELLOHI = SoundEventImpl.get("minecraft:music_disc.mellohi"); + SoundEvent MUSIC_DISC_MELLOHI = BuiltinSoundEvent.get("minecraft:music_disc.mellohi"); - SoundEvent MUSIC_DISC_PIGSTEP = SoundEventImpl.get("minecraft:music_disc.pigstep"); + SoundEvent MUSIC_DISC_PIGSTEP = BuiltinSoundEvent.get("minecraft:music_disc.pigstep"); - SoundEvent MUSIC_DISC_STAL = SoundEventImpl.get("minecraft:music_disc.stal"); + SoundEvent MUSIC_DISC_STAL = BuiltinSoundEvent.get("minecraft:music_disc.stal"); - SoundEvent MUSIC_DISC_STRAD = SoundEventImpl.get("minecraft:music_disc.strad"); + SoundEvent MUSIC_DISC_STRAD = BuiltinSoundEvent.get("minecraft:music_disc.strad"); - SoundEvent MUSIC_DISC_WAIT = SoundEventImpl.get("minecraft:music_disc.wait"); + SoundEvent MUSIC_DISC_WAIT = BuiltinSoundEvent.get("minecraft:music_disc.wait"); - SoundEvent MUSIC_DISC_WARD = SoundEventImpl.get("minecraft:music_disc.ward"); + SoundEvent MUSIC_DISC_WARD = BuiltinSoundEvent.get("minecraft:music_disc.ward"); - SoundEvent MUSIC_DISC_OTHERSIDE = SoundEventImpl.get("minecraft:music_disc.otherside"); + SoundEvent MUSIC_DISC_OTHERSIDE = BuiltinSoundEvent.get("minecraft:music_disc.otherside"); - SoundEvent MUSIC_DISC_RELIC = SoundEventImpl.get("minecraft:music_disc.relic"); + SoundEvent MUSIC_DISC_RELIC = BuiltinSoundEvent.get("minecraft:music_disc.relic"); - SoundEvent MUSIC_DRAGON = SoundEventImpl.get("minecraft:music.dragon"); + SoundEvent MUSIC_DRAGON = BuiltinSoundEvent.get("minecraft:music.dragon"); - SoundEvent MUSIC_END = SoundEventImpl.get("minecraft:music.end"); + SoundEvent MUSIC_END = BuiltinSoundEvent.get("minecraft:music.end"); - SoundEvent MUSIC_GAME = SoundEventImpl.get("minecraft:music.game"); + SoundEvent MUSIC_GAME = BuiltinSoundEvent.get("minecraft:music.game"); - SoundEvent MUSIC_MENU = SoundEventImpl.get("minecraft:music.menu"); + SoundEvent MUSIC_MENU = BuiltinSoundEvent.get("minecraft:music.menu"); - SoundEvent MUSIC_NETHER_BASALT_DELTAS = SoundEventImpl.get("minecraft:music.nether.basalt_deltas"); + SoundEvent MUSIC_NETHER_BASALT_DELTAS = BuiltinSoundEvent.get("minecraft:music.nether.basalt_deltas"); - SoundEvent MUSIC_NETHER_CRIMSON_FOREST = SoundEventImpl.get("minecraft:music.nether.crimson_forest"); + SoundEvent MUSIC_NETHER_CRIMSON_FOREST = BuiltinSoundEvent.get("minecraft:music.nether.crimson_forest"); - SoundEvent MUSIC_OVERWORLD_DEEP_DARK = SoundEventImpl.get("minecraft:music.overworld.deep_dark"); + SoundEvent MUSIC_OVERWORLD_DEEP_DARK = BuiltinSoundEvent.get("minecraft:music.overworld.deep_dark"); - SoundEvent MUSIC_OVERWORLD_DRIPSTONE_CAVES = SoundEventImpl.get("minecraft:music.overworld.dripstone_caves"); + SoundEvent MUSIC_OVERWORLD_DRIPSTONE_CAVES = BuiltinSoundEvent.get("minecraft:music.overworld.dripstone_caves"); - SoundEvent MUSIC_OVERWORLD_GROVE = SoundEventImpl.get("minecraft:music.overworld.grove"); + SoundEvent MUSIC_OVERWORLD_GROVE = BuiltinSoundEvent.get("minecraft:music.overworld.grove"); - SoundEvent MUSIC_OVERWORLD_JAGGED_PEAKS = SoundEventImpl.get("minecraft:music.overworld.jagged_peaks"); + SoundEvent MUSIC_OVERWORLD_JAGGED_PEAKS = BuiltinSoundEvent.get("minecraft:music.overworld.jagged_peaks"); - SoundEvent MUSIC_OVERWORLD_LUSH_CAVES = SoundEventImpl.get("minecraft:music.overworld.lush_caves"); + SoundEvent MUSIC_OVERWORLD_LUSH_CAVES = BuiltinSoundEvent.get("minecraft:music.overworld.lush_caves"); - SoundEvent MUSIC_OVERWORLD_SWAMP = SoundEventImpl.get("minecraft:music.overworld.swamp"); + SoundEvent MUSIC_OVERWORLD_SWAMP = BuiltinSoundEvent.get("minecraft:music.overworld.swamp"); - SoundEvent MUSIC_OVERWORLD_FOREST = SoundEventImpl.get("minecraft:music.overworld.forest"); + SoundEvent MUSIC_OVERWORLD_FOREST = BuiltinSoundEvent.get("minecraft:music.overworld.forest"); - SoundEvent MUSIC_OVERWORLD_OLD_GROWTH_TAIGA = SoundEventImpl.get("minecraft:music.overworld.old_growth_taiga"); + SoundEvent MUSIC_OVERWORLD_OLD_GROWTH_TAIGA = BuiltinSoundEvent.get("minecraft:music.overworld.old_growth_taiga"); - SoundEvent MUSIC_OVERWORLD_MEADOW = SoundEventImpl.get("minecraft:music.overworld.meadow"); + SoundEvent MUSIC_OVERWORLD_MEADOW = BuiltinSoundEvent.get("minecraft:music.overworld.meadow"); - SoundEvent MUSIC_OVERWORLD_CHERRY_GROVE = SoundEventImpl.get("minecraft:music.overworld.cherry_grove"); + SoundEvent MUSIC_OVERWORLD_CHERRY_GROVE = BuiltinSoundEvent.get("minecraft:music.overworld.cherry_grove"); - SoundEvent MUSIC_NETHER_NETHER_WASTES = SoundEventImpl.get("minecraft:music.nether.nether_wastes"); + SoundEvent MUSIC_NETHER_NETHER_WASTES = BuiltinSoundEvent.get("minecraft:music.nether.nether_wastes"); - SoundEvent MUSIC_OVERWORLD_FROZEN_PEAKS = SoundEventImpl.get("minecraft:music.overworld.frozen_peaks"); + SoundEvent MUSIC_OVERWORLD_FROZEN_PEAKS = BuiltinSoundEvent.get("minecraft:music.overworld.frozen_peaks"); - SoundEvent MUSIC_OVERWORLD_SNOWY_SLOPES = SoundEventImpl.get("minecraft:music.overworld.snowy_slopes"); + SoundEvent MUSIC_OVERWORLD_SNOWY_SLOPES = BuiltinSoundEvent.get("minecraft:music.overworld.snowy_slopes"); - SoundEvent MUSIC_NETHER_SOUL_SAND_VALLEY = SoundEventImpl.get("minecraft:music.nether.soul_sand_valley"); + SoundEvent MUSIC_NETHER_SOUL_SAND_VALLEY = BuiltinSoundEvent.get("minecraft:music.nether.soul_sand_valley"); - SoundEvent MUSIC_OVERWORLD_STONY_PEAKS = SoundEventImpl.get("minecraft:music.overworld.stony_peaks"); + SoundEvent MUSIC_OVERWORLD_STONY_PEAKS = BuiltinSoundEvent.get("minecraft:music.overworld.stony_peaks"); - SoundEvent MUSIC_NETHER_WARPED_FOREST = SoundEventImpl.get("minecraft:music.nether.warped_forest"); + SoundEvent MUSIC_NETHER_WARPED_FOREST = BuiltinSoundEvent.get("minecraft:music.nether.warped_forest"); - SoundEvent MUSIC_OVERWORLD_FLOWER_FOREST = SoundEventImpl.get("minecraft:music.overworld.flower_forest"); + SoundEvent MUSIC_OVERWORLD_FLOWER_FOREST = BuiltinSoundEvent.get("minecraft:music.overworld.flower_forest"); - SoundEvent MUSIC_OVERWORLD_DESERT = SoundEventImpl.get("minecraft:music.overworld.desert"); + SoundEvent MUSIC_OVERWORLD_DESERT = BuiltinSoundEvent.get("minecraft:music.overworld.desert"); - SoundEvent MUSIC_OVERWORLD_BADLANDS = SoundEventImpl.get("minecraft:music.overworld.badlands"); + SoundEvent MUSIC_OVERWORLD_BADLANDS = BuiltinSoundEvent.get("minecraft:music.overworld.badlands"); - SoundEvent MUSIC_OVERWORLD_JUNGLE = SoundEventImpl.get("minecraft:music.overworld.jungle"); + SoundEvent MUSIC_OVERWORLD_JUNGLE = BuiltinSoundEvent.get("minecraft:music.overworld.jungle"); - SoundEvent MUSIC_OVERWORLD_SPARSE_JUNGLE = SoundEventImpl.get("minecraft:music.overworld.sparse_jungle"); + SoundEvent MUSIC_OVERWORLD_SPARSE_JUNGLE = BuiltinSoundEvent.get("minecraft:music.overworld.sparse_jungle"); - SoundEvent MUSIC_OVERWORLD_BAMBOO_JUNGLE = SoundEventImpl.get("minecraft:music.overworld.bamboo_jungle"); + SoundEvent MUSIC_OVERWORLD_BAMBOO_JUNGLE = BuiltinSoundEvent.get("minecraft:music.overworld.bamboo_jungle"); - SoundEvent MUSIC_UNDER_WATER = SoundEventImpl.get("minecraft:music.under_water"); + SoundEvent MUSIC_UNDER_WATER = BuiltinSoundEvent.get("minecraft:music.under_water"); - SoundEvent BLOCK_NETHER_BRICKS_BREAK = SoundEventImpl.get("minecraft:block.nether_bricks.break"); + SoundEvent BLOCK_NETHER_BRICKS_BREAK = BuiltinSoundEvent.get("minecraft:block.nether_bricks.break"); - SoundEvent BLOCK_NETHER_BRICKS_STEP = SoundEventImpl.get("minecraft:block.nether_bricks.step"); + SoundEvent BLOCK_NETHER_BRICKS_STEP = BuiltinSoundEvent.get("minecraft:block.nether_bricks.step"); - SoundEvent BLOCK_NETHER_BRICKS_PLACE = SoundEventImpl.get("minecraft:block.nether_bricks.place"); + SoundEvent BLOCK_NETHER_BRICKS_PLACE = BuiltinSoundEvent.get("minecraft:block.nether_bricks.place"); - SoundEvent BLOCK_NETHER_BRICKS_HIT = SoundEventImpl.get("minecraft:block.nether_bricks.hit"); + SoundEvent BLOCK_NETHER_BRICKS_HIT = BuiltinSoundEvent.get("minecraft:block.nether_bricks.hit"); - SoundEvent BLOCK_NETHER_BRICKS_FALL = SoundEventImpl.get("minecraft:block.nether_bricks.fall"); + SoundEvent BLOCK_NETHER_BRICKS_FALL = BuiltinSoundEvent.get("minecraft:block.nether_bricks.fall"); - SoundEvent BLOCK_NETHER_WART_BREAK = SoundEventImpl.get("minecraft:block.nether_wart.break"); + SoundEvent BLOCK_NETHER_WART_BREAK = BuiltinSoundEvent.get("minecraft:block.nether_wart.break"); - SoundEvent ITEM_NETHER_WART_PLANT = SoundEventImpl.get("minecraft:item.nether_wart.plant"); + SoundEvent ITEM_NETHER_WART_PLANT = BuiltinSoundEvent.get("minecraft:item.nether_wart.plant"); - SoundEvent BLOCK_NETHER_WOOD_BREAK = SoundEventImpl.get("minecraft:block.nether_wood.break"); + SoundEvent BLOCK_NETHER_WOOD_BREAK = BuiltinSoundEvent.get("minecraft:block.nether_wood.break"); - SoundEvent BLOCK_NETHER_WOOD_FALL = SoundEventImpl.get("minecraft:block.nether_wood.fall"); + SoundEvent BLOCK_NETHER_WOOD_FALL = BuiltinSoundEvent.get("minecraft:block.nether_wood.fall"); - SoundEvent BLOCK_NETHER_WOOD_HIT = SoundEventImpl.get("minecraft:block.nether_wood.hit"); + SoundEvent BLOCK_NETHER_WOOD_HIT = BuiltinSoundEvent.get("minecraft:block.nether_wood.hit"); - SoundEvent BLOCK_NETHER_WOOD_PLACE = SoundEventImpl.get("minecraft:block.nether_wood.place"); + SoundEvent BLOCK_NETHER_WOOD_PLACE = BuiltinSoundEvent.get("minecraft:block.nether_wood.place"); - SoundEvent BLOCK_NETHER_WOOD_STEP = SoundEventImpl.get("minecraft:block.nether_wood.step"); + SoundEvent BLOCK_NETHER_WOOD_STEP = BuiltinSoundEvent.get("minecraft:block.nether_wood.step"); - SoundEvent BLOCK_NETHER_WOOD_DOOR_CLOSE = SoundEventImpl.get("minecraft:block.nether_wood_door.close"); + SoundEvent BLOCK_NETHER_WOOD_DOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.nether_wood_door.close"); - SoundEvent BLOCK_NETHER_WOOD_DOOR_OPEN = SoundEventImpl.get("minecraft:block.nether_wood_door.open"); + SoundEvent BLOCK_NETHER_WOOD_DOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.nether_wood_door.open"); - SoundEvent BLOCK_NETHER_WOOD_TRAPDOOR_CLOSE = SoundEventImpl.get("minecraft:block.nether_wood_trapdoor.close"); + SoundEvent BLOCK_NETHER_WOOD_TRAPDOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.nether_wood_trapdoor.close"); - SoundEvent BLOCK_NETHER_WOOD_TRAPDOOR_OPEN = SoundEventImpl.get("minecraft:block.nether_wood_trapdoor.open"); + SoundEvent BLOCK_NETHER_WOOD_TRAPDOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.nether_wood_trapdoor.open"); - SoundEvent BLOCK_NETHER_WOOD_BUTTON_CLICK_OFF = SoundEventImpl.get("minecraft:block.nether_wood_button.click_off"); + SoundEvent BLOCK_NETHER_WOOD_BUTTON_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.nether_wood_button.click_off"); - SoundEvent BLOCK_NETHER_WOOD_BUTTON_CLICK_ON = SoundEventImpl.get("minecraft:block.nether_wood_button.click_on"); + SoundEvent BLOCK_NETHER_WOOD_BUTTON_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.nether_wood_button.click_on"); - SoundEvent BLOCK_NETHER_WOOD_PRESSURE_PLATE_CLICK_OFF = SoundEventImpl.get("minecraft:block.nether_wood_pressure_plate.click_off"); + SoundEvent BLOCK_NETHER_WOOD_PRESSURE_PLATE_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.nether_wood_pressure_plate.click_off"); - SoundEvent BLOCK_NETHER_WOOD_PRESSURE_PLATE_CLICK_ON = SoundEventImpl.get("minecraft:block.nether_wood_pressure_plate.click_on"); + SoundEvent BLOCK_NETHER_WOOD_PRESSURE_PLATE_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.nether_wood_pressure_plate.click_on"); - SoundEvent BLOCK_NETHER_WOOD_FENCE_GATE_CLOSE = SoundEventImpl.get("minecraft:block.nether_wood_fence_gate.close"); + SoundEvent BLOCK_NETHER_WOOD_FENCE_GATE_CLOSE = BuiltinSoundEvent.get("minecraft:block.nether_wood_fence_gate.close"); - SoundEvent BLOCK_NETHER_WOOD_FENCE_GATE_OPEN = SoundEventImpl.get("minecraft:block.nether_wood_fence_gate.open"); + SoundEvent BLOCK_NETHER_WOOD_FENCE_GATE_OPEN = BuiltinSoundEvent.get("minecraft:block.nether_wood_fence_gate.open"); - SoundEvent INTENTIONALLY_EMPTY = SoundEventImpl.get("minecraft:intentionally_empty"); + SoundEvent INTENTIONALLY_EMPTY = BuiltinSoundEvent.get("minecraft:intentionally_empty"); - SoundEvent BLOCK_PACKED_MUD_BREAK = SoundEventImpl.get("minecraft:block.packed_mud.break"); + SoundEvent BLOCK_PACKED_MUD_BREAK = BuiltinSoundEvent.get("minecraft:block.packed_mud.break"); - SoundEvent BLOCK_PACKED_MUD_FALL = SoundEventImpl.get("minecraft:block.packed_mud.fall"); + SoundEvent BLOCK_PACKED_MUD_FALL = BuiltinSoundEvent.get("minecraft:block.packed_mud.fall"); - SoundEvent BLOCK_PACKED_MUD_HIT = SoundEventImpl.get("minecraft:block.packed_mud.hit"); + SoundEvent BLOCK_PACKED_MUD_HIT = BuiltinSoundEvent.get("minecraft:block.packed_mud.hit"); - SoundEvent BLOCK_PACKED_MUD_PLACE = SoundEventImpl.get("minecraft:block.packed_mud.place"); + SoundEvent BLOCK_PACKED_MUD_PLACE = BuiltinSoundEvent.get("minecraft:block.packed_mud.place"); - SoundEvent BLOCK_PACKED_MUD_STEP = SoundEventImpl.get("minecraft:block.packed_mud.step"); + SoundEvent BLOCK_PACKED_MUD_STEP = BuiltinSoundEvent.get("minecraft:block.packed_mud.step"); - SoundEvent BLOCK_STEM_BREAK = SoundEventImpl.get("minecraft:block.stem.break"); + SoundEvent BLOCK_STEM_BREAK = BuiltinSoundEvent.get("minecraft:block.stem.break"); - SoundEvent BLOCK_STEM_STEP = SoundEventImpl.get("minecraft:block.stem.step"); + SoundEvent BLOCK_STEM_STEP = BuiltinSoundEvent.get("minecraft:block.stem.step"); - SoundEvent BLOCK_STEM_PLACE = SoundEventImpl.get("minecraft:block.stem.place"); + SoundEvent BLOCK_STEM_PLACE = BuiltinSoundEvent.get("minecraft:block.stem.place"); - SoundEvent BLOCK_STEM_HIT = SoundEventImpl.get("minecraft:block.stem.hit"); + SoundEvent BLOCK_STEM_HIT = BuiltinSoundEvent.get("minecraft:block.stem.hit"); - SoundEvent BLOCK_STEM_FALL = SoundEventImpl.get("minecraft:block.stem.fall"); + SoundEvent BLOCK_STEM_FALL = BuiltinSoundEvent.get("minecraft:block.stem.fall"); - SoundEvent BLOCK_NYLIUM_BREAK = SoundEventImpl.get("minecraft:block.nylium.break"); + SoundEvent BLOCK_NYLIUM_BREAK = BuiltinSoundEvent.get("minecraft:block.nylium.break"); - SoundEvent BLOCK_NYLIUM_STEP = SoundEventImpl.get("minecraft:block.nylium.step"); + SoundEvent BLOCK_NYLIUM_STEP = BuiltinSoundEvent.get("minecraft:block.nylium.step"); - SoundEvent BLOCK_NYLIUM_PLACE = SoundEventImpl.get("minecraft:block.nylium.place"); + SoundEvent BLOCK_NYLIUM_PLACE = BuiltinSoundEvent.get("minecraft:block.nylium.place"); - SoundEvent BLOCK_NYLIUM_HIT = SoundEventImpl.get("minecraft:block.nylium.hit"); + SoundEvent BLOCK_NYLIUM_HIT = BuiltinSoundEvent.get("minecraft:block.nylium.hit"); - SoundEvent BLOCK_NYLIUM_FALL = SoundEventImpl.get("minecraft:block.nylium.fall"); + SoundEvent BLOCK_NYLIUM_FALL = BuiltinSoundEvent.get("minecraft:block.nylium.fall"); - SoundEvent BLOCK_NETHER_SPROUTS_BREAK = SoundEventImpl.get("minecraft:block.nether_sprouts.break"); + SoundEvent BLOCK_NETHER_SPROUTS_BREAK = BuiltinSoundEvent.get("minecraft:block.nether_sprouts.break"); - SoundEvent BLOCK_NETHER_SPROUTS_STEP = SoundEventImpl.get("minecraft:block.nether_sprouts.step"); + SoundEvent BLOCK_NETHER_SPROUTS_STEP = BuiltinSoundEvent.get("minecraft:block.nether_sprouts.step"); - SoundEvent BLOCK_NETHER_SPROUTS_PLACE = SoundEventImpl.get("minecraft:block.nether_sprouts.place"); + SoundEvent BLOCK_NETHER_SPROUTS_PLACE = BuiltinSoundEvent.get("minecraft:block.nether_sprouts.place"); - SoundEvent BLOCK_NETHER_SPROUTS_HIT = SoundEventImpl.get("minecraft:block.nether_sprouts.hit"); + SoundEvent BLOCK_NETHER_SPROUTS_HIT = BuiltinSoundEvent.get("minecraft:block.nether_sprouts.hit"); - SoundEvent BLOCK_NETHER_SPROUTS_FALL = SoundEventImpl.get("minecraft:block.nether_sprouts.fall"); + SoundEvent BLOCK_NETHER_SPROUTS_FALL = BuiltinSoundEvent.get("minecraft:block.nether_sprouts.fall"); - SoundEvent BLOCK_FUNGUS_BREAK = SoundEventImpl.get("minecraft:block.fungus.break"); + SoundEvent BLOCK_FUNGUS_BREAK = BuiltinSoundEvent.get("minecraft:block.fungus.break"); - SoundEvent BLOCK_FUNGUS_STEP = SoundEventImpl.get("minecraft:block.fungus.step"); + SoundEvent BLOCK_FUNGUS_STEP = BuiltinSoundEvent.get("minecraft:block.fungus.step"); - SoundEvent BLOCK_FUNGUS_PLACE = SoundEventImpl.get("minecraft:block.fungus.place"); + SoundEvent BLOCK_FUNGUS_PLACE = BuiltinSoundEvent.get("minecraft:block.fungus.place"); - SoundEvent BLOCK_FUNGUS_HIT = SoundEventImpl.get("minecraft:block.fungus.hit"); + SoundEvent BLOCK_FUNGUS_HIT = BuiltinSoundEvent.get("minecraft:block.fungus.hit"); - SoundEvent BLOCK_FUNGUS_FALL = SoundEventImpl.get("minecraft:block.fungus.fall"); + SoundEvent BLOCK_FUNGUS_FALL = BuiltinSoundEvent.get("minecraft:block.fungus.fall"); - SoundEvent BLOCK_WEEPING_VINES_BREAK = SoundEventImpl.get("minecraft:block.weeping_vines.break"); + SoundEvent BLOCK_WEEPING_VINES_BREAK = BuiltinSoundEvent.get("minecraft:block.weeping_vines.break"); - SoundEvent BLOCK_WEEPING_VINES_STEP = SoundEventImpl.get("minecraft:block.weeping_vines.step"); + SoundEvent BLOCK_WEEPING_VINES_STEP = BuiltinSoundEvent.get("minecraft:block.weeping_vines.step"); - SoundEvent BLOCK_WEEPING_VINES_PLACE = SoundEventImpl.get("minecraft:block.weeping_vines.place"); + SoundEvent BLOCK_WEEPING_VINES_PLACE = BuiltinSoundEvent.get("minecraft:block.weeping_vines.place"); - SoundEvent BLOCK_WEEPING_VINES_HIT = SoundEventImpl.get("minecraft:block.weeping_vines.hit"); + SoundEvent BLOCK_WEEPING_VINES_HIT = BuiltinSoundEvent.get("minecraft:block.weeping_vines.hit"); - SoundEvent BLOCK_WEEPING_VINES_FALL = SoundEventImpl.get("minecraft:block.weeping_vines.fall"); + SoundEvent BLOCK_WEEPING_VINES_FALL = BuiltinSoundEvent.get("minecraft:block.weeping_vines.fall"); - SoundEvent BLOCK_WART_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.wart_block.break"); + SoundEvent BLOCK_WART_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.wart_block.break"); - SoundEvent BLOCK_WART_BLOCK_STEP = SoundEventImpl.get("minecraft:block.wart_block.step"); + SoundEvent BLOCK_WART_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.wart_block.step"); - SoundEvent BLOCK_WART_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.wart_block.place"); + SoundEvent BLOCK_WART_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.wart_block.place"); - SoundEvent BLOCK_WART_BLOCK_HIT = SoundEventImpl.get("minecraft:block.wart_block.hit"); + SoundEvent BLOCK_WART_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.wart_block.hit"); - SoundEvent BLOCK_WART_BLOCK_FALL = SoundEventImpl.get("minecraft:block.wart_block.fall"); + SoundEvent BLOCK_WART_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.wart_block.fall"); - SoundEvent BLOCK_NETHERITE_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.netherite_block.break"); + SoundEvent BLOCK_NETHERITE_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.netherite_block.break"); - SoundEvent BLOCK_NETHERITE_BLOCK_STEP = SoundEventImpl.get("minecraft:block.netherite_block.step"); + SoundEvent BLOCK_NETHERITE_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.netherite_block.step"); - SoundEvent BLOCK_NETHERITE_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.netherite_block.place"); + SoundEvent BLOCK_NETHERITE_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.netherite_block.place"); - SoundEvent BLOCK_NETHERITE_BLOCK_HIT = SoundEventImpl.get("minecraft:block.netherite_block.hit"); + SoundEvent BLOCK_NETHERITE_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.netherite_block.hit"); - SoundEvent BLOCK_NETHERITE_BLOCK_FALL = SoundEventImpl.get("minecraft:block.netherite_block.fall"); + SoundEvent BLOCK_NETHERITE_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.netherite_block.fall"); - SoundEvent BLOCK_NETHERRACK_BREAK = SoundEventImpl.get("minecraft:block.netherrack.break"); + SoundEvent BLOCK_NETHERRACK_BREAK = BuiltinSoundEvent.get("minecraft:block.netherrack.break"); - SoundEvent BLOCK_NETHERRACK_STEP = SoundEventImpl.get("minecraft:block.netherrack.step"); + SoundEvent BLOCK_NETHERRACK_STEP = BuiltinSoundEvent.get("minecraft:block.netherrack.step"); - SoundEvent BLOCK_NETHERRACK_PLACE = SoundEventImpl.get("minecraft:block.netherrack.place"); + SoundEvent BLOCK_NETHERRACK_PLACE = BuiltinSoundEvent.get("minecraft:block.netherrack.place"); - SoundEvent BLOCK_NETHERRACK_HIT = SoundEventImpl.get("minecraft:block.netherrack.hit"); + SoundEvent BLOCK_NETHERRACK_HIT = BuiltinSoundEvent.get("minecraft:block.netherrack.hit"); - SoundEvent BLOCK_NETHERRACK_FALL = SoundEventImpl.get("minecraft:block.netherrack.fall"); + SoundEvent BLOCK_NETHERRACK_FALL = BuiltinSoundEvent.get("minecraft:block.netherrack.fall"); - SoundEvent BLOCK_NOTE_BLOCK_BASEDRUM = SoundEventImpl.get("minecraft:block.note_block.basedrum"); + SoundEvent BLOCK_NOTE_BLOCK_BASEDRUM = BuiltinSoundEvent.get("minecraft:block.note_block.basedrum"); - SoundEvent BLOCK_NOTE_BLOCK_BASS = SoundEventImpl.get("minecraft:block.note_block.bass"); + SoundEvent BLOCK_NOTE_BLOCK_BASS = BuiltinSoundEvent.get("minecraft:block.note_block.bass"); - SoundEvent BLOCK_NOTE_BLOCK_BELL = SoundEventImpl.get("minecraft:block.note_block.bell"); + SoundEvent BLOCK_NOTE_BLOCK_BELL = BuiltinSoundEvent.get("minecraft:block.note_block.bell"); - SoundEvent BLOCK_NOTE_BLOCK_CHIME = SoundEventImpl.get("minecraft:block.note_block.chime"); + SoundEvent BLOCK_NOTE_BLOCK_CHIME = BuiltinSoundEvent.get("minecraft:block.note_block.chime"); - SoundEvent BLOCK_NOTE_BLOCK_FLUTE = SoundEventImpl.get("minecraft:block.note_block.flute"); + SoundEvent BLOCK_NOTE_BLOCK_FLUTE = BuiltinSoundEvent.get("minecraft:block.note_block.flute"); - SoundEvent BLOCK_NOTE_BLOCK_GUITAR = SoundEventImpl.get("minecraft:block.note_block.guitar"); + SoundEvent BLOCK_NOTE_BLOCK_GUITAR = BuiltinSoundEvent.get("minecraft:block.note_block.guitar"); - SoundEvent BLOCK_NOTE_BLOCK_HARP = SoundEventImpl.get("minecraft:block.note_block.harp"); + SoundEvent BLOCK_NOTE_BLOCK_HARP = BuiltinSoundEvent.get("minecraft:block.note_block.harp"); - SoundEvent BLOCK_NOTE_BLOCK_HAT = SoundEventImpl.get("minecraft:block.note_block.hat"); + SoundEvent BLOCK_NOTE_BLOCK_HAT = BuiltinSoundEvent.get("minecraft:block.note_block.hat"); - SoundEvent BLOCK_NOTE_BLOCK_PLING = SoundEventImpl.get("minecraft:block.note_block.pling"); + SoundEvent BLOCK_NOTE_BLOCK_PLING = BuiltinSoundEvent.get("minecraft:block.note_block.pling"); - SoundEvent BLOCK_NOTE_BLOCK_SNARE = SoundEventImpl.get("minecraft:block.note_block.snare"); + SoundEvent BLOCK_NOTE_BLOCK_SNARE = BuiltinSoundEvent.get("minecraft:block.note_block.snare"); - SoundEvent BLOCK_NOTE_BLOCK_XYLOPHONE = SoundEventImpl.get("minecraft:block.note_block.xylophone"); + SoundEvent BLOCK_NOTE_BLOCK_XYLOPHONE = BuiltinSoundEvent.get("minecraft:block.note_block.xylophone"); - SoundEvent BLOCK_NOTE_BLOCK_IRON_XYLOPHONE = SoundEventImpl.get("minecraft:block.note_block.iron_xylophone"); + SoundEvent BLOCK_NOTE_BLOCK_IRON_XYLOPHONE = BuiltinSoundEvent.get("minecraft:block.note_block.iron_xylophone"); - SoundEvent BLOCK_NOTE_BLOCK_COW_BELL = SoundEventImpl.get("minecraft:block.note_block.cow_bell"); + SoundEvent BLOCK_NOTE_BLOCK_COW_BELL = BuiltinSoundEvent.get("minecraft:block.note_block.cow_bell"); - SoundEvent BLOCK_NOTE_BLOCK_DIDGERIDOO = SoundEventImpl.get("minecraft:block.note_block.didgeridoo"); + SoundEvent BLOCK_NOTE_BLOCK_DIDGERIDOO = BuiltinSoundEvent.get("minecraft:block.note_block.didgeridoo"); - SoundEvent BLOCK_NOTE_BLOCK_BIT = SoundEventImpl.get("minecraft:block.note_block.bit"); + SoundEvent BLOCK_NOTE_BLOCK_BIT = BuiltinSoundEvent.get("minecraft:block.note_block.bit"); - SoundEvent BLOCK_NOTE_BLOCK_BANJO = SoundEventImpl.get("minecraft:block.note_block.banjo"); + SoundEvent BLOCK_NOTE_BLOCK_BANJO = BuiltinSoundEvent.get("minecraft:block.note_block.banjo"); - SoundEvent BLOCK_NOTE_BLOCK_IMITATE_ZOMBIE = SoundEventImpl.get("minecraft:block.note_block.imitate.zombie"); + SoundEvent BLOCK_NOTE_BLOCK_IMITATE_ZOMBIE = BuiltinSoundEvent.get("minecraft:block.note_block.imitate.zombie"); - SoundEvent BLOCK_NOTE_BLOCK_IMITATE_SKELETON = SoundEventImpl.get("minecraft:block.note_block.imitate.skeleton"); + SoundEvent BLOCK_NOTE_BLOCK_IMITATE_SKELETON = BuiltinSoundEvent.get("minecraft:block.note_block.imitate.skeleton"); - SoundEvent BLOCK_NOTE_BLOCK_IMITATE_CREEPER = SoundEventImpl.get("minecraft:block.note_block.imitate.creeper"); + SoundEvent BLOCK_NOTE_BLOCK_IMITATE_CREEPER = BuiltinSoundEvent.get("minecraft:block.note_block.imitate.creeper"); - SoundEvent BLOCK_NOTE_BLOCK_IMITATE_ENDER_DRAGON = SoundEventImpl.get("minecraft:block.note_block.imitate.ender_dragon"); + SoundEvent BLOCK_NOTE_BLOCK_IMITATE_ENDER_DRAGON = BuiltinSoundEvent.get("minecraft:block.note_block.imitate.ender_dragon"); - SoundEvent BLOCK_NOTE_BLOCK_IMITATE_WITHER_SKELETON = SoundEventImpl.get("minecraft:block.note_block.imitate.wither_skeleton"); + SoundEvent BLOCK_NOTE_BLOCK_IMITATE_WITHER_SKELETON = BuiltinSoundEvent.get("minecraft:block.note_block.imitate.wither_skeleton"); - SoundEvent BLOCK_NOTE_BLOCK_IMITATE_PIGLIN = SoundEventImpl.get("minecraft:block.note_block.imitate.piglin"); + SoundEvent BLOCK_NOTE_BLOCK_IMITATE_PIGLIN = BuiltinSoundEvent.get("minecraft:block.note_block.imitate.piglin"); - SoundEvent ENTITY_OCELOT_HURT = SoundEventImpl.get("minecraft:entity.ocelot.hurt"); + SoundEvent ENTITY_OCELOT_HURT = BuiltinSoundEvent.get("minecraft:entity.ocelot.hurt"); - SoundEvent ENTITY_OCELOT_AMBIENT = SoundEventImpl.get("minecraft:entity.ocelot.ambient"); + SoundEvent ENTITY_OCELOT_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.ocelot.ambient"); - SoundEvent ENTITY_OCELOT_DEATH = SoundEventImpl.get("minecraft:entity.ocelot.death"); + SoundEvent ENTITY_OCELOT_DEATH = BuiltinSoundEvent.get("minecraft:entity.ocelot.death"); - SoundEvent ITEM_OMINOUS_BOTTLE_DISPOSE = SoundEventImpl.get("minecraft:item.ominous_bottle.dispose"); + SoundEvent ITEM_OMINOUS_BOTTLE_DISPOSE = BuiltinSoundEvent.get("minecraft:item.ominous_bottle.dispose"); - SoundEvent ENTITY_PAINTING_BREAK = SoundEventImpl.get("minecraft:entity.painting.break"); + SoundEvent ENTITY_PAINTING_BREAK = BuiltinSoundEvent.get("minecraft:entity.painting.break"); - SoundEvent ENTITY_PAINTING_PLACE = SoundEventImpl.get("minecraft:entity.painting.place"); + SoundEvent ENTITY_PAINTING_PLACE = BuiltinSoundEvent.get("minecraft:entity.painting.place"); - SoundEvent ENTITY_PANDA_PRE_SNEEZE = SoundEventImpl.get("minecraft:entity.panda.pre_sneeze"); + SoundEvent ENTITY_PANDA_PRE_SNEEZE = BuiltinSoundEvent.get("minecraft:entity.panda.pre_sneeze"); - SoundEvent ENTITY_PANDA_SNEEZE = SoundEventImpl.get("minecraft:entity.panda.sneeze"); + SoundEvent ENTITY_PANDA_SNEEZE = BuiltinSoundEvent.get("minecraft:entity.panda.sneeze"); - SoundEvent ENTITY_PANDA_AMBIENT = SoundEventImpl.get("minecraft:entity.panda.ambient"); + SoundEvent ENTITY_PANDA_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.panda.ambient"); - SoundEvent ENTITY_PANDA_DEATH = SoundEventImpl.get("minecraft:entity.panda.death"); + SoundEvent ENTITY_PANDA_DEATH = BuiltinSoundEvent.get("minecraft:entity.panda.death"); - SoundEvent ENTITY_PANDA_EAT = SoundEventImpl.get("minecraft:entity.panda.eat"); + SoundEvent ENTITY_PANDA_EAT = BuiltinSoundEvent.get("minecraft:entity.panda.eat"); - SoundEvent ENTITY_PANDA_STEP = SoundEventImpl.get("minecraft:entity.panda.step"); + SoundEvent ENTITY_PANDA_STEP = BuiltinSoundEvent.get("minecraft:entity.panda.step"); - SoundEvent ENTITY_PANDA_CANT_BREED = SoundEventImpl.get("minecraft:entity.panda.cant_breed"); + SoundEvent ENTITY_PANDA_CANT_BREED = BuiltinSoundEvent.get("minecraft:entity.panda.cant_breed"); - SoundEvent ENTITY_PANDA_AGGRESSIVE_AMBIENT = SoundEventImpl.get("minecraft:entity.panda.aggressive_ambient"); + SoundEvent ENTITY_PANDA_AGGRESSIVE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.panda.aggressive_ambient"); - SoundEvent ENTITY_PANDA_WORRIED_AMBIENT = SoundEventImpl.get("minecraft:entity.panda.worried_ambient"); + SoundEvent ENTITY_PANDA_WORRIED_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.panda.worried_ambient"); - SoundEvent ENTITY_PANDA_HURT = SoundEventImpl.get("minecraft:entity.panda.hurt"); + SoundEvent ENTITY_PANDA_HURT = BuiltinSoundEvent.get("minecraft:entity.panda.hurt"); - SoundEvent ENTITY_PANDA_BITE = SoundEventImpl.get("minecraft:entity.panda.bite"); + SoundEvent ENTITY_PANDA_BITE = BuiltinSoundEvent.get("minecraft:entity.panda.bite"); - SoundEvent ENTITY_PARROT_AMBIENT = SoundEventImpl.get("minecraft:entity.parrot.ambient"); + SoundEvent ENTITY_PARROT_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.parrot.ambient"); - SoundEvent ENTITY_PARROT_DEATH = SoundEventImpl.get("minecraft:entity.parrot.death"); + SoundEvent ENTITY_PARROT_DEATH = BuiltinSoundEvent.get("minecraft:entity.parrot.death"); - SoundEvent ENTITY_PARROT_EAT = SoundEventImpl.get("minecraft:entity.parrot.eat"); + SoundEvent ENTITY_PARROT_EAT = BuiltinSoundEvent.get("minecraft:entity.parrot.eat"); - SoundEvent ENTITY_PARROT_FLY = SoundEventImpl.get("minecraft:entity.parrot.fly"); + SoundEvent ENTITY_PARROT_FLY = BuiltinSoundEvent.get("minecraft:entity.parrot.fly"); - SoundEvent ENTITY_PARROT_HURT = SoundEventImpl.get("minecraft:entity.parrot.hurt"); + SoundEvent ENTITY_PARROT_HURT = BuiltinSoundEvent.get("minecraft:entity.parrot.hurt"); - SoundEvent ENTITY_PARROT_IMITATE_BLAZE = SoundEventImpl.get("minecraft:entity.parrot.imitate.blaze"); + SoundEvent ENTITY_PARROT_IMITATE_BLAZE = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.blaze"); - SoundEvent ENTITY_PARROT_IMITATE_BOGGED = SoundEventImpl.get("minecraft:entity.parrot.imitate.bogged"); + SoundEvent ENTITY_PARROT_IMITATE_BOGGED = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.bogged"); - SoundEvent ENTITY_PARROT_IMITATE_BREEZE = SoundEventImpl.get("minecraft:entity.parrot.imitate.breeze"); + SoundEvent ENTITY_PARROT_IMITATE_BREEZE = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.breeze"); - SoundEvent ENTITY_PARROT_IMITATE_CREEPER = SoundEventImpl.get("minecraft:entity.parrot.imitate.creeper"); + SoundEvent ENTITY_PARROT_IMITATE_CREEPER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.creeper"); - SoundEvent ENTITY_PARROT_IMITATE_DROWNED = SoundEventImpl.get("minecraft:entity.parrot.imitate.drowned"); + SoundEvent ENTITY_PARROT_IMITATE_DROWNED = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.drowned"); - SoundEvent ENTITY_PARROT_IMITATE_ELDER_GUARDIAN = SoundEventImpl.get("minecraft:entity.parrot.imitate.elder_guardian"); + SoundEvent ENTITY_PARROT_IMITATE_ELDER_GUARDIAN = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.elder_guardian"); - SoundEvent ENTITY_PARROT_IMITATE_ENDER_DRAGON = SoundEventImpl.get("minecraft:entity.parrot.imitate.ender_dragon"); + SoundEvent ENTITY_PARROT_IMITATE_ENDER_DRAGON = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.ender_dragon"); - SoundEvent ENTITY_PARROT_IMITATE_ENDERMITE = SoundEventImpl.get("minecraft:entity.parrot.imitate.endermite"); + SoundEvent ENTITY_PARROT_IMITATE_ENDERMITE = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.endermite"); - SoundEvent ENTITY_PARROT_IMITATE_EVOKER = SoundEventImpl.get("minecraft:entity.parrot.imitate.evoker"); + SoundEvent ENTITY_PARROT_IMITATE_EVOKER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.evoker"); - SoundEvent ENTITY_PARROT_IMITATE_GHAST = SoundEventImpl.get("minecraft:entity.parrot.imitate.ghast"); + SoundEvent ENTITY_PARROT_IMITATE_GHAST = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.ghast"); - SoundEvent ENTITY_PARROT_IMITATE_GUARDIAN = SoundEventImpl.get("minecraft:entity.parrot.imitate.guardian"); + SoundEvent ENTITY_PARROT_IMITATE_GUARDIAN = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.guardian"); - SoundEvent ENTITY_PARROT_IMITATE_HOGLIN = SoundEventImpl.get("minecraft:entity.parrot.imitate.hoglin"); + SoundEvent ENTITY_PARROT_IMITATE_HOGLIN = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.hoglin"); - SoundEvent ENTITY_PARROT_IMITATE_HUSK = SoundEventImpl.get("minecraft:entity.parrot.imitate.husk"); + SoundEvent ENTITY_PARROT_IMITATE_HUSK = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.husk"); - SoundEvent ENTITY_PARROT_IMITATE_ILLUSIONER = SoundEventImpl.get("minecraft:entity.parrot.imitate.illusioner"); + SoundEvent ENTITY_PARROT_IMITATE_ILLUSIONER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.illusioner"); - SoundEvent ENTITY_PARROT_IMITATE_MAGMA_CUBE = SoundEventImpl.get("minecraft:entity.parrot.imitate.magma_cube"); + SoundEvent ENTITY_PARROT_IMITATE_MAGMA_CUBE = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.magma_cube"); - SoundEvent ENTITY_PARROT_IMITATE_PHANTOM = SoundEventImpl.get("minecraft:entity.parrot.imitate.phantom"); + SoundEvent ENTITY_PARROT_IMITATE_PHANTOM = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.phantom"); - SoundEvent ENTITY_PARROT_IMITATE_PIGLIN = SoundEventImpl.get("minecraft:entity.parrot.imitate.piglin"); + SoundEvent ENTITY_PARROT_IMITATE_PIGLIN = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.piglin"); - SoundEvent ENTITY_PARROT_IMITATE_PIGLIN_BRUTE = SoundEventImpl.get("minecraft:entity.parrot.imitate.piglin_brute"); + SoundEvent ENTITY_PARROT_IMITATE_PIGLIN_BRUTE = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.piglin_brute"); - SoundEvent ENTITY_PARROT_IMITATE_PILLAGER = SoundEventImpl.get("minecraft:entity.parrot.imitate.pillager"); + SoundEvent ENTITY_PARROT_IMITATE_PILLAGER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.pillager"); - SoundEvent ENTITY_PARROT_IMITATE_RAVAGER = SoundEventImpl.get("minecraft:entity.parrot.imitate.ravager"); + SoundEvent ENTITY_PARROT_IMITATE_RAVAGER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.ravager"); - SoundEvent ENTITY_PARROT_IMITATE_SHULKER = SoundEventImpl.get("minecraft:entity.parrot.imitate.shulker"); + SoundEvent ENTITY_PARROT_IMITATE_SHULKER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.shulker"); - SoundEvent ENTITY_PARROT_IMITATE_SILVERFISH = SoundEventImpl.get("minecraft:entity.parrot.imitate.silverfish"); + SoundEvent ENTITY_PARROT_IMITATE_SILVERFISH = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.silverfish"); - SoundEvent ENTITY_PARROT_IMITATE_SKELETON = SoundEventImpl.get("minecraft:entity.parrot.imitate.skeleton"); + SoundEvent ENTITY_PARROT_IMITATE_SKELETON = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.skeleton"); - SoundEvent ENTITY_PARROT_IMITATE_SLIME = SoundEventImpl.get("minecraft:entity.parrot.imitate.slime"); + SoundEvent ENTITY_PARROT_IMITATE_SLIME = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.slime"); - SoundEvent ENTITY_PARROT_IMITATE_SPIDER = SoundEventImpl.get("minecraft:entity.parrot.imitate.spider"); + SoundEvent ENTITY_PARROT_IMITATE_SPIDER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.spider"); - SoundEvent ENTITY_PARROT_IMITATE_STRAY = SoundEventImpl.get("minecraft:entity.parrot.imitate.stray"); + SoundEvent ENTITY_PARROT_IMITATE_STRAY = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.stray"); - SoundEvent ENTITY_PARROT_IMITATE_VEX = SoundEventImpl.get("minecraft:entity.parrot.imitate.vex"); + SoundEvent ENTITY_PARROT_IMITATE_VEX = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.vex"); - SoundEvent ENTITY_PARROT_IMITATE_VINDICATOR = SoundEventImpl.get("minecraft:entity.parrot.imitate.vindicator"); + SoundEvent ENTITY_PARROT_IMITATE_VINDICATOR = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.vindicator"); - SoundEvent ENTITY_PARROT_IMITATE_WARDEN = SoundEventImpl.get("minecraft:entity.parrot.imitate.warden"); + SoundEvent ENTITY_PARROT_IMITATE_WARDEN = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.warden"); - SoundEvent ENTITY_PARROT_IMITATE_WITCH = SoundEventImpl.get("minecraft:entity.parrot.imitate.witch"); + SoundEvent ENTITY_PARROT_IMITATE_WITCH = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.witch"); - SoundEvent ENTITY_PARROT_IMITATE_WITHER = SoundEventImpl.get("minecraft:entity.parrot.imitate.wither"); + SoundEvent ENTITY_PARROT_IMITATE_WITHER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.wither"); - SoundEvent ENTITY_PARROT_IMITATE_WITHER_SKELETON = SoundEventImpl.get("minecraft:entity.parrot.imitate.wither_skeleton"); + SoundEvent ENTITY_PARROT_IMITATE_WITHER_SKELETON = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.wither_skeleton"); - SoundEvent ENTITY_PARROT_IMITATE_ZOGLIN = SoundEventImpl.get("minecraft:entity.parrot.imitate.zoglin"); + SoundEvent ENTITY_PARROT_IMITATE_ZOGLIN = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.zoglin"); - SoundEvent ENTITY_PARROT_IMITATE_ZOMBIE = SoundEventImpl.get("minecraft:entity.parrot.imitate.zombie"); + SoundEvent ENTITY_PARROT_IMITATE_ZOMBIE = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.zombie"); - SoundEvent ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER = SoundEventImpl.get("minecraft:entity.parrot.imitate.zombie_villager"); + SoundEvent ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER = BuiltinSoundEvent.get("minecraft:entity.parrot.imitate.zombie_villager"); - SoundEvent ENTITY_PARROT_STEP = SoundEventImpl.get("minecraft:entity.parrot.step"); + SoundEvent ENTITY_PARROT_STEP = BuiltinSoundEvent.get("minecraft:entity.parrot.step"); - SoundEvent ENTITY_PHANTOM_AMBIENT = SoundEventImpl.get("minecraft:entity.phantom.ambient"); + SoundEvent ENTITY_PHANTOM_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.phantom.ambient"); - SoundEvent ENTITY_PHANTOM_BITE = SoundEventImpl.get("minecraft:entity.phantom.bite"); + SoundEvent ENTITY_PHANTOM_BITE = BuiltinSoundEvent.get("minecraft:entity.phantom.bite"); - SoundEvent ENTITY_PHANTOM_DEATH = SoundEventImpl.get("minecraft:entity.phantom.death"); + SoundEvent ENTITY_PHANTOM_DEATH = BuiltinSoundEvent.get("minecraft:entity.phantom.death"); - SoundEvent ENTITY_PHANTOM_FLAP = SoundEventImpl.get("minecraft:entity.phantom.flap"); + SoundEvent ENTITY_PHANTOM_FLAP = BuiltinSoundEvent.get("minecraft:entity.phantom.flap"); - SoundEvent ENTITY_PHANTOM_HURT = SoundEventImpl.get("minecraft:entity.phantom.hurt"); + SoundEvent ENTITY_PHANTOM_HURT = BuiltinSoundEvent.get("minecraft:entity.phantom.hurt"); - SoundEvent ENTITY_PHANTOM_SWOOP = SoundEventImpl.get("minecraft:entity.phantom.swoop"); + SoundEvent ENTITY_PHANTOM_SWOOP = BuiltinSoundEvent.get("minecraft:entity.phantom.swoop"); - SoundEvent ENTITY_PIG_AMBIENT = SoundEventImpl.get("minecraft:entity.pig.ambient"); + SoundEvent ENTITY_PIG_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.pig.ambient"); - SoundEvent ENTITY_PIG_DEATH = SoundEventImpl.get("minecraft:entity.pig.death"); + SoundEvent ENTITY_PIG_DEATH = BuiltinSoundEvent.get("minecraft:entity.pig.death"); - SoundEvent ENTITY_PIG_HURT = SoundEventImpl.get("minecraft:entity.pig.hurt"); + SoundEvent ENTITY_PIG_HURT = BuiltinSoundEvent.get("minecraft:entity.pig.hurt"); - SoundEvent ENTITY_PIG_SADDLE = SoundEventImpl.get("minecraft:entity.pig.saddle"); + SoundEvent ENTITY_PIG_SADDLE = BuiltinSoundEvent.get("minecraft:entity.pig.saddle"); - SoundEvent ENTITY_PIG_STEP = SoundEventImpl.get("minecraft:entity.pig.step"); + SoundEvent ENTITY_PIG_STEP = BuiltinSoundEvent.get("minecraft:entity.pig.step"); - SoundEvent ENTITY_PIGLIN_ADMIRING_ITEM = SoundEventImpl.get("minecraft:entity.piglin.admiring_item"); + SoundEvent ENTITY_PIGLIN_ADMIRING_ITEM = BuiltinSoundEvent.get("minecraft:entity.piglin.admiring_item"); - SoundEvent ENTITY_PIGLIN_AMBIENT = SoundEventImpl.get("minecraft:entity.piglin.ambient"); + SoundEvent ENTITY_PIGLIN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.piglin.ambient"); - SoundEvent ENTITY_PIGLIN_ANGRY = SoundEventImpl.get("minecraft:entity.piglin.angry"); + SoundEvent ENTITY_PIGLIN_ANGRY = BuiltinSoundEvent.get("minecraft:entity.piglin.angry"); - SoundEvent ENTITY_PIGLIN_CELEBRATE = SoundEventImpl.get("minecraft:entity.piglin.celebrate"); + SoundEvent ENTITY_PIGLIN_CELEBRATE = BuiltinSoundEvent.get("minecraft:entity.piglin.celebrate"); - SoundEvent ENTITY_PIGLIN_DEATH = SoundEventImpl.get("minecraft:entity.piglin.death"); + SoundEvent ENTITY_PIGLIN_DEATH = BuiltinSoundEvent.get("minecraft:entity.piglin.death"); - SoundEvent ENTITY_PIGLIN_JEALOUS = SoundEventImpl.get("minecraft:entity.piglin.jealous"); + SoundEvent ENTITY_PIGLIN_JEALOUS = BuiltinSoundEvent.get("minecraft:entity.piglin.jealous"); - SoundEvent ENTITY_PIGLIN_HURT = SoundEventImpl.get("minecraft:entity.piglin.hurt"); + SoundEvent ENTITY_PIGLIN_HURT = BuiltinSoundEvent.get("minecraft:entity.piglin.hurt"); - SoundEvent ENTITY_PIGLIN_RETREAT = SoundEventImpl.get("minecraft:entity.piglin.retreat"); + SoundEvent ENTITY_PIGLIN_RETREAT = BuiltinSoundEvent.get("minecraft:entity.piglin.retreat"); - SoundEvent ENTITY_PIGLIN_STEP = SoundEventImpl.get("minecraft:entity.piglin.step"); + SoundEvent ENTITY_PIGLIN_STEP = BuiltinSoundEvent.get("minecraft:entity.piglin.step"); - SoundEvent ENTITY_PIGLIN_CONVERTED_TO_ZOMBIFIED = SoundEventImpl.get("minecraft:entity.piglin.converted_to_zombified"); + SoundEvent ENTITY_PIGLIN_CONVERTED_TO_ZOMBIFIED = BuiltinSoundEvent.get("minecraft:entity.piglin.converted_to_zombified"); - SoundEvent ENTITY_PIGLIN_BRUTE_AMBIENT = SoundEventImpl.get("minecraft:entity.piglin_brute.ambient"); + SoundEvent ENTITY_PIGLIN_BRUTE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.piglin_brute.ambient"); - SoundEvent ENTITY_PIGLIN_BRUTE_ANGRY = SoundEventImpl.get("minecraft:entity.piglin_brute.angry"); + SoundEvent ENTITY_PIGLIN_BRUTE_ANGRY = BuiltinSoundEvent.get("minecraft:entity.piglin_brute.angry"); - SoundEvent ENTITY_PIGLIN_BRUTE_DEATH = SoundEventImpl.get("minecraft:entity.piglin_brute.death"); + SoundEvent ENTITY_PIGLIN_BRUTE_DEATH = BuiltinSoundEvent.get("minecraft:entity.piglin_brute.death"); - SoundEvent ENTITY_PIGLIN_BRUTE_HURT = SoundEventImpl.get("minecraft:entity.piglin_brute.hurt"); + SoundEvent ENTITY_PIGLIN_BRUTE_HURT = BuiltinSoundEvent.get("minecraft:entity.piglin_brute.hurt"); - SoundEvent ENTITY_PIGLIN_BRUTE_STEP = SoundEventImpl.get("minecraft:entity.piglin_brute.step"); + SoundEvent ENTITY_PIGLIN_BRUTE_STEP = BuiltinSoundEvent.get("minecraft:entity.piglin_brute.step"); - SoundEvent ENTITY_PIGLIN_BRUTE_CONVERTED_TO_ZOMBIFIED = SoundEventImpl.get("minecraft:entity.piglin_brute.converted_to_zombified"); + SoundEvent ENTITY_PIGLIN_BRUTE_CONVERTED_TO_ZOMBIFIED = BuiltinSoundEvent.get("minecraft:entity.piglin_brute.converted_to_zombified"); - SoundEvent ENTITY_PILLAGER_AMBIENT = SoundEventImpl.get("minecraft:entity.pillager.ambient"); + SoundEvent ENTITY_PILLAGER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.pillager.ambient"); - SoundEvent ENTITY_PILLAGER_CELEBRATE = SoundEventImpl.get("minecraft:entity.pillager.celebrate"); + SoundEvent ENTITY_PILLAGER_CELEBRATE = BuiltinSoundEvent.get("minecraft:entity.pillager.celebrate"); - SoundEvent ENTITY_PILLAGER_DEATH = SoundEventImpl.get("minecraft:entity.pillager.death"); + SoundEvent ENTITY_PILLAGER_DEATH = BuiltinSoundEvent.get("minecraft:entity.pillager.death"); - SoundEvent ENTITY_PILLAGER_HURT = SoundEventImpl.get("minecraft:entity.pillager.hurt"); + SoundEvent ENTITY_PILLAGER_HURT = BuiltinSoundEvent.get("minecraft:entity.pillager.hurt"); - SoundEvent BLOCK_PISTON_CONTRACT = SoundEventImpl.get("minecraft:block.piston.contract"); + SoundEvent BLOCK_PISTON_CONTRACT = BuiltinSoundEvent.get("minecraft:block.piston.contract"); - SoundEvent BLOCK_PISTON_EXTEND = SoundEventImpl.get("minecraft:block.piston.extend"); + SoundEvent BLOCK_PISTON_EXTEND = BuiltinSoundEvent.get("minecraft:block.piston.extend"); - SoundEvent ENTITY_PLAYER_ATTACK_CRIT = SoundEventImpl.get("minecraft:entity.player.attack.crit"); + SoundEvent ENTITY_PLAYER_ATTACK_CRIT = BuiltinSoundEvent.get("minecraft:entity.player.attack.crit"); - SoundEvent ENTITY_PLAYER_ATTACK_KNOCKBACK = SoundEventImpl.get("minecraft:entity.player.attack.knockback"); + SoundEvent ENTITY_PLAYER_ATTACK_KNOCKBACK = BuiltinSoundEvent.get("minecraft:entity.player.attack.knockback"); - SoundEvent ENTITY_PLAYER_ATTACK_NODAMAGE = SoundEventImpl.get("minecraft:entity.player.attack.nodamage"); + SoundEvent ENTITY_PLAYER_ATTACK_NODAMAGE = BuiltinSoundEvent.get("minecraft:entity.player.attack.nodamage"); - SoundEvent ENTITY_PLAYER_ATTACK_STRONG = SoundEventImpl.get("minecraft:entity.player.attack.strong"); + SoundEvent ENTITY_PLAYER_ATTACK_STRONG = BuiltinSoundEvent.get("minecraft:entity.player.attack.strong"); - SoundEvent ENTITY_PLAYER_ATTACK_SWEEP = SoundEventImpl.get("minecraft:entity.player.attack.sweep"); + SoundEvent ENTITY_PLAYER_ATTACK_SWEEP = BuiltinSoundEvent.get("minecraft:entity.player.attack.sweep"); - SoundEvent ENTITY_PLAYER_ATTACK_WEAK = SoundEventImpl.get("minecraft:entity.player.attack.weak"); + SoundEvent ENTITY_PLAYER_ATTACK_WEAK = BuiltinSoundEvent.get("minecraft:entity.player.attack.weak"); - SoundEvent ENTITY_PLAYER_BIG_FALL = SoundEventImpl.get("minecraft:entity.player.big_fall"); + SoundEvent ENTITY_PLAYER_BIG_FALL = BuiltinSoundEvent.get("minecraft:entity.player.big_fall"); - SoundEvent ENTITY_PLAYER_BREATH = SoundEventImpl.get("minecraft:entity.player.breath"); + SoundEvent ENTITY_PLAYER_BREATH = BuiltinSoundEvent.get("minecraft:entity.player.breath"); - SoundEvent ENTITY_PLAYER_BURP = SoundEventImpl.get("minecraft:entity.player.burp"); + SoundEvent ENTITY_PLAYER_BURP = BuiltinSoundEvent.get("minecraft:entity.player.burp"); - SoundEvent ENTITY_PLAYER_DEATH = SoundEventImpl.get("minecraft:entity.player.death"); + SoundEvent ENTITY_PLAYER_DEATH = BuiltinSoundEvent.get("minecraft:entity.player.death"); - SoundEvent ENTITY_PLAYER_HURT = SoundEventImpl.get("minecraft:entity.player.hurt"); + SoundEvent ENTITY_PLAYER_HURT = BuiltinSoundEvent.get("minecraft:entity.player.hurt"); - SoundEvent ENTITY_PLAYER_HURT_DROWN = SoundEventImpl.get("minecraft:entity.player.hurt_drown"); + SoundEvent ENTITY_PLAYER_HURT_DROWN = BuiltinSoundEvent.get("minecraft:entity.player.hurt_drown"); - SoundEvent ENTITY_PLAYER_HURT_FREEZE = SoundEventImpl.get("minecraft:entity.player.hurt_freeze"); + SoundEvent ENTITY_PLAYER_HURT_FREEZE = BuiltinSoundEvent.get("minecraft:entity.player.hurt_freeze"); - SoundEvent ENTITY_PLAYER_HURT_ON_FIRE = SoundEventImpl.get("minecraft:entity.player.hurt_on_fire"); + SoundEvent ENTITY_PLAYER_HURT_ON_FIRE = BuiltinSoundEvent.get("minecraft:entity.player.hurt_on_fire"); - SoundEvent ENTITY_PLAYER_HURT_SWEET_BERRY_BUSH = SoundEventImpl.get("minecraft:entity.player.hurt_sweet_berry_bush"); + SoundEvent ENTITY_PLAYER_HURT_SWEET_BERRY_BUSH = BuiltinSoundEvent.get("minecraft:entity.player.hurt_sweet_berry_bush"); - SoundEvent ENTITY_PLAYER_LEVELUP = SoundEventImpl.get("minecraft:entity.player.levelup"); + SoundEvent ENTITY_PLAYER_LEVELUP = BuiltinSoundEvent.get("minecraft:entity.player.levelup"); - SoundEvent ENTITY_PLAYER_SMALL_FALL = SoundEventImpl.get("minecraft:entity.player.small_fall"); + SoundEvent ENTITY_PLAYER_SMALL_FALL = BuiltinSoundEvent.get("minecraft:entity.player.small_fall"); - SoundEvent ENTITY_PLAYER_SPLASH = SoundEventImpl.get("minecraft:entity.player.splash"); + SoundEvent ENTITY_PLAYER_SPLASH = BuiltinSoundEvent.get("minecraft:entity.player.splash"); - SoundEvent ENTITY_PLAYER_SPLASH_HIGH_SPEED = SoundEventImpl.get("minecraft:entity.player.splash.high_speed"); + SoundEvent ENTITY_PLAYER_SPLASH_HIGH_SPEED = BuiltinSoundEvent.get("minecraft:entity.player.splash.high_speed"); - SoundEvent ENTITY_PLAYER_SWIM = SoundEventImpl.get("minecraft:entity.player.swim"); + SoundEvent ENTITY_PLAYER_SWIM = BuiltinSoundEvent.get("minecraft:entity.player.swim"); - SoundEvent ENTITY_PLAYER_TELEPORT = SoundEventImpl.get("minecraft:entity.player.teleport"); + SoundEvent ENTITY_PLAYER_TELEPORT = BuiltinSoundEvent.get("minecraft:entity.player.teleport"); - SoundEvent ENTITY_POLAR_BEAR_AMBIENT = SoundEventImpl.get("minecraft:entity.polar_bear.ambient"); + SoundEvent ENTITY_POLAR_BEAR_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.polar_bear.ambient"); - SoundEvent ENTITY_POLAR_BEAR_AMBIENT_BABY = SoundEventImpl.get("minecraft:entity.polar_bear.ambient_baby"); + SoundEvent ENTITY_POLAR_BEAR_AMBIENT_BABY = BuiltinSoundEvent.get("minecraft:entity.polar_bear.ambient_baby"); - SoundEvent ENTITY_POLAR_BEAR_DEATH = SoundEventImpl.get("minecraft:entity.polar_bear.death"); + SoundEvent ENTITY_POLAR_BEAR_DEATH = BuiltinSoundEvent.get("minecraft:entity.polar_bear.death"); - SoundEvent ENTITY_POLAR_BEAR_HURT = SoundEventImpl.get("minecraft:entity.polar_bear.hurt"); + SoundEvent ENTITY_POLAR_BEAR_HURT = BuiltinSoundEvent.get("minecraft:entity.polar_bear.hurt"); - SoundEvent ENTITY_POLAR_BEAR_STEP = SoundEventImpl.get("minecraft:entity.polar_bear.step"); + SoundEvent ENTITY_POLAR_BEAR_STEP = BuiltinSoundEvent.get("minecraft:entity.polar_bear.step"); - SoundEvent ENTITY_POLAR_BEAR_WARNING = SoundEventImpl.get("minecraft:entity.polar_bear.warning"); + SoundEvent ENTITY_POLAR_BEAR_WARNING = BuiltinSoundEvent.get("minecraft:entity.polar_bear.warning"); - SoundEvent BLOCK_POLISHED_DEEPSLATE_BREAK = SoundEventImpl.get("minecraft:block.polished_deepslate.break"); + SoundEvent BLOCK_POLISHED_DEEPSLATE_BREAK = BuiltinSoundEvent.get("minecraft:block.polished_deepslate.break"); - SoundEvent BLOCK_POLISHED_DEEPSLATE_FALL = SoundEventImpl.get("minecraft:block.polished_deepslate.fall"); + SoundEvent BLOCK_POLISHED_DEEPSLATE_FALL = BuiltinSoundEvent.get("minecraft:block.polished_deepslate.fall"); - SoundEvent BLOCK_POLISHED_DEEPSLATE_HIT = SoundEventImpl.get("minecraft:block.polished_deepslate.hit"); + SoundEvent BLOCK_POLISHED_DEEPSLATE_HIT = BuiltinSoundEvent.get("minecraft:block.polished_deepslate.hit"); - SoundEvent BLOCK_POLISHED_DEEPSLATE_PLACE = SoundEventImpl.get("minecraft:block.polished_deepslate.place"); + SoundEvent BLOCK_POLISHED_DEEPSLATE_PLACE = BuiltinSoundEvent.get("minecraft:block.polished_deepslate.place"); - SoundEvent BLOCK_POLISHED_DEEPSLATE_STEP = SoundEventImpl.get("minecraft:block.polished_deepslate.step"); + SoundEvent BLOCK_POLISHED_DEEPSLATE_STEP = BuiltinSoundEvent.get("minecraft:block.polished_deepslate.step"); - SoundEvent BLOCK_PORTAL_AMBIENT = SoundEventImpl.get("minecraft:block.portal.ambient"); + SoundEvent BLOCK_PORTAL_AMBIENT = BuiltinSoundEvent.get("minecraft:block.portal.ambient"); - SoundEvent BLOCK_PORTAL_TRAVEL = SoundEventImpl.get("minecraft:block.portal.travel"); + SoundEvent BLOCK_PORTAL_TRAVEL = BuiltinSoundEvent.get("minecraft:block.portal.travel"); - SoundEvent BLOCK_PORTAL_TRIGGER = SoundEventImpl.get("minecraft:block.portal.trigger"); + SoundEvent BLOCK_PORTAL_TRIGGER = BuiltinSoundEvent.get("minecraft:block.portal.trigger"); - SoundEvent BLOCK_POWDER_SNOW_BREAK = SoundEventImpl.get("minecraft:block.powder_snow.break"); + SoundEvent BLOCK_POWDER_SNOW_BREAK = BuiltinSoundEvent.get("minecraft:block.powder_snow.break"); - SoundEvent BLOCK_POWDER_SNOW_FALL = SoundEventImpl.get("minecraft:block.powder_snow.fall"); + SoundEvent BLOCK_POWDER_SNOW_FALL = BuiltinSoundEvent.get("minecraft:block.powder_snow.fall"); - SoundEvent BLOCK_POWDER_SNOW_HIT = SoundEventImpl.get("minecraft:block.powder_snow.hit"); + SoundEvent BLOCK_POWDER_SNOW_HIT = BuiltinSoundEvent.get("minecraft:block.powder_snow.hit"); - SoundEvent BLOCK_POWDER_SNOW_PLACE = SoundEventImpl.get("minecraft:block.powder_snow.place"); + SoundEvent BLOCK_POWDER_SNOW_PLACE = BuiltinSoundEvent.get("minecraft:block.powder_snow.place"); - SoundEvent BLOCK_POWDER_SNOW_STEP = SoundEventImpl.get("minecraft:block.powder_snow.step"); + SoundEvent BLOCK_POWDER_SNOW_STEP = BuiltinSoundEvent.get("minecraft:block.powder_snow.step"); - SoundEvent ENTITY_PUFFER_FISH_AMBIENT = SoundEventImpl.get("minecraft:entity.puffer_fish.ambient"); + SoundEvent ENTITY_PUFFER_FISH_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.puffer_fish.ambient"); - SoundEvent ENTITY_PUFFER_FISH_BLOW_OUT = SoundEventImpl.get("minecraft:entity.puffer_fish.blow_out"); + SoundEvent ENTITY_PUFFER_FISH_BLOW_OUT = BuiltinSoundEvent.get("minecraft:entity.puffer_fish.blow_out"); - SoundEvent ENTITY_PUFFER_FISH_BLOW_UP = SoundEventImpl.get("minecraft:entity.puffer_fish.blow_up"); + SoundEvent ENTITY_PUFFER_FISH_BLOW_UP = BuiltinSoundEvent.get("minecraft:entity.puffer_fish.blow_up"); - SoundEvent ENTITY_PUFFER_FISH_DEATH = SoundEventImpl.get("minecraft:entity.puffer_fish.death"); + SoundEvent ENTITY_PUFFER_FISH_DEATH = BuiltinSoundEvent.get("minecraft:entity.puffer_fish.death"); - SoundEvent ENTITY_PUFFER_FISH_FLOP = SoundEventImpl.get("minecraft:entity.puffer_fish.flop"); + SoundEvent ENTITY_PUFFER_FISH_FLOP = BuiltinSoundEvent.get("minecraft:entity.puffer_fish.flop"); - SoundEvent ENTITY_PUFFER_FISH_HURT = SoundEventImpl.get("minecraft:entity.puffer_fish.hurt"); + SoundEvent ENTITY_PUFFER_FISH_HURT = BuiltinSoundEvent.get("minecraft:entity.puffer_fish.hurt"); - SoundEvent ENTITY_PUFFER_FISH_STING = SoundEventImpl.get("minecraft:entity.puffer_fish.sting"); + SoundEvent ENTITY_PUFFER_FISH_STING = BuiltinSoundEvent.get("minecraft:entity.puffer_fish.sting"); - SoundEvent BLOCK_PUMPKIN_CARVE = SoundEventImpl.get("minecraft:block.pumpkin.carve"); + SoundEvent BLOCK_PUMPKIN_CARVE = BuiltinSoundEvent.get("minecraft:block.pumpkin.carve"); - SoundEvent ENTITY_RABBIT_AMBIENT = SoundEventImpl.get("minecraft:entity.rabbit.ambient"); + SoundEvent ENTITY_RABBIT_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.rabbit.ambient"); - SoundEvent ENTITY_RABBIT_ATTACK = SoundEventImpl.get("minecraft:entity.rabbit.attack"); + SoundEvent ENTITY_RABBIT_ATTACK = BuiltinSoundEvent.get("minecraft:entity.rabbit.attack"); - SoundEvent ENTITY_RABBIT_DEATH = SoundEventImpl.get("minecraft:entity.rabbit.death"); + SoundEvent ENTITY_RABBIT_DEATH = BuiltinSoundEvent.get("minecraft:entity.rabbit.death"); - SoundEvent ENTITY_RABBIT_HURT = SoundEventImpl.get("minecraft:entity.rabbit.hurt"); + SoundEvent ENTITY_RABBIT_HURT = BuiltinSoundEvent.get("minecraft:entity.rabbit.hurt"); - SoundEvent ENTITY_RABBIT_JUMP = SoundEventImpl.get("minecraft:entity.rabbit.jump"); + SoundEvent ENTITY_RABBIT_JUMP = BuiltinSoundEvent.get("minecraft:entity.rabbit.jump"); - SoundEvent EVENT_RAID_HORN = SoundEventImpl.get("minecraft:event.raid.horn"); + SoundEvent EVENT_RAID_HORN = BuiltinSoundEvent.get("minecraft:event.raid.horn"); - SoundEvent ENTITY_RAVAGER_AMBIENT = SoundEventImpl.get("minecraft:entity.ravager.ambient"); + SoundEvent ENTITY_RAVAGER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.ravager.ambient"); - SoundEvent ENTITY_RAVAGER_ATTACK = SoundEventImpl.get("minecraft:entity.ravager.attack"); + SoundEvent ENTITY_RAVAGER_ATTACK = BuiltinSoundEvent.get("minecraft:entity.ravager.attack"); - SoundEvent ENTITY_RAVAGER_CELEBRATE = SoundEventImpl.get("minecraft:entity.ravager.celebrate"); + SoundEvent ENTITY_RAVAGER_CELEBRATE = BuiltinSoundEvent.get("minecraft:entity.ravager.celebrate"); - SoundEvent ENTITY_RAVAGER_DEATH = SoundEventImpl.get("minecraft:entity.ravager.death"); + SoundEvent ENTITY_RAVAGER_DEATH = BuiltinSoundEvent.get("minecraft:entity.ravager.death"); - SoundEvent ENTITY_RAVAGER_HURT = SoundEventImpl.get("minecraft:entity.ravager.hurt"); + SoundEvent ENTITY_RAVAGER_HURT = BuiltinSoundEvent.get("minecraft:entity.ravager.hurt"); - SoundEvent ENTITY_RAVAGER_STEP = SoundEventImpl.get("minecraft:entity.ravager.step"); + SoundEvent ENTITY_RAVAGER_STEP = BuiltinSoundEvent.get("minecraft:entity.ravager.step"); - SoundEvent ENTITY_RAVAGER_STUNNED = SoundEventImpl.get("minecraft:entity.ravager.stunned"); + SoundEvent ENTITY_RAVAGER_STUNNED = BuiltinSoundEvent.get("minecraft:entity.ravager.stunned"); - SoundEvent ENTITY_RAVAGER_ROAR = SoundEventImpl.get("minecraft:entity.ravager.roar"); + SoundEvent ENTITY_RAVAGER_ROAR = BuiltinSoundEvent.get("minecraft:entity.ravager.roar"); - SoundEvent BLOCK_NETHER_GOLD_ORE_BREAK = SoundEventImpl.get("minecraft:block.nether_gold_ore.break"); + SoundEvent BLOCK_NETHER_GOLD_ORE_BREAK = BuiltinSoundEvent.get("minecraft:block.nether_gold_ore.break"); - SoundEvent BLOCK_NETHER_GOLD_ORE_FALL = SoundEventImpl.get("minecraft:block.nether_gold_ore.fall"); + SoundEvent BLOCK_NETHER_GOLD_ORE_FALL = BuiltinSoundEvent.get("minecraft:block.nether_gold_ore.fall"); - SoundEvent BLOCK_NETHER_GOLD_ORE_HIT = SoundEventImpl.get("minecraft:block.nether_gold_ore.hit"); + SoundEvent BLOCK_NETHER_GOLD_ORE_HIT = BuiltinSoundEvent.get("minecraft:block.nether_gold_ore.hit"); - SoundEvent BLOCK_NETHER_GOLD_ORE_PLACE = SoundEventImpl.get("minecraft:block.nether_gold_ore.place"); + SoundEvent BLOCK_NETHER_GOLD_ORE_PLACE = BuiltinSoundEvent.get("minecraft:block.nether_gold_ore.place"); - SoundEvent BLOCK_NETHER_GOLD_ORE_STEP = SoundEventImpl.get("minecraft:block.nether_gold_ore.step"); + SoundEvent BLOCK_NETHER_GOLD_ORE_STEP = BuiltinSoundEvent.get("minecraft:block.nether_gold_ore.step"); - SoundEvent BLOCK_NETHER_ORE_BREAK = SoundEventImpl.get("minecraft:block.nether_ore.break"); + SoundEvent BLOCK_NETHER_ORE_BREAK = BuiltinSoundEvent.get("minecraft:block.nether_ore.break"); - SoundEvent BLOCK_NETHER_ORE_FALL = SoundEventImpl.get("minecraft:block.nether_ore.fall"); + SoundEvent BLOCK_NETHER_ORE_FALL = BuiltinSoundEvent.get("minecraft:block.nether_ore.fall"); - SoundEvent BLOCK_NETHER_ORE_HIT = SoundEventImpl.get("minecraft:block.nether_ore.hit"); + SoundEvent BLOCK_NETHER_ORE_HIT = BuiltinSoundEvent.get("minecraft:block.nether_ore.hit"); - SoundEvent BLOCK_NETHER_ORE_PLACE = SoundEventImpl.get("minecraft:block.nether_ore.place"); + SoundEvent BLOCK_NETHER_ORE_PLACE = BuiltinSoundEvent.get("minecraft:block.nether_ore.place"); - SoundEvent BLOCK_NETHER_ORE_STEP = SoundEventImpl.get("minecraft:block.nether_ore.step"); + SoundEvent BLOCK_NETHER_ORE_STEP = BuiltinSoundEvent.get("minecraft:block.nether_ore.step"); - SoundEvent BLOCK_REDSTONE_TORCH_BURNOUT = SoundEventImpl.get("minecraft:block.redstone_torch.burnout"); + SoundEvent BLOCK_REDSTONE_TORCH_BURNOUT = BuiltinSoundEvent.get("minecraft:block.redstone_torch.burnout"); - SoundEvent BLOCK_RESPAWN_ANCHOR_AMBIENT = SoundEventImpl.get("minecraft:block.respawn_anchor.ambient"); + SoundEvent BLOCK_RESPAWN_ANCHOR_AMBIENT = BuiltinSoundEvent.get("minecraft:block.respawn_anchor.ambient"); - SoundEvent BLOCK_RESPAWN_ANCHOR_CHARGE = SoundEventImpl.get("minecraft:block.respawn_anchor.charge"); + SoundEvent BLOCK_RESPAWN_ANCHOR_CHARGE = BuiltinSoundEvent.get("minecraft:block.respawn_anchor.charge"); - SoundEvent BLOCK_RESPAWN_ANCHOR_DEPLETE = SoundEventImpl.get("minecraft:block.respawn_anchor.deplete"); + SoundEvent BLOCK_RESPAWN_ANCHOR_DEPLETE = BuiltinSoundEvent.get("minecraft:block.respawn_anchor.deplete"); - SoundEvent BLOCK_RESPAWN_ANCHOR_SET_SPAWN = SoundEventImpl.get("minecraft:block.respawn_anchor.set_spawn"); + SoundEvent BLOCK_RESPAWN_ANCHOR_SET_SPAWN = BuiltinSoundEvent.get("minecraft:block.respawn_anchor.set_spawn"); - SoundEvent BLOCK_ROOTED_DIRT_BREAK = SoundEventImpl.get("minecraft:block.rooted_dirt.break"); + SoundEvent BLOCK_ROOTED_DIRT_BREAK = BuiltinSoundEvent.get("minecraft:block.rooted_dirt.break"); - SoundEvent BLOCK_ROOTED_DIRT_FALL = SoundEventImpl.get("minecraft:block.rooted_dirt.fall"); + SoundEvent BLOCK_ROOTED_DIRT_FALL = BuiltinSoundEvent.get("minecraft:block.rooted_dirt.fall"); - SoundEvent BLOCK_ROOTED_DIRT_HIT = SoundEventImpl.get("minecraft:block.rooted_dirt.hit"); + SoundEvent BLOCK_ROOTED_DIRT_HIT = BuiltinSoundEvent.get("minecraft:block.rooted_dirt.hit"); - SoundEvent BLOCK_ROOTED_DIRT_PLACE = SoundEventImpl.get("minecraft:block.rooted_dirt.place"); + SoundEvent BLOCK_ROOTED_DIRT_PLACE = BuiltinSoundEvent.get("minecraft:block.rooted_dirt.place"); - SoundEvent BLOCK_ROOTED_DIRT_STEP = SoundEventImpl.get("minecraft:block.rooted_dirt.step"); + SoundEvent BLOCK_ROOTED_DIRT_STEP = BuiltinSoundEvent.get("minecraft:block.rooted_dirt.step"); - SoundEvent ENTITY_SALMON_AMBIENT = SoundEventImpl.get("minecraft:entity.salmon.ambient"); + SoundEvent ENTITY_SALMON_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.salmon.ambient"); - SoundEvent ENTITY_SALMON_DEATH = SoundEventImpl.get("minecraft:entity.salmon.death"); + SoundEvent ENTITY_SALMON_DEATH = BuiltinSoundEvent.get("minecraft:entity.salmon.death"); - SoundEvent ENTITY_SALMON_FLOP = SoundEventImpl.get("minecraft:entity.salmon.flop"); + SoundEvent ENTITY_SALMON_FLOP = BuiltinSoundEvent.get("minecraft:entity.salmon.flop"); - SoundEvent ENTITY_SALMON_HURT = SoundEventImpl.get("minecraft:entity.salmon.hurt"); + SoundEvent ENTITY_SALMON_HURT = BuiltinSoundEvent.get("minecraft:entity.salmon.hurt"); - SoundEvent BLOCK_SAND_BREAK = SoundEventImpl.get("minecraft:block.sand.break"); + SoundEvent BLOCK_SAND_BREAK = BuiltinSoundEvent.get("minecraft:block.sand.break"); - SoundEvent BLOCK_SAND_FALL = SoundEventImpl.get("minecraft:block.sand.fall"); + SoundEvent BLOCK_SAND_FALL = BuiltinSoundEvent.get("minecraft:block.sand.fall"); - SoundEvent BLOCK_SAND_HIT = SoundEventImpl.get("minecraft:block.sand.hit"); + SoundEvent BLOCK_SAND_HIT = BuiltinSoundEvent.get("minecraft:block.sand.hit"); - SoundEvent BLOCK_SAND_PLACE = SoundEventImpl.get("minecraft:block.sand.place"); + SoundEvent BLOCK_SAND_PLACE = BuiltinSoundEvent.get("minecraft:block.sand.place"); - SoundEvent BLOCK_SAND_STEP = SoundEventImpl.get("minecraft:block.sand.step"); + SoundEvent BLOCK_SAND_STEP = BuiltinSoundEvent.get("minecraft:block.sand.step"); - SoundEvent BLOCK_SCAFFOLDING_BREAK = SoundEventImpl.get("minecraft:block.scaffolding.break"); + SoundEvent BLOCK_SCAFFOLDING_BREAK = BuiltinSoundEvent.get("minecraft:block.scaffolding.break"); - SoundEvent BLOCK_SCAFFOLDING_FALL = SoundEventImpl.get("minecraft:block.scaffolding.fall"); + SoundEvent BLOCK_SCAFFOLDING_FALL = BuiltinSoundEvent.get("minecraft:block.scaffolding.fall"); - SoundEvent BLOCK_SCAFFOLDING_HIT = SoundEventImpl.get("minecraft:block.scaffolding.hit"); + SoundEvent BLOCK_SCAFFOLDING_HIT = BuiltinSoundEvent.get("minecraft:block.scaffolding.hit"); - SoundEvent BLOCK_SCAFFOLDING_PLACE = SoundEventImpl.get("minecraft:block.scaffolding.place"); + SoundEvent BLOCK_SCAFFOLDING_PLACE = BuiltinSoundEvent.get("minecraft:block.scaffolding.place"); - SoundEvent BLOCK_SCAFFOLDING_STEP = SoundEventImpl.get("minecraft:block.scaffolding.step"); + SoundEvent BLOCK_SCAFFOLDING_STEP = BuiltinSoundEvent.get("minecraft:block.scaffolding.step"); - SoundEvent BLOCK_SCULK_SPREAD = SoundEventImpl.get("minecraft:block.sculk.spread"); + SoundEvent BLOCK_SCULK_SPREAD = BuiltinSoundEvent.get("minecraft:block.sculk.spread"); - SoundEvent BLOCK_SCULK_CHARGE = SoundEventImpl.get("minecraft:block.sculk.charge"); + SoundEvent BLOCK_SCULK_CHARGE = BuiltinSoundEvent.get("minecraft:block.sculk.charge"); - SoundEvent BLOCK_SCULK_BREAK = SoundEventImpl.get("minecraft:block.sculk.break"); + SoundEvent BLOCK_SCULK_BREAK = BuiltinSoundEvent.get("minecraft:block.sculk.break"); - SoundEvent BLOCK_SCULK_FALL = SoundEventImpl.get("minecraft:block.sculk.fall"); + SoundEvent BLOCK_SCULK_FALL = BuiltinSoundEvent.get("minecraft:block.sculk.fall"); - SoundEvent BLOCK_SCULK_HIT = SoundEventImpl.get("minecraft:block.sculk.hit"); + SoundEvent BLOCK_SCULK_HIT = BuiltinSoundEvent.get("minecraft:block.sculk.hit"); - SoundEvent BLOCK_SCULK_PLACE = SoundEventImpl.get("minecraft:block.sculk.place"); + SoundEvent BLOCK_SCULK_PLACE = BuiltinSoundEvent.get("minecraft:block.sculk.place"); - SoundEvent BLOCK_SCULK_STEP = SoundEventImpl.get("minecraft:block.sculk.step"); + SoundEvent BLOCK_SCULK_STEP = BuiltinSoundEvent.get("minecraft:block.sculk.step"); - SoundEvent BLOCK_SCULK_CATALYST_BLOOM = SoundEventImpl.get("minecraft:block.sculk_catalyst.bloom"); + SoundEvent BLOCK_SCULK_CATALYST_BLOOM = BuiltinSoundEvent.get("minecraft:block.sculk_catalyst.bloom"); - SoundEvent BLOCK_SCULK_CATALYST_BREAK = SoundEventImpl.get("minecraft:block.sculk_catalyst.break"); + SoundEvent BLOCK_SCULK_CATALYST_BREAK = BuiltinSoundEvent.get("minecraft:block.sculk_catalyst.break"); - SoundEvent BLOCK_SCULK_CATALYST_FALL = SoundEventImpl.get("minecraft:block.sculk_catalyst.fall"); + SoundEvent BLOCK_SCULK_CATALYST_FALL = BuiltinSoundEvent.get("minecraft:block.sculk_catalyst.fall"); - SoundEvent BLOCK_SCULK_CATALYST_HIT = SoundEventImpl.get("minecraft:block.sculk_catalyst.hit"); + SoundEvent BLOCK_SCULK_CATALYST_HIT = BuiltinSoundEvent.get("minecraft:block.sculk_catalyst.hit"); - SoundEvent BLOCK_SCULK_CATALYST_PLACE = SoundEventImpl.get("minecraft:block.sculk_catalyst.place"); + SoundEvent BLOCK_SCULK_CATALYST_PLACE = BuiltinSoundEvent.get("minecraft:block.sculk_catalyst.place"); - SoundEvent BLOCK_SCULK_CATALYST_STEP = SoundEventImpl.get("minecraft:block.sculk_catalyst.step"); + SoundEvent BLOCK_SCULK_CATALYST_STEP = BuiltinSoundEvent.get("minecraft:block.sculk_catalyst.step"); - SoundEvent BLOCK_SCULK_SENSOR_CLICKING = SoundEventImpl.get("minecraft:block.sculk_sensor.clicking"); + SoundEvent BLOCK_SCULK_SENSOR_CLICKING = BuiltinSoundEvent.get("minecraft:block.sculk_sensor.clicking"); - SoundEvent BLOCK_SCULK_SENSOR_CLICKING_STOP = SoundEventImpl.get("minecraft:block.sculk_sensor.clicking_stop"); + SoundEvent BLOCK_SCULK_SENSOR_CLICKING_STOP = BuiltinSoundEvent.get("minecraft:block.sculk_sensor.clicking_stop"); - SoundEvent BLOCK_SCULK_SENSOR_BREAK = SoundEventImpl.get("minecraft:block.sculk_sensor.break"); + SoundEvent BLOCK_SCULK_SENSOR_BREAK = BuiltinSoundEvent.get("minecraft:block.sculk_sensor.break"); - SoundEvent BLOCK_SCULK_SENSOR_FALL = SoundEventImpl.get("minecraft:block.sculk_sensor.fall"); + SoundEvent BLOCK_SCULK_SENSOR_FALL = BuiltinSoundEvent.get("minecraft:block.sculk_sensor.fall"); - SoundEvent BLOCK_SCULK_SENSOR_HIT = SoundEventImpl.get("minecraft:block.sculk_sensor.hit"); + SoundEvent BLOCK_SCULK_SENSOR_HIT = BuiltinSoundEvent.get("minecraft:block.sculk_sensor.hit"); - SoundEvent BLOCK_SCULK_SENSOR_PLACE = SoundEventImpl.get("minecraft:block.sculk_sensor.place"); + SoundEvent BLOCK_SCULK_SENSOR_PLACE = BuiltinSoundEvent.get("minecraft:block.sculk_sensor.place"); - SoundEvent BLOCK_SCULK_SENSOR_STEP = SoundEventImpl.get("minecraft:block.sculk_sensor.step"); + SoundEvent BLOCK_SCULK_SENSOR_STEP = BuiltinSoundEvent.get("minecraft:block.sculk_sensor.step"); - SoundEvent BLOCK_SCULK_SHRIEKER_BREAK = SoundEventImpl.get("minecraft:block.sculk_shrieker.break"); + SoundEvent BLOCK_SCULK_SHRIEKER_BREAK = BuiltinSoundEvent.get("minecraft:block.sculk_shrieker.break"); - SoundEvent BLOCK_SCULK_SHRIEKER_FALL = SoundEventImpl.get("minecraft:block.sculk_shrieker.fall"); + SoundEvent BLOCK_SCULK_SHRIEKER_FALL = BuiltinSoundEvent.get("minecraft:block.sculk_shrieker.fall"); - SoundEvent BLOCK_SCULK_SHRIEKER_HIT = SoundEventImpl.get("minecraft:block.sculk_shrieker.hit"); + SoundEvent BLOCK_SCULK_SHRIEKER_HIT = BuiltinSoundEvent.get("minecraft:block.sculk_shrieker.hit"); - SoundEvent BLOCK_SCULK_SHRIEKER_PLACE = SoundEventImpl.get("minecraft:block.sculk_shrieker.place"); + SoundEvent BLOCK_SCULK_SHRIEKER_PLACE = BuiltinSoundEvent.get("minecraft:block.sculk_shrieker.place"); - SoundEvent BLOCK_SCULK_SHRIEKER_SHRIEK = SoundEventImpl.get("minecraft:block.sculk_shrieker.shriek"); + SoundEvent BLOCK_SCULK_SHRIEKER_SHRIEK = BuiltinSoundEvent.get("minecraft:block.sculk_shrieker.shriek"); - SoundEvent BLOCK_SCULK_SHRIEKER_STEP = SoundEventImpl.get("minecraft:block.sculk_shrieker.step"); + SoundEvent BLOCK_SCULK_SHRIEKER_STEP = BuiltinSoundEvent.get("minecraft:block.sculk_shrieker.step"); - SoundEvent BLOCK_SCULK_VEIN_BREAK = SoundEventImpl.get("minecraft:block.sculk_vein.break"); + SoundEvent BLOCK_SCULK_VEIN_BREAK = BuiltinSoundEvent.get("minecraft:block.sculk_vein.break"); - SoundEvent BLOCK_SCULK_VEIN_FALL = SoundEventImpl.get("minecraft:block.sculk_vein.fall"); + SoundEvent BLOCK_SCULK_VEIN_FALL = BuiltinSoundEvent.get("minecraft:block.sculk_vein.fall"); - SoundEvent BLOCK_SCULK_VEIN_HIT = SoundEventImpl.get("minecraft:block.sculk_vein.hit"); + SoundEvent BLOCK_SCULK_VEIN_HIT = BuiltinSoundEvent.get("minecraft:block.sculk_vein.hit"); - SoundEvent BLOCK_SCULK_VEIN_PLACE = SoundEventImpl.get("minecraft:block.sculk_vein.place"); + SoundEvent BLOCK_SCULK_VEIN_PLACE = BuiltinSoundEvent.get("minecraft:block.sculk_vein.place"); - SoundEvent BLOCK_SCULK_VEIN_STEP = SoundEventImpl.get("minecraft:block.sculk_vein.step"); + SoundEvent BLOCK_SCULK_VEIN_STEP = BuiltinSoundEvent.get("minecraft:block.sculk_vein.step"); - SoundEvent ENTITY_SHEEP_AMBIENT = SoundEventImpl.get("minecraft:entity.sheep.ambient"); + SoundEvent ENTITY_SHEEP_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.sheep.ambient"); - SoundEvent ENTITY_SHEEP_DEATH = SoundEventImpl.get("minecraft:entity.sheep.death"); + SoundEvent ENTITY_SHEEP_DEATH = BuiltinSoundEvent.get("minecraft:entity.sheep.death"); - SoundEvent ENTITY_SHEEP_HURT = SoundEventImpl.get("minecraft:entity.sheep.hurt"); + SoundEvent ENTITY_SHEEP_HURT = BuiltinSoundEvent.get("minecraft:entity.sheep.hurt"); - SoundEvent ENTITY_SHEEP_SHEAR = SoundEventImpl.get("minecraft:entity.sheep.shear"); + SoundEvent ENTITY_SHEEP_SHEAR = BuiltinSoundEvent.get("minecraft:entity.sheep.shear"); - SoundEvent ENTITY_SHEEP_STEP = SoundEventImpl.get("minecraft:entity.sheep.step"); + SoundEvent ENTITY_SHEEP_STEP = BuiltinSoundEvent.get("minecraft:entity.sheep.step"); - SoundEvent ITEM_SHIELD_BLOCK = SoundEventImpl.get("minecraft:item.shield.block"); + SoundEvent ITEM_SHIELD_BLOCK = BuiltinSoundEvent.get("minecraft:item.shield.block"); - SoundEvent ITEM_SHIELD_BREAK = SoundEventImpl.get("minecraft:item.shield.break"); + SoundEvent ITEM_SHIELD_BREAK = BuiltinSoundEvent.get("minecraft:item.shield.break"); - SoundEvent BLOCK_SHROOMLIGHT_BREAK = SoundEventImpl.get("minecraft:block.shroomlight.break"); + SoundEvent BLOCK_SHROOMLIGHT_BREAK = BuiltinSoundEvent.get("minecraft:block.shroomlight.break"); - SoundEvent BLOCK_SHROOMLIGHT_STEP = SoundEventImpl.get("minecraft:block.shroomlight.step"); + SoundEvent BLOCK_SHROOMLIGHT_STEP = BuiltinSoundEvent.get("minecraft:block.shroomlight.step"); - SoundEvent BLOCK_SHROOMLIGHT_PLACE = SoundEventImpl.get("minecraft:block.shroomlight.place"); + SoundEvent BLOCK_SHROOMLIGHT_PLACE = BuiltinSoundEvent.get("minecraft:block.shroomlight.place"); - SoundEvent BLOCK_SHROOMLIGHT_HIT = SoundEventImpl.get("minecraft:block.shroomlight.hit"); + SoundEvent BLOCK_SHROOMLIGHT_HIT = BuiltinSoundEvent.get("minecraft:block.shroomlight.hit"); - SoundEvent BLOCK_SHROOMLIGHT_FALL = SoundEventImpl.get("minecraft:block.shroomlight.fall"); + SoundEvent BLOCK_SHROOMLIGHT_FALL = BuiltinSoundEvent.get("minecraft:block.shroomlight.fall"); - SoundEvent ITEM_SHOVEL_FLATTEN = SoundEventImpl.get("minecraft:item.shovel.flatten"); + SoundEvent ITEM_SHOVEL_FLATTEN = BuiltinSoundEvent.get("minecraft:item.shovel.flatten"); - SoundEvent ENTITY_SHULKER_AMBIENT = SoundEventImpl.get("minecraft:entity.shulker.ambient"); + SoundEvent ENTITY_SHULKER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.shulker.ambient"); - SoundEvent BLOCK_SHULKER_BOX_CLOSE = SoundEventImpl.get("minecraft:block.shulker_box.close"); + SoundEvent BLOCK_SHULKER_BOX_CLOSE = BuiltinSoundEvent.get("minecraft:block.shulker_box.close"); - SoundEvent BLOCK_SHULKER_BOX_OPEN = SoundEventImpl.get("minecraft:block.shulker_box.open"); + SoundEvent BLOCK_SHULKER_BOX_OPEN = BuiltinSoundEvent.get("minecraft:block.shulker_box.open"); - SoundEvent ENTITY_SHULKER_BULLET_HIT = SoundEventImpl.get("minecraft:entity.shulker_bullet.hit"); + SoundEvent ENTITY_SHULKER_BULLET_HIT = BuiltinSoundEvent.get("minecraft:entity.shulker_bullet.hit"); - SoundEvent ENTITY_SHULKER_BULLET_HURT = SoundEventImpl.get("minecraft:entity.shulker_bullet.hurt"); + SoundEvent ENTITY_SHULKER_BULLET_HURT = BuiltinSoundEvent.get("minecraft:entity.shulker_bullet.hurt"); - SoundEvent ENTITY_SHULKER_CLOSE = SoundEventImpl.get("minecraft:entity.shulker.close"); + SoundEvent ENTITY_SHULKER_CLOSE = BuiltinSoundEvent.get("minecraft:entity.shulker.close"); - SoundEvent ENTITY_SHULKER_DEATH = SoundEventImpl.get("minecraft:entity.shulker.death"); + SoundEvent ENTITY_SHULKER_DEATH = BuiltinSoundEvent.get("minecraft:entity.shulker.death"); - SoundEvent ENTITY_SHULKER_HURT = SoundEventImpl.get("minecraft:entity.shulker.hurt"); + SoundEvent ENTITY_SHULKER_HURT = BuiltinSoundEvent.get("minecraft:entity.shulker.hurt"); - SoundEvent ENTITY_SHULKER_HURT_CLOSED = SoundEventImpl.get("minecraft:entity.shulker.hurt_closed"); + SoundEvent ENTITY_SHULKER_HURT_CLOSED = BuiltinSoundEvent.get("minecraft:entity.shulker.hurt_closed"); - SoundEvent ENTITY_SHULKER_OPEN = SoundEventImpl.get("minecraft:entity.shulker.open"); + SoundEvent ENTITY_SHULKER_OPEN = BuiltinSoundEvent.get("minecraft:entity.shulker.open"); - SoundEvent ENTITY_SHULKER_SHOOT = SoundEventImpl.get("minecraft:entity.shulker.shoot"); + SoundEvent ENTITY_SHULKER_SHOOT = BuiltinSoundEvent.get("minecraft:entity.shulker.shoot"); - SoundEvent ENTITY_SHULKER_TELEPORT = SoundEventImpl.get("minecraft:entity.shulker.teleport"); + SoundEvent ENTITY_SHULKER_TELEPORT = BuiltinSoundEvent.get("minecraft:entity.shulker.teleport"); - SoundEvent ENTITY_SILVERFISH_AMBIENT = SoundEventImpl.get("minecraft:entity.silverfish.ambient"); + SoundEvent ENTITY_SILVERFISH_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.silverfish.ambient"); - SoundEvent ENTITY_SILVERFISH_DEATH = SoundEventImpl.get("minecraft:entity.silverfish.death"); + SoundEvent ENTITY_SILVERFISH_DEATH = BuiltinSoundEvent.get("minecraft:entity.silverfish.death"); - SoundEvent ENTITY_SILVERFISH_HURT = SoundEventImpl.get("minecraft:entity.silverfish.hurt"); + SoundEvent ENTITY_SILVERFISH_HURT = BuiltinSoundEvent.get("minecraft:entity.silverfish.hurt"); - SoundEvent ENTITY_SILVERFISH_STEP = SoundEventImpl.get("minecraft:entity.silverfish.step"); + SoundEvent ENTITY_SILVERFISH_STEP = BuiltinSoundEvent.get("minecraft:entity.silverfish.step"); - SoundEvent ENTITY_SKELETON_AMBIENT = SoundEventImpl.get("minecraft:entity.skeleton.ambient"); + SoundEvent ENTITY_SKELETON_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.skeleton.ambient"); - SoundEvent ENTITY_SKELETON_CONVERTED_TO_STRAY = SoundEventImpl.get("minecraft:entity.skeleton.converted_to_stray"); + SoundEvent ENTITY_SKELETON_CONVERTED_TO_STRAY = BuiltinSoundEvent.get("minecraft:entity.skeleton.converted_to_stray"); - SoundEvent ENTITY_SKELETON_DEATH = SoundEventImpl.get("minecraft:entity.skeleton.death"); + SoundEvent ENTITY_SKELETON_DEATH = BuiltinSoundEvent.get("minecraft:entity.skeleton.death"); - SoundEvent ENTITY_SKELETON_HORSE_AMBIENT = SoundEventImpl.get("minecraft:entity.skeleton_horse.ambient"); + SoundEvent ENTITY_SKELETON_HORSE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.ambient"); - SoundEvent ENTITY_SKELETON_HORSE_DEATH = SoundEventImpl.get("minecraft:entity.skeleton_horse.death"); + SoundEvent ENTITY_SKELETON_HORSE_DEATH = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.death"); - SoundEvent ENTITY_SKELETON_HORSE_HURT = SoundEventImpl.get("minecraft:entity.skeleton_horse.hurt"); + SoundEvent ENTITY_SKELETON_HORSE_HURT = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.hurt"); - SoundEvent ENTITY_SKELETON_HORSE_SWIM = SoundEventImpl.get("minecraft:entity.skeleton_horse.swim"); + SoundEvent ENTITY_SKELETON_HORSE_SWIM = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.swim"); - SoundEvent ENTITY_SKELETON_HORSE_AMBIENT_WATER = SoundEventImpl.get("minecraft:entity.skeleton_horse.ambient_water"); + SoundEvent ENTITY_SKELETON_HORSE_AMBIENT_WATER = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.ambient_water"); - SoundEvent ENTITY_SKELETON_HORSE_GALLOP_WATER = SoundEventImpl.get("minecraft:entity.skeleton_horse.gallop_water"); + SoundEvent ENTITY_SKELETON_HORSE_GALLOP_WATER = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.gallop_water"); - SoundEvent ENTITY_SKELETON_HORSE_JUMP_WATER = SoundEventImpl.get("minecraft:entity.skeleton_horse.jump_water"); + SoundEvent ENTITY_SKELETON_HORSE_JUMP_WATER = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.jump_water"); - SoundEvent ENTITY_SKELETON_HORSE_STEP_WATER = SoundEventImpl.get("minecraft:entity.skeleton_horse.step_water"); + SoundEvent ENTITY_SKELETON_HORSE_STEP_WATER = BuiltinSoundEvent.get("minecraft:entity.skeleton_horse.step_water"); - SoundEvent ENTITY_SKELETON_HURT = SoundEventImpl.get("minecraft:entity.skeleton.hurt"); + SoundEvent ENTITY_SKELETON_HURT = BuiltinSoundEvent.get("minecraft:entity.skeleton.hurt"); - SoundEvent ENTITY_SKELETON_SHOOT = SoundEventImpl.get("minecraft:entity.skeleton.shoot"); + SoundEvent ENTITY_SKELETON_SHOOT = BuiltinSoundEvent.get("minecraft:entity.skeleton.shoot"); - SoundEvent ENTITY_SKELETON_STEP = SoundEventImpl.get("minecraft:entity.skeleton.step"); + SoundEvent ENTITY_SKELETON_STEP = BuiltinSoundEvent.get("minecraft:entity.skeleton.step"); - SoundEvent ENTITY_SLIME_ATTACK = SoundEventImpl.get("minecraft:entity.slime.attack"); + SoundEvent ENTITY_SLIME_ATTACK = BuiltinSoundEvent.get("minecraft:entity.slime.attack"); - SoundEvent ENTITY_SLIME_DEATH = SoundEventImpl.get("minecraft:entity.slime.death"); + SoundEvent ENTITY_SLIME_DEATH = BuiltinSoundEvent.get("minecraft:entity.slime.death"); - SoundEvent ENTITY_SLIME_HURT = SoundEventImpl.get("minecraft:entity.slime.hurt"); + SoundEvent ENTITY_SLIME_HURT = BuiltinSoundEvent.get("minecraft:entity.slime.hurt"); - SoundEvent ENTITY_SLIME_JUMP = SoundEventImpl.get("minecraft:entity.slime.jump"); + SoundEvent ENTITY_SLIME_JUMP = BuiltinSoundEvent.get("minecraft:entity.slime.jump"); - SoundEvent ENTITY_SLIME_SQUISH = SoundEventImpl.get("minecraft:entity.slime.squish"); + SoundEvent ENTITY_SLIME_SQUISH = BuiltinSoundEvent.get("minecraft:entity.slime.squish"); - SoundEvent BLOCK_SLIME_BLOCK_BREAK = SoundEventImpl.get("minecraft:block.slime_block.break"); + SoundEvent BLOCK_SLIME_BLOCK_BREAK = BuiltinSoundEvent.get("minecraft:block.slime_block.break"); - SoundEvent BLOCK_SLIME_BLOCK_FALL = SoundEventImpl.get("minecraft:block.slime_block.fall"); + SoundEvent BLOCK_SLIME_BLOCK_FALL = BuiltinSoundEvent.get("minecraft:block.slime_block.fall"); - SoundEvent BLOCK_SLIME_BLOCK_HIT = SoundEventImpl.get("minecraft:block.slime_block.hit"); + SoundEvent BLOCK_SLIME_BLOCK_HIT = BuiltinSoundEvent.get("minecraft:block.slime_block.hit"); - SoundEvent BLOCK_SLIME_BLOCK_PLACE = SoundEventImpl.get("minecraft:block.slime_block.place"); + SoundEvent BLOCK_SLIME_BLOCK_PLACE = BuiltinSoundEvent.get("minecraft:block.slime_block.place"); - SoundEvent BLOCK_SLIME_BLOCK_STEP = SoundEventImpl.get("minecraft:block.slime_block.step"); + SoundEvent BLOCK_SLIME_BLOCK_STEP = BuiltinSoundEvent.get("minecraft:block.slime_block.step"); - SoundEvent BLOCK_SMALL_AMETHYST_BUD_BREAK = SoundEventImpl.get("minecraft:block.small_amethyst_bud.break"); + SoundEvent BLOCK_SMALL_AMETHYST_BUD_BREAK = BuiltinSoundEvent.get("minecraft:block.small_amethyst_bud.break"); - SoundEvent BLOCK_SMALL_AMETHYST_BUD_PLACE = SoundEventImpl.get("minecraft:block.small_amethyst_bud.place"); + SoundEvent BLOCK_SMALL_AMETHYST_BUD_PLACE = BuiltinSoundEvent.get("minecraft:block.small_amethyst_bud.place"); - SoundEvent BLOCK_SMALL_DRIPLEAF_BREAK = SoundEventImpl.get("minecraft:block.small_dripleaf.break"); + SoundEvent BLOCK_SMALL_DRIPLEAF_BREAK = BuiltinSoundEvent.get("minecraft:block.small_dripleaf.break"); - SoundEvent BLOCK_SMALL_DRIPLEAF_FALL = SoundEventImpl.get("minecraft:block.small_dripleaf.fall"); + SoundEvent BLOCK_SMALL_DRIPLEAF_FALL = BuiltinSoundEvent.get("minecraft:block.small_dripleaf.fall"); - SoundEvent BLOCK_SMALL_DRIPLEAF_HIT = SoundEventImpl.get("minecraft:block.small_dripleaf.hit"); + SoundEvent BLOCK_SMALL_DRIPLEAF_HIT = BuiltinSoundEvent.get("minecraft:block.small_dripleaf.hit"); - SoundEvent BLOCK_SMALL_DRIPLEAF_PLACE = SoundEventImpl.get("minecraft:block.small_dripleaf.place"); + SoundEvent BLOCK_SMALL_DRIPLEAF_PLACE = BuiltinSoundEvent.get("minecraft:block.small_dripleaf.place"); - SoundEvent BLOCK_SMALL_DRIPLEAF_STEP = SoundEventImpl.get("minecraft:block.small_dripleaf.step"); + SoundEvent BLOCK_SMALL_DRIPLEAF_STEP = BuiltinSoundEvent.get("minecraft:block.small_dripleaf.step"); - SoundEvent BLOCK_SOUL_SAND_BREAK = SoundEventImpl.get("minecraft:block.soul_sand.break"); + SoundEvent BLOCK_SOUL_SAND_BREAK = BuiltinSoundEvent.get("minecraft:block.soul_sand.break"); - SoundEvent BLOCK_SOUL_SAND_STEP = SoundEventImpl.get("minecraft:block.soul_sand.step"); + SoundEvent BLOCK_SOUL_SAND_STEP = BuiltinSoundEvent.get("minecraft:block.soul_sand.step"); - SoundEvent BLOCK_SOUL_SAND_PLACE = SoundEventImpl.get("minecraft:block.soul_sand.place"); + SoundEvent BLOCK_SOUL_SAND_PLACE = BuiltinSoundEvent.get("minecraft:block.soul_sand.place"); - SoundEvent BLOCK_SOUL_SAND_HIT = SoundEventImpl.get("minecraft:block.soul_sand.hit"); + SoundEvent BLOCK_SOUL_SAND_HIT = BuiltinSoundEvent.get("minecraft:block.soul_sand.hit"); - SoundEvent BLOCK_SOUL_SAND_FALL = SoundEventImpl.get("minecraft:block.soul_sand.fall"); + SoundEvent BLOCK_SOUL_SAND_FALL = BuiltinSoundEvent.get("minecraft:block.soul_sand.fall"); - SoundEvent BLOCK_SOUL_SOIL_BREAK = SoundEventImpl.get("minecraft:block.soul_soil.break"); + SoundEvent BLOCK_SOUL_SOIL_BREAK = BuiltinSoundEvent.get("minecraft:block.soul_soil.break"); - SoundEvent BLOCK_SOUL_SOIL_STEP = SoundEventImpl.get("minecraft:block.soul_soil.step"); + SoundEvent BLOCK_SOUL_SOIL_STEP = BuiltinSoundEvent.get("minecraft:block.soul_soil.step"); - SoundEvent BLOCK_SOUL_SOIL_PLACE = SoundEventImpl.get("minecraft:block.soul_soil.place"); + SoundEvent BLOCK_SOUL_SOIL_PLACE = BuiltinSoundEvent.get("minecraft:block.soul_soil.place"); - SoundEvent BLOCK_SOUL_SOIL_HIT = SoundEventImpl.get("minecraft:block.soul_soil.hit"); + SoundEvent BLOCK_SOUL_SOIL_HIT = BuiltinSoundEvent.get("minecraft:block.soul_soil.hit"); - SoundEvent BLOCK_SOUL_SOIL_FALL = SoundEventImpl.get("minecraft:block.soul_soil.fall"); + SoundEvent BLOCK_SOUL_SOIL_FALL = BuiltinSoundEvent.get("minecraft:block.soul_soil.fall"); - SoundEvent PARTICLE_SOUL_ESCAPE = SoundEventImpl.get("minecraft:particle.soul_escape"); + SoundEvent PARTICLE_SOUL_ESCAPE = BuiltinSoundEvent.get("minecraft:particle.soul_escape"); - SoundEvent BLOCK_SPORE_BLOSSOM_BREAK = SoundEventImpl.get("minecraft:block.spore_blossom.break"); + SoundEvent BLOCK_SPORE_BLOSSOM_BREAK = BuiltinSoundEvent.get("minecraft:block.spore_blossom.break"); - SoundEvent BLOCK_SPORE_BLOSSOM_FALL = SoundEventImpl.get("minecraft:block.spore_blossom.fall"); + SoundEvent BLOCK_SPORE_BLOSSOM_FALL = BuiltinSoundEvent.get("minecraft:block.spore_blossom.fall"); - SoundEvent BLOCK_SPORE_BLOSSOM_HIT = SoundEventImpl.get("minecraft:block.spore_blossom.hit"); + SoundEvent BLOCK_SPORE_BLOSSOM_HIT = BuiltinSoundEvent.get("minecraft:block.spore_blossom.hit"); - SoundEvent BLOCK_SPORE_BLOSSOM_PLACE = SoundEventImpl.get("minecraft:block.spore_blossom.place"); + SoundEvent BLOCK_SPORE_BLOSSOM_PLACE = BuiltinSoundEvent.get("minecraft:block.spore_blossom.place"); - SoundEvent BLOCK_SPORE_BLOSSOM_STEP = SoundEventImpl.get("minecraft:block.spore_blossom.step"); + SoundEvent BLOCK_SPORE_BLOSSOM_STEP = BuiltinSoundEvent.get("minecraft:block.spore_blossom.step"); - SoundEvent ENTITY_STRIDER_AMBIENT = SoundEventImpl.get("minecraft:entity.strider.ambient"); + SoundEvent ENTITY_STRIDER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.strider.ambient"); - SoundEvent ENTITY_STRIDER_HAPPY = SoundEventImpl.get("minecraft:entity.strider.happy"); + SoundEvent ENTITY_STRIDER_HAPPY = BuiltinSoundEvent.get("minecraft:entity.strider.happy"); - SoundEvent ENTITY_STRIDER_RETREAT = SoundEventImpl.get("minecraft:entity.strider.retreat"); + SoundEvent ENTITY_STRIDER_RETREAT = BuiltinSoundEvent.get("minecraft:entity.strider.retreat"); - SoundEvent ENTITY_STRIDER_DEATH = SoundEventImpl.get("minecraft:entity.strider.death"); + SoundEvent ENTITY_STRIDER_DEATH = BuiltinSoundEvent.get("minecraft:entity.strider.death"); - SoundEvent ENTITY_STRIDER_HURT = SoundEventImpl.get("minecraft:entity.strider.hurt"); + SoundEvent ENTITY_STRIDER_HURT = BuiltinSoundEvent.get("minecraft:entity.strider.hurt"); - SoundEvent ENTITY_STRIDER_STEP = SoundEventImpl.get("minecraft:entity.strider.step"); + SoundEvent ENTITY_STRIDER_STEP = BuiltinSoundEvent.get("minecraft:entity.strider.step"); - SoundEvent ENTITY_STRIDER_STEP_LAVA = SoundEventImpl.get("minecraft:entity.strider.step_lava"); + SoundEvent ENTITY_STRIDER_STEP_LAVA = BuiltinSoundEvent.get("minecraft:entity.strider.step_lava"); - SoundEvent ENTITY_STRIDER_EAT = SoundEventImpl.get("minecraft:entity.strider.eat"); + SoundEvent ENTITY_STRIDER_EAT = BuiltinSoundEvent.get("minecraft:entity.strider.eat"); - SoundEvent ENTITY_STRIDER_SADDLE = SoundEventImpl.get("minecraft:entity.strider.saddle"); + SoundEvent ENTITY_STRIDER_SADDLE = BuiltinSoundEvent.get("minecraft:entity.strider.saddle"); - SoundEvent ENTITY_SLIME_DEATH_SMALL = SoundEventImpl.get("minecraft:entity.slime.death_small"); + SoundEvent ENTITY_SLIME_DEATH_SMALL = BuiltinSoundEvent.get("minecraft:entity.slime.death_small"); - SoundEvent ENTITY_SLIME_HURT_SMALL = SoundEventImpl.get("minecraft:entity.slime.hurt_small"); + SoundEvent ENTITY_SLIME_HURT_SMALL = BuiltinSoundEvent.get("minecraft:entity.slime.hurt_small"); - SoundEvent ENTITY_SLIME_JUMP_SMALL = SoundEventImpl.get("minecraft:entity.slime.jump_small"); + SoundEvent ENTITY_SLIME_JUMP_SMALL = BuiltinSoundEvent.get("minecraft:entity.slime.jump_small"); - SoundEvent ENTITY_SLIME_SQUISH_SMALL = SoundEventImpl.get("minecraft:entity.slime.squish_small"); + SoundEvent ENTITY_SLIME_SQUISH_SMALL = BuiltinSoundEvent.get("minecraft:entity.slime.squish_small"); - SoundEvent BLOCK_SMITHING_TABLE_USE = SoundEventImpl.get("minecraft:block.smithing_table.use"); + SoundEvent BLOCK_SMITHING_TABLE_USE = BuiltinSoundEvent.get("minecraft:block.smithing_table.use"); - SoundEvent BLOCK_SMOKER_SMOKE = SoundEventImpl.get("minecraft:block.smoker.smoke"); + SoundEvent BLOCK_SMOKER_SMOKE = BuiltinSoundEvent.get("minecraft:block.smoker.smoke"); - SoundEvent ENTITY_SNIFFER_STEP = SoundEventImpl.get("minecraft:entity.sniffer.step"); + SoundEvent ENTITY_SNIFFER_STEP = BuiltinSoundEvent.get("minecraft:entity.sniffer.step"); - SoundEvent ENTITY_SNIFFER_EAT = SoundEventImpl.get("minecraft:entity.sniffer.eat"); + SoundEvent ENTITY_SNIFFER_EAT = BuiltinSoundEvent.get("minecraft:entity.sniffer.eat"); - SoundEvent ENTITY_SNIFFER_IDLE = SoundEventImpl.get("minecraft:entity.sniffer.idle"); + SoundEvent ENTITY_SNIFFER_IDLE = BuiltinSoundEvent.get("minecraft:entity.sniffer.idle"); - SoundEvent ENTITY_SNIFFER_HURT = SoundEventImpl.get("minecraft:entity.sniffer.hurt"); + SoundEvent ENTITY_SNIFFER_HURT = BuiltinSoundEvent.get("minecraft:entity.sniffer.hurt"); - SoundEvent ENTITY_SNIFFER_DEATH = SoundEventImpl.get("minecraft:entity.sniffer.death"); + SoundEvent ENTITY_SNIFFER_DEATH = BuiltinSoundEvent.get("minecraft:entity.sniffer.death"); - SoundEvent ENTITY_SNIFFER_DROP_SEED = SoundEventImpl.get("minecraft:entity.sniffer.drop_seed"); + SoundEvent ENTITY_SNIFFER_DROP_SEED = BuiltinSoundEvent.get("minecraft:entity.sniffer.drop_seed"); - SoundEvent ENTITY_SNIFFER_SCENTING = SoundEventImpl.get("minecraft:entity.sniffer.scenting"); + SoundEvent ENTITY_SNIFFER_SCENTING = BuiltinSoundEvent.get("minecraft:entity.sniffer.scenting"); - SoundEvent ENTITY_SNIFFER_SNIFFING = SoundEventImpl.get("minecraft:entity.sniffer.sniffing"); + SoundEvent ENTITY_SNIFFER_SNIFFING = BuiltinSoundEvent.get("minecraft:entity.sniffer.sniffing"); - SoundEvent ENTITY_SNIFFER_SEARCHING = SoundEventImpl.get("minecraft:entity.sniffer.searching"); + SoundEvent ENTITY_SNIFFER_SEARCHING = BuiltinSoundEvent.get("minecraft:entity.sniffer.searching"); - SoundEvent ENTITY_SNIFFER_DIGGING = SoundEventImpl.get("minecraft:entity.sniffer.digging"); + SoundEvent ENTITY_SNIFFER_DIGGING = BuiltinSoundEvent.get("minecraft:entity.sniffer.digging"); - SoundEvent ENTITY_SNIFFER_DIGGING_STOP = SoundEventImpl.get("minecraft:entity.sniffer.digging_stop"); + SoundEvent ENTITY_SNIFFER_DIGGING_STOP = BuiltinSoundEvent.get("minecraft:entity.sniffer.digging_stop"); - SoundEvent ENTITY_SNIFFER_HAPPY = SoundEventImpl.get("minecraft:entity.sniffer.happy"); + SoundEvent ENTITY_SNIFFER_HAPPY = BuiltinSoundEvent.get("minecraft:entity.sniffer.happy"); - SoundEvent BLOCK_SNIFFER_EGG_PLOP = SoundEventImpl.get("minecraft:block.sniffer_egg.plop"); + SoundEvent BLOCK_SNIFFER_EGG_PLOP = BuiltinSoundEvent.get("minecraft:block.sniffer_egg.plop"); - SoundEvent BLOCK_SNIFFER_EGG_CRACK = SoundEventImpl.get("minecraft:block.sniffer_egg.crack"); + SoundEvent BLOCK_SNIFFER_EGG_CRACK = BuiltinSoundEvent.get("minecraft:block.sniffer_egg.crack"); - SoundEvent BLOCK_SNIFFER_EGG_HATCH = SoundEventImpl.get("minecraft:block.sniffer_egg.hatch"); + SoundEvent BLOCK_SNIFFER_EGG_HATCH = BuiltinSoundEvent.get("minecraft:block.sniffer_egg.hatch"); - SoundEvent ENTITY_SNOWBALL_THROW = SoundEventImpl.get("minecraft:entity.snowball.throw"); + SoundEvent ENTITY_SNOWBALL_THROW = BuiltinSoundEvent.get("minecraft:entity.snowball.throw"); - SoundEvent BLOCK_SNOW_BREAK = SoundEventImpl.get("minecraft:block.snow.break"); + SoundEvent BLOCK_SNOW_BREAK = BuiltinSoundEvent.get("minecraft:block.snow.break"); - SoundEvent BLOCK_SNOW_FALL = SoundEventImpl.get("minecraft:block.snow.fall"); + SoundEvent BLOCK_SNOW_FALL = BuiltinSoundEvent.get("minecraft:block.snow.fall"); - SoundEvent ENTITY_SNOW_GOLEM_AMBIENT = SoundEventImpl.get("minecraft:entity.snow_golem.ambient"); + SoundEvent ENTITY_SNOW_GOLEM_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.snow_golem.ambient"); - SoundEvent ENTITY_SNOW_GOLEM_DEATH = SoundEventImpl.get("minecraft:entity.snow_golem.death"); + SoundEvent ENTITY_SNOW_GOLEM_DEATH = BuiltinSoundEvent.get("minecraft:entity.snow_golem.death"); - SoundEvent ENTITY_SNOW_GOLEM_HURT = SoundEventImpl.get("minecraft:entity.snow_golem.hurt"); + SoundEvent ENTITY_SNOW_GOLEM_HURT = BuiltinSoundEvent.get("minecraft:entity.snow_golem.hurt"); - SoundEvent ENTITY_SNOW_GOLEM_SHOOT = SoundEventImpl.get("minecraft:entity.snow_golem.shoot"); + SoundEvent ENTITY_SNOW_GOLEM_SHOOT = BuiltinSoundEvent.get("minecraft:entity.snow_golem.shoot"); - SoundEvent ENTITY_SNOW_GOLEM_SHEAR = SoundEventImpl.get("minecraft:entity.snow_golem.shear"); + SoundEvent ENTITY_SNOW_GOLEM_SHEAR = BuiltinSoundEvent.get("minecraft:entity.snow_golem.shear"); - SoundEvent BLOCK_SNOW_HIT = SoundEventImpl.get("minecraft:block.snow.hit"); + SoundEvent BLOCK_SNOW_HIT = BuiltinSoundEvent.get("minecraft:block.snow.hit"); - SoundEvent BLOCK_SNOW_PLACE = SoundEventImpl.get("minecraft:block.snow.place"); + SoundEvent BLOCK_SNOW_PLACE = BuiltinSoundEvent.get("minecraft:block.snow.place"); - SoundEvent BLOCK_SNOW_STEP = SoundEventImpl.get("minecraft:block.snow.step"); + SoundEvent BLOCK_SNOW_STEP = BuiltinSoundEvent.get("minecraft:block.snow.step"); - SoundEvent ENTITY_SPIDER_AMBIENT = SoundEventImpl.get("minecraft:entity.spider.ambient"); + SoundEvent ENTITY_SPIDER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.spider.ambient"); - SoundEvent ENTITY_SPIDER_DEATH = SoundEventImpl.get("minecraft:entity.spider.death"); + SoundEvent ENTITY_SPIDER_DEATH = BuiltinSoundEvent.get("minecraft:entity.spider.death"); - SoundEvent ENTITY_SPIDER_HURT = SoundEventImpl.get("minecraft:entity.spider.hurt"); + SoundEvent ENTITY_SPIDER_HURT = BuiltinSoundEvent.get("minecraft:entity.spider.hurt"); - SoundEvent ENTITY_SPIDER_STEP = SoundEventImpl.get("minecraft:entity.spider.step"); + SoundEvent ENTITY_SPIDER_STEP = BuiltinSoundEvent.get("minecraft:entity.spider.step"); - SoundEvent ENTITY_SPLASH_POTION_BREAK = SoundEventImpl.get("minecraft:entity.splash_potion.break"); + SoundEvent ENTITY_SPLASH_POTION_BREAK = BuiltinSoundEvent.get("minecraft:entity.splash_potion.break"); - SoundEvent ENTITY_SPLASH_POTION_THROW = SoundEventImpl.get("minecraft:entity.splash_potion.throw"); + SoundEvent ENTITY_SPLASH_POTION_THROW = BuiltinSoundEvent.get("minecraft:entity.splash_potion.throw"); - SoundEvent BLOCK_SPONGE_BREAK = SoundEventImpl.get("minecraft:block.sponge.break"); + SoundEvent BLOCK_SPONGE_BREAK = BuiltinSoundEvent.get("minecraft:block.sponge.break"); - SoundEvent BLOCK_SPONGE_FALL = SoundEventImpl.get("minecraft:block.sponge.fall"); + SoundEvent BLOCK_SPONGE_FALL = BuiltinSoundEvent.get("minecraft:block.sponge.fall"); - SoundEvent BLOCK_SPONGE_HIT = SoundEventImpl.get("minecraft:block.sponge.hit"); + SoundEvent BLOCK_SPONGE_HIT = BuiltinSoundEvent.get("minecraft:block.sponge.hit"); - SoundEvent BLOCK_SPONGE_PLACE = SoundEventImpl.get("minecraft:block.sponge.place"); + SoundEvent BLOCK_SPONGE_PLACE = BuiltinSoundEvent.get("minecraft:block.sponge.place"); - SoundEvent BLOCK_SPONGE_STEP = SoundEventImpl.get("minecraft:block.sponge.step"); + SoundEvent BLOCK_SPONGE_STEP = BuiltinSoundEvent.get("minecraft:block.sponge.step"); - SoundEvent BLOCK_SPONGE_ABSORB = SoundEventImpl.get("minecraft:block.sponge.absorb"); + SoundEvent BLOCK_SPONGE_ABSORB = BuiltinSoundEvent.get("minecraft:block.sponge.absorb"); - SoundEvent ITEM_SPYGLASS_USE = SoundEventImpl.get("minecraft:item.spyglass.use"); + SoundEvent ITEM_SPYGLASS_USE = BuiltinSoundEvent.get("minecraft:item.spyglass.use"); - SoundEvent ITEM_SPYGLASS_STOP_USING = SoundEventImpl.get("minecraft:item.spyglass.stop_using"); + SoundEvent ITEM_SPYGLASS_STOP_USING = BuiltinSoundEvent.get("minecraft:item.spyglass.stop_using"); - SoundEvent ENTITY_SQUID_AMBIENT = SoundEventImpl.get("minecraft:entity.squid.ambient"); + SoundEvent ENTITY_SQUID_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.squid.ambient"); - SoundEvent ENTITY_SQUID_DEATH = SoundEventImpl.get("minecraft:entity.squid.death"); + SoundEvent ENTITY_SQUID_DEATH = BuiltinSoundEvent.get("minecraft:entity.squid.death"); - SoundEvent ENTITY_SQUID_HURT = SoundEventImpl.get("minecraft:entity.squid.hurt"); + SoundEvent ENTITY_SQUID_HURT = BuiltinSoundEvent.get("minecraft:entity.squid.hurt"); - SoundEvent ENTITY_SQUID_SQUIRT = SoundEventImpl.get("minecraft:entity.squid.squirt"); + SoundEvent ENTITY_SQUID_SQUIRT = BuiltinSoundEvent.get("minecraft:entity.squid.squirt"); - SoundEvent BLOCK_STONE_BREAK = SoundEventImpl.get("minecraft:block.stone.break"); + SoundEvent BLOCK_STONE_BREAK = BuiltinSoundEvent.get("minecraft:block.stone.break"); - SoundEvent BLOCK_STONE_BUTTON_CLICK_OFF = SoundEventImpl.get("minecraft:block.stone_button.click_off"); + SoundEvent BLOCK_STONE_BUTTON_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.stone_button.click_off"); - SoundEvent BLOCK_STONE_BUTTON_CLICK_ON = SoundEventImpl.get("minecraft:block.stone_button.click_on"); + SoundEvent BLOCK_STONE_BUTTON_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.stone_button.click_on"); - SoundEvent BLOCK_STONE_FALL = SoundEventImpl.get("minecraft:block.stone.fall"); + SoundEvent BLOCK_STONE_FALL = BuiltinSoundEvent.get("minecraft:block.stone.fall"); - SoundEvent BLOCK_STONE_HIT = SoundEventImpl.get("minecraft:block.stone.hit"); + SoundEvent BLOCK_STONE_HIT = BuiltinSoundEvent.get("minecraft:block.stone.hit"); - SoundEvent BLOCK_STONE_PLACE = SoundEventImpl.get("minecraft:block.stone.place"); + SoundEvent BLOCK_STONE_PLACE = BuiltinSoundEvent.get("minecraft:block.stone.place"); - SoundEvent BLOCK_STONE_PRESSURE_PLATE_CLICK_OFF = SoundEventImpl.get("minecraft:block.stone_pressure_plate.click_off"); + SoundEvent BLOCK_STONE_PRESSURE_PLATE_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.stone_pressure_plate.click_off"); - SoundEvent BLOCK_STONE_PRESSURE_PLATE_CLICK_ON = SoundEventImpl.get("minecraft:block.stone_pressure_plate.click_on"); + SoundEvent BLOCK_STONE_PRESSURE_PLATE_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.stone_pressure_plate.click_on"); - SoundEvent BLOCK_STONE_STEP = SoundEventImpl.get("minecraft:block.stone.step"); + SoundEvent BLOCK_STONE_STEP = BuiltinSoundEvent.get("minecraft:block.stone.step"); - SoundEvent ENTITY_STRAY_AMBIENT = SoundEventImpl.get("minecraft:entity.stray.ambient"); + SoundEvent ENTITY_STRAY_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.stray.ambient"); - SoundEvent ENTITY_STRAY_DEATH = SoundEventImpl.get("minecraft:entity.stray.death"); + SoundEvent ENTITY_STRAY_DEATH = BuiltinSoundEvent.get("minecraft:entity.stray.death"); - SoundEvent ENTITY_STRAY_HURT = SoundEventImpl.get("minecraft:entity.stray.hurt"); + SoundEvent ENTITY_STRAY_HURT = BuiltinSoundEvent.get("minecraft:entity.stray.hurt"); - SoundEvent ENTITY_STRAY_STEP = SoundEventImpl.get("minecraft:entity.stray.step"); + SoundEvent ENTITY_STRAY_STEP = BuiltinSoundEvent.get("minecraft:entity.stray.step"); - SoundEvent BLOCK_SWEET_BERRY_BUSH_BREAK = SoundEventImpl.get("minecraft:block.sweet_berry_bush.break"); + SoundEvent BLOCK_SWEET_BERRY_BUSH_BREAK = BuiltinSoundEvent.get("minecraft:block.sweet_berry_bush.break"); - SoundEvent BLOCK_SWEET_BERRY_BUSH_PLACE = SoundEventImpl.get("minecraft:block.sweet_berry_bush.place"); + SoundEvent BLOCK_SWEET_BERRY_BUSH_PLACE = BuiltinSoundEvent.get("minecraft:block.sweet_berry_bush.place"); - SoundEvent BLOCK_SWEET_BERRY_BUSH_PICK_BERRIES = SoundEventImpl.get("minecraft:block.sweet_berry_bush.pick_berries"); + SoundEvent BLOCK_SWEET_BERRY_BUSH_PICK_BERRIES = BuiltinSoundEvent.get("minecraft:block.sweet_berry_bush.pick_berries"); - SoundEvent ENTITY_TADPOLE_DEATH = SoundEventImpl.get("minecraft:entity.tadpole.death"); + SoundEvent ENTITY_TADPOLE_DEATH = BuiltinSoundEvent.get("minecraft:entity.tadpole.death"); - SoundEvent ENTITY_TADPOLE_FLOP = SoundEventImpl.get("minecraft:entity.tadpole.flop"); + SoundEvent ENTITY_TADPOLE_FLOP = BuiltinSoundEvent.get("minecraft:entity.tadpole.flop"); - SoundEvent ENTITY_TADPOLE_GROW_UP = SoundEventImpl.get("minecraft:entity.tadpole.grow_up"); + SoundEvent ENTITY_TADPOLE_GROW_UP = BuiltinSoundEvent.get("minecraft:entity.tadpole.grow_up"); - SoundEvent ENTITY_TADPOLE_HURT = SoundEventImpl.get("minecraft:entity.tadpole.hurt"); + SoundEvent ENTITY_TADPOLE_HURT = BuiltinSoundEvent.get("minecraft:entity.tadpole.hurt"); - SoundEvent ENCHANT_THORNS_HIT = SoundEventImpl.get("minecraft:enchant.thorns.hit"); + SoundEvent ENCHANT_THORNS_HIT = BuiltinSoundEvent.get("minecraft:enchant.thorns.hit"); - SoundEvent ENTITY_TNT_PRIMED = SoundEventImpl.get("minecraft:entity.tnt.primed"); + SoundEvent ENTITY_TNT_PRIMED = BuiltinSoundEvent.get("minecraft:entity.tnt.primed"); - SoundEvent ITEM_TOTEM_USE = SoundEventImpl.get("minecraft:item.totem.use"); + SoundEvent ITEM_TOTEM_USE = BuiltinSoundEvent.get("minecraft:item.totem.use"); - SoundEvent ITEM_TRIDENT_HIT = SoundEventImpl.get("minecraft:item.trident.hit"); + SoundEvent ITEM_TRIDENT_HIT = BuiltinSoundEvent.get("minecraft:item.trident.hit"); - SoundEvent ITEM_TRIDENT_HIT_GROUND = SoundEventImpl.get("minecraft:item.trident.hit_ground"); + SoundEvent ITEM_TRIDENT_HIT_GROUND = BuiltinSoundEvent.get("minecraft:item.trident.hit_ground"); - SoundEvent ITEM_TRIDENT_RETURN = SoundEventImpl.get("minecraft:item.trident.return"); + SoundEvent ITEM_TRIDENT_RETURN = BuiltinSoundEvent.get("minecraft:item.trident.return"); - SoundEvent ITEM_TRIDENT_RIPTIDE_1 = SoundEventImpl.get("minecraft:item.trident.riptide_1"); + SoundEvent ITEM_TRIDENT_RIPTIDE_1 = BuiltinSoundEvent.get("minecraft:item.trident.riptide_1"); - SoundEvent ITEM_TRIDENT_RIPTIDE_2 = SoundEventImpl.get("minecraft:item.trident.riptide_2"); + SoundEvent ITEM_TRIDENT_RIPTIDE_2 = BuiltinSoundEvent.get("minecraft:item.trident.riptide_2"); - SoundEvent ITEM_TRIDENT_RIPTIDE_3 = SoundEventImpl.get("minecraft:item.trident.riptide_3"); + SoundEvent ITEM_TRIDENT_RIPTIDE_3 = BuiltinSoundEvent.get("minecraft:item.trident.riptide_3"); - SoundEvent ITEM_TRIDENT_THROW = SoundEventImpl.get("minecraft:item.trident.throw"); + SoundEvent ITEM_TRIDENT_THROW = BuiltinSoundEvent.get("minecraft:item.trident.throw"); - SoundEvent ITEM_TRIDENT_THUNDER = SoundEventImpl.get("minecraft:item.trident.thunder"); + SoundEvent ITEM_TRIDENT_THUNDER = BuiltinSoundEvent.get("minecraft:item.trident.thunder"); - SoundEvent BLOCK_TRIPWIRE_ATTACH = SoundEventImpl.get("minecraft:block.tripwire.attach"); + SoundEvent BLOCK_TRIPWIRE_ATTACH = BuiltinSoundEvent.get("minecraft:block.tripwire.attach"); - SoundEvent BLOCK_TRIPWIRE_CLICK_OFF = SoundEventImpl.get("minecraft:block.tripwire.click_off"); + SoundEvent BLOCK_TRIPWIRE_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.tripwire.click_off"); - SoundEvent BLOCK_TRIPWIRE_CLICK_ON = SoundEventImpl.get("minecraft:block.tripwire.click_on"); + SoundEvent BLOCK_TRIPWIRE_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.tripwire.click_on"); - SoundEvent BLOCK_TRIPWIRE_DETACH = SoundEventImpl.get("minecraft:block.tripwire.detach"); + SoundEvent BLOCK_TRIPWIRE_DETACH = BuiltinSoundEvent.get("minecraft:block.tripwire.detach"); - SoundEvent ENTITY_TROPICAL_FISH_AMBIENT = SoundEventImpl.get("minecraft:entity.tropical_fish.ambient"); + SoundEvent ENTITY_TROPICAL_FISH_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.tropical_fish.ambient"); - SoundEvent ENTITY_TROPICAL_FISH_DEATH = SoundEventImpl.get("minecraft:entity.tropical_fish.death"); + SoundEvent ENTITY_TROPICAL_FISH_DEATH = BuiltinSoundEvent.get("minecraft:entity.tropical_fish.death"); - SoundEvent ENTITY_TROPICAL_FISH_FLOP = SoundEventImpl.get("minecraft:entity.tropical_fish.flop"); + SoundEvent ENTITY_TROPICAL_FISH_FLOP = BuiltinSoundEvent.get("minecraft:entity.tropical_fish.flop"); - SoundEvent ENTITY_TROPICAL_FISH_HURT = SoundEventImpl.get("minecraft:entity.tropical_fish.hurt"); + SoundEvent ENTITY_TROPICAL_FISH_HURT = BuiltinSoundEvent.get("minecraft:entity.tropical_fish.hurt"); - SoundEvent BLOCK_TUFF_BREAK = SoundEventImpl.get("minecraft:block.tuff.break"); + SoundEvent BLOCK_TUFF_BREAK = BuiltinSoundEvent.get("minecraft:block.tuff.break"); - SoundEvent BLOCK_TUFF_STEP = SoundEventImpl.get("minecraft:block.tuff.step"); + SoundEvent BLOCK_TUFF_STEP = BuiltinSoundEvent.get("minecraft:block.tuff.step"); - SoundEvent BLOCK_TUFF_PLACE = SoundEventImpl.get("minecraft:block.tuff.place"); + SoundEvent BLOCK_TUFF_PLACE = BuiltinSoundEvent.get("minecraft:block.tuff.place"); - SoundEvent BLOCK_TUFF_HIT = SoundEventImpl.get("minecraft:block.tuff.hit"); + SoundEvent BLOCK_TUFF_HIT = BuiltinSoundEvent.get("minecraft:block.tuff.hit"); - SoundEvent BLOCK_TUFF_FALL = SoundEventImpl.get("minecraft:block.tuff.fall"); + SoundEvent BLOCK_TUFF_FALL = BuiltinSoundEvent.get("minecraft:block.tuff.fall"); - SoundEvent BLOCK_TUFF_BRICKS_BREAK = SoundEventImpl.get("minecraft:block.tuff_bricks.break"); + SoundEvent BLOCK_TUFF_BRICKS_BREAK = BuiltinSoundEvent.get("minecraft:block.tuff_bricks.break"); - SoundEvent BLOCK_TUFF_BRICKS_FALL = SoundEventImpl.get("minecraft:block.tuff_bricks.fall"); + SoundEvent BLOCK_TUFF_BRICKS_FALL = BuiltinSoundEvent.get("minecraft:block.tuff_bricks.fall"); - SoundEvent BLOCK_TUFF_BRICKS_HIT = SoundEventImpl.get("minecraft:block.tuff_bricks.hit"); + SoundEvent BLOCK_TUFF_BRICKS_HIT = BuiltinSoundEvent.get("minecraft:block.tuff_bricks.hit"); - SoundEvent BLOCK_TUFF_BRICKS_PLACE = SoundEventImpl.get("minecraft:block.tuff_bricks.place"); + SoundEvent BLOCK_TUFF_BRICKS_PLACE = BuiltinSoundEvent.get("minecraft:block.tuff_bricks.place"); - SoundEvent BLOCK_TUFF_BRICKS_STEP = SoundEventImpl.get("minecraft:block.tuff_bricks.step"); + SoundEvent BLOCK_TUFF_BRICKS_STEP = BuiltinSoundEvent.get("minecraft:block.tuff_bricks.step"); - SoundEvent BLOCK_POLISHED_TUFF_BREAK = SoundEventImpl.get("minecraft:block.polished_tuff.break"); + SoundEvent BLOCK_POLISHED_TUFF_BREAK = BuiltinSoundEvent.get("minecraft:block.polished_tuff.break"); - SoundEvent BLOCK_POLISHED_TUFF_FALL = SoundEventImpl.get("minecraft:block.polished_tuff.fall"); + SoundEvent BLOCK_POLISHED_TUFF_FALL = BuiltinSoundEvent.get("minecraft:block.polished_tuff.fall"); - SoundEvent BLOCK_POLISHED_TUFF_HIT = SoundEventImpl.get("minecraft:block.polished_tuff.hit"); + SoundEvent BLOCK_POLISHED_TUFF_HIT = BuiltinSoundEvent.get("minecraft:block.polished_tuff.hit"); - SoundEvent BLOCK_POLISHED_TUFF_PLACE = SoundEventImpl.get("minecraft:block.polished_tuff.place"); + SoundEvent BLOCK_POLISHED_TUFF_PLACE = BuiltinSoundEvent.get("minecraft:block.polished_tuff.place"); - SoundEvent BLOCK_POLISHED_TUFF_STEP = SoundEventImpl.get("minecraft:block.polished_tuff.step"); + SoundEvent BLOCK_POLISHED_TUFF_STEP = BuiltinSoundEvent.get("minecraft:block.polished_tuff.step"); - SoundEvent ENTITY_TURTLE_AMBIENT_LAND = SoundEventImpl.get("minecraft:entity.turtle.ambient_land"); + SoundEvent ENTITY_TURTLE_AMBIENT_LAND = BuiltinSoundEvent.get("minecraft:entity.turtle.ambient_land"); - SoundEvent ENTITY_TURTLE_DEATH = SoundEventImpl.get("minecraft:entity.turtle.death"); + SoundEvent ENTITY_TURTLE_DEATH = BuiltinSoundEvent.get("minecraft:entity.turtle.death"); - SoundEvent ENTITY_TURTLE_DEATH_BABY = SoundEventImpl.get("minecraft:entity.turtle.death_baby"); + SoundEvent ENTITY_TURTLE_DEATH_BABY = BuiltinSoundEvent.get("minecraft:entity.turtle.death_baby"); - SoundEvent ENTITY_TURTLE_EGG_BREAK = SoundEventImpl.get("minecraft:entity.turtle.egg_break"); + SoundEvent ENTITY_TURTLE_EGG_BREAK = BuiltinSoundEvent.get("minecraft:entity.turtle.egg_break"); - SoundEvent ENTITY_TURTLE_EGG_CRACK = SoundEventImpl.get("minecraft:entity.turtle.egg_crack"); + SoundEvent ENTITY_TURTLE_EGG_CRACK = BuiltinSoundEvent.get("minecraft:entity.turtle.egg_crack"); - SoundEvent ENTITY_TURTLE_EGG_HATCH = SoundEventImpl.get("minecraft:entity.turtle.egg_hatch"); + SoundEvent ENTITY_TURTLE_EGG_HATCH = BuiltinSoundEvent.get("minecraft:entity.turtle.egg_hatch"); - SoundEvent ENTITY_TURTLE_HURT = SoundEventImpl.get("minecraft:entity.turtle.hurt"); + SoundEvent ENTITY_TURTLE_HURT = BuiltinSoundEvent.get("minecraft:entity.turtle.hurt"); - SoundEvent ENTITY_TURTLE_HURT_BABY = SoundEventImpl.get("minecraft:entity.turtle.hurt_baby"); + SoundEvent ENTITY_TURTLE_HURT_BABY = BuiltinSoundEvent.get("minecraft:entity.turtle.hurt_baby"); - SoundEvent ENTITY_TURTLE_LAY_EGG = SoundEventImpl.get("minecraft:entity.turtle.lay_egg"); + SoundEvent ENTITY_TURTLE_LAY_EGG = BuiltinSoundEvent.get("minecraft:entity.turtle.lay_egg"); - SoundEvent ENTITY_TURTLE_SHAMBLE = SoundEventImpl.get("minecraft:entity.turtle.shamble"); + SoundEvent ENTITY_TURTLE_SHAMBLE = BuiltinSoundEvent.get("minecraft:entity.turtle.shamble"); - SoundEvent ENTITY_TURTLE_SHAMBLE_BABY = SoundEventImpl.get("minecraft:entity.turtle.shamble_baby"); + SoundEvent ENTITY_TURTLE_SHAMBLE_BABY = BuiltinSoundEvent.get("minecraft:entity.turtle.shamble_baby"); - SoundEvent ENTITY_TURTLE_SWIM = SoundEventImpl.get("minecraft:entity.turtle.swim"); + SoundEvent ENTITY_TURTLE_SWIM = BuiltinSoundEvent.get("minecraft:entity.turtle.swim"); - SoundEvent UI_BUTTON_CLICK = SoundEventImpl.get("minecraft:ui.button.click"); + SoundEvent UI_BUTTON_CLICK = BuiltinSoundEvent.get("minecraft:ui.button.click"); - SoundEvent UI_LOOM_SELECT_PATTERN = SoundEventImpl.get("minecraft:ui.loom.select_pattern"); + SoundEvent UI_LOOM_SELECT_PATTERN = BuiltinSoundEvent.get("minecraft:ui.loom.select_pattern"); - SoundEvent UI_LOOM_TAKE_RESULT = SoundEventImpl.get("minecraft:ui.loom.take_result"); + SoundEvent UI_LOOM_TAKE_RESULT = BuiltinSoundEvent.get("minecraft:ui.loom.take_result"); - SoundEvent UI_CARTOGRAPHY_TABLE_TAKE_RESULT = SoundEventImpl.get("minecraft:ui.cartography_table.take_result"); + SoundEvent UI_CARTOGRAPHY_TABLE_TAKE_RESULT = BuiltinSoundEvent.get("minecraft:ui.cartography_table.take_result"); - SoundEvent UI_STONECUTTER_TAKE_RESULT = SoundEventImpl.get("minecraft:ui.stonecutter.take_result"); + SoundEvent UI_STONECUTTER_TAKE_RESULT = BuiltinSoundEvent.get("minecraft:ui.stonecutter.take_result"); - SoundEvent UI_STONECUTTER_SELECT_RECIPE = SoundEventImpl.get("minecraft:ui.stonecutter.select_recipe"); + SoundEvent UI_STONECUTTER_SELECT_RECIPE = BuiltinSoundEvent.get("minecraft:ui.stonecutter.select_recipe"); - SoundEvent UI_TOAST_CHALLENGE_COMPLETE = SoundEventImpl.get("minecraft:ui.toast.challenge_complete"); + SoundEvent UI_TOAST_CHALLENGE_COMPLETE = BuiltinSoundEvent.get("minecraft:ui.toast.challenge_complete"); - SoundEvent UI_TOAST_IN = SoundEventImpl.get("minecraft:ui.toast.in"); + SoundEvent UI_TOAST_IN = BuiltinSoundEvent.get("minecraft:ui.toast.in"); - SoundEvent UI_TOAST_OUT = SoundEventImpl.get("minecraft:ui.toast.out"); + SoundEvent UI_TOAST_OUT = BuiltinSoundEvent.get("minecraft:ui.toast.out"); - SoundEvent BLOCK_VAULT_ACTIVATE = SoundEventImpl.get("minecraft:block.vault.activate"); + SoundEvent BLOCK_VAULT_ACTIVATE = BuiltinSoundEvent.get("minecraft:block.vault.activate"); - SoundEvent BLOCK_VAULT_AMBIENT = SoundEventImpl.get("minecraft:block.vault.ambient"); + SoundEvent BLOCK_VAULT_AMBIENT = BuiltinSoundEvent.get("minecraft:block.vault.ambient"); - SoundEvent BLOCK_VAULT_BREAK = SoundEventImpl.get("minecraft:block.vault.break"); + SoundEvent BLOCK_VAULT_BREAK = BuiltinSoundEvent.get("minecraft:block.vault.break"); - SoundEvent BLOCK_VAULT_CLOSE_SHUTTER = SoundEventImpl.get("minecraft:block.vault.close_shutter"); + SoundEvent BLOCK_VAULT_CLOSE_SHUTTER = BuiltinSoundEvent.get("minecraft:block.vault.close_shutter"); - SoundEvent BLOCK_VAULT_DEACTIVATE = SoundEventImpl.get("minecraft:block.vault.deactivate"); + SoundEvent BLOCK_VAULT_DEACTIVATE = BuiltinSoundEvent.get("minecraft:block.vault.deactivate"); - SoundEvent BLOCK_VAULT_EJECT_ITEM = SoundEventImpl.get("minecraft:block.vault.eject_item"); + SoundEvent BLOCK_VAULT_EJECT_ITEM = BuiltinSoundEvent.get("minecraft:block.vault.eject_item"); - SoundEvent BLOCK_VAULT_FALL = SoundEventImpl.get("minecraft:block.vault.fall"); + SoundEvent BLOCK_VAULT_FALL = BuiltinSoundEvent.get("minecraft:block.vault.fall"); - SoundEvent BLOCK_VAULT_HIT = SoundEventImpl.get("minecraft:block.vault.hit"); + SoundEvent BLOCK_VAULT_HIT = BuiltinSoundEvent.get("minecraft:block.vault.hit"); - SoundEvent BLOCK_VAULT_INSERT_ITEM = SoundEventImpl.get("minecraft:block.vault.insert_item"); + SoundEvent BLOCK_VAULT_INSERT_ITEM = BuiltinSoundEvent.get("minecraft:block.vault.insert_item"); - SoundEvent BLOCK_VAULT_INSERT_ITEM_FAIL = SoundEventImpl.get("minecraft:block.vault.insert_item_fail"); + SoundEvent BLOCK_VAULT_INSERT_ITEM_FAIL = BuiltinSoundEvent.get("minecraft:block.vault.insert_item_fail"); - SoundEvent BLOCK_VAULT_OPEN_SHUTTER = SoundEventImpl.get("minecraft:block.vault.open_shutter"); + SoundEvent BLOCK_VAULT_OPEN_SHUTTER = BuiltinSoundEvent.get("minecraft:block.vault.open_shutter"); - SoundEvent BLOCK_VAULT_PLACE = SoundEventImpl.get("minecraft:block.vault.place"); + SoundEvent BLOCK_VAULT_PLACE = BuiltinSoundEvent.get("minecraft:block.vault.place"); - SoundEvent BLOCK_VAULT_STEP = SoundEventImpl.get("minecraft:block.vault.step"); + SoundEvent BLOCK_VAULT_STEP = BuiltinSoundEvent.get("minecraft:block.vault.step"); - SoundEvent ENTITY_VEX_AMBIENT = SoundEventImpl.get("minecraft:entity.vex.ambient"); + SoundEvent ENTITY_VEX_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.vex.ambient"); - SoundEvent ENTITY_VEX_CHARGE = SoundEventImpl.get("minecraft:entity.vex.charge"); + SoundEvent ENTITY_VEX_CHARGE = BuiltinSoundEvent.get("minecraft:entity.vex.charge"); - SoundEvent ENTITY_VEX_DEATH = SoundEventImpl.get("minecraft:entity.vex.death"); + SoundEvent ENTITY_VEX_DEATH = BuiltinSoundEvent.get("minecraft:entity.vex.death"); - SoundEvent ENTITY_VEX_HURT = SoundEventImpl.get("minecraft:entity.vex.hurt"); + SoundEvent ENTITY_VEX_HURT = BuiltinSoundEvent.get("minecraft:entity.vex.hurt"); - SoundEvent ENTITY_VILLAGER_AMBIENT = SoundEventImpl.get("minecraft:entity.villager.ambient"); + SoundEvent ENTITY_VILLAGER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.villager.ambient"); - SoundEvent ENTITY_VILLAGER_CELEBRATE = SoundEventImpl.get("minecraft:entity.villager.celebrate"); + SoundEvent ENTITY_VILLAGER_CELEBRATE = BuiltinSoundEvent.get("minecraft:entity.villager.celebrate"); - SoundEvent ENTITY_VILLAGER_DEATH = SoundEventImpl.get("minecraft:entity.villager.death"); + SoundEvent ENTITY_VILLAGER_DEATH = BuiltinSoundEvent.get("minecraft:entity.villager.death"); - SoundEvent ENTITY_VILLAGER_HURT = SoundEventImpl.get("minecraft:entity.villager.hurt"); + SoundEvent ENTITY_VILLAGER_HURT = BuiltinSoundEvent.get("minecraft:entity.villager.hurt"); - SoundEvent ENTITY_VILLAGER_NO = SoundEventImpl.get("minecraft:entity.villager.no"); + SoundEvent ENTITY_VILLAGER_NO = BuiltinSoundEvent.get("minecraft:entity.villager.no"); - SoundEvent ENTITY_VILLAGER_TRADE = SoundEventImpl.get("minecraft:entity.villager.trade"); + SoundEvent ENTITY_VILLAGER_TRADE = BuiltinSoundEvent.get("minecraft:entity.villager.trade"); - SoundEvent ENTITY_VILLAGER_YES = SoundEventImpl.get("minecraft:entity.villager.yes"); + SoundEvent ENTITY_VILLAGER_YES = BuiltinSoundEvent.get("minecraft:entity.villager.yes"); - SoundEvent ENTITY_VILLAGER_WORK_ARMORER = SoundEventImpl.get("minecraft:entity.villager.work_armorer"); + SoundEvent ENTITY_VILLAGER_WORK_ARMORER = BuiltinSoundEvent.get("minecraft:entity.villager.work_armorer"); - SoundEvent ENTITY_VILLAGER_WORK_BUTCHER = SoundEventImpl.get("minecraft:entity.villager.work_butcher"); + SoundEvent ENTITY_VILLAGER_WORK_BUTCHER = BuiltinSoundEvent.get("minecraft:entity.villager.work_butcher"); - SoundEvent ENTITY_VILLAGER_WORK_CARTOGRAPHER = SoundEventImpl.get("minecraft:entity.villager.work_cartographer"); + SoundEvent ENTITY_VILLAGER_WORK_CARTOGRAPHER = BuiltinSoundEvent.get("minecraft:entity.villager.work_cartographer"); - SoundEvent ENTITY_VILLAGER_WORK_CLERIC = SoundEventImpl.get("minecraft:entity.villager.work_cleric"); + SoundEvent ENTITY_VILLAGER_WORK_CLERIC = BuiltinSoundEvent.get("minecraft:entity.villager.work_cleric"); - SoundEvent ENTITY_VILLAGER_WORK_FARMER = SoundEventImpl.get("minecraft:entity.villager.work_farmer"); + SoundEvent ENTITY_VILLAGER_WORK_FARMER = BuiltinSoundEvent.get("minecraft:entity.villager.work_farmer"); - SoundEvent ENTITY_VILLAGER_WORK_FISHERMAN = SoundEventImpl.get("minecraft:entity.villager.work_fisherman"); + SoundEvent ENTITY_VILLAGER_WORK_FISHERMAN = BuiltinSoundEvent.get("minecraft:entity.villager.work_fisherman"); - SoundEvent ENTITY_VILLAGER_WORK_FLETCHER = SoundEventImpl.get("minecraft:entity.villager.work_fletcher"); + SoundEvent ENTITY_VILLAGER_WORK_FLETCHER = BuiltinSoundEvent.get("minecraft:entity.villager.work_fletcher"); - SoundEvent ENTITY_VILLAGER_WORK_LEATHERWORKER = SoundEventImpl.get("minecraft:entity.villager.work_leatherworker"); + SoundEvent ENTITY_VILLAGER_WORK_LEATHERWORKER = BuiltinSoundEvent.get("minecraft:entity.villager.work_leatherworker"); - SoundEvent ENTITY_VILLAGER_WORK_LIBRARIAN = SoundEventImpl.get("minecraft:entity.villager.work_librarian"); + SoundEvent ENTITY_VILLAGER_WORK_LIBRARIAN = BuiltinSoundEvent.get("minecraft:entity.villager.work_librarian"); - SoundEvent ENTITY_VILLAGER_WORK_MASON = SoundEventImpl.get("minecraft:entity.villager.work_mason"); + SoundEvent ENTITY_VILLAGER_WORK_MASON = BuiltinSoundEvent.get("minecraft:entity.villager.work_mason"); - SoundEvent ENTITY_VILLAGER_WORK_SHEPHERD = SoundEventImpl.get("minecraft:entity.villager.work_shepherd"); + SoundEvent ENTITY_VILLAGER_WORK_SHEPHERD = BuiltinSoundEvent.get("minecraft:entity.villager.work_shepherd"); - SoundEvent ENTITY_VILLAGER_WORK_TOOLSMITH = SoundEventImpl.get("minecraft:entity.villager.work_toolsmith"); + SoundEvent ENTITY_VILLAGER_WORK_TOOLSMITH = BuiltinSoundEvent.get("minecraft:entity.villager.work_toolsmith"); - SoundEvent ENTITY_VILLAGER_WORK_WEAPONSMITH = SoundEventImpl.get("minecraft:entity.villager.work_weaponsmith"); + SoundEvent ENTITY_VILLAGER_WORK_WEAPONSMITH = BuiltinSoundEvent.get("minecraft:entity.villager.work_weaponsmith"); - SoundEvent ENTITY_VINDICATOR_AMBIENT = SoundEventImpl.get("minecraft:entity.vindicator.ambient"); + SoundEvent ENTITY_VINDICATOR_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.vindicator.ambient"); - SoundEvent ENTITY_VINDICATOR_CELEBRATE = SoundEventImpl.get("minecraft:entity.vindicator.celebrate"); + SoundEvent ENTITY_VINDICATOR_CELEBRATE = BuiltinSoundEvent.get("minecraft:entity.vindicator.celebrate"); - SoundEvent ENTITY_VINDICATOR_DEATH = SoundEventImpl.get("minecraft:entity.vindicator.death"); + SoundEvent ENTITY_VINDICATOR_DEATH = BuiltinSoundEvent.get("minecraft:entity.vindicator.death"); - SoundEvent ENTITY_VINDICATOR_HURT = SoundEventImpl.get("minecraft:entity.vindicator.hurt"); + SoundEvent ENTITY_VINDICATOR_HURT = BuiltinSoundEvent.get("minecraft:entity.vindicator.hurt"); - SoundEvent BLOCK_VINE_BREAK = SoundEventImpl.get("minecraft:block.vine.break"); + SoundEvent BLOCK_VINE_BREAK = BuiltinSoundEvent.get("minecraft:block.vine.break"); - SoundEvent BLOCK_VINE_FALL = SoundEventImpl.get("minecraft:block.vine.fall"); + SoundEvent BLOCK_VINE_FALL = BuiltinSoundEvent.get("minecraft:block.vine.fall"); - SoundEvent BLOCK_VINE_HIT = SoundEventImpl.get("minecraft:block.vine.hit"); + SoundEvent BLOCK_VINE_HIT = BuiltinSoundEvent.get("minecraft:block.vine.hit"); - SoundEvent BLOCK_VINE_PLACE = SoundEventImpl.get("minecraft:block.vine.place"); + SoundEvent BLOCK_VINE_PLACE = BuiltinSoundEvent.get("minecraft:block.vine.place"); - SoundEvent BLOCK_VINE_STEP = SoundEventImpl.get("minecraft:block.vine.step"); + SoundEvent BLOCK_VINE_STEP = BuiltinSoundEvent.get("minecraft:block.vine.step"); - SoundEvent BLOCK_LILY_PAD_PLACE = SoundEventImpl.get("minecraft:block.lily_pad.place"); + SoundEvent BLOCK_LILY_PAD_PLACE = BuiltinSoundEvent.get("minecraft:block.lily_pad.place"); - SoundEvent ENTITY_WANDERING_TRADER_AMBIENT = SoundEventImpl.get("minecraft:entity.wandering_trader.ambient"); + SoundEvent ENTITY_WANDERING_TRADER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.ambient"); - SoundEvent ENTITY_WANDERING_TRADER_DEATH = SoundEventImpl.get("minecraft:entity.wandering_trader.death"); + SoundEvent ENTITY_WANDERING_TRADER_DEATH = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.death"); - SoundEvent ENTITY_WANDERING_TRADER_DISAPPEARED = SoundEventImpl.get("minecraft:entity.wandering_trader.disappeared"); + SoundEvent ENTITY_WANDERING_TRADER_DISAPPEARED = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.disappeared"); - SoundEvent ENTITY_WANDERING_TRADER_DRINK_MILK = SoundEventImpl.get("minecraft:entity.wandering_trader.drink_milk"); + SoundEvent ENTITY_WANDERING_TRADER_DRINK_MILK = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.drink_milk"); - SoundEvent ENTITY_WANDERING_TRADER_DRINK_POTION = SoundEventImpl.get("minecraft:entity.wandering_trader.drink_potion"); + SoundEvent ENTITY_WANDERING_TRADER_DRINK_POTION = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.drink_potion"); - SoundEvent ENTITY_WANDERING_TRADER_HURT = SoundEventImpl.get("minecraft:entity.wandering_trader.hurt"); + SoundEvent ENTITY_WANDERING_TRADER_HURT = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.hurt"); - SoundEvent ENTITY_WANDERING_TRADER_NO = SoundEventImpl.get("minecraft:entity.wandering_trader.no"); + SoundEvent ENTITY_WANDERING_TRADER_NO = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.no"); - SoundEvent ENTITY_WANDERING_TRADER_REAPPEARED = SoundEventImpl.get("minecraft:entity.wandering_trader.reappeared"); + SoundEvent ENTITY_WANDERING_TRADER_REAPPEARED = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.reappeared"); - SoundEvent ENTITY_WANDERING_TRADER_TRADE = SoundEventImpl.get("minecraft:entity.wandering_trader.trade"); + SoundEvent ENTITY_WANDERING_TRADER_TRADE = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.trade"); - SoundEvent ENTITY_WANDERING_TRADER_YES = SoundEventImpl.get("minecraft:entity.wandering_trader.yes"); + SoundEvent ENTITY_WANDERING_TRADER_YES = BuiltinSoundEvent.get("minecraft:entity.wandering_trader.yes"); - SoundEvent ENTITY_WARDEN_AGITATED = SoundEventImpl.get("minecraft:entity.warden.agitated"); + SoundEvent ENTITY_WARDEN_AGITATED = BuiltinSoundEvent.get("minecraft:entity.warden.agitated"); - SoundEvent ENTITY_WARDEN_AMBIENT = SoundEventImpl.get("minecraft:entity.warden.ambient"); + SoundEvent ENTITY_WARDEN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.warden.ambient"); - SoundEvent ENTITY_WARDEN_ANGRY = SoundEventImpl.get("minecraft:entity.warden.angry"); + SoundEvent ENTITY_WARDEN_ANGRY = BuiltinSoundEvent.get("minecraft:entity.warden.angry"); - SoundEvent ENTITY_WARDEN_ATTACK_IMPACT = SoundEventImpl.get("minecraft:entity.warden.attack_impact"); + SoundEvent ENTITY_WARDEN_ATTACK_IMPACT = BuiltinSoundEvent.get("minecraft:entity.warden.attack_impact"); - SoundEvent ENTITY_WARDEN_DEATH = SoundEventImpl.get("minecraft:entity.warden.death"); + SoundEvent ENTITY_WARDEN_DEATH = BuiltinSoundEvent.get("minecraft:entity.warden.death"); - SoundEvent ENTITY_WARDEN_DIG = SoundEventImpl.get("minecraft:entity.warden.dig"); + SoundEvent ENTITY_WARDEN_DIG = BuiltinSoundEvent.get("minecraft:entity.warden.dig"); - SoundEvent ENTITY_WARDEN_EMERGE = SoundEventImpl.get("minecraft:entity.warden.emerge"); + SoundEvent ENTITY_WARDEN_EMERGE = BuiltinSoundEvent.get("minecraft:entity.warden.emerge"); - SoundEvent ENTITY_WARDEN_HEARTBEAT = SoundEventImpl.get("minecraft:entity.warden.heartbeat"); + SoundEvent ENTITY_WARDEN_HEARTBEAT = BuiltinSoundEvent.get("minecraft:entity.warden.heartbeat"); - SoundEvent ENTITY_WARDEN_HURT = SoundEventImpl.get("minecraft:entity.warden.hurt"); + SoundEvent ENTITY_WARDEN_HURT = BuiltinSoundEvent.get("minecraft:entity.warden.hurt"); - SoundEvent ENTITY_WARDEN_LISTENING = SoundEventImpl.get("minecraft:entity.warden.listening"); + SoundEvent ENTITY_WARDEN_LISTENING = BuiltinSoundEvent.get("minecraft:entity.warden.listening"); - SoundEvent ENTITY_WARDEN_LISTENING_ANGRY = SoundEventImpl.get("minecraft:entity.warden.listening_angry"); + SoundEvent ENTITY_WARDEN_LISTENING_ANGRY = BuiltinSoundEvent.get("minecraft:entity.warden.listening_angry"); - SoundEvent ENTITY_WARDEN_NEARBY_CLOSE = SoundEventImpl.get("minecraft:entity.warden.nearby_close"); + SoundEvent ENTITY_WARDEN_NEARBY_CLOSE = BuiltinSoundEvent.get("minecraft:entity.warden.nearby_close"); - SoundEvent ENTITY_WARDEN_NEARBY_CLOSER = SoundEventImpl.get("minecraft:entity.warden.nearby_closer"); + SoundEvent ENTITY_WARDEN_NEARBY_CLOSER = BuiltinSoundEvent.get("minecraft:entity.warden.nearby_closer"); - SoundEvent ENTITY_WARDEN_NEARBY_CLOSEST = SoundEventImpl.get("minecraft:entity.warden.nearby_closest"); + SoundEvent ENTITY_WARDEN_NEARBY_CLOSEST = BuiltinSoundEvent.get("minecraft:entity.warden.nearby_closest"); - SoundEvent ENTITY_WARDEN_ROAR = SoundEventImpl.get("minecraft:entity.warden.roar"); + SoundEvent ENTITY_WARDEN_ROAR = BuiltinSoundEvent.get("minecraft:entity.warden.roar"); - SoundEvent ENTITY_WARDEN_SNIFF = SoundEventImpl.get("minecraft:entity.warden.sniff"); + SoundEvent ENTITY_WARDEN_SNIFF = BuiltinSoundEvent.get("minecraft:entity.warden.sniff"); - SoundEvent ENTITY_WARDEN_SONIC_BOOM = SoundEventImpl.get("minecraft:entity.warden.sonic_boom"); + SoundEvent ENTITY_WARDEN_SONIC_BOOM = BuiltinSoundEvent.get("minecraft:entity.warden.sonic_boom"); - SoundEvent ENTITY_WARDEN_SONIC_CHARGE = SoundEventImpl.get("minecraft:entity.warden.sonic_charge"); + SoundEvent ENTITY_WARDEN_SONIC_CHARGE = BuiltinSoundEvent.get("minecraft:entity.warden.sonic_charge"); - SoundEvent ENTITY_WARDEN_STEP = SoundEventImpl.get("minecraft:entity.warden.step"); + SoundEvent ENTITY_WARDEN_STEP = BuiltinSoundEvent.get("minecraft:entity.warden.step"); - SoundEvent ENTITY_WARDEN_TENDRIL_CLICKS = SoundEventImpl.get("minecraft:entity.warden.tendril_clicks"); + SoundEvent ENTITY_WARDEN_TENDRIL_CLICKS = BuiltinSoundEvent.get("minecraft:entity.warden.tendril_clicks"); - SoundEvent BLOCK_HANGING_SIGN_WAXED_INTERACT_FAIL = SoundEventImpl.get("minecraft:block.hanging_sign.waxed_interact_fail"); + SoundEvent BLOCK_HANGING_SIGN_WAXED_INTERACT_FAIL = BuiltinSoundEvent.get("minecraft:block.hanging_sign.waxed_interact_fail"); - SoundEvent BLOCK_SIGN_WAXED_INTERACT_FAIL = SoundEventImpl.get("minecraft:block.sign.waxed_interact_fail"); + SoundEvent BLOCK_SIGN_WAXED_INTERACT_FAIL = BuiltinSoundEvent.get("minecraft:block.sign.waxed_interact_fail"); - SoundEvent BLOCK_WATER_AMBIENT = SoundEventImpl.get("minecraft:block.water.ambient"); + SoundEvent BLOCK_WATER_AMBIENT = BuiltinSoundEvent.get("minecraft:block.water.ambient"); - SoundEvent WEATHER_RAIN = SoundEventImpl.get("minecraft:weather.rain"); + SoundEvent WEATHER_RAIN = BuiltinSoundEvent.get("minecraft:weather.rain"); - SoundEvent WEATHER_RAIN_ABOVE = SoundEventImpl.get("minecraft:weather.rain.above"); + SoundEvent WEATHER_RAIN_ABOVE = BuiltinSoundEvent.get("minecraft:weather.rain.above"); - SoundEvent BLOCK_WET_GRASS_BREAK = SoundEventImpl.get("minecraft:block.wet_grass.break"); + SoundEvent BLOCK_WET_GRASS_BREAK = BuiltinSoundEvent.get("minecraft:block.wet_grass.break"); - SoundEvent BLOCK_WET_GRASS_FALL = SoundEventImpl.get("minecraft:block.wet_grass.fall"); + SoundEvent BLOCK_WET_GRASS_FALL = BuiltinSoundEvent.get("minecraft:block.wet_grass.fall"); - SoundEvent BLOCK_WET_GRASS_HIT = SoundEventImpl.get("minecraft:block.wet_grass.hit"); + SoundEvent BLOCK_WET_GRASS_HIT = BuiltinSoundEvent.get("minecraft:block.wet_grass.hit"); - SoundEvent BLOCK_WET_GRASS_PLACE = SoundEventImpl.get("minecraft:block.wet_grass.place"); + SoundEvent BLOCK_WET_GRASS_PLACE = BuiltinSoundEvent.get("minecraft:block.wet_grass.place"); - SoundEvent BLOCK_WET_GRASS_STEP = SoundEventImpl.get("minecraft:block.wet_grass.step"); + SoundEvent BLOCK_WET_GRASS_STEP = BuiltinSoundEvent.get("minecraft:block.wet_grass.step"); - SoundEvent BLOCK_WET_SPONGE_BREAK = SoundEventImpl.get("minecraft:block.wet_sponge.break"); + SoundEvent BLOCK_WET_SPONGE_BREAK = BuiltinSoundEvent.get("minecraft:block.wet_sponge.break"); - SoundEvent BLOCK_WET_SPONGE_DRIES = SoundEventImpl.get("minecraft:block.wet_sponge.dries"); + SoundEvent BLOCK_WET_SPONGE_DRIES = BuiltinSoundEvent.get("minecraft:block.wet_sponge.dries"); - SoundEvent BLOCK_WET_SPONGE_FALL = SoundEventImpl.get("minecraft:block.wet_sponge.fall"); + SoundEvent BLOCK_WET_SPONGE_FALL = BuiltinSoundEvent.get("minecraft:block.wet_sponge.fall"); - SoundEvent BLOCK_WET_SPONGE_HIT = SoundEventImpl.get("minecraft:block.wet_sponge.hit"); + SoundEvent BLOCK_WET_SPONGE_HIT = BuiltinSoundEvent.get("minecraft:block.wet_sponge.hit"); - SoundEvent BLOCK_WET_SPONGE_PLACE = SoundEventImpl.get("minecraft:block.wet_sponge.place"); + SoundEvent BLOCK_WET_SPONGE_PLACE = BuiltinSoundEvent.get("minecraft:block.wet_sponge.place"); - SoundEvent BLOCK_WET_SPONGE_STEP = SoundEventImpl.get("minecraft:block.wet_sponge.step"); + SoundEvent BLOCK_WET_SPONGE_STEP = BuiltinSoundEvent.get("minecraft:block.wet_sponge.step"); - SoundEvent ENTITY_WIND_CHARGE_WIND_BURST = SoundEventImpl.get("minecraft:entity.wind_charge.wind_burst"); + SoundEvent ENTITY_WIND_CHARGE_WIND_BURST = BuiltinSoundEvent.get("minecraft:entity.wind_charge.wind_burst"); - SoundEvent ENTITY_WIND_CHARGE_THROW = SoundEventImpl.get("minecraft:entity.wind_charge.throw"); + SoundEvent ENTITY_WIND_CHARGE_THROW = BuiltinSoundEvent.get("minecraft:entity.wind_charge.throw"); - SoundEvent ENTITY_WITCH_AMBIENT = SoundEventImpl.get("minecraft:entity.witch.ambient"); + SoundEvent ENTITY_WITCH_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.witch.ambient"); - SoundEvent ENTITY_WITCH_CELEBRATE = SoundEventImpl.get("minecraft:entity.witch.celebrate"); + SoundEvent ENTITY_WITCH_CELEBRATE = BuiltinSoundEvent.get("minecraft:entity.witch.celebrate"); - SoundEvent ENTITY_WITCH_DEATH = SoundEventImpl.get("minecraft:entity.witch.death"); + SoundEvent ENTITY_WITCH_DEATH = BuiltinSoundEvent.get("minecraft:entity.witch.death"); - SoundEvent ENTITY_WITCH_DRINK = SoundEventImpl.get("minecraft:entity.witch.drink"); + SoundEvent ENTITY_WITCH_DRINK = BuiltinSoundEvent.get("minecraft:entity.witch.drink"); - SoundEvent ENTITY_WITCH_HURT = SoundEventImpl.get("minecraft:entity.witch.hurt"); + SoundEvent ENTITY_WITCH_HURT = BuiltinSoundEvent.get("minecraft:entity.witch.hurt"); - SoundEvent ENTITY_WITCH_THROW = SoundEventImpl.get("minecraft:entity.witch.throw"); + SoundEvent ENTITY_WITCH_THROW = BuiltinSoundEvent.get("minecraft:entity.witch.throw"); - SoundEvent ENTITY_WITHER_AMBIENT = SoundEventImpl.get("minecraft:entity.wither.ambient"); + SoundEvent ENTITY_WITHER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.wither.ambient"); - SoundEvent ENTITY_WITHER_BREAK_BLOCK = SoundEventImpl.get("minecraft:entity.wither.break_block"); + SoundEvent ENTITY_WITHER_BREAK_BLOCK = BuiltinSoundEvent.get("minecraft:entity.wither.break_block"); - SoundEvent ENTITY_WITHER_DEATH = SoundEventImpl.get("minecraft:entity.wither.death"); + SoundEvent ENTITY_WITHER_DEATH = BuiltinSoundEvent.get("minecraft:entity.wither.death"); - SoundEvent ENTITY_WITHER_HURT = SoundEventImpl.get("minecraft:entity.wither.hurt"); + SoundEvent ENTITY_WITHER_HURT = BuiltinSoundEvent.get("minecraft:entity.wither.hurt"); - SoundEvent ENTITY_WITHER_SHOOT = SoundEventImpl.get("minecraft:entity.wither.shoot"); + SoundEvent ENTITY_WITHER_SHOOT = BuiltinSoundEvent.get("minecraft:entity.wither.shoot"); - SoundEvent ENTITY_WITHER_SKELETON_AMBIENT = SoundEventImpl.get("minecraft:entity.wither_skeleton.ambient"); + SoundEvent ENTITY_WITHER_SKELETON_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.wither_skeleton.ambient"); - SoundEvent ENTITY_WITHER_SKELETON_DEATH = SoundEventImpl.get("minecraft:entity.wither_skeleton.death"); + SoundEvent ENTITY_WITHER_SKELETON_DEATH = BuiltinSoundEvent.get("minecraft:entity.wither_skeleton.death"); - SoundEvent ENTITY_WITHER_SKELETON_HURT = SoundEventImpl.get("minecraft:entity.wither_skeleton.hurt"); + SoundEvent ENTITY_WITHER_SKELETON_HURT = BuiltinSoundEvent.get("minecraft:entity.wither_skeleton.hurt"); - SoundEvent ENTITY_WITHER_SKELETON_STEP = SoundEventImpl.get("minecraft:entity.wither_skeleton.step"); + SoundEvent ENTITY_WITHER_SKELETON_STEP = BuiltinSoundEvent.get("minecraft:entity.wither_skeleton.step"); - SoundEvent ENTITY_WITHER_SPAWN = SoundEventImpl.get("minecraft:entity.wither.spawn"); + SoundEvent ENTITY_WITHER_SPAWN = BuiltinSoundEvent.get("minecraft:entity.wither.spawn"); - SoundEvent ITEM_WOLF_ARMOR_BREAK = SoundEventImpl.get("minecraft:item.wolf_armor.break"); + SoundEvent ITEM_WOLF_ARMOR_BREAK = BuiltinSoundEvent.get("minecraft:item.wolf_armor.break"); - SoundEvent ITEM_WOLF_ARMOR_CRACK = SoundEventImpl.get("minecraft:item.wolf_armor.crack"); + SoundEvent ITEM_WOLF_ARMOR_CRACK = BuiltinSoundEvent.get("minecraft:item.wolf_armor.crack"); - SoundEvent ITEM_WOLF_ARMOR_DAMAGE = SoundEventImpl.get("minecraft:item.wolf_armor.damage"); + SoundEvent ITEM_WOLF_ARMOR_DAMAGE = BuiltinSoundEvent.get("minecraft:item.wolf_armor.damage"); - SoundEvent ITEM_WOLF_ARMOR_REPAIR = SoundEventImpl.get("minecraft:item.wolf_armor.repair"); + SoundEvent ITEM_WOLF_ARMOR_REPAIR = BuiltinSoundEvent.get("minecraft:item.wolf_armor.repair"); - SoundEvent ENTITY_WOLF_AMBIENT = SoundEventImpl.get("minecraft:entity.wolf.ambient"); + SoundEvent ENTITY_WOLF_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.wolf.ambient"); - SoundEvent ENTITY_WOLF_DEATH = SoundEventImpl.get("minecraft:entity.wolf.death"); + SoundEvent ENTITY_WOLF_DEATH = BuiltinSoundEvent.get("minecraft:entity.wolf.death"); - SoundEvent ENTITY_WOLF_GROWL = SoundEventImpl.get("minecraft:entity.wolf.growl"); + SoundEvent ENTITY_WOLF_GROWL = BuiltinSoundEvent.get("minecraft:entity.wolf.growl"); - SoundEvent ENTITY_WOLF_HOWL = SoundEventImpl.get("minecraft:entity.wolf.howl"); + SoundEvent ENTITY_WOLF_HOWL = BuiltinSoundEvent.get("minecraft:entity.wolf.howl"); - SoundEvent ENTITY_WOLF_HURT = SoundEventImpl.get("minecraft:entity.wolf.hurt"); + SoundEvent ENTITY_WOLF_HURT = BuiltinSoundEvent.get("minecraft:entity.wolf.hurt"); - SoundEvent ENTITY_WOLF_PANT = SoundEventImpl.get("minecraft:entity.wolf.pant"); + SoundEvent ENTITY_WOLF_PANT = BuiltinSoundEvent.get("minecraft:entity.wolf.pant"); - SoundEvent ENTITY_WOLF_SHAKE = SoundEventImpl.get("minecraft:entity.wolf.shake"); + SoundEvent ENTITY_WOLF_SHAKE = BuiltinSoundEvent.get("minecraft:entity.wolf.shake"); - SoundEvent ENTITY_WOLF_STEP = SoundEventImpl.get("minecraft:entity.wolf.step"); + SoundEvent ENTITY_WOLF_STEP = BuiltinSoundEvent.get("minecraft:entity.wolf.step"); - SoundEvent ENTITY_WOLF_WHINE = SoundEventImpl.get("minecraft:entity.wolf.whine"); + SoundEvent ENTITY_WOLF_WHINE = BuiltinSoundEvent.get("minecraft:entity.wolf.whine"); - SoundEvent BLOCK_WOODEN_DOOR_CLOSE = SoundEventImpl.get("minecraft:block.wooden_door.close"); + SoundEvent BLOCK_WOODEN_DOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.wooden_door.close"); - SoundEvent BLOCK_WOODEN_DOOR_OPEN = SoundEventImpl.get("minecraft:block.wooden_door.open"); + SoundEvent BLOCK_WOODEN_DOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.wooden_door.open"); - SoundEvent BLOCK_WOODEN_TRAPDOOR_CLOSE = SoundEventImpl.get("minecraft:block.wooden_trapdoor.close"); + SoundEvent BLOCK_WOODEN_TRAPDOOR_CLOSE = BuiltinSoundEvent.get("minecraft:block.wooden_trapdoor.close"); - SoundEvent BLOCK_WOODEN_TRAPDOOR_OPEN = SoundEventImpl.get("minecraft:block.wooden_trapdoor.open"); + SoundEvent BLOCK_WOODEN_TRAPDOOR_OPEN = BuiltinSoundEvent.get("minecraft:block.wooden_trapdoor.open"); - SoundEvent BLOCK_WOODEN_BUTTON_CLICK_OFF = SoundEventImpl.get("minecraft:block.wooden_button.click_off"); + SoundEvent BLOCK_WOODEN_BUTTON_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.wooden_button.click_off"); - SoundEvent BLOCK_WOODEN_BUTTON_CLICK_ON = SoundEventImpl.get("minecraft:block.wooden_button.click_on"); + SoundEvent BLOCK_WOODEN_BUTTON_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.wooden_button.click_on"); - SoundEvent BLOCK_WOODEN_PRESSURE_PLATE_CLICK_OFF = SoundEventImpl.get("minecraft:block.wooden_pressure_plate.click_off"); + SoundEvent BLOCK_WOODEN_PRESSURE_PLATE_CLICK_OFF = BuiltinSoundEvent.get("minecraft:block.wooden_pressure_plate.click_off"); - SoundEvent BLOCK_WOODEN_PRESSURE_PLATE_CLICK_ON = SoundEventImpl.get("minecraft:block.wooden_pressure_plate.click_on"); + SoundEvent BLOCK_WOODEN_PRESSURE_PLATE_CLICK_ON = BuiltinSoundEvent.get("minecraft:block.wooden_pressure_plate.click_on"); - SoundEvent BLOCK_WOOD_BREAK = SoundEventImpl.get("minecraft:block.wood.break"); + SoundEvent BLOCK_WOOD_BREAK = BuiltinSoundEvent.get("minecraft:block.wood.break"); - SoundEvent BLOCK_WOOD_FALL = SoundEventImpl.get("minecraft:block.wood.fall"); + SoundEvent BLOCK_WOOD_FALL = BuiltinSoundEvent.get("minecraft:block.wood.fall"); - SoundEvent BLOCK_WOOD_HIT = SoundEventImpl.get("minecraft:block.wood.hit"); + SoundEvent BLOCK_WOOD_HIT = BuiltinSoundEvent.get("minecraft:block.wood.hit"); - SoundEvent BLOCK_WOOD_PLACE = SoundEventImpl.get("minecraft:block.wood.place"); + SoundEvent BLOCK_WOOD_PLACE = BuiltinSoundEvent.get("minecraft:block.wood.place"); - SoundEvent BLOCK_WOOD_STEP = SoundEventImpl.get("minecraft:block.wood.step"); + SoundEvent BLOCK_WOOD_STEP = BuiltinSoundEvent.get("minecraft:block.wood.step"); - SoundEvent BLOCK_WOOL_BREAK = SoundEventImpl.get("minecraft:block.wool.break"); + SoundEvent BLOCK_WOOL_BREAK = BuiltinSoundEvent.get("minecraft:block.wool.break"); - SoundEvent BLOCK_WOOL_FALL = SoundEventImpl.get("minecraft:block.wool.fall"); + SoundEvent BLOCK_WOOL_FALL = BuiltinSoundEvent.get("minecraft:block.wool.fall"); - SoundEvent BLOCK_WOOL_HIT = SoundEventImpl.get("minecraft:block.wool.hit"); + SoundEvent BLOCK_WOOL_HIT = BuiltinSoundEvent.get("minecraft:block.wool.hit"); - SoundEvent BLOCK_WOOL_PLACE = SoundEventImpl.get("minecraft:block.wool.place"); + SoundEvent BLOCK_WOOL_PLACE = BuiltinSoundEvent.get("minecraft:block.wool.place"); - SoundEvent BLOCK_WOOL_STEP = SoundEventImpl.get("minecraft:block.wool.step"); + SoundEvent BLOCK_WOOL_STEP = BuiltinSoundEvent.get("minecraft:block.wool.step"); - SoundEvent ENTITY_ZOGLIN_AMBIENT = SoundEventImpl.get("minecraft:entity.zoglin.ambient"); + SoundEvent ENTITY_ZOGLIN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.zoglin.ambient"); - SoundEvent ENTITY_ZOGLIN_ANGRY = SoundEventImpl.get("minecraft:entity.zoglin.angry"); + SoundEvent ENTITY_ZOGLIN_ANGRY = BuiltinSoundEvent.get("minecraft:entity.zoglin.angry"); - SoundEvent ENTITY_ZOGLIN_ATTACK = SoundEventImpl.get("minecraft:entity.zoglin.attack"); + SoundEvent ENTITY_ZOGLIN_ATTACK = BuiltinSoundEvent.get("minecraft:entity.zoglin.attack"); - SoundEvent ENTITY_ZOGLIN_DEATH = SoundEventImpl.get("minecraft:entity.zoglin.death"); + SoundEvent ENTITY_ZOGLIN_DEATH = BuiltinSoundEvent.get("minecraft:entity.zoglin.death"); - SoundEvent ENTITY_ZOGLIN_HURT = SoundEventImpl.get("minecraft:entity.zoglin.hurt"); + SoundEvent ENTITY_ZOGLIN_HURT = BuiltinSoundEvent.get("minecraft:entity.zoglin.hurt"); - SoundEvent ENTITY_ZOGLIN_STEP = SoundEventImpl.get("minecraft:entity.zoglin.step"); + SoundEvent ENTITY_ZOGLIN_STEP = BuiltinSoundEvent.get("minecraft:entity.zoglin.step"); - SoundEvent ENTITY_ZOMBIE_AMBIENT = SoundEventImpl.get("minecraft:entity.zombie.ambient"); + SoundEvent ENTITY_ZOMBIE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.zombie.ambient"); - SoundEvent ENTITY_ZOMBIE_ATTACK_WOODEN_DOOR = SoundEventImpl.get("minecraft:entity.zombie.attack_wooden_door"); + SoundEvent ENTITY_ZOMBIE_ATTACK_WOODEN_DOOR = BuiltinSoundEvent.get("minecraft:entity.zombie.attack_wooden_door"); - SoundEvent ENTITY_ZOMBIE_ATTACK_IRON_DOOR = SoundEventImpl.get("minecraft:entity.zombie.attack_iron_door"); + SoundEvent ENTITY_ZOMBIE_ATTACK_IRON_DOOR = BuiltinSoundEvent.get("minecraft:entity.zombie.attack_iron_door"); - SoundEvent ENTITY_ZOMBIE_BREAK_WOODEN_DOOR = SoundEventImpl.get("minecraft:entity.zombie.break_wooden_door"); + SoundEvent ENTITY_ZOMBIE_BREAK_WOODEN_DOOR = BuiltinSoundEvent.get("minecraft:entity.zombie.break_wooden_door"); - SoundEvent ENTITY_ZOMBIE_CONVERTED_TO_DROWNED = SoundEventImpl.get("minecraft:entity.zombie.converted_to_drowned"); + SoundEvent ENTITY_ZOMBIE_CONVERTED_TO_DROWNED = BuiltinSoundEvent.get("minecraft:entity.zombie.converted_to_drowned"); - SoundEvent ENTITY_ZOMBIE_DEATH = SoundEventImpl.get("minecraft:entity.zombie.death"); + SoundEvent ENTITY_ZOMBIE_DEATH = BuiltinSoundEvent.get("minecraft:entity.zombie.death"); - SoundEvent ENTITY_ZOMBIE_DESTROY_EGG = SoundEventImpl.get("minecraft:entity.zombie.destroy_egg"); + SoundEvent ENTITY_ZOMBIE_DESTROY_EGG = BuiltinSoundEvent.get("minecraft:entity.zombie.destroy_egg"); - SoundEvent ENTITY_ZOMBIE_HORSE_AMBIENT = SoundEventImpl.get("minecraft:entity.zombie_horse.ambient"); + SoundEvent ENTITY_ZOMBIE_HORSE_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.zombie_horse.ambient"); - SoundEvent ENTITY_ZOMBIE_HORSE_DEATH = SoundEventImpl.get("minecraft:entity.zombie_horse.death"); + SoundEvent ENTITY_ZOMBIE_HORSE_DEATH = BuiltinSoundEvent.get("minecraft:entity.zombie_horse.death"); - SoundEvent ENTITY_ZOMBIE_HORSE_HURT = SoundEventImpl.get("minecraft:entity.zombie_horse.hurt"); + SoundEvent ENTITY_ZOMBIE_HORSE_HURT = BuiltinSoundEvent.get("minecraft:entity.zombie_horse.hurt"); - SoundEvent ENTITY_ZOMBIE_HURT = SoundEventImpl.get("minecraft:entity.zombie.hurt"); + SoundEvent ENTITY_ZOMBIE_HURT = BuiltinSoundEvent.get("minecraft:entity.zombie.hurt"); - SoundEvent ENTITY_ZOMBIE_INFECT = SoundEventImpl.get("minecraft:entity.zombie.infect"); + SoundEvent ENTITY_ZOMBIE_INFECT = BuiltinSoundEvent.get("minecraft:entity.zombie.infect"); - SoundEvent ENTITY_ZOMBIFIED_PIGLIN_AMBIENT = SoundEventImpl.get("minecraft:entity.zombified_piglin.ambient"); + SoundEvent ENTITY_ZOMBIFIED_PIGLIN_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.zombified_piglin.ambient"); - SoundEvent ENTITY_ZOMBIFIED_PIGLIN_ANGRY = SoundEventImpl.get("minecraft:entity.zombified_piglin.angry"); + SoundEvent ENTITY_ZOMBIFIED_PIGLIN_ANGRY = BuiltinSoundEvent.get("minecraft:entity.zombified_piglin.angry"); - SoundEvent ENTITY_ZOMBIFIED_PIGLIN_DEATH = SoundEventImpl.get("minecraft:entity.zombified_piglin.death"); + SoundEvent ENTITY_ZOMBIFIED_PIGLIN_DEATH = BuiltinSoundEvent.get("minecraft:entity.zombified_piglin.death"); - SoundEvent ENTITY_ZOMBIFIED_PIGLIN_HURT = SoundEventImpl.get("minecraft:entity.zombified_piglin.hurt"); + SoundEvent ENTITY_ZOMBIFIED_PIGLIN_HURT = BuiltinSoundEvent.get("minecraft:entity.zombified_piglin.hurt"); - SoundEvent ENTITY_ZOMBIE_STEP = SoundEventImpl.get("minecraft:entity.zombie.step"); + SoundEvent ENTITY_ZOMBIE_STEP = BuiltinSoundEvent.get("minecraft:entity.zombie.step"); - SoundEvent ENTITY_ZOMBIE_VILLAGER_AMBIENT = SoundEventImpl.get("minecraft:entity.zombie_villager.ambient"); + SoundEvent ENTITY_ZOMBIE_VILLAGER_AMBIENT = BuiltinSoundEvent.get("minecraft:entity.zombie_villager.ambient"); - SoundEvent ENTITY_ZOMBIE_VILLAGER_CONVERTED = SoundEventImpl.get("minecraft:entity.zombie_villager.converted"); + SoundEvent ENTITY_ZOMBIE_VILLAGER_CONVERTED = BuiltinSoundEvent.get("minecraft:entity.zombie_villager.converted"); - SoundEvent ENTITY_ZOMBIE_VILLAGER_CURE = SoundEventImpl.get("minecraft:entity.zombie_villager.cure"); + SoundEvent ENTITY_ZOMBIE_VILLAGER_CURE = BuiltinSoundEvent.get("minecraft:entity.zombie_villager.cure"); - SoundEvent ENTITY_ZOMBIE_VILLAGER_DEATH = SoundEventImpl.get("minecraft:entity.zombie_villager.death"); + SoundEvent ENTITY_ZOMBIE_VILLAGER_DEATH = BuiltinSoundEvent.get("minecraft:entity.zombie_villager.death"); - SoundEvent ENTITY_ZOMBIE_VILLAGER_HURT = SoundEventImpl.get("minecraft:entity.zombie_villager.hurt"); + SoundEvent ENTITY_ZOMBIE_VILLAGER_HURT = BuiltinSoundEvent.get("minecraft:entity.zombie_villager.hurt"); - SoundEvent ENTITY_ZOMBIE_VILLAGER_STEP = SoundEventImpl.get("minecraft:entity.zombie_villager.step"); + SoundEvent ENTITY_ZOMBIE_VILLAGER_STEP = BuiltinSoundEvent.get("minecraft:entity.zombie_villager.step"); - SoundEvent EVENT_MOB_EFFECT_BAD_OMEN = SoundEventImpl.get("minecraft:event.mob_effect.bad_omen"); + SoundEvent EVENT_MOB_EFFECT_BAD_OMEN = BuiltinSoundEvent.get("minecraft:event.mob_effect.bad_omen"); - SoundEvent EVENT_MOB_EFFECT_TRIAL_OMEN = SoundEventImpl.get("minecraft:event.mob_effect.trial_omen"); + SoundEvent EVENT_MOB_EFFECT_TRIAL_OMEN = BuiltinSoundEvent.get("minecraft:event.mob_effect.trial_omen"); - SoundEvent EVENT_MOB_EFFECT_RAID_OMEN = SoundEventImpl.get("minecraft:event.mob_effect.raid_omen"); + SoundEvent EVENT_MOB_EFFECT_RAID_OMEN = BuiltinSoundEvent.get("minecraft:event.mob_effect.raid_omen"); } diff --git a/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java b/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java index 4b7e3fd8e..893ae6ce2 100644 --- a/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java +++ b/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java @@ -10,11 +10,11 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.title.Title; import net.kyori.adventure.title.TitlePart; -import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.sound.SoundEvent; +import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.TickUtils; import org.jetbrains.annotations.NotNull; @@ -111,15 +111,12 @@ public class AdventurePacketConvertor { * @return the sound packet */ public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, double x, double y, double z) { - final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString()); + final NamespaceID soundName = NamespaceID.from(sound.name().asString()); + SoundEvent minestomSound = SoundEvent.fromNamespaceId(soundName); + if (minestomSound == null) minestomSound = SoundEvent.of(soundName, null); + final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong()); - if (minestomSound == null) { - return new SoundEffectPacket(sound.name().asString(), null, sound.source(), - new Vec(x, y, z), sound.volume(), sound.pitch(), seed); - } else { - return new SoundEffectPacket(minestomSound, null, sound.source(), - new Vec(x, y, z), sound.volume(), sound.pitch(), seed); - } + return new SoundEffectPacket(minestomSound, sound.source(), (int) x, (int) y, (int) z, sound.volume(), sound.pitch(), seed); } /** @@ -136,14 +133,12 @@ public class AdventurePacketConvertor { if (!(emitter instanceof Entity entity)) throw new IllegalArgumentException("you can only call this method with entities"); - final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString()); - final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong()); + final NamespaceID soundName = NamespaceID.from(sound.name().asString()); + SoundEvent minestomSound = SoundEvent.fromNamespaceId(soundName); + if (minestomSound == null) minestomSound = SoundEvent.of(soundName, null); - if (minestomSound != null) { - return new EntitySoundEffectPacket(minestomSound, null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed); - } else { - return new EntitySoundEffectPacket(sound.name().asString(), null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed); - } + final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong()); + return new EntitySoundEffectPacket(minestomSound, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed); } /** diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index e35d81cc7..fe1622b49 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -366,8 +366,7 @@ public class LivingEntity extends Entity implements EquipmentHandler { // TODO: separate living entity categories soundCategory = Source.HOSTILE; } - sendPacketToViewersAndSelf(new SoundEffectPacket(sound, null, soundCategory, - getPosition(), 1.0f, 1.0f, 0)); + sendPacketToViewersAndSelf(new SoundEffectPacket(sound, soundCategory, getPosition(), 1.0f, 1.0f, 0)); } }); diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index d85ee5751..da48bc5f6 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -8,6 +8,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.entity.pathfinding.PFColumnarSpace; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.instance.generator.Generator; import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.packet.server.play.ChunkDataPacket; import net.minestom.server.snapshot.Snapshotable; @@ -227,11 +228,11 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, } /** - * Gets if this chunk will or had been loaded with a {@link ChunkGenerator}. + * Gets if this chunk will or had been loaded with a {@link Generator}. *

* If false, the chunk will be entirely empty when loaded. * - * @return true if this chunk is affected by a {@link ChunkGenerator} + * @return true if this chunk is affected by a {@link Generator} */ public boolean shouldGenerate() { return shouldGenerate; @@ -241,7 +242,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, * Gets if this chunk is read-only. *

* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}. - * It does not affect {@link IChunkLoader} and {@link ChunkGenerator}. + * It does not affect {@link IChunkLoader} and {@link Generator}. * * @return true if the chunk is read-only */ @@ -253,7 +254,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, * Changes the read state of the chunk. *

* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}. - * It does not affect {@link IChunkLoader} and {@link ChunkGenerator}. + * It does not affect {@link IChunkLoader} and {@link Generator}. * * @param readOnly true to make the chunk read-only, false otherwise */ diff --git a/src/main/java/net/minestom/server/instance/IChunkLoader.java b/src/main/java/net/minestom/server/instance/IChunkLoader.java index 549f32e00..bf5029631 100644 --- a/src/main/java/net/minestom/server/instance/IChunkLoader.java +++ b/src/main/java/net/minestom/server/instance/IChunkLoader.java @@ -27,7 +27,7 @@ public interface IChunkLoader { } /** - * Loads a {@link Chunk}, all blocks should be set since the {@link ChunkGenerator} is not applied. + * Loads a {@link Chunk}, all blocks should be set since the {@link net.minestom.server.instance.generator.Generator} is not applied. * * @param instance the {@link Instance} where the {@link Chunk} belong * @param chunkX the chunk X diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java index 631d25521..78bf92658 100644 --- a/src/main/java/net/minestom/server/item/ItemComponent.java +++ b/src/main/java/net/minestom/server/item/ItemComponent.java @@ -66,7 +66,7 @@ public sealed interface ItemComponent extends StaticProtocolObject permits It ItemComponent LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE); ItemComponent FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE); ItemComponent FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE); - ItemComponent PROFILE = declare("profile", null, null); //todo + ItemComponent PROFILE = declare("profile", HeadProfile.NETWORK_TYPE, HeadProfile.NBT_TYPE); ItemComponent NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING); ItemComponent BANNER_PATTERNS = declare("banner_patterns", null, null); //todo ItemComponent BASE_COLOR = declare("base_color", DyeColor.NETWORK_TYPE, DyeColor.NBT_TYPE); diff --git a/src/main/java/net/minestom/server/item/component/HeadProfile.java b/src/main/java/net/minestom/server/item/component/HeadProfile.java new file mode 100644 index 000000000..9e8afcae1 --- /dev/null +++ b/src/main/java/net/minestom/server/item/component/HeadProfile.java @@ -0,0 +1,87 @@ +package net.minestom.server.item.component; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.IntArrayBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.minestom.server.entity.PlayerSkin; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.UUID; + +public record HeadProfile(@Nullable String name, @Nullable UUID uuid, @NotNull List properties) { + public static final HeadProfile EMPTY = new HeadProfile(null, null, List.of()); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { + @Override + public void write(@NotNull NetworkBuffer buffer, HeadProfile value) { + buffer.writeOptional(NetworkBuffer.STRING, value.name); + buffer.writeOptional(NetworkBuffer.UUID, value.uuid); + buffer.writeCollection(Property.NETWORK_TYPE, value.properties); + } + + @Override + public HeadProfile read(@NotNull NetworkBuffer buffer) { + return new HeadProfile(buffer.readOptional(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.UUID), buffer.readCollection(Property.NETWORK_TYPE, Short.MAX_VALUE)); + } + }; + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new HeadProfile( + tag.get("name") instanceof StringBinaryTag string ? string.value() : null, + tag.get("uuid") instanceof IntArrayBinaryTag intArray ? BinaryTagSerializer.UUID.read(intArray) : null, + Property.NBT_LIST_TYPE.read(tag.getList("properties")) + ), + profile -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + if (profile.name != null) builder.putString("name", profile.name); + if (profile.uuid != null) builder.put("uuid", BinaryTagSerializer.UUID.write(profile.uuid)); + if (!profile.properties.isEmpty()) builder.put("properties", Property.NBT_LIST_TYPE.write(profile.properties)); + return builder.build(); + } + ); + + public HeadProfile(@NotNull PlayerSkin playerSkin) { + this(null, null, List.of(new Property("textures", playerSkin.textures(), playerSkin.signature()))); + } + + public @Nullable PlayerSkin skin() { + for (Property property : properties) { + if ("textures".equals(property.name)) { + return new PlayerSkin(property.value, property.signature); + } + } + return null; + } + + public record Property(@NotNull String name, @NotNull String value, @Nullable String signature) { + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { + @Override + public void write(@NotNull NetworkBuffer buffer, Property value) { + buffer.write(NetworkBuffer.STRING, value.name); + buffer.write(NetworkBuffer.STRING, value.value); + buffer.writeOptional(NetworkBuffer.STRING, value.signature); + } + + @Override + public Property read(@NotNull NetworkBuffer buffer) { + return new Property(buffer.read(NetworkBuffer.STRING), buffer.read(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.STRING)); + } + }; + public static final BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.COMPOUND.map( + tag -> new Property(tag.getString("name"), tag.getString("value"), + tag.get("signature") instanceof StringBinaryTag signature ? signature.value() : null), + property -> { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + builder.putString("name", property.name); + builder.putString("value", property.value); + if (property.signature != null) builder.putString("signature", property.signature); + return builder.build(); + } + ); + public static final BinaryTagSerializer> NBT_LIST_TYPE = NBT_TYPE.list(); + } + +} diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index 09d2ab544..021e96c8e 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -27,6 +27,7 @@ import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; @ApiStatus.Experimental public final class NetworkBuffer { @@ -84,6 +85,24 @@ public final class NetworkBuffer { return NetworkBufferTypeImpl.fromEnum(enumClass); } + public static Type lazy(@NotNull Supplier> supplier) { + return new NetworkBuffer.Type<>() { + private Type type; + + @Override + public void write(@NotNull NetworkBuffer buffer, T value) { + if (type == null) type = supplier.get(); + type.write(buffer, value); + } + + @Override + public T read(@NotNull NetworkBuffer buffer) { + if (type == null) type = supplier.get(); + return null; + } + }; + } + ByteBuffer nioBuffer; final boolean resizable; int writeIndex; diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java index 259787148..ed384771a 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java @@ -6,17 +6,12 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.sound.SoundEvent; -import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; public record EntitySoundEffectPacket( - // only one of soundEvent and soundName may be present - @Nullable SoundEvent soundEvent, - @Nullable String soundName, - @Nullable Float range, // Only allowed with soundName + @NotNull SoundEvent soundEvent, @NotNull Sound.Source source, int entityId, float volume, @@ -24,64 +19,18 @@ public record EntitySoundEffectPacket( long seed ) implements ServerPacket.Play { - public EntitySoundEffectPacket { - Check.argCondition(soundEvent == null && soundName == null, "soundEvent and soundName cannot both be null"); - Check.argCondition(soundEvent != null && soundName != null, "soundEvent and soundName cannot both be present"); - Check.argCondition(soundName == null && range != null, "range cannot be present if soundName is null"); - } - - public EntitySoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Sound.Source source, - int entityId, float volume, float pitch, long seed) { - this(soundEvent, null, range, source, entityId, volume, pitch, seed); - } - - public EntitySoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Sound.Source source, - int entityId, float volume, float pitch, long seed) { - this(null, soundName, range, source, entityId, volume, pitch, seed); - } - public EntitySoundEffectPacket(@NotNull NetworkBuffer reader) { - this(fromReader(reader)); - } - - private EntitySoundEffectPacket(@NotNull EntitySoundEffectPacket packet) { - this(packet.soundEvent, packet.soundName, packet.range, packet.source, packet.entityId, packet.volume, packet.pitch, packet.seed); - } - - private static @NotNull EntitySoundEffectPacket fromReader(@NotNull NetworkBuffer reader) { - int soundId = reader.read(VAR_INT); - SoundEvent soundEvent; - String soundName; - Float range = null; - if (soundId == 0) { - soundEvent = null; - soundName = reader.read(STRING); - range = reader.readOptional(FLOAT); - } else { - soundEvent = SoundEvent.fromId(soundId - 1); - soundName = null; - } - return new EntitySoundEffectPacket( - soundEvent, - soundName, - range, + this(reader.read(SoundEvent.NETWORK_TYPE), reader.readEnum(Sound.Source.class), reader.read(VAR_INT), reader.read(FLOAT), reader.read(FLOAT), - reader.read(LONG) - ); + reader.read(LONG)); } @Override public void write(@NotNull NetworkBuffer writer) { - if (soundEvent != null) { - writer.write(VAR_INT, soundEvent.id() + 1); - } else { - writer.write(VAR_INT, 0); - writer.write(STRING, soundName); - writer.writeOptional(FLOAT, range); - } + writer.write(SoundEvent.NETWORK_TYPE, soundEvent); writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(VAR_INT, entityId); writer.write(FLOAT, volume); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java index 798ded661..4b5dbb42d 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java @@ -17,23 +17,23 @@ public record ExplosionPacket(double x, double y, double z, float radius, @NotNull BlockInteraction blockInteraction, int smallParticleId, byte @NotNull [] smallParticleData, int largeParticleId, byte @NotNull [] largeParticleData, - @NotNull String soundName, boolean hasFixedSoundRange, float soundRange) implements ServerPacket.Play { + @NotNull SoundEvent sound) implements ServerPacket.Play { + public static final SoundEvent DEFAULT_SOUND = SoundEvent.ENTITY_GENERIC_EXPLODE; + private static @NotNull ExplosionPacket fromReader(@NotNull NetworkBuffer reader) { double x = reader.read(DOUBLE), y = reader.read(DOUBLE), z = reader.read(DOUBLE); float radius = reader.read(FLOAT); byte[] records = reader.readBytes(reader.read(VAR_INT) * 3); float playerMotionX = reader.read(FLOAT), playerMotionY = reader.read(FLOAT), playerMotionZ = reader.read(FLOAT); - BlockInteraction blockInteraction = BlockInteraction.values()[reader.read(VAR_INT)]; + BlockInteraction blockInteraction = reader.readEnum(BlockInteraction.class); int smallParticleId = reader.read(VAR_INT); byte[] smallParticleData = readParticleData(reader, Particle.fromId(smallParticleId)); int largeParticleId = reader.read(VAR_INT); byte[] largeParticleData = readParticleData(reader, Particle.fromId(largeParticleId)); - String soundName = reader.read(STRING); - boolean hasFixedSoundRange = reader.read(BOOLEAN); - float soundRange = hasFixedSoundRange ? reader.read(FLOAT) : 0; + SoundEvent sound = reader.read(SoundEvent.NETWORK_TYPE); return new ExplosionPacket(x, y, z, radius, records, playerMotionX, playerMotionY, playerMotionZ, blockInteraction, smallParticleId, smallParticleData, largeParticleId, largeParticleData, - soundName, hasFixedSoundRange, soundRange); + sound); } private static byte @NotNull [] readParticleData(@NotNull NetworkBuffer reader, Particle particle) { @@ -75,13 +75,13 @@ public record ExplosionPacket(double x, double y, double z, float radius, this(x, y, z, radius, records, playerMotionX, playerMotionY, playerMotionZ, BlockInteraction.DESTROY, Particle.EXPLOSION.id(), new byte[] {}, Particle.EXPLOSION_EMITTER.id(), new byte[] {}, - SoundEvent.ENTITY_GENERIC_EXPLODE.name(), false, 0); + DEFAULT_SOUND); } private ExplosionPacket(@NotNull ExplosionPacket packet) { this(packet.x, packet.y, packet.z, packet.radius, packet.records, packet.playerMotionX, packet.playerMotionY, packet.playerMotionZ, packet.blockInteraction, packet.smallParticleId, packet.smallParticleData, packet.largeParticleId, packet.largeParticleData, - packet.soundName, packet.hasFixedSoundRange, packet.soundRange); + packet.sound); } @Override @@ -100,9 +100,7 @@ public record ExplosionPacket(double x, double y, double z, float radius, writer.write(RAW_BYTES, smallParticleData); writer.write(VAR_INT, largeParticleId); writer.write(RAW_BYTES, largeParticleData); - writer.write(STRING, soundName); - writer.write(BOOLEAN, hasFixedSoundRange); - if (hasFixedSoundRange) writer.write(FLOAT, soundRange); + writer.write(SoundEvent.NETWORK_TYPE, sound); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java index 831c157a4..488872888 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java @@ -7,17 +7,12 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.sound.SoundEvent; -import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; public record SoundEffectPacket( - // only one of soundEvent and soundName may be present - @Nullable SoundEvent soundEvent, - @Nullable String soundName, - @Nullable Float range, // Only allowed with soundName + @NotNull SoundEvent soundEvent, @NotNull Source source, int x, int y, @@ -27,67 +22,25 @@ public record SoundEffectPacket( long seed ) implements ServerPacket.Play { - public SoundEffectPacket { - Check.argCondition(soundEvent == null && soundName == null, "soundEvent and soundName cannot both be null"); - Check.argCondition(soundEvent != null && soundName != null, "soundEvent and soundName cannot both be present"); - Check.argCondition(soundName == null && range != null, "range cannot be present if soundName is null"); + public SoundEffectPacket(@NotNull SoundEvent soundEvent, @NotNull Source source, @NotNull Point position, float volume, float pitch, long seed) { + this(soundEvent, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed); } - private static @NotNull SoundEffectPacket fromReader(@NotNull NetworkBuffer reader) { - int soundId = reader.read(VAR_INT); - SoundEvent soundEvent; - String soundName; - Float range = null; - if (soundId == 0) { - soundEvent = null; - soundName = reader.read(STRING); - range = reader.readOptional(FLOAT); - } else { - soundEvent = SoundEvent.fromId(soundId - 1); - soundName = null; - } - return new SoundEffectPacket( - soundEvent, - soundName, - range, + public SoundEffectPacket(@NotNull NetworkBuffer reader) { + this(reader.read(SoundEvent.NETWORK_TYPE), reader.readEnum(Source.class), reader.read(INT) * 8, reader.read(INT) * 8, reader.read(INT) * 8, reader.read(FLOAT), reader.read(FLOAT), - reader.read(LONG) - ); + reader.read(LONG)); } - public SoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Source source, - @NotNull Point position, float volume, float pitch, long seed) { - this(soundEvent, null, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed); - } - - public SoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Source source, - @NotNull Point position, float volume, float pitch, long seed) { - this(null, soundName, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed); - } - - public SoundEffectPacket(@NotNull NetworkBuffer reader) { - this(fromReader(reader)); - } - - private SoundEffectPacket(@NotNull SoundEffectPacket packet) { - this(packet.soundEvent, packet.soundName, packet.range, packet.source, - packet.x, packet.y, packet.z, packet.volume, packet.pitch, packet.seed); - } @Override public void write(@NotNull NetworkBuffer writer) { - if (soundEvent != null) { - writer.write(VAR_INT, soundEvent.id() + 1); - } else { - writer.write(VAR_INT, 0); - writer.write(STRING, soundName); - writer.writeOptional(FLOAT, range); - } + writer.write(SoundEvent.NETWORK_TYPE, soundEvent); writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(INT, x * 8); writer.write(INT, y * 8); diff --git a/src/main/java/net/minestom/server/sound/BuiltinSoundEvent.java b/src/main/java/net/minestom/server/sound/BuiltinSoundEvent.java new file mode 100644 index 000000000..9cfbfd894 --- /dev/null +++ b/src/main/java/net/minestom/server/sound/BuiltinSoundEvent.java @@ -0,0 +1,58 @@ +package net.minestom.server.sound; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.registry.Registry; +import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +record BuiltinSoundEvent(NamespaceID namespace, int id) implements StaticProtocolObject, SoundEvent { + private static final Registry.Container CONTAINER = Registry.createStaticContainer(Registry.Resource.SOUNDS, + (namespace, properties) -> new BuiltinSoundEvent(NamespaceID.from(namespace), properties.getInt("id"))); + + public static final NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type<>() { + @Override + public void write(@NotNull NetworkBuffer buffer, SoundEvent value) { + switch (value) { + case BuiltinSoundEvent soundEvent -> buffer.write(NetworkBuffer.VAR_INT, soundEvent.id + 1); + case CustomSoundEvent soundEvent -> { + buffer.write(NetworkBuffer.VAR_INT, 0); // Custom sound + buffer.write(NetworkBuffer.STRING, soundEvent.name()); + buffer.writeOptional(NetworkBuffer.FLOAT, soundEvent.range()); + } + } + } + + @Override + public SoundEvent read(@NotNull NetworkBuffer buffer) { + int id = buffer.read(NetworkBuffer.VAR_INT) - 1; + if (id != -1) return getId(id); + + NamespaceID namespace = NamespaceID.from(buffer.read(NetworkBuffer.STRING)); + return new CustomSoundEvent(namespace, buffer.readOptional(NetworkBuffer.FLOAT)); + } + }; + + static SoundEvent get(@NotNull String namespace) { + return CONTAINER.get(namespace); + } + + static SoundEvent getSafe(@NotNull String namespace) { + return CONTAINER.getSafe(namespace); + } + + static SoundEvent getId(int id) { + return CONTAINER.getId(id); + } + + static Collection values() { + return CONTAINER.values(); + } + + @Override + public String toString() { + return name(); + } +} diff --git a/src/main/java/net/minestom/server/sound/CustomSoundEvent.java b/src/main/java/net/minestom/server/sound/CustomSoundEvent.java new file mode 100644 index 000000000..a7c1e9dd2 --- /dev/null +++ b/src/main/java/net/minestom/server/sound/CustomSoundEvent.java @@ -0,0 +1,8 @@ +package net.minestom.server.sound; + +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +record CustomSoundEvent(@NotNull NamespaceID namespace, @Nullable Float range) implements SoundEvent { +} diff --git a/src/main/java/net/minestom/server/sound/SoundEvent.java b/src/main/java/net/minestom/server/sound/SoundEvent.java index 842790dc4..16d143299 100644 --- a/src/main/java/net/minestom/server/sound/SoundEvent.java +++ b/src/main/java/net/minestom/server/sound/SoundEvent.java @@ -2,33 +2,67 @@ package net.minestom.server.sound; import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; -import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.registry.ProtocolObject; import net.minestom.server.utils.NamespaceID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; -public sealed interface SoundEvent extends StaticProtocolObject, Sound.Type, SoundEvents permits SoundEventImpl { +/** + * Can represent a builtin/vanilla sound or a custom sound. + */ +public sealed interface SoundEvent extends ProtocolObject, Sound.Type, SoundEvents permits BuiltinSoundEvent, CustomSoundEvent { - static @NotNull Collection<@NotNull SoundEvent> values() { - return SoundEventImpl.values(); + @NotNull NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.lazy(() -> BuiltinSoundEvent.NETWORK_TYPE); //todo what is the init issue here?? + + static @NotNull Collection values() { + return BuiltinSoundEvent.values(); } + /** + * Get a builtin sound event by its namespace ID. Will never return a custom/resource pack sound. + * + * @param namespaceID the namespace ID of the sound event + * @return the sound event, or null if not found + */ static @Nullable SoundEvent fromNamespaceId(@NotNull String namespaceID) { - return SoundEventImpl.getSafe(namespaceID); + return BuiltinSoundEvent.getSafe(namespaceID); } + /** + * Get a builtin sound event by its namespace ID. Will never return a custom/resource pack sound. + * + * @param namespaceID the namespace ID of the sound event + * @return the sound event, or null if not found + */ static @Nullable SoundEvent fromNamespaceId(@NotNull NamespaceID namespaceID) { return fromNamespaceId(namespaceID.asString()); } + /** + * Get a builtin sound event by its protocol ID. Will never return a custom/resource pack sound. + * + * @param id the ID of the sound event + * @return the sound event, or null if not found + */ static @Nullable SoundEvent fromId(int id) { - return SoundEventImpl.getId(id); + return BuiltinSoundEvent.getId(id); + } + + /** + * Create a custom sound event. The {@link NamespaceID} should match a sound provided in the resource pack. + * @param namespaceID the namespace ID of the custom sound event + * @param range the range of the sound event, or null for (legacy) dynamic range + * @return the custom sound event + */ + static @NotNull SoundEvent of(@NotNull NamespaceID namespaceID, @Nullable Float range) { + return new CustomSoundEvent(namespaceID, range); } @Override default @NotNull Key key() { - return StaticProtocolObject.super.key(); + return ProtocolObject.super.key(); } } diff --git a/src/main/java/net/minestom/server/sound/SoundEventImpl.java b/src/main/java/net/minestom/server/sound/SoundEventImpl.java deleted file mode 100644 index fb7bd952b..000000000 --- a/src/main/java/net/minestom/server/sound/SoundEventImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.minestom.server.sound; - -import net.minestom.server.registry.Registry; -import net.minestom.server.utils.NamespaceID; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; - -record SoundEventImpl(NamespaceID namespace, int id) implements SoundEvent { - private static final Registry.Container CONTAINER = Registry.createStaticContainer(Registry.Resource.SOUNDS, - (namespace, properties) -> new SoundEventImpl(NamespaceID.from(namespace), properties.getInt("id"))); - - static SoundEvent get(@NotNull String namespace) { - return CONTAINER.get(namespace); - } - - static SoundEvent getSafe(@NotNull String namespace) { - return CONTAINER.getSafe(namespace); - } - - static SoundEvent getId(int id) { - return CONTAINER.getId(id); - } - - static Collection values() { - return CONTAINER.values(); - } - - @Override - public String toString() { - return name(); - } -} diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java index 2f3d612ad..f7b8fb29a 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java @@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.*; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.UniqueIdUtils; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -154,6 +155,21 @@ public interface BinaryTagSerializer { ); BinaryTagSerializer ITEM = COMPOUND.map(ItemStack::fromItemNBT, ItemStack::toItemNBT); + BinaryTagSerializer UUID = new BinaryTagSerializer<>() { + @Override + public @NotNull BinaryTag write(java.util.@NotNull UUID value) { + return UniqueIdUtils.toNbt(value); + } + + @Override + public java.util.@NotNull UUID read(@NotNull BinaryTag tag) { + if (!(tag instanceof IntArrayBinaryTag intArrayTag)) { + throw new IllegalArgumentException("unexpected uuid type: " + tag.type()); + } + return UniqueIdUtils.fromNbt(intArrayTag); + } + }; + @NotNull BinaryTag write(@NotNull T value); @NotNull T read(@NotNull BinaryTag tag); From ff0d121937dfa34fac5aaadb18e0fa748ded3c54 Mon Sep 17 00:00:00 2001 From: mworzala Date: Fri, 19 Apr 2024 08:26:25 -0400 Subject: [PATCH 30/46] feat: generate RecipeType (for ids) --- .../java/net/minestom/codegen/Generators.java | 5 +- .../codegen/recipe/RecipeTypeGenerator.java | 113 ++++++++++++++++++ .../minestom/server/recipe/RecipeType.java | 76 ++++++++++++ .../net/minestom/server/entity/Player.java | 2 +- .../server/play/DeclareRecipesPacket.java | 41 +++---- .../server/recipe/BlastingRecipe.java | 2 +- .../server/recipe/CampfireCookingRecipe.java | 2 +- .../net/minestom/server/recipe/Recipe.java | 32 ++--- .../server/recipe/RecipeConversion.java | 18 +-- .../minestom/server/recipe/RecipeManager.java | 3 +- .../minestom/server/recipe/ShapedRecipe.java | 2 +- .../server/recipe/ShapelessRecipe.java | 2 +- .../server/recipe/SmeltingRecipe.java | 2 +- .../recipe/SmithingTransformRecipe.java | 2 +- .../server/recipe/SmithingTrimRecipe.java | 3 +- .../minestom/server/recipe/SmokingRecipe.java | 2 +- .../server/recipe/StonecutterRecipe.java | 2 +- .../net/minestom/server/sound/SoundEvent.java | 11 ++ 18 files changed, 255 insertions(+), 65 deletions(-) create mode 100644 code-generators/src/main/java/net/minestom/codegen/recipe/RecipeTypeGenerator.java create mode 100644 src/autogenerated/java/net/minestom/server/recipe/RecipeType.java diff --git a/code-generators/src/main/java/net/minestom/codegen/Generators.java b/code-generators/src/main/java/net/minestom/codegen/Generators.java index f9bf04092..6a980f559 100644 --- a/code-generators/src/main/java/net/minestom/codegen/Generators.java +++ b/code-generators/src/main/java/net/minestom/codegen/Generators.java @@ -2,6 +2,7 @@ package net.minestom.codegen; import net.minestom.codegen.color.DyeColorGenerator; import net.minestom.codegen.fluid.FluidGenerator; +import net.minestom.codegen.recipe.RecipeTypeGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,9 +19,11 @@ public class Generators { } File outputFolder = new File(args[0]); - // Generate DyeColors + // Special generators new DyeColorGenerator(resource("dye_colors.json"), outputFolder).generate(); + new RecipeTypeGenerator(resource("recipe_types.json"), outputFolder).generate(); + // Generic protocol object var generator = new CodeGenerator(outputFolder); generator.generate(resource("blocks.json"), "net.minestom.server.instance.block", "Block", "BlockImpl", "Blocks"); generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "Materials"); diff --git a/code-generators/src/main/java/net/minestom/codegen/recipe/RecipeTypeGenerator.java b/code-generators/src/main/java/net/minestom/codegen/recipe/RecipeTypeGenerator.java new file mode 100644 index 000000000..1a8ecbc07 --- /dev/null +++ b/code-generators/src/main/java/net/minestom/codegen/recipe/RecipeTypeGenerator.java @@ -0,0 +1,113 @@ +package net.minestom.codegen.recipe; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.squareup.javapoet.*; +import net.minestom.codegen.MinestomCodeGenerator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.lang.model.element.Modifier; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Comparator; +import java.util.List; +import java.util.stream.StreamSupport; + +public class RecipeTypeGenerator extends MinestomCodeGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(RecipeTypeGenerator.class); + private final InputStream recipeTypesFile; + private final File outputFolder; + + public RecipeTypeGenerator(@Nullable InputStream recipeTypesFile, @NotNull File outputFolder) { + this.recipeTypesFile = recipeTypesFile; + this.outputFolder = outputFolder; + } + + @Override + public void generate() { + if (recipeTypesFile == null) { + LOGGER.error("Failed to find recipe_types.json."); + LOGGER.error("Stopped code generation for recipe types."); + return; + } + if (!outputFolder.exists() && !outputFolder.mkdirs()) { + LOGGER.error("Output folder for code generation does not exist and could not be created."); + return; + } + + // Important classes we use alot + JsonArray recipeTypes = GSON.fromJson(new InputStreamReader(recipeTypesFile), JsonArray.class); + ClassName recipeTypeCN = ClassName.get("net.minestom.server.recipe", "RecipeType"); + TypeSpec.Builder recipeTypeEnum = TypeSpec.enumBuilder(recipeTypeCN) + .addSuperinterface(ClassName.get("net.minestom.server.registry", "StaticProtocolObject")) + .addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName()); + ClassName namespaceIdCN = ClassName.get("net.minestom.server.utils", "NamespaceID"); + + ClassName networkBufferCN = ClassName.get("net.minestom.server.network", "NetworkBuffer"); + ParameterizedTypeName networkBufferTypeCN = ParameterizedTypeName.get(networkBufferCN.nestedClass("Type"), recipeTypeCN); + + // Fields + recipeTypeEnum.addFields( + List.of( + FieldSpec.builder(networkBufferTypeCN, "NETWORK_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("$T.fromEnum($T.class)", networkBufferCN, recipeTypeCN) + .build(), + FieldSpec.builder(namespaceIdCN, "namespace", Modifier.PRIVATE, Modifier.FINAL).build() + ) + ); + + // Methods + recipeTypeEnum.addMethods( + List.of( + // Constructor + MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(namespaceIdCN, "namespace").addAnnotation(NotNull.class).build()) + .addStatement("this.namespace = namespace") + .build(), + MethodSpec.methodBuilder("namespace") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(NotNull.class) + .addAnnotation(Override.class) + .returns(namespaceIdCN) + .addStatement("return this.namespace") + .build(), + MethodSpec.methodBuilder("id") + .addModifiers(Modifier.PUBLIC) + .returns(TypeName.INT) + .addAnnotation(Override.class) + .addStatement("return this.ordinal()") + .build() + ) + ); + + // Use data + for (JsonObject recipeTypeObject : StreamSupport.stream(recipeTypes.spliterator(), true).map(JsonElement::getAsJsonObject).sorted(Comparator.comparingInt(o -> o.get("id").getAsInt())).toList()) { + String recipeTypeName = recipeTypeObject.get("name").getAsString(); + recipeTypeEnum.addEnumConstant(recipeTypeConstantName(recipeTypeName), TypeSpec.anonymousClassBuilder( + "$T.from($S)", + namespaceIdCN, recipeTypeName + ).build() + ); + } + + // Write files to outputFolder + writeFiles( + List.of( + JavaFile.builder("net.minestom.server.recipe", recipeTypeEnum.build()) + .indent(" ") + .skipJavaLangImports(true) + .build() + ), + outputFolder + ); + } + + private static @NotNull String recipeTypeConstantName(@NotNull String name) { + return toConstant(name).replace("CRAFTING_", ""); + } +} diff --git a/src/autogenerated/java/net/minestom/server/recipe/RecipeType.java b/src/autogenerated/java/net/minestom/server/recipe/RecipeType.java new file mode 100644 index 000000000..d00a60f10 --- /dev/null +++ b/src/autogenerated/java/net/minestom/server/recipe/RecipeType.java @@ -0,0 +1,76 @@ +package net.minestom.server.recipe; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.registry.StaticProtocolObject; +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.NotNull; + +/** + * AUTOGENERATED by RecipeTypeGenerator + */ +public enum RecipeType implements StaticProtocolObject { + SHAPED(NamespaceID.from("minecraft:crafting_shaped")), + + SHAPELESS(NamespaceID.from("minecraft:crafting_shapeless")), + + SPECIAL_ARMORDYE(NamespaceID.from("minecraft:crafting_special_armordye")), + + SPECIAL_BOOKCLONING(NamespaceID.from("minecraft:crafting_special_bookcloning")), + + SPECIAL_MAPCLONING(NamespaceID.from("minecraft:crafting_special_mapcloning")), + + SPECIAL_MAPEXTENDING(NamespaceID.from("minecraft:crafting_special_mapextending")), + + SPECIAL_FIREWORK_ROCKET(NamespaceID.from("minecraft:crafting_special_firework_rocket")), + + SPECIAL_FIREWORK_STAR(NamespaceID.from("minecraft:crafting_special_firework_star")), + + SPECIAL_FIREWORK_STAR_FADE(NamespaceID.from("minecraft:crafting_special_firework_star_fade")), + + SPECIAL_TIPPEDARROW(NamespaceID.from("minecraft:crafting_special_tippedarrow")), + + SPECIAL_BANNERDUPLICATE(NamespaceID.from("minecraft:crafting_special_bannerduplicate")), + + SPECIAL_SHIELDDECORATION(NamespaceID.from("minecraft:crafting_special_shielddecoration")), + + SPECIAL_SHULKERBOXCOLORING(NamespaceID.from("minecraft:crafting_special_shulkerboxcoloring")), + + SPECIAL_SUSPICIOUSSTEW(NamespaceID.from("minecraft:crafting_special_suspiciousstew")), + + SPECIAL_REPAIRITEM(NamespaceID.from("minecraft:crafting_special_repairitem")), + + SMELTING(NamespaceID.from("minecraft:smelting")), + + BLASTING(NamespaceID.from("minecraft:blasting")), + + SMOKING(NamespaceID.from("minecraft:smoking")), + + CAMPFIRE_COOKING(NamespaceID.from("minecraft:campfire_cooking")), + + STONECUTTING(NamespaceID.from("minecraft:stonecutting")), + + SMITHING_TRANSFORM(NamespaceID.from("minecraft:smithing_transform")), + + SMITHING_TRIM(NamespaceID.from("minecraft:smithing_trim")), + + DECORATED_POT(NamespaceID.from("minecraft:crafting_decorated_pot")); + + public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.fromEnum(RecipeType.class); + + private final NamespaceID namespace; + + RecipeType(@NotNull NamespaceID namespace) { + this.namespace = namespace; + } + + @NotNull + @Override + public NamespaceID namespace() { + return this.namespace; + } + + @Override + public int id() { + return this.ordinal(); + } +} diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index cfb16705f..c789b36c4 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -354,7 +354,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, for (Recipe recipe : recipeManager.getRecipes()) { if (!recipe.shouldShow(this)) continue; - recipesIdentifier.add(recipe.getRecipeId()); + recipesIdentifier.add(recipe.id()); } if (!recipesIdentifier.isEmpty()) { UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket(0, diff --git a/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java index 7c3f4913a..37560d5ed 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/DeclareRecipesPacket.java @@ -5,6 +5,7 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.recipe.RecipeCategory; +import net.minestom.server.recipe.RecipeType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,7 +44,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem public void write(@NotNull NetworkBuffer writer) { writer.writeCollection(recipes, (bWriter, recipe) -> { bWriter.write(STRING, recipe.recipeId()); - bWriter.write(STRING, recipe.type()); + bWriter.write(RecipeType.NETWORK_TYPE, recipe.type()); bWriter.write(recipe); }); } @@ -58,7 +59,7 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem DeclaredSmeltingRecipe, DeclaredBlastingRecipe, DeclaredSmokingRecipe, DeclaredCampfireCookingRecipe, DeclaredStonecutterRecipe, DeclaredSmithingTrimRecipe, DeclaredSmithingTransformRecipe { - @NotNull String type(); + @NotNull RecipeType type(); @NotNull String recipeId(); } @@ -82,8 +83,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "crafting_shapeless"; + public @NotNull RecipeType type() { + return RecipeType.SHAPELESS; } } @@ -133,8 +134,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "crafting_shaped"; + public @NotNull RecipeType type() { + return RecipeType.SHAPED; } } @@ -160,8 +161,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "smelting"; + public @NotNull RecipeType type() { + return RecipeType.SMELTING; } } @@ -187,8 +188,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "blasting"; + public @NotNull RecipeType type() { + return RecipeType.BLASTING; } } @@ -214,8 +215,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "smoking"; + public @NotNull RecipeType type() { + return RecipeType.SMOKING; } } @@ -241,8 +242,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "campfire_cooking"; + public @NotNull RecipeType type() { + return RecipeType.CAMPFIRE_COOKING; } } @@ -261,8 +262,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "stonecutting"; + public @NotNull RecipeType type() { + return RecipeType.STONECUTTING; } } @@ -282,8 +283,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "smithing_transform"; + public @NotNull RecipeType type() { + return RecipeType.SMITHING_TRANSFORM; } } @@ -301,8 +302,8 @@ public record DeclareRecipesPacket(@NotNull List recipes) implem } @Override - public @NotNull String type() { - return "smithing_trim"; + public @NotNull RecipeType type() { + return RecipeType.SMITHING_TRIM; } } diff --git a/src/main/java/net/minestom/server/recipe/BlastingRecipe.java b/src/main/java/net/minestom/server/recipe/BlastingRecipe.java index 21f43342a..91148daf0 100644 --- a/src/main/java/net/minestom/server/recipe/BlastingRecipe.java +++ b/src/main/java/net/minestom/server/recipe/BlastingRecipe.java @@ -20,7 +20,7 @@ public abstract class BlastingRecipe extends Recipe { float experience, int cookingTime ) { - super(Type.BLASTING, recipeId); + super(RecipeType.BLASTING, recipeId); this.group = group; this.category = category; this.result = result; diff --git a/src/main/java/net/minestom/server/recipe/CampfireCookingRecipe.java b/src/main/java/net/minestom/server/recipe/CampfireCookingRecipe.java index fa8764145..7a9a45b3d 100644 --- a/src/main/java/net/minestom/server/recipe/CampfireCookingRecipe.java +++ b/src/main/java/net/minestom/server/recipe/CampfireCookingRecipe.java @@ -20,7 +20,7 @@ public abstract class CampfireCookingRecipe extends Recipe { float experience, int cookingTime ) { - super(Type.CAMPFIRE_COOKING, recipeId); + super(RecipeType.CAMPFIRE_COOKING, recipeId); this.group = group; this.category = category; this.result = result; diff --git a/src/main/java/net/minestom/server/recipe/Recipe.java b/src/main/java/net/minestom/server/recipe/Recipe.java index 75246bd53..ad183a5d4 100644 --- a/src/main/java/net/minestom/server/recipe/Recipe.java +++ b/src/main/java/net/minestom/server/recipe/Recipe.java @@ -4,36 +4,22 @@ import net.minestom.server.entity.Player; import org.jetbrains.annotations.NotNull; public abstract class Recipe { - protected final Type recipeType; - protected final String recipeId; + protected final RecipeType type; + protected final String id; - protected Recipe(@NotNull Type recipeType, @NotNull String recipeId) { - this.recipeType = recipeType; - this.recipeId = recipeId; + protected Recipe(@NotNull RecipeType type, @NotNull String id) { + this.type = type; + this.id = id; } public abstract boolean shouldShow(@NotNull Player player); @NotNull - public Type getRecipeType() { - return recipeType; + public RecipeType type() { + return type; } - @NotNull - public String getRecipeId() { - return recipeId; + public @NotNull String id() { + return id; } - - public enum Type { - SHAPELESS, - SHAPED, - SMELTING, - BLASTING, - SMOKING, - CAMPFIRE_COOKING, - STONECUTTING, - SMITHING_TRANSFORM, - SMITHING_TRIM - } - } diff --git a/src/main/java/net/minestom/server/recipe/RecipeConversion.java b/src/main/java/net/minestom/server/recipe/RecipeConversion.java index 102f3926b..cbc904d8c 100644 --- a/src/main/java/net/minestom/server/recipe/RecipeConversion.java +++ b/src/main/java/net/minestom/server/recipe/RecipeConversion.java @@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull; final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredShapelessCraftingRecipe shapeless(@NotNull ShapelessRecipe shapelessRecipe) { - return new DeclareRecipesPacket.DeclaredShapelessCraftingRecipe(shapelessRecipe.getRecipeId(), + return new DeclareRecipesPacket.DeclaredShapelessCraftingRecipe(shapelessRecipe.id(), shapelessRecipe.getGroup(), shapelessRecipe.getCategory(), shapelessRecipe.getIngredients(), @@ -14,7 +14,7 @@ final class RecipeConversion { } static @NotNull DeclareRecipesPacket.DeclaredShapedCraftingRecipe shaped(@NotNull ShapedRecipe shapedRecipe) { - return new DeclareRecipesPacket.DeclaredShapedCraftingRecipe(shapedRecipe.getRecipeId(), + return new DeclareRecipesPacket.DeclaredShapedCraftingRecipe(shapedRecipe.id(), shapedRecipe.getGroup(), shapedRecipe.getCategory(), shapedRecipe.getWidth(), @@ -26,7 +26,7 @@ final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredSmeltingRecipe smelting(@NotNull SmeltingRecipe smeltingRecipe) { return new DeclareRecipesPacket.DeclaredSmeltingRecipe( - smeltingRecipe.getRecipeId(), + smeltingRecipe.id(), smeltingRecipe.getGroup(), smeltingRecipe.getCategory(), smeltingRecipe.getIngredient(), @@ -37,7 +37,7 @@ final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredBlastingRecipe blasting(@NotNull BlastingRecipe blastingRecipe) { return new DeclareRecipesPacket.DeclaredBlastingRecipe( - blastingRecipe.getRecipeId(), + blastingRecipe.id(), blastingRecipe.getGroup(), blastingRecipe.getCategory(), blastingRecipe.getIngredient(), @@ -48,7 +48,7 @@ final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredSmokingRecipe smoking(@NotNull SmokingRecipe smokingRecipe) { return new DeclareRecipesPacket.DeclaredSmokingRecipe( - smokingRecipe.getRecipeId(), + smokingRecipe.id(), smokingRecipe.getGroup(), smokingRecipe.getCategory(), smokingRecipe.getIngredient(), @@ -59,7 +59,7 @@ final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredCampfireCookingRecipe campfire(@NotNull CampfireCookingRecipe campfireCookingRecipe) { return new DeclareRecipesPacket.DeclaredCampfireCookingRecipe( - campfireCookingRecipe.getRecipeId(), + campfireCookingRecipe.id(), campfireCookingRecipe.getGroup(), campfireCookingRecipe.getCategory(), campfireCookingRecipe.getIngredient(), @@ -70,7 +70,7 @@ final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredStonecutterRecipe stonecutter(@NotNull StonecutterRecipe stonecuttingRecipe) { return new DeclareRecipesPacket.DeclaredStonecutterRecipe( - stonecuttingRecipe.getRecipeId(), + stonecuttingRecipe.id(), stonecuttingRecipe.getGroup(), stonecuttingRecipe.getIngredient(), stonecuttingRecipe.getResult()); @@ -78,7 +78,7 @@ final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredSmithingTransformRecipe smithingTransform(@NotNull SmithingTransformRecipe smithingTransformRecipe) { return new DeclareRecipesPacket.DeclaredSmithingTransformRecipe( - smithingTransformRecipe.getRecipeId(), + smithingTransformRecipe.id(), smithingTransformRecipe.getTemplate(), smithingTransformRecipe.getBaseIngredient(), smithingTransformRecipe.getAdditionIngredient(), @@ -87,7 +87,7 @@ final class RecipeConversion { static @NotNull DeclareRecipesPacket.DeclaredSmithingTrimRecipe smithingTrim(@NotNull SmithingTrimRecipe smithingTrimRecipe) { return new DeclareRecipesPacket.DeclaredSmithingTrimRecipe( - smithingTrimRecipe.getRecipeId(), + smithingTrimRecipe.id(), smithingTrimRecipe.getTemplate(), smithingTrimRecipe.getBaseIngredient(), smithingTrimRecipe.getAdditionIngredient()); diff --git a/src/main/java/net/minestom/server/recipe/RecipeManager.java b/src/main/java/net/minestom/server/recipe/RecipeManager.java index 80587c262..a7285918f 100644 --- a/src/main/java/net/minestom/server/recipe/RecipeManager.java +++ b/src/main/java/net/minestom/server/recipe/RecipeManager.java @@ -45,7 +45,7 @@ public class RecipeManager { private @NotNull DeclareRecipesPacket createDeclareRecipesPacket() { var entries = new ArrayList(); for (var recipe : recipes) { - entries.add(switch (recipe.recipeType) { + entries.add(switch (recipe.type) { case SHAPELESS -> RecipeConversion.shapeless((ShapelessRecipe) recipe); case SHAPED -> RecipeConversion.shaped((ShapedRecipe) recipe); case SMELTING -> RecipeConversion.smelting((SmeltingRecipe) recipe); @@ -55,6 +55,7 @@ public class RecipeManager { case STONECUTTING -> RecipeConversion.stonecutter((StonecutterRecipe) recipe); case SMITHING_TRANSFORM -> RecipeConversion.smithingTransform((SmithingTransformRecipe) recipe); case SMITHING_TRIM -> RecipeConversion.smithingTrim((SmithingTrimRecipe) recipe); + default -> throw new IllegalStateException("Unhandled recipe type : " + recipe.type); }); } return new DeclareRecipesPacket(List.of()); diff --git a/src/main/java/net/minestom/server/recipe/ShapedRecipe.java b/src/main/java/net/minestom/server/recipe/ShapedRecipe.java index 5fb9ce295..7e195ffaa 100644 --- a/src/main/java/net/minestom/server/recipe/ShapedRecipe.java +++ b/src/main/java/net/minestom/server/recipe/ShapedRecipe.java @@ -25,7 +25,7 @@ public abstract class ShapedRecipe extends Recipe { @NotNull RecipeCategory.Crafting category, @Nullable List ingredients, @NotNull ItemStack result, boolean showNotification) { - super(Type.SHAPED, recipeId); + super(RecipeType.SHAPED, recipeId); this.width = width; this.height = height; this.group = group; diff --git a/src/main/java/net/minestom/server/recipe/ShapelessRecipe.java b/src/main/java/net/minestom/server/recipe/ShapelessRecipe.java index 343b74ece..391029344 100644 --- a/src/main/java/net/minestom/server/recipe/ShapelessRecipe.java +++ b/src/main/java/net/minestom/server/recipe/ShapelessRecipe.java @@ -22,7 +22,7 @@ public abstract class ShapelessRecipe extends Recipe { @Nullable List ingredients, @NotNull ItemStack result ) { - super(Type.SHAPELESS, recipeId); + super(RecipeType.SHAPELESS, recipeId); this.group = group; this.category = category; this.ingredients = Objects.requireNonNullElseGet(ingredients, LinkedList::new); diff --git a/src/main/java/net/minestom/server/recipe/SmeltingRecipe.java b/src/main/java/net/minestom/server/recipe/SmeltingRecipe.java index c01aaee52..35a0a1061 100644 --- a/src/main/java/net/minestom/server/recipe/SmeltingRecipe.java +++ b/src/main/java/net/minestom/server/recipe/SmeltingRecipe.java @@ -20,7 +20,7 @@ public abstract class SmeltingRecipe extends Recipe { float experience, int cookingTime ) { - super(Type.SMELTING, recipeId); + super(RecipeType.SMELTING, recipeId); this.group = group; this.category = category; this.result = result; diff --git a/src/main/java/net/minestom/server/recipe/SmithingTransformRecipe.java b/src/main/java/net/minestom/server/recipe/SmithingTransformRecipe.java index b5eecf824..b1ae9bdff 100644 --- a/src/main/java/net/minestom/server/recipe/SmithingTransformRecipe.java +++ b/src/main/java/net/minestom/server/recipe/SmithingTransformRecipe.java @@ -17,7 +17,7 @@ public abstract class SmithingTransformRecipe extends Recipe { @NotNull DeclareRecipesPacket.Ingredient additionIngredient, @NotNull ItemStack result ) { - super(Type.SMITHING_TRANSFORM, recipeId); + super(RecipeType.SMITHING_TRANSFORM, recipeId); this.template = template; this.baseIngredient = baseIngredient; this.additionIngredient = additionIngredient; diff --git a/src/main/java/net/minestom/server/recipe/SmithingTrimRecipe.java b/src/main/java/net/minestom/server/recipe/SmithingTrimRecipe.java index f1b61dec9..1259392a7 100644 --- a/src/main/java/net/minestom/server/recipe/SmithingTrimRecipe.java +++ b/src/main/java/net/minestom/server/recipe/SmithingTrimRecipe.java @@ -1,6 +1,5 @@ package net.minestom.server.recipe; -import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.DeclareRecipesPacket; import org.jetbrains.annotations.NotNull; @@ -15,7 +14,7 @@ public abstract class SmithingTrimRecipe extends Recipe { @NotNull DeclareRecipesPacket.Ingredient baseIngredient, @NotNull DeclareRecipesPacket.Ingredient additionIngredient ) { - super(Type.SMITHING_TRIM, recipeId); + super(RecipeType.SMITHING_TRIM, recipeId); this.template = template; this.baseIngredient = baseIngredient; this.additionIngredient = additionIngredient; diff --git a/src/main/java/net/minestom/server/recipe/SmokingRecipe.java b/src/main/java/net/minestom/server/recipe/SmokingRecipe.java index 928b61efa..ef27f1a58 100644 --- a/src/main/java/net/minestom/server/recipe/SmokingRecipe.java +++ b/src/main/java/net/minestom/server/recipe/SmokingRecipe.java @@ -20,7 +20,7 @@ public abstract class SmokingRecipe extends Recipe { float experience, int cookingTime ) { - super(Type.SMOKING, recipeId); + super(RecipeType.SMOKING, recipeId); this.group = group; this.category = category; this.result = result; diff --git a/src/main/java/net/minestom/server/recipe/StonecutterRecipe.java b/src/main/java/net/minestom/server/recipe/StonecutterRecipe.java index a2dce2d45..78dc4db84 100644 --- a/src/main/java/net/minestom/server/recipe/StonecutterRecipe.java +++ b/src/main/java/net/minestom/server/recipe/StonecutterRecipe.java @@ -15,7 +15,7 @@ public abstract class StonecutterRecipe extends Recipe { @NotNull DeclareRecipesPacket.Ingredient ingredient, @NotNull ItemStack result ) { - super(Type.STONECUTTING, recipeId); + super(RecipeType.STONECUTTING, recipeId); this.group = group; this.ingredient = ingredient; this.result = result; diff --git a/src/main/java/net/minestom/server/sound/SoundEvent.java b/src/main/java/net/minestom/server/sound/SoundEvent.java index 16d143299..4a6898e43 100644 --- a/src/main/java/net/minestom/server/sound/SoundEvent.java +++ b/src/main/java/net/minestom/server/sound/SoundEvent.java @@ -51,6 +51,17 @@ public sealed interface SoundEvent extends ProtocolObject, Sound.Type, SoundEven return BuiltinSoundEvent.getId(id); } + /** + * Create a custom sound event. The namespace should match a sound provided in the resource pack. + * + * @param namespaceID the namespace ID of the custom sound event + * @param range the range of the sound event, or null for (legacy) dynamic range + * @return the custom sound event + */ + static @NotNull SoundEvent of(@NotNull String namespaceID, @Nullable Float range) { + return new CustomSoundEvent(NamespaceID.from(namespaceID), range); + } + /** * Create a custom sound event. The {@link NamespaceID} should match a sound provided in the resource pack. * @param namespaceID the namespace ID of the custom sound event From 964b519146019d2985a1db7a8493ce4da565fa7c Mon Sep 17 00:00:00 2001 From: mworzala Date: Fri, 19 Apr 2024 13:58:39 -0400 Subject: [PATCH 31/46] chore: rebase fixes on inventory branch --- .../server/inventory/TransactionOperator.java | 32 ++++++++----------- .../inventory/click/ClickProcessors.java | 10 +++--- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/TransactionOperator.java b/src/main/java/net/minestom/server/inventory/TransactionOperator.java index 21204cfc7..b0372dde7 100644 --- a/src/main/java/net/minestom/server/inventory/TransactionOperator.java +++ b/src/main/java/net/minestom/server/inventory/TransactionOperator.java @@ -1,7 +1,6 @@ package net.minestom.server.inventory; import net.minestom.server.item.ItemStack; -import net.minestom.server.item.StackingRule; import org.jetbrains.annotations.NotNull; import java.util.function.BiPredicate; @@ -43,23 +42,22 @@ public interface TransactionOperator extends UnaryOperator { final ItemStack left = entry.left(); final ItemStack right = entry.right(); - final StackingRule rule = StackingRule.get(); // Quick exit if the right is air (nothing can be transferred anyway) // If the left is air then we know it can be transferred, but it can also be transferred if they're stackable // and left isn't full, even if left isn't air. - if (right.isAir() || (!left.isAir() && !(rule.canBeStacked(left, right) && rule.getAmount(left) < rule.getMaxSize(left)))) { + if (right.isAir() || (!left.isAir() && !(left.isSimilar(right) && left.amount() < left.maxStackSize()))) { return null; } - int leftAmount = left.isAir() ? 0 : rule.getAmount(left); - int rightAmount = rule.getAmount(right); + int leftAmount = left.isAir() ? 0 : left.amount(); + int rightAmount = right.amount(); - int addedAmount = Math.min(Math.min(rightAmount, count), rule.getMaxSize(left) - leftAmount); + int addedAmount = Math.min(Math.min(rightAmount, count), left.maxStackSize() - leftAmount); if (addedAmount == 0) return null; - return new Entry(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); + return new Entry((left.isAir() ? right : left).withAmount(leftAmount + addedAmount), right.withAmount(rightAmount - addedAmount)); }; } @@ -70,21 +68,20 @@ public interface TransactionOperator extends UnaryOperator { final ItemStack left = entry.left(); final ItemStack right = entry.right(); - final StackingRule rule = StackingRule.get(); // Quick exit if the right is air (nothing can be transferred anyway) // If the left is air then we know it can be transferred, but it can also be transferred if they're stackable // and left isn't full, even if left isn't air. - if (right.isAir() || (!left.isAir() && !(rule.canBeStacked(left, right) && rule.getAmount(left) < rule.getMaxSize(left)))) { + if (right.isAir() || (!left.isAir() && !(left.isSimilar(right) && left.amount() < left.maxStackSize()))) { return null; } - int leftAmount = left.isAir() ? 0 : rule.getAmount(left); - int rightAmount = rule.getAmount(right); + int leftAmount = left.isAir() ? 0 : left.amount(); + int rightAmount = right.amount(); - int addedAmount = Math.min(rightAmount, rule.getMaxSize(left) - leftAmount); + int addedAmount = Math.min(rightAmount, left.maxStackSize() - leftAmount); - return new Entry(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount)); + return new Entry((left.isAir() ? right : left).withAmount(leftAmount + addedAmount), right.withAmount(rightAmount - addedAmount)); }; /** @@ -99,14 +96,13 @@ public interface TransactionOperator extends UnaryOperator { final ItemStack left = entry.left(); final ItemStack right = entry.right(); - final StackingRule rule = StackingRule.get(); - if (right.isAir() || !rule.canBeStacked(left, right)) { + if (right.isAir() || !left.isSimilar(right)) { return null; } - final int leftAmount = rule.getAmount(left); - final int rightAmount = rule.getAmount(right); + final int leftAmount = left.amount(); + final int rightAmount = right.amount(); final int subtracted = Math.min(leftAmount, rightAmount); - return new Entry(rule.apply(left, leftAmount - subtracted), rule.apply(right, rightAmount - subtracted)); + return new Entry(left.withAmount(leftAmount - subtracted), right.withAmount(rightAmount - subtracted)); }; default Entry apply(@NotNull ItemStack left, @NotNull ItemStack right) { diff --git a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java index f34121662..e57f23b9a 100644 --- a/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java +++ b/src/main/java/net/minestom/server/inventory/click/ClickProcessors.java @@ -49,11 +49,11 @@ public final class ClickProcessors { if (cursor.isAir() && clickedItem.isAir()) return List.of(); // Both are air, no changes if (cursor.isAir()) { // Take half (rounded up) of the clicked item - int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d); + int newAmount = (int) Math.ceil(clickedItem.amount() / 2d); final TransactionOperator.Entry cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem); if (cursorSlot == null) return List.of(); return List.of(new Main(slot, cursorSlot.right()), new Cursor(cursorSlot.left())); - } else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over + } else if (clickedItem.isAir() || clickedItem.isSimilar(cursor)) { // Can add, transfer one over final TransactionOperator.Entry slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor); if (slotCursor == null) return List.of(); return List.of(new Main(slot, slotCursor.left()), new Cursor(slotCursor.right())); @@ -65,7 +65,7 @@ public final class ClickProcessors { public static @NotNull List middleClick(int slot, @NotNull Click.Getter getter) { final ItemStack item = getter.get(slot); if (!getter.cursor().isAir() || item.isAir()) return List.of(); - return List.of(new Cursor(RULE.apply(item, RULE.getMaxSize(item)))); + return List.of(new Cursor(item.withAmount(item.maxStackSize()))); } public static @NotNull List shiftClick(int slot, @NotNull List slots, @NotNull Click.Getter getter) { @@ -90,9 +90,9 @@ public final class ClickProcessors { if (cursor.isAir()) return List.of(); final TransactionType unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, - (left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots); + (left, right) -> left.amount() < left.maxStackSize()), slots); final TransactionType stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT, - (left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots); + (left, right) -> left.amount() == left.maxStackSize()), slots); final TransactionType.Entry result = TransactionType.join(unstacked, stacked).apply(cursor, getter::get); List changes = new ArrayList<>(); From a50014ad8ce53bad5ea5ce884aea54cba845ffd4 Mon Sep 17 00:00:00 2001 From: mworzala Date: Sat, 20 Apr 2024 20:29:53 -0400 Subject: [PATCH 32/46] fix: do not send close inventory packet when opening a new inventory while one is open --- .../serializer/nbt/NbtComponentSerializerImpl.java | 7 ++++++- src/main/java/net/minestom/server/entity/Player.java | 10 +++++----- .../net/minestom/server/listener/WindowListener.java | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java index 7e36842e6..610a72cf1 100644 --- a/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java +++ b/src/main/java/net/minestom/server/adventure/serializer/nbt/NbtComponentSerializerImpl.java @@ -67,7 +67,9 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { } else { // Try to infer the type from the fields present. Set keys = compound.keySet(); - if (keys.contains("text")) { + if (keys.isEmpty()) { + return Component.empty(); + } else if (keys.contains("text")) { builder = deserializeTextComponent(compound); } else if (keys.contains("translate")) { builder = deserializeTranslatableComponent(compound); @@ -79,6 +81,9 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer { builder = deserializeKeybindComponent(compound); } else if (keys.contains("nbt")) { builder = deserializeNbtComponent(compound); + } else if (keys.contains("")) { + //todo This feels like a bug, im not sure why this is created. + builder = Component.text().content(compound.getString("")); } else throw new UnsupportedOperationException("Unable to infer component type"); } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index c789b36c4..ddb96b51b 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1737,11 +1737,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable, return openInventory; } - private void tryCloseInventory(boolean fromClient) { + private void tryCloseInventory(boolean sendClosePacket) { var closedInventory = getOpenInventory(); if (closedInventory == null) return; - didCloseInventory = fromClient; + didCloseInventory = !sendClosePacket; if (closedInventory.removeViewer(this)) { if (closedInventory == getOpenInventory()) { @@ -1775,12 +1775,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable, * It closes the player inventory (when opened) if {@link #getOpenInventory()} returns null. */ public void closeInventory() { - closeInventory(false); + closeInventory(true); } @ApiStatus.Internal - public void closeInventory(boolean fromClient) { - tryCloseInventory(fromClient); + public void closeInventory(boolean sendClosePacket) { + tryCloseInventory(sendClosePacket); inventory.update(); } diff --git a/src/main/java/net/minestom/server/listener/WindowListener.java b/src/main/java/net/minestom/server/listener/WindowListener.java index cb887368a..b2e9794a0 100644 --- a/src/main/java/net/minestom/server/listener/WindowListener.java +++ b/src/main/java/net/minestom/server/listener/WindowListener.java @@ -42,7 +42,7 @@ public class WindowListener { InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(openInventory, player); EventDispatcher.call(inventoryCloseEvent); - player.closeInventory(true); + player.closeInventory(false); Inventory newInventory = inventoryCloseEvent.getNewInventory(); if (newInventory != null) From 1246fa57d70d20b6b1ce040ba6308cff1bcbc6f2 Mon Sep 17 00:00:00 2001 From: mworzala Date: Tue, 23 Apr 2024 00:03:52 -0400 Subject: [PATCH 33/46] feat: anvil reading, other minor fixes --- .../java/net/minestom/demo/PlayerInit.java | 3 +- .../minestom/server/instance/AnvilLoader.java | 463 ----------------- .../server/instance/IChunkLoader.java | 1 + .../server/instance/InstanceContainer.java | 1 + .../server/instance/anvil/AnvilLoader.java | 482 ++++++++++++++++++ .../server/instance/anvil/RegionFile.java | 127 +++++ .../server/inventory/ContainerInventory.java | 8 +- .../net/minestom/server/item/ItemStack.java | 22 +- .../net/minestom/server/item/Material.java | 4 +- .../server/item/component/PotDecorations.java | 21 +- .../net/minestom/server/utils/ArrayUtils.java | 33 ++ .../server/utils/chunk/ChunkUtils.java | 9 +- .../server/utils/nbt/BinaryTagSerializer.java | 22 + .../instance/AnvilLoaderIntegrationTest.java | 1 + .../light/LightParityIntegrationTest.java | 6 +- .../instance/anvil_vanilla_sample/level.dat | Bin 0 -> 2268 bytes .../anvil_vanilla_sample/region/r.-1.-1.mca | Bin 0 -> 1748992 bytes .../anvil_vanilla_sample/region/r.-1.0.mca | Bin 0 -> 2899968 bytes .../anvil_vanilla_sample/region/r.-1.1.mca | Bin 0 -> 491520 bytes .../anvil_vanilla_sample/region/r.0.-1.mca | Bin 0 -> 4952064 bytes .../anvil_vanilla_sample/region/r.0.0.mca | Bin 0 -> 9129984 bytes .../anvil_vanilla_sample/region/r.0.1.mca | Bin 0 -> 2363392 bytes .../anvil_vanilla_sample/region/r.1.-1.mca | Bin 0 -> 1609728 bytes .../anvil_vanilla_sample/region/r.1.0.mca | Bin 0 -> 8232960 bytes .../anvil_vanilla_sample/region/r.1.1.mca | Bin 0 -> 3571712 bytes .../anvil_vanilla_sample/region/r.2.-1.mca | Bin 0 -> 499712 bytes .../anvil_vanilla_sample/region/r.2.0.mca | Bin 0 -> 4005888 bytes .../anvil_vanilla_sample/region/r.2.1.mca | Bin 0 -> 1953792 bytes 28 files changed, 703 insertions(+), 500 deletions(-) delete mode 100644 src/main/java/net/minestom/server/instance/AnvilLoader.java create mode 100644 src/main/java/net/minestom/server/instance/anvil/AnvilLoader.java create mode 100644 src/main/java/net/minestom/server/instance/anvil/RegionFile.java create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/level.dat create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.-1.-1.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.-1.0.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.-1.1.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.0.-1.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.0.0.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.0.1.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.1.-1.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.1.0.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.1.1.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.2.-1.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.2.0.mca create mode 100644 src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.2.1.mca diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index fb8714b03..fb2316332 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -26,6 +26,7 @@ import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.LightingChunk; +import net.minestom.server.instance.anvil.AnvilLoader; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.predicate.BlockPredicate; import net.minestom.server.instance.block.predicate.BlockTypeFilter; @@ -191,7 +192,7 @@ public class PlayerInit { static { InstanceManager instanceManager = MinecraftServer.getInstanceManager(); - InstanceContainer instanceContainer = instanceManager.createInstanceContainer(DimensionType.OVERWORLD); + InstanceContainer instanceContainer = instanceManager.createInstanceContainer(DimensionType.OVERWORLD, new AnvilLoader("/Users/matt/dev/projects/hollowcube/minestom-ce/src/test/resources/net/minestom/server/instance/anvil_vanilla_sample")); instanceContainer.setGenerator(unit -> { unit.modifier().fillHeight(0, 40, Block.STONE); diff --git a/src/main/java/net/minestom/server/instance/AnvilLoader.java b/src/main/java/net/minestom/server/instance/AnvilLoader.java deleted file mode 100644 index 02c96a477..000000000 --- a/src/main/java/net/minestom/server/instance/AnvilLoader.java +++ /dev/null @@ -1,463 +0,0 @@ -package net.minestom.server.instance; - -import it.unimi.dsi.fastutil.ints.IntIntImmutablePair; -import net.minestom.server.MinecraftServer; -import net.minestom.server.utils.NamespaceID; -import net.minestom.server.utils.async.AsyncUtils; -import net.minestom.server.world.biomes.Biome; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.file.Path; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; - -public class AnvilLoader implements IChunkLoader { - private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class); - private final static Biome PLAINS = MinecraftServer.getBiomeManager().getByName(NamespaceID.from("minecraft:plains")); - -// private final Map alreadyLoaded = new ConcurrentHashMap<>(); - private final Path path; - private final Path levelPath; - private final Path regionPath; - - private static class RegionCache extends ConcurrentHashMap> { - } - - /** - * Represents the chunks currently loaded per region. Used to determine when a region file can be unloaded. - */ - private final RegionCache perRegionLoadedChunks = new RegionCache(); - - // thread local to avoid contention issues with locks -// private final ThreadLocal> blockStateId2ObjectCacheTLS = ThreadLocal.withInitial(Int2ObjectArrayMap::new); - - public AnvilLoader(@NotNull Path path) { - this.path = path; - this.levelPath = path.resolve("level.dat"); - this.regionPath = path.resolve("region"); - } - - public AnvilLoader(@NotNull String path) { - this(Path.of(path)); - } - - @Override - public void loadInstance(@NotNull Instance instance) { -// if (!Files.exists(levelPath)) { -// return; -// } -// try (var reader = new NBTReader(Files.newInputStream(levelPath))) { -// final NBTCompound tag = (NBTCompound) reader.read(); -// Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING); -// instance.tagHandler().updateContent(tag); -// } catch (IOException | NBTException e) { -// MinecraftServer.getExceptionManager().handleException(e); -// } - } - - @Override - public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { -// if (!Files.exists(path)) { -// // No world folder -// return CompletableFuture.completedFuture(null); -// } -// try { -// return loadMCA(instance, chunkX, chunkZ); -// } catch (Exception e) { -// MinecraftServer.getExceptionManager().handleException(e); -// } - return CompletableFuture.completedFuture(null); - } -// -// private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException, AnvilException { -// final RegionFile mcaFile = getMCAFile(instance, chunkX, chunkZ); -// if (mcaFile == null) -// return CompletableFuture.completedFuture(null); -// final NBTCompound chunkData = mcaFile.getChunkData(chunkX, chunkZ); -// if (chunkData == null) -// return CompletableFuture.completedFuture(null); -// -// final ChunkReader chunkReader = new ChunkReader(chunkData); -// -// Chunk chunk = instance.getChunkSupplier().createChunk(instance, chunkX, chunkZ); -// synchronized (chunk) { -// var yRange = chunkReader.getYRange(); -// if (yRange.getStart() < instance.getDimensionType().getMinY()) { -// throw new AnvilException( -// String.format("Trying to load chunk with minY = %d, but instance dimension type (%s) has a minY of %d", -// yRange.getStart(), -// instance.getDimensionType().getName().asString(), -// instance.getDimensionType().getMinY() -// )); -// } -// if (yRange.getEndInclusive() > instance.getDimensionType().getMaxY()) { -// throw new AnvilException( -// String.format("Trying to load chunk with maxY = %d, but instance dimension type (%s) has a maxY of %d", -// yRange.getEndInclusive(), -// instance.getDimensionType().getName().asString(), -// instance.getDimensionType().getMaxY() -// )); -// } -// -// // TODO: Parallelize block, block entities and biome loading -// // Blocks + Biomes -// loadSections(chunk, chunkReader); -// -// // Block entities -// loadBlockEntities(chunk, chunkReader); -// } -// synchronized (perRegionLoadedChunks) { -// int regionX = CoordinatesKt.chunkToRegion(chunkX); -// int regionZ = CoordinatesKt.chunkToRegion(chunkZ); -// var chunks = perRegionLoadedChunks.computeIfAbsent(new IntIntImmutablePair(regionX, regionZ), r -> new HashSet<>()); // region cache may have been removed on another thread due to unloadChunk -// chunks.add(new IntIntImmutablePair(chunkX, chunkZ)); -// } -// return CompletableFuture.completedFuture(chunk); -// } -// -// private @Nullable RegionFile getMCAFile(Instance instance, int chunkX, int chunkZ) { -// final int regionX = CoordinatesKt.chunkToRegion(chunkX); -// final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); -// return alreadyLoaded.computeIfAbsent(RegionFile.Companion.createFileName(regionX, regionZ), n -> { -// try { -// final Path regionPath = this.regionPath.resolve(n); -// if (!Files.exists(regionPath)) { -// return null; -// } -// synchronized (perRegionLoadedChunks) { -// Set previousVersion = perRegionLoadedChunks.put(new IntIntImmutablePair(regionX, regionZ), new HashSet<>()); -// assert previousVersion == null : "The AnvilLoader cache should not already have data for this region."; -// } -// return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ, instance.getDimensionType().getMinY(), instance.getDimensionType().getMaxY() - 1); -// } catch (IOException | AnvilException e) { -// MinecraftServer.getExceptionManager().handleException(e); -// return null; -// } -// }); -// } -// -// private void loadSections(Chunk chunk, ChunkReader chunkReader) { -// final HashMap biomeCache = new HashMap<>(); -// for (NBTCompound sectionNBT : chunkReader.getSections()) { -// ChunkSectionReader sectionReader = new ChunkSectionReader(chunkReader.getMinecraftVersion(), sectionNBT); -// -// if (sectionReader.isSectionEmpty()) continue; -// final int sectionY = sectionReader.getY(); -// final int yOffset = Chunk.CHUNK_SECTION_SIZE * sectionY; -// -// Section section = chunk.getSection(sectionY); -// -// if (sectionReader.getSkyLight() != null) { -// section.setSkyLight(sectionReader.getSkyLight().copyArray()); -// } -// if (sectionReader.getBlockLight() != null) { -// section.setBlockLight(sectionReader.getBlockLight().copyArray()); -// } -// -// // Biomes -// if (chunkReader.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) { -// SectionBiomeInformation sectionBiomeInformation = chunkReader.readSectionBiomes(sectionReader); -// -// if (sectionBiomeInformation != null && sectionBiomeInformation.hasBiomeInformation()) { -// if (sectionBiomeInformation.isFilledWithSingleBiome()) { -// for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { -// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { -// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { -// int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x; -// int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z; -// int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y; -// String biomeName = sectionBiomeInformation.getBaseBiome(); -// Biome biome = biomeCache.computeIfAbsent(biomeName, n -> -// Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS)); -// chunk.setBiome(finalX, finalY, finalZ, biome); -// } -// } -// } -// } else { -// for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { -// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { -// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { -// int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x; -// int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z; -// int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y; -// -// int index = x / 4 + (z / 4) * 4 + (y / 4) * 16; -// String biomeName = sectionBiomeInformation.getBiomes()[index]; -// Biome biome = biomeCache.computeIfAbsent(biomeName, n -> -// Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS)); -// chunk.setBiome(finalX, finalY, finalZ, biome); -// } -// } -// } -// } -// } -// } -// -// // Blocks -// final NBTList blockPalette = sectionReader.getBlockPalette(); -// if (blockPalette != null) { -// final int[] blockStateIndices = sectionReader.getUncompressedBlockStateIDs(); -// Block[] convertedPalette = new Block[blockPalette.getSize()]; -// for (int i = 0; i < convertedPalette.length; i++) { -// final NBTCompound paletteEntry = blockPalette.get(i); -// String blockName = Objects.requireNonNull(paletteEntry.getString("Name")); -// if (blockName.equals("minecraft:air")) { -// convertedPalette[i] = Block.AIR; -// } else { -// if (blockName.equals("minecraft:grass")) { -// blockName = "minecraft:short_grass"; -// } -// Block block = Objects.requireNonNull(Block.fromNamespaceId(blockName)); -// // Properties -// final Map properties = new HashMap<>(); -// NBTCompound propertiesNBT = paletteEntry.getCompound("Properties"); -// if (propertiesNBT != null) { -// for (var property : propertiesNBT) { -// if (property.getValue().getID() != NBTType.TAG_String) { -// LOGGER.warn("Fail to parse block state properties {}, expected a TAG_String for {}, but contents were {}", -// propertiesNBT, -// property.getKey(), -// property.getValue().toSNBT()); -// } else { -// properties.put(property.getKey(), ((NBTString) property.getValue()).getValue()); -// } -// } -// } -// -// if (!properties.isEmpty()) block = block.withProperties(properties); -// // Handler -// final BlockHandler handler = MinecraftServer.getBlockManager().getHandler(block.name()); -// if (handler != null) block = block.withHandler(handler); -// -// convertedPalette[i] = block; -// } -// } -// -// for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { -// for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) { -// for (int x = 0; x < Chunk.CHUNK_SECTION_SIZE; x++) { -// try { -// final int blockIndex = y * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE + z * Chunk.CHUNK_SECTION_SIZE + x; -// final int paletteIndex = blockStateIndices[blockIndex]; -// final Block block = convertedPalette[paletteIndex]; -// -// chunk.setBlock(x, y + yOffset, z, block); -// } catch (Exception e) { -// MinecraftServer.getExceptionManager().handleException(e); -// } -// } -// } -// } -// } -// } -// } -// -// private void loadBlockEntities(Chunk loadedChunk, ChunkReader chunkReader) { -// for (NBTCompound te : chunkReader.getBlockEntities()) { -// final var x = te.getInt("x"); -// final var y = te.getInt("y"); -// final var z = te.getInt("z"); -// if (x == null || y == null || z == null) { -// LOGGER.warn("Tile entity has failed to load due to invalid coordinate"); -// continue; -// } -// Block block = loadedChunk.getBlock(x, y, z); -// -// final String tileEntityID = te.getString("id"); -// if (tileEntityID != null) { -// final BlockHandler handler = MinecraftServer.getBlockManager().getHandlerOrDummy(tileEntityID); -// block = block.withHandler(handler); -// } -// // Remove anvil tags -// MutableNBTCompound mutableCopy = te.toMutableCompound(); -// mutableCopy.remove("id"); -// mutableCopy.remove("x"); -// mutableCopy.remove("y"); -// mutableCopy.remove("z"); -// mutableCopy.remove("keepPacked"); -// // Place block -// final var finalBlock = mutableCopy.getSize() > 0 ? -// block.withNbt(mutableCopy.toCompound()) : block; -// loadedChunk.setBlock(x, y, z, finalBlock); -// } -// } -// - @Override - public @NotNull CompletableFuture saveInstance(@NotNull Instance instance) { -// final NBTCompound nbt = instance.tagHandler().asCompound(); -// if (nbt.isEmpty()) { -// // Instance has no data -// return AsyncUtils.VOID_FUTURE; -// } -// try (NBTWriter writer = new NBTWriter(Files.newOutputStream(levelPath))) { -// writer.writeNamed("", nbt); -// } catch (IOException e) { -// e.printStackTrace(); -// } - return AsyncUtils.VOID_FUTURE; - } - - @Override - public @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk) { -// final int chunkX = chunk.getChunkX(); -// final int chunkZ = chunk.getChunkZ(); -// RegionFile mcaFile; -// synchronized (alreadyLoaded) { -// mcaFile = getMCAFile(chunk.instance, chunkX, chunkZ); -// if (mcaFile == null) { -// final int regionX = CoordinatesKt.chunkToRegion(chunkX); -// final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); -// final String n = RegionFile.Companion.createFileName(regionX, regionZ); -// File regionFile = new File(regionPath.toFile(), n); -// try { -// if (!regionFile.exists()) { -// if (!regionFile.getParentFile().exists()) { -// regionFile.getParentFile().mkdirs(); -// } -// regionFile.createNewFile(); -// } -// mcaFile = new RegionFile(new RandomAccessFile(regionFile, "rw"), regionX, regionZ); -// alreadyLoaded.put(n, mcaFile); -// } catch (AnvilException | IOException e) { -// LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); -// MinecraftServer.getExceptionManager().handleException(e); -// return AsyncUtils.VOID_FUTURE; -// } -// } -// } -// ChunkWriter writer = new ChunkWriter(SupportedVersion.Companion.getLatest()); -// save(chunk, writer); -// try { -// LOGGER.debug("Attempt saving at {} {}", chunk.getChunkX(), chunk.getChunkZ()); -// mcaFile.writeColumnData(writer.toNBT(), chunk.getChunkX(), chunk.getChunkZ()); -// } catch (IOException e) { -// LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); -// MinecraftServer.getExceptionManager().handleException(e); -// return AsyncUtils.VOID_FUTURE; -// } - return AsyncUtils.VOID_FUTURE; - } - -// private BlockState getBlockState(final Block block) { -// return blockStateId2ObjectCacheTLS.get().computeIfAbsent(block.stateId(), _unused -> new BlockState(block.name(), block.properties())); -// } -// -// private void save(Chunk chunk, ChunkWriter chunkWriter) { -// final int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE; -// final int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE - 1; -// chunkWriter.setYPos(minY); -// List blockEntities = new ArrayList<>(); -// chunkWriter.setStatus(ChunkColumn.GenerationStatus.Full); -// -// List sectionData = new ArrayList<>((maxY - minY + 1) / Chunk.CHUNK_SECTION_SIZE); -// int[] palettedBiomes = new int[ChunkSection.Companion.getBiomeArraySize()]; -// int[] palettedBlockStates = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z]; -// for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) { -// ChunkSectionWriter sectionWriter = new ChunkSectionWriter(SupportedVersion.Companion.getLatest(), (byte) sectionY); -// -// Section section = chunk.getSection(sectionY); -// sectionWriter.setSkyLights(section.skyLight().array()); -// sectionWriter.setBlockLights(section.blockLight().array()); -// -// BiomePalette biomePalette = new BiomePalette(); -// BlockPalette blockPalette = new BlockPalette(); -// for (int sectionLocalY = 0; sectionLocalY < Chunk.CHUNK_SECTION_SIZE; sectionLocalY++) { -// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { -// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { -// final int y = sectionLocalY + sectionY * Chunk.CHUNK_SECTION_SIZE; -// -// final int blockIndex = x + sectionLocalY * 16 * 16 + z * 16; -// -// final Block block = chunk.getBlock(x, y, z); -// -// final BlockState hephaistosBlockState = getBlockState(block); -// blockPalette.increaseReference(hephaistosBlockState); -// -// palettedBlockStates[blockIndex] = blockPalette.getPaletteIndex(hephaistosBlockState); -// -// // biome are stored for 4x4x4 volumes, avoid unnecessary work -// if (x % 4 == 0 && sectionLocalY % 4 == 0 && z % 4 == 0) { -// int biomeIndex = (x / 4) + (sectionLocalY / 4) * 4 * 4 + (z / 4) * 4; -// final Biome biome = chunk.getBiome(x, y, z); -// final String biomeName = biome.name(); -// -// biomePalette.increaseReference(biomeName); -// palettedBiomes[biomeIndex] = biomePalette.getPaletteIndex(biomeName); -// } -// -// // Block entities -// final BlockHandler handler = block.handler(); -// final NBTCompound originalNBT = block.nbt(); -// if (originalNBT != null || handler != null) { -// MutableNBTCompound nbt = originalNBT != null ? -// originalNBT.toMutableCompound() : new MutableNBTCompound(); -// -// if (handler != null) { -// nbt.setString("id", handler.getNamespaceId().asString()); -// } -// nbt.setInt("x", x + Chunk.CHUNK_SIZE_X * chunk.getChunkX()); -// nbt.setInt("y", y); -// nbt.setInt("z", z + Chunk.CHUNK_SIZE_Z * chunk.getChunkZ()); -// nbt.setByte("keepPacked", (byte) 0); -// blockEntities.add(nbt.toCompound()); -// } -// } -// } -// } -// -// sectionWriter.setPalettedBiomes(biomePalette, palettedBiomes); -// sectionWriter.setPalettedBlockStates(blockPalette, palettedBlockStates); -// -// sectionData.add(sectionWriter.toNBT()); -// } -// -// chunkWriter.setSectionsData(NBT.List(NBTType.TAG_Compound, sectionData)); -// chunkWriter.setBlockEntityData(NBT.List(NBTType.TAG_Compound, blockEntities)); -// } -// -// /** -// * Unload a given chunk. Also unloads a region when no chunk from that region is loaded. -// * -// * @param chunk the chunk to unload -// */ -// @Override -// public void unloadChunk(Chunk chunk) { -// final int regionX = CoordinatesKt.chunkToRegion(chunk.chunkX); -// final int regionZ = CoordinatesKt.chunkToRegion(chunk.chunkZ); -// -// final IntIntImmutablePair regionKey = new IntIntImmutablePair(regionX, regionZ); -// synchronized (perRegionLoadedChunks) { -// Set chunks = perRegionLoadedChunks.get(regionKey); -// if (chunks != null) { // if null, trying to unload a chunk from a region that was not created by the AnvilLoader -// // don't check return value, trying to unload a chunk not created by the AnvilLoader is valid -// chunks.remove(new IntIntImmutablePair(chunk.chunkX, chunk.chunkZ)); -// -// if (chunks.isEmpty()) { -// perRegionLoadedChunks.remove(regionKey); -// RegionFile regionFile = alreadyLoaded.remove(RegionFile.Companion.createFileName(regionX, regionZ)); -// if (regionFile != null) { -// try { -// regionFile.close(); -// } catch (IOException e) { -// MinecraftServer.getExceptionManager().handleException(e); -// } -// } -// } -// } -// } -// } - - @Override - public boolean supportsParallelLoading() { - return true; - } - - @Override - public boolean supportsParallelSaving() { - return true; - } -} diff --git a/src/main/java/net/minestom/server/instance/IChunkLoader.java b/src/main/java/net/minestom/server/instance/IChunkLoader.java index bf5029631..31f6f8ee3 100644 --- a/src/main/java/net/minestom/server/instance/IChunkLoader.java +++ b/src/main/java/net/minestom/server/instance/IChunkLoader.java @@ -1,6 +1,7 @@ package net.minestom.server.instance; import net.minestom.server.MinecraftServer; +import net.minestom.server.instance.anvil.AnvilLoader; import net.minestom.server.utils.async.AsyncUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index 3c516a8ab..3f9683f69 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -11,6 +11,7 @@ import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.instance.InstanceChunkLoadEvent; import net.minestom.server.event.instance.InstanceChunkUnloadEvent; import net.minestom.server.event.player.PlayerBlockBreakEvent; +import net.minestom.server.instance.anvil.AnvilLoader; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockFace; import net.minestom.server.instance.block.BlockHandler; diff --git a/src/main/java/net/minestom/server/instance/anvil/AnvilLoader.java b/src/main/java/net/minestom/server/instance/anvil/AnvilLoader.java new file mode 100644 index 000000000..1162e0307 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/anvil/AnvilLoader.java @@ -0,0 +1,482 @@ +package net.minestom.server.instance.anvil; + +import it.unimi.dsi.fastutil.ints.IntIntImmutablePair; +import net.kyori.adventure.nbt.*; +import net.minestom.server.MinecraftServer; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.IChunkLoader; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.Section; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.utils.ArrayUtils; +import net.minestom.server.utils.NamespaceID; +import net.minestom.server.utils.async.AsyncUtils; +import net.minestom.server.utils.chunk.ChunkUtils; +import net.minestom.server.utils.validate.Check; +import net.minestom.server.world.DimensionType; +import net.minestom.server.world.biomes.Biome; +import net.minestom.server.world.biomes.BiomeManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +public class AnvilLoader implements IChunkLoader { + private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class); + private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager(); + private final static Biome PLAINS = BIOME_MANAGER.getByName(NamespaceID.from("minecraft:plains")); + + private final Map alreadyLoaded = new ConcurrentHashMap<>(); + private final Path path; + private final Path levelPath; + private final Path regionPath; + + private static class RegionCache extends ConcurrentHashMap> { + } + + /** + * Represents the chunks currently loaded per region. Used to determine when a region file can be unloaded. + */ + private final RegionCache perRegionLoadedChunks = new RegionCache(); + private final ReentrantLock perRegionLoadedChunksLock = new ReentrantLock(); + + // thread local to avoid contention issues with locks +// private final ThreadLocal> blockStateId2ObjectCacheTLS = ThreadLocal.withInitial(Int2ObjectArrayMap::new); + + public AnvilLoader(@NotNull Path path) { + this.path = path; + this.levelPath = path.resolve("level.dat"); + this.regionPath = path.resolve("region"); + } + + public AnvilLoader(@NotNull String path) { + this(Path.of(path)); + } + + @Override + public void loadInstance(@NotNull Instance instance) { + if (!Files.exists(levelPath)) { + return; + } + try (InputStream is = Files.newInputStream(levelPath)) { + final CompoundBinaryTag tag = BinaryTagIO.reader().readNamed(is, BinaryTagIO.Compression.GZIP).getValue(); + Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING); + instance.tagHandler().updateContent(tag); + } catch (IOException e) { + MinecraftServer.getExceptionManager().handleException(e); + } + } + + @Override + public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { + if (!Files.exists(path)) { + // No world folder + return CompletableFuture.completedFuture(null); + } + try { + return loadMCA(instance, chunkX, chunkZ); + } catch (Exception e) { + MinecraftServer.getExceptionManager().handleException(e); + return CompletableFuture.completedFuture(null); + } + } + + private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException { + final RegionFile mcaFile = getMCAFile(instance, chunkX, chunkZ); + if (mcaFile == null) + return CompletableFuture.completedFuture(null); + final CompoundBinaryTag chunkData = mcaFile.getChunk(chunkX, chunkZ); + if (chunkData == null) + return CompletableFuture.completedFuture(null); + + // Ensure the chunk matches the expected Y range + DimensionType dimensionType = instance.getDimensionType(); + int minY = chunkData.getInt("yPos") * Chunk.CHUNK_SECTION_SIZE; + Check.stateCondition(minY != dimensionType.getMinY(), "Trying to load chunk with minY = {0}, but instance dimension type ({1}) has a minY of {2}", + minY, dimensionType.getName().asString(), dimensionType.getMinY()); + int maxY = minY + (chunkData.getList("sections", BinaryTagTypes.COMPOUND).size() * Chunk.CHUNK_SECTION_SIZE); + Check.stateCondition(maxY != dimensionType.getMaxY(), "Trying to load chunk with maxY = {0}, but instance dimension type ({1}) has a maxY of {2}", + maxY, dimensionType.getName().asString(), dimensionType.getMaxY()); + + // Load the chunk data (assuming it is fully generated) + final Chunk chunk = instance.getChunkSupplier().createChunk(instance, chunkX, chunkZ); + synchronized (chunk) { // todo: boo, synchronized + final String status = chunkData.getString("status"); + + // TODO: Should we handle other states? + if (status.isEmpty() || "minecraft:full".equals(status)) { + // TODO: Parallelize block, block entities and biome loading + // Blocks + Biomes + loadSections(chunk, chunkData); + + // Block entities + loadBlockEntities(chunk, chunkData); + } else { + LOGGER.warn("Skipping partially generated chunk at {}, {} with status {}", chunkX, chunkZ, status); + } + } + + // Cache the index of the loaded chunk + perRegionLoadedChunksLock.lock(); + try { + int regionX = ChunkUtils.toRegionCoordinate(chunkX); + int regionZ = ChunkUtils.toRegionCoordinate(chunkZ); + var chunks = perRegionLoadedChunks.computeIfAbsent(new IntIntImmutablePair(regionX, regionZ), r -> new HashSet<>()); // region cache may have been removed on another thread due to unloadChunk + chunks.add(new IntIntImmutablePair(chunkX, chunkZ)); + } finally { + perRegionLoadedChunksLock.unlock(); + } + return CompletableFuture.completedFuture(chunk); + } + + private @Nullable RegionFile getMCAFile(Instance instance, int chunkX, int chunkZ) { + final int regionX = ChunkUtils.toRegionCoordinate(chunkX); + final int regionZ = ChunkUtils.toRegionCoordinate(chunkZ); + return alreadyLoaded.computeIfAbsent(RegionFile.getFileName(regionX, regionZ), n -> { + final Path regionPath = this.regionPath.resolve(n); + if (!Files.exists(regionPath)) { + return null; + } + perRegionLoadedChunksLock.lock(); + try { + Set previousVersion = perRegionLoadedChunks.put(new IntIntImmutablePair(regionX, regionZ), new HashSet<>()); + assert previousVersion == null : "The AnvilLoader cache should not already have data for this region."; + return new RegionFile(regionPath, regionX, regionZ, instance.getDimensionType()); + } catch (IOException e) { + MinecraftServer.getExceptionManager().handleException(e); + return null; + } finally { + perRegionLoadedChunksLock.unlock(); + } + }); + } + + private void loadSections(@NotNull Chunk chunk, @NotNull CompoundBinaryTag chunkData) { + for (BinaryTag sectionTag : chunkData.getList("sections", BinaryTagTypes.COMPOUND)) { + final CompoundBinaryTag sectionData = (CompoundBinaryTag) sectionTag; + + final int sectionY = sectionData.getInt("Y", Integer.MIN_VALUE); + Check.stateCondition(sectionY == Integer.MIN_VALUE, "Missing section Y value"); + final int yOffset = Chunk.CHUNK_SECTION_SIZE * sectionY; + + final Section section = chunk.getSection(sectionY); + + // Lighting + if (sectionData.get("SkyLight") instanceof ByteArrayBinaryTag skyLightTag && skyLightTag.size() == 2048) { + section.setSkyLight(skyLightTag.value()); + } + if (sectionData.get("BlockLight") instanceof ByteArrayBinaryTag blockLightTag && blockLightTag.size() == 2048) { + section.setBlockLight(blockLightTag.value()); + } + + { // Biomes + final CompoundBinaryTag biomesTag = sectionData.getCompound("biomes"); + final ListBinaryTag biomePaletteTag = biomesTag.getList("palette", BinaryTagTypes.STRING); + Biome[] convertedPalette = loadBiomePalette(biomePaletteTag); + + if (convertedPalette.length == 1) { + // One solid block, no need to check the data + section.biomePalette().fill(BIOME_MANAGER.getId(convertedPalette[0])); + } else if (convertedPalette.length > 1) { + final long[] packedIndices = biomesTag.getLongArray("data"); + Check.stateCondition(packedIndices.length == 0, "Missing packed biomes data"); + int[] biomeIndices = new int[64]; + ArrayUtils.unpack(biomeIndices, packedIndices, packedIndices.length * 64 / biomeIndices.length); + + section.biomePalette().setAll((x, y, z) -> { + final int index = x + z * 4 + y * 16; + final Biome biome = convertedPalette[biomeIndices[index]]; + return BIOME_MANAGER.getId(biome); + }); + } + } + + { // Blocks + final CompoundBinaryTag blockStatesTag = sectionData.getCompound("block_states"); + final ListBinaryTag blockPaletteTag = blockStatesTag.getList("palette", BinaryTagTypes.COMPOUND); + Block[] convertedPalette = loadBlockPalette(blockPaletteTag); + if (blockPaletteTag.size() == 1) { + // One solid block, no need to check the data + section.blockPalette().fill(convertedPalette[0].stateId()); + } else if (blockPaletteTag.size() > 1) { + final long[] packedStates = blockStatesTag.getLongArray("data"); + Check.stateCondition(packedStates.length == 0, "Missing packed states data"); + int[] blockStateIndices = new int[Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE]; + ArrayUtils.unpack(blockStateIndices, packedStates, packedStates.length * 64 / blockStateIndices.length); + + for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { + for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) { + for (int x = 0; x < Chunk.CHUNK_SECTION_SIZE; x++) { + try { + final int blockIndex = y * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE + z * Chunk.CHUNK_SECTION_SIZE + x; + final int paletteIndex = blockStateIndices[blockIndex]; + final Block block = convertedPalette[paletteIndex]; + + chunk.setBlock(x, y + yOffset, z, block); + } catch (Exception e) { + MinecraftServer.getExceptionManager().handleException(e); + } + } + } + } + } + } + } + } + + private Block[] loadBlockPalette(@NotNull ListBinaryTag paletteTag) { + Block[] convertedPalette = new Block[paletteTag.size()]; + for (int i = 0; i < convertedPalette.length; i++) { + CompoundBinaryTag paletteEntry = paletteTag.getCompound(i); + String blockName = paletteEntry.getString("Name"); + if (blockName.equals("minecraft:air")) { + convertedPalette[i] = Block.AIR; + } else { + Block block = Objects.requireNonNull(Block.fromNamespaceId(blockName), "Unknown block " + blockName); + // Properties + final Map properties = new HashMap<>(); + CompoundBinaryTag propertiesNBT = paletteEntry.getCompound("Properties"); + for (var property : propertiesNBT) { + if (property.getValue() instanceof StringBinaryTag propertyValue) { + properties.put(property.getKey(), propertyValue.value()); + } else { + LOGGER.warn("Fail to parse block state properties {}, expected a string for {}, but contents were {}", + propertiesNBT, property.getKey(), TagStringIOExt.writeTag(property.getValue())); + } + } + if (!properties.isEmpty()) block = block.withProperties(properties); + + // Handler + final BlockHandler handler = MinecraftServer.getBlockManager().getHandler(block.name()); + if (handler != null) block = block.withHandler(handler); + + convertedPalette[i] = block; + } + } + return convertedPalette; + } + + private Biome[] loadBiomePalette(@NotNull ListBinaryTag paletteTag) { + Biome[] convertedPalette = new Biome[paletteTag.size()]; + for (int i = 0; i < convertedPalette.length; i++) { + final String name = paletteTag.getString(i); + convertedPalette[i] = Objects.requireNonNullElse(BIOME_MANAGER.getByName(name), PLAINS); + } + return convertedPalette; + } + + private void loadBlockEntities(@NotNull Chunk loadedChunk, @NotNull CompoundBinaryTag chunkData) { + for (BinaryTag blockEntityTag : chunkData.getList("block_entities", BinaryTagTypes.COMPOUND)) { + final CompoundBinaryTag blockEntity = (CompoundBinaryTag) blockEntityTag; + + final int x = blockEntity.getInt("x"); + final int y = blockEntity.getInt("y"); + final int z = blockEntity.getInt("z"); + Block block = loadedChunk.getBlock(x, y, z); + + // Load the block handler if the id is present + if (blockEntity.get("id") instanceof StringBinaryTag blockEntityId) { + final BlockHandler handler = MinecraftServer.getBlockManager().getHandlerOrDummy(blockEntityId.value()); + block = block.withHandler(handler); + } + + // Remove anvil tags + CompoundBinaryTag trimmedTag = CompoundBinaryTag.builder().put(blockEntity) + .remove("id").remove("keepPacked") + .remove("x").remove("y").remove("z") + .build(); + + // Place block + final var finalBlock = trimmedTag.size() > 0 ? block.withNbt(trimmedTag) : block; + loadedChunk.setBlock(x, y, z, finalBlock); + } + } + + @Override + public @NotNull CompletableFuture saveInstance(@NotNull Instance instance) { + final CompoundBinaryTag nbt = instance.tagHandler().asCompound(); + if (nbt.size() == 0) { + // Instance has no data + return AsyncUtils.VOID_FUTURE; + } + try (OutputStream os = Files.newOutputStream(levelPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + BinaryTagIO.writer().writeNamed(Map.entry("", nbt), os, BinaryTagIO.Compression.GZIP); + } catch (IOException e) { + MinecraftServer.getExceptionManager().handleException(e); + } + return AsyncUtils.VOID_FUTURE; + } + + @Override + public @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk) { +// final int chunkX = chunk.getChunkX(); +// final int chunkZ = chunk.getChunkZ(); +// RegionFile mcaFile; +// synchronized (alreadyLoaded) { +// mcaFile = getMCAFile(chunk.instance, chunkX, chunkZ); +// if (mcaFile == null) { +// final int regionX = CoordinatesKt.chunkToRegion(chunkX); +// final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); +// final String n = RegionFile.Companion.createFileName(regionX, regionZ); +// File regionFile = new File(regionPath.toFile(), n); +// try { +// if (!regionFile.exists()) { +// if (!regionFile.getParentFile().exists()) { +// regionFile.getParentFile().mkdirs(); +// } +// regionFile.createNewFile(); +// } +// mcaFile = new RegionFile(new RandomAccessFile(regionFile, "rw"), regionX, regionZ); +// alreadyLoaded.put(n, mcaFile); +// } catch (AnvilException | IOException e) { +// LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); +// MinecraftServer.getExceptionManager().handleException(e); +// return AsyncUtils.VOID_FUTURE; +// } +// } +// } +// ChunkWriter writer = new ChunkWriter(SupportedVersion.Companion.getLatest()); +// save(chunk, writer); +// try { +// LOGGER.debug("Attempt saving at {} {}", chunk.getChunkX(), chunk.getChunkZ()); +// mcaFile.writeColumnData(writer.toNBT(), chunk.getChunkX(), chunk.getChunkZ()); +// } catch (IOException e) { +// LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); +// MinecraftServer.getExceptionManager().handleException(e); +// return AsyncUtils.VOID_FUTURE; +// } + return AsyncUtils.VOID_FUTURE; + } + +// private BlockState getBlockState(final Block block) { +// return blockStateId2ObjectCacheTLS.get().computeIfAbsent(block.stateId(), _unused -> new BlockState(block.name(), block.properties())); +// } +// +// private void save(Chunk chunk, ChunkWriter chunkWriter) { +// final int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE; +// final int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE - 1; +// chunkWriter.setYPos(minY); +// List blockEntities = new ArrayList<>(); +// chunkWriter.setStatus(ChunkColumn.GenerationStatus.Full); +// +// List sectionData = new ArrayList<>((maxY - minY + 1) / Chunk.CHUNK_SECTION_SIZE); +// int[] palettedBiomes = new int[ChunkSection.Companion.getBiomeArraySize()]; +// int[] palettedBlockStates = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z]; +// for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) { +// ChunkSectionWriter sectionWriter = new ChunkSectionWriter(SupportedVersion.Companion.getLatest(), (byte) sectionY); +// +// Section section = chunk.getSection(sectionY); +// sectionWriter.setSkyLights(section.skyLight().array()); +// sectionWriter.setBlockLights(section.blockLight().array()); +// +// BiomePalette biomePalette = new BiomePalette(); +// BlockPalette blockPalette = new BlockPalette(); +// for (int sectionLocalY = 0; sectionLocalY < Chunk.CHUNK_SECTION_SIZE; sectionLocalY++) { +// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { +// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { +// final int y = sectionLocalY + sectionY * Chunk.CHUNK_SECTION_SIZE; +// +// final int blockIndex = x + sectionLocalY * 16 * 16 + z * 16; +// +// final Block block = chunk.getBlock(x, y, z); +// +// final BlockState hephaistosBlockState = getBlockState(block); +// blockPalette.increaseReference(hephaistosBlockState); +// +// palettedBlockStates[blockIndex] = blockPalette.getPaletteIndex(hephaistosBlockState); +// +// // biome are stored for 4x4x4 volumes, avoid unnecessary work +// if (x % 4 == 0 && sectionLocalY % 4 == 0 && z % 4 == 0) { +// int biomeIndex = (x / 4) + (sectionLocalY / 4) * 4 * 4 + (z / 4) * 4; +// final Biome biome = chunk.getBiome(x, y, z); +// final String biomeName = biome.name(); +// +// biomePalette.increaseReference(biomeName); +// palettedBiomes[biomeIndex] = biomePalette.getPaletteIndex(biomeName); +// } +// +// // Block entities +// final BlockHandler handler = block.handler(); +// final NBTCompound originalNBT = block.nbt(); +// if (originalNBT != null || handler != null) { +// MutableNBTCompound nbt = originalNBT != null ? +// originalNBT.toMutableCompound() : new MutableNBTCompound(); +// +// if (handler != null) { +// nbt.setString("id", handler.getNamespaceId().asString()); +// } +// nbt.setInt("x", x + Chunk.CHUNK_SIZE_X * chunk.getChunkX()); +// nbt.setInt("y", y); +// nbt.setInt("z", z + Chunk.CHUNK_SIZE_Z * chunk.getChunkZ()); +// nbt.setByte("keepPacked", (byte) 0); +// blockEntities.add(nbt.toCompound()); +// } +// } +// } +// } +// +// sectionWriter.setPalettedBiomes(biomePalette, palettedBiomes); +// sectionWriter.setPalettedBlockStates(blockPalette, palettedBlockStates); +// +// sectionData.add(sectionWriter.toNBT()); +// } +// +// chunkWriter.setSectionsData(NBT.List(NBTType.TAG_Compound, sectionData)); +// chunkWriter.setBlockEntityData(NBT.List(NBTType.TAG_Compound, blockEntities)); +// } +// +// /** +// * Unload a given chunk. Also unloads a region when no chunk from that region is loaded. +// * +// * @param chunk the chunk to unload +// */ +// @Override +// public void unloadChunk(Chunk chunk) { +// final int regionX = CoordinatesKt.chunkToRegion(chunk.chunkX); +// final int regionZ = CoordinatesKt.chunkToRegion(chunk.chunkZ); +// +// final IntIntImmutablePair regionKey = new IntIntImmutablePair(regionX, regionZ); +// synchronized (perRegionLoadedChunks) { +// Set chunks = perRegionLoadedChunks.get(regionKey); +// if (chunks != null) { // if null, trying to unload a chunk from a region that was not created by the AnvilLoader +// // don't check return value, trying to unload a chunk not created by the AnvilLoader is valid +// chunks.remove(new IntIntImmutablePair(chunk.chunkX, chunk.chunkZ)); +// +// if (chunks.isEmpty()) { +// perRegionLoadedChunks.remove(regionKey); +// RegionFile regionFile = alreadyLoaded.remove(RegionFile.Companion.createFileName(regionX, regionZ)); +// if (regionFile != null) { +// try { +// regionFile.close(); +// } catch (IOException e) { +// MinecraftServer.getExceptionManager().handleException(e); +// } +// } +// } +// } +// } +// } + + @Override + public boolean supportsParallelLoading() { + return true; + } + + @Override + public boolean supportsParallelSaving() { + return true; + } +} diff --git a/src/main/java/net/minestom/server/instance/anvil/RegionFile.java b/src/main/java/net/minestom/server/instance/anvil/RegionFile.java new file mode 100644 index 000000000..be1cab63d --- /dev/null +++ b/src/main/java/net/minestom/server/instance/anvil/RegionFile.java @@ -0,0 +1,127 @@ +package net.minestom.server.instance.anvil; + +import it.unimi.dsi.fastutil.booleans.BooleanArrayList; +import it.unimi.dsi.fastutil.booleans.BooleanList; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.utils.chunk.ChunkUtils; +import net.minestom.server.world.DimensionType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Path; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Implements a thread-safe reader and writer for Minecraft region files. + * + * @see Region file format + * @see Hephaistos implementation + */ +final class RegionFile implements AutoCloseable { + + private static final int MAX_ENTRY_COUNT = 1024; + private static final int SECTOR_SIZE = 4096; + private static final int SECTOR_1MB = 1024 * 1024 / SECTOR_SIZE; + private static final int HEADER_LENGTH = MAX_ENTRY_COUNT * 2 * 4; // 2 4-byte fields per entry + + private static final BinaryTagIO.Reader TAG_READER = BinaryTagIO.unlimitedReader(); + + public static @NotNull String getFileName(int regionX, int regionZ) { + return "r." + regionX + "." + regionZ + ".mca"; + } + + private final ReentrantLock lock = new ReentrantLock(); + private final RandomAccessFile file; + + private final int[] locations = new int[MAX_ENTRY_COUNT]; + private final int[] timestamps = new int[MAX_ENTRY_COUNT]; + private final BooleanList freeSectors = new BooleanArrayList(2); + + public RegionFile(@NotNull Path path, int regionX, int regionZ, @NotNull DimensionType dimensionType) throws IOException { + this.file = new RandomAccessFile(path.toFile(), "rw"); + + readHeader(); + } + + public boolean hasChunkData(int chunkX, int chunkZ) { + lock.lock(); + try { + return locations[getChunkIndex(chunkX, chunkZ)] != 0; + } finally { + lock.unlock(); + } + } + + public @Nullable CompoundBinaryTag getChunk(int chunkX, int chunkZ) throws IOException { + lock.lock(); + try { + if (!hasChunkData(chunkX, chunkZ)) return null; + + int location = locations[getChunkIndex(chunkX, chunkZ)]; + file.seek((long) (location >> 8) * SECTOR_SIZE); // Move to start of first sector + int length = file.readInt(); + int compressionType = file.readByte(); + BinaryTagIO.Compression compression = switch (compressionType) { + case 1 -> BinaryTagIO.Compression.GZIP; + case 2 -> BinaryTagIO.Compression.ZLIB; + case 3 -> BinaryTagIO.Compression.NONE; + default -> throw new IOException("Unsupported compression type: " + compressionType); + }; + + // Read the raw content + byte[] data = new byte[length - 1]; + file.read(data); + + // Parse it as a compound tag + return TAG_READER.read(new ByteArrayInputStream(data), compression); + } finally { + lock.unlock(); + } + } + + + @Override + public void close() throws Exception { + file.close(); + } + + private void readHeader() throws IOException { + file.seek(0); + if (file.length() < HEADER_LENGTH) { + // new file, fill in data + file.write(new byte[HEADER_LENGTH]); + } + + //todo: addPadding() + + final long totalSectors = file.length() / SECTOR_SIZE; + for (int i = 0; i < totalSectors; i++) freeSectors.add(true); + + // Read locations + file.seek(0); + for (int i = 0; i < MAX_ENTRY_COUNT; i++) { + int location = locations[i] = file.readInt(); + int offset = location >> 8; + int length = location & 0xFF; + + if (location != 0 && offset + length <= freeSectors.size()) { + for (int sectorIndex = 0; sectorIndex < length; sectorIndex++) { + freeSectors.set(sectorIndex + offset, false); + } + } + } + + // Read timestamps + for (int i = 0; i < MAX_ENTRY_COUNT; i++) { + timestamps[i] = file.readInt(); + } + } + + private int getChunkIndex(int chunkX, int chunkZ) { + return (ChunkUtils.toRegionLocal(chunkZ) << 5) | ChunkUtils.toRegionLocal(chunkX); + } +} diff --git a/src/main/java/net/minestom/server/inventory/ContainerInventory.java b/src/main/java/net/minestom/server/inventory/ContainerInventory.java index c9f7d049c..875d364b8 100644 --- a/src/main/java/net/minestom/server/inventory/ContainerInventory.java +++ b/src/main/java/net/minestom/server/inventory/ContainerInventory.java @@ -66,9 +66,11 @@ public non-sealed class ContainerInventory extends InventoryImpl { } } - inventory.update(player); - if (inventory != playerInventory) { - playerInventory.update(player); + if (inventory.isViewer(player)) { + inventory.update(player); + if (inventory != playerInventory) { + playerInventory.update(player); + } } return null; } diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index bbedc7d2f..abc91f86e 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -1,19 +1,23 @@ package net.minestom.server.item; import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; +import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.inventory.ContainerInventory; import net.minestom.server.item.component.CustomData; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagReadable; import net.minestom.server.tag.TagWritable; +import net.minestom.server.utils.nbt.BinaryTagSerializer; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; +import java.io.IOException; import java.util.function.Consumer; import java.util.function.IntUnaryOperator; import java.util.function.UnaryOperator; @@ -33,6 +37,7 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @NotNull ItemStack AIR = ItemStack.of(Material.AIR); @NotNull NetworkBuffer.Type NETWORK_TYPE = ItemStackImpl.NETWORK_TYPE; + @NotNull BinaryTagSerializer NBT_TYPE = ItemStackImpl.NBT_TYPE; @Contract(value = "_ -> new", pure = true) static @NotNull Builder builder(@NotNull Material material) { @@ -55,7 +60,7 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv * @param nbtCompound The nbt representation of the item */ static @NotNull ItemStack fromItemNBT(@NotNull CompoundBinaryTag nbtCompound) { - return ItemStackImpl.NBT_TYPE.read(nbtCompound); + return NBT_TYPE.read(nbtCompound); } @Contract(pure = true) @@ -126,15 +131,12 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv @Override default @NotNull HoverEvent asHoverEvent(@NotNull UnaryOperator op) { - //todo -// try { -// final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC); -// return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.showItem(material(), amount(), tagHolder))); -// } catch (IOException e) { -// //todo(matt): revisit, -// throw new RuntimeException(e); -// } - throw new UnsupportedOperationException("todo"); + try { + BinaryTagHolder tagHolder = BinaryTagHolder.encode((CompoundBinaryTag) NBT_TYPE.write(this), MinestomAdventure.NBT_CODEC); + return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.showItem(material(), amount(), tagHolder))); + } catch (IOException e) { + throw new RuntimeException("failed to encode itemstack nbt", e); + } } sealed interface Builder extends TagWritable diff --git a/src/main/java/net/minestom/server/item/Material.java b/src/main/java/net/minestom/server/item/Material.java index 900a3c8b0..43badc2cd 100644 --- a/src/main/java/net/minestom/server/item/Material.java +++ b/src/main/java/net/minestom/server/item/Material.java @@ -47,8 +47,8 @@ import java.util.Collection; public sealed interface Material extends StaticProtocolObject, Materials permits MaterialImpl { - NetworkBuffer.Type NETWORK_TYPE = MaterialImpl.NETWORK_TYPE; - BinaryTagSerializer NBT_TYPE = MaterialImpl.NBT_TYPE; + NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.lazy(() -> MaterialImpl.NETWORK_TYPE); + BinaryTagSerializer NBT_TYPE = BinaryTagSerializer.lazy(() -> MaterialImpl.NBT_TYPE); /** * Returns the material registry. diff --git a/src/main/java/net/minestom/server/item/component/PotDecorations.java b/src/main/java/net/minestom/server/item/component/PotDecorations.java index 35fd2302a..4d5fb4bd5 100644 --- a/src/main/java/net/minestom/server/item/component/PotDecorations.java +++ b/src/main/java/net/minestom/server/item/component/PotDecorations.java @@ -1,6 +1,5 @@ package net.minestom.server.item.component; -import net.kyori.adventure.nbt.BinaryTag; import net.minestom.server.item.Material; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.nbt.BinaryTagSerializer; @@ -17,24 +16,8 @@ public record PotDecorations( public static final @NotNull Material DEFAULT_ITEM = Material.BRICK; public static final PotDecorations EMPTY = new PotDecorations(DEFAULT_ITEM, DEFAULT_ITEM, DEFAULT_ITEM, DEFAULT_ITEM); - public static NetworkBuffer.Type NETWORK_TYPE = new NetworkBuffer.Type() { - @Override public void write(@NotNull NetworkBuffer buffer, PotDecorations value) { - Material.NETWORK_TYPE.list(4).map(PotDecorations::new, PotDecorations::asList).write(buffer, value); - } - - @Override public PotDecorations read(@NotNull NetworkBuffer buffer) { - return Material.NETWORK_TYPE.list(4).map(PotDecorations::new, PotDecorations::asList).read(buffer); - } - }; - public static BinaryTagSerializer NBT_TYPE = new BinaryTagSerializer() { - @Override public @NotNull BinaryTag write(@NotNull PotDecorations value) { - return Material.NBT_TYPE.list().map(PotDecorations::new, PotDecorations::asList).write(value); - } - - @Override public @NotNull PotDecorations read(@NotNull BinaryTag tag) { - return Material.NBT_TYPE.list().map(PotDecorations::new, PotDecorations::asList).read(tag); - } - }; + public static NetworkBuffer.Type NETWORK_TYPE = Material.NETWORK_TYPE.list(4).map(PotDecorations::new, PotDecorations::asList); + public static BinaryTagSerializer NBT_TYPE = Material.NBT_TYPE.list().map(PotDecorations::new, PotDecorations::asList); public PotDecorations(@NotNull List list) { this(getOrAir(list, 0), getOrAir(list, 1), getOrAir(list, 2), getOrAir(list, 3)); diff --git a/src/main/java/net/minestom/server/utils/ArrayUtils.java b/src/main/java/net/minestom/server/utils/ArrayUtils.java index fb32053f3..de23b16d1 100644 --- a/src/main/java/net/minestom/server/utils/ArrayUtils.java +++ b/src/main/java/net/minestom/server/utils/ArrayUtils.java @@ -76,4 +76,37 @@ public final class ArrayUtils { default -> Map.copyOf(new Object2ObjectArrayMap<>(keys, values, length)); }; } + + public static long[] pack(int[] ints, int bitsPerEntry) { + int intsPerLong = (int) Math.floor(64d / bitsPerEntry); + long[] longs = new long[(int) Math.ceil(ints.length / (double) intsPerLong)]; + + long mask = (1L << bitsPerEntry) - 1L; + for (int i = 0; i < longs.length; i++) { + for (int intIndex = 0; intIndex < intsPerLong; intIndex++) { + int bitIndex = intIndex * bitsPerEntry; + int intActualIndex = intIndex + i * intsPerLong; + if (intActualIndex < ints.length) { + longs[i] |= (ints[intActualIndex] & mask) << bitIndex; + } + } + } + + return longs; + } + + public static void unpack(int[] out, long[] in, int bitsPerEntry) { + assert in.length != 0: "unpack input array is zero"; + + var intsPerLong = Math.floor(64d / bitsPerEntry); + var intsPerLongCeil = (int) Math.ceil(intsPerLong); + + long mask = (1L << bitsPerEntry) - 1L; + for (int i = 0; i < out.length; i++) { + int longIndex = i / intsPerLongCeil; + int subIndex = i % intsPerLongCeil; + + out[i] = (int) ((in[longIndex] >>> (bitsPerEntry * subIndex)) & mask); + } + } } diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index aaa210ad1..9300de8e6 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -1,6 +1,5 @@ package net.minestom.server.utils.chunk; -import net.minestom.server.ServerFlag; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; import net.minestom.server.instance.Chunk; @@ -289,6 +288,14 @@ public final class ChunkUtils { return xyz & 0xF; } + public static int toRegionCoordinate(int chunkCoordinate) { + return chunkCoordinate >> 5; + } + + public static int toRegionLocal(int chunkCoordinate) { + return chunkCoordinate & 0x1F; + } + public static int floorSection(int coordinate) { return coordinate - (coordinate & 0xF); } diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java index f7b8fb29a..21b2ea82a 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.*; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; public interface BinaryTagSerializer { @@ -35,6 +36,27 @@ public interface BinaryTagSerializer { }; } + static @NotNull BinaryTagSerializer lazy(@NotNull Supplier> self) { + return new BinaryTagSerializer<>() { + private BinaryTagSerializer serializer = null; + + @Override + public @NotNull BinaryTag write(@NotNull T value) { + return serializer().write(value); + } + + @Override + public @NotNull T read(@NotNull BinaryTag tag) { + return serializer().read(tag); + } + + private BinaryTagSerializer serializer() { + if (serializer == null) serializer = self.get(); + return serializer; + } + }; + } + static @NotNull BinaryTagSerializer coerced(@NotNull BinaryTagType type) { return new BinaryTagSerializer<>() { @Override diff --git a/src/test/java/net/minestom/server/instance/AnvilLoaderIntegrationTest.java b/src/test/java/net/minestom/server/instance/AnvilLoaderIntegrationTest.java index e66729c4e..c58c9c81c 100644 --- a/src/test/java/net/minestom/server/instance/AnvilLoaderIntegrationTest.java +++ b/src/test/java/net/minestom/server/instance/AnvilLoaderIntegrationTest.java @@ -1,5 +1,6 @@ package net.minestom.server.instance; +import net.minestom.server.instance.anvil.AnvilLoader; import net.minestom.server.instance.block.Block; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.NamespaceID; diff --git a/src/test/java/net/minestom/server/instance/light/LightParityIntegrationTest.java b/src/test/java/net/minestom/server/instance/light/LightParityIntegrationTest.java index c9741e2fc..da61ba340 100644 --- a/src/test/java/net/minestom/server/instance/light/LightParityIntegrationTest.java +++ b/src/test/java/net/minestom/server/instance/light/LightParityIntegrationTest.java @@ -1,7 +1,11 @@ package net.minestom.server.instance.light; import net.minestom.server.coordinate.Vec; -import net.minestom.server.instance.*; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.instance.LightingChunk; +import net.minestom.server.instance.Section; +import net.minestom.server.instance.anvil.AnvilLoader; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.palette.Palette; import net.minestom.testing.Env; diff --git a/src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/level.dat b/src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/level.dat new file mode 100644 index 0000000000000000000000000000000000000000..1ab0d311332d0ddd81ed7f86eef9c422dc57aa65 GIT binary patch literal 2268 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.-1.-1.mca b/src/test/resources/net/minestom/server/instance/anvil_vanilla_sample/region/r.-1.-1.mca new file mode 100644 index 0000000000000000000000000000000000000000..59d7f272b82fdc071d6a38b3eea42c3e87a2c8d5 GIT binary patch literal 1748992 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

vVu*8g_ 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%0DYdht-g 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

gVL%_h&qkG0})DTU^iX#TK$(W@{e|}W4Vl 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

Ts{TE+{F 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