Merge pull request #7

* Initial mixins for custom chat

* More experiment

* Custom chat messages support + join, leave, /me

* Add custom death message

* Bump version
This commit is contained in:
Alessandro Proto 2024-11-04 18:04:47 +01:00 committed by GitHub
parent 59fc43c227
commit 362132f696
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 420 additions and 10 deletions

View file

@ -9,7 +9,7 @@ yarn_mappings=1.20.1+build.10
loader_version=0.16.8 loader_version=0.16.8
# Mod Properties # Mod Properties
mod_version=1.16.4 mod_version=1.17.0
maven_group=cc.reconnected maven_group=cc.reconnected
archives_base_name=rcc-server archives_base_name=rcc-server

View file

@ -19,8 +19,12 @@ import net.kyori.adventure.platform.fabric.FabricServerAudiences;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.luckperms.api.LuckPerms; import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider; import net.luckperms.api.LuckPermsProvider;
import net.minecraft.network.message.MessageType;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.util.WorldSavePath; import net.minecraft.util.WorldSavePath;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -52,6 +56,8 @@ public class RccServer implements ModInitializer {
return luckPerms; return luckPerms;
} }
public static MinecraftServer server;
private volatile FabricServerAudiences adventure; private volatile FabricServerAudiences adventure;
public FabricServerAudiences adventure() { public FabricServerAudiences adventure() {
@ -62,11 +68,14 @@ public class RccServer implements ModInitializer {
return ret; return ret;
} }
public static final RegistryKey<MessageType> CHAT_TYPE = RegistryKey.of(RegistryKeys.MESSAGE_TYPE, new Identifier(MOD_ID, "chat"));
@Override @Override
public void onInitialize() { public void onInitialize() {
LOGGER.info("Starting rcc-server"); LOGGER.info("Starting rcc-server");
ServerLifecycleEvents.SERVER_STARTING.register(server -> { ServerLifecycleEvents.SERVER_STARTING.register(server -> {
RccServer.server = server;
state.register(server.getSavePath(WorldSavePath.ROOT).resolve("data").resolve(RccServer.MOD_ID)); state.register(server.getSavePath(WorldSavePath.ROOT).resolve("data").resolve(RccServer.MOD_ID));
this.adventure = FabricServerAudiences.of(server); this.adventure = FabricServerAudiences.of(server);
}); });

View file

@ -3,8 +3,7 @@ package cc.reconnected.server;
import io.wispforest.owo.config.annotation.Config; import io.wispforest.owo.config.annotation.Config;
import io.wispforest.owo.config.annotation.Nest; import io.wispforest.owo.config.annotation.Nest;
import java.util.ArrayList; import java.util.*;
import java.util.List;
@Config(name = "rcc-server-config", wrapperName = "RccServerConfig") @Config(name = "rcc-server-config", wrapperName = "RccServerConfig")
public class RccServerConfigModel { public class RccServerConfigModel {
@ -32,6 +31,12 @@ public class RccServerConfigModel {
@Nest @Nest
public AutoRestart autoRestart = new AutoRestart(); public AutoRestart autoRestart = new AutoRestart();
@Nest
public CustomNameConfig customName = new CustomNameConfig();
@Nest
public CustomChatFormat customChatFormat = new CustomChatFormat();
public static class HttpApi { public static class HttpApi {
public boolean enableHttpApi = true; public boolean enableHttpApi = true;
public int httpPort = 25581; public int httpPort = 25581;
@ -106,4 +111,21 @@ public class RccServerConfigModel {
1 1
)); ));
} }
public static class CustomNameConfig {
public LinkedHashMap<String, String> formats = new LinkedHashMap<>(Map.of(
"admin", "<red><username></red>",
"default", "<green><username></green>"
));
}
public static class CustomChatFormat {
public boolean enableMarkdown = true;
public String chatFormat = "<display_name><gray>:</gray> <message>";
public String emoteFormat = "<gray>\uD83D\uDC64 <display_name> <i><message></i></gray>";
public String joinFormat = "<green>+</green> <display_name> <yellow>joined!</yellow>";
public String joinRenamedFormat = "<green>+</green> <display_name> <yellow>joined! <i>(Previously known as <previous_name>)</i></yellow>";
public String leaveFormat = "<red>-</red> <display_name> <yellow>left!</yellow>";
public String deathFormat = "<gray>\u2620 <death_message></gray>";
}
} }

View file

@ -0,0 +1,43 @@
package cc.reconnected.server.core;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.util.Components;
import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.Placeholders;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.luckperms.api.model.group.Group;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;
public class CustomNameFormat {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
public static MutableText getNameForPlayer(ServerPlayerEntity player) {
var formats = RccServer.CONFIG.customName.formats().entrySet();
var lp = RccServer.getInstance().luckPerms();
var playerContext = PlaceholderContext.of(player);
var user = lp.getPlayerAdapter(ServerPlayerEntity.class).getUser(player);
var groups = user.getInheritedGroups(user.getQueryOptions()).stream().map(Group::getName).toList();
String format = null;
for (var entry : formats) {
if (groups.contains(entry.getKey())) {
format = entry.getValue();
break;
}
}
if (format == null) {
format = "<username>";
}
var displayName = miniMessage.deserialize(format, TagResolver.resolver(
Placeholder.parsed("username", player.getGameProfile().getName())
));
return Placeholders.parseText(Components.toText(displayName), playerContext).copy();
}
}

View file

@ -0,0 +1,41 @@
package cc.reconnected.server.core.customChat;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.parser.MarkdownParser;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public class CustomChatMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
public static void sendChatMessage(ServerPlayerEntity receiver, SignedMessage message, MessageType.Parameters params) {
var playerUuid = message.link().sender();
var player = RccServer.server.getPlayerManager().getPlayer(playerUuid);
Text messageText;
if (RccServer.CONFIG.customChatFormat.enableMarkdown()) {
messageText = MarkdownParser.defaultParser.parseNode(message.getSignedContent()).toText();
} else {
messageText = message.getContent();
}
var component = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.chatFormat(), TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName()),
Placeholder.component("message", messageText)
));
var text = Components.toText(component);
var msgType = RccServer.server.getRegistryManager().get(RegistryKeys.MESSAGE_TYPE).getOrThrow(RccServer.CHAT_TYPE);
var newParams = new MessageType.Parameters(msgType, text, null);
receiver.networkHandler.sendChatMessage(message, newParams);
}
}

