Skip to content

Commit

Permalink
Merge pull request #113 from Machine-Maker/dev/1.19.3-chat
Browse files Browse the repository at this point in the history
1.19.3 chat for bukkit
  • Loading branch information
Machine-Maker committed Dec 9, 2022
2 parents df71d1e + 7f1c948 commit e31dcaf
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 20 deletions.
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

0 comments on commit e31dcaf

Please sign in to comment.