From 4f0944ba9fd1d2513fda93fd6f10d7b5d5e1d33a Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Mon, 1 Mar 2021 15:25:23 +0000 Subject: [PATCH] Initial adventure implementation - deprecate old text classes - make CommandSender and Audience - implement in ConsoleSender - partially implement in Player --- build.gradle | 7 + .../minestom/server/chat/ChatClickEvent.java | 1 + .../net/minestom/server/chat/ChatColor.java | 1 + .../minestom/server/chat/ChatHoverEvent.java | 1 + .../net/minestom/server/chat/ChatParser.java | 1 + .../net/minestom/server/chat/ColoredText.java | 1 + .../net/minestom/server/chat/JsonMessage.java | 12 ++ .../net/minestom/server/chat/RichMessage.java | 1 + .../server/chat/TranslatableText.java | 1 + .../server/command/CommandSender.java | 19 +- .../server/command/ConsoleSender.java | 10 + .../net/minestom/server/entity/Player.java | 171 +++++++++++++----- .../packet/server/play/ChatMessagePacket.java | 74 +++++++- .../packet/server/play/DisconnectPacket.java | 18 +- .../play/PlayerListHeaderAndFooterPacket.java | 27 ++- .../packet/server/play/TitlePacket.java | 81 +++++++-- .../net/minestom/server/utils/TickUtils.java | 25 +++ 17 files changed, 357 insertions(+), 94 deletions(-) create mode 100644 src/main/java/net/minestom/server/utils/TickUtils.java diff --git a/build.gradle b/build.gradle index 1ecf25be9..af869d0d8 100644 --- a/build.gradle +++ b/build.gradle @@ -182,6 +182,13 @@ dependencies { generatorsImplementation("com.squareup:javapoet:1.13.0") + + // Adventure, for text messages + api platform("net.kyori:adventure-bom:4.5.1") + api "net.kyori:adventure-api" + implementation "net.kyori:adventure-text-serializer-gson" + implementation "net.kyori:adventure-text-serializer-plain" + implementation "net.kyori:adventure-text-serializer-legacy" } publishing { diff --git a/src/main/java/net/minestom/server/chat/ChatClickEvent.java b/src/main/java/net/minestom/server/chat/ChatClickEvent.java index 3f002a44e..331ed1d3c 100644 --- a/src/main/java/net/minestom/server/chat/ChatClickEvent.java +++ b/src/main/java/net/minestom/server/chat/ChatClickEvent.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.NotNull; /** * Represents a click event for a specific portion of the message. */ +@Deprecated public class ChatClickEvent { private final String action; diff --git a/src/main/java/net/minestom/server/chat/ChatColor.java b/src/main/java/net/minestom/server/chat/ChatColor.java index d2bf99a86..c53a4d0ee 100644 --- a/src/main/java/net/minestom/server/chat/ChatColor.java +++ b/src/main/java/net/minestom/server/chat/ChatColor.java @@ -18,6 +18,7 @@ import java.util.Map; *

* Immutable class. */ +@Deprecated public final class ChatColor { // Special diff --git a/src/main/java/net/minestom/server/chat/ChatHoverEvent.java b/src/main/java/net/minestom/server/chat/ChatHoverEvent.java index 191f043ca..a236ff9b4 100644 --- a/src/main/java/net/minestom/server/chat/ChatHoverEvent.java +++ b/src/main/java/net/minestom/server/chat/ChatHoverEvent.java @@ -10,6 +10,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound; /** * Represents a hover event for a specific portion of the message. */ +@Deprecated public class ChatHoverEvent { private final String action; diff --git a/src/main/java/net/minestom/server/chat/ChatParser.java b/src/main/java/net/minestom/server/chat/ChatParser.java index 6211fd1e0..7628ff9c0 100644 --- a/src/main/java/net/minestom/server/chat/ChatParser.java +++ b/src/main/java/net/minestom/server/chat/ChatParser.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; /** * Class used to convert JSON string to proper chat message representation. */ +@Deprecated public final class ChatParser { public static final char COLOR_CHAR = (char) 0xA7; // Represent the character 'ยง' diff --git a/src/main/java/net/minestom/server/chat/ColoredText.java b/src/main/java/net/minestom/server/chat/ColoredText.java index 9da56f302..7739a8655 100644 --- a/src/main/java/net/minestom/server/chat/ColoredText.java +++ b/src/main/java/net/minestom/server/chat/ColoredText.java @@ -17,6 +17,7 @@ import java.util.regex.Pattern; * To create one, you simply call one of the static methods like {@link #of(ChatColor, String)}, * you can then continue to append text with {@link #append(ChatColor, String)}. */ +@Deprecated public class ColoredText extends JsonMessage { private static final char SEPARATOR_START = '{'; diff --git a/src/main/java/net/minestom/server/chat/JsonMessage.java b/src/main/java/net/minestom/server/chat/JsonMessage.java index 3842b738a..1cfa81a7a 100644 --- a/src/main/java/net/minestom/server/chat/JsonMessage.java +++ b/src/main/java/net/minestom/server/chat/JsonMessage.java @@ -3,6 +3,8 @@ package net.minestom.server.chat; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -14,6 +16,7 @@ import java.util.Objects; * * @see Chat Format */ +@Deprecated public abstract class JsonMessage { // true if the compiled string is up-to-date, false otherwise @@ -49,6 +52,14 @@ public abstract class JsonMessage { return getTextMessage(getJsonObject()).toString(); } + /** + * Gets this JSON message as an Adventure Component. + * @return the component + */ + public Component asComponent() { + return GsonComponentSerializer.gson().deserialize(this.toString()); + } + /** * Gets the Json representation. *

@@ -102,6 +113,7 @@ public abstract class JsonMessage { return message; } + @Deprecated public static class RawJsonMessage extends JsonMessage { private final JsonObject jsonObject; diff --git a/src/main/java/net/minestom/server/chat/RichMessage.java b/src/main/java/net/minestom/server/chat/RichMessage.java index 97a3e4d0a..986e1727c 100644 --- a/src/main/java/net/minestom/server/chat/RichMessage.java +++ b/src/main/java/net/minestom/server/chat/RichMessage.java @@ -19,6 +19,7 @@ import java.util.List; * events can be assigned with {@link #setClickEvent(ChatClickEvent)} and {@link #setHoverEvent(ChatHoverEvent)} * and new text element can also be appended {@link #append(ColoredText)}. */ +@Deprecated public class RichMessage extends JsonMessage { private final List components = new ArrayList<>(); diff --git a/src/main/java/net/minestom/server/chat/TranslatableText.java b/src/main/java/net/minestom/server/chat/TranslatableText.java index 9be8b7d37..62a88ad2d 100644 --- a/src/main/java/net/minestom/server/chat/TranslatableText.java +++ b/src/main/java/net/minestom/server/chat/TranslatableText.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a translatable component which can be used in {@link ColoredText}. */ +@Deprecated public class TranslatableText { private final String code; diff --git a/src/main/java/net/minestom/server/command/CommandSender.java b/src/main/java/net/minestom/server/command/CommandSender.java index b32d49da9..a32561fde 100644 --- a/src/main/java/net/minestom/server/command/CommandSender.java +++ b/src/main/java/net/minestom/server/command/CommandSender.java @@ -1,5 +1,7 @@ package net.minestom.server.command; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; import net.minestom.server.chat.JsonMessage; import net.minestom.server.entity.Player; import net.minestom.server.permission.PermissionHandler; @@ -10,20 +12,26 @@ import org.jetbrains.annotations.NotNull; *

* Main implementations are {@link Player} and {@link ConsoleSender}. */ -public interface CommandSender extends PermissionHandler { +public interface CommandSender extends PermissionHandler, Audience { /** * Sends a raw string message. * * @param message the message to send + * + * @deprecated Use {@link #sendMessage(Component)} */ + @Deprecated void sendMessage(@NotNull String message); /** * Sends multiple raw string messages. * * @param messages the messages to send + * + * @deprecated Use {@link #sendMessage(Component)} */ + @Deprecated default void sendMessage(@NotNull String[] messages) { for (String message : messages) { sendMessage(message); @@ -35,13 +43,12 @@ public interface CommandSender extends PermissionHandler { * If this is not a {@link Player}, only the content of the message will be sent as a string. * * @param text The {@link JsonMessage} to send. + * + * @deprecated Use {@link #sendMessage(Component)} * */ + @Deprecated default void sendMessage(@NotNull JsonMessage text) { - if (this instanceof Player) { - this.sendMessage(text); - } else { - sendMessage(text.getRawMessage()); - } + this.sendMessage(text.asComponent()); } /** diff --git a/src/main/java/net/minestom/server/command/ConsoleSender.java b/src/main/java/net/minestom/server/command/ConsoleSender.java index 826b78be3..da3480225 100644 --- a/src/main/java/net/minestom/server/command/ConsoleSender.java +++ b/src/main/java/net/minestom/server/command/ConsoleSender.java @@ -1,6 +1,11 @@ package net.minestom.server.command; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; import net.minestom.server.permission.Permission; +import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,6 +22,11 @@ public class ConsoleSender implements CommandSender { private final Set permissions = new CopyOnWriteArraySet<>(); + @Override + public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { + LOGGER.info(PlainComponentSerializer.plain().serialize(message)); + } + @Override public void sendMessage(@NotNull String message) { LOGGER.info(message); diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 006be9c9a..4ffd7e79a 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1,6 +1,14 @@ package net.minestom.server.entity; import com.google.common.collect.Queues; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.inventory.Book; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.title.Title; import net.minestom.server.MinecraftServer; import net.minestom.server.advancements.AdvancementTab; import net.minestom.server.attribute.Attribute; @@ -59,6 +67,7 @@ import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.UpdateOption; import net.minestom.server.utils.validate.Check; import net.minestom.server.world.DimensionType; +import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -766,6 +775,7 @@ public class Player extends LivingEntity implements CommandSender { * @param message the message to send, * you can use {@link ColoredText} and/or {@link RichMessage} to create it easily */ + @Override public void sendMessage(@NotNull JsonMessage message) { sendJsonMessage(message.toString()); } @@ -775,7 +785,10 @@ public class Player extends LivingEntity implements CommandSender { * * @param text the text with the legacy color formatting * @param colorChar the color character + * + * @deprecated Use {@link #sendMessage(Component)} */ + @Deprecated public void sendLegacyMessage(@NotNull String text, char colorChar) { ColoredText coloredText = ColoredText.ofLegacy(text, colorChar); sendJsonMessage(coloredText.toString()); @@ -785,15 +798,26 @@ public class Player extends LivingEntity implements CommandSender { * Sends a legacy message with the default color char {@link ChatParser#COLOR_CHAR}. * * @param text the text with the legacy color formatting + * + * @deprecated Use {@link #sendMessage(Component)} */ + @Deprecated public void sendLegacyMessage(@NotNull String text) { ColoredText coloredText = ColoredText.ofLegacy(text, ChatParser.COLOR_CHAR); sendJsonMessage(coloredText.toString()); } + /** + * @deprecated Use {@link #sendMessage(Component)} + */ + @Deprecated public void sendJsonMessage(@NotNull String json) { - ChatMessagePacket chatMessagePacket = - new ChatMessagePacket(json, ChatMessagePacket.Position.CHAT); + this.sendMessage(json); + } + + @Override + public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { + ChatMessagePacket chatMessagePacket = new ChatMessagePacket(GsonComponentSerializer.gson().serialize(message), type, source.uuid()); playerConnection.sendPacket(chatMessagePacket); } @@ -926,13 +950,21 @@ public class Player extends LivingEntity implements CommandSender { * * @param header the header text, null to set empty * @param footer the footer text, null to set empty + * + * @deprecated Use {@link #sendPlayerListHeaderAndFooter(Component, Component)} */ + @Deprecated public void sendHeaderFooter(@Nullable JsonMessage header, @Nullable JsonMessage footer) { - PlayerListHeaderAndFooterPacket playerListHeaderAndFooterPacket = new PlayerListHeaderAndFooterPacket(); - playerListHeaderAndFooterPacket.header = header; - playerListHeaderAndFooterPacket.footer = footer; + this.sendPlayerListHeaderAndFooter(header == null ? Component.empty() : header.asComponent(), + footer == null ? Component.empty() : footer.asComponent()); + } - playerConnection.sendPacket(playerListHeaderAndFooterPacket); + @Override + public void sendPlayerListHeaderAndFooter(@NonNull Component header, @NonNull Component footer) { + playerConnection.sendPacket(new PlayerListHeaderAndFooterPacket( + GsonComponentSerializer.gson().serialize(header), + GsonComponentSerializer.gson().serialize(footer) + )); } /** @@ -941,25 +973,12 @@ public class Player extends LivingEntity implements CommandSender { * @param text the text of the title * @param action the action of the title (where to show it) * @see #sendTitleTime(int, int, int) to specify the display time + * + * @deprecated Use {@link #showTitle(Title)} and {@link #sendActionBar(Component)} */ + @Deprecated private void sendTitle(@NotNull JsonMessage text, @NotNull TitlePacket.Action action) { - TitlePacket titlePacket = new TitlePacket(); - titlePacket.action = action; - - switch (action) { - case SET_TITLE: - titlePacket.titleText = text; - break; - case SET_SUBTITLE: - titlePacket.subtitleText = text; - break; - case SET_ACTION_BAR: - titlePacket.actionBarText = text; - break; - default: - throw new UnsupportedOperationException("Invalid TitlePacket.Action type!"); - } - + TitlePacket titlePacket = new TitlePacket(action, text.toString()); playerConnection.sendPacket(titlePacket); } @@ -969,10 +988,12 @@ public class Player extends LivingEntity implements CommandSender { * @param title the title message * @param subtitle the subtitle message * @see #sendTitleTime(int, int, int) to specify the display time + * + * @deprecated Use {@link #showTitle(Title)} */ + @Deprecated public void sendTitleSubtitleMessage(@NotNull JsonMessage title, @NotNull JsonMessage subtitle) { - sendTitle(title, TitlePacket.Action.SET_TITLE); - sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE); + this.showTitle(Title.title(title.asComponent(), subtitle.asComponent())); } /** @@ -980,9 +1001,12 @@ public class Player extends LivingEntity implements CommandSender { * * @param title the title message * @see #sendTitleTime(int, int, int) to specify the display time + * + * @deprecated Use {@link #showTitle(Title)} */ + @Deprecated public void sendTitleMessage(@NotNull JsonMessage title) { - sendTitle(title, TitlePacket.Action.SET_TITLE); + this.showTitle(Title.title(title.asComponent(), Component.empty())); } /** @@ -990,9 +1014,12 @@ public class Player extends LivingEntity implements CommandSender { * * @param subtitle the subtitle message * @see #sendTitleTime(int, int, int) to specify the display time + * + * @deprecated Use {@link #showTitle(Title)} */ + @Deprecated public void sendSubtitleMessage(@NotNull JsonMessage subtitle) { - sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE); + this.showTitle(Title.title(Component.empty(), subtitle.asComponent())); } /** @@ -1000,9 +1027,25 @@ public class Player extends LivingEntity implements CommandSender { * * @param actionBar the action bar message * @see #sendTitleTime(int, int, int) to specify the display time + * + * @deprecated Use {@link #sendActionBar(Component)} */ + @Deprecated public void sendActionBarMessage(@NotNull JsonMessage actionBar) { - sendTitle(actionBar, TitlePacket.Action.SET_ACTION_BAR); + this.sendActionBar(actionBar.asComponent()); + } + + @Override + public void showTitle(@NonNull Title title) { + for (TitlePacket titlePacket : TitlePacket.of(title)) { + playerConnection.sendPacket(titlePacket); + } + } + + @Override + public void sendActionBar(@NonNull Component message) { + TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, GsonComponentSerializer.gson().serialize(message)); + playerConnection.sendPacket(titlePacket); } /** @@ -1011,31 +1054,40 @@ public class Player extends LivingEntity implements CommandSender { * @param fadeIn ticks to spend fading in * @param stay ticks to keep the title displayed * @param fadeOut ticks to spend out, not when to start fading out + * + * @deprecated Use {@link #showTitle(Title)}. Note that this will overwrite the + * existing title. This is expected behavior and will be the case in 1.17. */ + @Deprecated public void sendTitleTime(int fadeIn, int stay, int fadeOut) { - TitlePacket titlePacket = new TitlePacket(); - titlePacket.action = TitlePacket.Action.SET_TIMES_AND_DISPLAY; - titlePacket.fadeIn = fadeIn; - titlePacket.stay = stay; - titlePacket.fadeOut = fadeOut; + TitlePacket titlePacket = new TitlePacket(fadeIn, stay, fadeOut); playerConnection.sendPacket(titlePacket); } /** * Hides the previous title. + * @deprecated Use {@link #clearTitle()}. Note this title cannot be shown again. This + * is expected behavior and will be the case in 1.17. */ + @Deprecated public void hideTitle() { - TitlePacket titlePacket = new TitlePacket(); - titlePacket.action = TitlePacket.Action.HIDE; + TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.HIDE); playerConnection.sendPacket(titlePacket); } /** * Resets the previous title. + * @deprecated Use {@link #clearTitle()}. Note this title cannot be shown again. This + * is expected behavior and will be the case in 1.17. */ public void resetTitle() { - TitlePacket titlePacket = new TitlePacket(); - titlePacket.action = TitlePacket.Action.RESET; + TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.RESET); + playerConnection.sendPacket(titlePacket); + } + + @Override + public void clearTitle() { + TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.RESET); playerConnection.sendPacket(titlePacket); } @@ -1043,7 +1095,10 @@ public class Player extends LivingEntity implements CommandSender { * Opens a book ui for the player with the given book metadata. * * @param bookMeta The metadata of the book to open + * + * @deprecated Use {@link #openBook(Book)} */ + @Deprecated public void openBook(@NotNull WrittenBookMeta bookMeta) { // Set book in offhand final ItemStack writtenBook = new ItemStack(Material.WRITTEN_BOOK, (byte) 1); @@ -1063,6 +1118,11 @@ public class Player extends LivingEntity implements CommandSender { this.inventory.update(); } + @Override + public void openBook(@NonNull Book book) { + // TODO write the book + } + @Override public boolean isImmune(@NotNull DamageType type) { if (!getGameMode().canTakeDamage()) { @@ -1730,16 +1790,40 @@ public class Player extends LivingEntity implements CommandSender { * Kicks the player with a reason. * * @param text the kick reason + * + * @deprecated Use {@link #kick(Component)} */ + @Deprecated public void kick(@NotNull JsonMessage text) { + this.kick(text.asComponent()); + } + + /** + * Kicks the player with a reason. + * + * @param message the kick reason + * + * @deprecated Use {@link #kick(Component)} + */ + @Deprecated + public void kick(@NotNull String message) { + this.kick(Component.text(message)); + } + + /** + * Kicks the player with a reason. + * + * @param component the reason + */ + public void kick(@NotNull Component component) { final ConnectionState connectionState = playerConnection.getConnectionState(); // Packet type depends on the current player connection state final ServerPacket disconnectPacket; if (connectionState == ConnectionState.LOGIN) { - disconnectPacket = new LoginDisconnectPacket(text); + disconnectPacket = new LoginDisconnectPacket(GsonComponentSerializer.gson().serialize(component)); } else { - disconnectPacket = new DisconnectPacket(text); + disconnectPacket = new DisconnectPacket(GsonComponentSerializer.gson().serialize(component)); } if (playerConnection instanceof NettyPlayerConnection) { @@ -1751,15 +1835,6 @@ public class Player extends LivingEntity implements CommandSender { } } - /** - * Kicks the player with a reason. - * - * @param message the kick reason - */ - public void kick(@NotNull String message) { - kick(ColoredText.of(message)); - } - /** * Changes the current held slot for the player. * diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java index 02d096e76..a498275d8 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java @@ -1,32 +1,68 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.text.Component; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.UUID; +/** + * Represents an outgoing chat message packet. Do not use this to send messages above the + * hotbar (the game info position) as it is preferred to use + * {@link TitlePacket} due to MC-119145. + */ public class ChatMessagePacket implements ServerPacket { + private static final UUID NULL_UUID = new UUID(0, 0); public String jsonMessage; - public Position position; + public MessageType messageType; public UUID uuid; + @Deprecated public ChatMessagePacket(String jsonMessage, Position position, UUID uuid) { - this.jsonMessage = jsonMessage; - this.position = position; - this.uuid = uuid; + this(jsonMessage, position.asMessageType(), uuid); } + @Deprecated public ChatMessagePacket(String jsonMessage, Position position) { - this(jsonMessage, position, new UUID(0, 0)); + this(jsonMessage, position, NULL_UUID); + } + + /** + * Constructs a new chat message packet with a zeroed UUID. To send formatted + * messages please use the respective {@link Audience#sendMessage(Component)} + * functions. + * + * @param jsonMessage the raw message payload + * @param messageType the message type + */ + public ChatMessagePacket(String jsonMessage, MessageType messageType) { + this(jsonMessage, messageType, NULL_UUID); + } + + /** + * Constructs a new chat message packet. To send formatted messages please use the + * respective {@link Audience#sendMessage(Component)} functions. + * + * @param jsonMessage the raw message payload + * @param messageType the message type + * @param uuid the sender of the chat message + */ + public ChatMessagePacket(String jsonMessage, MessageType messageType, UUID uuid) { + this.jsonMessage = jsonMessage; + this.messageType = messageType; + this.uuid = uuid; } @Override public void write(@NotNull BinaryWriter writer) { writer.writeSizedString(jsonMessage); - writer.writeByte((byte) position.ordinal()); + writer.writeByte((byte) (messageType == null ? 3 : messageType.ordinal())); writer.writeUuid(uuid); } @@ -35,9 +71,29 @@ public class ChatMessagePacket implements ServerPacket { return ServerPacketIdentifier.CHAT_MESSAGE; } + /** + * @deprecated Use {@link MessageType} + */ + @Deprecated public enum Position { - CHAT, - SYSTEM_MESSAGE, - GAME_INFO + CHAT(MessageType.CHAT), + SYSTEM_MESSAGE(MessageType.SYSTEM), + GAME_INFO(null); + + private final MessageType messageType; + + Position(MessageType messageType) { + this.messageType = messageType; + } + + /** + * Gets this position as an Adventure message type. Note this will return + * {@code null} for {@link #GAME_INFO} as it is preferred to use + * {@link TitlePacket} due to MC-119145. + * @return the message type + */ + public @Nullable MessageType asMessageType() { + return this.messageType; + } } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java index 3ef3a6512..c3ef7bd56 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java @@ -1,5 +1,6 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.text.Component; import net.minestom.server.chat.JsonMessage; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -7,16 +8,27 @@ import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; public class DisconnectPacket implements ServerPacket { + private String payload; - public JsonMessage message; // Only text + /** + * Creates a new disconnect packet with a given string. + * @param payload the message + */ + public DisconnectPacket(@NotNull String payload) { + this.payload = payload; + } + /** + * @deprecated Use {@link #DisconnectPacket(String)} + */ + @Deprecated public DisconnectPacket(@NotNull JsonMessage message){ - this.message = message; + this(message.toString()); } @Override public void write(@NotNull BinaryWriter writer) { - writer.writeSizedString(message.toString()); + writer.writeSizedString(payload); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java index 245437adf..021f98727 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java @@ -1,31 +1,26 @@ package net.minestom.server.network.packet.server.play; -import net.minestom.server.chat.JsonMessage; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryWriter; +import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; public class PlayerListHeaderAndFooterPacket implements ServerPacket { + public String header; + public String footer; - private static final String EMPTY_COMPONENT = "{\"translate\":\"\"}"; - - public JsonMessage header; // Only text - public JsonMessage footer; // Only text + public PlayerListHeaderAndFooterPacket(@NotNull String header, @NotNull String footer) { + Validate.notNull(header, "Header cannot be null."); + Validate.notNull(footer, "Footer cannot be null."); + this.header = header; + this.footer = footer; + } @Override public void write(@NotNull BinaryWriter writer) { - if (header == null) { - writer.writeSizedString(EMPTY_COMPONENT); - } else { - writer.writeSizedString(header.toString()); - } - - if (footer == null) { - writer.writeSizedString(EMPTY_COMPONENT); - } else { - writer.writeSizedString(footer.toString()); - } + writer.writeSizedString(header); + writer.writeSizedString(footer); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java index 2a54e9770..25f8717bc 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java @@ -1,24 +1,64 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.title.Title; import net.minestom.server.chat.JsonMessage; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.utils.TickUtils; import net.minestom.server.utils.binary.BinaryWriter; +import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static net.minestom.server.network.packet.server.play.TitlePacket.Action.*; + public class TitlePacket implements ServerPacket { - public Action action; + private Action action; - public JsonMessage titleText; // Only text + private String payload; - public JsonMessage subtitleText; // Only text + private int fadeIn, stay, fadeOut; - public JsonMessage actionBarText; // Only text + /** + * Constructs a new title packet from an action that can take a string argument. + * @param action the action + * @param payload the payload + * @throws IllegalArgumentException if the action is not {@link Action#SET_TITLE}, + * {@link Action#SET_SUBTITLE} or {@link Action#SET_ACTION_BAR} + */ + public TitlePacket(@NotNull Action action, @NotNull String payload) { + Validate.isTrue(action == SET_TITLE || action == SET_SUBTITLE || action == SET_ACTION_BAR, "Invalid action type"); + this.action = action; + this.payload = payload; + } - public int fadeIn; - public int stay; - public int fadeOut; + /** + * Constructs a new title packet from a clear or reset action. + * @param action the action + * @throws IllegalArgumentException if the action is not {@link Action#RESET}, + * or {@link Action#HIDE} + */ + public TitlePacket(@NotNull Action action) { + this.action = action; + } + + /** + * Constructs a new title packet for {@link Action#SET_TIMES_AND_DISPLAY}. + * @param fadeIn the fade in time + * @param stay the stay time + * @param fadeOut the fade out time + */ + public TitlePacket(int fadeIn, int stay, int fadeOut) { + this.action = SET_TIMES_AND_DISPLAY; + this.fadeIn = fadeIn; + this.stay = stay; + this.fadeOut = fadeOut; + } @Override public void write(@NotNull BinaryWriter writer) { @@ -26,13 +66,9 @@ public class TitlePacket implements ServerPacket { switch (action) { case SET_TITLE: - writer.writeSizedString(titleText.toString()); - break; case SET_SUBTITLE: - writer.writeSizedString(subtitleText.toString()); - break; case SET_ACTION_BAR: - writer.writeSizedString(actionBarText.toString()); + writer.writeSizedString(payload); break; case SET_TIMES_AND_DISPLAY: writer.writeInt(fadeIn); @@ -59,4 +95,25 @@ public class TitlePacket implements ServerPacket { RESET } + /** + * Creates a collection of title packets from an Adventure title. + * @param title the title + * @return the packets + */ + public static Collection of(Title title) { + List packets = new ArrayList<>(4); + + // base packets + packets.add(new TitlePacket(SET_TITLE, GsonComponentSerializer.gson().serialize(title.title()))); + packets.add(new TitlePacket(SET_SUBTITLE, GsonComponentSerializer.gson().serialize(title.subtitle()))); + + // times packet + Title.Times times = title.times(); + if (times != null) { + packets.add(new TitlePacket(TickUtils.fromDuration(times.fadeIn()), TickUtils.fromDuration(times.stay()), + TickUtils.fromDuration(times.fadeOut()))); + } + + return packets; + } } diff --git a/src/main/java/net/minestom/server/utils/TickUtils.java b/src/main/java/net/minestom/server/utils/TickUtils.java new file mode 100644 index 000000000..6b6bfcc89 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/TickUtils.java @@ -0,0 +1,25 @@ +package net.minestom.server.utils; + +import net.minestom.server.MinecraftServer; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; + +/** + * Tick related utilities. + */ +public class TickUtils { + + /** + * Creates a number of ticks from a given duration, based on {@link MinecraftServer#TICK_MS}. + * @param duration the duration + * @return the number of ticks + * @throws IllegalArgumentException if duration is negative + */ + public static int fromDuration(@NotNull Duration duration) { + Validate.isTrue(!duration.isNegative(), "Duration cannot be negative"); + + return (int) (duration.toMillis() / MinecraftServer.TICK_MS); + } +}