diff --git a/src/main/java/org/polyfrost/chatting/hook/ChatLineHook.java b/src/main/java/org/polyfrost/chatting/hook/ChatLineHook.java index 22f180c..5e5b37a 100644 --- a/src/main/java/org/polyfrost/chatting/hook/ChatLineHook.java +++ b/src/main/java/org/polyfrost/chatting/hook/ChatLineHook.java @@ -5,11 +5,15 @@ import java.lang.ref.WeakReference; import java.util.HashSet; +import java.util.List; public interface ChatLineHook { HashSet> chatLines = new HashSet<>(); boolean isDetected(); void setDetected(boolean detected); + List getChildren(); + long getTimestamp(); + void setTimestamp(long timestamp); NetworkPlayerInfo getPlayerInfo(); void setPlayerInfo(NetworkPlayerInfo playerInfo); NetworkPlayerInfo getDetectedPlayerInfo(); diff --git a/src/main/java/org/polyfrost/chatting/mixin/ChatLineMixin.java b/src/main/java/org/polyfrost/chatting/mixin/ChatLineMixin.java index cde09ea..6d137de 100644 --- a/src/main/java/org/polyfrost/chatting/mixin/ChatLineMixin.java +++ b/src/main/java/org/polyfrost/chatting/mixin/ChatLineMixin.java @@ -17,6 +17,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; @Mixin(ChatLine.class) public class ChatLineMixin implements ChatLineHook { @@ -26,6 +28,23 @@ public class ChatLineMixin implements ChatLineHook { private NetworkPlayerInfo detectedPlayerInfo; private static long lastUniqueId = 0; private long uniqueId = 0; + private long timestamp; + private List children = new ArrayList<>(); + + @Override + public List getChildren() { + return children; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } @Inject(method = "", at = @At("RETURN")) private void onInit(int i, IChatComponent iChatComponent, int j, CallbackInfo ci) { diff --git a/src/main/java/org/polyfrost/chatting/mixin/GuiChatMixin.java b/src/main/java/org/polyfrost/chatting/mixin/GuiChatMixin.java index 8d55e0a..dc95fa0 100644 --- a/src/main/java/org/polyfrost/chatting/mixin/GuiChatMixin.java +++ b/src/main/java/org/polyfrost/chatting/mixin/GuiChatMixin.java @@ -2,13 +2,6 @@ import cc.polyfrost.oneconfig.libs.universal.UDesktop; import cc.polyfrost.oneconfig.libs.universal.UResolution; -import org.polyfrost.chatting.chat.*; -import org.polyfrost.chatting.config.ChattingConfig; -import org.polyfrost.chatting.gui.components.ClearButton; -import org.polyfrost.chatting.gui.components.ScreenshotButton; -import org.polyfrost.chatting.gui.components.SearchButton; -import org.polyfrost.chatting.hook.ChatLineHook; -import org.polyfrost.chatting.hook.GuiNewChatHook; import com.google.common.collect.Lists; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ChatLine; @@ -19,8 +12,13 @@ import net.minecraftforge.fml.client.config.GuiUtils; import org.apache.commons.lang3.StringUtils; import org.lwjgl.input.Mouse; -import org.polyfrost.chatting.chat.ChatSearchingManager; -import org.polyfrost.chatting.chat.ChatShortcuts; +import org.polyfrost.chatting.chat.*; +import org.polyfrost.chatting.config.ChattingConfig; +import org.polyfrost.chatting.gui.components.ClearButton; +import org.polyfrost.chatting.gui.components.ScreenshotButton; +import org.polyfrost.chatting.gui.components.SearchButton; +import org.polyfrost.chatting.hook.ChatLineHook; +import org.polyfrost.chatting.hook.GuiNewChatHook; import org.polyfrost.chatting.utils.ModCompatHooks; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -72,6 +70,11 @@ private void init(CallbackInfo ci) { } } + @Inject(method = "onGuiClosed", at = @At("HEAD")) + private void onGuiClosedHook(CallbackInfo ci) { + ModCompatHooks.setHoveredText(null); + } + @Inject(method = "updateScreen", at = @At("HEAD")) private void updateScreen(CallbackInfo ci) { if (ChattingConfig.INSTANCE.getChatSearch() && searchButton.isEnabled()) { @@ -95,6 +98,8 @@ private void keyTyped(char typedChar, int keyCode, CallbackInfo ci) { @Inject(method = "drawScreen", at = @At("HEAD")) private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) { GuiNewChatHook hook = ((GuiNewChatHook) Minecraft.getMinecraft().ingameGUI.getChatGUI()); + ChatLine hoveredLine = hook.getHoveredLine(Mouse.getY()); + ModCompatHooks.setHoveredText(hoveredLine); float f = mc.ingameGUI.getChatGUI().getChatScale(); int x = MathHelper.floor_float((float) mouseX / f); if (hook.isHovering() && (hook.getRight() + ModCompatHooks.getXOffset() + 3) <= x && (hook.getRight() + ModCompatHooks.getXOffset()) + 13 > x) { diff --git a/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMapMixin.java b/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMapMixin.java index ca1c0df..2d65f7d 100644 --- a/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMapMixin.java +++ b/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMapMixin.java @@ -3,6 +3,7 @@ import net.minecraft.client.gui.ChatLine; import net.minecraft.client.gui.GuiNewChat; import net.minecraft.util.IChatComponent; +import org.polyfrost.chatting.hook.ChatLineHook; import org.polyfrost.chatting.hook.GuiNewChatHook; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -12,6 +13,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import tv.twitch.chat.Chat; import java.util.ArrayList; import java.util.HashMap; @@ -50,6 +52,9 @@ private void handleDrawnLineRemoved(IChatComponent chatComponent, int chatLineId private void handleLineAdded(IChatComponent chatComponent, int chatLineId, int updateCounter, boolean displayOnly, CallbackInfo ci) { if (!displayOnly) { ChatLine masterLine = chatLines.get(0); + ChatLineHook masterHook = (ChatLineHook) masterLine; + masterHook.setTimestamp(System.currentTimeMillis()); + masterHook.getChildren().addAll(tempDrawnLines); for (ChatLine tempDrawnLine : tempDrawnLines) drawnToFull.put(tempDrawnLine, masterLine); }else { lastTempLine = null; diff --git a/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin.java b/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin.java index 4733ab6..429270a 100644 --- a/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin.java +++ b/src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin.java @@ -3,7 +3,13 @@ import cc.polyfrost.oneconfig.libs.universal.UMouse; import cc.polyfrost.oneconfig.libs.universal.UResolution; import cc.polyfrost.oneconfig.utils.Notifications; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.*; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; +import net.minecraft.util.MathHelper; +import net.minecraft.util.ResourceLocation; import org.polyfrost.chatting.Chatting; import org.polyfrost.chatting.chat.ChatSearchingManager; import org.polyfrost.chatting.config.ChattingConfig; @@ -11,12 +17,6 @@ import org.polyfrost.chatting.hook.GuiNewChatHook; import org.polyfrost.chatting.utils.ModCompatHooks; import org.polyfrost.chatting.utils.RenderUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.*; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.util.EnumChatFormatting; -import net.minecraft.util.MathHelper; -import net.minecraft.util.ResourceLocation; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -24,6 +24,7 @@ import org.spongepowered.asm.mixin.injection.*; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import org.spongepowered.asm.mixin.injection.invoke.arg.Args; import java.awt.datatransfer.StringSelection; @@ -66,19 +67,6 @@ public abstract class GuiNewChatMixin extends Gui implements GuiNewChatHook { @Unique private static final ResourceLocation DELETE = new ResourceLocation("chatting:delete.png"); - /*? - @Unique - private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); - @ModifyArg(method = "setChatLine", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ChatLine;(ILnet/minecraft/util/IChatComponent;I)V")) - private IChatComponent handleAddDrawnLine(IChatComponent iChatComponent) { - if (!ChattingConfig.INSTANCE.getShowTimestamp()) return iChatComponent; - String time = " §7["+ sdf.format(new Date(System.currentTimeMillis())) + "]§r"; - iChatComponent.appendSibling(new ChatComponentText(time)); - return iChatComponent; - } - - */ - @Inject(method = "drawChat", at = @At("HEAD")) private void checkScreenshotKeybind(int j2, CallbackInfo ci) { if (Chatting.INSTANCE.getKeybind().isPressed()) { @@ -172,10 +160,14 @@ private void checkStuff(int j2, CallbackInfo ci) { @Unique private int chatting$lastMouseY = 0; - @Inject(method = "getChatComponent", at = @At(value = "INVOKE", target = "Ljava/util/List;get(I)Ljava/lang/Object;")) - private void storeMouseXAndY(int mouseX, int mouseY, CallbackInfoReturnable cir) { + @Unique + private ChatLine chatting$currentComponent; + + @Inject(method = "getChatComponent", at = @At(value = "INVOKE", target = "Ljava/util/List;get(I)Ljava/lang/Object;"), locals = LocalCapture.CAPTURE_FAILHARD) + private void storeMouseXAndY(int mouseX, int mouseY, CallbackInfoReturnable cir, ScaledResolution scaledResolution, int i, float f, int j, int k, int l, int m) { chatting$lastMouseX = mouseX; chatting$lastMouseY = mouseY; + chatting$currentComponent = drawnChatLines.get(m); } @ModifyVariable(method = "getChatComponent", at = @At("STORE"), ordinal = 0) @@ -203,6 +195,11 @@ private void cancelChatComponent(int mouseX, int mouseY, CallbackInfoReturnable< } } + @ModifyVariable(method = "getChatComponent", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ChatLine;getChatComponent()Lnet/minecraft/util/IChatComponent;"), index = 11) + private int fixOffsetForGetChatComponent(int value) { + return ModCompatHooks.getStartOffset(value, chatting$currentComponent); + } + @Override public int getRight() { return chatting$right; diff --git a/src/main/java/org/polyfrost/chatting/mixin/GuiUtilRenderComponentsMixin.java b/src/main/java/org/polyfrost/chatting/mixin/GuiUtilRenderComponentsMixin.java index fd497a6..bed2eee 100644 --- a/src/main/java/org/polyfrost/chatting/mixin/GuiUtilRenderComponentsMixin.java +++ b/src/main/java/org/polyfrost/chatting/mixin/GuiUtilRenderComponentsMixin.java @@ -14,6 +14,7 @@ private static int modifyChatLineX(net.minecraft.client.gui.FontRenderer fontRen if (ChattingConfig.INSTANCE.getShowChatHeads() && (ChattingConfig.INSTANCE.getOffsetNonPlayerMessages() || ChatHeadHooks.INSTANCE.detect(text, null))) { return fontRenderer.getStringWidth(text) + 10; } + // TODO: time thingy? return fontRenderer.getStringWidth(text); } } diff --git a/src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt b/src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt index 0701471..7ed45e4 100644 --- a/src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt +++ b/src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt @@ -136,25 +136,20 @@ object ChattingConfig : Config( ) var hideChatHeadOnConsecutiveMessages = true - /*/ - @Property( - type = PropertyType.SWITCH, + @Switch( name = "Show Timestamp", description = "Show message timestamp.", category = "General" ) var showTimestamp = false - @Property( - type = PropertyType.SWITCH, + @Switch( name = "Timestamp Only On Hover", description = "Show timestamp only on mouse hover.", category = "General" ) var showTimestampHover = true - */ - @Info( text = "If Chatting detects a public chat message that seems like spam, and the probability is higher than this, it will hide it.\n" + "Made for Hypixel Skyblock. Set to 100% to disable. 95% is a reasonable threshold to use it at.\n" + "Note that this is not and never will be 100% accurate; however, it's pretty much guaranteed to block most spam.", size = 2, diff --git a/src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt b/src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt index 591461d..1a53f8d 100644 --- a/src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt +++ b/src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt @@ -3,11 +3,6 @@ package org.polyfrost.chatting.utils import cc.polyfrost.oneconfig.renderer.TextRenderer import cc.polyfrost.oneconfig.utils.dsl.getAlpha import cc.polyfrost.oneconfig.utils.dsl.mc -import org.polyfrost.chatting.Chatting.isBetterChat -import org.polyfrost.chatting.Chatting.isPatcher -import org.polyfrost.chatting.config.ChattingConfig.offsetNonPlayerMessages -import org.polyfrost.chatting.config.ChattingConfig.showChatHeads -import org.polyfrost.chatting.config.ChattingConfig.textRenderType import club.sk1er.patcher.config.PatcherConfig import com.llamalad7.betterchat.BetterChat import net.minecraft.client.Minecraft @@ -15,9 +10,18 @@ import net.minecraft.client.gui.ChatLine import net.minecraft.client.gui.FontRenderer import net.minecraft.client.gui.Gui import net.minecraft.client.renderer.GlStateManager +import org.polyfrost.chatting.Chatting.isBetterChat +import org.polyfrost.chatting.Chatting.isPatcher +import org.polyfrost.chatting.config.ChattingConfig.offsetNonPlayerMessages +import org.polyfrost.chatting.config.ChattingConfig.showChatHeads +import org.polyfrost.chatting.config.ChattingConfig.showTimestamp +import org.polyfrost.chatting.config.ChattingConfig.textRenderType import org.polyfrost.chatting.hook.ChatLineHook import org.polyfrost.chatting.hook.GuiNewChatHook import org.polyfrost.chatting.mixin.GuiNewChatAccessor +import java.text.SimpleDateFormat +import java.util.* +import kotlin.math.pow // This exists because mixin doesn't like dummy classes object ModCompatHooks { @@ -53,11 +57,44 @@ object ModCompatHooks { val drawnChatLines: List get() = (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines + @JvmStatic + fun getFullMessage(chatLine: ChatLine): ChatLine? { + return (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).getFullMessage(chatLine) + } + + private var hoveredText: ChatLine? = null + private var hoverProgress = 0L + private val formatter = SimpleDateFormat("HH:mm") + + fun getTimeStampText(comp: ChatLine): String { + comp as ChatLineHook + return "[${formatter.format(Date(comp.timestamp))}] " + } + + @JvmStatic + fun setHoveredText(comp: ChatLine?) { + val newComp = comp?.let(::getFullMessage) + if (hoveredText != newComp) { + hoveredText = newComp + hoverProgress = System.currentTimeMillis() + } + } + @JvmStatic fun redirectDrawString(text: String, x: Float, y: Float, color: Int, chatLine: ChatLine, screenshot: Boolean): Int { var actualX = x + val hook = chatLine as ChatLineHook + if (showTimestamp && !screenshot) { + val timeOffsetInfo = getTimeOffset(chatLine) + if (timeOffsetInfo != null) { + if (timeOffsetInfo.shouldRender) { + fontRenderer.drawString(timeOffsetInfo.text, actualX, y, -1, true) + } + actualX += timeOffsetInfo.offset + } + } if (showChatHeads && !screenshot) { - val hook = chatLine as ChatLineHook + val renderX = actualX if (hook.isDetected || offsetNonPlayerMessages) { actualX += 10f } @@ -70,7 +107,7 @@ object ModCompatHooks { GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) GlStateManager.color(1.0f, 1.0f, 1.0f, color.getAlpha() / 255f) Gui.drawScaledCustomSizeModalRect( - (x).toInt(), + (renderX).toInt(), (y - 1f).toInt(), 8.0f, 8.0f, @@ -82,7 +119,7 @@ object ModCompatHooks { 64.0f ) Gui.drawScaledCustomSizeModalRect( - (x).toInt(), + (renderX).toInt(), (y - 1f).toInt(), 40.0f, 8.0f, @@ -106,4 +143,35 @@ object ModCompatHooks { else -> fontRenderer.drawString(text, actualX, y, color, true) } } + + + data class TimeOffsetInfo( + val text: String, + val shouldRender: Boolean, + val offset: Int, + ) + + private fun ease(x: Double): Double { + return if (x < 0.5) 4 * x * x * x else 1 - (-2.0 * x + 2).pow(3.0) / 2 + } + + private fun getTimeOffset(comp: ChatLine): TimeOffsetInfo? { + if (!showTimestamp) return null + val root = getFullMessage(comp) + if (root == null || root != hoveredText) return null + if ((root as ChatLineHook).children.firstOrNull() != comp) return null + val text = getTimeStampText(root) + val strWidth = fontRenderer.getStringWidth(text) + val animationTime = ((System.currentTimeMillis() - hoverProgress) / 100.0).coerceIn(0.0, 1.0) + val easedAnimationPercentage = ease(animationTime) + val progress = (easedAnimationPercentage * strWidth).toInt() + return TimeOffsetInfo(text, progress == strWidth, progress) + } + + @JvmStatic + fun getStartOffset(value: Int, comp: ChatLine): Int { + var actualX = value + actualX += getTimeOffset(comp)?.offset ?: 0 + return actualX + } }