Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.19.3 chat for bukkit #113

Merged
merged 7 commits into from Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -49,6 +49,7 @@ final class BukkitAudience extends FacetAudience<CommandSender> {
() -> new ViaFacet.Chat<>(Player.class, VIA),
// () -> new SpigotFacet.ChatWithType(),
// () -> new SpigotFacet.Chat(),
() -> new CraftBukkitFacet.Chat1_19_3(),
() -> new CraftBukkitFacet.Chat(),
() -> new BukkitFacet.Chat());
private static final Collection<Facet.ActionBar<Player, ?>> ACTION_BAR = Facet.of(
Expand Down
Expand Up @@ -28,7 +28,6 @@
import java.util.Collection;
import java.util.Set;
import java.util.function.Function;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.permission.PermissionChecker;
Expand Down Expand Up @@ -79,7 +78,7 @@ protected Chat() {
}

@Override
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final @NotNull String message, final @NotNull MessageType type) {
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final @NotNull String message, final @NotNull Object type) {
viewer.sendMessage(message);
}
}
Expand Down
@@ -0,0 +1,125 @@
/*
* This file is part of adventure-platform, licensed under the MIT License.
*
* Copyright (c) 2018-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.platform.bukkit;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Modifier;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;

import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findClass;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findConstructor;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findMcClassName;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findNmsClassName;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.searchMethod;
import static net.kyori.adventure.platform.facet.Knob.logError;

final class CraftBukkitAccess {
static final @Nullable Class<?> CLASS_CHAT_COMPONENT = findClass(
findNmsClassName("IChatBaseComponent"),
findMcClassName("network.chat.IChatBaseComponent"),
findMcClassName("network.chat.Component")
);
static final @Nullable Class<?> CLASS_REGISTRY = findClass(
findMcClassName("core.IRegistry"),
findMcClassName("core.Registry")
);
static final @Nullable Class<?> CLASS_SERVER_LEVEL = findClass(
findMcClassName("server.level.WorldServer"),
findMcClassName("server.level.ServerLevel")
);
static final @Nullable Class<?> CLASS_REGISTRY_ACCESS = findClass(
findMcClassName("core.IRegistryCustom"),
findMcClassName("core.RegistryAccess")
);
static final @Nullable Class<?> CLASS_RESOURCE_KEY = findClass(findMcClassName("resources.ResourceKey"));
static final @Nullable Class<?> CLASS_RESOURCE_LOCATION = findClass(
findMcClassName("resources.MinecraftKey"),
findMcClassName("resources.ResourceLocation")
);

private CraftBukkitAccess() {
}

static final class Chat1_19_3 {
static final @Nullable MethodHandle NEW_RESOURCE_LOCATION = findConstructor(CLASS_RESOURCE_LOCATION, String.class, String.class);
static final @Nullable MethodHandle RESOURCE_KEY_CREATE = searchMethod(CLASS_RESOURCE_KEY, Modifier.PUBLIC | Modifier.STATIC, "create", CLASS_RESOURCE_KEY, CLASS_RESOURCE_KEY, CLASS_RESOURCE_LOCATION);
static final @Nullable MethodHandle SERVER_PLAYER_GET_LEVEL = searchMethod(CraftBukkitFacet.CRAFT_PLAYER_GET_HANDLE.type().returnType(), Modifier.PUBLIC, "getLevel", CLASS_SERVER_LEVEL);
static final @Nullable MethodHandle SERVER_LEVEL_GET_REGISTRY_ACCESS = searchMethod(CLASS_SERVER_LEVEL, Modifier.PUBLIC, "registryAccess", CLASS_REGISTRY_ACCESS);
static final @Nullable MethodHandle REGISTRY_ACCESS_GET_REGISTRY_OPTIONAL = searchMethod(CLASS_REGISTRY_ACCESS, Modifier.PUBLIC, "registry", Optional.class, CLASS_RESOURCE_KEY);
static final @Nullable MethodHandle REGISTRY_GET_OPTIONAL = searchMethod(CLASS_REGISTRY, Modifier.PUBLIC, "getOptional", Optional.class, CLASS_RESOURCE_LOCATION);
static final @Nullable MethodHandle REGISTRY_GET_ID = searchMethod(CLASS_REGISTRY, Modifier.PUBLIC, "getId", int.class, Object.class);
static final @Nullable MethodHandle DISGUISED_CHAT_PACKET_CONSTRUCTOR;
static final @Nullable MethodHandle CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR;

static final Object CHAT_TYPE_RESOURCE_KEY;

static {
MethodHandle boundNetworkConstructor = null;
MethodHandle disguisedChatPacketConstructor = null;
Object chatTypeResourceKey = null;

try {
Class<?> classChatTypeBoundNetwork = findClass(findMcClassName("network.chat.ChatType$BoundNetwork"));
if (classChatTypeBoundNetwork == null) {
final Class<?> parentClass = findClass(findMcClassName("network.chat.ChatMessageType"));
if (parentClass != null) {
for (final Class<?> childClass : parentClass.getClasses()) {
boundNetworkConstructor = findConstructor(childClass, int.class, CLASS_CHAT_COMPONENT, CLASS_CHAT_COMPONENT);
if (boundNetworkConstructor != null) {
classChatTypeBoundNetwork = childClass;
break;
}
}
}
}

final Class<?> disguisedChatPacketClass = findClass(findMcClassName("network.protocol.game.ClientboundDisguisedChatPacket"));
if (disguisedChatPacketClass != null && classChatTypeBoundNetwork != null) {
disguisedChatPacketConstructor = findConstructor(disguisedChatPacketClass, CLASS_CHAT_COMPONENT, classChatTypeBoundNetwork);
}

if (NEW_RESOURCE_LOCATION != null && RESOURCE_KEY_CREATE != null) {
final MethodHandle createRegistryKey = searchMethod(CLASS_RESOURCE_KEY, Modifier.PUBLIC | Modifier.STATIC, "createRegistryKey", CLASS_RESOURCE_KEY, CLASS_RESOURCE_LOCATION);
if (createRegistryKey != null) {
chatTypeResourceKey = createRegistryKey.invoke(NEW_RESOURCE_LOCATION.invoke("minecraft", "chat_type"));
}
}
} catch (final Throwable error) {
logError(error, "Failed to initialize 1.19.3 chat support");
}

DISGUISED_CHAT_PACKET_CONSTRUCTOR = disguisedChatPacketConstructor;
CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR = boundNetworkConstructor;
CHAT_TYPE_RESOURCE_KEY = chatTypeResourceKey;
}

private Chat1_19_3() {
}

static boolean isSupported() {
return SERVER_LEVEL_GET_REGISTRY_ACCESS != null && REGISTRY_ACCESS_GET_REGISTRY_OPTIONAL != null && REGISTRY_GET_OPTIONAL != null && CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR != null && DISGUISED_CHAT_PACKET_CONSTRUCTOR != null && CHAT_TYPE_RESOURCE_KEY != null;
}
}
}
Expand Up @@ -45,13 +45,15 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.chat.ChatType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagTypes;
Expand Down Expand Up @@ -125,7 +127,7 @@ public boolean isSupported() {
private static final Class<?> CLASS_CRAFT_ENTITY = findCraftClass("entity.CraftEntity");
private static final MethodHandle CRAFT_ENTITY_GET_HANDLE = findMethod(CLASS_CRAFT_ENTITY, "getHandle", CLASS_NMS_ENTITY);
static final @Nullable Class<? extends Player> CLASS_CRAFT_PLAYER = findCraftClass("entity.CraftPlayer", Player.class);
private static final @Nullable MethodHandle CRAFT_PLAYER_GET_HANDLE;
static final @Nullable MethodHandle CRAFT_PLAYER_GET_HANDLE;
private static final @Nullable MethodHandle ENTITY_PLAYER_GET_CONNECTION;
private static final @Nullable MethodHandle PLAYER_CONNECTION_SEND_PACKET;

Expand Down Expand Up @@ -239,7 +241,7 @@ public Object createMessage(final @NotNull V viewer, final @NotNull Component me
}

private static final @Nullable MethodHandle LEGACY_CHAT_PACKET_CONSTRUCTOR; // (IChatBaseComponent, byte)
private static final @Nullable MethodHandle CHAT_PACKET_CONSTRUCTOR; // (ChatMessageType, IChatBaseComponent, UUID) -> PacketPlayOutChat
private static final @Nullable MethodHandle CHAT_PACKET_CONSTRUCTOR; // (ChatMessageType, IChatBaseComponent, UUID) / (IChatBaseComponent, boolean) -> PacketPlayOutChat

static {
MethodHandle legacyChatPacketConstructor = null;
Expand Down Expand Up @@ -295,14 +297,47 @@ public Object createMessage(final @NotNull V viewer, final @NotNull Component me
LEGACY_CHAT_PACKET_CONSTRUCTOR = legacyChatPacketConstructor;
}

static class Chat1_19_3 extends Chat {
@Override
public boolean isSupported() {
return super.isSupported() && CraftBukkitAccess.Chat1_19_3.isSupported();
}

@Override
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final @NotNull Object message, final @NotNull Object type) {
if (!(type instanceof ChatType.Bound)) {
super.sendMessage(viewer, source, message, type);
} else {
final ChatType.Bound bound = (ChatType.Bound) type;
try {
final Object registryAccess = CraftBukkitAccess.Chat1_19_3.SERVER_LEVEL_GET_REGISTRY_ACCESS.invoke(CraftBukkitAccess.Chat1_19_3.SERVER_PLAYER_GET_LEVEL.invoke(CRAFT_PLAYER_GET_HANDLE.invoke(viewer)));
final Object chatTypeRegistry = ((Optional<?>) CraftBukkitAccess.Chat1_19_3.REGISTRY_ACCESS_GET_REGISTRY_OPTIONAL.invoke(registryAccess, CraftBukkitAccess.Chat1_19_3.CHAT_TYPE_RESOURCE_KEY)).orElseThrow(NoSuchElementException::new);
final Object typeResourceLocation = CraftBukkitAccess.Chat1_19_3.NEW_RESOURCE_LOCATION.invoke(bound.type().key().namespace(), bound.type().key().value());
final Object chatTypeObject = ((Optional<?>) CraftBukkitAccess.Chat1_19_3.REGISTRY_GET_OPTIONAL.invoke(chatTypeRegistry, typeResourceLocation)).orElseThrow(NoSuchElementException::new);
final int networkId = (int) CraftBukkitAccess.Chat1_19_3.REGISTRY_GET_ID.invoke(chatTypeRegistry, chatTypeObject);
if (networkId < 0) {
throw new IllegalArgumentException("Could not get a valid network id from " + type);
}
final Object nameComponent = this.createMessage(viewer, bound.name());
final Object targetComponent = bound.target() != null ? this.createMessage(viewer, bound.target()) : null;
final Object boundNetwork = CraftBukkitAccess.Chat1_19_3.CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR.invoke(networkId, nameComponent, targetComponent);

this.sendMessage(viewer, CraftBukkitAccess.Chat1_19_3.DISGUISED_CHAT_PACKET_CONSTRUCTOR.invoke(message, boundNetwork));
} catch (final Throwable error) {
logError(error, "Failed to send a 1.19.3+ message: %s %s", message, type);
}
}
}
}

static class Chat extends PacketFacet<CommandSender> implements Facet.Chat<CommandSender, Object> {
@Override
public boolean isSupported() {
return super.isSupported() && CHAT_PACKET_CONSTRUCTOR != null;
}

@Override
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final @NotNull Object message, final @NotNull MessageType type) {
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final @NotNull Object message, final @NotNull Object type) {
final Object messageType = type == MessageType.CHAT ? MESSAGE_TYPE_CHAT : MESSAGE_TYPE_SYSTEM;
try {
this.sendMessage(viewer, CHAT_PACKET_CONSTRUCTOR.invoke(message, messageType, source.uuid()));
Expand Down
Expand Up @@ -28,6 +28,7 @@
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -175,7 +176,11 @@ public static MethodHandle searchMethod(final @Nullable Class<?> holderClass, fi
for (final String methodName : methodNames) {
if (methodName == null) continue;
try {
return LOOKUP.findVirtual(holderClass, methodName, MethodType.methodType(returnClass, parameterClasses));
if (modifier != null && Modifier.isStatic(modifier)) {
return LOOKUP.findStatic(holderClass, methodName, MethodType.methodType(returnClass, parameterClasses));
} else {
return LOOKUP.findVirtual(holderClass, methodName, MethodType.methodType(returnClass, parameterClasses));
}
} catch (final NoSuchMethodException | IllegalAccessException e) {
}
}
Expand All @@ -184,7 +189,11 @@ public static MethodHandle searchMethod(final @Nullable Class<?> holderClass, fi
if ((modifier == null || (method.getModifiers() & modifier) == 0)
|| !Arrays.equals(method.getParameterTypes(), parameterClasses)) continue;
try {
return LOOKUP.findVirtual(holderClass, method.getName(), MethodType.methodType(returnClass, parameterClasses));
if (Modifier.isStatic(modifier)) {
return LOOKUP.findStatic(holderClass, method.getName(), MethodType.methodType(returnClass, parameterClasses));
} else {
return LOOKUP.findVirtual(holderClass, method.getName(), MethodType.methodType(returnClass, parameterClasses));
}
} catch (final NoSuchMethodException | IllegalAccessException e) {
}
}
Expand Down
Expand Up @@ -93,7 +93,7 @@ public boolean isSupported() {
}

@Override
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final BaseComponent @NotNull[] message, final @NotNull MessageType type) {
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final BaseComponent @NotNull[] message, final @NotNull Object type) {
viewer.spigot().sendMessage(message);
}
}
Expand Down Expand Up @@ -123,8 +123,8 @@ public boolean isSupported() {

@Override
@SuppressWarnings("deprecation")
public void sendMessage(final @NotNull Player viewer, final @NotNull Identity source, final BaseComponent @NotNull[] message, final @NotNull MessageType type) {
final ChatMessageType chat = this.createType(type);
public void sendMessage(final @NotNull Player viewer, final @NotNull Identity source, final BaseComponent @NotNull[] message, final @NotNull Object type) {
final ChatMessageType chat = type instanceof MessageType ? this.createType((MessageType) type) : ChatMessageType.SYSTEM; // if it's not a legacy adventure MessageType it doesn't matter cause its not used
if (chat != null) {
viewer.spigot().sendMessage(chat, message);
}
Expand Down
Expand Up @@ -93,7 +93,7 @@ public boolean isApplicable(final @NotNull CommandSender viewer) {
}

@Override
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final BaseComponent @NotNull [] message, final @NotNull MessageType type) {
public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Identity source, final BaseComponent @NotNull [] message, final @NotNull Object type) {
viewer.sendMessage(message);
}
}
Expand Down Expand Up @@ -134,7 +134,7 @@ public boolean isSupported() {
}

@Override
public void sendMessage(final @NotNull ProxiedPlayer viewer, final @NotNull Identity source, final BaseComponent @NotNull [] message, final @NotNull MessageType type) {
public void sendMessage(final @NotNull ProxiedPlayer viewer, final @NotNull Identity source, final BaseComponent @NotNull [] message, final @NotNull Object type) {
if (type == MessageType.CHAT) {
viewer.sendMessage(source.uuid(), message);
} else {
Expand All @@ -155,8 +155,8 @@ static class ChatPlayer extends Message implements Facet.Chat<ProxiedPlayer, Bas
}

@Override
public void sendMessage(final @NotNull ProxiedPlayer viewer, final @NotNull Identity source, final BaseComponent @NotNull [] message, final @NotNull MessageType type) {
final ChatMessageType chat = this.createType(type);
public void sendMessage(final @NotNull ProxiedPlayer viewer, final @NotNull Identity source, final BaseComponent @NotNull [] message, final @NotNull Object type) {
final ChatMessageType chat = type instanceof MessageType ? this.createType((MessageType) type) : ChatMessageType.SYSTEM; // if it's not a legacy adventure MessageType it doesn't matter cause its not used
if (chat != null) {
viewer.sendMessage(chat, message);
}
Expand Down
Expand Up @@ -181,7 +181,7 @@ interface Chat<V, M> extends Message<V, M> {
* @param type a message type
* @since 4.0.0
*/
void sendMessage(final @NotNull V viewer, final @NotNull Identity source, final @NotNull M message, final @NotNull MessageType type);
void sendMessage(final @NotNull V viewer, final @NotNull Identity source, final @NotNull M message, final @NotNull Object type);
}

