From 8b373f577d9c6dde26357ef3fc86691f1efef9b4 Mon Sep 17 00:00:00 2001 From: Wyvest Date: Wed, 22 Nov 2023 08:18:19 +0900 Subject: update PGT and relocate to org.polyfrost --- src/main/kotlin/cc/woverflow/chatting/Chatting.kt | 272 ---------------- .../cc/woverflow/chatting/chat/ChatRegexes.kt | 11 - .../woverflow/chatting/chat/ChatScrollingHook.kt | 5 - .../chatting/chat/ChatSearchingManager.kt | 42 --- .../cc/woverflow/chatting/chat/ChatShortcuts.kt | 79 ----- .../cc/woverflow/chatting/chat/ChatSpamBlock.kt | 124 -------- .../kotlin/cc/woverflow/chatting/chat/ChatTab.kt | 112 ------- .../kotlin/cc/woverflow/chatting/chat/ChatTabs.kt | 354 --------------------- .../cc/woverflow/chatting/chat/ChatTabsJson.kt | 15 - .../woverflow/chatting/command/ChattingCommand.kt | 14 - .../cc/woverflow/chatting/config/ChattingConfig.kt | 313 ------------------ .../chatting/gui/components/CleanButton.kt | 103 ------ .../chatting/gui/components/ClearButton.kt | 42 --- .../chatting/gui/components/RenderType.kt | 7 - .../chatting/gui/components/ScreenshotButton.kt | 36 --- .../chatting/gui/components/SearchButton.kt | 70 ---- .../woverflow/chatting/gui/components/TabButton.kt | 46 --- .../cc/woverflow/chatting/utils/EaseOutQuart.kt | 7 - .../cc/woverflow/chatting/utils/ModCompatHooks.kt | 100 ------ .../cc/woverflow/chatting/utils/RenderUtils.kt | 259 --------------- src/main/kotlin/org/polyfrost/chatting/Chatting.kt | 272 ++++++++++++++++ .../org/polyfrost/chatting/chat/ChatRegexes.kt | 11 + .../polyfrost/chatting/chat/ChatScrollingHook.kt | 5 + .../chatting/chat/ChatSearchingManager.kt | 42 +++ .../org/polyfrost/chatting/chat/ChatShortcuts.kt | 79 +++++ .../org/polyfrost/chatting/chat/ChatSpamBlock.kt | 124 ++++++++ .../kotlin/org/polyfrost/chatting/chat/ChatTab.kt | 112 +++++++ .../kotlin/org/polyfrost/chatting/chat/ChatTabs.kt | 354 +++++++++++++++++++++ .../org/polyfrost/chatting/chat/ChatTabsJson.kt | 15 + .../polyfrost/chatting/command/ChattingCommand.kt | 14 + .../polyfrost/chatting/config/ChattingConfig.kt | 313 ++++++++++++++++++ .../chatting/gui/components/CleanButton.kt | 103 ++++++ .../chatting/gui/components/ClearButton.kt | 42 +++ .../chatting/gui/components/RenderType.kt | 7 + .../chatting/gui/components/ScreenshotButton.kt | 36 +++ .../chatting/gui/components/SearchButton.kt | 70 ++++ .../polyfrost/chatting/gui/components/TabButton.kt | 46 +++ .../org/polyfrost/chatting/utils/EaseOutQuart.kt | 7 + .../org/polyfrost/chatting/utils/ModCompatHooks.kt | 100 ++++++ .../org/polyfrost/chatting/utils/RenderUtils.kt | 259 +++++++++++++++ 40 files changed, 2011 insertions(+), 2011 deletions(-) delete mode 100644 src/main/kotlin/cc/woverflow/chatting/Chatting.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatRegexes.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatScrollingHook.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatSearchingManager.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatShortcuts.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatTab.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatTabsJson.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/command/ChattingCommand.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/ClearButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/RenderType.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/ScreenshotButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/SearchButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/utils/EaseOutQuart.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/utils/ModCompatHooks.kt delete mode 100644 src/main/kotlin/cc/woverflow/chatting/utils/RenderUtils.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/Chatting.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/command/ChattingCommand.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/gui/components/CleanButton.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/gui/components/ClearButton.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/gui/components/RenderType.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/gui/components/ScreenshotButton.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/gui/components/SearchButton.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/gui/components/TabButton.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/utils/EaseOutQuart.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt create mode 100644 src/main/kotlin/org/polyfrost/chatting/utils/RenderUtils.kt (limited to 'src/main/kotlin') diff --git a/src/main/kotlin/cc/woverflow/chatting/Chatting.kt b/src/main/kotlin/cc/woverflow/chatting/Chatting.kt deleted file mode 100644 index 9452dcf..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/Chatting.kt +++ /dev/null @@ -1,272 +0,0 @@ -package cc.woverflow.chatting - -import cc.polyfrost.oneconfig.libs.universal.UDesktop -import cc.polyfrost.oneconfig.libs.universal.UMinecraft -import cc.polyfrost.oneconfig.libs.universal.UResolution -import cc.polyfrost.oneconfig.utils.Notifications -import cc.polyfrost.oneconfig.utils.commands.CommandManager -import cc.polyfrost.oneconfig.utils.dsl.browseLink -import cc.woverflow.chatting.chat.ChatSearchingManager -import cc.woverflow.chatting.chat.ChatShortcuts -import cc.woverflow.chatting.chat.ChatSpamBlock -import cc.woverflow.chatting.chat.ChatTabs -import cc.woverflow.chatting.command.ChattingCommand -import cc.woverflow.chatting.config.ChattingConfig -import cc.woverflow.chatting.hook.ChatLineHook -import cc.woverflow.chatting.mixin.GuiNewChatAccessor -import cc.woverflow.chatting.utils.ModCompatHooks -import cc.woverflow.chatting.utils.copyToClipboard -import cc.woverflow.chatting.utils.createBindFramebuffer -import cc.woverflow.chatting.utils.screenshot -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.* -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.client.renderer.OpenGlHelper -import net.minecraft.client.settings.KeyBinding -import net.minecraft.client.shader.Framebuffer -import net.minecraft.util.MathHelper -import net.minecraftforge.client.event.RenderGameOverlayEvent -import net.minecraftforge.common.MinecraftForge.EVENT_BUS -import net.minecraftforge.fml.client.registry.ClientRegistry -import net.minecraftforge.fml.common.Loader -import net.minecraftforge.fml.common.Mod -import net.minecraftforge.fml.common.event.FMLInitializationEvent -import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import net.minecraftforge.fml.common.gameevent.TickEvent -import org.lwjgl.input.Keyboard -import java.awt.image.BufferedImage -import java.io.File -import java.text.SimpleDateFormat -import java.util.* - - -@Mod( - modid = Chatting.ID, - name = Chatting.NAME, - version = Chatting.VER, - modLanguageAdapter = "cc.polyfrost.oneconfig.utils.KotlinLanguageAdapter" -) -object Chatting { - - val keybind = KeyBinding("Screenshot Chat", Keyboard.KEY_NONE, "Chatting") - const val NAME = "@NAME@" - const val VER = "@VER@" - const val ID = "@ID@" - var doTheThing = false - var isPatcher = false - private set - var isBetterChat = false - private set - var isSkytils = false - private set - var isHychat = false - private set - - private var time = -1L - var deltaTime = 17L - - private val fileFormatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd_HH.mm.ss'.png'") - - val oldModDir = File(File(Minecraft.getMinecraft().mcDataDir, "W-OVERFLOW"), NAME) - - @Mod.EventHandler - fun onInitialization(event: FMLInitializationEvent) { - ChattingConfig - CommandManager.INSTANCE.registerCommand(ChattingCommand()) - ClientRegistry.registerKeyBinding(keybind) - EVENT_BUS.register(this) - EVENT_BUS.register(ChatSpamBlock) - ChatTabs.initialize() - ChatShortcuts.initialize() - } - - @Mod.EventHandler - fun onPostInitialization(event: FMLPostInitializationEvent) { - isPatcher = Loader.isModLoaded("patcher") - isBetterChat = Loader.isModLoaded("betterchat") - isSkytils = Loader.isModLoaded("skytils") - isHychat = Loader.isModLoaded("hychat") - } - - @Mod.EventHandler - fun onForgeLoad(event: FMLLoadCompleteEvent) { - if (ChattingConfig.informForAlternatives) { - if (isHychat) { - Notifications.INSTANCE.send( - NAME, - "Hychat can be removed as it is replaced by Chatting. Click here for more information.", - Runnable { - UDesktop.browseLink("https://microcontrollersdev.github.io/Alternatives/1.8.9/hychat") - }) - } - if (isSkytils) { - try { - skytilsCompat(Class.forName("gg.skytils.skytilsmod.core.Config")) - } catch (e: Exception) { - e.printStackTrace() - try { - skytilsCompat(Class.forName("skytils.skytilsmod.core.Config")) - } catch (e: Exception) { - e.printStackTrace() - } - } - } - if (isBetterChat) { - Notifications.INSTANCE.send( - NAME, - "BetterChat can be removed as it is replaced by Chatting. Click here to open your mods folder to delete the BetterChat file.", - Runnable { - UDesktop.open(File("./mods")) - }) - } - } - } - - private fun skytilsCompat(skytilsClass: Class<*>) { - val instance = skytilsClass.getDeclaredField("INSTANCE") - val chatTabs = skytilsClass.getDeclaredField("chatTabs") - chatTabs.isAccessible = true - if (chatTabs.getBoolean(instance)) { - Notifications.INSTANCE.send( - NAME, - "Skytils' chat tabs can be disabled as it is replace by Chatting.\nClick here to automatically do this.", - Runnable { - chatTabs.setBoolean(instance, false) - ChattingConfig.chatTabs = true - ChattingConfig.hypixelOnlyChatTabs = true - ChattingConfig.save() - skytilsClass.getMethod("markDirty").invoke(instance) - skytilsClass.getMethod("writeData").invoke(instance) - }) - } - val copyChat = skytilsClass.getDeclaredField("copyChat") - copyChat.isAccessible = true - if (copyChat.getBoolean(instance)) { - Notifications.INSTANCE.send( - NAME, - "Skytils' copy chat messages can be disabled as it is replace by Chatting.\nClick here to automatically do this.", - Runnable { - copyChat.setBoolean(instance, false) - skytilsClass.getMethod("markDirty").invoke(instance) - skytilsClass.getMethod("writeData").invoke(instance) - }) - } - } - - @SubscribeEvent - fun onRenderTick(event: RenderGameOverlayEvent.Pre) { - if (event.type == RenderGameOverlayEvent.ElementType.ALL) { - if (time == -1L) { - time = UMinecraft.getTime() - } else { - val currentTime = UMinecraft.getTime() - deltaTime = currentTime - time - time = currentTime - } - } - } - - @SubscribeEvent - fun onTickEvent(event: TickEvent.ClientTickEvent) { - if (event.phase == TickEvent.Phase.START && Minecraft.getMinecraft().theWorld != null && Minecraft.getMinecraft().thePlayer != null && (Minecraft.getMinecraft().currentScreen == null || Minecraft.getMinecraft().currentScreen is GuiChat)) { - if (doTheThing) { - screenshotChat() - doTheThing = false - } - } - } - - fun getChatHeight(opened: Boolean): Int { - var height = if (opened) ChattingConfig.focusedHeight else ChattingConfig.unfocusedHeight - height = (height * Minecraft.getMinecraft().gameSettings.chatScale).toInt() - val chatY = ModCompatHooks.yOffset + ModCompatHooks.chatPosition - if (height + chatY + 27 > (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale).toInt() - 27 - chatY) { - height = (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale).toInt() - 27 - chatY - } - return height - } - - fun screenshotLine(line: ChatLine): BufferedImage? { - val hud = Minecraft.getMinecraft().ingameGUI - val chat = hud.chatGUI - val i = MathHelper.floor_float(chat.chatWidth / chat.chatScale) - return screenshot( - hashMapOf().also { - GuiUtilRenderComponents.splitText( - line.chatComponent, - i, - Minecraft.getMinecraft().fontRendererObj, - false, - false - ).map { it.formattedText }.reversed().forEach { string -> - it[line] = string - } - } - ) - } - - private fun screenshotChat() { - screenshotChat(0) - } - - fun screenshotChat(scrollPos: Int) { - val hud = Minecraft.getMinecraft().ingameGUI - val chat = hud.chatGUI - val chatLines = LinkedHashMap() - ChatSearchingManager.filterMessages( - ChatSearchingManager.lastSearch, - (chat as GuiNewChatAccessor).drawnChatLines - )?.let { drawnLines -> - val chatHeight = - if (ChattingConfig.customChatHeight) getChatHeight(true) / 9 else GuiNewChat.calculateChatboxHeight( - Minecraft.getMinecraft().gameSettings.chatHeightFocused / 9 - ) - for (i in scrollPos until drawnLines.size.coerceAtMost(scrollPos + chatHeight)) { - chatLines[drawnLines[i]] = drawnLines[i].chatComponent.formattedText - } - - screenshot(chatLines)?.copyToClipboard() - } - } - - private fun screenshot(messages: HashMap): BufferedImage? { - if (messages.isEmpty()) { - Notifications.INSTANCE.send("Chatting", "Chat window is empty.") - return null - } - if (!OpenGlHelper.isFramebufferEnabled()) { - Notifications.INSTANCE.send( - "Chatting", - "Screenshot failed, please disable “Fast Render” in OptiFine’s “Performance” tab." - ) - return null - } - - val fr: FontRenderer = ModCompatHooks.fontRenderer - val width = messages.maxOf { fr.getStringWidth(it.value) + (if (ChattingConfig.showChatHeads && ((it.key as ChatLineHook).hasDetected() || ChattingConfig.offsetNonPlayerMessages)) 10 else 0) } + 4 - val fb: Framebuffer = createBindFramebuffer(width * 2, (messages.size * 9) * 2) - val file = File(Minecraft.getMinecraft().mcDataDir, "screenshots/chat/" + fileFormatter.format(Date())) - - GlStateManager.scale(2f, 2f, 1f) - val scale = Minecraft.getMinecraft().gameSettings.chatScale - GlStateManager.scale(scale, scale, 1f) - messages.entries.forEachIndexed { i: Int, entry: MutableMap.MutableEntry -> - ModCompatHooks.redirectDrawString(entry.value, 0f, (messages.size - 1 - i) * 9f, 0xffffff, entry.key, true) - } - - val image = fb.screenshot(file) - Minecraft.getMinecraft().entityRenderer.setupOverlayRendering() - Minecraft.getMinecraft().framebuffer.bindFramebuffer(true) - Notifications.INSTANCE.send( - "Chatting", - "Chat screenshotted successfully." + (if (ChattingConfig.copyMode != 1) "\nClick to open." else ""), - Runnable { - if (!UDesktop.open(file)) { - Notifications.INSTANCE.send("Chatting", "Could not browse!") - } - }) - return image - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatRegexes.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatRegexes.kt deleted file mode 100644 index a0a8e86..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatRegexes.kt +++ /dev/null @@ -1,11 +0,0 @@ -package cc.woverflow.chatting.chat - -data class ChatRegexes(val regexList: List?) { - val compiledRegexList: MutableList = arrayListOf() - - init { - regexList?.forEach { - compiledRegexList.add(Regex(it)) - } - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatScrollingHook.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatScrollingHook.kt deleted file mode 100644 index b81de94..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatScrollingHook.kt +++ /dev/null @@ -1,5 +0,0 @@ -package cc.woverflow.chatting.chat - -object ChatScrollingHook { - var shouldSmooth = false -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatSearchingManager.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatSearchingManager.kt deleted file mode 100644 index 33a2642..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatSearchingManager.kt +++ /dev/null @@ -1,42 +0,0 @@ -package cc.woverflow.chatting.chat - -import cc.polyfrost.oneconfig.libs.caffeine.cache.Cache -import cc.polyfrost.oneconfig.libs.caffeine.cache.Caffeine -import cc.polyfrost.oneconfig.libs.universal.wrappers.message.UTextComponent -import net.minecraft.client.gui.ChatLine -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.ThreadPoolExecutor -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger - -object ChatSearchingManager { - private var counter: AtomicInteger = AtomicInteger(0) - private var POOL: ThreadPoolExecutor = ThreadPoolExecutor( - 50, 50, - 0L, TimeUnit.SECONDS, - LinkedBlockingQueue() - ) { r -> - Thread( - r, - "Chat Filter Cache Thread ${counter.incrementAndGet()}" - ) - } - - @JvmStatic - val cache: Cache> = Caffeine.newBuilder().executor(POOL).maximumSize(5000).build() - - var lastSearch = "" - - @JvmStatic - fun filterMessages(text: String, list: List): List? { - if (text.isBlank()) return list - val cached = cache.getIfPresent(text) - return cached ?: run { - cache.put(text, list.filter { - UTextComponent.stripFormatting(it.chatComponent.unformattedText).lowercase() - .contains(text.lowercase()) - }) - cache.getIfPresent(text) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatShortcuts.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatShortcuts.kt deleted file mode 100644 index ef1881d..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatShortcuts.kt +++ /dev/null @@ -1,79 +0,0 @@ -package cc.woverflow.chatting.chat - -import cc.polyfrost.oneconfig.config.core.ConfigUtils -import cc.woverflow.chatting.Chatting -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import java.io.File - -object ChatShortcuts { - private val oldShortcutsFile = File(Chatting.oldModDir, "chatshortcuts.json") - private val shortcutsFile = ConfigUtils.getProfileFile("chatshortcuts.json") - private val PARSER = JsonParser() - - private var initialized = false - - val shortcuts = object : ArrayList>() { - private val comparator = Comparator> { o1, o2 -> - return@Comparator o2.first.length.compareTo(o1.first.length) - } - - override fun add(element: Pair): Boolean { - val value = super.add(element) - sortWith(comparator) - return value - } - } - - fun initialize() { - if (initialized) { - return - } else { - initialized = true - } - if (shortcutsFile.exists()) { - try { - val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject - for (shortcut in jsonObj.entrySet()) { - shortcuts.add(shortcut.key to shortcut.value.asString) - } - return - } catch (_: Throwable) { - shortcutsFile.renameTo(File(shortcutsFile.parentFile, "chatshortcuts.json.bak")) - } - } - shortcutsFile.createNewFile() - if (oldShortcutsFile.exists()) { - shortcutsFile.writeText( - oldShortcutsFile.readText() - ) - } else { - shortcutsFile.writeText(JsonObject().toString()) - } - } - - fun removeShortcut(key: String) { - shortcuts.removeIf { it.first == key } - val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject - jsonObj.remove(key) - shortcutsFile.writeText(jsonObj.toString()) - } - - fun writeShortcut(key: String, value: String) { - shortcuts.add(key to value) - val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject - jsonObj.addProperty(key, value) - shortcutsFile.writeText(jsonObj.toString()) - } - - fun handleSentCommand(command: String): String { - shortcuts.forEach { - if (command == it.first || (command.startsWith(it.first) && command.substringAfter(it.first) - .startsWith(" ")) - ) { - return command.replaceFirst(it.first, it.second) - } - } - return command - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt deleted file mode 100644 index 471eec8..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt +++ /dev/null @@ -1,124 +0,0 @@ -package cc.woverflow.chatting.chat - -import cc.woverflow.chatting.config.ChattingConfig -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import java.text.Normalizer -import net.minecraft.util.ChatComponentText -import net.minecraft.util.EnumChatFormatting -import net.minecraftforge.client.event.ClientChatReceivedEvent -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent - -object ChatSpamBlock { - /* - Made by @KTibow - Based off of Unspam (also by @KTibow) - Algorithm based off of https://paulgraham.com/spam.html - */ - private val PLAYER_MESSAGE = Regex("^(\\[VIP\\+?\\] |\\[MVP\\+?\\+?\\] |)(\\w{2,16}): (.*)$") - - @SubscribeEvent - fun onChat(event: ClientChatReceivedEvent) { - val message = event.message.unformattedText.replace(Regex("\u00A7."), "") - if (!PLAYER_MESSAGE.matches(message)) return - - val (rank, player, content) = PLAYER_MESSAGE.matchEntire(message)!!.destructured - - if (ChattingConfig.spamThreshold != 100) { - val tokens = tokenize(content) - val spamProb = findSpamProbability(tokens) - if (spamProb * 100 > ChattingConfig.spamThreshold) { - if (ChattingConfig.hideSpam) { - event.isCanceled = true - } else { - var newMessage = - EnumChatFormatting.DARK_GRAY.toString() + - EnumChatFormatting.STRIKETHROUGH.toString() - if (!ChattingConfig.customChatFormatting) { - newMessage += rank - } - newMessage += "$player${EnumChatFormatting.DARK_GRAY}: $content" - event.message = ChatComponentText(newMessage) - } - return - } - } - if (ChattingConfig.customChatFormatting) { - val coloredPlayer = findRankColor(rank) + player + EnumChatFormatting.RESET.toString() - event.message = ChatComponentText("$coloredPlayer: $content") - } - } - - private fun tokenize(message: String): MutableList { - val strippedMessage = - Normalizer.normalize(message, Normalizer.Form.NFKC) - .replace(Regex("[^\\w\\s/]"), " ") - .lowercase() - .trim() - val tokens = strippedMessage.split(Regex("\\s+")).toMutableList() - if (tokens.size <= 2) { - tokens.add("TINY_LENGTH") - } else if (tokens.size <= 4) { - tokens.add("SMALL_LENGTH") - } else if (tokens.size <= 7) { - tokens.add("MEDIUM_LENGTH") - } else { - tokens.add("LONG_LENGTH") - } - if (message.replace(Regex("[\\w\\s]"), "").length > 2) { - tokens.add("SPECIAL_CHARS") - } else if (message.replace(Regex("[\\w\\s]"), "").isNotEmpty()) { - tokens.add("SPECIAL_CHAR") - } else { - tokens.add("LOW_SPECIAL_CHARS") - } - if (message.replace(Regex("[^A-Z]"), "").length >= message.length / 4) { - tokens.add("HIGH_CAPS") - } else { - tokens.add("LOW_CAPS") - } - return tokens - } - - private fun findSpamProbability(tokens: MutableList): Double { - val tokenProbs = mutableMapOf() - for (token in tokens) { - if (!spamInfoJson.has(token)) continue - val spamInToken = spamInfoJson.get(token).asJsonObject.get("spam").asDouble - val fineInToken = spamInfoJson.get(token).asJsonObject.get("fine").asDouble - tokenProbs[token] = - ((spamInToken / messageCountsJson.get("spam").asInt) / - (fineInToken / messageCountsJson.get("fine").asInt + - spamInToken / messageCountsJson.get("spam").asInt)) - } - val spamProbs = tokenProbs.values.toMutableList() - val fineProbs = tokenProbs.values.map { 1 - it }.toMutableList() - val spamProbability = spamProbs.reduce { a, b -> a * b } - val fineProbability = fineProbs.reduce { a, b -> a * b } - return spamProbability / (spamProbability + fineProbability) - } - - private fun findRankColor(rank: String): String { - println(rank) - return when (rank) { - "[VIP] ", - "[VIP+] " -> EnumChatFormatting.GREEN.toString() - "[MVP] ", - "[MVP+] " -> EnumChatFormatting.AQUA.toString() - "[MVP++] " -> EnumChatFormatting.GOLD.toString() - else -> EnumChatFormatting.GRAY.toString() - } - } - - private fun getResourceAsText(path: String): String? = - object {}.javaClass.getResource(path)?.readText() - private val spamInfoJson: JsonObject - private val messageCountsJson: JsonObject - - init { - // Load the file spamInfo.json from resources/ - val spamInfo = getResourceAsText("/spamInfo.json") - spamInfoJson = JsonParser().parse(spamInfo).asJsonObject - messageCountsJson = JsonParser().parse(" { \"fine\": 668, \"spam\": 230 }").asJsonObject - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatTab.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTab.kt deleted file mode 100644 index 50de1f4..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatTab.kt +++ /dev/null @@ -1,112 +0,0 @@ -package cc.woverflow.chatting.chat - -import cc.woverflow.chatting.gui.components.TabButton -import com.google.gson.annotations.SerializedName -import net.minecraft.client.Minecraft -import net.minecraft.util.EnumChatFormatting -import net.minecraft.util.IChatComponent -import java.util.* - -data class ChatTab( - val enabled: Boolean, - val name: String, - val unformatted: Boolean, - val lowercase: Boolean?, - @SerializedName("starts") val startsWith: List?, - val contains: List?, - @SerializedName("ends") val endsWith: List?, - val equals: List?, - @SerializedName("regex") val uncompiledRegex: List?, - @SerializedName("ignore_starts") val ignoreStartsWith: List?, - @SerializedName("ignore_contains") val ignoreContains: List?, - @SerializedName("ignore_ends") val ignoreEndsWith: List?, - @SerializedName("ignore_equals") val ignoreEquals: List?, - @SerializedName("ignore_regex") val uncompiledIgnoreRegex: List?, - val color: Int?, - @SerializedName("hovered_color") val hoveredColor: Int?, - @SerializedName("selected_color") val selectedColor: Int?, - val prefix: String?, -) { - lateinit var button: TabButton - lateinit var compiledRegex: ChatRegexes - lateinit var compiledIgnoreRegex: ChatRegexes - - //Ugly hack to make GSON not make button / regex null - fun initialize() { - compiledRegex = ChatRegexes(uncompiledRegex) - compiledIgnoreRegex = ChatRegexes(uncompiledIgnoreRegex) - val width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(name) - button = TabButton(653452, run { - val returnValue = x - 2 - x += 6 + width - return@run returnValue - }, width + 4, 12, this) - } - - fun shouldRender(chatComponent: IChatComponent): Boolean { - val message = - (if (unformatted) EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.unformattedText) else chatComponent.formattedText).let { - if (lowercase == true) it.lowercase( - Locale.ENGLISH - ) else it - } - ignoreStartsWith?.forEach { - if (message.startsWith(it)) { - return false - } - } - ignoreEquals?.forEach { - if (message == it) { - return false - } - } - ignoreEndsWith?.forEach { - if (message.endsWith(it)) { - return false - } - } - ignoreContains?.forEach { - if (message.contains(it)) { - return false - } - } - compiledIgnoreRegex.compiledRegexList.forEach { - if (it.matches(message)) { - return false - } - } - if (startsWith.isNullOrEmpty() && equals.isNullOrEmpty() && endsWith.isNullOrEmpty() && contains.isNullOrEmpty() && uncompiledRegex.isNullOrEmpty()) { - return true - } - equals?.forEach { - if (message == it) { - return true - } - } - startsWith?.forEach { - if (message.startsWith(it)) { - return true - } - } - endsWith?.forEach { - if (message.endsWith(it)) { - return true - } - } - contains?.forEach { - if (message.contains(it)) { - return true - } - } - compiledRegex.compiledRegexList.forEach { - if (it.matches(message)) { - return true - } - } - return false - } - - companion object { - private var x = 4 - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt deleted file mode 100644 index 08423e5..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt +++ /dev/null @@ -1,354 +0,0 @@ -package cc.woverflow.chatting.chat - -import cc.polyfrost.oneconfig.config.core.ConfigUtils -import cc.woverflow.chatting.Chatting -import cc.woverflow.chatting.gui.components.TabButton -import com.google.gson.GsonBuilder -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import com.google.gson.JsonPrimitive -import net.minecraft.client.Minecraft -import net.minecraft.util.IChatComponent -import java.io.File - -object ChatTabs { - private val GSON = GsonBuilder().setPrettyPrinting().create() - private val PARSER = JsonParser() - val tabs = arrayListOf() - var currentTabs: ArrayList = object : ArrayList() { - override fun add(element: ChatTab?): Boolean { - if (element == null) return false - val returnValue = super.add(element) - if (Minecraft.getMinecraft().theWorld != null && returnValue) { - Minecraft.getMinecraft().ingameGUI.chatGUI.refreshChat() - } - return returnValue - } - } - var hasCancelledAnimation = false - private var initialized = false - - private val tabFile = ConfigUtils.getProfileFile("chattabs.json") - private val oldTabFile = File(Chatting.oldModDir, "chattabs.json") - - fun initialize() { - if (initialized) { - return - } else { - initialized = true - } - if (!tabFile.exists()) { - if (oldTabFile.exists()) { - tabFile.writeText(oldTabFile.readText()) - handleFile() - } else { - generateNewFile() - } - } else { - handleFile() - } - tabs.forEach { - it.initialize() - } - currentTabs.clear() - currentTabs.add(tabs[0]) - } - - private fun handleFile() { - try { - val chatTabJson = GSON.fromJson(tabFile.readText(), ChatTabsJson::class.java) - when (chatTabJson.version) { - 1 -> { - // ver 2 adds `enabled` - chatTabJson.tabs.forEach { - applyVersion2Changes(it.asJsonObject) - applyVersion3Changes(it.asJsonObject) - applyVersion4Changes(it.asJsonObject) - applyVersion5Changes(it.asJsonObject) - applyVersion6Changes(it.asJsonObject) - } - chatTabJson.version = ChatTabsJson.VERSION - tabFile.writeText(GSON.toJson(chatTabJson)) - } - 2 -> { - // ver 3 adds ignore_ - chatTabJson.tabs.forEach { - applyVersion3Changes(it.asJsonObject) - applyVersion4Changes(it.asJsonObject) - applyVersion5Changes(it.asJsonObject) - applyVersion6Changes(it.asJsonObject) - } - chatTabJson.version = ChatTabsJson.VERSION - tabFile.writeText(GSON.toJson(chatTabJson)) - } - 3 -> { - // ver 4 adds color options - chatTabJson.tabs.forEach { - applyVersion4Changes(it.asJsonObject) - applyVersion5Changes(it.asJsonObject) - applyVersion6Changes(it.asJsonObject) - } - chatTabJson.version = ChatTabsJson.VERSION - tabFile.writeText(GSON.toJson(chatTabJson)) - } - 4 -> { - // ver 5 adds lowercase - chatTabJson.tabs.forEach { - applyVersion5Changes(it.asJsonObject) - applyVersion6Changes(it.asJsonObject) - } - chatTabJson.version = ChatTabsJson.VERSION - tabFile.writeText(GSON.toJson(chatTabJson)) - } - 5 -> { - // ver 6 changes pm regex - chatTabJson.tabs.forEach { - applyVersion6Changes(it.asJsonObject) - } - chatTabJson.version = ChatTabsJson.VERSION - tabFile.writeText(GSON.toJson(chatTabJson)) - } - } - chatTabJson.tabs.forEach { - val chatTab = GSON.fromJson(it.toString(), ChatTab::class.java) - if (chatTab.enabled) { - tabs.add(chatTab) - } - } - } catch (e: Throwable) { - e.printStackTrace() - tabFile.delete() - generateNewFile() - } - } - - private fun applyVersion2Changes(json: JsonObject) { - json.addProperty("enabled", true) - } - - private fun applyVersion3Changes(json: JsonObject) { - json.add("ignore_starts", JsonArray()) - json.add("ignore_contains", JsonArray()) - json.add("ignore_ends", JsonArray()) - json.add("ignore_equals", JsonArray()) - json.add("ignore_regex", JsonArray()) - } - - private fun applyVersion4Changes(json: JsonObject) { - json.addProperty("color", TabButton.color) - json.addProperty("hovered_color", TabButton.hoveredColor) - json.addProperty("selected_color", TabButton.selectedColor) - } - - private fun applyVersion5Changes(json: JsonObject) { - json.addProperty("lowercase", false) - } - - private fun applyVersion6Changes(json: JsonObject) { - if (json.has("starts")) { - val starts = json["starts"].asJsonArray - var detected = false - starts.iterator().let { - while (it.hasNext()) { - when (it.next().asString) { - "To " -> { - detected = true - it.remove() - } - "From " -> { - detected = true - it.remove() - } - } - } - } - if (detected) { - json.add("regex", JsonArray().apply { - add(JsonPrimitive("^(?§dTo|§dFrom) (?.+): §r(?.*)(?:§r)?\$")) - }) - json.remove("unformatted") - json.addProperty("unformatted", false) - } - } - if (json.has("ends")) { - val ends = json["ends"].asJsonArray - var detected = false - ends.iterator().let { - while (it.hasNext()) { - when (it.next().asString) { - "§r§ehas invited you to join their party!", -> { - detected = true - it.remove() - } - } - } - } - if (detected) { - json.add("contains", JsonArray().apply { - add(JsonPrimitive("§r§ehas invited you to join their party!")) - }) - } - } - } - - fun shouldRender(message: IChatComponent): Boolean { - if (currentTabs.isEmpty()) return true - for (tab in currentTabs) { - if (tab?.shouldRender(message) == true) { - return true - } - } - return false - } - - private fun generateNewFile() { - tabFile.createNewFile() - val jsonObject = JsonObject() - val defaultTabs = generateDefaultTabs() - jsonObject.add("tabs", defaultTabs) - jsonObject.addProperty("version", ChatTabsJson.VERSION) - tabFile.writeText(GSON.toJson(jsonObject)) - } - - private fun generateDefaultTabs(): JsonArray { - val all = ChatTab( - true, - "ALL", - unformatted = false, - lowercase = false, - startsWith = null, - contains = null, - endsWith = null, - equals = null, - uncompiledRegex = null, - ignoreStartsWith = null, - ignoreContains = null, - ignoreEndsWith = null, - ignoreEquals = null, - uncompiledIgnoreRegex = null, - color = TabButton.color, - hoveredColor = TabButton.hoveredColor, - selectedColor = TabButton.selectedColor, - prefix = "" - ) - val party = ChatTab( - true, - "PARTY", - unformatted = false, - lowercase = false, - startsWith = listOf("§r§9Party §8> ", "§r§9P §8> ", "§eThe party was transferred to §r", "§eKicked §r"), - contains = listOf("§r§ehas invited you to join their party!"), - endsWith = listOf( - "§r§eto the party! They have §r§c60 §r§eseconds to accept.§r", - "§r§ehas disbanded the party!§r", - "§r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before they are removed from the party.§r", - " §r§ejoined the party.§r", - " §r§ehas left the party.§r", - " §r§ehas been removed from the party.§r", - "§r§e because they were offline.§r" - ), - equals = listOf("§cThe party was disbanded because all invites expired and the party was empty§r"), - uncompiledRegex = listOf( //regexes from https://github.com/kwevin/Hychat-Tabs/blob/main/tabs/re-add%20prefixes%20%26%20fix%20shortened%20tags/chat.json cause i cant write regex - "(§r)*(§9Party §8\u003e)+(.*)", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§einvited §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto the party! They have §r§c60 §r§eseconds to accept\\.§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas left the party\\.§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ejoined the party\\.§r", - "§eYou left the party\\.§r", - "§eYou have joined §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)\u0027s §r§eparty!§r", - "§cThe party was disbanded because all invites expired and the party was empty§r", - "§cYou cannot invite that player since they\u0027re not online\\.§r", - "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e, warped you to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e\u0027s house\\.§r", - "§eSkyBlock Party Warp §r§7\\([0-9]+ players?\\)§r", - "§a. §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§f §r§awarped to your server§r", - "§eYou summoned §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§f §r§eto your server\\.§r", - "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e, warped you to their house\\.§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§aenabled Private Game§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§cdisabled Private Game§r", - "§cThe party is now muted\\. §r", - "§aThe party is no longer muted\\.§r", - "§cThere are no offline players to remove\\.§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas been removed from the party\\.§r", - "§eThe party was transferred to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eby §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has promoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Leader§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has promoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Moderator§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eis now a Party Moderator§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has demoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Member§r", - "§cYou can\u0027t demote yourself!§r", - "§6Party Members \\([0-9]+\\)§r", - "§eParty Leader: §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) ?§r(?:§[a-zA-Z0-9]).§r", - "§eParty Members: §r(?:(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r(?:§[a-zA-Z0-9]) . §r)+", - "§eParty Moderators: §r(?:(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r(?:§[a-zA-Z0-9]) . §r)+", - "§eThe party invite to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas expired§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§cdisabled All Invite§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§aenabled All Invite§r", - "§cYou cannot invite that player\\.§r", - "§cYou are not allowed to invite players\\.§r", - "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before the party is disbanded\\.§r", - "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before they are removed from the party.§r", - "§cYou are not in a party right now\\.§r", - "§cThis party is currently muted\\.§r", - "(§r)*(§9P §8\u003e)+(.*)" - ), - ignoreStartsWith = null, - ignoreContains = null, - ignoreEndsWith = null, - ignoreEquals = null, - uncompiledIgnoreRegex = null, - color = TabButton.color, - hoveredColor = TabButton.hoveredColor, - selectedColor = TabButton.selectedColor, - prefix = "/pc " - ) - val guild = ChatTab( - true, - "GUILD", - unformatted = true, - lowercase = false, - startsWith = listOf("Guild >", "G >"), - contains = null, - endsWith = null, - equals = null, - uncompiledRegex = null, - ignoreStartsWith = null, - ignoreContains = null, - ignoreEndsWith = null, - ignoreEquals = null, - uncompiledIgnoreRegex = null, - color = TabButton.color, - hoveredColor = TabButton.hoveredColor, - selectedColor = TabButton.selectedColor, - prefix = "/gc " - ) - val pm = ChatTab( - true, - "PM", - unformatted = false, - lowercase = false, - startsWith = null, - contains = null, - endsWith = null, - equals = null, - uncompiledRegex = listOf("^(?§dTo|§dFrom) (?.+): §r(?.*)(?:§r)?\$"), - ignoreStartsWith = null, - ignoreContains = null, - ignoreEndsWith = null, - ignoreEquals = null, - uncompiledIgnoreRegex = null, - color = TabButton.color, - hoveredColor = TabButton.hoveredColor, - selectedColor = TabButton.selectedColor, - prefix = "/r " - ) - tabs.add(all) - tabs.add(party) - tabs.add(guild) - tabs.add(pm) - val jsonArray = JsonArray() - jsonArray.add(PARSER.parse(GSON.toJson(all)).asJsonObject) - jsonArray.add(PARSER.parse(GSON.toJson(party)).asJsonObject) - jsonArray.add(PARSER.parse(GSON.toJson(guild)).asJsonObject) - jsonArray.add(PARSER.parse(GSON.toJson(pm)).asJsonObject) - return jsonArray - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabsJson.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabsJson.kt deleted file mode 100644 index c632561..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabsJson.kt +++ /dev/null @@ -1,15 +0,0 @@ -package cc.woverflow.chatting.chat - -import com.google.gson.JsonArray -import com.google.gson.annotations.SerializedName - -data class ChatTabsJson(@SerializedName("tabs") val tabs: JsonArray, var version: Int) { - - override fun toString(): String { - return "{\"tabs\": $tabs, \"version\": $version}" - } - - companion object { - const val VERSION = 6 - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/command/ChattingCommand.kt b/src/main/kotlin/cc/woverflow/chatting/command/ChattingCommand.kt deleted file mode 100644 index 7fcd4a8..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/command/ChattingCommand.kt +++ /dev/null @@ -1,14 +0,0 @@ -package cc.woverflow.chatting.command - -import cc.polyfrost.oneconfig.utils.commands.annotations.Command -import cc.polyfrost.oneconfig.utils.commands.annotations.Main -import cc.woverflow.chatting.Chatting -import cc.woverflow.chatting.config.ChattingConfig - -@Command(value = Chatting.ID, description = "Access the " + Chatting.NAME + " GUI.") -class ChattingCommand { - @Main - fun main() { - ChattingConfig.openGui() - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt b/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt deleted file mode 100644 index 711250f..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt +++ /dev/null @@ -1,313 +0,0 @@ -package cc.woverflow.chatting.config - -import cc.polyfrost.oneconfig.config.Config -import cc.polyfrost.oneconfig.config.annotations.* -import cc.polyfrost.oneconfig.config.core.OneColor -import cc.polyfrost.oneconfig.config.data.InfoType -import cc.polyfrost.oneconfig.config.data.Mod -import cc.polyfrost.oneconfig.config.data.ModType -import cc.polyfrost.oneconfig.config.migration.VigilanceMigrator -import cc.polyfrost.oneconfig.utils.hypixel.HypixelUtils -import cc.woverflow.chatting.Chatting -import cc.woverflow.chatting.chat.ChatShortcuts -import cc.woverflow.chatting.chat.ChatTab -import cc.woverflow.chatting.chat.ChatTabs -import cc.woverflow.chatting.gui.components.TabButton -import cc.woverflow.chatting.hook.ChatLineHook -import cc.woverflow.chatting.utils.ModCompatHooks -import java.io.File - -object ChattingConfig : Config( - Mod( - Chatting.NAME, - ModType.UTIL_QOL, - "/chatting_dark.svg", - VigilanceMigrator(File(Chatting.oldModDir, Chatting.ID + ".toml").toPath().toString()) - ), "chatting.json" -) { - - @Dropdown( - name = "Text Render Type", category = "General", options = ["No Shadow", "Shadow", "Full Shadow"], - description = "Specifies how text should be rendered in the chat. Full Shadow displays a shadow on all sides of the text, while Shadow only displays a shadow on the right and bottom sides of the text." - ) - var textRenderType = 1 - - @Color( - name = "Chat Background Color", category = "General", - description = "The color of the chat background." - ) - var chatBackgroundColor = OneColor(0, 0, 0, 128) - - @Color( - name = "Copy Chat Message Background Color", category = "General", - description = "The color of the chat background when hovering over a message." - ) - var hoveredChatBackgroundColor = OneColor(80, 80, 80, 128) - - @Switch( - name = "Right Click to Copy Chat Message", category = "General", - description = "Enable right clicking on a chat message to copy it." - ) - var rightClickCopy = false - - @Switch( - name = "Compact Input Box", category = "General", - description = "Make the chat input box the same width as the chat box." - ) - var compactInputBox = false - - @Color( - name = "Input Box Background Color", category = "General", - description = "The color of the chat input box background." - ) - var inputBoxBackgroundColor = OneColor(0, 0, 0, 128) - - @Color( - name = "Chat Button Background Color", category = "General", - description = "The color of the chat button background." - ) - var chatButtonBackgroundColor = OneColor(0, 0, 0, 128) - - @Color( - name = "Chat Button Hovered Background Color", category = "General", - description = "The color of the chat button background when hovered." - ) - var chatButtonHoveredBackgroundColor = OneColor(255, 255, 255, 128) - - @Switch( - name = "Inform Outdated Mods", category = "General", - description = "Inform the user when a mod can be replaced by Chatting." - ) - var informForAlternatives = true - - @Switch( - name = "Smooth Chat Messages", - category = "Animations", subcategory = "Messages", - description = "Smoothly animate chat messages when they appear." - ) - var smoothChat = true - - @Slider( - name = "Message Animation Speed", - category = "Animations", subcategory = "Messages", - min = 0.0f, max = 1.0f, - description = "The speed at which chat messages animate." - ) - var messageSpeed = 0.5f - - @Switch( - name = "Smooth Chat Scrolling", - category = "Animations", subcategory = "Scrolling", - description = "Smoothly animate scrolling when scrolling through the chat." - ) - var smoothScrolling = true - - @Slider( - name = "Scrolling Animation Speed", - category = "Animations", subcategory = "Scrolling", - min = 0.0f, max = 1.0f, - description = "The speed at which scrolling animates." - ) - var scrollingSpeed = 0.15f - - @Switch( - name = "Remove Scroll Bar", - category = "Animations", subcategory = "Scrolling", - description = "Removes the vanilla scroll bar from the chat." - ) - var removeScrollBar = true - - @Switch( - name = "Show Chat Heads", description = "Show the chat heads of players in chat", category = "Chat Heads", - ) - var showChatHeads = true - - @Switch( - name = "Offset Non-Player Messages", - description = "Offset all messages, even if a player has not been detected.", - category = "Chat Heads" - ) - var offsetNonPlayerMessages = false - - @Switch( - name = "Hide Chat Head on Consecutive Messages", - description = "Hide the chat head if the previous message was from the same player.", - category = "Chat Heads" - ) - var hideChatHeadOnConsecutiveMessages = true - - /*/ - @Property( - type = PropertyType.SWITCH, - name = "Show Timestamp", - description = "Show message timestamp.", - category = "General" - ) - var showTimestamp = false - - @Property( - type = PropertyType.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, - category = "Player Chats", - type = InfoType.INFO - ) - var ignored = false - - @Slider( - min = 80F, max = 100F, name = "Spam Blocker Threshold", category = "Player Chats" - ) - var spamThreshold = 100 - - @Switch( - name = "Custom SkyBlock Chat Formatting (remove ranks)", category = "Player Chats" - ) - var customChatFormatting = false - - @Switch( - name = "Completely Hide Spam", category = "Player Chats" - ) - var hideSpam = false - - @Switch( - name = "Custom Chat Height", category = "Chat Window", - description = "Set a custom height for the chat window. Allows for more customization than the vanilla chat height options." - ) - var customChatHeight = false - - @Slider( - min = 180F, max = 2160F, name = "Focused Height (px)", category = "Chat Window", - description = "The height of the chat window when focused." - ) - var focusedHeight = 180 - - @Slider( - min = 180F, max = 2160F, name = "Unfocused Height (px)", category = "Chat Window", - description = "The height of the chat window when unfocused." - ) - var unfocusedHeight = 180 - - @Dropdown( - name = "Screenshot Mode", category = "Screenshotting", options = ["Save To System", "Add To Clipboard", "Both"], - description = "What to do when taking a screenshot." - ) - var copyMode = 0 - - @Checkbox( - name = "Chat Searching", category = "Searching", - description = "Enable searching through chat messages." - ) - var chatSearch = true - - @Switch( - name = "Chat Tabs", category = "Tabs", - description = "Allow filtering chat messages by a tab." - ) - var chatTabs = true - get() { - if (!field) return false - return if (hypixelOnlyChatTabs) { - HypixelUtils.INSTANCE.isHypixel - } else { - true - } - } - - @Checkbox( - name = "Enable Tabs Only on Hypixel", category = "Tabs", - description = "Only enable chat tabs on Hypixel" - ) - var hypixelOnlyChatTabs = true - - @Switch( - name = "Chat Shortcuts", category = "Shortcuts" - ) - var chatShortcuts = false - get() { - if (!field) return false - return if (hypixelOnlyChatShortcuts) { - HypixelUtils.INSTANCE.isHypixel - } else { - true - } - } - - @Checkbox( - name = "Enable Shortcuts Only on Hypixel", category = "Shortcuts" - ) - var hypixelOnlyChatShortcuts = true - - @Switch( - name = "Remove Tooltip Background", category = "Tooltips", - description = "Removes the background from tooltips." - ) - var removeTooltipBackground = false - - @Dropdown( - name = "Tooltip Text Render Type", category = "Tooltips", options = ["No Shadow", "Shadow", "Full Shadow"], - description = "The type of shadow to render on tooltips." - ) - var tooltipTextRenderType = 1 - - init { - initialize() - addDependency("offsetNonPlayerMessages", "showChatHeads") - addDependency("hideChatHeadOnConsecutiveMessages", "showChatHeads") - addDependency("hypixelOnlyChatTabs", "chatTabs") - addDependency("hypixelOnlyChatShortcuts", "chatShortcuts") - addDependency("focusedHeight", "customChatHeight") - addDependency("unfocusedHeight", "customChatHeight") - addDependency("scrollingSpeed", "smoothScrolling") - addDependency("messageSpeed", "smoothChat") - addDependency("smoothChat", "BetterChat Smooth Chat") { - return@addDependency !ModCompatHooks.betterChatSmoothMessages - } - addListener("hideChatHeadOnConsecutiveMessages") { - ChatLineHook.chatLines.map { it.get() as ChatLineHook? }.forEach { it?.updatePlayerInfo() } - } - addListener("chatTabs") { - ChatTabs.initialize() - if (!chatTabs) { - val dummy = ChatTab( - true, - "ALL", - unformatted = false, - lowercase = false, - startsWith = null, - contains = null, - endsWith = null, - equals = null, - uncompiledRegex = null, - ignoreStartsWith = null, - ignoreContains = null, - ignoreEndsWith = null, - ignoreEquals = null, - uncompiledIgnoreRegex = null, - color = TabButton.color, - hoveredColor = TabButton.hoveredColor, - selectedColor = TabButton.selectedColor, - prefix = "" - ) - dummy.initialize() - ChatTabs.currentTabs.clear() - ChatTabs.currentTabs.add(dummy) - } else { - ChatTabs.currentTabs.clear() - ChatTabs.currentTabs.add(ChatTabs.tabs[0]) - } - } - addListener("chatShortcuts") { - ChatShortcuts.initialize() - } - // addDependency("showTimestampHover", "showTimestamp") - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt deleted file mode 100644 index de590e6..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt +++ /dev/null @@ -1,103 +0,0 @@ -package cc.woverflow.chatting.gui.components - -import cc.polyfrost.oneconfig.renderer.TextRenderer -import cc.woverflow.chatting.Chatting -import cc.woverflow.chatting.config.ChattingConfig -import cc.woverflow.chatting.hook.GuiNewChatHook -import club.sk1er.patcher.config.PatcherConfig -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.GuiButton -import net.minecraft.client.renderer.GlStateManager - -/** - * Taken from ChatShortcuts under MIT License - * https://github.com/P0keDev/ChatShortcuts/blob/master/LICENSE - * @author P0keDev - */ -open class CleanButton( - buttonId: Int, - private val x: () -> Int, - private val y: () -> Int, - widthIn: Int, - heightIn: Int, - name: String, - private val renderType: () -> RenderType, - private val textColor: (packedFGColour: Int, enabled: Boolean, hovered: Boolean) -> Int = { packedFGColour: Int, enabled: Boolean, hovered: Boolean -> - var j = 14737632 - if (packedFGColour != 0) { - j = packedFGColour - } else if (!enabled) { - j = 10526880 - } else if (hovered) { - j = 16777120 - } - j - }, -) : - GuiButton(buttonId, x.invoke(), 0, widthIn, heightIn, name) { - - open fun isEnabled(): Boolean { - return false - } - - open fun onMousePress() { - - } - - override fun mousePressed(mc: Minecraft, mouseX: Int, mouseY: Int): Boolean { - val isPressed = - visible && mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height - if (isPressed) { - onMousePress() - } - return isPressed - } - - override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { - enabled = isEnabled() - xPosition = x() - yPosition = y() - if (visible) { - val fontrenderer = mc.fontRendererObj - GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) - hovered = - mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height - if (!Chatting.isPatcher || !PatcherConfig.transparentChatInputField) { - drawRect( - xPosition, - yPosition, - xPosition + width, - yPosition + height, - getBackgroundColor(hovered) - ) - } - mouseDragged(mc, mouseX, mouseY) - val j = textColor(packedFGColour, enabled, hovered) - when (renderType()) { - RenderType.NONE, RenderType.SHADOW -> { - drawCenteredString( - fontrenderer, - displayString, - xPosition + width / 2, - yPosition + (height - 8) / 2, - j - ) - } - - RenderType.FULL -> { - TextRenderer.drawBorderedText( - displayString, - ((xPosition + width / 2) - (fontrenderer.getStringWidth(displayString) / 2)).toFloat(), - (yPosition + (height - 8) / 2).toFloat(), - j, - (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity - ) - } - } - } - } - - private fun getBackgroundColor(hovered: Boolean) = - if (hovered) ChattingConfig.chatButtonHoveredBackgroundColor.rgb - else ChattingConfig.chatButtonBackgroundColor.rgb -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/ClearButton.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/ClearButton.kt deleted file mode 100644 index 6ac3d34..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/gui/components/ClearButton.kt +++ /dev/null @@ -1,42 +0,0 @@ -package cc.woverflow.chatting.gui.components - -import cc.polyfrost.oneconfig.libs.universal.ChatColor -import cc.polyfrost.oneconfig.libs.universal.UChat -import cc.polyfrost.oneconfig.libs.universal.UResolution -import cc.polyfrost.oneconfig.utils.Multithreading -import cc.woverflow.chatting.Chatting -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.Gui -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.util.ResourceLocation - -class ClearButton : - CleanButton(13379014, { UResolution.scaledWidth - 28 }, { UResolution.scaledHeight - 27 }, 12, 12, "", - { RenderType.NONE }) { - - var times = 0 - - override fun onMousePress() { - ++times - if (times > 1) { - times = 0 - Minecraft.getMinecraft().ingameGUI.chatGUI.clearChatMessages() - } else { - UChat.chat(ChatColor.RED + ChatColor.BOLD.toString() + "Click again to clear the chat!") - Multithreading.runAsync { - Thread.sleep(3000) - times = 0 - } - } - } - - override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { - super.drawButton(mc, mouseX, mouseY) - if (visible) { - if (hovered) GlStateManager.color(1f, 1f, 160f / 255f) - else GlStateManager.color(1f, 1f, 1f) - mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "delete.png")) - Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/RenderType.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/RenderType.kt deleted file mode 100644 index 8a56d5b..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/gui/components/RenderType.kt +++ /dev/null @@ -1,7 +0,0 @@ -package cc.woverflow.chatting.gui.components - -enum class RenderType { - NONE, - SHADOW, - FULL -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/ScreenshotButton.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/ScreenshotButton.kt deleted file mode 100644 index 4f65427..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/gui/components/ScreenshotButton.kt +++ /dev/null @@ -1,36 +0,0 @@ -package cc.woverflow.chatting.gui.components - -import cc.polyfrost.oneconfig.libs.universal.UResolution -import cc.polyfrost.oneconfig.libs.universal.UScreen -import cc.woverflow.chatting.Chatting -import cc.woverflow.chatting.mixin.GuiNewChatAccessor -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.Gui -import net.minecraft.client.gui.GuiChat -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.util.ResourceLocation - -class ScreenshotButton : - CleanButton(448318, { UResolution.scaledWidth - 42 }, { UResolution.scaledHeight - 27 }, 12, 12, "", - { RenderType.NONE }) { - - override fun onMousePress() { - val chat = Minecraft.getMinecraft().ingameGUI.chatGUI - if (UScreen.currentScreen is GuiChat) { - Chatting.screenshotChat((chat as GuiNewChatAccessor).scrollPos) - } - } - - override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { - super.drawButton(mc, mouseX, mouseY) - if (visible) { - if (hovered) { - GlStateManager.color(1f, 1f, 160f / 255f) - } else { - GlStateManager.color(1f, 1f, 1f) - } - mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "screenshot.png")) - Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/SearchButton.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/SearchButton.kt deleted file mode 100644 index 54e9041..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/gui/components/SearchButton.kt +++ /dev/null @@ -1,70 +0,0 @@ -package cc.woverflow.chatting.gui.components - -import cc.polyfrost.oneconfig.libs.universal.UResolution -import cc.woverflow.chatting.Chatting -import cc.woverflow.chatting.chat.ChatSearchingManager -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.Gui -import net.minecraft.client.gui.GuiTextField -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.util.ResourceLocation - -class SearchButton : - CleanButton(3993935, { UResolution.scaledWidth - 14 }, { UResolution.scaledHeight - 27 }, 12, 12, "", - { RenderType.NONE }) { - val inputField = SearchTextField() - private var chatBox = false - - override fun isEnabled(): Boolean { - return chatBox - } - - override fun onMousePress() { - chatBox = !chatBox - inputField.setEnabled(chatBox) - inputField.isFocused = chatBox - ChatSearchingManager.lastSearch = "" - inputField.text = "" - } - - override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { - inputField.drawTextBox() - super.drawButton(mc, mouseX, mouseY) - if (visible) { - mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "search.png")) - if (isEnabled()) { - GlStateManager.color(224f / 255f, 224f / 255f, 224f / 255f) - } else if (mouseX >= xPosition && mouseX <= xPosition + 10 && mouseY >= yPosition && mouseY <= yPosition + 10) { - GlStateManager.color(1f, 1f, 160f / 255f) - } else { - GlStateManager.color(1f, 1f, 1f) - } - Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) - } - } - - inner class SearchTextField : GuiTextField( - 69420, - Minecraft.getMinecraft().fontRendererObj, - UResolution.scaledWidth * 4 / 5 - 60, - UResolution.scaledHeight - 26, - UResolution.scaledWidth / 5, - 12 - ) { - - init { - maxStringLength = 100 - enableBackgroundDrawing = true - isFocused = false - text = "" - setCanLoseFocus(true) - } - - override fun drawTextBox() { - if (isEnabled()) { - if (!isFocused) isFocused = true - super.drawTextBox() - } - } - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt deleted file mode 100644 index e3b96f1..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt +++ /dev/null @@ -1,46 +0,0 @@ -package cc.woverflow.chatting.gui.components - -import cc.polyfrost.oneconfig.libs.universal.UKeyboard -import cc.polyfrost.oneconfig.libs.universal.UResolution -import cc.woverflow.chatting.chat.ChatTab -import cc.woverflow.chatting.chat.ChatTabs -import cc.woverflow.chatting.config.ChattingConfig - -class TabButton(buttonId: Int, x: Int, widthIn: Int, heightIn: Int, private val chatTab: ChatTab) : - CleanButton(buttonId, { x }, { - UResolution.scaledHeight - 26 - }, widthIn, heightIn, chatTab.name, { RenderType.values()[ChattingConfig.textRenderType] }, { packedFGColour: Int, enabled: Boolean, hovered: Boolean -> - var j = chatTab.color ?: color - if (packedFGColour != 0) { - j = packedFGColour - } else if (!enabled) { - j = chatTab.selectedColor ?: selectedColor - } else if (hovered) { - j = chatTab.hoveredColor ?: hoveredColor - } - j - }) { - - override fun onMousePress() { - if (UKeyboard.isShiftKeyDown()) { - if (ChatTabs.currentTabs.contains(chatTab)) { - ChatTabs.currentTabs.remove(chatTab) - } else { - ChatTabs.currentTabs.add(chatTab) - } - } else { - ChatTabs.currentTabs.clear() - ChatTabs.currentTabs.add(chatTab) - } - } - - override fun isEnabled(): Boolean { - return ChatTabs.currentTabs.contains(chatTab) - } - - companion object { - const val color: Int = 14737632 - const val hoveredColor: Int = 16777120 - const val selectedColor: Int = 10526880 - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/utils/EaseOutQuart.kt b/src/main/kotlin/cc/woverflow/chatting/utils/EaseOutQuart.kt deleted file mode 100644 index 7943b4d..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/utils/EaseOutQuart.kt +++ /dev/null @@ -1,7 +0,0 @@ -package cc.woverflow.chatting.utils - -import cc.polyfrost.oneconfig.gui.animations.Animation - -class EaseOutQuart(duration: Float, start: Float, end: Float, reverse: Boolean) : Animation(duration, start, end, reverse) { - override fun animate(x: Float) = -1 * (x - 1) * (x - 1) * (x - 1) * (x - 1) + 1 -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/utils/ModCompatHooks.kt b/src/main/kotlin/cc/woverflow/chatting/utils/ModCompatHooks.kt deleted file mode 100644 index 3c60c72..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/utils/ModCompatHooks.kt +++ /dev/null @@ -1,100 +0,0 @@ -package cc.woverflow.chatting.utils - -import cc.polyfrost.oneconfig.renderer.TextRenderer -import cc.polyfrost.oneconfig.utils.dsl.getAlpha -import cc.polyfrost.oneconfig.utils.dsl.mc -import cc.woverflow.chatting.Chatting.isBetterChat -import cc.woverflow.chatting.Chatting.isPatcher -import cc.woverflow.chatting.config.ChattingConfig.offsetNonPlayerMessages -import cc.woverflow.chatting.config.ChattingConfig.showChatHeads -import cc.woverflow.chatting.config.ChattingConfig.textRenderType -import cc.woverflow.chatting.hook.ChatLineHook -import cc.woverflow.chatting.hook.GuiNewChatHook -import club.sk1er.patcher.config.PatcherConfig -import com.llamalad7.betterchat.BetterChat -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.ChatLine -import net.minecraft.client.gui.FontRenderer -import net.minecraft.client.gui.Gui -import net.minecraft.client.renderer.GlStateManager - -// This exists because mixin doesn't like dummy classes -object ModCompatHooks { - @JvmStatic - val xOffset - get() = if (isBetterChat) BetterChat.getSettings().xOffset else 0 - - @JvmStatic - val yOffset - get() = if (isBetterChat) BetterChat.getSettings().yOffset else 0 - - @JvmStatic - val chatPosition - get() = if (isPatcher && PatcherConfig.chatPosition) 12 else 0 - - @JvmStatic - val betterChatSmoothMessages - get() = if (isBetterChat) BetterChat.getSettings().smooth else false - - @JvmStatic - val extendedChatLength - get() = if (isPatcher) 32667 else 0 - - @JvmStatic - val fontRenderer: FontRenderer - get() = Minecraft.getMinecraft().fontRendererObj - - @JvmStatic - fun redirectDrawString(text: String, x: Float, y: Float, color: Int, chatLine: ChatLine, screenshot: Boolean): Int { - var actualX = x - if (showChatHeads && !screenshot) { - val hook = chatLine as ChatLineHook - if (hook.hasDetected() || offsetNonPlayerMessages) { - actualX += 10f - } - val networkPlayerInfo = hook.playerInfo - if (networkPlayerInfo != null) { - GlStateManager.enableBlend() - GlStateManager.enableAlpha() - GlStateManager.enableTexture2D() - mc.textureManager.bindTexture(networkPlayerInfo.locationSkin) - GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) - GlStateManager.color(1.0f, 1.0f, 1.0f, color.getAlpha() / 255f) - Gui.drawScaledCustomSizeModalRect( - (x).toInt(), - (y - 1f).toInt(), - 8.0f, - 8.0f, - 8, - 8, - 8, - 8, - 64.0f, - 64.0f - ) - Gui.drawScaledCustomSizeModalRect( - (x).toInt(), - (y - 1f).toInt(), - 40.0f, - 8.0f, - 8, - 8, - 8, - 8, - 64.0f, - 64.0f - ) - GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) - } - } - return when (textRenderType) { - 0 -> fontRenderer.drawString(text, actualX, y, color, false) - 2 -> TextRenderer.drawBorderedText(text, - actualX, - y, - color, - (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity) - else -> fontRenderer.drawString(text, actualX, y, color, true) - } - } -} diff --git a/src/main/kotlin/cc/woverflow/chatting/utils/RenderUtils.kt b/src/main/kotlin/cc/woverflow/chatting/utils/RenderUtils.kt deleted file mode 100644 index 12051cf..0000000 --- a/src/main/kotlin/cc/woverflow/chatting/utils/RenderUtils.kt +++ /dev/null @@ -1,259 +0,0 @@ -@file:JvmName("RenderUtils") - -package cc.woverflow.chatting.utils - -import cc.polyfrost.oneconfig.utils.IOUtils -import cc.woverflow.chatting.config.ChattingConfig -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.client.renderer.texture.TextureUtil -import net.minecraft.client.shader.Framebuffer -import org.apache.commons.lang3.SystemUtils -import org.lwjgl.BufferUtils -import org.lwjgl.opengl.GL11 -import org.lwjgl.opengl.GL12 -import sun.awt.datatransfer.DataTransferer -import sun.awt.datatransfer.SunClipboard -import java.awt.Toolkit -import java.awt.image.BufferedImage -import java.io.File -import java.lang.reflect.Field -import java.lang.reflect.Method -import java.nio.ByteBuffer -import java.nio.ByteOrder -import javax.imageio.ImageIO - -/** - * Taken from https://github.com/Moulberry/HyChat - */ -fun createBindFramebuffer(w: Int, h: Int): Framebuffer { - val framebuffer = Framebuffer(w, h, false) - framebuffer.framebufferColor[0] = 0x36 / 255f - framebuffer.framebufferColor[1] = 0x39 / 255f - framebuffer.framebufferColor[2] = 0x3F / 255f - framebuffer.framebufferClear() - GlStateManager.matrixMode(5889) - GlStateManager.loadIdentity() - GlStateManager.ortho(0.0, w.toDouble(), h.toDouble(), 0.0, 1000.0, 3000.0) - GlStateManager.matrixMode(5888) - GlStateManager.loadIdentity() - GlStateManager.translate(0.0f, 0.0f, -2000.0f) - framebuffer.bindFramebuffer(true) - return framebuffer -} - -/** - * Taken from https://github.com/Moulberry/HyChat - * Modified so if not on Windows just in case it will switch it to RGB and remove the transparent background. - */ -fun BufferedImage.copyToClipboard() { - if (SystemUtils.IS_OS_WINDOWS) { - try { - val width = this.width - val height = this.height - val hdrSize = 0x28 - val buffer: ByteBuffer = ByteBuffer.allocate(hdrSize + width * height * 4) - buffer.order(ByteOrder.LITTLE_ENDIAN) - //Header size - buffer.putInt(hdrSize) - //Width - buffer.putInt(width) - //Int32 biHeight; - buffer.putInt(height) - //Int16 biPlanes; - buffer.put(1.toByte()) - buffer.put(0.toByte()) - //Int16 biBitCount; - buffer.put(32.toByte()) - buffer.put(0.toByte()) - //Compression - buffer.putInt(0) - //Int32 biSizeImage; - buffer.putInt(width * height * 4) - buffer.putInt(0) - buffer.putInt(0) - buffer.putInt(0) - buffer.putInt(0) - - //Image data - for (y in 0 until height) { - for (x in 0 until width) { - val argb: Int = this.getRGB(x, height - y - 1) - if (argb shr 24 and 0xFF == 0) { - buffer.putInt(0x00000000) - } else { - buffer.putInt(argb) - } - } - } - buffer.flip() - val hdrSizev5 = 0x7C - val bufferv5: ByteBuffer = ByteBuffer.allocate(hdrSizev5 + width * height * 4) - bufferv5.order(ByteOrder.LITTLE_ENDIAN) - //Header size - bufferv5.putInt(hdrSizev5) - //Width - bufferv5.putInt(width) - //Int32 biHeight; - bufferv5.putInt(height) - //Int16 biPlanes; - bufferv5.put(1.toByte()) - bufferv5.put(0.toByte()) - //Int16 biBitCount; - bufferv5.put(32.toByte()) - bufferv5.put(0.toByte()) - //Compression - bufferv5.putInt(0) - //Int32 biSizeImage; - bufferv5.putInt(width * height * 4) - bufferv5.putInt(0) - bufferv5.putInt(0) - bufferv5.putInt(0) - bufferv5.putInt(0) - bufferv5.order(ByteOrder.BIG_ENDIAN) - bufferv5.putInt(-0x1000000) - bufferv5.putInt(0x00FF0000) - bufferv5.putInt(0x0000FF00) - bufferv5.putInt(0x000000FF) - bufferv5.order(ByteOrder.LITTLE_ENDIAN) - - //BGRs - bufferv5.put(0x42.toByte()) - bufferv5.put(0x47.toByte()) - bufferv5.put(0x52.toByte()) - bufferv5.put(0x73.toByte()) - for (i in bufferv5.position() until hdrSizev5) { - bufferv5.put(0.toByte()) - } - - //Image data - for (y in 0 until height) { - for (x in 0 until width) { - val argb: Int = this.getRGB(x, height - y - 1) - val a = argb shr 24 and 0xFF - var r = argb shr 16 and 0xFF - var g = argb shr 8 and 0xFF - var b = argb and 0xFF - r = r * a / 0xFF - g = g * a / 0xFF - b = b * a / 0xFF - bufferv5.putInt(a shl 24 or (r shl 16) or (g shl 8) or b) - } - } - bufferv5.flip() - val clip = Toolkit.getDefaultToolkit().systemClipboard - val dt = DataTransferer.getInstance() - val f: Field = dt.javaClass.getDeclaredField("CF_DIB") - f.isAccessible = true - val format: Long = f.getLong(null) - val openClipboard: Method = clip.javaClass.getDeclaredMethod("openClipboard", SunClipboard::class.java) - openClipboard.isAccessible = true - openClipboard.invoke(clip, clip) - val publishClipboardData: Method = clip.javaClass.getDeclaredMethod( - "publishClipboardData", - Long::class.javaPrimitiveType, - ByteArray::class.java - ) - publishClipboardData.isAccessible = true - val arr: ByteArray = buffer.array() - publishClipboardData.invoke(clip, format, arr) - val closeClipboard: Method = clip.javaClass.getDeclaredMethod("closeClipboard") - closeClipboard.isAccessible = true - closeClipboard.invoke(clip) - return - } catch (e: Exception) { - e.printStackTrace() - } - } - val pixels: IntArray = - this.getRGB(0, 0, this.width, this.height, null, 0, this.width) - val newImage = BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB) - newImage.setRGB(0, 0, newImage.width, newImage.height, pixels, 0, newImage.width) - - try { - IOUtils.copyImageToClipboard(this) - } catch (e: Exception) { - e.printStackTrace() - } -} - -/** - * Taken from https://github.com/Moulberry/HyChat - */ -fun Framebuffer.screenshot(file: File): BufferedImage { - val w = this.framebufferWidth - val h = this.framebufferHeight - val i = w * h - val pixelBuffer = BufferUtils.createIntBuffer(i) - val pixelValues = IntArray(i) - GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1) - GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1) - GlStateManager.bindTexture(this.framebufferTexture) - GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer) - pixelBuffer[pixelValues] //Load buffer into array - TextureUtil.processPixelValues(pixelValues, w, h) //Flip vertically - val bufferedimage = BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) - val j = this.framebufferTextureHeight - this.framebufferHeight - for (k in j until this.framebufferTextureHeight) { - for (l in 0 until this.framebufferWidth) { - bufferedimage.setRGB(l, k - j, pixelValues[k * this.framebufferTextureWidth + l]) - } - } - if (ChattingConfig.copyMode != 1) { - try { - file.parentFile.mkdirs() - ImageIO.write(bufferedimage, "png", file) - } catch (e: Exception) { - e.printStackTrace() - } - } - return bufferedimage -} -/*/ -private val timePattern = Regex("\\[\\d+:\\d+:\\d+]") -private var lastLines = mutableListOf() -fun timestampPre() { - if (!ChattingConfig.showTimestampHover) return - val drawnChatLines = (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines - val chatLine = getChatLineOverMouse(UMouse.getTrueX().roundToInt(), UMouse.getTrueY().roundToInt()) - - lastLines.clear() - for (line in drawnChatLines) { - val chatComponent = line.chatComponent.createCopy() - val newline = ChatLine(line.updatedCounter, chatComponent, line.chatLineID) - lastLines.add(newline) - } - - drawnChatLines.map { - if (it != chatLine) it.chatComponent.siblings.removeAll { itt -> - timePattern.find(ChatColor.stripControlCodes(itt.unformattedText)!!) != null - } - } -} - -fun timestampPost() { - if (!ChattingConfig.showTimestampHover) return - val drawnChatLines = (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines - drawnChatLines.clear() - drawnChatLines.addAll(lastLines) -} - -private fun getChatLineOverMouse(mouseX: Int, mouseY: Int): ChatLine? { - val chat = Minecraft.getMinecraft().ingameGUI.chatGUI - if (!chat.chatOpen) return null - val scaledResolution = ScaledResolution(Minecraft.getMinecraft()) - val i = scaledResolution.scaleFactor - val f = chat.chatScale - val j = MathHelper.floor_float((mouseX / i - 3).toFloat() / f) - val k = MathHelper.floor_float((mouseY / i - 27).toFloat() / f) - if (j < 0 || k < 0) return null - val drawnChatLines = (chat as GuiNewChatAccessor).drawnChatLines - val l = chat.lineCount.coerceAtMost(drawnChatLines.size) - if (j <= MathHelper.floor_float(chat.chatWidth.toFloat() / f) && k < fontRenderer.FONT_HEIGHT * l + l) { - val m = k / Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT + chat.scrollPos - if (m >= 0 && m < drawnChatLines.size) - return drawnChatLines[m] - } - return null -} - - */ \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/Chatting.kt b/src/main/kotlin/org/polyfrost/chatting/Chatting.kt new file mode 100644 index 0000000..0e8745c --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/Chatting.kt @@ -0,0 +1,272 @@ +package org.polyfrost.chatting + +import cc.polyfrost.oneconfig.libs.universal.UDesktop +import cc.polyfrost.oneconfig.libs.universal.UMinecraft +import cc.polyfrost.oneconfig.libs.universal.UResolution +import cc.polyfrost.oneconfig.utils.Notifications +import cc.polyfrost.oneconfig.utils.commands.CommandManager +import cc.polyfrost.oneconfig.utils.dsl.browseLink +import org.polyfrost.chatting.chat.ChatSearchingManager +import org.polyfrost.chatting.chat.ChatShortcuts +import org.polyfrost.chatting.chat.ChatSpamBlock +import org.polyfrost.chatting.chat.ChatTabs +import org.polyfrost.chatting.command.ChattingCommand +import org.polyfrost.chatting.config.ChattingConfig +import org.polyfrost.chatting.utils.ModCompatHooks +import org.polyfrost.chatting.utils.copyToClipboard +import org.polyfrost.chatting.utils.createBindFramebuffer +import org.polyfrost.chatting.utils.screenshot +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.* +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.OpenGlHelper +import net.minecraft.client.settings.KeyBinding +import net.minecraft.client.shader.Framebuffer +import net.minecraft.util.MathHelper +import net.minecraftforge.client.event.RenderGameOverlayEvent +import net.minecraftforge.common.MinecraftForge.EVENT_BUS +import net.minecraftforge.fml.client.registry.ClientRegistry +import net.minecraftforge.fml.common.Loader +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.event.FMLInitializationEvent +import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent +import org.lwjgl.input.Keyboard +import org.polyfrost.chatting.hook.ChatLineHook +import org.polyfrost.chatting.mixin.GuiNewChatAccessor +import java.awt.image.BufferedImage +import java.io.File +import java.text.SimpleDateFormat +import java.util.* + + +@Mod( + modid = Chatting.ID, + name = Chatting.NAME, + version = Chatting.VER, + modLanguageAdapter = "cc.polyfrost.oneconfig.utils.KotlinLanguageAdapter" +) +object Chatting { + + val keybind = KeyBinding("Screenshot Chat", Keyboard.KEY_NONE, "Chatting") + const val NAME = "@NAME@" + const val VER = "@VER@" + const val ID = "@ID@" + var doTheThing = false + var isPatcher = false + private set + var isBetterChat = false + private set + var isSkytils = false + private set + var isHychat = false + private set + + private var time = -1L + var deltaTime = 17L + + private val fileFormatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd_HH.mm.ss'.png'") + + val oldModDir = File(File(Minecraft.getMinecraft().mcDataDir, "W-OVERFLOW"), NAME) + + @Mod.EventHandler + fun onInitialization(event: FMLInitializationEvent) { + ChattingConfig + CommandManager.INSTANCE.registerCommand(ChattingCommand()) + ClientRegistry.registerKeyBinding(keybind) + EVENT_BUS.register(this) + EVENT_BUS.register(ChatSpamBlock) + ChatTabs.initialize() + ChatShortcuts.initialize() + } + + @Mod.EventHandler + fun onPostInitialization(event: FMLPostInitializationEvent) { + isPatcher = Loader.isModLoaded("patcher") + isBetterChat = Loader.isModLoaded("betterchat") + isSkytils = Loader.isModLoaded("skytils") + isHychat = Loader.isModLoaded("hychat") + } + + @Mod.EventHandler + fun onForgeLoad(event: FMLLoadCompleteEvent) { + if (ChattingConfig.informForAlternatives) { + if (isHychat) { + Notifications.INSTANCE.send( + NAME, + "Hychat can be removed as it is replaced by Chatting. Click here for more information.", + Runnable { + UDesktop.browseLink("https://microcontrollersdev.github.io/Alternatives/1.8.9/hychat") + }) + } + if (isSkytils) { + try { + skytilsCompat(Class.forName("gg.skytils.skytilsmod.core.Config")) + } catch (e: Exception) { + e.printStackTrace() + try { + skytilsCompat(Class.forName("skytils.skytilsmod.core.Config")) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + if (isBetterChat) { + Notifications.INSTANCE.send( + NAME, + "BetterChat can be removed as it is replaced by Chatting. Click here to open your mods folder to delete the BetterChat file.", + Runnable { + UDesktop.open(File("./mods")) + }) + } + } + } + + private fun skytilsCompat(skytilsClass: Class<*>) { + val instance = skytilsClass.getDeclaredField("INSTANCE") + val chatTabs = skytilsClass.getDeclaredField("chatTabs") + chatTabs.isAccessible = true + if (chatTabs.getBoolean(instance)) { + Notifications.INSTANCE.send( + NAME, + "Skytils' chat tabs can be disabled as it is replace by Chatting.\nClick here to automatically do this.", + Runnable { + chatTabs.setBoolean(instance, false) + ChattingConfig.chatTabs = true + ChattingConfig.hypixelOnlyChatTabs = true + ChattingConfig.save() + skytilsClass.getMethod("markDirty").invoke(instance) + skytilsClass.getMethod("writeData").invoke(instance) + }) + } + val copyChat = skytilsClass.getDeclaredField("copyChat") + copyChat.isAccessible = true + if (copyChat.getBoolean(instance)) { + Notifications.INSTANCE.send( + NAME, + "Skytils' copy chat messages can be disabled as it is replace by Chatting.\nClick here to automatically do this.", + Runnable { + copyChat.setBoolean(instance, false) + skytilsClass.getMethod("markDirty").invoke(instance) + skytilsClass.getMethod("writeData").invoke(instance) + }) + } + } + + @SubscribeEvent + fun onRenderTick(event: RenderGameOverlayEvent.Pre) { + if (event.type == RenderGameOverlayEvent.ElementType.ALL) { + if (time == -1L) { + time = UMinecraft.getTime() + } else { + val currentTime = UMinecraft.getTime() + deltaTime = currentTime - time + time = currentTime + } + } + } + + @SubscribeEvent + fun onTickEvent(event: TickEvent.ClientTickEvent) { + if (event.phase == TickEvent.Phase.START && Minecraft.getMinecraft().theWorld != null && Minecraft.getMinecraft().thePlayer != null && (Minecraft.getMinecraft().currentScreen == null || Minecraft.getMinecraft().currentScreen is GuiChat)) { + if (doTheThing) { + screenshotChat() + doTheThing = false + } + } + } + + fun getChatHeight(opened: Boolean): Int { + var height = if (opened) ChattingConfig.focusedHeight else ChattingConfig.unfocusedHeight + height = (height * Minecraft.getMinecraft().gameSettings.chatScale).toInt() + val chatY = ModCompatHooks.yOffset + ModCompatHooks.chatPosition + if (height + chatY + 27 > (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale).toInt() - 27 - chatY) { + height = (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale).toInt() - 27 - chatY + } + return height + } + + fun screenshotLine(line: ChatLine): BufferedImage? { + val hud = Minecraft.getMinecraft().ingameGUI + val chat = hud.chatGUI + val i = MathHelper.floor_float(chat.chatWidth / chat.chatScale) + return screenshot( + hashMapOf().also { + GuiUtilRenderComponents.splitText( + line.chatComponent, + i, + Minecraft.getMinecraft().fontRendererObj, + false, + false + ).map { it.formattedText }.reversed().forEach { string -> + it[line] = string + } + } + ) + } + + private fun screenshotChat() { + screenshotChat(0) + } + + fun screenshotChat(scrollPos: Int) { + val hud = Minecraft.getMinecraft().ingameGUI + val chat = hud.chatGUI + val chatLines = LinkedHashMap() + ChatSearchingManager.filterMessages( + ChatSearchingManager.lastSearch, + (chat as GuiNewChatAccessor).drawnChatLines + )?.let { drawnLines -> + val chatHeight = + if (ChattingConfig.customChatHeight) getChatHeight(true) / 9 else GuiNewChat.calculateChatboxHeight( + Minecraft.getMinecraft().gameSettings.chatHeightFocused / 9 + ) + for (i in scrollPos until drawnLines.size.coerceAtMost(scrollPos + chatHeight)) { + chatLines[drawnLines[i]] = drawnLines[i].chatComponent.formattedText + } + + screenshot(chatLines)?.copyToClipboard() + } + } + + private fun screenshot(messages: HashMap): BufferedImage? { + if (messages.isEmpty()) { + Notifications.INSTANCE.send("Chatting", "Chat window is empty.") + return null + } + if (!OpenGlHelper.isFramebufferEnabled()) { + Notifications.INSTANCE.send( + "Chatting", + "Screenshot failed, please disable “Fast Render” in OptiFine’s “Performance” tab." + ) + return null + } + + val fr: FontRenderer = ModCompatHooks.fontRenderer + val width = messages.maxOf { fr.getStringWidth(it.value) + (if (ChattingConfig.showChatHeads && ((it.key as ChatLineHook).hasDetected() || ChattingConfig.offsetNonPlayerMessages)) 10 else 0) } + 4 + val fb: Framebuffer = createBindFramebuffer(width * 2, (messages.size * 9) * 2) + val file = File(Minecraft.getMinecraft().mcDataDir, "screenshots/chat/" + fileFormatter.format(Date())) + + GlStateManager.scale(2f, 2f, 1f) + val scale = Minecraft.getMinecraft().gameSettings.chatScale + GlStateManager.scale(scale, scale, 1f) + messages.entries.forEachIndexed { i: Int, entry: MutableMap.MutableEntry -> + ModCompatHooks.redirectDrawString(entry.value, 0f, (messages.size - 1 - i) * 9f, 0xffffff, entry.key, true) + } + + val image = fb.screenshot(file) + Minecraft.getMinecraft().entityRenderer.setupOverlayRendering() + Minecraft.getMinecraft().framebuffer.bindFramebuffer(true) + Notifications.INSTANCE.send( + "Chatting", + "Chat screenshotted successfully." + (if (ChattingConfig.copyMode != 1) "\nClick to open." else ""), + Runnable { + if (!UDesktop.open(file)) { + Notifications.INSTANCE.send("Chatting", "Could not browse!") + } + }) + return image + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt new file mode 100644 index 0000000..0d6909e --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt @@ -0,0 +1,11 @@ +package org.polyfrost.chatting.chat + +data class ChatRegexes(val regexList: List?) { + val compiledRegexList: MutableList = arrayListOf() + + init { + regexList?.forEach { + compiledRegexList.add(Regex(it)) + } + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt new file mode 100644 index 0000000..982329a --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt @@ -0,0 +1,5 @@ +package org.polyfrost.chatting.chat + +object ChatScrollingHook { + var shouldSmooth = false +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt new file mode 100644 index 0000000..d20a358 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt @@ -0,0 +1,42 @@ +package org.polyfrost.chatting.chat + +import cc.polyfrost.oneconfig.libs.caffeine.cache.Cache +import cc.polyfrost.oneconfig.libs.caffeine.cache.Caffeine +import cc.polyfrost.oneconfig.libs.universal.wrappers.message.UTextComponent +import net.minecraft.client.gui.ChatLine +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +object ChatSearchingManager { + private var counter: AtomicInteger = AtomicInteger(0) + private var POOL: ThreadPoolExecutor = ThreadPoolExecutor( + 50, 50, + 0L, TimeUnit.SECONDS, + LinkedBlockingQueue() + ) { r -> + Thread( + r, + "Chat Filter Cache Thread ${counter.incrementAndGet()}" + ) + } + + @JvmStatic + val cache: Cache> = Caffeine.newBuilder().executor(POOL).maximumSize(5000).build() + + var lastSearch = "" + + @JvmStatic + fun filterMessages(text: String, list: List): List? { + if (text.isBlank()) return list + val cached = cache.getIfPresent(text) + return cached ?: run { + cache.put(text, list.filter { + UTextComponent.stripFormatting(it.chatComponent.unformattedText).lowercase() + .contains(text.lowercase()) + }) + cache.getIfPresent(text) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt new file mode 100644 index 0000000..0c85553 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt @@ -0,0 +1,79 @@ +package org.polyfrost.chatting.chat + +import cc.polyfrost.oneconfig.config.core.ConfigUtils +import org.polyfrost.chatting.Chatting +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import java.io.File + +object ChatShortcuts { + private val oldShortcutsFile = File(Chatting.oldModDir, "chatshortcuts.json") + private val shortcutsFile = ConfigUtils.getProfileFile("chatshortcuts.json") + private val PARSER = JsonParser() + + private var initialized = false + + val shortcuts = object : ArrayList>() { + private val comparator = Comparator> { o1, o2 -> + return@Comparator o2.first.length.compareTo(o1.first.length) + } + + override fun add(element: Pair): Boolean { + val value = super.add(element) + sortWith(comparator) + return value + } + } + + fun initialize() { + if (initialized) { + return + } else { + initialized = true + } + if (shortcutsFile.exists()) { + try { + val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject + for (shortcut in jsonObj.entrySet()) { + shortcuts.add(shortcut.key to shortcut.value.asString) + } + return + } catch (_: Throwable) { + shortcutsFile.renameTo(File(shortcutsFile.parentFile, "chatshortcuts.json.bak")) + } + } + shortcutsFile.createNewFile() + if (oldShortcutsFile.exists()) { + shortcutsFile.writeText( + oldShortcutsFile.readText() + ) + } else { + shortcutsFile.writeText(JsonObject().toString()) + } + } + + fun removeShortcut(key: String) { + shortcuts.removeIf { it.first == key } + val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject + jsonObj.remove(key) + shortcutsFile.writeText(jsonObj.toString()) + } + + fun writeShortcut(key: String, value: String) { + shortcuts.add(key to value) + val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject + jsonObj.addProperty(key, value) + shortcutsFile.writeText(jsonObj.toString()) + } + + fun handleSentCommand(command: String): String { + shortcuts.forEach { + if (command == it.first || (command.startsWith(it.first) && command.substringAfter(it.first) + .startsWith(" ")) + ) { + return command.replaceFirst(it.first, it.second) + } + } + return command + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt new file mode 100644 index 0000000..da5dde8 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt @@ -0,0 +1,124 @@ +package org.polyfrost.chatting.chat + +import org.polyfrost.chatting.config.ChattingConfig +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import java.text.Normalizer +import net.minecraft.util.ChatComponentText +import net.minecraft.util.EnumChatFormatting +import net.minecraftforge.client.event.ClientChatReceivedEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object ChatSpamBlock { + /* + Made by @KTibow + Based off of Unspam (also by @KTibow) + Algorithm based off of https://paulgraham.com/spam.html + */ + private val PLAYER_MESSAGE = Regex("^(\\[VIP\\+?\\] |\\[MVP\\+?\\+?\\] |)(\\w{2,16}): (.*)$") + + @SubscribeEvent + fun onChat(event: ClientChatReceivedEvent) { + val message = event.message.unformattedText.replace(Regex("\u00A7."), "") + if (!PLAYER_MESSAGE.matches(message)) return + + val (rank, player, content) = PLAYER_MESSAGE.matchEntire(message)!!.destructured + + if (ChattingConfig.spamThreshold != 100) { + val tokens = tokenize(content) + val spamProb = findSpamProbability(tokens) + if (spamProb * 100 > ChattingConfig.spamThreshold) { + if (ChattingConfig.hideSpam) { + event.isCanceled = true + } else { + var newMessage = + EnumChatFormatting.DARK_GRAY.toString() + + EnumChatFormatting.STRIKETHROUGH.toString() + if (!ChattingConfig.customChatFormatting) { + newMessage += rank + } + newMessage += "$player${EnumChatFormatting.DARK_GRAY}: $content" + event.message = ChatComponentText(newMessage) + } + return + } + } + if (ChattingConfig.customChatFormatting) { + val coloredPlayer = findRankColor(rank) + player + EnumChatFormatting.RESET.toString() + event.message = ChatComponentText("$coloredPlayer: $content") + } + } + + private fun tokenize(message: String): MutableList { + val strippedMessage = + Normalizer.normalize(message, Normalizer.Form.NFKC) + .replace(Regex("[^\\w\\s/]"), " ") + .lowercase() + .trim() + val tokens = strippedMessage.split(Regex("\\s+")).toMutableList() + if (tokens.size <= 2) { + tokens.add("TINY_LENGTH") + } else if (tokens.size <= 4) { + tokens.add("SMALL_LENGTH") + } else if (tokens.size <= 7) { + tokens.add("MEDIUM_LENGTH") + } else { + tokens.add("LONG_LENGTH") + } + if (message.replace(Regex("[\\w\\s]"), "").length > 2) { + tokens.add("SPECIAL_CHARS") + } else if (message.replace(Regex("[\\w\\s]"), "").isNotEmpty()) { + tokens.add("SPECIAL_CHAR") + } else { + tokens.add("LOW_SPECIAL_CHARS") + } + if (message.replace(Regex("[^A-Z]"), "").length >= message.length / 4) { + tokens.add("HIGH_CAPS") + } else { + tokens.add("LOW_CAPS") + } + return tokens + } + + private fun findSpamProbability(tokens: MutableList): Double { + val tokenProbs = mutableMapOf() + for (token in tokens) { + if (!spamInfoJson.has(token)) continue + val spamInToken = spamInfoJson.get(token).asJsonObject.get("spam").asDouble + val fineInToken = spamInfoJson.get(token).asJsonObject.get("fine").asDouble + tokenProbs[token] = + ((spamInToken / messageCountsJson.get("spam").asInt) / + (fineInToken / messageCountsJson.get("fine").asInt + + spamInToken / messageCountsJson.get("spam").asInt)) + } + val spamProbs = tokenProbs.values.toMutableList() + val fineProbs = tokenProbs.values.map { 1 - it }.toMutableList() + val spamProbability = spamProbs.reduce { a, b -> a * b } + val fineProbability = fineProbs.reduce { a, b -> a * b } + return spamProbability / (spamProbability + fineProbability) + } + + private fun findRankColor(rank: String): String { + println(rank) + return when (rank) { + "[VIP] ", + "[VIP+] " -> EnumChatFormatting.GREEN.toString() + "[MVP] ", + "[MVP+] " -> EnumChatFormatting.AQUA.toString() + "[MVP++] " -> EnumChatFormatting.GOLD.toString() + else -> EnumChatFormatting.GRAY.toString() + } + } + + private fun getResourceAsText(path: String): String? = + object {}.javaClass.getResource(path)?.readText() + private val spamInfoJson: JsonObject + private val messageCountsJson: JsonObject + + init { + // Load the file spamInfo.json from resources/ + val spamInfo = getResourceAsText("/spamInfo.json") + spamInfoJson = JsonParser().parse(spamInfo).asJsonObject + messageCountsJson = JsonParser().parse(" { \"fine\": 668, \"spam\": 230 }").asJsonObject + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt new file mode 100644 index 0000000..bd65f11 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt @@ -0,0 +1,112 @@ +package org.polyfrost.chatting.chat + +import org.polyfrost.chatting.gui.components.TabButton +import com.google.gson.annotations.SerializedName +import net.minecraft.client.Minecraft +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.IChatComponent +import java.util.* + +data class ChatTab( + val enabled: Boolean, + val name: String, + val unformatted: Boolean, + val lowercase: Boolean?, + @SerializedName("starts") val startsWith: List?, + val contains: List?, + @SerializedName("ends") val endsWith: List?, + val equals: List?, + @SerializedName("regex") val uncompiledRegex: List?, + @SerializedName("ignore_starts") val ignoreStartsWith: List?, + @SerializedName("ignore_contains") val ignoreContains: List?, + @SerializedName("ignore_ends") val ignoreEndsWith: List?, + @SerializedName("ignore_equals") val ignoreEquals: List?, + @SerializedName("ignore_regex") val uncompiledIgnoreRegex: List?, + val color: Int?, + @SerializedName("hovered_color") val hoveredColor: Int?, + @SerializedName("selected_color") val selectedColor: Int?, + val prefix: String?, +) { + lateinit var button: TabButton + lateinit var compiledRegex: ChatRegexes + lateinit var compiledIgnoreRegex: ChatRegexes + + //Ugly hack to make GSON not make button / regex null + fun initialize() { + compiledRegex = ChatRegexes(uncompiledRegex) + compiledIgnoreRegex = ChatRegexes(uncompiledIgnoreRegex) + val width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(name) + button = TabButton(653452, run { + val returnValue = x - 2 + x += 6 + width + return@run returnValue + }, width + 4, 12, this) + } + + fun shouldRender(chatComponent: IChatComponent): Boolean { + val message = + (if (unformatted) EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.unformattedText) else chatComponent.formattedText).let { + if (lowercase == true) it.lowercase( + Locale.ENGLISH + ) else it + } + ignoreStartsWith?.forEach { + if (message.startsWith(it)) { + return false + } + } + ignoreEquals?.forEach { + if (message == it) { + return false + } + } + ignoreEndsWith?.forEach { + if (message.endsWith(it)) { + return false + } + } + ignoreContains?.forEach { + if (message.contains(it)) { + return false + } + } + compiledIgnoreRegex.compiledRegexList.forEach { + if (it.matches(message)) { + return false + } + } + if (startsWith.isNullOrEmpty() && equals.isNullOrEmpty() && endsWith.isNullOrEmpty() && contains.isNullOrEmpty() && uncompiledRegex.isNullOrEmpty()) { + return true + } + equals?.forEach { + if (message == it) { + return true + } + } + startsWith?.forEach { + if (message.startsWith(it)) { + return true + } + } + endsWith?.forEach { + if (message.endsWith(it)) { + return true + } + } + contains?.forEach { + if (message.contains(it)) { + return true + } + } + compiledRegex.compiledRegexList.forEach { + if (it.matches(message)) { + return true + } + } + return false + } + + companion object { + private var x = 4 + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt new file mode 100644 index 0000000..b46f55d --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt @@ -0,0 +1,354 @@ +package org.polyfrost.chatting.chat + +import cc.polyfrost.oneconfig.config.core.ConfigUtils +import org.polyfrost.chatting.Chatting +import org.polyfrost.chatting.gui.components.TabButton +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import net.minecraft.client.Minecraft +import net.minecraft.util.IChatComponent +import java.io.File + +object ChatTabs { + private val GSON = GsonBuilder().setPrettyPrinting().create() + private val PARSER = JsonParser() + val tabs = arrayListOf() + var currentTabs: ArrayList = object : ArrayList() { + override fun add(element: ChatTab?): Boolean { + if (element == null) return false + val returnValue = super.add(element) + if (Minecraft.getMinecraft().theWorld != null && returnValue) { + Minecraft.getMinecraft().ingameGUI.chatGUI.refreshChat() + } + return returnValue + } + } + var hasCancelledAnimation = false + private var initialized = false + + private val tabFile = ConfigUtils.getProfileFile("chattabs.json") + private val oldTabFile = File(Chatting.oldModDir, "chattabs.json") + + fun initialize() { + if (initialized) { + return + } else { + initialized = true + } + if (!tabFile.exists()) { + if (oldTabFile.exists()) { + tabFile.writeText(oldTabFile.readText()) + handleFile() + } else { + generateNewFile() + } + } else { + handleFile() + } + tabs.forEach { + it.initialize() + } + currentTabs.clear() + currentTabs.add(tabs[0]) + } + + private fun handleFile() { + try { + val chatTabJson = GSON.fromJson(tabFile.readText(), ChatTabsJson::class.java) + when (chatTabJson.version) { + 1 -> { + // ver 2 adds `enabled` + chatTabJson.tabs.forEach { + applyVersion2Changes(it.asJsonObject) + applyVersion3Changes(it.asJsonObject) + applyVersion4Changes(it.asJsonObject) + applyVersion5Changes(it.asJsonObject) + applyVersion6Changes(it.asJsonObject) + } + chatTabJson.version = ChatTabsJson.VERSION + tabFile.writeText(GSON.toJson(chatTabJson)) + } + 2 -> { + // ver 3 adds ignore_ + chatTabJson.tabs.forEach { + applyVersion3Changes(it.asJsonObject) + applyVersion4Changes(it.asJsonObject) + applyVersion5Changes(it.asJsonObject) + applyVersion6Changes(it.asJsonObject) + } + chatTabJson.version = ChatTabsJson.VERSION + tabFile.writeText(GSON.toJson(chatTabJson)) + } + 3 -> { + // ver 4 adds color options + chatTabJson.tabs.forEach { + applyVersion4Changes(it.asJsonObject) + applyVersion5Changes(it.asJsonObject) + applyVersion6Changes(it.asJsonObject) + } + chatTabJson.version = ChatTabsJson.VERSION + tabFile.writeText(GSON.toJson(chatTabJson)) + } + 4 -> { + // ver 5 adds lowercase + chatTabJson.tabs.forEach { + applyVersion5Changes(it.asJsonObject) + applyVersion6Changes(it.asJsonObject) + } + chatTabJson.version = ChatTabsJson.VERSION + tabFile.writeText(GSON.toJson(chatTabJson)) + } + 5 -> { + // ver 6 changes pm regex + chatTabJson.tabs.forEach { + applyVersion6Changes(it.asJsonObject) + } + chatTabJson.version = ChatTabsJson.VERSION + tabFile.writeText(GSON.toJson(chatTabJson)) + } + } + chatTabJson.tabs.forEach { + val chatTab = GSON.fromJson(it.toString(), ChatTab::class.java) + if (chatTab.enabled) { + tabs.add(chatTab) + } + } + } catch (e: Throwable) { + e.printStackTrace() + tabFile.delete() + generateNewFile() + } + } + + private fun applyVersion2Changes(json: JsonObject) { + json.addProperty("enabled", true) + } + + private fun applyVersion3Changes(json: JsonObject) { + json.add("ignore_starts", JsonArray()) + json.add("ignore_contains", JsonArray()) + json.add("ignore_ends", JsonArray()) + json.add("ignore_equals", JsonArray()) + json.add("ignore_regex", JsonArray()) + } + + private fun applyVersion4Changes(json: JsonObject) { + json.addProperty("color", TabButton.color) + json.addProperty("hovered_color", TabButton.hoveredColor) + json.addProperty("selected_color", TabButton.selectedColor) + } + + private fun applyVersion5Changes(json: JsonObject) { + json.addProperty("lowercase", false) + } + + private fun applyVersion6Changes(json: JsonObject) { + if (json.has("starts")) { + val starts = json["starts"].asJsonArray + var detected = false + starts.iterator().let { + while (it.hasNext()) { + when (it.next().asString) { + "To " -> { + detected = true + it.remove() + } + "From " -> { + detected = true + it.remove() + } + } + } + } + if (detected) { + json.add("regex", JsonArray().apply { + add(JsonPrimitive("^(?§dTo|§dFrom) (?.+): §r(?.*)(?:§r)?\$")) + }) + json.remove("unformatted") + json.addProperty("unformatted", false) + } + } + if (json.has("ends")) { + val ends = json["ends"].asJsonArray + var detected = false + ends.iterator().let { + while (it.hasNext()) { + when (it.next().asString) { + "§r§ehas invited you to join their party!", -> { + detected = true + it.remove() + } + } + } + } + if (detected) { + json.add("contains", JsonArray().apply { + add(JsonPrimitive("§r§ehas invited you to join their party!")) + }) + } + } + } + + fun shouldRender(message: IChatComponent): Boolean { + if (currentTabs.isEmpty()) return true + for (tab in currentTabs) { + if (tab?.shouldRender(message) == true) { + return true + } + } + return false + } + + private fun generateNewFile() { + tabFile.createNewFile() + val jsonObject = JsonObject() + val defaultTabs = generateDefaultTabs() + jsonObject.add("tabs", defaultTabs) + jsonObject.addProperty("version", ChatTabsJson.VERSION) + tabFile.writeText(GSON.toJson(jsonObject)) + } + + private fun generateDefaultTabs(): JsonArray { + val all = ChatTab( + true, + "ALL", + unformatted = false, + lowercase = false, + startsWith = null, + contains = null, + endsWith = null, + equals = null, + uncompiledRegex = null, + ignoreStartsWith = null, + ignoreContains = null, + ignoreEndsWith = null, + ignoreEquals = null, + uncompiledIgnoreRegex = null, + color = TabButton.color, + hoveredColor = TabButton.hoveredColor, + selectedColor = TabButton.selectedColor, + prefix = "" + ) + val party = ChatTab( + true, + "PARTY", + unformatted = false, + lowercase = false, + startsWith = listOf("§r§9Party §8> ", "§r§9P §8> ", "§eThe party was transferred to §r", "§eKicked §r"), + contains = listOf("§r§ehas invited you to join their party!"), + endsWith = listOf( + "§r§eto the party! They have §r§c60 §r§eseconds to accept.§r", + "§r§ehas disbanded the party!§r", + "§r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before they are removed from the party.§r", + " §r§ejoined the party.§r", + " §r§ehas left the party.§r", + " §r§ehas been removed from the party.§r", + "§r§e because they were offline.§r" + ), + equals = listOf("§cThe party was disbanded because all invites expired and the party was empty§r"), + uncompiledRegex = listOf( //regexes from https://github.com/kwevin/Hychat-Tabs/blob/main/tabs/re-add%20prefixes%20%26%20fix%20shortened%20tags/chat.json cause i cant write regex + "(§r)*(§9Party §8\u003e)+(.*)", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§einvited §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto the party! They have §r§c60 §r§eseconds to accept\\.§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas left the party\\.§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ejoined the party\\.§r", + "§eYou left the party\\.§r", + "§eYou have joined §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)\u0027s §r§eparty!§r", + "§cThe party was disbanded because all invites expired and the party was empty§r", + "§cYou cannot invite that player since they\u0027re not online\\.§r", + "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e, warped you to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e\u0027s house\\.§r", + "§eSkyBlock Party Warp §r§7\\([0-9]+ players?\\)§r", + "§a. §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§f §r§awarped to your server§r", + "§eYou summoned §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§f §r§eto your server\\.§r", + "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e, warped you to their house\\.§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§aenabled Private Game§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§cdisabled Private Game§r", + "§cThe party is now muted\\. §r", + "§aThe party is no longer muted\\.§r", + "§cThere are no offline players to remove\\.§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas been removed from the party\\.§r", + "§eThe party was transferred to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eby §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has promoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Leader§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has promoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Moderator§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eis now a Party Moderator§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has demoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Member§r", + "§cYou can\u0027t demote yourself!§r", + "§6Party Members \\([0-9]+\\)§r", + "§eParty Leader: §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) ?§r(?:§[a-zA-Z0-9]).§r", + "§eParty Members: §r(?:(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r(?:§[a-zA-Z0-9]) . §r)+", + "§eParty Moderators: §r(?:(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r(?:§[a-zA-Z0-9]) . §r)+", + "§eThe party invite to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas expired§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§cdisabled All Invite§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§aenabled All Invite§r", + "§cYou cannot invite that player\\.§r", + "§cYou are not allowed to invite players\\.§r", + "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before the party is disbanded\\.§r", + "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before they are removed from the party.§r", + "§cYou are not in a party right now\\.§r", + "§cThis party is currently muted\\.§r", + "(§r)*(§9P §8\u003e)+(.*)" + ), + ignoreStartsWith = null, + ignoreContains = null, + ignoreEndsWith = null, + ignoreEquals = null, + uncompiledIgnoreRegex = null, + color = TabButton.color, + hoveredColor = TabButton.hoveredColor, + selectedColor = TabButton.selectedColor, + prefix = "/pc " + ) + val guild = ChatTab( + true, + "GUILD", + unformatted = true, + lowercase = false, + startsWith = listOf("Guild >", "G >"), + contains = null, + endsWith = null, + equals = null, + uncompiledRegex = null, + ignoreStartsWith = null, + ignoreContains = null, + ignoreEndsWith = null, + ignoreEquals = null, + uncompiledIgnoreRegex = null, + color = TabButton.color, + hoveredColor = TabButton.hoveredColor, + selectedColor = TabButton.selectedColor, + prefix = "/gc " + ) + val pm = ChatTab( + true, + "PM", + unformatted = false, + lowercase = false, + startsWith = null, + contains = null, + endsWith = null, + equals = null, + uncompiledRegex = listOf("^(?§dTo|§dFrom) (?.+): §r(?.*)(?:§r)?\$"), + ignoreStartsWith = null, + ignoreContains = null, + ignoreEndsWith = null, + ignoreEquals = null, + uncompiledIgnoreRegex = null, + color = TabButton.color, + hoveredColor = TabButton.hoveredColor, + selectedColor = TabButton.selectedColor, + prefix = "/r " + ) + tabs.add(all) + tabs.add(party) + tabs.add(guild) + tabs.add(pm) + val jsonArray = JsonArray() + jsonArray.add(PARSER.parse(GSON.toJson(all)).asJsonObject) + jsonArray.add(PARSER.parse(GSON.toJson(party)).asJsonObject) + jsonArray.add(PARSER.parse(GSON.toJson(guild)).asJsonObject) + jsonArray.add(PARSER.parse(GSON.toJson(pm)).asJsonObject) + return jsonArray + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt new file mode 100644 index 0000000..c5939c3 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt @@ -0,0 +1,15 @@ +package org.polyfrost.chatting.chat + +import com.google.gson.JsonArray +import com.google.gson.annotations.SerializedName + +data class ChatTabsJson(@SerializedName("tabs") val tabs: JsonArray, var version: Int) { + + override fun toString(): String { + return "{\"tabs\": $tabs, \"version\": $version}" + } + + companion object { + const val VERSION = 6 + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/command/ChattingCommand.kt b/src/main/kotlin/org/polyfrost/chatting/command/ChattingCommand.kt new file mode 100644 index 0000000..86aff72 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/command/ChattingCommand.kt @@ -0,0 +1,14 @@ +package org.polyfrost.chatting.command + +import cc.polyfrost.oneconfig.utils.commands.annotations.Command +import cc.polyfrost.oneconfig.utils.commands.annotations.Main +import org.polyfrost.chatting.Chatting +import org.polyfrost.chatting.config.ChattingConfig + +@Command(value = Chatting.ID, description = "Access the " + Chatting.NAME + " GUI.") +class ChattingCommand { + @Main + fun main() { + ChattingConfig.openGui() + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt b/src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt new file mode 100644 index 0000000..0701471 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt @@ -0,0 +1,313 @@ +package org.polyfrost.chatting.config + +import cc.polyfrost.oneconfig.config.Config +import cc.polyfrost.oneconfig.config.annotations.* +import cc.polyfrost.oneconfig.config.core.OneColor +import cc.polyfrost.oneconfig.config.data.InfoType +import cc.polyfrost.oneconfig.config.data.Mod +import cc.polyfrost.oneconfig.config.data.ModType +import cc.polyfrost.oneconfig.config.migration.VigilanceMigrator +import cc.polyfrost.oneconfig.utils.hypixel.HypixelUtils +import org.polyfrost.chatting.Chatting +import org.polyfrost.chatting.chat.ChatShortcuts +import org.polyfrost.chatting.chat.ChatTab +import org.polyfrost.chatting.chat.ChatTabs +import org.polyfrost.chatting.gui.components.TabButton +import org.polyfrost.chatting.hook.ChatLineHook +import org.polyfrost.chatting.utils.ModCompatHooks +import java.io.File + +object ChattingConfig : Config( + Mod( + Chatting.NAME, + ModType.UTIL_QOL, + "/chatting_dark.svg", + VigilanceMigrator(File(Chatting.oldModDir, Chatting.ID + ".toml").toPath().toString()) + ), "chatting.json" +) { + + @Dropdown( + name = "Text Render Type", category = "General", options = ["No Shadow", "Shadow", "Full Shadow"], + description = "Specifies how text should be rendered in the chat. Full Shadow displays a shadow on all sides of the text, while Shadow only displays a shadow on the right and bottom sides of the text." + ) + var textRenderType = 1 + + @Color( + name = "Chat Background Color", category = "General", + description = "The color of the chat background." + ) + var chatBackgroundColor = OneColor(0, 0, 0, 128) + + @Color( + name = "Copy Chat Message Background Color", category = "General", + description = "The color of the chat background when hovering over a message." + ) + var hoveredChatBackgroundColor = OneColor(80, 80, 80, 128) + + @Switch( + name = "Right Click to Copy Chat Message", category = "General", + description = "Enable right clicking on a chat message to copy it." + ) + var rightClickCopy = false + + @Switch( + name = "Compact Input Box", category = "General", + description = "Make the chat input box the same width as the chat box." + ) + var compactInputBox = false + + @Color( + name = "Input Box Background Color", category = "General", + description = "The color of the chat input box background." + ) + var inputBoxBackgroundColor = OneColor(0, 0, 0, 128) + + @Color( + name = "Chat Button Background Color", category = "General", + description = "The color of the chat button background." + ) + var chatButtonBackgroundColor = OneColor(0, 0, 0, 128) + + @Color( + name = "Chat Button Hovered Background Color", category = "General", + description = "The color of the chat button background when hovered." + ) + var chatButtonHoveredBackgroundColor = OneColor(255, 255, 255, 128) + + @Switch( + name = "Inform Outdated Mods", category = "General", + description = "Inform the user when a mod can be replaced by Chatting." + ) + var informForAlternatives = true + + @Switch( + name = "Smooth Chat Messages", + category = "Animations", subcategory = "Messages", + description = "Smoothly animate chat messages when they appear." + ) + var smoothChat = true + + @Slider( + name = "Message Animation Speed", + category = "Animations", subcategory = "Messages", + min = 0.0f, max = 1.0f, + description = "The speed at which chat messages animate." + ) + var messageSpeed = 0.5f + + @Switch( + name = "Smooth Chat Scrolling", + category = "Animations", subcategory = "Scrolling", + description = "Smoothly animate scrolling when scrolling through the chat." + ) + var smoothScrolling = true + + @Slider( + name = "Scrolling Animation Speed", + category = "Animations", subcategory = "Scrolling", + min = 0.0f, max = 1.0f, + description = "The speed at which scrolling animates." + ) + var scrollingSpeed = 0.15f + + @Switch( + name = "Remove Scroll Bar", + category = "Animations", subcategory = "Scrolling", + description = "Removes the vanilla scroll bar from the chat." + ) + var removeScrollBar = true + + @Switch( + name = "Show Chat Heads", description = "Show the chat heads of players in chat", category = "Chat Heads", + ) + var showChatHeads = true + + @Switch( + name = "Offset Non-Player Messages", + description = "Offset all messages, even if a player has not been detected.", + category = "Chat Heads" + ) + var offsetNonPlayerMessages = false + + @Switch( + name = "Hide Chat Head on Consecutive Messages", + description = "Hide the chat head if the previous message was from the same player.", + category = "Chat Heads" + ) + var hideChatHeadOnConsecutiveMessages = true + + /*/ + @Property( + type = PropertyType.SWITCH, + name = "Show Timestamp", + description = "Show message timestamp.", + category = "General" + ) + var showTimestamp = false + + @Property( + type = PropertyType.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, + category = "Player Chats", + type = InfoType.INFO + ) + var ignored = false + + @Slider( + min = 80F, max = 100F, name = "Spam Blocker Threshold", category = "Player Chats" + ) + var spamThreshold = 100 + + @Switch( + name = "Custom SkyBlock Chat Formatting (remove ranks)", category = "Player Chats" + ) + var customChatFormatting = false + + @Switch( + name = "Completely Hide Spam", category = "Player Chats" + ) + var hideSpam = false + + @Switch( + name = "Custom Chat Height", category = "Chat Window", + description = "Set a custom height for the chat window. Allows for more customization than the vanilla chat height options." + ) + var customChatHeight = false + + @Slider( + min = 180F, max = 2160F, name = "Focused Height (px)", category = "Chat Window", + description = "The height of the chat window when focused." + ) + var focusedHeight = 180 + + @Slider( + min = 180F, max = 2160F, name = "Unfocused Height (px)", category = "Chat Window", + description = "The height of the chat window when unfocused." + ) + var unfocusedHeight = 180 + + @Dropdown( + name = "Screenshot Mode", category = "Screenshotting", options = ["Save To System", "Add To Clipboard", "Both"], + description = "What to do when taking a screenshot." + ) + var copyMode = 0 + + @Checkbox( + name = "Chat Searching", category = "Searching", + description = "Enable searching through chat messages." + ) + var chatSearch = true + + @Switch( + name = "Chat Tabs", category = "Tabs", + description = "Allow filtering chat messages by a tab." + ) + var chatTabs = true + get() { + if (!field) return false + return if (hypixelOnlyChatTabs) { + HypixelUtils.INSTANCE.isHypixel + } else { + true + } + } + + @Checkbox( + name = "Enable Tabs Only on Hypixel", category = "Tabs", + description = "Only enable chat tabs on Hypixel" + ) + var hypixelOnlyChatTabs = true + + @Switch( + name = "Chat Shortcuts", category = "Shortcuts" + ) + var chatShortcuts = false + get() { + if (!field) return false + return if (hypixelOnlyChatShortcuts) { + HypixelUtils.INSTANCE.isHypixel + } else { + true + } + } + + @Checkbox( + name = "Enable Shortcuts Only on Hypixel", category = "Shortcuts" + ) + var hypixelOnlyChatShortcuts = true + + @Switch( + name = "Remove Tooltip Background", category = "Tooltips", + description = "Removes the background from tooltips." + ) + var removeTooltipBackground = false + + @Dropdown( + name = "Tooltip Text Render Type", category = "Tooltips", options = ["No Shadow", "Shadow", "Full Shadow"], + description = "The type of shadow to render on tooltips." + ) + var tooltipTextRenderType = 1 + + init { + initialize() + addDependency("offsetNonPlayerMessages", "showChatHeads") + addDependency("hideChatHeadOnConsecutiveMessages", "showChatHeads") + addDependency("hypixelOnlyChatTabs", "chatTabs") + addDependency("hypixelOnlyChatShortcuts", "chatShortcuts") + addDependency("focusedHeight", "customChatHeight") + addDependency("unfocusedHeight", "customChatHeight") + addDependency("scrollingSpeed", "smoothScrolling") + addDependency("messageSpeed", "smoothChat") + addDependency("smoothChat", "BetterChat Smooth Chat") { + return@addDependency !ModCompatHooks.betterChatSmoothMessages + } + addListener("hideChatHeadOnConsecutiveMessages") { + ChatLineHook.chatLines.map { it.get() as ChatLineHook? }.forEach { it?.updatePlayerInfo() } + } + addListener("chatTabs") { + ChatTabs.initialize() + if (!chatTabs) { + val dummy = ChatTab( + true, + "ALL", + unformatted = false, + lowercase = false, + startsWith = null, + contains = null, + endsWith = null, + equals = null, + uncompiledRegex = null, + ignoreStartsWith = null, + ignoreContains = null, + ignoreEndsWith = null, + ignoreEquals = null, + uncompiledIgnoreRegex = null, + color = TabButton.color, + hoveredColor = TabButton.hoveredColor, + selectedColor = TabButton.selectedColor, + prefix = "" + ) + dummy.initialize() + ChatTabs.currentTabs.clear() + ChatTabs.currentTabs.add(dummy) + } else { + ChatTabs.currentTabs.clear() + ChatTabs.currentTabs.add(ChatTabs.tabs[0]) + } + } + addListener("chatShortcuts") { + ChatShortcuts.initialize() + } + // addDependency("showTimestampHover", "showTimestamp") + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/gui/components/CleanButton.kt b/src/main/kotlin/org/polyfrost/chatting/gui/components/CleanButton.kt new file mode 100644 index 0000000..d4c4acd --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/gui/components/CleanButton.kt @@ -0,0 +1,103 @@ +package org.polyfrost.chatting.gui.components + +import cc.polyfrost.oneconfig.renderer.TextRenderer +import org.polyfrost.chatting.Chatting +import org.polyfrost.chatting.config.ChattingConfig +import club.sk1er.patcher.config.PatcherConfig +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiButton +import net.minecraft.client.renderer.GlStateManager +import org.polyfrost.chatting.hook.GuiNewChatHook + +/** + * Taken from ChatShortcuts under MIT License + * https://github.com/P0keDev/ChatShortcuts/blob/master/LICENSE + * @author P0keDev + */ +open class CleanButton( + buttonId: Int, + private val x: () -> Int, + private val y: () -> Int, + widthIn: Int, + heightIn: Int, + name: String, + private val renderType: () -> RenderType, + private val textColor: (packedFGColour: Int, enabled: Boolean, hovered: Boolean) -> Int = { packedFGColour: Int, enabled: Boolean, hovered: Boolean -> + var j = 14737632 + if (packedFGColour != 0) { + j = packedFGColour + } else if (!enabled) { + j = 10526880 + } else if (hovered) { + j = 16777120 + } + j + }, +) : + GuiButton(buttonId, x.invoke(), 0, widthIn, heightIn, name) { + + open fun isEnabled(): Boolean { + return false + } + + open fun onMousePress() { + + } + + override fun mousePressed(mc: Minecraft, mouseX: Int, mouseY: Int): Boolean { + val isPressed = + visible && mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height + if (isPressed) { + onMousePress() + } + return isPressed + } + + override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { + enabled = isEnabled() + xPosition = x() + yPosition = y() + if (visible) { + val fontrenderer = mc.fontRendererObj + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) + hovered = + mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height + if (!Chatting.isPatcher || !PatcherConfig.transparentChatInputField) { + drawRect( + xPosition, + yPosition, + xPosition + width, + yPosition + height, + getBackgroundColor(hovered) + ) + } + mouseDragged(mc, mouseX, mouseY) + val j = textColor(packedFGColour, enabled, hovered) + when (renderType()) { + RenderType.NONE, RenderType.SHADOW -> { + drawCenteredString( + fontrenderer, + displayString, + xPosition + width / 2, + yPosition + (height - 8) / 2, + j + ) + } + + RenderType.FULL -> { + TextRenderer.drawBorderedText( + displayString, + ((xPosition + width / 2) - (fontrenderer.getStringWidth(displayString) / 2)).toFloat(), + (yPosition + (height - 8) / 2).toFloat(), + j, + (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity + ) + } + } + } + } + + private fun getBackgroundColor(hovered: Boolean) = + if (hovered) ChattingConfig.chatButtonHoveredBackgroundColor.rgb + else ChattingConfig.chatButtonBackgroundColor.rgb +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/gui/components/ClearButton.kt b/src/main/kotlin/org/polyfrost/chatting/gui/components/ClearButton.kt new file mode 100644 index 0000000..535cfca --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/gui/components/ClearButton.kt @@ -0,0 +1,42 @@ +package org.polyfrost.chatting.gui.components + +import cc.polyfrost.oneconfig.libs.universal.ChatColor +import cc.polyfrost.oneconfig.libs.universal.UChat +import cc.polyfrost.oneconfig.libs.universal.UResolution +import cc.polyfrost.oneconfig.utils.Multithreading +import org.polyfrost.chatting.Chatting +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.Gui +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.util.ResourceLocation + +class ClearButton : + CleanButton(13379014, { UResolution.scaledWidth - 28 }, { UResolution.scaledHeight - 27 }, 12, 12, "", + { RenderType.NONE }) { + + var times = 0 + + override fun onMousePress() { + ++times + if (times > 1) { + times = 0 + Minecraft.getMinecraft().ingameGUI.chatGUI.clearChatMessages() + } else { + UChat.chat(ChatColor.RED + ChatColor.BOLD.toString() + "Click again to clear the chat!") + Multithreading.runAsync { + Thread.sleep(3000) + times = 0 + } + } + } + + override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { + super.drawButton(mc, mouseX, mouseY) + if (visible) { + if (hovered) GlStateManager.color(1f, 1f, 160f / 255f) + else GlStateManager.color(1f, 1f, 1f) + mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "delete.png")) + Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/gui/components/RenderType.kt b/src/main/kotlin/org/polyfrost/chatting/gui/components/RenderType.kt new file mode 100644 index 0000000..a150d64 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/gui/components/RenderType.kt @@ -0,0 +1,7 @@ +package org.polyfrost.chatting.gui.components + +enum class RenderType { + NONE, + SHADOW, + FULL +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/gui/components/ScreenshotButton.kt b/src/main/kotlin/org/polyfrost/chatting/gui/components/ScreenshotButton.kt new file mode 100644 index 0000000..d8da4ad --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/gui/components/ScreenshotButton.kt @@ -0,0 +1,36 @@ +package org.polyfrost.chatting.gui.components + +import cc.polyfrost.oneconfig.libs.universal.UResolution +import cc.polyfrost.oneconfig.libs.universal.UScreen +import org.polyfrost.chatting.Chatting +import org.polyfrost.chatting.mixin.GuiNewChatAccessor +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.Gui +import net.minecraft.client.gui.GuiChat +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.util.ResourceLocation + +class ScreenshotButton : + CleanButton(448318, { UResolution.scaledWidth - 42 }, { UResolution.scaledHeight - 27 }, 12, 12, "", + { RenderType.NONE }) { + + override fun onMousePress() { + val chat = Minecraft.getMinecraft().ingameGUI.chatGUI + if (UScreen.currentScreen is GuiChat) { + Chatting.screenshotChat((chat as GuiNewChatAccessor).scrollPos) + } + } + + override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { + super.drawButton(mc, mouseX, mouseY) + if (visible) { + if (hovered) { + GlStateManager.color(1f, 1f, 160f / 255f) + } else { + GlStateManager.color(1f, 1f, 1f) + } + mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "screenshot.png")) + Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/gui/components/SearchButton.kt b/src/main/kotlin/org/polyfrost/chatting/gui/components/SearchButton.kt new file mode 100644 index 0000000..7981945 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/gui/components/SearchButton.kt @@ -0,0 +1,70 @@ +package org.polyfrost.chatting.gui.components + +import cc.polyfrost.oneconfig.libs.universal.UResolution +import org.polyfrost.chatting.Chatting +import org.polyfrost.chatting.chat.ChatSearchingManager +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.Gui +import net.minecraft.client.gui.GuiTextField +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.util.ResourceLocation + +class SearchButton : + CleanButton(3993935, { UResolution.scaledWidth - 14 }, { UResolution.scaledHeight - 27 }, 12, 12, "", + { RenderType.NONE }) { + val inputField = SearchTextField() + private var chatBox = false + + override fun isEnabled(): Boolean { + return chatBox + } + + override fun onMousePress() { + chatBox = !chatBox + inputField.setEnabled(chatBox) + inputField.isFocused = chatBox + ChatSearchingManager.lastSearch = "" + inputField.text = "" + } + + override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { + inputField.drawTextBox() + super.drawButton(mc, mouseX, mouseY) + if (visible) { + mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "search.png")) + if (isEnabled()) { + GlStateManager.color(224f / 255f, 224f / 255f, 224f / 255f) + } else if (mouseX >= xPosition && mouseX <= xPosition + 10 && mouseY >= yPosition && mouseY <= yPosition + 10) { + GlStateManager.color(1f, 1f, 160f / 255f) + } else { + GlStateManager.color(1f, 1f, 1f) + } + Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) + } + } + + inner class SearchTextField : GuiTextField( + 69420, + Minecraft.getMinecraft().fontRendererObj, + UResolution.scaledWidth * 4 / 5 - 60, + UResolution.scaledHeight - 26, + UResolution.scaledWidth / 5, + 12 + ) { + + init { + maxStringLength = 100 + enableBackgroundDrawing = true + isFocused = false + text = "" + setCanLoseFocus(true) + } + + override fun drawTextBox() { + if (isEnabled()) { + if (!isFocused) isFocused = true + super.drawTextBox() + } + } + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/gui/components/TabButton.kt b/src/main/kotlin/org/polyfrost/chatting/gui/components/TabButton.kt new file mode 100644 index 0000000..d0743c3 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/gui/components/TabButton.kt @@ -0,0 +1,46 @@ +package org.polyfrost.chatting.gui.components + +import cc.polyfrost.oneconfig.libs.universal.UKeyboard +import cc.polyfrost.oneconfig.libs.universal.UResolution +import org.polyfrost.chatting.chat.ChatTab +import org.polyfrost.chatting.chat.ChatTabs +import org.polyfrost.chatting.config.ChattingConfig + +class TabButton(buttonId: Int, x: Int, widthIn: Int, heightIn: Int, private val chatTab: ChatTab) : + CleanButton(buttonId, { x }, { + UResolution.scaledHeight - 26 + }, widthIn, heightIn, chatTab.name, { RenderType.values()[ChattingConfig.textRenderType] }, { packedFGColour: Int, enabled: Boolean, hovered: Boolean -> + var j = chatTab.color ?: color + if (packedFGColour != 0) { + j = packedFGColour + } else if (!enabled) { + j = chatTab.selectedColor ?: selectedColor + } else if (hovered) { + j = chatTab.hoveredColor ?: hoveredColor + } + j + }) { + + override fun onMousePress() { + if (UKeyboard.isShiftKeyDown()) { + if (ChatTabs.currentTabs.contains(chatTab)) { + ChatTabs.currentTabs.remove(chatTab) + } else { + ChatTabs.currentTabs.add(chatTab) + } + } else { + ChatTabs.currentTabs.clear() + ChatTabs.currentTabs.add(chatTab) + } + } + + override fun isEnabled(): Boolean { + return ChatTabs.currentTabs.contains(chatTab) + } + + companion object { + const val color: Int = 14737632 + const val hoveredColor: Int = 16777120 + const val selectedColor: Int = 10526880 + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/utils/EaseOutQuart.kt b/src/main/kotlin/org/polyfrost/chatting/utils/EaseOutQuart.kt new file mode 100644 index 0000000..4b6b7a5 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/utils/EaseOutQuart.kt @@ -0,0 +1,7 @@ +package org.polyfrost.chatting.utils + +import cc.polyfrost.oneconfig.gui.animations.Animation + +class EaseOutQuart(duration: Float, start: Float, end: Float, reverse: Boolean) : Animation(duration, start, end, reverse) { + override fun animate(x: Float) = -1 * (x - 1) * (x - 1) * (x - 1) * (x - 1) + 1 +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt b/src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt new file mode 100644 index 0000000..ad7d329 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt @@ -0,0 +1,100 @@ +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 +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.hook.ChatLineHook +import org.polyfrost.chatting.hook.GuiNewChatHook + +// This exists because mixin doesn't like dummy classes +object ModCompatHooks { + @JvmStatic + val xOffset + get() = if (isBetterChat) BetterChat.getSettings().xOffset else 0 + + @JvmStatic + val yOffset + get() = if (isBetterChat) BetterChat.getSettings().yOffset else 0 + + @JvmStatic + val chatPosition + get() = if (isPatcher && PatcherConfig.chatPosition) 12 else 0 + + @JvmStatic + val betterChatSmoothMessages + get() = if (isBetterChat) BetterChat.getSettings().smooth else false + + @JvmStatic + val extendedChatLength + get() = if (isPatcher) 32667 else 0 + + @JvmStatic + val fontRenderer: FontRenderer + get() = Minecraft.getMinecraft().fontRendererObj + + @JvmStatic + fun redirectDrawString(text: String, x: Float, y: Float, color: Int, chatLine: ChatLine, screenshot: Boolean): Int { + var actualX = x + if (showChatHeads && !screenshot) { + val hook = chatLine as ChatLineHook + if (hook.hasDetected() || offsetNonPlayerMessages) { + actualX += 10f + } + val networkPlayerInfo = hook.playerInfo + if (networkPlayerInfo != null) { + GlStateManager.enableBlend() + GlStateManager.enableAlpha() + GlStateManager.enableTexture2D() + mc.textureManager.bindTexture(networkPlayerInfo.locationSkin) + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + GlStateManager.color(1.0f, 1.0f, 1.0f, color.getAlpha() / 255f) + Gui.drawScaledCustomSizeModalRect( + (x).toInt(), + (y - 1f).toInt(), + 8.0f, + 8.0f, + 8, + 8, + 8, + 8, + 64.0f, + 64.0f + ) + Gui.drawScaledCustomSizeModalRect( + (x).toInt(), + (y - 1f).toInt(), + 40.0f, + 8.0f, + 8, + 8, + 8, + 8, + 64.0f, + 64.0f + ) + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) + } + } + return when (textRenderType) { + 0 -> fontRenderer.drawString(text, actualX, y, color, false) + 2 -> TextRenderer.drawBorderedText(text, + actualX, + y, + color, + (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity) + else -> fontRenderer.drawString(text, actualX, y, color, true) + } + } +} diff --git a/src/main/kotlin/org/polyfrost/chatting/utils/RenderUtils.kt b/src/main/kotlin/org/polyfrost/chatting/utils/RenderUtils.kt new file mode 100644 index 0000000..6eaa78b --- /dev/null +++ b/src/main/kotlin/org/polyfrost/chatting/utils/RenderUtils.kt @@ -0,0 +1,259 @@ +@file:JvmName("RenderUtils") + +package org.polyfrost.chatting.utils + +import cc.polyfrost.oneconfig.utils.IOUtils +import org.polyfrost.chatting.config.ChattingConfig +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.texture.TextureUtil +import net.minecraft.client.shader.Framebuffer +import org.apache.commons.lang3.SystemUtils +import org.lwjgl.BufferUtils +import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL12 +import sun.awt.datatransfer.DataTransferer +import sun.awt.datatransfer.SunClipboard +import java.awt.Toolkit +import java.awt.image.BufferedImage +import java.io.File +import java.lang.reflect.Field +import java.lang.reflect.Method +import java.nio.ByteBuffer +import java.nio.ByteOrder +import javax.imageio.ImageIO + +/** + * Taken from https://github.com/Moulberry/HyChat + */ +fun createBindFramebuffer(w: Int, h: Int): Framebuffer { + val framebuffer = Framebuffer(w, h, false) + framebuffer.framebufferColor[0] = 0x36 / 255f + framebuffer.framebufferColor[1] = 0x39 / 255f + framebuffer.framebufferColor[2] = 0x3F / 255f + framebuffer.framebufferClear() + GlStateManager.matrixMode(5889) + GlStateManager.loadIdentity() + GlStateManager.ortho(0.0, w.toDouble(), h.toDouble(), 0.0, 1000.0, 3000.0) + GlStateManager.matrixMode(5888) + GlStateManager.loadIdentity() + GlStateManager.translate(0.0f, 0.0f, -2000.0f) + framebuffer.bindFramebuffer(true) + return framebuffer +} + +/** + * Taken from https://github.com/Moulberry/HyChat + * Modified so if not on Windows just in case it will switch it to RGB and remove the transparent background. + */ +fun BufferedImage.copyToClipboard() { + if (SystemUtils.IS_OS_WINDOWS) { + try { + val width = this.width + val height = this.height + val hdrSize = 0x28 + val buffer: ByteBuffer = ByteBuffer.allocate(hdrSize + width * height * 4) + buffer.order(ByteOrder.LITTLE_ENDIAN) + //Header size + buffer.putInt(hdrSize) + //Width + buffer.putInt(width) + //Int32 biHeight; + buffer.putInt(height) + //Int16 biPlanes; + buffer.put(1.toByte()) + buffer.put(0.toByte()) + //Int16 biBitCount; + buffer.put(32.toByte()) + buffer.put(0.toByte()) + //Compression + buffer.putInt(0) + //Int32 biSizeImage; + buffer.putInt(width * height * 4) + buffer.putInt(0) + buffer.putInt(0) + buffer.putInt(0) + buffer.putInt(0) + + //Image data + for (y in 0 until height) { + for (x in 0 until width) { + val argb: Int = this.getRGB(x, height - y - 1) + if (argb shr 24 and 0xFF == 0) { + buffer.putInt(0x00000000) + } else { + buffer.putInt(argb) + } + } + } + buffer.flip() + val hdrSizev5 = 0x7C + val bufferv5: ByteBuffer = ByteBuffer.allocate(hdrSizev5 + width * height * 4) + bufferv5.order(ByteOrder.LITTLE_ENDIAN) + //Header size + bufferv5.putInt(hdrSizev5) + //Width + bufferv5.putInt(width) + //Int32 biHeight; + bufferv5.putInt(height) + //Int16 biPlanes; + bufferv5.put(1.toByte()) + bufferv5.put(0.toByte()) + //Int16 biBitCount; + bufferv5.put(32.toByte()) + bufferv5.put(0.toByte()) + //Compression + bufferv5.putInt(0) + //Int32 biSizeImage; + bufferv5.putInt(width * height * 4) + bufferv5.putInt(0) + bufferv5.putInt(0) + bufferv5.putInt(0) + bufferv5.putInt(0) + bufferv5.order(ByteOrder.BIG_ENDIAN) + bufferv5.putInt(-0x1000000) + bufferv5.putInt(0x00FF0000) + bufferv5.putInt(0x0000FF00) + bufferv5.putInt(0x000000FF) + bufferv5.order(ByteOrder.LITTLE_ENDIAN) + + //BGRs + bufferv5.put(0x42.toByte()) + bufferv5.put(0x47.toByte()) + bufferv5.put(0x52.toByte()) + bufferv5.put(0x73.toByte()) + for (i in bufferv5.position() until hdrSizev5) { + bufferv5.put(0.toByte()) + } + + //Image data + for (y in 0 until height) { + for (x in 0 until width) { + val argb: Int = this.getRGB(x, height - y - 1) + val a = argb shr 24 and 0xFF + var r = argb shr 16 and 0xFF + var g = argb shr 8 and 0xFF + var b = argb and 0xFF + r = r * a / 0xFF + g = g * a / 0xFF + b = b * a / 0xFF + bufferv5.putInt(a shl 24 or (r shl 16) or (g shl 8) or b) + } + } + bufferv5.flip() + val clip = Toolkit.getDefaultToolkit().systemClipboard + val dt = DataTransferer.getInstance() + val f: Field = dt.javaClass.getDeclaredField("CF_DIB") + f.isAccessible = true + val format: Long = f.getLong(null) + val openClipboard: Method = clip.javaClass.getDeclaredMethod("openClipboard", SunClipboard::class.java) + openClipboard.isAccessible = true + openClipboard.invoke(clip, clip) + val publishClipboardData: Method = clip.javaClass.getDeclaredMethod( + "publishClipboardData", + Long::class.javaPrimitiveType, + ByteArray::class.java + ) + publishClipboardData.isAccessible = true + val arr: ByteArray = buffer.array() + publishClipboardData.invoke(clip, format, arr) + val closeClipboard: Method = clip.javaClass.getDeclaredMethod("closeClipboard") + closeClipboard.isAccessible = true + closeClipboard.invoke(clip) + return + } catch (e: Exception) { + e.printStackTrace() + } + } + val pixels: IntArray = + this.getRGB(0, 0, this.width, this.height, null, 0, this.width) + val newImage = BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB) + newImage.setRGB(0, 0, newImage.width, newImage.height, pixels, 0, newImage.width) + + try { + IOUtils.copyImageToClipboard(this) + } catch (e: Exception) { + e.printStackTrace() + } +} + +/** + * Taken from https://github.com/Moulberry/HyChat + */ +fun Framebuffer.screenshot(file: File): BufferedImage { + val w = this.framebufferWidth + val h = this.framebufferHeight + val i = w * h + val pixelBuffer = BufferUtils.createIntBuffer(i) + val pixelValues = IntArray(i) + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1) + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1) + GlStateManager.bindTexture(this.framebufferTexture) + GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer) + pixelBuffer[pixelValues] //Load buffer into array + TextureUtil.processPixelValues(pixelValues, w, h) //Flip vertically + val bufferedimage = BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) + val j = this.framebufferTextureHeight - this.framebufferHeight + for (k in j until this.framebufferTextureHeight) { + for (l in 0 until this.framebufferWidth) { + bufferedimage.setRGB(l, k - j, pixelValues[k * this.framebufferTextureWidth + l]) + } + } + if (ChattingConfig.copyMode != 1) { + try { + file.parentFile.mkdirs() + ImageIO.write(bufferedimage, "png", file) + } catch (e: Exception) { + e.printStackTrace() + } + } + return bufferedimage +} +/*/ +private val timePattern = Regex("\\[\\d+:\\d+:\\d+]") +private var lastLines = mutableListOf() +fun timestampPre() { + if (!ChattingConfig.showTimestampHover) return + val drawnChatLines = (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines + val chatLine = getChatLineOverMouse(UMouse.getTrueX().roundToInt(), UMouse.getTrueY().roundToInt()) + + lastLines.clear() + for (line in drawnChatLines) { + val chatComponent = line.chatComponent.createCopy() + val newline = ChatLine(line.updatedCounter, chatComponent, line.chatLineID) + lastLines.add(newline) + } + + drawnChatLines.map { + if (it != chatLine) it.chatComponent.siblings.removeAll { itt -> + timePattern.find(ChatColor.stripControlCodes(itt.unformattedText)!!) != null + } + } +} + +fun timestampPost() { + if (!ChattingConfig.showTimestampHover) return + val drawnChatLines = (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines + drawnChatLines.clear() + drawnChatLines.addAll(lastLines) +} + +private fun getChatLineOverMouse(mouseX: Int, mouseY: Int): ChatLine? { + val chat = Minecraft.getMinecraft().ingameGUI.chatGUI + if (!chat.chatOpen) return null + val scaledResolution = ScaledResolution(Minecraft.getMinecraft()) + val i = scaledResolution.scaleFactor + val f = chat.chatScale + val j = MathHelper.floor_float((mouseX / i - 3).toFloat() / f) + val k = MathHelper.floor_float((mouseY / i - 27).toFloat() / f) + if (j < 0 || k < 0) return null + val drawnChatLines = (chat as GuiNewChatAccessor).drawnChatLines + val l = chat.lineCount.coerceAtMost(drawnChatLines.size) + if (j <= MathHelper.floor_float(chat.chatWidth.toFloat() / f) && k < fontRenderer.FONT_HEIGHT * l + l) { + val m = k / Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT + chat.scrollPos + if (m >= 0 && m < drawnChatLines.size) + return drawnChatLines[m] + } + return null +} + + */ \ No newline at end of file -- cgit