diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatHud.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatHud.java index aa3a87455369..3e59e4256ba3 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatHud.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatHud.java @@ -20,26 +20,31 @@ import com.llamalad7.mixinextras.sugar.Local; import net.ccbluex.liquidbounce.features.module.modules.misc.betterchat.ModuleBetterChat; +import net.ccbluex.liquidbounce.interfaces.ChatHudAddition; import net.ccbluex.liquidbounce.interfaces.ChatMessageAddition; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.ChatHud; import net.minecraft.client.gui.hud.ChatHudLine; import net.minecraft.text.OrderedText; import org.spongepowered.asm.mixin.Final; 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.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArgs; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; import java.util.List; @Mixin(ChatHud.class) -public abstract class MixinChatHud { +public abstract class MixinChatHud implements ChatHudAddition { @Shadow @Final - private List visibleMessages; + public List visibleMessages; @Shadow public abstract boolean isChatFocused(); @@ -57,6 +62,12 @@ public abstract class MixinChatHud { @Final public List messages; + @Shadow + public abstract int getWidth(); + + @Unique + private int chatY = -1; + /** * Spoofs the message size to be empty to avoid deletion. */ @@ -85,7 +96,6 @@ public void hookClear(boolean clearHistory, CallbackInfo ci) { * Modifies {@link ChatHud#addVisibleMessage(ChatHudLine)} so, that the id is * forwarded and if {@link ModuleBetterChat} is enabled, older lines won't be removed. */ - @SuppressWarnings("JavadocReference") @Inject(method = "addVisibleMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;isChatFocused()Z", shift = At.Shift.BEFORE), cancellable = true) public void hookAddVisibleMessage(ChatHudLine message, CallbackInfo ci, @Local List list) { var focused = isChatFocused(); @@ -117,4 +127,32 @@ public void hookAddVisibleMessage(ChatHudLine message, CallbackInfo ci, @Local L ci.cancel(); } + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;getLineHeight()I", ordinal = 0)) + public void hookStoreChatY(DrawContext context, int currentTick, int mouseX, int mouseY, boolean focused, CallbackInfo ci, @Local(ordinal = 7) int m) { + this.chatY = m; + } + + @ModifyArgs(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;fill(IIIII)V", ordinal = 0)) + private void modifyArgs( + Args args, + @Local(ordinal = 1, argsOnly = true) int mouseX, + @Local(ordinal = 2, argsOnly = true) int mouseY + ) { + if(!(ModuleBetterChat.INSTANCE.getRunning() && ModuleBetterChat.Copy.INSTANCE.getRunning() && ModuleBetterChat.Copy.INSTANCE.getHighlight())) { + return; + } + + var hovering = mouseX >= 0 && mouseX <= ((int) args.get(2)) -4 && + mouseY >= ((int)args.get(1)+1) && mouseY <= ((int)args.get(3)); + + if (hovering) { + args.set(4, 140 << 24); + } + } + + @Override + public int liquidbounce_getChatY() { + return chatY; + } } + diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatHudAccessor.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatHudAccessor.java new file mode 100644 index 000000000000..03d74577b4b4 --- /dev/null +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatHudAccessor.java @@ -0,0 +1,28 @@ +package net.ccbluex.liquidbounce.injection.mixins.minecraft.gui; + +import net.minecraft.client.gui.hud.ChatHud; +import net.minecraft.client.gui.hud.ChatHudLine; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.List; + +/** + * @author 00101110001100010111000101111 + * @since 12/18/2024 + **/ +@Mixin(ChatHud.class) +public interface MixinChatHudAccessor { + @Invoker("toChatLineY") + double invokeToChatLineY(double y); + + @Invoker("getMessageIndex") + int invokeGetMessageIndex(double chatLineX, double chatLineY); + + @Invoker("getLineHeight") + int invokeGetLineHeight(); + + @Accessor + List getVisibleMessages(); +} diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatScreen.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatScreen.java index 202e073322a0..60f55d8f5e1d 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatScreen.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatScreen.java @@ -21,13 +21,26 @@ import net.ccbluex.liquidbounce.event.EventManager; import net.ccbluex.liquidbounce.event.events.ChatSendEvent; +import net.ccbluex.liquidbounce.event.events.NotificationEvent; +import net.ccbluex.liquidbounce.features.module.modules.misc.betterchat.ModuleBetterChat; +import net.ccbluex.liquidbounce.interfaces.ChatHudAddition; +import net.ccbluex.liquidbounce.utils.client.ClientUtilsKt; +import net.minecraft.client.gui.hud.ChatHud; +import net.minecraft.client.gui.hud.ChatHudLine; import net.minecraft.client.gui.screen.ChatScreen; +import net.minecraft.text.CharacterVisitor; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; 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.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.ArrayList; +import java.util.List; + @Mixin(ChatScreen.class) public abstract class MixinChatScreen extends MixinScreen { @@ -56,4 +69,111 @@ private void handleChatMessage(String chatText, boolean addToHistory, CallbackIn } } + @Inject(method = "mouseClicked", at = @At("HEAD")) + private void hookMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + if (!(ModuleBetterChat.INSTANCE.getRunning() && ModuleBetterChat.Copy.INSTANCE.getRunning())) { + return; + } + + int[] activeMessage = getActiveMessage((int)mouseX, (int)mouseY); + + if (activeMessage == null) { + return; + } + + ChatHud chatHud = this.client.inGameHud.getChatHud(); + MixinChatHudAccessor accessor = (MixinChatHudAccessor) chatHud; + + List visibleMessages = accessor.getVisibleMessages(); + List messageParts = new ArrayList<>(); + messageParts.add(visibleMessages.get(activeMessage[3])); + + for (int index = activeMessage[3] + 1; index < visibleMessages.size(); index++) { + if (visibleMessages.get(index).endOfEntry()) + break; + + messageParts.addFirst(visibleMessages.get(index)); + } + + if (messageParts.isEmpty()) + return; + + copyMessage(messageParts, button); + } + + @Unique + private void copyMessage(List messageParts, int button) { + final StringBuilder builder = new StringBuilder(); + + CharacterVisitor visitor = (index, style, codePoint) -> { + builder.append((char) codePoint); + return true; + }; + + for (ChatHudLine.Visible line : messageParts) { + line.content().accept(visitor); + } + + if (isPressed(GLFW.GLFW_KEY_LEFT_SHIFT, GLFW.GLFW_KEY_RIGHT_SHIFT) && button == GLFW.GLFW_MOUSE_BUTTON_1) { + client.keyboard.setClipboard(builder.toString()); + + if (ModuleBetterChat.Copy.INSTANCE.getNotification()) { + ClientUtilsKt.notification( + "ChatCopy", + "The line is copied", + NotificationEvent.Severity.SUCCESS + ); + } + } else if (button == GLFW.GLFW_MOUSE_BUTTON_2) { + if (client.currentScreen instanceof ChatScreen chat) { + ((MixinChatScreenAccessor) chat).getChatField().setText(builder.toString()); + } + } + } + + @Unique + private boolean isPressed(int... keys) { + for (int key : keys) { + if (GLFW.glfwGetKey(client.getWindow().getHandle(), key) == GLFW.GLFW_PRESS) { + return true; + } + } + + return false; + } + + // [0] - y, + // [1] - width, + // [2] - height, + // [3] - (message) index + @Unique + private int @Nullable [] getActiveMessage(int mouseX, int mouseY) { + ChatHud chatHud = this.client.inGameHud.getChatHud(); + MixinChatHudAccessor accessor = (MixinChatHudAccessor) chatHud; + ChatHudAddition addition = (ChatHudAddition) chatHud; + + float chatScale = (float) chatHud.getChatScale(); + int chatLineY = (int) accessor.invokeToChatLineY(mouseY); + int messageIndex = accessor.invokeGetMessageIndex(0, chatLineY); + int buttonX = (int) (chatHud.getWidth() + 14 * chatScale); + + if (messageIndex == -1 || mouseX > buttonX + 14 * chatScale) + return null; + + int chatY = addition.liquidbounce_getChatY(); + + int buttonSize = (int) (9 * chatScale); + int lineHeight = accessor.invokeGetLineHeight(); + int scaledButtonY = chatY - (chatLineY + 1) * lineHeight + (int) Math.ceil((lineHeight - 9) / 2.0); + float buttonY = scaledButtonY * chatScale; + + boolean hovering = mouseX >= 0 && mouseX <= buttonX && mouseY >= buttonY && mouseY <= buttonY + buttonSize; + + if (hovering) { + return new int[]{(int) buttonY, buttonX, buttonSize, messageIndex}; + } else { + return null; + } + } } + diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatScreenAccessor.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatScreenAccessor.java new file mode 100644 index 000000000000..ba3a7e3a15bb --- /dev/null +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinChatScreenAccessor.java @@ -0,0 +1,12 @@ +package net.ccbluex.liquidbounce.injection.mixins.minecraft.gui; + +import net.minecraft.client.gui.screen.ChatScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ChatScreen.class) +public interface MixinChatScreenAccessor { + @Accessor("chatField") + TextFieldWidget getChatField(); +} diff --git a/src/main/java/net/ccbluex/liquidbounce/interfaces/ChatHudAddition.java b/src/main/java/net/ccbluex/liquidbounce/interfaces/ChatHudAddition.java new file mode 100644 index 000000000000..dd59b8e3e82b --- /dev/null +++ b/src/main/java/net/ccbluex/liquidbounce/interfaces/ChatHudAddition.java @@ -0,0 +1,5 @@ +package net.ccbluex.liquidbounce.interfaces; + +public interface ChatHudAddition { + int liquidbounce_getChatY(); +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/betterchat/ModuleBetterChat.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/betterchat/ModuleBetterChat.kt index 2e246aa71705..439f3cace2e7 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/betterchat/ModuleBetterChat.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/betterchat/ModuleBetterChat.kt @@ -66,7 +66,16 @@ object ModuleBetterChat : ClientModule("BetterChat", Category.MISC, aliases = ar private val forceUnicodeChat by boolean("ForceUnicodeChat", false) init { - tree(AntiSpam) + treeAll( + AntiSpam, + Copy + ) + } + + + object Copy : ToggleableConfigurable(this, "Copy", true) { + val notification by boolean("Notificate", true) + val highlight by boolean("Highlight", true) } var antiChatClearPaused = false diff --git a/src/main/resources/liquidbounce.mixins.json b/src/main/resources/liquidbounce.mixins.json index 179325edce29..66a65f214276 100644 --- a/src/main/resources/liquidbounce.mixins.json +++ b/src/main/resources/liquidbounce.mixins.json @@ -41,8 +41,10 @@ "minecraft.fluid.MixinFlowableFluid", "minecraft.gui.MixinBossBarHud", "minecraft.gui.MixinChatHud", + "minecraft.gui.MixinChatHudAccessor", "minecraft.gui.MixinChatInputSuggestor", "minecraft.gui.MixinChatScreen", + "minecraft.gui.MixinChatScreenAccessor", "minecraft.gui.MixinHandledScreen", "minecraft.gui.MixinInGameHud", "minecraft.gui.MixinPlayerListHud",