Adventure

Co-authored-by: zml <zml@stellardrift.ca>
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
Co-authored-by: Yannick Lamprecht <yannicklamprecht@live.de>
This commit is contained in:
Riley Park
2021-01-29 17:21:55 +01:00
parent 8888031206
commit 15081a5912
70 changed files with 3298 additions and 160 deletions

View File

@@ -0,0 +1,70 @@
package io.papermc.paper.chat;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* A chat renderer is responsible for rendering chat messages sent by {@link Player}s to the server.
*/
@NullMarked
@FunctionalInterface
public interface ChatRenderer {
/**
* Renders a chat message. This will be called once for each receiving {@link Audience}.
*
* @param source the message source
* @param sourceDisplayName the display name of the source player
* @param message the chat message
* @param viewer the receiving {@link Audience}
* @return a rendered chat message
*/
@ApiStatus.OverrideOnly
Component render(Player source, Component sourceDisplayName, Component message, Audience viewer);
/**
* Create a new instance of the default {@link ChatRenderer}.
*
* @return a new {@link ChatRenderer}
*/
static ChatRenderer defaultRenderer() {
return new ViewerUnawareImpl.Default((source, sourceDisplayName, message) -> Component.translatable("chat.type.text", sourceDisplayName, message));
}
@ApiStatus.Internal
sealed interface Default extends ChatRenderer, ViewerUnaware permits ViewerUnawareImpl.Default {
}
/**
* Creates a new viewer-unaware {@link ChatRenderer}, which will render the chat message a single time,
* displaying the same rendered message to every viewing {@link Audience}.
*
* @param renderer the viewer unaware renderer
* @return a new {@link ChatRenderer}
*/
static ChatRenderer viewerUnaware(final ViewerUnaware renderer) {
return new ViewerUnawareImpl(renderer);
}
/**
* Similar to {@link ChatRenderer}, but without knowledge of the message viewer.
*
* @see ChatRenderer#viewerUnaware(ViewerUnaware)
*/
interface ViewerUnaware {
/**
* Renders a chat message.
*
* @param source the message source
* @param sourceDisplayName the display name of the source player
* @param message the chat message
* @return a rendered chat message
*/
@ApiStatus.OverrideOnly
Component render(Player source, Component sourceDisplayName, Component message);
}
}

View File

@@ -0,0 +1,38 @@
package io.papermc.paper.chat;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@ApiStatus.Internal
@NullMarked
sealed class ViewerUnawareImpl implements ChatRenderer, ChatRenderer.ViewerUnaware permits ViewerUnawareImpl.Default {
private final ViewerUnaware unaware;
private @Nullable Component message;
ViewerUnawareImpl(final ViewerUnaware unaware) {
this.unaware = unaware;
}
@Override
public Component render(final Player source, final Component sourceDisplayName, final Component message, final Audience viewer) {
return this.render(source, sourceDisplayName, message);
}
@Override
public Component render(final Player source, final Component sourceDisplayName, final Component message) {
if (this.message == null) {
this.message = this.unaware.render(source, sourceDisplayName, message);
}
return this.message;
}
static final class Default extends ViewerUnawareImpl implements ChatRenderer.Default {
Default(final ViewerUnaware unaware) {
super(unaware);
}
}
}

View File

