diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 5635b7385fa..4fab237a329 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -11,15 +11,23 @@ import net.minestom.server.ServerProcess; import net.minestom.server.ServerSettings; import net.minestom.server.command.CommandManager; +import net.minestom.server.entity.Player; 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.ItemStack; +import net.minestom.server.item.Material; +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.utils.identity.NamedAndIdentified; import net.minestom.server.utils.time.TimeUnit; +import org.jetbrains.annotations.NotNull; import java.time.Duration; +import java.util.List; public class Main { @@ -64,6 +72,7 @@ public static void main(String[] args) { commandManager.register(new TestCommand2()); commandManager.register(new ConfigCommand()); commandManager.register(new SidebarCommand(serverProcess)); + commandManager.register(new SetEntityType()); commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED))); @@ -105,6 +114,22 @@ public static void main(String[] args) { //responseData.setPlayersHidden(true); }); + var ironBlockRecipe = new ShapedRecipe( + "minestom:test", 2, 2, "", + RecipeCategory.Crafting.MISC, + List.of( + new DeclareRecipesPacket.Ingredient(List.of(ItemStack.of(Material.IRON_INGOT))), + new DeclareRecipesPacket.Ingredient(List.of(ItemStack.of(Material.IRON_INGOT))), + new DeclareRecipesPacket.Ingredient(List.of(ItemStack.of(Material.IRON_INGOT))), + new DeclareRecipesPacket.Ingredient(List.of(ItemStack.of(Material.IRON_INGOT))) + ), ItemStack.of(Material.IRON_BLOCK), true) { + @Override + public boolean shouldShow(@NotNull Player player) { + return true; + } + }; + serverProcess.getRecipeManager().addRecipe(ironBlockRecipe); + new PlayerInit(serverProcess).init(); // VelocityProxy.enable("abcdef"); diff --git a/demo/src/main/java/net/minestom/demo/commands/SetEntityType.java b/demo/src/main/java/net/minestom/demo/commands/SetEntityType.java new file mode 100644 index 00000000000..365db971fd9 --- /dev/null +++ b/demo/src/main/java/net/minestom/demo/commands/SetEntityType.java @@ -0,0 +1,29 @@ +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.ArgumentType; +import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentEntityType; +import net.minestom.server.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class SetEntityType extends Command { + private final ArgumentEntityType entityTypeArg = ArgumentType.EntityType("type"); + + public SetEntityType() { + super("setentitytype"); + + addSyntax(this::execute, entityTypeArg); + } + + private void execute(@NotNull CommandSender sender, @NotNull CommandContext context) { + if (!(sender instanceof Player player)) { + return; + } + + var entityType = context.get(entityTypeArg); + player.switchEntityType(entityType); + player.sendMessage("set entity type to " + entityType); + } +} diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 8046bf55817..3a771e19a58 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -1237,7 +1237,7 @@ protected void updatePose() { setPose(Pose.FALL_FLYING); } else if (entityMeta.isSwimming()) { setPose(Pose.SWIMMING); - } else if (this instanceof LivingEntity && ((LivingEntityMeta) entityMeta).isInRiptideSpinAttack()) { + } else if (entityMeta instanceof LivingEntityMeta livingMeta && livingMeta.isInRiptideSpinAttack()) { setPose(Pose.SPIN_ATTACK); } else if (entityMeta.isSneaking()) { setPose(Pose.SNEAKING); diff --git a/src/main/java/net/minestom/server/entity/Metadata.java b/src/main/java/net/minestom/server/entity/Metadata.java index 54f2f8dacda..f71041a7a7f 100644 --- a/src/main/java/net/minestom/server/entity/Metadata.java +++ b/src/main/java/net/minestom/server/entity/Metadata.java @@ -155,6 +155,8 @@ public static Entry Quaternion(float @NotNull[] value) { public static final byte TYPE_VECTOR3 = 26; public static final byte TYPE_QUATERNION = 27; + // Impl Note: Adding an entry here requires that a default value entry is added in MetadataImpl.EMPTY_VALUES + 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 7401369d520..c2777eee2bb 100644 --- a/src/main/java/net/minestom/server/entity/MetadataImpl.java +++ b/src/main/java/net/minestom/server/entity/MetadataImpl.java @@ -2,6 +2,10 @@ import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Vec; +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.instance.block.Block; import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.utils.Direction; @@ -10,10 +14,6 @@ import org.jetbrains.annotations.UnknownNullability; import org.jglrxavpok.hephaistos.nbt.NBTEnd; -import static net.minestom.server.entity.Metadata.Boolean; -import static net.minestom.server.entity.Metadata.Byte; -import static net.minestom.server.entity.Metadata.Float; -import static net.minestom.server.entity.Metadata.String; import static net.minestom.server.entity.Metadata.*; import static net.minestom.server.network.NetworkBuffer.VAR_INT; @@ -23,6 +23,7 @@ final class MetadataImpl { static { EMPTY_VALUES.set(TYPE_BYTE, Byte((byte) 0)); EMPTY_VALUES.set(TYPE_VARINT, VarInt(0)); + EMPTY_VALUES.set(TYPE_LONG, Long(0L)); EMPTY_VALUES.set(TYPE_FLOAT, Float(0f)); EMPTY_VALUES.set(TYPE_STRING, String("")); EMPTY_VALUES.set(TYPE_CHAT, Chat(Component.empty())); @@ -34,12 +35,20 @@ final class MetadataImpl { EMPTY_VALUES.set(TYPE_OPTPOSITION, OptPosition(null)); EMPTY_VALUES.set(TYPE_DIRECTION, Direction(Direction.DOWN)); 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_PARTICLE -> throw new UnsupportedOperationException(); EMPTY_VALUES.set(TYPE_VILLAGERDATA, VillagerData(0, 0, 0)); EMPTY_VALUES.set(TYPE_OPTVARINT, OptVarInt(null)); EMPTY_VALUES.set(TYPE_POSE, Pose(Entity.Pose.STANDING)); + EMPTY_VALUES.set(TYPE_CAT_VARIANT, CatVariant(CatMeta.Variant.TABBY)); + EMPTY_VALUES.set(TYPE_FROG_VARIANT, FrogVariant(FrogMeta.Variant.TEMPERATE)); + // OptGlobalPos + // PaintingVariant + EMPTY_VALUES.set(TYPE_SNIFFER_STATE, SnifferState(SnifferMeta.State.IDLING)); + 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 eed1160098b..d636546b4bd 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -34,6 +34,7 @@ import net.minestom.server.effects.Effects; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.fakeplayer.FakePlayer; +import net.minestom.server.entity.metadata.LivingEntityMeta; import net.minestom.server.entity.metadata.PlayerMeta; import net.minestom.server.entity.vehicle.PlayerVehicleInformation; import net.minestom.server.event.inventory.InventoryOpenEvent; @@ -823,14 +824,14 @@ protected void updatePose() { Pose newPose; // Figure out their expected state - var meta = Objects.requireNonNull(getLivingEntityMeta()); + var meta = getEntityMeta(); if (meta.isFlyingWithElytra()) { newPose = Pose.FALL_FLYING; } else if (false) { // When should they be sleeping? We don't have any in-bed state... newPose = Pose.SLEEPING; } else if (meta.isSwimming()) { newPose = Pose.SWIMMING; - } else if (meta.isInRiptideSpinAttack()) { + } else if (meta instanceof LivingEntityMeta livingMeta && livingMeta.isInRiptideSpinAttack()) { newPose = Pose.SPIN_ATTACK; } else if (isSneaking() && !isFlying()) { newPose = Pose.SNEAKING; @@ -1017,27 +1018,39 @@ public void setHealth(float health) { sendPacket(new UpdateHealthPacket(health, food, foodSaturation)); } - @Override - public @NotNull PlayerMeta getEntityMeta() { + /** + * Gets the entity meta for the player. + * + *

Note that this method will throw an exception if the player's entity type has + * been changed with {@link #switchEntityType(EntityType)}. It is wise to check + * {@link #getEntityType()} first.

+ */ + public @NotNull PlayerMeta getPlayerMeta() { return (PlayerMeta) super.getEntityMeta(); } /** * Gets the player additional hearts. * + *

Note that this function is uncallable if the player has their entity type switched + * with {@link #switchEntityType(EntityType)}.

+ * * @return the player additional hearts */ public float getAdditionalHearts() { - return getEntityMeta().getAdditionalHearts(); + return getPlayerMeta().getAdditionalHearts(); } /** * Changes the amount of additional hearts shown. * + *

Note that this function is uncallable if the player has their entity type switched + * with {@link #switchEntityType(EntityType)}.

+ * * @param additionalHearts the count of additional hearts */ public void setAdditionalHearts(float additionalHearts) { - getEntityMeta().setAdditionalHearts(additionalHearts); + getPlayerMeta().setAdditionalHearts(additionalHearts); } /** diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index 248afba081b..47b218fe80b 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -4,7 +4,7 @@ import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Player; -import net.minestom.server.entity.metadata.PlayerMeta; +import net.minestom.server.entity.metadata.LivingEntityMeta; import net.minestom.server.event.item.ItemUpdateStateEvent; import net.minestom.server.event.player.PlayerCancelDiggingEvent; import net.minestom.server.event.player.PlayerFinishDiggingEvent; @@ -144,8 +144,8 @@ private static void dropSingle(Player player) { } private static void updateItemState(Player player) { - PlayerMeta meta = player.getEntityMeta(); - if (!meta.isHandActive()) return; + LivingEntityMeta meta = player.getLivingEntityMeta(); + if (meta == null || !meta.isHandActive()) return; Player.Hand hand = meta.getActiveHand(); player.refreshEating(null); 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 4fc9765e02f..e6e001acab0 100644 --- a/src/main/java/net/minestom/server/listener/preplay/LoginListener.java +++ b/src/main/java/net/minestom/server/listener/preplay/LoginListener.java @@ -122,8 +122,12 @@ public static void loginEncryptionResponseListener(@NotNull ClientEncryptionResp final HttpClient client = HttpClient.newHttpClient(); final HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build(); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).whenComplete((response, throwable) -> { - if (throwable != null) { - connection.getServerProcess().getExceptionHandler().handleException(throwable); + final boolean ok = throwable == null && response.statusCode() == 200 && response.body() != null && !response.body().isEmpty(); + + if (!ok) { + if (throwable != null) { + connection.getServerProcess().getExceptionHandler().handleException(throwable); + } if (socketConnection.getPlayer() != null) { socketConnection.getPlayer().kick(Component.text("Failed to contact Mojang's Session Servers (Are they down?)")); } else { @@ -133,15 +137,6 @@ public static void loginEncryptionResponseListener(@NotNull ClientEncryptionResp } try { final JsonObject gameProfile = GSON.fromJson(response.body(), JsonObject.class); - if (gameProfile == null) { - // Invalid response - if (socketConnection.getPlayer() != null) { - socketConnection.getPlayer().kick(Component.text("Failed to get data from Mojang's Session Servers (Are they down?)")); - } else { - socketConnection.disconnect(); - } - return; - } socketConnection.setEncryptionKey(getSecretKey(mojangAuth, packet.sharedSecret())); UUID profileUUID = java.util.UUID.fromString(gameProfile.get("id").getAsString() .replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); 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 14d46a793a9..072d764ea4d 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 @@ -89,16 +89,16 @@ public void write(@NotNull NetworkBuffer writer) { } } - public record DeclaredShapedCraftingRecipe(@NotNull String recipeId, int width, int height, + public record DeclaredShapedCraftingRecipe(@NotNull String recipeId, @NotNull String group, @NotNull RecipeCategory.Crafting category, - @NotNull List ingredients, + int width, int height, @NotNull List ingredients, @NotNull ItemStack result, boolean showNotification) implements DeclaredRecipe { public DeclaredShapedCraftingRecipe { ingredients = List.copyOf(ingredients); } private DeclaredShapedCraftingRecipe(DeclaredShapedCraftingRecipe packet) { - this(packet.recipeId, packet.width, packet.height, packet.group, packet.category, packet.ingredients, packet.result, packet.showNotification); + this(packet.recipeId, packet.group, packet.category, packet.width, packet.height, packet.ingredients, packet.result, packet.showNotification); } public DeclaredShapedCraftingRecipe(@NotNull NetworkBuffer reader) { @@ -108,25 +108,25 @@ public DeclaredShapedCraftingRecipe(@NotNull NetworkBuffer reader) { private static DeclaredShapedCraftingRecipe read(@NotNull NetworkBuffer reader) { String recipeId = reader.read(STRING); - int width = reader.read(VAR_INT); - int height = reader.read(VAR_INT); String group = reader.read(STRING); RecipeCategory.Crafting category = reader.readEnum(RecipeCategory.Crafting.class); + int width = reader.read(VAR_INT); + int height = reader.read(VAR_INT); List ingredients = new ArrayList<>(); for (int slot = 0; slot < width * height; slot++) { ingredients.add(new Ingredient(reader)); } ItemStack result = reader.read(ITEM); boolean showNotification = reader.read(BOOLEAN); - return new DeclaredShapedCraftingRecipe(recipeId, width, height, group, category, ingredients, result, showNotification); + return new DeclaredShapedCraftingRecipe(recipeId, group, category, width, height, ingredients, result, showNotification); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, width); - writer.write(VAR_INT, height); writer.write(STRING, group); writer.writeEnum(RecipeCategory.Crafting.class, category); + writer.write(VAR_INT, width); + writer.write(VAR_INT, height); for (Ingredient ingredient : ingredients) { ingredient.write(writer); } diff --git a/src/main/java/net/minestom/server/recipe/RecipeManager.java b/src/main/java/net/minestom/server/recipe/RecipeManager.java index ba23a35235e..3f6db289851 100644 --- a/src/main/java/net/minestom/server/recipe/RecipeManager.java +++ b/src/main/java/net/minestom/server/recipe/RecipeManager.java @@ -59,10 +59,10 @@ private void refreshRecipesPacket() { recipesCache.add( new DeclareRecipesPacket.DeclaredShapedCraftingRecipe( shapedRecipe.getRecipeId(), - shapedRecipe.getWidth(), - shapedRecipe.getHeight(), shapedRecipe.getGroup(), shapedRecipe.getCategory(), + shapedRecipe.getWidth(), + shapedRecipe.getHeight(), shapedRecipe.getIngredients(), shapedRecipe.getResult(), shapedRecipe.getShowNotification())); diff --git a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java index 58131cb3913..e3a972b3612 100644 --- a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java +++ b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java @@ -14,16 +14,14 @@ import net.minestom.server.network.packet.client.handshake.ClientHandshakePacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.common.DisconnectPacket; -import net.minestom.server.network.packet.server.status.ResponsePacket; import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; import net.minestom.server.network.packet.server.login.LoginSuccessPacket; import net.minestom.server.network.packet.server.login.SetCompressionPacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.packet.server.play.DeclareRecipesPacket.Ingredient; import net.minestom.server.network.packet.server.status.PongPacket; +import net.minestom.server.network.packet.server.status.ResponsePacket; import net.minestom.server.recipe.RecipeCategory; -import net.minestom.server.utils.crypto.KeyUtils; -import org.apache.commons.net.util.Base64; import org.jglrxavpok.hephaistos.nbt.NBT; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -92,10 +90,10 @@ public static void setupServer() { ), new DeclareRecipesPacket.DeclaredShapedCraftingRecipe( "minecraft:torch", - 1, - 2, "", RecipeCategory.Crafting.MISC, + 1, + 2, List.of(new Ingredient(List.of(ItemStack.of(Material.COAL))), new Ingredient(List.of(ItemStack.of(Material.STICK)))), ItemStack.of(Material.TORCH),