Simplify component translation

This commit is contained in:
Kieran Wallbanks 2021-03-12 15:33:19 +00:00
parent c40139349b
commit 52ce8027f2
23 changed files with 559 additions and 225 deletions

View File

@ -47,10 +47,7 @@ public class BossBarManager implements BossBar.Listener {
Holder holder = this.getOrCreateHandler(bar);
if (holder.players.add(player.getUuid())) {
BossBarPacket packet = holder.createAddPacket();
packet.title = MinecraftServer.getSerializationManager().prepare(packet.title, player);
player.getPlayerConnection().sendPacket(packet);
player.getPlayerConnection().sendPacket(holder.createAddPacket());
}
}
/**
@ -108,27 +105,12 @@ public class BossBarManager implements BossBar.Listener {
private void updatePlayers(BossBarPacket packet, Set<UUID> players) {
Iterator<UUID> iterator = players.iterator();
// check if we need to translate the bossbar
Component rawTitle = packet.title;
boolean translate = false;
if (packet.action == UPDATE_TITLE || packet.action == ADD) {
Component rootTitle = MinecraftServer.getSerializationManager().prepare(rawTitle, MinecraftServer.getSerializationManager().getDefaultLocale());
if (!rawTitle.equals(rootTitle)) {
translate = true;
}
}
while (iterator.hasNext()) {
Player player = MinecraftServer.getConnectionManager().getPlayer(iterator.next());
if (player == null) {
iterator.remove();
} else {
if (translate) {
packet.title = MinecraftServer.getSerializationManager().prepare(rawTitle, player);
}
player.getPlayerConnection().sendPacket(packet);
}
}

View File

@ -0,0 +1,39 @@
package net.minestom.server.adventure;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
/**
* Represents an object that holds some amount of components.
* @param <T> the holding class
*/
public interface ComponentHolder<T> {
/**
* Gets the components held by this object.
* @return the components
*/
@NotNull Collection<Component> components();
/**
* Returns a copy of this object. For each component this object holds, the operator
* is applied to the copy before returning.
* @param operator the operator
* @return the copy
*/
@NotNull T copyWithOperator(@NotNull UnaryOperator<Component> operator);
/**
* Visits each component held by this object.
* @param visitor the visitor
*/
default void visitComponents(@NotNull Consumer<Component> visitor) {
for (Component component : this.components()) {
visitor.accept(component);
}
}
}

View File

