refactor: use custom ComponentRenderer for custom chat

This commit is contained in:
amyavi 2025-01-21 03:00:26 -03:00
parent 10645c1cad
commit 71927e060a
No known key found for this signature in database
12 changed files with 204 additions and 54 deletions

View file

@ -3,7 +3,7 @@ package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import land.chipmunk.chipmunkmod.modules.CustomChat;
import land.chipmunk.chipmunkmod.modules.custom_chat.CustomChat;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.text.Text;

View file

@ -5,7 +5,6 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.modules.CommandCore;
import land.chipmunk.chipmunkmod.modules.CustomChat;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.text.Text;
@ -26,8 +25,6 @@ public class ReloadConfigCommand {
try {
ChipmunkMod.CONFIG = ChipmunkMod.loadConfig();
CustomChat.INSTANCE.reloadFormat();
CommandCore.INSTANCE.reloadRelativeArea();
source.sendFeedback(Text.literal("Successfully reloaded the config"));

View file

@ -2,11 +2,13 @@ package land.chipmunk.chipmunkmod.config;
import land.chipmunk.chipmunkmod.config.migration.AbstractMigrationManager;
import land.chipmunk.chipmunkmod.config.migrations.MigrationV0;
import land.chipmunk.chipmunkmod.config.migrations.MigrationV1;
public final class ChipmunkModMigrations extends AbstractMigrationManager {
public ChipmunkModMigrations() {
super("version");
this.register(new MigrationV0()); // unversioned -> v0
this.register(new MigrationV1());
}
}

View file

@ -0,0 +1,66 @@
package land.chipmunk.chipmunkmod.config.migrations;
import land.chipmunk.chipmunkmod.config.migration.ConfigMigration;
import land.chipmunk.chipmunkmod.util.configurate.ConfigurateUtilities;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.SelectorComponent;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
import java.util.List;
import static org.spongepowered.configurate.NodePath.path;
public final class MigrationV1 implements ConfigMigration {
@Override
public int version() {
return 1;
}
@Override
public ConfigurationTransformation create() {
return ConfigurationTransformation.builder()
.addAction(path("customChat", "format"),
ConfigurateUtilities.componentTransformer(new CustomChatFormatMigrator(), null))
.build();
}
private static final class CustomChatFormatMigrator extends TranslatableComponentRenderer<Void> {
@Override
protected @NotNull Component renderSelector(final @NotNull SelectorComponent component,
final @NotNull Void context) {
final String pattern = component.pattern();
if (pattern.equals("USERNAME") || pattern.equals("UUID")) {
final SelectorComponent.Builder builder = Component.selector()
.pattern("@s");
return this.mergeStyleAndOptionallyDeepRender(component, builder, context);
}
return super.renderSelector(component, context);
}
// Older configs had things like: `{ "text": "", "extra": ["MESSAGE"] }`
// We don't need that anymore, transform it to `{ "text": "MESSAGE" }`
@Override
protected @NotNull Component renderText(@NotNull TextComponent component, @NotNull Void context) {
if (!component.content().isEmpty() || component.children().size() != 1) {
return super.renderText(component, context);
}
final Component onlyChild = component.children().getFirst();
if (!onlyChild.equals(Component.text("MESSAGE"))) {
return super.renderText(component, context);
}
final Component newComponent = component
.children(List.of()); // Clear children
final TextComponent.Builder builder = Component.text()
.content("MESSAGE");
return this.mergeStyleAndOptionallyDeepRender(newComponent, builder, context);
}
}
}

View file

@ -5,6 +5,7 @@ import land.chipmunk.chipmunkmod.command.CommandManager;
import land.chipmunk.chipmunkmod.listeners.Listener;
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
import land.chipmunk.chipmunkmod.modules.*;
import land.chipmunk.chipmunkmod.modules.custom_chat.CustomChat;
import net.minecraft.client.MinecraftClient;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.network.encryption.NetworkEncryptionUtils;
@ -15,7 +16,6 @@ import net.minecraft.network.message.MessageSignatureData;
import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerRemoveS2CPacket;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.resource.featuretoggle.FeatureSet;
import net.minecraft.text.PlainTextContent;

View file

@ -4,6 +4,7 @@ import com.google.common.hash.Hashing;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.listeners.Listener;
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
import land.chipmunk.chipmunkmod.modules.custom_chat.CustomChat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.text.PlainTextContent;

View file

@ -1,8 +1,11 @@
package land.chipmunk.chipmunkmod.modules;
package land.chipmunk.chipmunkmod.modules.custom_chat;
import com.google.common.hash.Hashing;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.modules.Chat;
import land.chipmunk.chipmunkmod.modules.CommandCore;
import land.chipmunk.chipmunkmod.modules.KaboomCheck;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.event.ClickEvent;
@ -15,12 +18,15 @@ import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern;
public class CustomChat {
private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacyAmpersand();
private static final GsonComponentSerializer GSON = GsonComponentSerializer.gson();
private static final CustomChatComponentRenderer RENDERER = new CustomChatComponentRenderer();
// https://github.com/kaboomserver/extras/blob/master/src/main/java/pw/kaboom/extras/modules/player/PlayerChat.java#L49C9-L81C26
private static final TextReplacementConfig URL_REPLACEMENT_CONFIG =
@ -71,8 +77,6 @@ public class CustomChat {
public CustomChat (MinecraftClient client) {
this.client = client;
reloadFormat();
}
public void init () {
@ -108,37 +112,19 @@ public class CustomChat {
timer.purge();
}
public void reloadFormat () {
this.format = GSON.serializeToTree(ChipmunkMod.CONFIG.customChat.format).toString();
}
public void chat (String message) {
final ClientPlayerEntity player = client.player;
if (player == null) return;
if (!enabled || !player.hasPermissionLevel(2) || !player.isCreative()) {
Chat.sendChatMessage(message, true);
return;
}
final Component styledMessage = LEGACY.deserialize(message)
.replaceText(URL_REPLACEMENT_CONFIG);
final String username = MinecraftClient.getInstance().getSession().getUsername();
final String sanitizedMessage = message
.replace("\\", "\\\\")
.replace("\"", "\\\"");
final String randomized = String.valueOf(Math.random());
final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacyAmpersand();
final Component messageWithColors = serializer.deserialize(message);
final Component completeMessage = messageWithColors.replaceText(URL_REPLACEMENT_CONFIG);
final String stringMessage = GSON.serialize(completeMessage).replace("MESSAGE", randomized);
final String key = ChipmunkMod.CONFIG.bots.chomens.formatKey;
final String hash = key != null ?
Hashing.sha256()
.hashString(key + total, StandardCharsets.UTF_8)
@ -148,29 +134,12 @@ public class CustomChat {
total++;
try {
// final MutablePlayerListEntry entry = Players.INSTANCE.getEntry(client.getNetworkHandler().getProfile().getId());
final CustomChatContext context = new CustomChatContext(player.getUuidAsString(), styledMessage,
Map.of("MESSAGE", message, "USERNAME", username, "HASH", hash));
final Component renderedFormat = RENDERER.render(ChipmunkMod.CONFIG.customChat.format, context)
.compact();
final String json = GSON.serialize(renderedFormat);
// final Component displayNameComponent = entry.displayName().asComponent();
// final String prefix = GsonComponentSerializer.gson().serialize(Component.join(JoinConfiguration.separator(Component.empty()), displayNameComponent.children().get(0)));
// final String displayName = GsonComponentSerializer.gson().serialize(Component.join(JoinConfiguration.separator(Component.empty()), displayNameComponent.children().get(1)));
// TODO: make this code not ohio code.,.,
String sanitizedFormat = format
// .replace("\"PREFIX\"", prefix)
// .replace("\"DISPLAYNAME\"", displayName)
.replace("USERNAME", username)
.replace("UUID", player.getUuidAsString())
.replace("HASH", hash)
.replace("\"extra\":[\"MESSAGE\"]", "\"extra\":[" + stringMessage + "]")
.replace("MESSAGE", sanitizedMessage)
.replace(randomized, "MESSAGE"); // ohio ohio
CommandCore.INSTANCE.run((KaboomCheck.INSTANCE.isKaboom ? "minecraft:tellraw @a " : "tellraw @a ") + sanitizedFormat);
} catch (Exception e) {
if (client.player == null) return;
client.player.sendMessage(Component.text(e.toString()).color(NamedTextColor.RED));
}
CommandCore.INSTANCE.run((KaboomCheck.INSTANCE.isKaboom ? "minecraft:tellraw @a " : "tellraw @a ") + json);
}
}

View file

@ -0,0 +1,95 @@
package land.chipmunk.chipmunkmod.modules.custom_chat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.SelectorComponent;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
// We don't do any translatable rendering here, but extending from that makes it easier to work with.
public final class CustomChatComponentRenderer extends TranslatableComponentRenderer<CustomChatContext> {
// Can't use this from super :(
private static final Set<Style.Merge> MERGES;
static {
final Set<Style.Merge> merges = EnumSet.allOf(Style.Merge.class);
merges.remove(Style.Merge.EVENTS);
MERGES = Collections.unmodifiableSet(merges);
}
@Override
protected @NotNull Component renderSelector(final @NotNull SelectorComponent component,
final @NotNull CustomChatContext context) {
final String pattern = component.pattern();
if (pattern.equals("@s")) {
final SelectorComponent.Builder builder = Component.selector()
.pattern(context.uuid());
return this.mergeStyleAndOptionallyDeepRender(component, builder, context);
}
return super.renderSelector(component, context);
}
@Override
protected @NotNull Component renderText(final @NotNull TextComponent component,
final @NotNull CustomChatContext context) {
final String content = component.content();
if (content.equals("MESSAGE")) {
return this.mergeStyle(component, context.message(), context);
}
final String arg = context.args().get(component.content());
if (arg != null) {
final TextComponent.Builder builder = Component.text()
.content(arg);
return this.mergeStyleAndOptionallyDeepRender(component, builder, context);
}
return super.renderText(component, context);
}
@SuppressWarnings("NonExtendableApiUsage") // we're not extending it silly
@Override
protected <B extends ComponentBuilder<?, ?>> void mergeStyle(final Component component, final B builder,
final CustomChatContext context) {
super.mergeStyle(component, builder, context);
// render clickEvent that may contain something like "MESSAGE"
// HoverEvent already handled by super
builder.clickEvent(this.mergeClickEvent(component.clickEvent(), context));
}
// super#mergeStyle requires a ComponentBuilder on mergeStyle, this does not
private Component mergeStyle(final Component root, final Component that, final CustomChatContext context) {
final Component result = that.mergeStyle(root, MERGES)
.clickEvent(mergeClickEvent(root.clickEvent(), context));
final @Nullable HoverEvent<?> hoverEvent = root.hoverEvent();
if (hoverEvent != null) {
return result.hoverEvent(hoverEvent.withRenderedValue(this, context));
}
return result;
}
private ClickEvent mergeClickEvent(final ClickEvent clickEvent, final CustomChatContext context) {
if (clickEvent == null) return null;
final String value = clickEvent.value();
final String arg = context.args().get(value);
if (arg == null) return clickEvent;
return ClickEvent.clickEvent(clickEvent.action(), arg);
}
}

View file

@ -0,0 +1,8 @@
package land.chipmunk.chipmunkmod.modules.custom_chat;
import net.kyori.adventure.text.Component;
import java.util.Map;
public record CustomChatContext(String uuid, Component message, Map<String, String> args) {
}

View file

@ -4,7 +4,7 @@ import com.mojang.brigadier.Command;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.config.Configuration;
import land.chipmunk.chipmunkmod.modules.Chat;
import land.chipmunk.chipmunkmod.modules.CustomChat;
import land.chipmunk.chipmunkmod.modules.custom_chat.CustomChat;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;

View file

@ -1,12 +1,14 @@
package land.chipmunk.chipmunkmod.util.configurate;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.renderer.ComponentRenderer;
import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializerCollection;
import org.spongepowered.configurate.transformation.TransformAction;
import java.util.Arrays;
@ -32,4 +34,15 @@ public final class ConfigurateUtilities {
return source.node(path);
}
public static<C> TransformAction componentTransformer(final ComponentRenderer<C> renderer, final C ctx) {
return (path, value) -> {
final Component originalComponent = value.get(Component.class);
if (originalComponent == null) return null;
final Component newComponent = renderer.render(originalComponent, ctx);
value.set(Component.class, newComponent);
return null;
};
}
}

View file

@ -14,7 +14,6 @@
"ElderGuardianAppearanceParticleMixin",
"TextMixin",
"TextSerializerMixin",
"CommandDispatcherMixin",
"SoundSystemMixin",
"TextFieldWidgetMixin"
],