@@ -0,0 +1,122 @@
package io.papermc.paper.event.player;
import io.papermc.paper.chat.ChatRenderer;
import java.util.Set;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.chat.SignedMessage;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import static java.util.Objects.requireNonNull;
/**
* An abstract implementation of a chat event, handling shared logic.
*/
@ApiStatus.NonExtendable
@NullMarked
public abstract class AbstractChatEvent extends PlayerEvent implements Cancellable {
private final Set<Audience> viewers;
private final Component originalMessage;
private final SignedMessage signedMessage;
private ChatRenderer renderer;
private Component message;
private boolean cancelled;
AbstractChatEvent(final boolean async, final Player player, final Set<Audience> viewers, final ChatRenderer renderer, final Component message, final Component originalMessage, final SignedMessage signedMessage) {
super(player, async);
this.viewers = viewers;
this.renderer = renderer;
this.message = message;
this.originalMessage = originalMessage;
this.signedMessage = signedMessage;
}
/**
* Gets a set of {@link Audience audiences} that this chat message will be displayed to.
*
* <p>The set returned may auto-populate on access. Any listener accessing the returned set should be aware that
* it may reduce performance for a lazy set implementation.</p>
*
* @return a mutable set of {@link Audience audiences} who will receive the chat message
*/
public final Set<Audience> viewers() {
return this.viewers;
}
/**
* Sets the chat renderer.
*
* @param renderer the chat renderer
* @throws NullPointerException if {@code renderer} is {@code null}
*/
public final void renderer(final ChatRenderer renderer) {
this.renderer = requireNonNull(renderer, "renderer");
}
/**
* Gets the chat renderer.
*
* @return the chat renderer
*/
public final ChatRenderer renderer() {
return this.renderer;
}
/**
* Gets the user-supplied message.
* The return value will reflect changes made using {@link #message(Component)}.
*
* @return the user-supplied message
*/
public final Component message() {
return this.message;
}
/**
* Sets the user-supplied message.
*
* @param message the user-supplied message
* @throws NullPointerException if {@code message} is {@code null}
*/
public final void message(final Component message) {
this.message = requireNonNull(message, "message");
}
/**
* Gets the original and unmodified user-supplied message.
* The return value will <b>not</b> reflect changes made using
* {@link #message(Component)}.
*
* @return the original user-supplied message
*/
public final Component originalMessage() {
return this.originalMessage;
}
/**
* Gets the signed message.
* Changes made in this event will <b>not</b> update
* the signed message.
*
* @return the signed message
*/
public final SignedMessage signedMessage() {
return this.signedMessage;
}
@Override
public final boolean isCancelled() {
return this.cancelled;
}
@Override
public final void setCancelled(final boolean cancel) {
this.cancelled = cancel;
}
}

View File