View file

@ -0,0 +1,41 @@
package cc.reconnected.server.core.customChat;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public class CustomConnectionMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
public static Text onJoin(ServerPlayerEntity player) {
var joinMessage = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.joinFormat(),
TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName())
));
return Components.toText(joinMessage);
}
public static Text onJoinRenamed(ServerPlayerEntity player, String previousName) {
var joinMessage = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.joinRenamedFormat(),
TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName()),
Placeholder.component("previous_name", Text.of(previousName))
));
return Components.toText(joinMessage);
}
public static Text onLeave(ServerPlayerEntity player) {
var leaveMessage = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.leaveFormat(),
TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName())
));
return Components.toText(leaveMessage);
}
}

View file

@ -0,0 +1,24 @@
package cc.reconnected.server.core.customChat;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.entity.damage.DamageTracker;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public class CustomDeathMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
public static Text onDeath(ServerPlayerEntity player, DamageTracker instance) {
var deathMessage = instance.getDeathMessage();
var deathComponent = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.deathFormat(),
TagResolver.resolver(
Placeholder.component("death_message", deathMessage)
));
return Components.toText(deathComponent);
}
}

View file

@ -0,0 +1,41 @@
package cc.reconnected.server.core.customChat;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.parser.MarkdownParser;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public class CustomEmoteMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
public static void sendEmoteMessage(ServerPlayerEntity receiver, SignedMessage message, MessageType.Parameters params) {
var playerUuid = message.link().sender();
var player = RccServer.server.getPlayerManager().getPlayer(playerUuid);
Text messageText;
if (RccServer.CONFIG.customChatFormat.enableMarkdown()) {
messageText = MarkdownParser.defaultParser.parseNode(message.getSignedContent()).toText();
} else {
messageText = message.getContent();
}
var component = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.emoteFormat(), TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName()),
Placeholder.component("message", messageText)
));
var text = Components.toText(component);
var msgType = RccServer.server.getRegistryManager().get(RegistryKeys.MESSAGE_TYPE).getOrThrow(RccServer.CHAT_TYPE);
var newParams = new MessageType.Parameters(msgType, text, null);
receiver.networkHandler.sendChatMessage(message, newParams);
}
}

