Chat handling done

This commit is contained in:
Noel Németh 2022-06-11 00:20:30 +02:00
parent d0a7f4544b
commit 533bb49eff
6 changed files with 102 additions and 57 deletions

View File

@ -47,6 +47,7 @@ import net.minestom.server.item.metadata.WrittenBookMeta;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.message.ChatMessageType;
import net.minestom.server.message.ChatPosition;
import net.minestom.server.message.MessageSender;
import net.minestom.server.message.Messenger;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
@ -90,6 +91,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@ -107,6 +110,7 @@ import java.util.function.UnaryOperator;
* You can easily create your own implementation of this and use it with {@link ConnectionManager#setPlayerProvider(PlayerProvider)}.
*/
public class Player extends LivingEntity implements CommandSender, Localizable, HoverEventSource<ShowEntity>, Identified, NamedAndIdentified {
private static final Logger LOGGER = LoggerFactory.getLogger(Player.class);
private static final Component REMOVE_MESSAGE = Component.text("You have been removed from the server without reason.", NamedTextColor.RED);
private static final int PACKET_PER_TICK = Integer.getInteger("minestom.packet-per-tick", 20);
private static final int PACKET_QUEUE_SIZE = Integer.getInteger("minestom.packet-queue-size", 1000);
@ -204,6 +208,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Adventure
private Identity identity;
private final Pointers pointers;
private Component lastPreviewedMessage;
public Player(@NotNull UUID uuid, @NotNull String username, @NotNull PlayerConnection playerConnection) {
super(EntityType.PLAYER, uuid);
@ -664,7 +669,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
Messenger.sendMessage(this, message, ChatPosition.fromMessageType(type), source.uuid());
if (type == MessageType.SYSTEM)
Messenger.sendSystemMessage(this, message, ChatPosition.SYSTEM_MESSAGE);
else
Messenger.sendMessage(Collections.singleton(this), PlayerChatMessagePacket.unsigned(message,
ChatPosition.CHAT, MessageSender.forUnsigned(Component.text("SYSTEM"))));
}
/**
@ -2061,6 +2070,14 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
playerConnection.sendPacket(on ? ServerDataPacket.TOGGLE_PREVIEW_ON : ServerDataPacket.TOGGLE_PREVIEW_OFF);
}
public @Nullable Component getLastPreviewedMessage() {
return lastPreviewedMessage;
}
public void setLastPreviewedMessage(@Nullable Component lastPreviewedMessage) {
this.lastPreviewedMessage = lastPreviewedMessage;
}
/**
* Represents the main or off hand of the player.
*/

View File

@ -5,13 +5,13 @@ import net.minestom.server.crypto.MessageSignature;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.message.MessageSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Called every time a {@link Player} write and send something in the chat.
@ -21,21 +21,31 @@ public class PlayerChatEvent implements PlayerInstanceEvent, CancellableEvent {
private final Player player;
private final Collection<Player> recipients;
private final Supplier<Component> defaultChatFormat;
private String message;
private final String rawMessage;
private Function<PlayerChatEvent, Component> chatFormat;
private boolean cancelled;
private MessageSignature signature;
private MessageSender sender;
private final Component message;
public PlayerChatEvent(@NotNull Player player, @NotNull Collection<Player> recipients,
@NotNull Supplier<Component> defaultChatFormat,
@NotNull String message, @NotNull MessageSignature signature) {
@NotNull String rawMessage, @NotNull MessageSignature signature,
@NotNull MessageSender sender, @NotNull Component message) {
this.player = player;
this.recipients = new ArrayList<>(recipients);
this.defaultChatFormat = defaultChatFormat;
this.rawMessage = rawMessage;
this.message = message;
this.signature = signature;
this.sender = sender;
}
public MessageSender getSender() {
return sender;
}
public void setSender(MessageSender sender) {
this.sender = sender;
}
/**
@ -63,17 +73,12 @@ public class PlayerChatEvent implements PlayerInstanceEvent, CancellableEvent {
*
* @return the sender's message
*/
public @NotNull String getMessage() {
return message;
public @NotNull String getRawMessage() {
return rawMessage;
}
/**
* Used to change the message.
*
* @param message the new message
*/
public void setMessage(@NotNull String message) {
this.message = message;
public @NotNull Component getMessage() {
return message;
}
/**
@ -87,10 +92,6 @@ public class PlayerChatEvent implements PlayerInstanceEvent, CancellableEvent {
return chatFormat;
}
public @NotNull Supplier<@NotNull Component> getDefaultChatFormat() {
return defaultChatFormat;
}
@Override
public boolean isCancelled() {
return cancelled;

View File

@ -15,9 +15,10 @@ import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
import net.minestom.server.network.packet.client.play.ClientChatPreviewPacket;
import net.minestom.server.network.packet.client.play.ClientCommandChatPacket;
import net.minestom.server.network.packet.server.play.ChatPreviewPacket;
import org.jetbrains.annotations.NotNull;
import net.minestom.server.network.packet.server.play.PlayerChatMessagePacket;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;
public class ChatMessageListener {
@ -42,40 +43,47 @@ public class ChatMessageListener {
}
final Collection<Player> players = CONNECTION_MANAGER.getOnlinePlayers();
PlayerChatEvent playerChatEvent = new PlayerChatEvent(player, players, () -> buildDefaultChatMessage(player, message), message, packet.signature());
final Component expectedMessage = Objects.requireNonNullElse(player.getLastPreviewedMessage(), Component.text(message));
PlayerChatEvent event = new PlayerChatEvent(player, players, message, packet.signature(), MessageSender.forSigned(player), expectedMessage);
// Call the event
EventDispatcher.callCancellable(playerChatEvent, () -> {
final Function<PlayerChatEvent, Component> formatFunction = playerChatEvent.getChatFormatFunction();
EventDispatcher.callCancellable(event, () -> {
player.setLastPreviewedMessage(null);
final Collection<Player> recipients = event.getRecipients();
if (recipients.isEmpty()) return;
Component textObject;
// TODO Maybe change format to BiFunction<Player, String, Component>?
final Function<PlayerChatEvent, Component> formatFunction = event.getChatFormatFunction();
if (formatFunction != null) {
// Custom format
textObject = formatFunction.apply(playerChatEvent);
// Let the event modify the message
if (event.getSender().unsigned()) {
// Event handler set unsigned sender, send message as unsigned -> players with
// "Only Show Secure Chat" option enabled won't see this message
Messenger.sendMessage(event.getRecipients(), PlayerChatMessagePacket
.unsigned(formatFunction.apply(event), ChatPosition.CHAT, event.getSender()));
} else {
// Send both version of message -> players will see different versions based on
// their "Only Show Secure Chat" option
Messenger.sendMessage(event.getRecipients(), PlayerChatMessagePacket
.signedWithUnsignedContent(event.getMessage(), formatFunction.apply(event),
ChatPosition.CHAT, event.getSender(), event.getSignature()));
}
} else {
// Default format
textObject = playerChatEvent.getDefaultChatFormat().get();
}
final Collection<Player> recipients = playerChatEvent.getRecipients();
if (!recipients.isEmpty()) {
// delegate to the messenger to avoid sending messages we shouldn't be
Messenger.sendSignedPlayerMessage(recipients, textObject, ChatPosition.CHAT,
new MessageSender(player.getUuid(), player.getDisplayName() != null ? player.getDisplayName() :
Component.text(player.getUsername()), player.getTeam() == null ?
null : player.getTeam().getTeamDisplayName()), packet.signature());
// There is no way the message got modified, send it with the original signature
// TODO Should we handle poor design where the signature or sender uuid got altered?
Messenger.sendMessage(event.getRecipients(), PlayerChatMessagePacket.signed(event.getMessage(),
ChatPosition.CHAT, event.getSender(), event.getSignature()));
}
});
}
private static @NotNull Component buildDefaultChatMessage(@NotNull Player player, @NotNull String message) {
return Component.text(message);
}
public static void previewListener(ClientChatPreviewPacket packet, Player player) {
final PlayerChatPreviewEvent event = new PlayerChatPreviewEvent(player, packet.queryId(), packet.query());
MinecraftServer.getGlobalEventHandler().callCancellable(event,
() -> player.sendPacket(new ChatPreviewPacket(event.getId(), event.getResult())));
() -> {
player.sendPacket(new ChatPreviewPacket(event.getId(), event.getResult()));
player.setLastPreviewedMessage(event.getResult());
});
}
}

View File

@ -2,9 +2,11 @@ package net.minestom.server.message;
import net.kyori.adventure.text.Component;
import net.minestom.server.crypto.MessageSignature;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.UUID;
public record MessageSender(UUID uuid, @NotNull Component displayName, @Nullable Component teamName) {
@ -20,4 +22,22 @@ public record MessageSender(UUID uuid, @NotNull Component displayName, @Nullable
public static MessageSender forSigned(@NotNull UUID uuid, @NotNull Component displayName) {
return new MessageSender(uuid, displayName, null);
}
public static MessageSender forSigned(Player player) {
return player.getTeam() == null ? forSigned(player.getUuid(), Objects.requireNonNullElse(player.getDisplayName(),
Component.text(player.getUsername()))) : new MessageSender(player.getUuid(),
Objects.requireNonNullElse(player.getDisplayName(), Component.text(player.getUsername())),
player.getTeam().getTeamDisplayName());
}
public static MessageSender forUnsigned(Player player) {
return player.getTeam() == null ? forUnsigned(Objects.requireNonNullElse(player.getDisplayName(),
Component.text(player.getUsername()))) : forUnsigned(
Objects.requireNonNullElse(player.getDisplayName(), Component.text(player.getUsername())),
player.getTeam().getTeamDisplayName());
}
public boolean unsigned() {
return MessageSignature.UNSIGNED_SENDER == uuid;
}
}

View File

@ -2,13 +2,11 @@ package net.minestom.server.message;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.crypto.MessageSignature;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.PlayerChatMessagePacket;
import net.minestom.server.network.packet.server.play.SystemChatPacket;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTException;
import org.jglrxavpok.hephaistos.parser.SNBTParser;
@ -16,7 +14,6 @@ import org.jglrxavpok.hephaistos.parser.SNBTParser;
import java.io.StringReader;
import java.util.Collection;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@ -245,13 +242,11 @@ public final class Messenger {
* @param player the player
* @param message the message
* @param position the position
* @param uuid the UUID of the sender, if any
* @return if the message was sent
*/
//fixme
public static boolean sendMessage(@NotNull Player player, @NotNull Component message, @NotNull ChatPosition position, @Nullable UUID uuid) {
public static boolean sendSystemMessage(@NotNull Player player, @NotNull Component message, @NotNull ChatPosition position) {
if (getChatMessageType(player).accepts(position)) {
player.sendPacket(new SystemChatPacket(message, 1));
player.sendPacket(new SystemChatPacket(message, position.getID()));
return true;
}
return false;
@ -261,15 +256,11 @@ public final class Messenger {
* Sends a message to some players, respecting their chat settings.
*
* @param recipients the players
* @param message the message
* @param position the position
*/
public static void sendSignedPlayerMessage(@NotNull Collection<Player> recipients, @NotNull Component message,
@NotNull ChatPosition position, @NotNull MessageSender sender,
@NotNull MessageSignature signature) {
public static void sendMessage(@NotNull Collection<Player> recipients, @NotNull PlayerChatMessagePacket packet) {
PacketUtils.sendGroupedPacket(recipients.stream().filter(x -> x.getSettings().getChatMessageType() == null ||
x.getSettings().getChatMessageType().accepts(position)).collect(Collectors.toList()),
PlayerChatMessagePacket.signed(message, position, sender, signature));
x.getSettings().getChatMessageType().accepts(ChatPosition.fromPacketID(packet.type())))
.collect(Collectors.toList()), packet);
}
/**

View File

@ -40,6 +40,14 @@ public record PlayerChatMessagePacket(@NotNull Component signedContent, @Nullabl
sender.displayName(), sender.teamName(), signature);
}
public static PlayerChatMessagePacket signedWithUnsignedContent(@NotNull Component message,
@NotNull Component unsignedContent,
ChatPosition type, @NotNull MessageSender sender,
@NotNull MessageSignature signature) {
return new PlayerChatMessagePacket(message, unsignedContent, type.getID(), sender.uuid(),
sender.displayName(), sender.teamName(), signature);
}
@Override
public void write(@NotNull BinaryWriter writer) {
writer.writeComponent(signedContent);