feat: translate Message arguments in commands

This commit is contained in:
Johannes Frohnmeyer 2024-09-23 15:00:53 +02:00
parent 42e6b1a03f
commit 32c34f2a6f
Signed by: Johannes
GPG Key ID: E76429612C2929F4
8 changed files with 144 additions and 0 deletions

View File

@ -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<String> reconstitute(String source, CommandContextBuilder<CommandSource> results) {
StringBuilder builder = new StringBuilder();
for (ParsedCommandNode<CommandSource> node : results.getNodes()) {
switch (node.getNode()) {
case ArgumentCommandNode<?, ?> arg -> {
ParsedArgument<CommandSource, ?> 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));
}
}

View File

@ -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<CommandSource> parse(String command) {
throw new IllegalStateException("Mixin failed to apply");
}
@Unique private final AtomicReference<ParseResults<CommandSource>> 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<CommandSource> 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<CommandSource> googlechat$modifyArguments(ClientPlayNetworkHandler handler, String command) {
ParseResults<CommandSource> results = googlechat$lastResults.getAndSet(null);
if (results != null) return results;
return parse(command);
}
}

View File

@ -6,6 +6,7 @@
"client": [
"ChatScreenMixin",
"ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerMixin",
"MessageHandlerMixin"
],
"injectors": {

View File

@ -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();
}

View File

@ -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<ServerCommandSource> context, String name, CallbackInfoReturnable<Text> 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);
}
}

View File

@ -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",

View File

@ -21,6 +21,7 @@
"client": ["io.gitlab.jfronny.googlechat.client.GoogleChatClient"]
},
"mixins": [
"google-chat.mixins.json",
{
"config": "google-chat.client.mixins.json",
"environment": "client"

View File

@ -0,0 +1,12 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.googlechat.server.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"MessageArgumentTypeMixin"
],
"injectors": {
"defaultRequire": 1
}
}