-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add live chat translation (pending service)
- Loading branch information
Showing
14 changed files
with
416 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
src/main/java/com/lovetropics/extras/mixin/translation/FutureChainMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.lovetropics.extras.mixin.translation; | ||
|
||
import net.minecraft.util.FutureChain; | ||
import org.spongepowered.asm.mixin.Final; | ||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.Mutable; | ||
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.CallbackInfo; | ||
|
||
import java.util.concurrent.Executor; | ||
|
||
@Mixin(FutureChain.class) | ||
public class FutureChainMixin { | ||
@Shadow | ||
@Final | ||
@Mutable | ||
private Executor checkedExecutor; | ||
@Shadow | ||
private volatile boolean closed; | ||
|
||
@Inject(method = "<init>", at = @At("TAIL")) | ||
private void init(final Executor executor, final CallbackInfo ci) { | ||
// Fix for a race condition that we're exposing when a player disconnects | ||
checkedExecutor = task -> { | ||
if (!closed) { | ||
executor.execute(() -> { | ||
if (!closed) { | ||
task.run(); | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
src/main/java/com/lovetropics/extras/mixin/translation/MessageArgumentMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.lovetropics.extras.mixin.translation; | ||
|
||
import com.lovetropics.extras.translation.TranslatableChatMessage; | ||
import com.lovetropics.extras.translation.TranslationBundle; | ||
import com.lovetropics.extras.translation.TranslationService; | ||
import net.minecraft.commands.CommandSourceStack; | ||
import net.minecraft.commands.arguments.MessageArgument; | ||
import net.minecraft.network.chat.Component; | ||
import net.minecraft.network.chat.PlayerChatMessage; | ||
import net.minecraft.server.MinecraftServer; | ||
import net.minecraft.server.level.ServerPlayer; | ||
import net.minecraft.server.network.FilteredText; | ||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.Overwrite; | ||
import org.spongepowered.asm.mixin.Shadow; | ||
import org.spongepowered.asm.mixin.Unique; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
import java.util.function.Consumer; | ||
|
||
@Mixin(MessageArgument.class) | ||
public abstract class MessageArgumentMixin { | ||
@Shadow | ||
private static CompletableFuture<FilteredText> filterPlainText(final CommandSourceStack pSource, final PlayerChatMessage pMessage) { | ||
return null; | ||
} | ||
|
||
/** | ||
* @author Gegy | ||
* @reason also does not need to be, please FIXME! | ||
*/ | ||
@Overwrite | ||
private static void resolveSignedMessage(final Consumer<PlayerChatMessage> callback, final CommandSourceStack source, final PlayerChatMessage message) { | ||
final MinecraftServer server = source.getServer(); | ||
final CompletableFuture<FilteredText> filteredText = filterPlainText(source, message); | ||
final CompletableFuture<Component> decoratedText = server.getChatDecorator().decorate(source.getPlayer(), message.decoratedContent()); | ||
final CompletableFuture<TranslationBundle> translations = ltextras$translate(source, message); | ||
source.getChatMessageChainer().append(executor -> | ||
CompletableFuture.allOf(filteredText, decoratedText, translations).thenAcceptAsync(unused -> { | ||
final PlayerChatMessage decoratedMessage = message.withUnsignedContent(decoratedText.join()).filter(filteredText.join().mask()); | ||
((TranslatableChatMessage) (Object) decoratedMessage).ltextras$addTranslations(translations.join()); | ||
callback.accept(decoratedMessage); | ||
}, executor) | ||
); | ||
} | ||
|
||
@Unique | ||
private static CompletableFuture<TranslationBundle> ltextras$translate(final CommandSourceStack source, final PlayerChatMessage message) { | ||
final ServerPlayer player = source.getPlayer(); | ||
if (player == null) { | ||
return CompletableFuture.completedFuture(TranslationBundle.EMPTY); | ||
} | ||
return TranslationService.INSTANCE.translate(player.getLanguage(), message.signedContent()); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
src/main/java/com/lovetropics/extras/mixin/translation/PlayerChatMessageMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.lovetropics.extras.mixin.translation; | ||
|
||
import com.lovetropics.extras.translation.TranslatableChatMessage; | ||
import com.lovetropics.extras.translation.TranslationBundle; | ||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; | ||
import net.minecraft.network.chat.Component; | ||
import net.minecraft.network.chat.PlayerChatMessage; | ||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.Shadow; | ||
import org.spongepowered.asm.mixin.Unique; | ||
|
||
import java.util.Map; | ||
|
||
@Mixin(PlayerChatMessage.class) | ||
public abstract class PlayerChatMessageMixin implements TranslatableChatMessage { | ||
@Shadow | ||
public abstract String signedContent(); | ||
|
||
@Unique | ||
private final Map<String, String> ltextras$translations = new Object2ObjectOpenHashMap<>(); | ||
|
||
@Override | ||
public void ltextras$addTranslations(final TranslationBundle bundle) { | ||
ltextras$translations.putAll(bundle.stringsByLanguage()); | ||
} | ||
|
||
@Override | ||
public PlayerChatMessage ltextras$translate(final String language) { | ||
final PlayerChatMessage self = (PlayerChatMessage) (Object) this; | ||
final String translation = ltextras$translations.get(language); | ||
if (translation != null && !translation.equals(signedContent())) { | ||
return self.withUnsignedContent(Component.literal(translation)); | ||
} | ||
return self; | ||
} | ||
|
||
@Override | ||
public boolean ltextras$hasTranslationFor(final String language) { | ||
final String translation = ltextras$translations.get(language); | ||
return translation != null && !translation.equals(signedContent()); | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
...main/java/com/lovetropics/extras/mixin/translation/ServerGamePacketListenerImplMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package com.lovetropics.extras.mixin.translation; | ||
|
||
import com.lovetropics.extras.ExtraLangKeys; | ||
import com.lovetropics.extras.translation.TranslatableChatMessage; | ||
import com.lovetropics.extras.translation.TranslationBundle; | ||
import com.lovetropics.extras.translation.TranslationService; | ||
import net.minecraft.network.chat.*; | ||
import net.minecraft.network.protocol.game.ServerboundChatPacket; | ||
import net.minecraft.server.MinecraftServer; | ||
import net.minecraft.server.level.ServerPlayer; | ||
import net.minecraft.server.network.FilteredText; | ||
import net.minecraft.server.network.ServerGamePacketListenerImpl; | ||
import net.minecraft.util.FutureChain; | ||
import net.minecraftforge.common.ForgeHooks; | ||
import org.spongepowered.asm.mixin.*; | ||
import org.spongepowered.asm.mixin.injection.At; | ||
import org.spongepowered.asm.mixin.injection.ModifyVariable; | ||
|
||
import java.time.Instant; | ||
import java.util.Optional; | ||
import java.util.concurrent.CompletableFuture; | ||
|
||
@Mixin(ServerGamePacketListenerImpl.class) | ||
public abstract class ServerGamePacketListenerImplMixin { | ||
@Unique | ||
private static final Component TRANSLATED_MARKER = Component.literal(" \uE041").withStyle(style -> style.withHoverEvent( | ||
new HoverEvent(HoverEvent.Action.SHOW_TEXT, ExtraLangKeys.MESSAGE_TRANSLATED.get()) | ||
)); | ||
|
||
@Shadow | ||
@Final | ||
private MinecraftServer server; | ||
@Shadow | ||
public ServerPlayer player; | ||
@Shadow | ||
@Final | ||
private FutureChain chatMessageChain; | ||
|
||
@Shadow | ||
private static boolean isChatMessageIllegal(final String pMessage) { | ||
return false; | ||
} | ||
|
||
@Shadow | ||
public abstract void disconnect(final Component pTextComponent); | ||
|
||
@Shadow | ||
protected abstract Optional<LastSeenMessages> tryHandleChat(final String pMessage, final Instant pTimestamp, final LastSeenMessages.Update pUpdate); | ||
|
||
@Shadow | ||
protected abstract PlayerChatMessage getSignedMessage(final ServerboundChatPacket pPacket, final LastSeenMessages pLastSeenMessages) throws SignedMessageChain.DecodeException; | ||
|
||
@Shadow | ||
protected abstract CompletableFuture<FilteredText> filterTextPacket(final String pText); | ||
|
||
@Shadow | ||
protected abstract void handleMessageDecodeFailure(final SignedMessageChain.DecodeException pException); | ||
|
||
@Shadow | ||
protected abstract void broadcastChatMessage(final PlayerChatMessage pMessage); | ||
|
||
/** | ||
* @author Gegy | ||
* @reason No good reason, please FIXME! | ||
*/ | ||
@Overwrite | ||
public void handleChat(final ServerboundChatPacket packet) { | ||
if (isChatMessageIllegal(packet.message())) { | ||
disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); | ||
return; | ||
} | ||
final Optional<LastSeenMessages> lastSeen = tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages()); | ||
if (lastSeen.isPresent()) { | ||
server.submit(() -> { | ||
final PlayerChatMessage message; | ||
try { | ||
message = getSignedMessage(packet, lastSeen.get()); | ||
} catch (final SignedMessageChain.DecodeException e) { | ||
handleMessageDecodeFailure(e); | ||
return; | ||
} | ||
|
||
final CompletableFuture<FilteredText> filteredText = filterTextPacket(message.signedContent()); | ||
final CompletableFuture<Component> decoratedText = ForgeHooks.getServerChatSubmittedDecorator().decorate(player, message.decoratedContent()); | ||
// Also request translations at this point, but make sure to not change the order that we distribute chat messages | ||
final CompletableFuture<TranslationBundle> translations = TranslationService.INSTANCE.translate(player.getLanguage(), message.signedContent()); | ||
chatMessageChain.append(executor -> CompletableFuture.allOf(filteredText, decoratedText, translations).thenAcceptAsync(unused -> { | ||
final Component decoratedContent = decoratedText.join(); | ||
if (decoratedContent == null) { | ||
return; | ||
} | ||
final PlayerChatMessage decoratedMessage = message.withUnsignedContent(decoratedContent).filter(filteredText.join().mask()); | ||
((TranslatableChatMessage) (Object) decoratedMessage).ltextras$addTranslations(translations.join()); | ||
broadcastChatMessage(decoratedMessage); | ||
}, executor)); | ||
}); | ||
} | ||
} | ||
|
||
@ModifyVariable(method = "sendPlayerChatMessage", at = @At("HEAD"), argsOnly = true) | ||
private PlayerChatMessage modifyChatMessage(final PlayerChatMessage message) { | ||
return ((TranslatableChatMessage) (Object) message).ltextras$translate(player.getLanguage()); | ||
} | ||
|
||
@ModifyVariable(method = "sendPlayerChatMessage", at = @At("HEAD"), argsOnly = true) | ||
private ChatType.Bound modifyChatMessageType(final ChatType.Bound chatType, final PlayerChatMessage message) { | ||
if (((TranslatableChatMessage) (Object) message).ltextras$hasTranslationFor(player.getLanguage())) { | ||
return new ChatType.Bound(chatType.chatType(), chatType.name().copy().append(TRANSLATED_MARKER), chatType.targetName()); | ||
} | ||
return chatType; | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/com/lovetropics/extras/translation/TranslatableChatMessage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.lovetropics.extras.translation; | ||
|
||
import net.minecraft.network.chat.PlayerChatMessage; | ||
|
||
public interface TranslatableChatMessage { | ||
void ltextras$addTranslations(TranslationBundle bundle); | ||
|
||
PlayerChatMessage ltextras$translate(String language); | ||
|
||
boolean ltextras$hasTranslationFor(String language); | ||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/com/lovetropics/extras/translation/TranslatableLanguage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.lovetropics.extras.translation; | ||
|
||
import net.minecraft.util.StringRepresentable; | ||
|
||
public enum TranslatableLanguage implements StringRepresentable { | ||
ENGLISH("en_us"), | ||
SPANISH("es_es"), | ||
FRENCH("fr_fr"), | ||
; | ||
|
||
public static final EnumCodec<TranslatableLanguage> CODEC = StringRepresentable.fromEnum(TranslatableLanguage::values); | ||
|
||
private final String key; | ||
|
||
TranslatableLanguage(final String key) { | ||
this.key = key; | ||
} | ||
|
||
@Override | ||
public String getSerializedName() { | ||
return key; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/lovetropics/extras/translation/TranslationBundle.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.lovetropics.extras.translation; | ||
|
||
import java.util.Map; | ||
|
||
public record TranslationBundle(Map<String, String> stringsByLanguage) { | ||
public static final TranslationBundle EMPTY = new TranslationBundle(Map.of()); | ||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/com/lovetropics/extras/translation/TranslationMode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.lovetropics.extras.translation; | ||
|
||
import net.minecraft.util.StringRepresentable; | ||
|
||
public enum TranslationMode implements StringRepresentable { | ||
EN_ES("en/es"), | ||
ES_EN("es/en"), | ||
EN_FR("en/fr"), | ||
FR_EN("fr/en"), | ||
; | ||
|
||
private final String key; | ||
|
||
TranslationMode(final String key) { | ||
this.key = key; | ||
} | ||
|
||
@Override | ||
public String getSerializedName() { | ||
return key; | ||
} | ||
} |
Oops, something went wrong.