@ -1,158 +0,0 @@
package net.minestom.server.adventure;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket;
import net.minestom.server.network.packet.server.play.TitlePacket;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Utility class for sending packets with translatable components. All functions in this
* class will send grouped packets if the components do not contain any translatable
* components. In the case that they do, the components are translated and send individually.
*/
public class LocalizablePacketSender {
/**
* Sends a title to many players, sending it as a grouped packet if it does not
* contain translatable elements.
*
* @param players the players
* @param title the title
*/
public static void sendGroupedTitle(@NotNull Collection<Player> players, @NotNull Title title) {
Component preparedTitle = MinecraftServer.getSerializationManager().prepare(title.title(), MinecraftServer.getSerializationManager().getDefaultLocale()),
preparedSubtitle = MinecraftServer.getSerializationManager().prepare(title.subtitle(), MinecraftServer.getSerializationManager().getDefaultLocale());
Collection<TitlePacket> rootPacket = TitlePacket.of(Title.title(preparedTitle, preparedSubtitle, title.times()));
if (title.title().equals(preparedTitle) && title.subtitle().equals(preparedSubtitle)) {
for (TitlePacket packet : rootPacket) {
PacketUtils.sendGroupedPacket(players, packet);
}
} else {
for (Player player : players) {
Collection<TitlePacket> packets;
if (player.getLocale() == null) {
packets = rootPacket;
} else {
packets = TitlePacket.of(Title.title(MinecraftServer.getSerializationManager().prepare(title.title(), player),
MinecraftServer.getSerializationManager().prepare(title.subtitle(), player), title.times()));
}
for (TitlePacket packet : packets) {
player.getPlayerConnection().sendPacket(packet);
}
}
}
}
/**
* Sends an action bar to many players, sending it as a grouped packet if it does not
* contain translatable elements.
*
* @param players the players
* @param component the component
*/
public static void sendGroupedActionBar(@NotNull Collection<Player> players, @NotNull Component component) {
Component preparedComponent = MinecraftServer.getSerializationManager().prepare(component, MinecraftServer.getSerializationManager().getDefaultLocale());
TitlePacket rootPacket = new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, preparedComponent);
if (component.equals(preparedComponent)) {
PacketUtils.sendGroupedPacket(players, rootPacket);
} else {
for (Player player : players) {
TitlePacket packet;
if (player.getLocale() == null) {
packet = rootPacket;
} else {
packet = new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, MinecraftServer.getSerializationManager().prepare(component, player));
}
player.getPlayerConnection().sendPacket(packet);
}
}
}
/**
* Sends a player list to many players, sending it as a grouped packet if it does not
* contain translatable elements.
*
* @param players the players
* @param header the header
* @param footer the footer
*/
public static void sendGroupedPlayerList(@NotNull Collection<Player> players, @Nullable Component header, @Nullable Component footer) {
// empty check first
if (header == null) {
header = Component.empty();
}
if (footer == null) {
footer = Component.empty();
}
// now back to the packets
Component preparedHeader = MinecraftServer.getSerializationManager().prepare(header, MinecraftServer.getSerializationManager().getDefaultLocale()),
preparedFooter = MinecraftServer.getSerializationManager().prepare(footer, MinecraftServer.getSerializationManager().getDefaultLocale());
PlayerListHeaderAndFooterPacket rootPacket = new PlayerListHeaderAndFooterPacket(preparedHeader, preparedFooter);
if (header.equals(preparedHeader) && footer.equals(preparedFooter)) {
PacketUtils.sendGroupedPacket(players, rootPacket);
} else {
for (Player player : players) {
PlayerListHeaderAndFooterPacket packet;
if (player.getLocale() == null) {
packet = rootPacket;
} else {
packet = new PlayerListHeaderAndFooterPacket(MinecraftServer.getSerializationManager().prepare(header, player),
MinecraftServer.getSerializationManager().prepare(footer, player));
}
player.getPlayerConnection().sendPacket(packet);
}
}
}
/**
* Sends a message to many players, sending it as a grouped packet if it does not
* contain translatable elements.
*
* @param players the players
* @param source the source of the message
* @param message the message
* @param messageType the type of the message
*/
public static void sendGroupedMessage(@NotNull Collection<Player> players, @NotNull Identity source, @NotNull Component message, @NotNull MessageType messageType) {
ChatMessagePacket.Position position = ChatMessagePacket.Position.fromMessageType(messageType);
Component preparedMessage = MinecraftServer.getSerializationManager().prepare(message, MinecraftServer.getSerializationManager().getDefaultLocale());
ChatMessagePacket rootPacket = new ChatMessagePacket(preparedMessage, position, source.uuid());
if (message.equals(preparedMessage)) {
PacketUtils.sendGroupedPacket(players, rootPacket);
} else {
for (Player player : players) {
ChatMessagePacket packet;
if (player.getLocale() == null) {
packet = rootPacket;
} else {
packet = new ChatMessagePacket(MinecraftServer.getSerializationManager().prepare(message, player), position, source.uuid());
}
player.getPlayerConnection().sendPacket(packet);
}
}
}
}

View File