@@ -0,0 +1,29 @@
package io.papermc.paper.event.player;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@ApiStatus.Experimental
@NullMarked
public class AsyncChatCommandDecorateEvent extends AsyncChatDecorateEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
@ApiStatus.Internal
public AsyncChatCommandDecorateEvent(final @Nullable Player player, final Component originalMessage) {
super(player, originalMessage);
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@@ -0,0 +1,105 @@
package io.papermc.paper.event.player;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.server.ServerEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* This event is fired when the server decorates a component for chat purposes. This is called
* before {@link AsyncChatEvent} and the other chat events. It is recommended that you modify the
* message here, and use the chat events for modifying receivers and later the chat type. If you
* want to keep the message as "signed" for the clients who get it, be sure to include the entire
* original message somewhere in the final message.
* <br>
* See {@link AsyncChatCommandDecorateEvent} for the decoration of messages sent via commands
*/
@ApiStatus.Experimental
@NullMarked
public class AsyncChatDecorateEvent extends ServerEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final @Nullable Player player;
private final Component originalMessage;
private Component result;
private boolean cancelled;
@ApiStatus.Internal
public AsyncChatDecorateEvent(final @Nullable Player player, final Component originalMessage) {
super(true);
this.player = player;
this.originalMessage = originalMessage;
this.result = originalMessage;
}
/**
* Gets the player (if available) associated with this event.
* <p>
* Certain commands request decorations without a player context
* which is why this is possibly null.
*
* @return the player or {@code null}
*/
public @Nullable Player player() {
return this.player;
}
/**
* Gets the original decoration input
*
* @return the input
*/
public Component originalMessage() {
return this.originalMessage;
}
/**
* Gets the decoration result. This may already be different from
* {@link #originalMessage()} if some other listener to this event
* changed the result.
*
* @return the result
*/
public Component result() {
return this.result;
}
/**
* Sets the resulting decorated component.
*
* @param result the result
*/
public void result(final Component result) {
this.result = result;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
/**
* A cancelled decorating event means that no changes to the result component
* will have any effect. The decorated component will be equal to the original
* component.
*/
@Override
public void setCancelled(final boolean cancel) {
this.cancelled = cancel;
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@@ -0,0 +1,44 @@
package io.papermc.paper.event.player;
import io.papermc.paper.chat.ChatRenderer;
import java.util.Set;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.chat.SignedMessage;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* An event fired when a {@link Player} sends a chat message to the server.
* <p>
* This event will sometimes fire synchronously, depending on how it was
* triggered.
* <p>
* If a player is the direct cause of this event by an incoming packet, this
* event will be asynchronous. If a plugin triggers this event by compelling a
* player to chat, this event will be synchronous.
* <p>
* Care should be taken to check {@link #isAsynchronous()} and treat the event
* appropriately.
*/
@NullMarked
public final class AsyncChatEvent extends AbstractChatEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
@ApiStatus.Internal
public AsyncChatEvent(final boolean async, final Player player, final Set<Audience> viewers, final ChatRenderer renderer, final Component message, final Component originalMessage, final SignedMessage signedMessage) {
super(async, player, viewers, renderer, message, originalMessage, signedMessage);
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@@ -0,0 +1,40 @@
package io.papermc.paper.event.player;
import io.papermc.paper.chat.ChatRenderer;
import java.util.Set;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.chat.SignedMessage;
import net.kyori.adventure.text.Component;
import org.bukkit.Warning;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* An event fired when a {@link Player} sends a chat message to the server.
*
* @deprecated Listening to this event forces chat to wait for the main thread, delaying chat messages.
* It is recommended to use {@link AsyncChatEvent} instead, wherever possible.
*/
@Deprecated
@Warning(reason = "Listening to this event forces chat to wait for the main thread, delaying chat messages.")
@NullMarked
public final class ChatEvent extends AbstractChatEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
@ApiStatus.Internal
public ChatEvent(final Player player, final Set<Audience> viewers, final ChatRenderer renderer, final Component message, final Component originalMessage, final SignedMessage signedMessage) {
super(false, player, viewers, renderer, message, originalMessage, signedMessage);
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@@ -0,0 +1,180 @@
package io.papermc.paper.text;
import java.io.IOException;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* Paper API-specific methods for working with {@link Component}s and related.
*/
@NullMarked
public final class PaperComponents {
private PaperComponents() {
throw new RuntimeException("PaperComponents is not to be instantiated!");
}
/**
* Resolves a component with a specific command sender and subject.
* <p>
* Note that in Vanilla, elevated permissions are usually required to use
* '@' selectors in various component types, but this method should not
* check such permissions from the sender.
* <p>
* A {@link CommandSender} argument is required to resolve:
* <ul>
* <li>{@link net.kyori.adventure.text.NBTComponent}</li>
* <li>{@link net.kyori.adventure.text.ScoreComponent}</li>
* <li>{@link net.kyori.adventure.text.SelectorComponent}</li>
* </ul>
* A {@link Entity} argument is optional to help resolve:
* <ul>
* <li>{@link net.kyori.adventure.text.ScoreComponent}</li>
* </ul>
* {@link net.kyori.adventure.text.TranslatableComponent}s don't require any extra arguments.
*
* @param input the component to resolve
* @param context the command sender to resolve with
* @param scoreboardSubject the scoreboard subject to use (for use with {@link net.kyori.adventure.text.ScoreComponent}s)
* @return the resolved component
* @throws IOException if a syntax error tripped during resolving
*/
public static Component resolveWithContext(final Component input, final @Nullable CommandSender context, final @Nullable Entity scoreboardSubject) throws IOException {
return resolveWithContext(input, context, scoreboardSubject, true);
}
/**
* Resolves a component with a specific command sender and subject.
* <p>
* Note that in Vanilla, elevated permissions are required to use
* '@' selectors in various component types. If the boolean {@code bypassPermissions}
* argument is {@code false}, the {@link CommandSender} argument will be used to query
* those permissions.
* <p>
* A {@link CommandSender} argument is required to resolve:
* <ul>
* <li>{@link net.kyori.adventure.text.NBTComponent}</li>
* <li>{@link net.kyori.adventure.text.ScoreComponent}</li>
* <li>{@link net.kyori.adventure.text.SelectorComponent}</li>
* </ul>
* A {@link Entity} argument is optional to help resolve:
* <ul>
* <li>{@link net.kyori.adventure.text.ScoreComponent}</li>
* </ul>
* {@link net.kyori.adventure.text.TranslatableComponent}s don't require any extra arguments.
*
* @param input the component to resolve
* @param context the command sender to resolve with
* @param scoreboardSubject the scoreboard subject to use (for use with {@link net.kyori.adventure.text.ScoreComponent}s)
* @param bypassPermissions true to bypass permissions checks for resolving components
* @return the resolved component
* @throws IOException if a syntax error tripped during resolving
*/
@SuppressWarnings("deprecation") // using unsafe as a bridge
public static Component resolveWithContext(final Component input, final @Nullable CommandSender context, final @Nullable Entity scoreboardSubject, final boolean bypassPermissions) throws IOException {
return Bukkit.getUnsafe().resolveWithContext(input, context, scoreboardSubject, bypassPermissions);
}
/**
* Return a component flattener that can use game data to resolve extra information about components.
*
* @return a component flattener
*/
@SuppressWarnings("deprecation") // using unsafe as a bridge
public static ComponentFlattener flattener() {
return Bukkit.getUnsafe().componentFlattener();
}
/**
* Get a serializer for {@link Component}s that will convert components to
* a plain-text string.
*
* <p>Implementations may provide a serializer capable of processing any
* information that requires access to implementation details.</p>
*
* @return a serializer to plain text
* @deprecated will be removed in adventure 5.0.0, use {@link PlainTextComponentSerializer#plainText()}
*/
@Deprecated(forRemoval = true, since = "1.18.1")
public static PlainComponentSerializer plainSerializer() {
return Bukkit.getUnsafe().plainComponentSerializer();
}
/**
* Get a serializer for {@link Component}s that will convert components to
* a plain-text string.
*
* <p>Implementations may provide a serializer capable of processing any
* information that requires access to implementation details.</p>
*
* @return a serializer to plain text
* @deprecated use {@link PlainTextComponentSerializer#plainText()}
*/
@Deprecated(forRemoval = true, since = "1.18.2")
public static PlainTextComponentSerializer plainTextSerializer() {
return Bukkit.getUnsafe().plainTextSerializer();
}
/**
* Get a serializer for {@link Component}s that will convert to and from the
* standard JSON serialization format using Gson.
*
* <p>Implementations may provide a serializer capable of processing any
* information that requires implementation details, such as legacy
* (pre-1.16) hover events.</p>
*
* @return a json component serializer
* @deprecated use {@link GsonComponentSerializer#gson()}
*/
@Deprecated(forRemoval = true, since = "1.18.2")
public static GsonComponentSerializer gsonSerializer() {
return Bukkit.getUnsafe().gsonComponentSerializer();
}
/**
* Get a serializer for {@link Component}s that will convert to and from the
* standard JSON serialization format using Gson, downsampling any RGB colors
* to their nearest {@link NamedTextColor} counterpart.
*
* <p>Implementations may provide a serializer capable of processing any
* information that requires implementation details, such as legacy
* (pre-1.16) hover events.</p>
*
* @return a json component serializer
* @deprecated use {@link GsonComponentSerializer#colorDownsamplingGson()}
*/
@Deprecated(forRemoval = true, since = "1.18.2")
public static GsonComponentSerializer colorDownsamplingGsonSerializer() {
return Bukkit.getUnsafe().colorDownsamplingGsonComponentSerializer();
}
/**
* Get a serializer for {@link Component}s that will convert to and from the
* legacy component format used by Bukkit. This serializer uses the
* {@link LegacyComponentSerializer.Builder#useUnusualXRepeatedCharacterHexFormat()}
* option to match upstream behavior.
*
* <p>This legacy serializer uses the standard section symbol to mark
* formatting characters.</p>
*
* <p>Implementations may provide a serializer capable of processing any
* information that requires access to implementation details.</p>
*
* @return a section serializer
* @deprecated use {@link LegacyComponentSerializer#legacySection()}
*/
@Deprecated(forRemoval = true, since = "1.18.2")
public static LegacyComponentSerializer legacySectionSerializer() {
return Bukkit.getUnsafe().legacyComponentSerializer();
}
}