View file

@ -0,0 +1,36 @@
package cc.reconnected.server.core.customChat;
import cc.reconnected.server.RccServer;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SentMessage;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public record CustomSentMessage(SignedMessage message) implements SentMessage {
@Override
public Text getContent() {
return this.message.getContent();
}
@Override
public void send(ServerPlayerEntity receiver, boolean filterMaskEnabled, MessageType.Parameters params) {
SignedMessage signedMessage = this.message.withFilterMaskEnabled(filterMaskEnabled);
RccServer.LOGGER.info("Message params type: {}", params.type().chat().translationKey());
if (!signedMessage.isFullyFiltered()) {
switch (params.type().chat().translationKey()) {
case "chat.type.text":
CustomChatMessage.sendChatMessage(receiver, message, params);
break;
case "chat.type.emote":
CustomEmoteMessage.sendEmoteMessage(receiver, message, params);
break;
default:
receiver.networkHandler.sendChatMessage(this.message, params);
break;
}
}
}
}

View file

@ -0,0 +1,33 @@
package cc.reconnected.server.mixin;
import cc.reconnected.server.core.CustomNameFormat;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin {
@Shadow
private MutableText addTellClickEvent(MutableText component) {
return null;
}
@Shadow
public abstract Text getName();
@Shadow
public abstract GameProfile getGameProfile();
@Inject(method = "getDisplayName", at = @At("HEAD"), cancellable = true)
public void getDisplayName(CallbackInfoReturnable<MutableText> cir) {
var name = CustomNameFormat.getNameForPlayer((ServerPlayerEntity) (Object) this);
cir.setReturnValue(addTellClickEvent(name));
}
}

View file