@ -8,6 +8,7 @@ import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.translation.Translator;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
@ -51,7 +52,7 @@ public class SerializationManager {
/**
* Gets the default locale used to translate {@link TranslatableComponent} if, when
* {@link #prepare(Component, Localizable)} is called with a localizable that
* {@link #translate(Component, Localizable)} is called with a localizable that
* does not have a locale.
*
* @return the default locale
@ -62,7 +63,7 @@ public class SerializationManager {
/**
* Sets the default locale used to translate {@link TranslatableComponent} if, when
* {@link #prepare(Component, Localizable)} is called with a localizable that
* {@link #translate(Component, Localizable)} is called with a localizable that
* does not have a locale.
*
* @param defaultLocale the new default locale
@ -90,7 +91,7 @@ public class SerializationManager {
*
* @return the prepared component
*/
public @NotNull Component prepare(@NotNull Component component, @NotNull Localizable localizable) {
public @NotNull Component translate(@NotNull Component component, @NotNull Localizable localizable) {
return GlobalTranslator.renderer().render(component, Objects.requireNonNullElse(localizable.getLocale(), this.getDefaultLocale()));
}
@ -103,7 +104,7 @@ public class SerializationManager {
*
* @return the prepared component
*/
public @NotNull Component prepare(@NotNull Component component, @NotNull Locale locale) {
public @NotNull Component translate(@NotNull Component component, @NotNull Locale locale) {
return GlobalTranslator.renderer().render(component, locale);
}
@ -126,8 +127,8 @@ public class SerializationManager {
*
* @return the string
*/
public String prepareAndSerialize(@NotNull Component component, @NotNull Localizable localizable) {
return this.prepareAndSerialize(component, Objects.requireNonNullElse(localizable.getLocale(), this.getDefaultLocale()));
public String translateAndSerialize(@NotNull Component component, @NotNull Localizable localizable) {
return this.translateAndSerialize(component, Objects.requireNonNullElse(localizable.getLocale(), this.getDefaultLocale()));
}
/**
@ -138,7 +139,35 @@ public class SerializationManager {
*
* @return the string
*/
public String prepareAndSerialize(@NotNull Component component, @NotNull Locale locale) {
return this.serialize(this.prepare(component, locale));
public String translateAndSerialize(@NotNull Component component, @NotNull Locale locale) {
return this.serialize(this.translate(component, locale));
}
/**
* Checks if a component can be translated server-side. This is done by running the
* component through the translator and seeing if the translated component is equal
* to the non translated component.
* @param component the component
* @return {@code true} if the component can be translated server-side,
* {@code false} otherwise
*/
public boolean isTranslatable(@NotNull Component component) {
return !component.equals(this.translate(component, this.getDefaultLocale()));
}
/**
* Checks if any of a series of components are translatable server-side.
* @param components the components
* @return {@code true} if any of the components can be translated server-side,
* {@code false} otherwise
*/
public boolean areAnyTranslatable(@NotNull Collection<Component> components) {
for (Component component : components) {
if (this.isTranslatable(component)) {
return true;
}
}
return false;
}
}

View File

@ -508,7 +508,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// #buildDeathScreenText can return null, check here
if (deathText != null) {
CombatEventPacket deathPacket = CombatEventPacket.death(this, null, MinecraftServer.getSerializationManager().prepare(deathText, this));
CombatEventPacket deathPacket = CombatEventPacket.death(this, null, deathText);
playerConnection.sendPacket(deathPacket);
}
@ -806,7 +806,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
ChatMessagePacket chatMessagePacket = new ChatMessagePacket(MinecraftServer.getSerializationManager().prepare(message, this), ChatMessagePacket.Position.fromMessageType(type), source.uuid());
ChatMessagePacket chatMessagePacket = new ChatMessagePacket(message, ChatMessagePacket.Position.fromMessageType(type), source.uuid());
playerConnection.sendPacket(chatMessagePacket);
}
@ -1009,8 +1009,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public void sendPlayerListHeaderAndFooter(@NotNull Component header, @NotNull Component footer) {
PlayerListHeaderAndFooterPacket packet
= new PlayerListHeaderAndFooterPacket(MinecraftServer.getSerializationManager().prepare(header, this), MinecraftServer.getSerializationManager().prepare(footer, this));
PlayerListHeaderAndFooterPacket packet = new PlayerListHeaderAndFooterPacket(header, footer);
playerConnection.sendPacket(packet);
}
@ -1084,9 +1083,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public void showTitle(@NotNull Title title) {
Component preparedTitle = MinecraftServer.getSerializationManager().prepare(title.title(), this),
preparedSubtitle = MinecraftServer.getSerializationManager().prepare(title.subtitle(), this);
Collection<TitlePacket> packet = TitlePacket.of(Title.title(preparedTitle, preparedSubtitle, title.times()));
Collection<TitlePacket> packet = TitlePacket.of(Title.title(title.title(), title.subtitle(), title.times()));
for (TitlePacket titlePacket : packet) {
playerConnection.sendPacket(titlePacket);
@ -1095,7 +1092,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public void sendActionBar(@NotNull Component message) {
TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, MinecraftServer.getSerializationManager().prepare(message, this));
TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, message);
playerConnection.sendPacket(titlePacket);
}
@ -1924,9 +1921,9 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Packet type depends on the current player connection state
final ServerPacket disconnectPacket;
if (connectionState == ConnectionState.LOGIN) {
disconnectPacket = new LoginDisconnectPacket(MinecraftServer.getSerializationManager().prepare(component, this));
disconnectPacket = new LoginDisconnectPacket(component);
} else {
disconnectPacket = new DisconnectPacket(MinecraftServer.getSerializationManager().prepare(component, this));
disconnectPacket = new DisconnectPacket(component);
}
if (playerConnection instanceof NettyPlayerConnection) {

View File

@ -209,8 +209,8 @@ public class WrittenBookMeta extends ItemMeta {
WrittenBookMeta meta = new WrittenBookMeta();
meta.resolved = false;
meta.generation = WrittenBookGeneration.ORIGINAL;
meta.author = MinecraftServer.getSerializationManager().prepareAndSerialize(book.author(), localizable);
meta.title = MinecraftServer.getSerializationManager().prepareAndSerialize(book.title(), localizable);
meta.author = MinecraftServer.getSerializationManager().translateAndSerialize(book.author(), localizable);
meta.title = MinecraftServer.getSerializationManager().translateAndSerialize(book.title(), localizable);
meta.pages = new ArrayList<>();
for (Component page : book.pages()) {

View File

@ -3,12 +3,9 @@ package net.minestom.server.network;
import io.netty.channel.Channel;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.adventure.LocalizablePacketSender;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.fakeplayer.FakePlayer;
@ -155,8 +152,9 @@ public final class ConnectionManager implements ForwardingAudience {
final Collection<Player> recipients = getRecipients(condition);
if (!recipients.isEmpty()) {
final String jsonText = jsonMessage.toString();
LocalizablePacketSender.sendGroupedMessage(recipients, Identity.nil(), jsonMessage.asComponent(), MessageType.CHAT);
for (Player recipient : recipients) {
recipient.sendMessage(jsonMessage);
}
}
}
@ -489,7 +487,6 @@ public final class ConnectionManager implements ForwardingAudience {
for (Player player : getOnlinePlayers()) {
final PlayerConnection playerConnection = player.getPlayerConnection();
if (playerConnection instanceof NettyPlayerConnection) {
disconnectPacket.message = MinecraftServer.getSerializationManager().prepare(disconnectPacket.message, player);
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) playerConnection;
final Channel channel = nettyPlayerConnection.getChannel();
channel.writeAndFlush(disconnectPacket);

View File

@ -0,0 +1,8 @@
package net.minestom.server.network.packet.server;
import net.minestom.server.adventure.ComponentHolder;
/**
* A server packet that can hold components.
*/
public interface ComponentHoldingServerPacket extends ServerPacket, ComponentHolder<ServerPacket> { }

View File

@ -2,14 +2,17 @@ package net.minestom.server.network.packet.server.login;
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.ComponentHoldingServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
public class LoginDisconnectPacket implements ServerPacket {
import java.util.Collection;
import java.util.List;
import java.util.function.UnaryOperator;
private final Component kickMessage; // JSON text
public class LoginDisconnectPacket implements ComponentHoldingServerPacket {
public Component kickMessage;
public LoginDisconnectPacket(@NotNull Component kickMessage) {
this.kickMessage = kickMessage;
@ -33,4 +36,13 @@ public class LoginDisconnectPacket implements ServerPacket {
return ServerPacketIdentifier.LOGIN_DISCONNECT;
}
@Override
public @NotNull Collection<Component> components() {
return List.of(this.kickMessage);
}
@Override
public @NotNull LoginDisconnectPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
return new LoginDisconnectPacket(operator.apply(this.kickMessage));
}
}

View File

@ -3,13 +3,20 @@ package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.minestom.server.advancements.FrameType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.binary.Writeable;
import org.jetbrains.annotations.NotNull;
public class AdvancementsPacket implements ServerPacket {
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.UnaryOperator;
public class AdvancementsPacket implements ComponentHoldingServerPacket {
public boolean resetAdvancements;
public AdvancementMapping[] advancementMappings;
@ -37,6 +44,32 @@ public class AdvancementsPacket implements ServerPacket {
return ServerPacketIdentifier.ADVANCEMENTS;
}
@Override
public @NotNull Collection<Component> components() {
List<Component> components = new ArrayList<>();
for (AdvancementMapping advancementMapping : advancementMappings) {
components.add(advancementMapping.value.displayData.title);
components.add(advancementMapping.value.displayData.description);
}
return components;
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
AdvancementsPacket packet = new AdvancementsPacket();
packet.resetAdvancements = this.resetAdvancements;
packet.advancementMappings = Arrays.copyOf(this.advancementMappings, this.advancementMappings.length);
packet.identifiersToRemove = Arrays.copyOf(this.identifiersToRemove, this.identifiersToRemove.length);
packet.progressMappings = Arrays.copyOf(this.progressMappings, this.progressMappings.length);
for (AdvancementMapping advancementMapping : packet.advancementMappings) {
advancementMapping.value.displayData.title = operator.apply(advancementMapping.value.displayData.title);
advancementMapping.value.displayData.description = operator.apply(advancementMapping.value.displayData.title);
}
return packet;
}
/**
* AdvancementMapping maps the namespaced ID to the Advancement.
*/

View File

@ -3,14 +3,18 @@ package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.minestom.server.adventure.AdventurePacketConvertor;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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 java.util.Collection;
import java.util.Collections;
import java.util.UUID;
import java.util.function.UnaryOperator;
public class BossBarPacket implements ServerPacket {
public class BossBarPacket implements ComponentHoldingServerPacket {
public UUID uuid;
public Action action;
@ -58,6 +62,40 @@ public class BossBarPacket implements ServerPacket {
return ServerPacketIdentifier.BOSS_BAR;
}
@Override
public @NotNull Collection<Component> components() {
if (title != null) {
return Collections.singleton(title);
} else {
return Collections.emptyList();
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
switch (action) {
case UPDATE_TITLE: {
BossBarPacket packet = new BossBarPacket();
packet.action = action;
packet.uuid = uuid;
packet.title = operator.apply(title);
return packet;
}
case ADD: {
BossBarPacket packet = new BossBarPacket();
packet.action = action;
packet.uuid = uuid;
packet.title = operator.apply(title);
packet.health = health;
packet.overlay = overlay;
packet.color = color;
packet.flags = flags;
return packet;
}
default: return this;
}
}
public enum Action {
ADD,
REMOVE,

View File

@ -2,18 +2,22 @@ package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.text.Component;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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.Collection;
import java.util.Collections;
import java.util.UUID;
import java.util.function.UnaryOperator;
/**
* Represents an outgoing chat message packet.
*/
public class ChatMessagePacket implements ServerPacket {
public class ChatMessagePacket implements ComponentHoldingServerPacket {
private static final UUID NULL_UUID = new UUID(0, 0);
public Component message;
@ -42,6 +46,16 @@ public class ChatMessagePacket implements ServerPacket {
return ServerPacketIdentifier.CHAT_MESSAGE;
}
@Override
public @NotNull Collection<Component> components() {
return Collections.singleton(message);
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
return new ChatMessagePacket(operator.apply(message), position, uuid);
}
public enum Position {
CHAT(MessageType.CHAT),
SYSTEM_MESSAGE(MessageType.SYSTEM),

View File

@ -4,16 +4,21 @@ import net.kyori.adventure.text.Component;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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 java.util.Collection;
import java.util.Collections;
import java.util.function.UnaryOperator;
/**
* Packet sent during combat to a {@link Player}.
* Only death is supported for the moment (other events are ignored anyway as of 1.15.2)
*/
public class CombatEventPacket implements ServerPacket {
public class CombatEventPacket implements ComponentHoldingServerPacket {
private EventType type;
private int duration;
@ -81,6 +86,29 @@ public class CombatEventPacket implements ServerPacket {
return ServerPacketIdentifier.COMBAT_EVENT;
}
@Override
public @NotNull Collection<Component> components() {
if (this.type == EventType.DEATH) {
return Collections.singleton(deathMessage);
} else {
return Collections.emptyList();
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (this.type == EventType.DEATH) {
CombatEventPacket packet = new CombatEventPacket();
packet.type = type;
packet.playerID = playerID;
packet.opponent = opponent;
packet.deathMessage = deathMessage;
return packet;
} else {
return this;
}
}
public enum EventType {
ENTER_COMBAT, END_COMBAT, // both ignored by Notchian client
DEATH,

View File

@ -2,12 +2,17 @@ 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.ComponentHoldingServerPacket;
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;
public class DisconnectPacket implements ServerPacket {
import java.util.Collection;
import java.util.Collections;
import java.util.function.UnaryOperator;
public class DisconnectPacket implements ComponentHoldingServerPacket {
public Component message;
/**
@ -35,4 +40,14 @@ public class DisconnectPacket implements ServerPacket {
public int getId() {
return ServerPacketIdentifier.DISCONNECT;
}
@Override
public @NotNull Collection<Component> components() {
return Collections.singleton(message);
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
return new DisconnectPacket(operator.apply(message));
}
}

View File

@ -1,12 +1,16 @@
package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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;
public class MapDataPacket implements ServerPacket {
import java.util.*;
import java.util.function.UnaryOperator;
public class MapDataPacket implements ComponentHoldingServerPacket {
public int mapId;
public byte scale;
@ -59,6 +63,44 @@ public class MapDataPacket implements ServerPacket {
return ServerPacketIdentifier.MAP_DATA;
}
@Override
public @NotNull Collection<Component> components() {
if (icons == null || icons.length == 0) {
return Collections.emptyList();
} else {
List<Component> components = new ArrayList<>();
for (Icon icon : icons) {
components.add(icon.displayName);
}
return components;
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (this.icons == null || this.icons.length == 0) {
return this;
} else {
MapDataPacket packet = new MapDataPacket();
packet.mapId = this.mapId;
packet.scale = this.scale;
packet.trackingPosition = this.trackingPosition;
packet.locked = this.locked;
packet.columns = this.columns;
packet.rows = this.rows;
packet.x = this.x;
packet.z = this.z;
packet.data = this.data;
packet.icons = Arrays.copyOf(this.icons, this.icons.length);
for (Icon icon : packet.icons) {
icon.displayName = operator.apply(icon.displayName);
}
return packet;
}
}
public static class Icon {
public int type;
public byte x, z;

View File

@ -1,18 +1,19 @@
package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.minestom.server.adventure.ComponentHolder;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.entity.GameMode;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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 java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.function.UnaryOperator;
public class PlayerInfoPacket implements ServerPacket {
public class PlayerInfoPacket implements ComponentHoldingServerPacket {
public Action action;
public List<PlayerInfo> playerInfos;
@ -39,6 +40,40 @@ public class PlayerInfoPacket implements ServerPacket {
return ServerPacketIdentifier.PLAYER_INFO;
}
@Override
public @NotNull Collection<Component> components() {
switch (this.action) {
case ADD_PLAYER:
case UPDATE_DISPLAY_NAME:
List<Component> components = new ArrayList<>();
for (PlayerInfo playerInfo : playerInfos) {
if (playerInfo instanceof ComponentHolder) {
components.addAll(((ComponentHolder<? extends PlayerInfo>) playerInfo).components());
}
}
return components;
default: return Collections.emptyList();
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
switch (this.action) {
case ADD_PLAYER:
case UPDATE_DISPLAY_NAME:
PlayerInfoPacket packet = new PlayerInfoPacket(action);
packet.playerInfos = new ArrayList<>(playerInfos.size());
for (PlayerInfo playerInfo : playerInfos) {
if (playerInfo instanceof ComponentHolder) {
playerInfos.add(((ComponentHolder<? extends PlayerInfo>) playerInfo).copyWithOperator(operator));
} else {
playerInfos.add(playerInfo);
}
}
default: return this;
}
}
public enum Action {
ADD_PLAYER(AddPlayer.class),
@ -70,7 +105,7 @@ public class PlayerInfoPacket implements ServerPacket {
public abstract void write(BinaryWriter writer);
}
public static class AddPlayer extends PlayerInfo {
public static class AddPlayer extends PlayerInfo implements ComponentHolder<AddPlayer> {
public String name;
public List<Property> properties;
@ -102,6 +137,26 @@ public class PlayerInfoPacket implements ServerPacket {
writer.writeComponent(displayName);
}
@Override
public @NotNull Collection<Component> components() {
if (displayName == null) {
return Collections.emptyList();
} else {
return Collections.singleton(displayName);
}
}
@Override
public @NotNull AddPlayer copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (displayName == null) {
return this;
} else {
AddPlayer addPlayer = new AddPlayer(uuid, name, gameMode, ping);
addPlayer.displayName = operator.apply(displayName);
return addPlayer;
}
}
public static class Property {
public String name;
@ -160,7 +215,7 @@ public class PlayerInfoPacket implements ServerPacket {
}
}
public static class UpdateDisplayName extends PlayerInfo {
public static class UpdateDisplayName extends PlayerInfo implements ComponentHolder<UpdateDisplayName> {
public Component displayName;
@ -184,6 +239,24 @@ public class PlayerInfoPacket implements ServerPacket {
if (hasDisplayName)
writer.writeComponent(displayName);
}
@Override
public @NotNull Collection<Component> components() {
if (displayName == null) {
return Collections.emptyList();
} else {
return Collections.singleton(displayName);
}
}
@Override
public @NotNull UpdateDisplayName copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (displayName == null) {
return this;
} else {
return new UpdateDisplayName(uuid, operator.apply(displayName));
}
}
}
public static class RemovePlayer extends PlayerInfo {

View File

@ -1,15 +1,20 @@
package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.UnaryOperator;
public class PlayerListHeaderAndFooterPacket implements ServerPacket {
public class PlayerListHeaderAndFooterPacket implements ComponentHoldingServerPacket {
public Component header;
public Component footer;
@ -28,4 +33,21 @@ public class PlayerListHeaderAndFooterPacket implements ServerPacket {
public int getId() {
return ServerPacketIdentifier.PLAYER_LIST_HEADER_AND_FOOTER;
}
@Override
public @NotNull Collection<Component> components() {
List<Component> components = new ArrayList<>();
if (header != null) {
components.add(header);
}
if (footer != null) {
components.add(footer);
}
return components;
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
return new PlayerListHeaderAndFooterPacket(header == null ? null : operator.apply(header), footer == null ? null : operator.apply(footer));
}
}

View File

@ -1,12 +1,17 @@
package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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;
public class ScoreboardObjectivePacket implements ServerPacket {
import java.util.Collection;
import java.util.Collections;
import java.util.function.UnaryOperator;
public class ScoreboardObjectivePacket implements ComponentHoldingServerPacket {
/**
* An unique name for the objective
@ -43,6 +48,29 @@ public class ScoreboardObjectivePacket implements ServerPacket {
return ServerPacketIdentifier.SCOREBOARD_OBJECTIVE;
}
@Override
public @NotNull Collection<Component> components() {
if (mode == 0 || mode == 2) {
return Collections.singleton(objectiveValue);
} else {
return Collections.emptyList();
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (mode == 0 || mode == 2) {
ScoreboardObjectivePacket packet = new ScoreboardObjectivePacket();
packet.objectiveName = objectiveName;
packet.mode = mode;
packet.objectiveValue = operator.apply(objectiveValue);
packet.type = type;
return packet;
} else {
return this;
}
}
/**
* This enumeration represents all available types for the scoreboard objective
*/

View File

@ -1,12 +1,20 @@
package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.minestom.server.adventure.ComponentHolder;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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;
public class TabCompletePacket implements ServerPacket {
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.UnaryOperator;
public class TabCompletePacket implements ComponentHoldingServerPacket {
public int transactionId;
public int start;
@ -33,10 +41,66 @@ public class TabCompletePacket implements ServerPacket {
return ServerPacketIdentifier.TAB_COMPLETE;
}
public static class Match {
@Override
public @NotNull Collection<Component> components() {
if (matches == null || matches.length == 0) {
return Collections.emptyList();
} else {
List<Component> components = new ArrayList<>(matches.length);
for (Match match : matches) {
if (match.hasTooltip) {
components.add(match.tooltip);
}
}
return components;
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (matches == null || matches.length == 0) {
return this;
} else {
TabCompletePacket packet = new TabCompletePacket();
packet.transactionId = transactionId;
packet.start = start;
packet.length = length;
packet.matches = new Match[matches.length];
for (int i = 0; i < matches.length; i++) {
packet.matches[i] = matches[i].copyWithOperator(operator);
}
return packet;
}
}
public static class Match implements ComponentHolder<Match> {
public String match;
public boolean hasTooltip;
public Component tooltip;
@Override
public @NotNull Collection<Component> components() {
if (hasTooltip) {
return Collections.singleton(tooltip);
} else {
return Collections.emptyList();
}
}
@Override
public @NotNull Match copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (hasTooltip) {
Match newMatch = new Match();
newMatch.match = match;
newMatch.hasTooltip = hasTooltip;
newMatch.tooltip = tooltip;
return newMatch;
} else {
return this;
}
}
}
}

View File

@ -2,15 +2,21 @@ package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.minestom.server.color.TeamColor;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
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 java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.UnaryOperator;
/**
* The packet creates or updates teams
*/
public class TeamsPacket implements ServerPacket {
public class TeamsPacket implements ComponentHoldingServerPacket {
/**
* The registry name of the team
@ -100,6 +106,35 @@ public class TeamsPacket implements ServerPacket {
return ServerPacketIdentifier.TEAMS;
}
@Override
public @NotNull Collection<Component> components() {
if (this.action == Action.UPDATE_TEAM_INFO || this.action == Action.CREATE_TEAM) {
return List.of(teamDisplayName, teamPrefix, teamSuffix);
} else {
return Collections.emptyList();
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (this.action == Action.UPDATE_TEAM_INFO || this.action == Action.CREATE_TEAM) {
TeamsPacket packet = new TeamsPacket();
packet.teamName = teamName;
packet.action = action;
packet.teamDisplayName = teamDisplayName == null ? null : operator.apply(teamDisplayName);
packet.friendlyFlags = friendlyFlags;
packet.nameTagVisibility = nameTagVisibility;
packet.collisionRule = collisionRule;
packet.teamColor = teamColor;
packet.teamPrefix = teamPrefix == null ? null : operator.apply(teamPrefix);
packet.teamSuffix = teamSuffix == null ? null : operator.apply(teamSuffix);
packet.entities = entities;
return packet;
} else {
return this;
}
}
/**
* An enumeration which representing all actions for the packet
*/

View File

@ -2,6 +2,7 @@ package net.minestom.server.network.packet.server.play;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.utils.TickUtils;
@ -11,11 +12,13 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.UnaryOperator;
import static net.minestom.server.network.packet.server.play.TitlePacket.Action.*;
public class TitlePacket implements ServerPacket {
public class TitlePacket implements ComponentHoldingServerPacket {
public Action action;
@ -90,6 +93,24 @@ public class TitlePacket implements ServerPacket {
return ServerPacketIdentifier.TITLE;
}
@Override
public @NotNull Collection<Component> components() {
if (action == SET_TITLE || action == SET_SUBTITLE || action == SET_ACTION_BAR) {
return Collections.singleton(payload);
} else {
return Collections.emptyList();
}
}
@Override
public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator<Component> operator) {
if (action == SET_TITLE || action == SET_SUBTITLE || action == SET_ACTION_BAR) {
return new TitlePacket(action, operator.apply(payload));
} else {
return this;
}
}
public enum Action {
SET_TITLE,
SET_SUBTITLE,

View File

@ -13,6 +13,7 @@ import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.netty.NettyServer;
import net.minestom.server.network.netty.codec.PacketCompressor;
import net.minestom.server.network.netty.packet.FramedPacket;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
import net.minestom.server.utils.BufUtils;
@ -25,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.crypto.SecretKey;
import java.awt.Component;
import java.net.SocketAddress;
import java.util.Map;
import java.util.UUID;
@ -180,6 +182,11 @@ public class NettyPlayerConnection extends PlayerConnection {
return;
} else if (message instanceof ServerPacket) {
final ServerPacket serverPacket = (ServerPacket) message;
if (getPlayer() != null && serverPacket instanceof ComponentHoldingServerPacket) {
serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component -> MinecraftServer.getSerializationManager().translate(component, getPlayer()));
}
synchronized (tickBuffer) {
PacketUtils.writeFramedPacket(tickBuffer, serverPacket, false);
}

View File

@ -6,6 +6,7 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.network.netty.packet.FramedPacket;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
@ -45,7 +46,14 @@ public final class PacketUtils {
if (players.isEmpty())
return;
if (MinecraftServer.hasGroupedPacket()) {
// work out if the packet needs to be sent individually due to server-side translating
boolean needsTranslating = false;
if (packet instanceof ComponentHoldingServerPacket) {
needsTranslating = MinecraftServer.getSerializationManager().areAnyTranslatable(((ComponentHoldingServerPacket) packet).components());
}
if (MinecraftServer.hasGroupedPacket() && !needsTranslating) {
// Send grouped packet...
final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players);
if (success) {