diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 3ad55749f..556f4bccf 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -23,6 +23,7 @@ import net.minestom.server.network.packet.server.play.DeclareRecipesPacket; import net.minestom.server.ping.ResponseData; import net.minestom.server.recipe.RecipeCategory; import net.minestom.server.recipe.ShapedRecipe; +import net.minestom.server.recipe.ShapelessRecipe; import net.minestom.server.utils.identity.NamedAndIdentified; import net.minestom.server.utils.time.TimeUnit; import org.jetbrains.annotations.NotNull; @@ -33,11 +34,6 @@ import java.util.List; public class Main { public static void main(String[] args) { - try { - Class.forName(ItemComponent.class.getName()); - } catch (Exception e) { - throw new RuntimeException(e); - } System.setProperty("minestom.experiment.pose-updates", "true"); MinecraftServer.setCompressionThreshold(0); @@ -141,22 +137,22 @@ public class Main { } }; MinecraftServer.getRecipeManager().addRecipe(ironBlockRecipe); -// var recipe = new ShapelessRecipe( -// "minestom:test2", "abc", -// RecipeCategory.Crafting.MISC, -// List.of( -// new DeclareRecipesPacket.Ingredient(List.of(ItemStack.AIR)) -// ), -// ItemStack.builder(Material.GOLD_BLOCK) -// .set(ItemComponent.CUSTOM_NAME, Component.text("abc")) -// .build() -// ) { -// @Override -// public boolean shouldShow(@NotNull Player player) { -// return true; -// } -// }; -// MinecraftServer.getRecipeManager().addRecipe(recipe); + var recipe = new ShapelessRecipe( + "minestom:test2", "abc", + RecipeCategory.Crafting.MISC, + List.of( + new DeclareRecipesPacket.Ingredient(List.of(ItemStack.of(Material.DIRT))) + ), + ItemStack.builder(Material.GOLD_BLOCK) + .set(ItemComponent.CUSTOM_NAME, Component.text("abc")) + .build() + ) { + @Override + public boolean shouldShow(@NotNull Player player) { + return true; + } + }; + MinecraftServer.getRecipeManager().addRecipe(recipe); PlayerInit.init(); diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 458e9b72e..945981c9f 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -7,7 +7,6 @@ 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; @@ -39,18 +38,11 @@ import net.minestom.server.item.component.ItemBlockState; import net.minestom.server.item.component.PotionContents; 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.potion.CustomPotionEffect; import net.minestom.server.potion.PotionEffect; -import net.minestom.server.sound.SoundEvent; import net.minestom.server.utils.MathUtils; -import net.minestom.server.utils.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; @@ -98,8 +90,6 @@ 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 -> { @@ -188,20 +178,6 @@ 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(); @@ -250,8 +226,7 @@ public class PlayerInit { BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager(); MinecraftServer.getSchedulerManager().buildTask(() -> { - if (LAST_TICK.get() == null) return; - if (MinecraftServer.getConnectionManager().getOnlinePlayerCount() == 0) + if (LAST_TICK.get() == null || MinecraftServer.getConnectionManager().getOnlinePlayerCount() == 0) return; long ramUsage = benchmarkManager.getUsedMemory(); diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 36fdba7ff..d2b07a177 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -44,9 +44,9 @@ public final class MinecraftServer { public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class); - public static final String VERSION_NAME = "1.20.5"; + public static final String VERSION_NAME = "1.20.6"; public static final int PROTOCOL_VERSION = 766; - public static final int DATA_VERSION = 3837; + public static final int DATA_VERSION = 3839; // Threads public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark"; diff --git a/src/main/java/net/minestom/server/collision/BlockCollision.java b/src/main/java/net/minestom/server/collision/BlockCollision.java index df11fb6f6..3d3595421 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 properties where the bounding box + // we should always check both shapes, so no short-circuit here, to handle cases 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/component/DataComponentImpl.java b/src/main/java/net/minestom/server/component/DataComponentImpl.java index 381c980dd..aba9cc6a1 100644 --- a/src/main/java/net/minestom/server/component/DataComponentImpl.java +++ b/src/main/java/net/minestom/server/component/DataComponentImpl.java @@ -1,6 +1,7 @@ package net.minestom.server.component; import net.kyori.adventure.nbt.BinaryTag; +import net.minestom.server.item.ItemComponent; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.collection.ObjectArray; @@ -18,10 +19,10 @@ record DataComponentImpl( @Nullable NetworkBuffer.Type network, @Nullable BinaryTagSerializer nbt ) implements DataComponent { + static final Map> NAMESPACES = new HashMap<>(32); static final ObjectArray> IDS = ObjectArray.singleThread(32); - @Override public @NotNull T read(@NotNull BinaryTag tag) { Check.notNull(nbt, "{0} cannot be deserialized from NBT", this); @@ -50,4 +51,14 @@ record DataComponentImpl( public String toString() { return name(); } + + static { + try { + // Force init of item component for the weird edge case of tests which reference a component by + // loading it (with fromNamespaceId) before referencing a component by name + Class.forName(ItemComponent.class.getName()); + } catch (ClassNotFoundException e) { + // Ignored + } + } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 4029189e6..69791cc7c 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1096,14 +1096,14 @@ public class Player extends LivingEntity implements CommandSender, Localizable, } /** - * Sets and refresh client food saturationModifier. + * Sets and refresh client food saturation. * - * @param foodSaturation the food saturationModifier + * @param foodSaturation the food saturation * @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 20 */ public void setFoodSaturation(float foodSaturation) { Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 20), - "Food saturationModifier has to be between 0 and 20"); + "Food saturation 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/event/player/AsyncPlayerConfigurationEvent.java b/src/main/java/net/minestom/server/event/player/AsyncPlayerConfigurationEvent.java index 9e371032c..91be87c52 100644 --- a/src/main/java/net/minestom/server/event/player/AsyncPlayerConfigurationEvent.java +++ b/src/main/java/net/minestom/server/event/player/AsyncPlayerConfigurationEvent.java @@ -3,6 +3,7 @@ package net.minestom.server.event.player; import net.minestom.server.entity.Player; import net.minestom.server.event.trait.PlayerEvent; import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.server.configuration.ResetChatPacket; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -20,6 +21,7 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent { private final boolean isFirstConfig; private boolean hardcore; + private boolean clearChat; private boolean sendRegistryData; private Instance spawningInstance; @@ -28,6 +30,7 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent { this.isFirstConfig = isFirstConfig; this.hardcore = false; + this.clearChat = false; this.sendRegistryData = isFirstConfig; this.spawningInstance = null; } @@ -52,6 +55,29 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent { this.hardcore = hardcore; } + /** + * If true, the player's chat will be cleared when exiting the configuration state, otherwise + * it will be preserved. The default is not to clear the chat. + * + * @return true if the chat will be cleared, false otherwise + * + * @see ResetChatPacket + */ + public boolean willClearChat() { + return clearChat; + } + + /** + * Set whether the player's chat will be cleared when exiting the configuration state. + * + * @param clearChat true to clear the chat, false otherwise + * + * @see ResetChatPacket + */ + public void setClearChat(boolean clearChat) { + this.clearChat = clearChat; + } + public boolean willSendRegistryData() { return sendRegistryData; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerAnvilInputEvent.java b/src/main/java/net/minestom/server/event/player/PlayerAnvilInputEvent.java new file mode 100644 index 000000000..5307d3d53 --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerAnvilInputEvent.java @@ -0,0 +1,31 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.network.packet.client.play.ClientNameItemPacket; +import org.jetbrains.annotations.NotNull; + +/** + * Called every time a {@link Player} types a letter in an anvil GUI. + * + * @see ClientNameItemPacket + */ +public class PlayerAnvilInputEvent implements PlayerInstanceEvent { + + private final Player player; + private final String input; + + public PlayerAnvilInputEvent(@NotNull Player player, @NotNull String input) { + this.player = player; + this.input = input; + } + + @Override + public @NotNull Player getPlayer() { + return player; + } + + public @NotNull String getInput() { + return input; + } +} diff --git a/src/main/java/net/minestom/server/instance/LightingChunk.java b/src/main/java/net/minestom/server/instance/LightingChunk.java index a0d522b98..8e4801a8c 100644 --- a/src/main/java/net/minestom/server/instance/LightingChunk.java +++ b/src/main/java/net/minestom/server/instance/LightingChunk.java @@ -1,7 +1,5 @@ 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; 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 249a44cdc..ae43b7798 100644 --- a/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java +++ b/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java @@ -1,13 +1,31 @@ package net.minestom.server.item.attribute; +import net.minestom.server.entity.EquipmentSlot; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + public enum AttributeSlot { - ANY, - MAINHAND, - OFFHAND, - FEET, - LEGS, - CHEST, - HEAD, - ARMOR, - BODY, + ANY(EquipmentSlot.values()), + MAINHAND(EquipmentSlot.MAIN_HAND), + OFFHAND(EquipmentSlot.OFF_HAND), + FEET(EquipmentSlot.BOOTS), + LEGS(EquipmentSlot.LEGGINGS), + CHEST(EquipmentSlot.CHESTPLATE), + HEAD(EquipmentSlot.HELMET), + ARMOR(EquipmentSlot.CHESTPLATE, EquipmentSlot.LEGGINGS, EquipmentSlot.BOOTS, EquipmentSlot.HELMET), + BODY(EquipmentSlot.CHESTPLATE, EquipmentSlot.LEGGINGS); + + private final List equipmentSlots; + + AttributeSlot(@NotNull EquipmentSlot... equipmentSlots) { + this.equipmentSlots = List.of(equipmentSlots); + } + + /** + * Returns the (potentially multiple) equipment slots associated with this attribute slot. + */ + public @NotNull List equipmentSlots() { + return this.equipmentSlots; + } } 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 e914d4eff..2c5f073b1 100644 --- a/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java +++ b/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java @@ -6,9 +6,12 @@ import org.jetbrains.annotations.NotNull; import java.util.UUID; -public record ItemAttribute(@NotNull UUID uuid, - @NotNull String name, - @NotNull Attribute attribute, - @NotNull AttributeOperation operation, double amount, - @NotNull AttributeSlot slot) { +public record ItemAttribute( + @NotNull UUID uuid, + @NotNull String name, + @NotNull Attribute attribute, + @NotNull AttributeOperation operation, + double amount, + @NotNull AttributeSlot slot +) { } 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 6759db732..9645e2f29 100644 --- a/src/main/java/net/minestom/server/item/component/EnchantmentList.java +++ b/src/main/java/net/minestom/server/item/component/EnchantmentList.java @@ -83,4 +83,8 @@ public record EnchantmentList(@NotNull Map enchantments, b newEnchantments.put(enchantment, level); return new EnchantmentList(newEnchantments, showInTooltip); } + + public @NotNull EnchantmentList withTooltip(boolean showInTooltip) { + return new EnchantmentList(enchantments, showInTooltip); + } } diff --git a/src/main/java/net/minestom/server/listener/AnvilListener.java b/src/main/java/net/minestom/server/listener/AnvilListener.java new file mode 100644 index 000000000..04226d14a --- /dev/null +++ b/src/main/java/net/minestom/server/listener/AnvilListener.java @@ -0,0 +1,25 @@ +package net.minestom.server.listener; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.event.player.PlayerAnvilInputEvent; +import net.minestom.server.inventory.ContainerInventory; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.network.packet.client.play.ClientNameItemPacket; +import org.jetbrains.annotations.NotNull; + +public final class AnvilListener { + + public static void nameItemListener(@NotNull ClientNameItemPacket packet, @NotNull Player player) { + if (!(player.getOpenInventory() instanceof ContainerInventory openInventory)) + return; + if (openInventory.getInventoryType() != InventoryType.ANVIL) + return; + + EventDispatcher.call(new PlayerAnvilInputEvent(player, packet.itemName())); + } + + private AnvilListener() { + } + +} diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 8ae86fcfd..042cd6582 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -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 properties 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 cases 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/manager/PacketListenerManager.java b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java index 740f6a5ff..a32635e63 100644 --- a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java +++ b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java @@ -96,6 +96,7 @@ public final class PacketListenerManager { setPlayListener(ClientChunkBatchReceivedPacket.class, ChunkBatchListener::batchReceivedListener); setPlayListener(ClientPingRequestPacket.class, PlayPingListener::requestListener); setListener(ConnectionState.PLAY, ClientCookieResponsePacket.class, CookieListener::handleCookieResponse); + setPlayListener(ClientNameItemPacket.class, AnvilListener::nameItemListener); } /** diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 22e892d0e..38c9e9989 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -13,10 +13,12 @@ import net.minestom.server.instance.Instance; import net.minestom.server.listener.preplay.LoginListener; import net.minestom.server.message.Messenger; import net.minestom.server.network.packet.client.login.ClientLoginStartPacket; +import net.minestom.server.network.packet.server.CachedPacket; 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.ResetChatPacket; 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; @@ -63,6 +65,8 @@ public final class ConnectionManager { private final Set unmodifiableConfigurationPlayers = Collections.unmodifiableSet(configurationPlayers); private final Set unmodifiablePlayPlayers = Collections.unmodifiableSet(playPlayers); + private final CachedPacket resetChatPacket = new CachedPacket(new ResetChatPacket()); + // The uuid provider once a player login private volatile UuidProvider uuidProvider = (playerConnection, username) -> UUID.randomUUID(); @@ -276,6 +280,10 @@ public final class ConnectionManager { final Instance spawningInstance = event.getSpawningInstance(); Check.notNull(spawningInstance, "You need to specify a spawning instance in the AsyncPlayerConfigurationEvent"); + if (event.willClearChat()) { + player.sendPacket(resetChatPacket); + } + // Registry data (if it should be sent) if (event.willSendRegistryData()) { 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 1517a4d62..f81771c97 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 @@ -18,7 +18,9 @@ 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), Intent.fromId(reader.read(VAR_INT))); + reader.read(UNSIGNED_SHORT), + // Not a readEnum call because the indices are not 0-based + Intent.fromId(reader.read(VAR_INT))); } @Override @@ -30,6 +32,7 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA } writer.write(STRING, serverAddress); writer.write(UNSIGNED_SHORT, serverPort); + // Not a writeEnum call because the indices are not 0-based writer.write(VAR_INT, intent.id()); } 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 999e72829..d54699576 100644 --- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java +++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java @@ -142,7 +142,7 @@ public interface BinaryTagSerializer { @Override public @NotNull Integer read(@NotNull BinaryTag tag) { - return tag instanceof IntBinaryTag intBinaryTag ? intBinaryTag.value() : 0; + return tag instanceof NumberBinaryTag numberTag ? numberTag.intValue() : 0; } };