/**
Expand Down
Expand Up @@ -36,6 +36,8 @@
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.chat.ChatType;
import net.kyori.adventure.chat.SignedMessage;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.sound.Sound;
Expand Down Expand Up @@ -194,6 +196,34 @@ public void sendMessage(final @NotNull Identity source, final @NotNull Component
}
}

@Override
public void sendMessage(final @NotNull Component original, final ChatType.@NotNull Bound boundChatType) {
if (this.chat == null) return;
final Object message = this.createMessage(original, this.chat);
if (message == null) return;

final Component name = this.provider.componentRenderer.render(boundChatType.name(), this);
Component target = null;
if (boundChatType.target() != null) {
target = this.provider.componentRenderer.render(boundChatType.target(), this);
}
final Object renderedType = boundChatType.type().bind(name, target);

for (final V viewer : this.viewers) {
this.chat.sendMessage(viewer, Identity.nil(), message, renderedType);
}
}

@Override
public void sendMessage(final @NotNull SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType) {
if (signedMessage.isSystem()) {
final Component content = signedMessage.unsignedContent() != null ? signedMessage.unsignedContent() : Component.text(signedMessage.message());
this.sendMessage(content, boundChatType);
} else {
Audience.super.sendMessage(signedMessage, boundChatType);
}
}

@Override
public void sendActionBar(final @NotNull Component original) {
if (this.actionBar == null) return;
Expand Down