@ -0,0 +1,42 @@
package cc.reconnected.server.mixin;
import cc.reconnected.server.core.customChat.CustomConnectionMessage;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableTextContent;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerManager.class)
public class PlayerManagerMixin {
@Unique
private ServerPlayerEntity rccServer$player = null;
@Inject(method="onPlayerConnect", at = @At("HEAD"))
private void rccServer$onJoin(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
rccServer$player = player;
}
@Inject(method="onPlayerConnect", at = @At("RETURN"))
private void rccServer$onJoinReturn(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
rccServer$player = null;
}
@ModifyArg(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
public Text rccServer$getPlayerJoinMessage(Text message) {
var ogText = (TranslatableTextContent) message.getContent();
var args = ogText.getArgs();
if (args.length == 1) {
return CustomConnectionMessage.onJoin(rccServer$player);
} else {
return CustomConnectionMessage.onJoinRenamed(rccServer$player, (String) args[1]);
}
}
}

View file

@ -0,0 +1,36 @@
package cc.reconnected.server.mixin;
import cc.reconnected.server.RccServer;
import com.mojang.datafixers.util.Pair;
import net.minecraft.network.message.MessageType;
import net.minecraft.registry.*;
import net.minecraft.resource.ResourceManager;
import net.minecraft.text.Decoration;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.List;
import java.util.Map;
@Mixin(RegistryLoader.class)
public class RegistryLoaderMixin {
@Inject(method = "load(Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/registry/DynamicRegistryManager;Ljava/util/List;)Lnet/minecraft/registry/DynamicRegistryManager$Immutable;", at = @At(value = "INVOKE", target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V", ordinal = 0, shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private static void rccServer$load(ResourceManager resourceManager, DynamicRegistryManager baseRegistryManager, List<RegistryLoader.Entry<?>> entries,
CallbackInfoReturnable<DynamicRegistryManager.Immutable> cir, Map _unused, List<Pair<MutableRegistry<?>, Object>> list) {
for (var pair : list) {
var registry = pair.getFirst();
if(registry.getKey().equals(RegistryKeys.MESSAGE_TYPE)) {
Registry.register((Registry<MessageType>) registry, RccServer.CHAT_TYPE,
new MessageType(
Decoration.ofChat("%s"),
Decoration.ofChat("%s")
));
}
}
}
}

View file

@ -0,0 +1,23 @@
package cc.reconnected.server.mixin;
import cc.reconnected.server.core.customChat.CustomSentMessage;
import net.minecraft.network.message.SentMessage;
import net.minecraft.network.message.SignedMessage;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SentMessage.class)
public interface SentMessageMixin {
@Inject(method = "of", at = @At("HEAD"), cancellable = true)
private static void rccServer$of(SignedMessage message, CallbackInfoReturnable<SentMessage> cir) {
if(message.isSenderMissing()) {
cir.setReturnValue(new SentMessage.Profileless(message.getContent()));
} else {
cir.setReturnValue(new CustomSentMessage(message));
}
}
}

View file

@ -1,16 +1,20 @@
package cc.reconnected.server.mixin; package cc.reconnected.server.mixin;
import cc.reconnected.server.RccServer; import cc.reconnected.server.RccServer;
import cc.reconnected.server.core.customChat.CustomConnectionMessage;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableTextContent;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.EnumSet; import java.util.EnumSet;
@ -24,9 +28,6 @@ public abstract class ServerPlayNetworkManagerMixin {
@Shadow @Shadow
public ServerPlayerEntity player; public ServerPlayerEntity player;
@Shadow
public abstract void sendPacket(Packet<?> packet);
@Inject(method = "tick", at = @At("TAIL")) @Inject(method = "tick", at = @At("TAIL"))
private void rccServer$updatePlayerList(CallbackInfo ci) { private void rccServer$updatePlayerList(CallbackInfo ci) {
if(RccServer.CONFIG.customTabList.enableTabList()) { if(RccServer.CONFIG.customTabList.enableTabList()) {
@ -35,5 +36,8 @@ public abstract class ServerPlayNetworkManagerMixin {
} }
} }
@ModifyArg(method = "onDisconnected", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
private Text rccServer$getPlayerLeaveMessage(Text message) {
return CustomConnectionMessage.onLeave(this.player);
}
} }

View file

@ -1,16 +1,19 @@
package cc.reconnected.server.mixin; package cc.reconnected.server.mixin;
import cc.reconnected.server.RccServer; import cc.reconnected.server.RccServer;
import cc.reconnected.server.core.customChat.CustomDeathMessage;
import eu.pb4.placeholders.api.PlaceholderContext; import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.Placeholders; import eu.pb4.placeholders.api.Placeholders;
import eu.pb4.placeholders.api.parsers.NodeParser; import eu.pb4.placeholders.api.parsers.NodeParser;
import eu.pb4.placeholders.api.parsers.TextParserV1; import eu.pb4.placeholders.api.parsers.TextParserV1;
import net.minecraft.entity.damage.DamageTracker;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ServerPlayerEntity.class) @Mixin(ServerPlayerEntity.class)
@ -27,4 +30,10 @@ public class ServerPlayerEntityMixin {
callback.setReturnValue(text); callback.setReturnValue(text);
} }
} }
@Redirect(method = "onDeath", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/damage/DamageTracker;getDeathMessage()Lnet/minecraft/text/Text;"))
private Text rccServer$getDeathMessage(DamageTracker instance) {
var player = (ServerPlayerEntity) (Object) this;
return CustomDeathMessage.onDeath(player, instance);
}
} }

View file

@ -6,6 +6,7 @@ import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text; import net.minecraft.text.Text;
public class Components { public class Components {
@ -19,7 +20,7 @@ public class Components {
.clickEvent(ClickEvent.runCommand(command)); .clickEvent(ClickEvent.runCommand(command));
} }
public static Text toText(Component component) { public static MutableText toText(Component component) {
var json = JSONComponentSerializer.json().serialize(component); var json = JSONComponentSerializer.json().serialize(component);
return Text.Serializer.fromJson(json); return Text.Serializer.fromJson(json);
} }

View file

@ -4,12 +4,17 @@
"package": "cc.reconnected.server.mixin", "package": "cc.reconnected.server.mixin",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"mixins": [ "mixins": [
"ServerPlayNetworkManagerMixin"
], ],
"client": [], "client": [],
"server": [ "server": [
"MessageCommandMixin", "MessageCommandMixin",
"ServerPlayerEntityMixin" "PlayerEntityMixin",
"RegistryLoaderMixin",
"SentMessageMixin",
"ServerPlayerEntityMixin",
"ServerPlayNetworkManagerMixin",
"PlayerManagerMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1