diff --git a/src/client/java/io/gitlab/jfronny/googlechat/client/GoogleChatClient.java b/src/client/java/io/gitlab/jfronny/googlechat/client/GoogleChatClient.java index 39381cd..6560f85 100644 --- a/src/client/java/io/gitlab/jfronny/googlechat/client/GoogleChatClient.java +++ b/src/client/java/io/gitlab/jfronny/googlechat/client/GoogleChatClient.java @@ -1,5 +1,11 @@ package io.gitlab.jfronny.googlechat.client; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedArgument; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; import io.gitlab.jfronny.googlechat.GoogleChat; import io.gitlab.jfronny.googlechat.GoogleChatConfig; import io.gitlab.jfronny.libjf.config.api.v2.ConfigHolder; @@ -10,8 +16,12 @@ import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.MessageArgumentType; import org.jetbrains.annotations.NotNull; +import java.util.Optional; + public class GoogleChatClient implements ClientModInitializer { @Override public void onInitializeClient() { @@ -38,4 +48,31 @@ public class GoogleChatClient implements ClientModInitializer { } }); } + + public static Optional reconstitute(String source, CommandContextBuilder results) { + StringBuilder builder = new StringBuilder(); + for (ParsedCommandNode node : results.getNodes()) { + switch (node.getNode()) { + case ArgumentCommandNode arg -> { + ParsedArgument pa = results.getArguments().get(arg.getName()); + String datum = pa.getRange().get(source); + if (pa.getResult() instanceof MessageArgumentType.MessageFormat fmt) builder.append(fmt.contents()); + else builder.append(datum); + builder.append(' '); + } + case LiteralCommandNode lit -> { + builder.append(lit.getLiteral()); + builder.append(' '); + } + case RootCommandNode root -> {} + default -> { + return Optional.empty(); + } + } + } + if (GoogleChatConfig.Advanced.debugLogs) { + GoogleChat.LOGGER.info("Reconstituted command: {0} from {1}", builder.substring(0, builder.length() - 1), source); + } + return Optional.of(builder.substring(0, builder.length() - 1)); + } } diff --git a/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ClientPlayNetworkHandlerMixin.java b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 0000000..614019d --- /dev/null +++ b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,56 @@ +package io.gitlab.jfronny.googlechat.client.mixin; + +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.ParsedArgument; +import io.gitlab.jfronny.googlechat.GoogleChat; +import io.gitlab.jfronny.googlechat.GoogleChatConfig; +import io.gitlab.jfronny.googlechat.TranslationDirection; +import io.gitlab.jfronny.googlechat.client.GoogleChatClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.MessageArgumentType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.concurrent.atomic.AtomicReference; + +@Mixin(ClientPlayNetworkHandler.class) +public class ClientPlayNetworkHandlerMixin { + @Shadow private ParseResults parse(String command) { + throw new IllegalStateException("Mixin failed to apply"); + } + + @Unique private final AtomicReference> googlechat$lastResults = new AtomicReference<>(null); + + @ModifyVariable(method = "sendChatCommand(Ljava/lang/String;)V", at = @At("HEAD"), ordinal = 0, argsOnly = true) + public String translateCommand(String command) { + ParseResults results = parse(command); + if (!GoogleChatConfig.General.enabled || !GoogleChatConfig.Advanced.translateMessageArguments || !results.getExceptions().isEmpty()) return command; + final boolean[] modified = {false}; + results.getContext().getArguments().replaceAll((key, value) -> { + if (value.getResult() instanceof MessageArgumentType.MessageFormat fmt) { + if (fmt.selectors().length != 0) return value; // Selectors contain position information, which this would break + fmt = new MessageArgumentType.MessageFormat(GoogleChat.translateIfNeeded(fmt.contents(), TranslationDirection.C2S, true), new MessageArgumentType.MessageSelector[0]); + modified[0] = true; + return new ParsedArgument<>(value.getRange().getStart(), value.getRange().getEnd(), fmt); + } + return value; + }); + if (!modified[0]) return command; + return GoogleChatClient.reconstitute(command, results.getContext()).map(s -> { + googlechat$lastResults.set(results); + return s; + }).orElse(command); + } + + @Redirect(method = "sendChatCommand(Ljava/lang/String;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;parse(Ljava/lang/String;)Lcom/mojang/brigadier/ParseResults;")) + public ParseResults googlechat$modifyArguments(ClientPlayNetworkHandler handler, String command) { + ParseResults results = googlechat$lastResults.getAndSet(null); + if (results != null) return results; + return parse(command); + } +} diff --git a/src/client/resources/google-chat.client.mixins.json b/src/client/resources/google-chat.client.mixins.json index 331889b..f452cfa 100644 --- a/src/client/resources/google-chat.client.mixins.json +++ b/src/client/resources/google-chat.client.mixins.json @@ -6,6 +6,7 @@ "client": [ "ChatScreenMixin", "ClientPlayerEntityMixin", + "ClientPlayNetworkHandlerMixin", "MessageHandlerMixin" ], "injectors": { diff --git a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java index bf43653..5681777 100644 --- a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java +++ b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java @@ -52,6 +52,7 @@ public class GoogleChatConfig { public static class Advanced { @Entry(min = 1, max = 1024) public static int cacheSize = 256; @Entry public static boolean async = true; + @Entry public static boolean translateMessageArguments = true; @Entry public static boolean debugLogs = FabricLoader.getInstance().isDevelopmentEnvironment(); } diff --git a/src/main/java/io/gitlab/jfronny/googlechat/server/mixin/MessageArgumentTypeMixin.java b/src/main/java/io/gitlab/jfronny/googlechat/server/mixin/MessageArgumentTypeMixin.java new file mode 100644 index 0000000..e2b392a --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/googlechat/server/mixin/MessageArgumentTypeMixin.java @@ -0,0 +1,34 @@ +package io.gitlab.jfronny.googlechat.server.mixin; + +import com.mojang.brigadier.context.CommandContext; +import io.gitlab.jfronny.googlechat.GoogleChatConfig; +import io.gitlab.jfronny.googlechat.TranslationDirection; +import io.gitlab.jfronny.googlechat.server.TranslatableContainer; +import net.minecraft.command.argument.MessageArgumentType; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; +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; + +@Mixin(MessageArgumentType.class) +public class MessageArgumentTypeMixin { + @Inject(method = "getMessage(Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/text/Text;", at = @At("TAIL"), cancellable = true) + private static void modifyMessage(CommandContext context, String name, CallbackInfoReturnable cir) { + if (!GoogleChatConfig.General.enabled || !GoogleChatConfig.Advanced.translateMessageArguments) return; + Text message = cir.getReturnValue(); + if (context.getSource().getPlayer() != null) { // Client messages should first be translated to the server language + if (TranslationDirection.C2S.hasTarget()) { + if (TranslationDirection.S2C.hasTarget()) { + // Do not translate back and forth + return; + } + } + message = TranslatableContainer.translateAndLog(message, TranslationDirection.C2S); + } + // All messages should be translated to the client language before sending + message = TranslatableContainer.translateAndLog(message, TranslationDirection.S2C); + cir.setReturnValue(message); + } +} diff --git a/src/main/resources/assets/google-chat/lang/en_us.json b/src/main/resources/assets/google-chat/lang/en_us.json index da6f6eb..cf095e1 100644 --- a/src/main/resources/assets/google-chat/lang/en_us.json +++ b/src/main/resources/assets/google-chat/lang/en_us.json @@ -25,6 +25,8 @@ "google-chat.jfconfig.advanced.cacheSize.tooltips": "The size of each message cache. Since there are six caches, the actual size will be six times this.", "google-chat.jfconfig.advanced.async": "Async", "google-chat.jfconfig.advanced.async.tooltips": "Whether to asynchronously process messages. Should prevent some stutters, but might cause other issues. Disable if you no longer receive messages", + "google-chat.jfconfig.advanced.translateMessageArguments": "Translate MessageArguments", + "google-chat.jfconfig.advanced.translateMessageArguments.tooltips": "Whether to translate arguments of commands designated as being messages", "google-chat.jfconfig.advanced.debugLogs": "Debug Logs", "google-chat.jfconfig.advanced.debugLogs.tooltips": "Log additional information about message processing. Useful for debugging", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 55f1ba5..f8394df 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -21,6 +21,7 @@ "client": ["io.gitlab.jfronny.googlechat.client.GoogleChatClient"] }, "mixins": [ + "google-chat.mixins.json", { "config": "google-chat.client.mixins.json", "environment": "client" diff --git a/src/main/resources/google-chat.mixins.json b/src/main/resources/google-chat.mixins.json new file mode 100644 index 0000000..83cca89 --- /dev/null +++ b/src/main/resources/google-chat.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "io.gitlab.jfronny.googlechat.server.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "MessageArgumentTypeMixin" + ], + "injectors": { + "defaultRequire": 1 + } +}