From 487709996c22fb0dbcac792076be799a09865600 Mon Sep 17 00:00:00 2001 From: Wyvest <45589059+Wyvest@users.noreply.github.com> Date: Mon, 3 Jan 2022 11:12:54 +0700 Subject: Chattils -> Chatting (1.1.0) Update images (ty Mo2men) update screenshot line tooltip (ty Mo2men) --- src/main/kotlin/cc/woverflow/chattils/Chattils.kt | 160 ------------- .../cc/woverflow/chattils/chat/ChatRegexes.kt | 11 - .../chattils/chat/ChatSearchingManager.kt | 47 ---- .../cc/woverflow/chattils/chat/ChatShortcuts.kt | 66 ------ .../kotlin/cc/woverflow/chattils/chat/ChatTab.kt | 88 -------- .../kotlin/cc/woverflow/chattils/chat/ChatTabs.kt | 174 --------------- .../cc/woverflow/chattils/chat/ChatTabsJson.kt | 11 - .../woverflow/chattils/command/ChattilsCommand.kt | 17 -- .../cc/woverflow/chattils/config/ChattilsConfig.kt | 195 ---------------- .../woverflow/chattils/gui/ChatShortcutEditGui.kt | 90 -------- .../woverflow/chattils/gui/ChatShortcutViewGui.kt | 57 ----- .../chattils/gui/components/CleanButton.kt | 70 ------ .../chattils/gui/components/ScreenshotButton.kt | 35 --- .../chattils/gui/components/SearchButton.kt | 69 ------ .../woverflow/chattils/gui/components/TabButton.kt | 19 -- .../woverflow/chattils/gui/components/TextBlock.kt | 41 ---- .../cc/woverflow/chattils/updater/DownloadGui.kt | 54 ----- .../cc/woverflow/chattils/updater/Updater.kt | 103 --------- .../woverflow/chattils/utils/ImageTransferable.kt | 22 -- .../chattils/utils/ListenableArrayList.kt | 9 - .../cc/woverflow/chattils/utils/ModCompatHooks.kt | 46 ---- .../cc/woverflow/chattils/utils/RenderHelper.kt | 248 --------------------- src/main/kotlin/cc/woverflow/chatting/Chatting.kt | 160 +++++++++++++ .../cc/woverflow/chatting/chat/ChatRegexes.kt | 11 + .../chatting/chat/ChatSearchingManager.kt | 47 ++++ .../cc/woverflow/chatting/chat/ChatShortcuts.kt | 66 ++++++ .../kotlin/cc/woverflow/chatting/chat/ChatTab.kt | 88 ++++++++ .../kotlin/cc/woverflow/chatting/chat/ChatTabs.kt | 174 +++++++++++++++ .../cc/woverflow/chatting/chat/ChatTabsJson.kt | 11 + .../woverflow/chatting/command/ChattingCommand.kt | 17 ++ .../cc/woverflow/chatting/config/ChattingConfig.kt | 195 ++++++++++++++++ .../woverflow/chatting/gui/ChatShortcutEditGui.kt | 90 ++++++++ .../woverflow/chatting/gui/ChatShortcutViewGui.kt | 57 +++++ .../chatting/gui/components/CleanButton.kt | 70 ++++++ .../chatting/gui/components/ScreenshotButton.kt | 35 +++ .../chatting/gui/components/SearchButton.kt | 69 ++++++ .../woverflow/chatting/gui/components/TabButton.kt | 19 ++ .../woverflow/chatting/gui/components/TextBlock.kt | 41 ++++ .../cc/woverflow/chatting/updater/DownloadGui.kt | 54 +++++ .../cc/woverflow/chatting/updater/Updater.kt | 98 ++++++++ .../woverflow/chatting/utils/ImageTransferable.kt | 22 ++ .../chatting/utils/ListenableArrayList.kt | 9 + .../cc/woverflow/chatting/utils/ModCompatHooks.kt | 46 ++++ .../cc/woverflow/chatting/utils/RenderHelper.kt | 248 +++++++++++++++++++++ 44 files changed, 1627 insertions(+), 1632 deletions(-) delete mode 100644 src/main/kotlin/cc/woverflow/chattils/Chattils.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/chat/ChatRegexes.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/chat/ChatSearchingManager.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/chat/ChatShortcuts.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/chat/ChatTab.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/chat/ChatTabs.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/chat/ChatTabsJson.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/command/ChattilsCommand.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/config/ChattilsConfig.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutEditGui.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutViewGui.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/gui/components/CleanButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/gui/components/ScreenshotButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/gui/components/SearchButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/gui/components/TabButton.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/gui/components/TextBlock.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/updater/DownloadGui.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/updater/Updater.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/utils/ImageTransferable.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/utils/ListenableArrayList.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/utils/ModCompatHooks.kt delete mode 100644 src/main/kotlin/cc/woverflow/chattils/utils/RenderHelper.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/Chatting.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatRegexes.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatSearchingManager.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatShortcuts.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatTab.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/chat/ChatTabsJson.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/command/ChattingCommand.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutEditGui.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutViewGui.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/ScreenshotButton.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/SearchButton.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/gui/components/TextBlock.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/updater/DownloadGui.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/updater/Updater.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/utils/ImageTransferable.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/utils/ListenableArrayList.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/utils/ModCompatHooks.kt create mode 100644 src/main/kotlin/cc/woverflow/chatting/utils/RenderHelper.kt (limited to 'src/main/kotlin/cc/woverflow') diff --git a/src/main/kotlin/cc/woverflow/chattils/Chattils.kt b/src/main/kotlin/cc/woverflow/chattils/Chattils.kt deleted file mode 100644 index 5ae5c6b..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/Chattils.kt +++ /dev/null @@ -1,160 +0,0 @@ -package cc.woverflow.chattils - -import cc.woverflow.chattils.chat.ChatSearchingManager -import cc.woverflow.chattils.chat.ChatShortcuts -import cc.woverflow.chattils.chat.ChatTabs -import cc.woverflow.chattils.command.ChattilsCommand -import cc.woverflow.chattils.config.ChattilsConfig -import cc.woverflow.chattils.hook.GuiNewChatHook -import cc.woverflow.chattils.mixin.GuiNewChatAccessor -import cc.woverflow.chattils.updater.Updater -import cc.woverflow.chattils.utils.ModCompatHooks -import cc.woverflow.chattils.utils.RenderHelper -import gg.essential.api.EssentialAPI -import gg.essential.universal.UDesktop -import gg.essential.universal.UResolution -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.* -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.client.settings.KeyBinding -import net.minecraft.client.shader.Framebuffer -import net.minecraft.util.MathHelper -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.FMLPostInitializationEvent -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent -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 = Chattils.ID, - name = Chattils.NAME, - version = Chattils.VER, - modLanguageAdapter = "gg.essential.api.utils.KotlinAdapter" -) -object Chattils { - - val keybind = KeyBinding("Screenshot Chat", Keyboard.KEY_NONE, "Chattils") - const val NAME = "@NAME@" - const val VER = "@VER@" - const val ID = "@ID@" - var doTheThing = false - lateinit var jarFile: File - private set - var isPatcher = false - private set - var isBetterChat = false - private set - - private val fileFormatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd_HH.mm.ss'.png'") - - val modDir = File(File(Minecraft.getMinecraft().mcDataDir, "W-OVERFLOW"), NAME) - - @Mod.EventHandler - fun onFMLPreInitialization(event: FMLPreInitializationEvent) { - if (!modDir.exists()) modDir.mkdirs() - jarFile = event.sourceFile - } - - @Mod.EventHandler - fun onInitialization(event: FMLInitializationEvent) { - ChattilsConfig.preload() - ChattilsCommand.register() - ClientRegistry.registerKeyBinding(keybind) - EVENT_BUS.register(this) - ChatTabs.initialize() - ChatShortcuts.initialize() - Updater.update() - } - - @Mod.EventHandler - fun onPostInitialization(event: FMLPostInitializationEvent) { - isPatcher = Loader.isModLoaded("patcher") - isBetterChat = Loader.isModLoaded("betterchat") - } - - @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) ChattilsConfig.focusedHeight else ChattilsConfig.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(GuiUtilRenderComponents.splitText(line.chatComponent, i, Minecraft.getMinecraft().fontRendererObj, false, false).map { it.formattedText }.reversed(), chat.chatWidth) - } - - private fun screenshotChat() { - screenshotChat(0) - } - - fun screenshotChat(scrollPos: Int) { - val hud = Minecraft.getMinecraft().ingameGUI - val chat = hud.chatGUI - val chatLines = ArrayList() - ChatSearchingManager.filterMessages((chat as GuiNewChatHook).prevText, (chat as GuiNewChatAccessor).drawnChatLines)?.let { drawnLines -> - val chatHeight = if (ChattilsConfig.customChatHeight) getChatHeight(true) / 9 else GuiNewChat.calculateChatboxHeight(Minecraft.getMinecraft().gameSettings.chatHeightFocused / 9) - for (i in scrollPos until drawnLines.size.coerceAtMost(scrollPos + chatHeight)) { - chatLines.add(drawnLines[i].chatComponent.formattedText) - } - - screenshot(chatLines, chat.chatWidth)?.let { - RenderHelper.copyBufferedImageToClipboard(it) - } - } - } - - private fun screenshot(messages: List, width: Int): BufferedImage? { - if (messages.isEmpty()) { - EssentialAPI.getNotifications().push("Chattils", "Chat window is empty.") - return null - } - - val fr: FontRenderer = ModCompatHooks.fontRenderer - val fb: Framebuffer = RenderHelper.createBindFramebuffer(width * 3, (messages.size * 9) * 3) - val file = File(Minecraft.getMinecraft().mcDataDir, "screenshots/chat/" + fileFormatter.format(Date())) - - GlStateManager.scale(3f, 3f, 1f) - val scale = Minecraft.getMinecraft().gameSettings.chatScale - GlStateManager.scale(scale, scale, 1f) - for (i in messages.indices) { - fr.drawStringWithShadow(messages[i], 0f, (messages.size - 1 - i) * 9f, 0xffffff) - } - - val image = RenderHelper.screenshotFramebuffer(fb, file) - Minecraft.getMinecraft().entityRenderer.setupOverlayRendering() - Minecraft.getMinecraft().framebuffer.bindFramebuffer(true) - EssentialAPI.getNotifications() - .push("Chattils", "Chat screenshotted successfully." + (if (ChattilsConfig.copyMode != 1) "\nClick to open." else "")) { - if (!UDesktop.open(file)) { - EssentialAPI.getNotifications().push("Chattils", "Could not browse!") - } - } - return image - } -} diff --git a/src/main/kotlin/cc/woverflow/chattils/chat/ChatRegexes.kt b/src/main/kotlin/cc/woverflow/chattils/chat/ChatRegexes.kt deleted file mode 100644 index 0776fa0..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/chat/ChatRegexes.kt +++ /dev/null @@ -1,11 +0,0 @@ -package cc.woverflow.chattils.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/chattils/chat/ChatSearchingManager.kt b/src/main/kotlin/cc/woverflow/chattils/chat/ChatSearchingManager.kt deleted file mode 100644 index 646c218..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/chat/ChatSearchingManager.kt +++ /dev/null @@ -1,47 +0,0 @@ -package cc.woverflow.chattils.chat - -import cc.woverflow.chattils.hook.GuiNewChatHook -import gg.essential.lib.caffeine.cache.Cache -import gg.essential.lib.caffeine.cache.Caffeine -import gg.essential.universal.wrappers.message.UTextComponent -import net.minecraft.client.Minecraft -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() - - @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) - } - } - - @JvmStatic - fun setPrevText(text: String) { - (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).prevText = text - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/chat/ChatShortcuts.kt b/src/main/kotlin/cc/woverflow/chattils/chat/ChatShortcuts.kt deleted file mode 100644 index 50cb73c..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/chat/ChatShortcuts.kt +++ /dev/null @@ -1,66 +0,0 @@ -package cc.woverflow.chattils.chat - -import cc.woverflow.chattils.Chattils -import cc.woverflow.chattils.utils.ListenableArrayList -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import java.io.File - -object ChatShortcuts { - private val shortcutsFile = File(Chattils.modDir, "chatshortcuts.json") - private val PARSER = JsonParser() - - private var initialized = false - - val shortcuts = ListenableArrayList>({ - it.sortWith(comparator) - }) - private val comparator = Comparator> { o1, o2 -> - return@Comparator o2.first.length.compareTo(o1.first.length) - } - - - fun initialize() { - if (initialized) { - return - } else { - initialized = true - } - if (!shortcutsFile.exists()) { - shortcutsFile.createNewFile() - shortcutsFile.writeText( - JsonObject().toString() - ) - } else { - val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject - for (shortcut in jsonObj.entrySet()) { - shortcuts.add(shortcut.key to shortcut.value.asString) - } - } - } - - 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/chattils/chat/ChatTab.kt b/src/main/kotlin/cc/woverflow/chattils/chat/ChatTab.kt deleted file mode 100644 index 87f28a1..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/chat/ChatTab.kt +++ /dev/null @@ -1,88 +0,0 @@ -package cc.woverflow.chattils.chat - -import cc.woverflow.chattils.gui.components.TabButton -import com.google.gson.annotations.SerializedName -import kotlinx.coroutines.runBlocking -import net.minecraft.client.Minecraft -import net.minecraft.util.EnumChatFormatting -import net.minecraft.util.IChatComponent - -data class ChatTab( - val enabled: Boolean, - val name: String, - val unformatted: Boolean, - @SerializedName("starts") val startsWith: List?, - val contains: List?, - @SerializedName("ends") val endsWith: List?, - val equals: List?, - @SerializedName("regex") val uncompiledRegex: List?, - val prefix: String -) { - lateinit var button: TabButton - lateinit var compiledRegex: ChatRegexes - - //Ugly hack to make GSON not make button / regex null - fun initialize() { - compiledRegex = ChatRegexes(uncompiledRegex) - val width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(name) - button = TabButton(653452, runBlocking { - val returnValue = x - 2 - x += 6 + width - return@runBlocking returnValue - }, width + 4, 12, this) - } - - fun shouldRender(chatComponent: IChatComponent): Boolean { - if (startsWith == null && equals == null && endsWith == null && contains == null && uncompiledRegex == null) { - return true - } - val message = - if (unformatted) EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.unformattedText) else chatComponent.formattedText - 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 - } - - override fun equals(other: Any?): Boolean { - return other is ChatTab && name == other.name && startsWith == other.startsWith && contains == other.contains && endsWith == other.endsWith && equals == other.equals && compiledRegex == other.compiledRegex - } - - override fun hashCode(): Int { - var result = name.hashCode() - result = 31 * result + (startsWith?.hashCode() ?: 0) - result = 31 * result + (contains?.hashCode() ?: 0) - result = 31 * result + (endsWith?.hashCode() ?: 0) - result = 31 * result + (equals?.hashCode() ?: 0) - result = 31 * result + (uncompiledRegex?.hashCode() ?: 0) - result = 31 * result + prefix.hashCode() - result = 31 * result + button.hashCode() - return result - } - - companion object { - private var x = 4 - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/chat/ChatTabs.kt b/src/main/kotlin/cc/woverflow/chattils/chat/ChatTabs.kt deleted file mode 100644 index ada0baa..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/chat/ChatTabs.kt +++ /dev/null @@ -1,174 +0,0 @@ -package cc.woverflow.chattils.chat - -import cc.woverflow.chattils.Chattils -import com.google.gson.GsonBuilder -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonParser -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 currentTab: ChatTab? = null - set(value) { - if (value != null) { - field = value - if (Minecraft.getMinecraft().theWorld != null) { - Minecraft.getMinecraft().ingameGUI.chatGUI.refreshChat() - } - } - } - private var initialized = false - - private val tabFile = File(Chattils.modDir, "chattabs.json") - - fun initialize() { - if (initialized) { - return - } else { - initialized = true - } - if (!tabFile.exists()) { - generateNewFile() - } else { - try { - val chatTabJson = GSON.fromJson(tabFile.readText(), ChatTabsJson::class.java) - if (chatTabJson.version == 1) { - // ver 2 adds `enabled` - chatTabJson.tabs.forEach { - it.asJsonObject.addProperty("enabled", true) - } - chatTabJson.version = 2 - tabFile.writeText(chatTabJson.toString()) - } - 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() - } - } - tabs.forEach { - it.initialize() - } - currentTab = tabs[0] - } - - fun shouldRender(message: IChatComponent): Boolean { - return currentTab?.shouldRender(message) ?: true - } - - private fun generateNewFile() { - tabFile.createNewFile() - val jsonObject = JsonObject() - val defaultTabs = generateDefaultTabs() - jsonObject.add("tabs", defaultTabs) - jsonObject.addProperty("version", 1) - tabFile.writeText(jsonObject.toString()) - } - - private fun generateDefaultTabs(): JsonArray { - val all = ChatTab(true, "ALL", false, null, null, null, null, null, "") - val party = ChatTab( - true, - "PARTY", - false, - listOf("§r§9Party §8> ", "§r§9P §8> ", "§eThe party was transferred to §r", "§eKicked §r"), - null, - listOf( - "§r§ehas invited you to join their party!", - "§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" - ), - listOf("§cThe party was disbanded because all invites expired and the party was empty§r"), - 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)+(.*)" - ), - "/pc " - ) - val guild = ChatTab( - true, - "GUILD", - true, - listOf("Guild >", "G >"), - null, - null, - null, - null, - "/gc " - ) - val pm = ChatTab( - true, - "PM", - true, - listOf("To ", "From "), - null, - null, - null, - null, - "/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/chattils/chat/ChatTabsJson.kt b/src/main/kotlin/cc/woverflow/chattils/chat/ChatTabsJson.kt deleted file mode 100644 index a63913b..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/chat/ChatTabsJson.kt +++ /dev/null @@ -1,11 +0,0 @@ -package cc.woverflow.chattils.chat - -import com.google.gson.JsonArray -import com.google.gson.annotations.SerializedName - -data class ChatTabsJson(@SerializedName("tabs") val tabs: JsonArray, @SerializedName("version") var version: Int) { - - override fun toString(): String { - return "{\"tabs\": $tabs, \"version\": \"$version\"}" - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/command/ChattilsCommand.kt b/src/main/kotlin/cc/woverflow/chattils/command/ChattilsCommand.kt deleted file mode 100644 index 67dd30e..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/command/ChattilsCommand.kt +++ /dev/null @@ -1,17 +0,0 @@ -package cc.woverflow.chattils.command - -import cc.woverflow.chattils.Chattils -import cc.woverflow.chattils.config.ChattilsConfig -import gg.essential.api.EssentialAPI -import gg.essential.api.commands.Command -import gg.essential.api.commands.DefaultHandler - -object ChattilsCommand : Command(Chattils.ID, true) { - - override val commandAliases: Set = setOf(Alias("stratus")) - - @DefaultHandler - fun handle() { - EssentialAPI.getGuiUtil().openScreen(ChattilsConfig.gui()) - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/config/ChattilsConfig.kt b/src/main/kotlin/cc/woverflow/chattils/config/ChattilsConfig.kt deleted file mode 100644 index d662755..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/config/ChattilsConfig.kt +++ /dev/null @@ -1,195 +0,0 @@ -package cc.woverflow.chattils.config - -import cc.woverflow.chattils.Chattils -import cc.woverflow.chattils.chat.ChatShortcuts -import cc.woverflow.chattils.chat.ChatTab -import cc.woverflow.chattils.chat.ChatTabs -import cc.woverflow.chattils.gui.ChatShortcutViewGui -import cc.woverflow.chattils.updater.DownloadGui -import cc.woverflow.chattils.updater.Updater -import gg.essential.api.EssentialAPI -import gg.essential.vigilance.Vigilant -import gg.essential.vigilance.data.Category -import gg.essential.vigilance.data.Property -import gg.essential.vigilance.data.PropertyType -import gg.essential.vigilance.data.SortingBehavior -import java.io.File - -object ChattilsConfig : Vigilant(File(Chattils.modDir, "${Chattils.ID}.toml"), Chattils.NAME, sortingBehavior = ConfigSorting) { - - @Property( - type = PropertyType.SELECTOR, - name = "Text Render Type", - description = "Choose the type of rendering for the text.", - category = "General", - options = ["No Shadow", "Shadow", "Full Shadow"] - ) - var textRenderType = 1 - - @Property( - type = PropertyType.SWITCH, - name = "Remove Tooltip Background", - description = "Remove the tooltip background.", - category = "General" - ) - var removeTooltipBackground = false - - @Property( - type = PropertyType.SWITCH, - name = "Custom Chat Height", - description = "Allows you to change the height of chat to heights greater than before.", - category = "Chat Window" - ) - var customChatHeight = true - - @Property( - type = PropertyType.SLIDER, - min = 180, - max = 10000, - name = "Focused Height", - description = "Height in pixels.", - category = "Chat Window" - ) - var focusedHeight = 180 - - @Property( - type = PropertyType.SLIDER, - min = 180, - max = 10000, - name = "Unfocused Height", - description = "Height in pixels.", - category = "Chat Window" - ) - var unfocusedHeight = 180 - - @Property( - type = PropertyType.SELECTOR, - name = "Screenshot Mode", - description = "The mode in which screenshotting will work.", - category = "Screenshotting", - options = [ - "Save To System", - "Add To Clipboard", - "Both" - ] - ) - var copyMode = 0 - - @Property( - type = PropertyType.SWITCH, - name = "Chat Searching", - description = "Add a chat search bar.", - category = "Searching" - ) - var chatSearch = true - - @Property( - type = PropertyType.SWITCH, - name = "Chat Tabs", - description = "Add chat tabs.", - category = "Tabs" - ) - var chatTabs = true - get() { - if (!field) return false - return if (hypixelOnlyChatTabs) { - EssentialAPI.getMinecraftUtil().isHypixel() - } else { - true - } - } - - @Property( - type = PropertyType.SWITCH, - name = "Enable Tabs Only on Hypixel", - description = "Enable chat tabs only in Hypixel.", - category = "Tabs" - ) - var hypixelOnlyChatTabs = true - - @Property( - type = PropertyType.SWITCH, - name = "Chat Shortcuts", - description = "Add chat shortcuts.", - category = "Shortcuts" - ) - var chatShortcuts = false - get() { - if (!field) return false - return if (hypixelOnlyChatShortcuts) { - EssentialAPI.getMinecraftUtil().isHypixel() - } else { - true - } - } - - @Property( - type = PropertyType.SWITCH, - name = "Enable Shortcuts Only on Hypixel", - description = "Enable chat shortcuts only in Hypixel.", - category = "Shortcuts" - ) - var hypixelOnlyChatShortcuts = true - - @Property( - type = PropertyType.BUTTON, - name = "Edit Chat Shortcuts", - description = "Edit chat shortcuts.", - category = "Shortcuts" - ) - fun openChatShortcutsGUI() { - EssentialAPI.getGuiUtil().openScreen(ChatShortcutViewGui()) - } - - @Property( - type = PropertyType.SWITCH, - name = "Show Update Notification", - description = "Show a notification when you start Minecraft informing you of new updates.", - category = "Updater" - ) - var showUpdate = true - - @Property( - type = PropertyType.BUTTON, - name = "Update Now", - description = "Update by clicking the button.", - category = "Updater" - ) - fun update() { - if (Updater.shouldUpdate) EssentialAPI.getGuiUtil() - .openScreen(DownloadGui()) else EssentialAPI.getNotifications() - .push( - Chattils.NAME, - "No update had been detected at startup, and thus the update GUI has not been shown." - ) - } - - init { - initialize() - registerListener("chatTabs") { funny: Boolean -> - chatTabs = funny - ChatTabs.initialize() - if (!funny) { - val dummy = ChatTab(true, "ALL", false, null, null, null, null, null, "") - dummy.initialize() - ChatTabs.currentTab = dummy - } else { - ChatTabs.currentTab = ChatTabs.tabs[0] - } - } - registerListener("chatShortcuts") { funny: Boolean -> - chatShortcuts = funny - ChatShortcuts.initialize() - } - } - - private object ConfigSorting : SortingBehavior() { - override fun getCategoryComparator(): Comparator = Comparator { o1, o2 -> - if (o1.name == "General") return@Comparator -1 - if (o2.name == "General") return@Comparator 1 - else compareValuesBy(o1, o2) { - it.name - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutEditGui.kt b/src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutEditGui.kt deleted file mode 100644 index aa4b933..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutEditGui.kt +++ /dev/null @@ -1,90 +0,0 @@ -package cc.woverflow.chattils.gui - -import cc.woverflow.chattils.chat.ChatShortcuts -import gg.essential.api.EssentialAPI -import gg.essential.api.gui.buildConfirmationModal -import gg.essential.elementa.ElementaVersion -import gg.essential.elementa.WindowScreen -import gg.essential.elementa.components.UIBlock -import gg.essential.elementa.constraints.CenterConstraint -import gg.essential.elementa.constraints.SiblingConstraint -import gg.essential.elementa.dsl.childOf -import gg.essential.elementa.dsl.constrain -import gg.essential.elementa.dsl.percent -import gg.essential.elementa.dsl.pixels -import gg.essential.vigilance.gui.VigilancePalette -import gg.essential.vigilance.gui.settings.ButtonComponent -import gg.essential.vigilance.gui.settings.TextComponent - -class ChatShortcutEditGui(private var alias: String, private var command: String, private val editing: Boolean) : - WindowScreen(restoreCurrentGuiOnClose = true, version = ElementaVersion.V1) { - - private val initialAlias = alias - private val initialCommand = command - - override fun initScreen(width: Int, height: Int) { - super.initScreen(width, height) - val block = UIBlock(VigilancePalette.getBackground()).constrain { - this.x = CenterConstraint() - this.y = CenterConstraint() - this.width = 100.pixels() - this.height = 100.pixels() - } childOf window - TextComponent(initialAlias, "Alias", wrap = false, protected = false).constrain { - x = CenterConstraint() - y = 10.percent() - }.childOf(block).onValueChange { - if (it is String) alias = it - } - TextComponent(initialCommand, "Command", wrap = false, protected = false).constrain { - x = CenterConstraint() - y = SiblingConstraint() - }.childOf(block).onValueChange { - if (it is String) command = it - } - if (editing) { - ButtonComponent("Reset") { - EssentialAPI.getGuiUtil().openScreen(ChatShortcutEditGui(initialAlias, initialCommand, editing)) - } constrain { - x = CenterConstraint() - y = 70.percent() - } childOf window - } - ButtonComponent("Save") { - alias = alias.substringAfter("/") - command = command.substringAfter("/") - if (editing) { - ChatShortcuts.removeShortcut(initialAlias) - } - if (alias.isBlank() || command.isBlank()) { - return@ButtonComponent - } - if (ChatShortcuts.shortcuts.any { it.first == alias }) { - EssentialAPI.getGuiUtil().openScreen(ChatShortcutConfirmGui(alias, command)) - return@ButtonComponent - } - ChatShortcuts.writeShortcut(alias, command) - restorePreviousScreen() - } constrain { - x = CenterConstraint() - y = 80.percent() - } childOf window - } - - inner class ChatShortcutConfirmGui(private var alias: String, private var command: String) : - WindowScreen(restoreCurrentGuiOnClose = true, version = ElementaVersion.V1) { - override fun initScreen(width: Int, height: Int) { - super.initScreen(width, height) - EssentialAPI.getEssentialComponentFactory().buildConfirmationModal { - text = "An alias with this name already exists, are you sure you want to overwrite it?" - onConfirm = { - ChatShortcuts.writeShortcut(alias, command) - EssentialAPI.getGuiUtil().openScreen(null) - } - onDeny = { - restorePreviousScreen() - } - } childOf this@ChatShortcutConfirmGui.window - } - } -} diff --git a/src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutViewGui.kt b/src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutViewGui.kt deleted file mode 100644 index f4ad049..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/gui/ChatShortcutViewGui.kt +++ /dev/null @@ -1,57 +0,0 @@ -package cc.woverflow.chattils.gui - -import cc.woverflow.chattils.chat.ChatShortcuts -import cc.woverflow.chattils.gui.components.TextBlock -import gg.essential.api.EssentialAPI -import gg.essential.elementa.ElementaVersion -import gg.essential.elementa.WindowScreen -import gg.essential.elementa.components.UIBlock -import gg.essential.elementa.constraints.CenterConstraint -import gg.essential.elementa.constraints.RelativeWindowConstraint -import gg.essential.elementa.constraints.SiblingConstraint -import gg.essential.elementa.dsl.* -import gg.essential.vigilance.gui.VigilancePalette -import gg.essential.vigilance.gui.settings.ButtonComponent - -class ChatShortcutViewGui : WindowScreen(version = ElementaVersion.V1) { - override fun initScreen(width: Int, height: Int) { - super.initScreen(width, height) - for ((index, shortcut) in ChatShortcuts.shortcuts.withIndex()) { - val block = UIBlock(VigilancePalette.getBackground()).constrain { - x = 3.percent() - y = (index * 12).percent() - this.width = 94.percent() - this.height = 25.pixels() - } childOf this.window - TextBlock(shortcut.first).constrain { - x = RelativeWindowConstraint(0.05F) - y = CenterConstraint() - } childOf block - TextBlock(shortcut.second).constrain { - x = SiblingConstraint(10F) - y = CenterConstraint() - } childOf block - ButtonComponent("Edit") { - println("${shortcut.first} ${shortcut.second}") - EssentialAPI.getGuiUtil().openScreen(ChatShortcutEditGui(shortcut.first, shortcut.second, true)) - } constrain { - x = SiblingConstraint(20F) - y = CenterConstraint() - } childOf block - ButtonComponent("Delete") { - println("${shortcut.first} ${shortcut.second}") - ChatShortcuts.removeShortcut(shortcut.first) - EssentialAPI.getGuiUtil().openScreen(ChatShortcutViewGui()) - } constrain { - x = SiblingConstraint(5F) - y = CenterConstraint() - } childOf block - } - ButtonComponent("New") { - EssentialAPI.getGuiUtil().openScreen(ChatShortcutEditGui("", "", false)) - } constrain { - x = CenterConstraint() - y = 80.percent() - } childOf window - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/gui/components/CleanButton.kt b/src/main/kotlin/cc/woverflow/chattils/gui/components/CleanButton.kt deleted file mode 100644 index 7011518..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/gui/components/CleanButton.kt +++ /dev/null @@ -1,70 +0,0 @@ -package cc.woverflow.chattils.gui.components - -import cc.woverflow.chattils.Chattils -import club.sk1er.patcher.config.PatcherConfig -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.GuiButton -import net.minecraft.client.renderer.GlStateManager -import java.awt.Color - -/** - * 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) : - 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.invoke() - yPosition = y.invoke() - 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 (!Chattils.isPatcher || !PatcherConfig.transparentChatInputField) { - drawRect( - xPosition, - yPosition, - xPosition + width, - yPosition + height, - if (hovered) hoveredColor else color - ) - } - mouseDragged(mc, mouseX, mouseY) - var j = 14737632 - if (packedFGColour != 0) { - j = packedFGColour - } else if (!enabled) { - j = 10526880 - } else if (hovered) { - j = 16777120 - } - drawCenteredString(fontrenderer, displayString, xPosition + width / 2, yPosition + (height - 8) / 2, j) - } - } - - companion object { - private val hoveredColor = Color(255, 255, 255, 128).rgb - private val color = Color(0, 0, 0, 128).rgb - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/gui/components/ScreenshotButton.kt b/src/main/kotlin/cc/woverflow/chattils/gui/components/ScreenshotButton.kt deleted file mode 100644 index 0e6f088..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/gui/components/ScreenshotButton.kt +++ /dev/null @@ -1,35 +0,0 @@ -package cc.woverflow.chattils.gui.components - -import cc.woverflow.chattils.Chattils -import cc.woverflow.chattils.mixin.GuiNewChatAccessor -import gg.essential.api.utils.GuiUtil -import gg.essential.universal.UResolution -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 - 28 }, { UResolution.scaledHeight - 27 }, 12, 12, "") { - - override fun onMousePress() { - val chat = Minecraft.getMinecraft().ingameGUI.chatGUI - if (GuiUtil.getOpenedScreen() is GuiChat) { - Chattils.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(Chattils.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/chattils/gui/components/SearchButton.kt b/src/main/kotlin/cc/woverflow/chattils/gui/components/SearchButton.kt deleted file mode 100644 index 04a2743..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/gui/components/SearchButton.kt +++ /dev/null @@ -1,69 +0,0 @@ -package cc.woverflow.chattils.gui.components - -import cc.woverflow.chattils.Chattils -import cc.woverflow.chattils.hook.GuiNewChatHook -import gg.essential.universal.UResolution -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, "") { - val inputField = SearchTextField() - private var chatBox = false - - override fun isEnabled(): Boolean { - return chatBox - } - - override fun onMousePress() { - chatBox = !chatBox - inputField.setEnabled(chatBox) - inputField.isFocused = chatBox - (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).prevText = "" - inputField.text = "" - } - - override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { - inputField.drawTextBox() - super.drawButton(mc, mouseX, mouseY) - if (visible) { - mc.textureManager.bindTexture(ResourceLocation(Chattils.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 - 27, - 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() - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/gui/components/TabButton.kt b/src/main/kotlin/cc/woverflow/chattils/gui/components/TabButton.kt deleted file mode 100644 index 4770900..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/gui/components/TabButton.kt +++ /dev/null @@ -1,19 +0,0 @@ -package cc.woverflow.chattils.gui.components - -import cc.woverflow.chattils.chat.ChatTab -import cc.woverflow.chattils.chat.ChatTabs -import gg.essential.universal.UResolution - -class TabButton(buttonId: Int, x: Int, widthIn: Int, heightIn: Int, private val chatTab: ChatTab) : - CleanButton(buttonId, { x }, { - UResolution.scaledHeight - 26 - }, widthIn, heightIn, chatTab.name) { - - override fun onMousePress() { - ChatTabs.currentTab = chatTab - } - - override fun isEnabled(): Boolean { - return chatTab != ChatTabs.currentTab - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/gui/components/TextBlock.kt b/src/main/kotlin/cc/woverflow/chattils/gui/components/TextBlock.kt deleted file mode 100644 index c83d22d..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/gui/components/TextBlock.kt +++ /dev/null @@ -1,41 +0,0 @@ -package cc.woverflow.chattils.gui.components - -import gg.essential.elementa.components.UIBlock -import gg.essential.elementa.components.UIText -import gg.essential.elementa.constraints.ChildBasedSizeConstraint -import gg.essential.elementa.dsl.* -import gg.essential.elementa.effects.OutlineEffect -import gg.essential.elementa.state.BasicState -import gg.essential.vigilance.gui.VigilancePalette -import gg.essential.vigilance.gui.settings.SettingComponent - -/** - * Heavily modified from Vigilance under LGPLv3 (modified to be just a text block) - * https://github.com/Sk1erLLC/Vigilance/blob/master/LICENSE - */ -class TextBlock( - text: String -) : SettingComponent() { - private val textHolder = UIBlock() constrain { - width = ChildBasedSizeConstraint() + 6.pixels() - height = ChildBasedSizeConstraint() + 6.pixels() - color = VigilancePalette.getDarkHighlight().toConstraint() - } childOf this effect OutlineEffect( - VigilancePalette.getDivider(), - 1f - ).bindColor(BasicState(VigilancePalette.getDivider())) - - private val text: UIText = UIText(text) constrain { - x = 3.pixels() - y = 3.pixels() - } - - init { - this.text childOf textHolder - - constrain { - width = ChildBasedSizeConstraint() - height = ChildBasedSizeConstraint() - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/updater/DownloadGui.kt b/src/main/kotlin/cc/woverflow/chattils/updater/DownloadGui.kt deleted file mode 100644 index c7f59ec..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/updater/DownloadGui.kt +++ /dev/null @@ -1,54 +0,0 @@ -package cc.woverflow.chattils.updater - -import cc.woverflow.chattils.Chattils -import gg.essential.api.EssentialAPI -import gg.essential.api.gui.buildConfirmationModal -import gg.essential.api.utils.Multithreading -import gg.essential.elementa.ElementaVersion -import gg.essential.elementa.WindowScreen -import gg.essential.elementa.dsl.childOf -import java.io.File - -class DownloadGui : WindowScreen(ElementaVersion.V1, true, true, true, -1) { - override fun initScreen(width: Int, height: Int) { - super.initScreen(width, height) - EssentialAPI.getEssentialComponentFactory().buildConfirmationModal { - this.text = "Are you sure you want to update?" - this.secondaryText = - "(This will update from v${Chattils.VER} to ${Updater.latestTag})" - this.onConfirm = { - restorePreviousScreen() - Multithreading.runAsync { - if (Updater.download( - Updater.updateUrl, - File( - "mods/${Chattils.NAME}-${ - Updater.latestTag!!.substringAfter("v") - }.jar" - ) - ) && Updater.download( - "https://github.com/Wyvest/Deleter/releases/download/v1.2/Deleter-1.2.jar", - File(Chattils.modDir.parentFile, "Deleter-1.2.jar") - ) - ) { - EssentialAPI.getNotifications() - .push( - Chattils.NAME, - "The ingame updater has successfully installed the newest version." - ) - Updater.addShutdownHook() - Updater.shouldUpdate = false - } else { - EssentialAPI.getNotifications().push( - Chattils.NAME, - "The ingame updater has NOT installed the newest version as something went wrong." - ) - } - } - } - this.onDeny = { - restorePreviousScreen() - } - } childOf this.window - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/updater/Updater.kt b/src/main/kotlin/cc/woverflow/chattils/updater/Updater.kt deleted file mode 100644 index 35b6e79..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/updater/Updater.kt +++ /dev/null @@ -1,103 +0,0 @@ -package cc.woverflow.chattils.updater - -import cc.woverflow.chattils.Chattils -import cc.woverflow.chattils.config.ChattilsConfig -import gg.essential.api.EssentialAPI -import gg.essential.api.utils.Multithreading -import gg.essential.api.utils.WebUtil.downloadToFile -import gg.essential.api.utils.WebUtil.fetchJSON -import gg.essential.universal.UDesktop.open -import net.minecraft.client.Minecraft -import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion -import java.io.File -import java.io.IOException - -object Updater { - var updateUrl = "" - var latestTag: String? = null - var shouldUpdate = false - - fun update() { - Multithreading.runAsync { - try { - val latestRelease = - fetchJSON("https://api.github.com/repos/W-OVERFLOW/${Chattils.ID}/releases/latest").getObject() - latestTag = latestRelease["tag_name"].asString - val currentVersion = - DefaultArtifactVersion(Chattils.VER.substringBefore("-")) - val latestVersion = DefaultArtifactVersion(latestTag!!.substringAfter("v").substringBefore("-")) - if (currentVersion >= latestVersion) { - if (currentVersion != latestVersion || !Chattils.VER.contains("-")) { - return@runAsync - } - } - updateUrl = - latestRelease["assets"].asJsonArray[0].asJsonObject["browser_download_url"] - .asString - if (updateUrl.isNotEmpty()) { - if (ChattilsConfig.showUpdate) { - EssentialAPI.getNotifications().push( - Chattils.NAME, - "${Chattils.NAME} has a new update ($latestTag)! Click here to download it automatically!" - ) { EssentialAPI.getGuiUtil().openScreen(DownloadGui()) } - } - shouldUpdate = true - } - } catch (e: Exception) { - e.printStackTrace() - } - } - } - - fun download(url: String, file: File): Boolean { - var url = url - if (file.exists()) return true - url = url.replace(" ", "%20") - try { - downloadToFile(url, file, "${Chattils.NAME}/${Chattils.VER}") - } catch (e: Exception) { - e.printStackTrace() - return false - } - return file.exists() - } - - /** - * Adapted from RequisiteLaunchwrapper under LGPLv3 - * https://github.com/Qalcyo/RequisiteLaunchwrapper/blob/main/LICENSE - */ - fun addShutdownHook() { - Runtime.getRuntime().addShutdownHook(Thread { - println("Opening Deleter task...") - try { - val runtime = javaRuntime - if (Minecraft.isRunningOnMac) { - open(Chattils.jarFile.parentFile) - } - val file = File(Chattils.modDir.parentFile, "Deleter-1.2.jar") - Runtime.getRuntime() - .exec("\"" + runtime + "\" -jar \"" + file.absolutePath + "\" \"" + Chattils.jarFile.absolutePath + "\"") - } catch (e: Exception) { - e.printStackTrace() - } - Thread.currentThread().interrupt() - }) - } - - /** - * Gets the current Java runtime being used. - * - * @link https://stackoverflow.com/a/47925649 - */ - @get:Throws(IOException::class) - val javaRuntime: String - get() { - val os = System.getProperty("os.name") - val java = System.getProperty("java.home") + File.separator + "bin" + File.separator + - if (os != null && os.lowercase().startsWith("windows")) "java.exe" else "java" - if (!File(java).isFile) { - throw IOException("Unable to find suitable java runtime at $java") - } - return java - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/utils/ImageTransferable.kt b/src/main/kotlin/cc/woverflow/chattils/utils/ImageTransferable.kt deleted file mode 100644 index b23e56e..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/utils/ImageTransferable.kt +++ /dev/null @@ -1,22 +0,0 @@ -package cc.woverflow.chattils.utils - -import java.awt.Image -import java.awt.datatransfer.DataFlavor -import java.awt.datatransfer.Transferable -import java.awt.datatransfer.UnsupportedFlavorException - -data class ImageTransferable(private val image: Image) : Transferable { - - override fun getTransferDataFlavors(): Array { - return arrayOf(DataFlavor.imageFlavor) - } - - override fun isDataFlavorSupported(flavor: DataFlavor?): Boolean { - return DataFlavor.imageFlavor.equals(flavor) - } - - override fun getTransferData(flavor: DataFlavor?): Any { - if (isDataFlavorSupported(flavor)) return image - throw UnsupportedFlavorException(flavor) - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/utils/ListenableArrayList.kt b/src/main/kotlin/cc/woverflow/chattils/utils/ListenableArrayList.kt deleted file mode 100644 index 3b270fd..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/utils/ListenableArrayList.kt +++ /dev/null @@ -1,9 +0,0 @@ -package cc.woverflow.chattils.utils - -class ListenableArrayList(private val runnable: (ListenableArrayList) -> Unit, vararg elements: T): ArrayList() { - override fun add(element: T): Boolean { - val value = super.add(element) - runnable.invoke(this) - return value - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chattils/utils/ModCompatHooks.kt b/src/main/kotlin/cc/woverflow/chattils/utils/ModCompatHooks.kt deleted file mode 100644 index 65c80f0..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/utils/ModCompatHooks.kt +++ /dev/null @@ -1,46 +0,0 @@ -package cc.woverflow.chattils.utils - -import club.sk1er.patcher.config.PatcherConfig -import com.llamalad7.betterchat.BetterChat -import cc.woverflow.chattils.Chattils.isBetterChat -import cc.woverflow.chattils.Chattils.isPatcher -import cc.woverflow.chattils.config.ChattilsConfig.textRenderType -import cc.woverflow.chattils.utils.RenderHelper.drawBorderedString -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.FontRenderer - -// 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 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): Int { - return when (textRenderType) { - 0 -> { - fontRenderer.drawString(text, x, y, color, false) - } - 2 -> { - drawBorderedString(fontRenderer, text, x.toInt(), y.toInt(), color) - } - else -> fontRenderer.drawString(text, x, y, color, true) - } - } -} diff --git a/src/main/kotlin/cc/woverflow/chattils/utils/RenderHelper.kt b/src/main/kotlin/cc/woverflow/chattils/utils/RenderHelper.kt deleted file mode 100644 index 41325ca..0000000 --- a/src/main/kotlin/cc/woverflow/chattils/utils/RenderHelper.kt +++ /dev/null @@ -1,248 +0,0 @@ -package cc.woverflow.chattils.utils - -import cc.woverflow.chattils.config.ChattilsConfig -import cc.woverflow.chattils.hook.GuiNewChatHook -import net.minecraft.client.Minecraft -import net.minecraft.client.gui.FontRenderer -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 - - -object RenderHelper { - private val regex = Regex("(?i)\\u00A7[0-9a-f]") - var bypassWyvtils = false - private set - - /** - * 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 copyBufferedImageToClipboard(bufferedImage: BufferedImage) { - if (SystemUtils.IS_OS_WINDOWS) { - try { - val width = bufferedImage.width - val height = bufferedImage.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 = bufferedImage.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 = bufferedImage.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 = - bufferedImage.getRGB(0, 0, bufferedImage.width, bufferedImage.height, null, 0, bufferedImage.width) - val newImage = BufferedImage(bufferedImage.width, bufferedImage.height, BufferedImage.TYPE_INT_RGB) - newImage.setRGB(0, 0, newImage.width, newImage.height, pixels, 0, newImage.width) - - try { - Toolkit.getDefaultToolkit().systemClipboard.setContents(ImageTransferable(bufferedImage), null) - } catch (e: Exception) { - e.printStackTrace() - } - } - - - /** - * Taken from https://github.com/Moulberry/HyChat - */ - fun screenshotFramebuffer(framebuffer: Framebuffer, file: File): BufferedImage { - val w = framebuffer.framebufferWidth - val h = framebuffer.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(framebuffer.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 = framebuffer.framebufferTextureHeight - framebuffer.framebufferHeight - for (k in j until framebuffer.framebufferTextureHeight) { - for (l in 0 until framebuffer.framebufferWidth) { - bufferedimage.setRGB(l, k - j, pixelValues[k * framebuffer.framebufferTextureWidth + l]) - } - } - if (ChattilsConfig.copyMode != 1) { - try { - file.parentFile.mkdirs() - ImageIO.write(bufferedimage, "png", file) - } catch (e: Exception) { - e.printStackTrace() - } - } - return bufferedimage - } - - /** - * 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 - */ - fun drawBorderedString( - fontRendererIn: FontRenderer, - text: String, - x: Int, - y: Int, - color: Int - ): Int { - val noColors = text.replace(regex, "\u00A7r") - var yes = 0 - if (((Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity / 4) > 3) { - bypassWyvtils = true - for (xOff in -2..2) { - for (yOff in -2..2) { - if (xOff * xOff != yOff * yOff) { - yes += fontRendererIn.drawString( - noColors, - (xOff / 2f) + x, (yOff / 2f) + y, ((Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity / 4) shl 24, false - ) - } - } - } - bypassWyvtils = false - } - yes += fontRendererIn.drawString(text, x, y, color) - return yes - } -} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/Chatting.kt b/src/main/kotlin/cc/woverflow/chatting/Chatting.kt new file mode 100644 index 0000000..5fb7c67 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/Chatting.kt @@ -0,0 +1,160 @@ +package cc.woverflow.chatting + +import cc.woverflow.chatting.chat.ChatSearchingManager +import cc.woverflow.chatting.chat.ChatShortcuts +import cc.woverflow.chatting.chat.ChatTabs +import cc.woverflow.chatting.command.ChattingCommand +import cc.woverflow.chatting.config.ChattingConfig +import cc.woverflow.chatting.hook.GuiNewChatHook +import cc.woverflow.chatting.mixin.GuiNewChatAccessor +import cc.woverflow.chatting.updater.Updater +import cc.woverflow.chatting.utils.ModCompatHooks +import cc.woverflow.chatting.utils.RenderHelper +import gg.essential.api.EssentialAPI +import gg.essential.universal.UDesktop +import gg.essential.universal.UResolution +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.* +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.settings.KeyBinding +import net.minecraft.client.shader.Framebuffer +import net.minecraft.util.MathHelper +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.FMLPostInitializationEvent +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent +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 = "gg.essential.api.utils.KotlinAdapter" +) +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 + lateinit var jarFile: File + private set + var isPatcher = false + private set + var isBetterChat = false + private set + + private val fileFormatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd_HH.mm.ss'.png'") + + val modDir = File(File(Minecraft.getMinecraft().mcDataDir, "W-OVERFLOW"), NAME) + + @Mod.EventHandler + fun onFMLPreInitialization(event: FMLPreInitializationEvent) { + if (!modDir.exists()) modDir.mkdirs() + jarFile = event.sourceFile + } + + @Mod.EventHandler + fun onInitialization(event: FMLInitializationEvent) { + ChattingConfig.preload() + ChattingCommand.register() + ClientRegistry.registerKeyBinding(keybind) + EVENT_BUS.register(this) + ChatTabs.initialize() + ChatShortcuts.initialize() + Updater.update() + } + + @Mod.EventHandler + fun onPostInitialization(event: FMLPostInitializationEvent) { + isPatcher = Loader.isModLoaded("patcher") + isBetterChat = Loader.isModLoaded("betterchat") + } + + @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(GuiUtilRenderComponents.splitText(line.chatComponent, i, Minecraft.getMinecraft().fontRendererObj, false, false).map { it.formattedText }.reversed(), chat.chatWidth) + } + + private fun screenshotChat() { + screenshotChat(0) + } + + fun screenshotChat(scrollPos: Int) { + val hud = Minecraft.getMinecraft().ingameGUI + val chat = hud.chatGUI + val chatLines = ArrayList() + ChatSearchingManager.filterMessages((chat as GuiNewChatHook).prevText, (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.add(drawnLines[i].chatComponent.formattedText) + } + + screenshot(chatLines, chat.chatWidth)?.let { + RenderHelper.copyBufferedImageToClipboard(it) + } + } + } + + private fun screenshot(messages: List, width: Int): BufferedImage? { + if (messages.isEmpty()) { + EssentialAPI.getNotifications().push("Chatting", "Chat window is empty.") + return null + } + + val fr: FontRenderer = ModCompatHooks.fontRenderer + val fb: Framebuffer = RenderHelper.createBindFramebuffer(width * 3, (messages.size * 9) * 3) + val file = File(Minecraft.getMinecraft().mcDataDir, "screenshots/chat/" + fileFormatter.format(Date())) + + GlStateManager.scale(3f, 3f, 1f) + val scale = Minecraft.getMinecraft().gameSettings.chatScale + GlStateManager.scale(scale, scale, 1f) + for (i in messages.indices) { + fr.drawStringWithShadow(messages[i], 0f, (messages.size - 1 - i) * 9f, 0xffffff) + } + + val image = RenderHelper.screenshotFramebuffer(fb, file) + Minecraft.getMinecraft().entityRenderer.setupOverlayRendering() + Minecraft.getMinecraft().framebuffer.bindFramebuffer(true) + EssentialAPI.getNotifications() + .push("Chatting", "Chat screenshotted successfully." + (if (ChattingConfig.copyMode != 1) "\nClick to open." else "")) { + if (!UDesktop.open(file)) { + EssentialAPI.getNotifications().push("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 new file mode 100644 index 0000000..a0a8e86 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/chat/ChatRegexes.kt @@ -0,0 +1,11 @@ +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/ChatSearchingManager.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatSearchingManager.kt new file mode 100644 index 0000000..1e08719 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/chat/ChatSearchingManager.kt @@ -0,0 +1,47 @@ +package cc.woverflow.chatting.chat + +import cc.woverflow.chatting.hook.GuiNewChatHook +import gg.essential.lib.caffeine.cache.Cache +import gg.essential.lib.caffeine.cache.Caffeine +import gg.essential.universal.wrappers.message.UTextComponent +import net.minecraft.client.Minecraft +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() + + @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) + } + } + + @JvmStatic + fun setPrevText(text: String) { + (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).prevText = 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 new file mode 100644 index 0000000..f72967f --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/chat/ChatShortcuts.kt @@ -0,0 +1,66 @@ +package cc.woverflow.chatting.chat + +import cc.woverflow.chatting.Chatting +import cc.woverflow.chatting.utils.ListenableArrayList +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import java.io.File + +object ChatShortcuts { + private val shortcutsFile = File(Chatting.modDir, "chatshortcuts.json") + private val PARSER = JsonParser() + + private var initialized = false + + val shortcuts = ListenableArrayList>({ + it.sortWith(comparator) + }) + private val comparator = Comparator> { o1, o2 -> + return@Comparator o2.first.length.compareTo(o1.first.length) + } + + + fun initialize() { + if (initialized) { + return + } else { + initialized = true + } + if (!shortcutsFile.exists()) { + shortcutsFile.createNewFile() + shortcutsFile.writeText( + JsonObject().toString() + ) + } else { + val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject + for (shortcut in jsonObj.entrySet()) { + shortcuts.add(shortcut.key to shortcut.value.asString) + } + } + } + + 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/ChatTab.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTab.kt new file mode 100644 index 0000000..1389233 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTab.kt @@ -0,0 +1,88 @@ +package cc.woverflow.chatting.chat + +import cc.woverflow.chatting.gui.components.TabButton +import com.google.gson.annotations.SerializedName +import kotlinx.coroutines.runBlocking +import net.minecraft.client.Minecraft +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.IChatComponent + +data class ChatTab( + val enabled: Boolean, + val name: String, + val unformatted: Boolean, + @SerializedName("starts") val startsWith: List?, + val contains: List?, + @SerializedName("ends") val endsWith: List?, + val equals: List?, + @SerializedName("regex") val uncompiledRegex: List?, + val prefix: String +) { + lateinit var button: TabButton + lateinit var compiledRegex: ChatRegexes + + //Ugly hack to make GSON not make button / regex null + fun initialize() { + compiledRegex = ChatRegexes(uncompiledRegex) + val width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(name) + button = TabButton(653452, runBlocking { + val returnValue = x - 2 + x += 6 + width + return@runBlocking returnValue + }, width + 4, 12, this) + } + + fun shouldRender(chatComponent: IChatComponent): Boolean { + if (startsWith == null && equals == null && endsWith == null && contains == null && uncompiledRegex == null) { + return true + } + val message = + if (unformatted) EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.unformattedText) else chatComponent.formattedText + 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 + } + + override fun equals(other: Any?): Boolean { + return other is ChatTab && name == other.name && startsWith == other.startsWith && contains == other.contains && endsWith == other.endsWith && equals == other.equals && compiledRegex == other.compiledRegex + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + (startsWith?.hashCode() ?: 0) + result = 31 * result + (contains?.hashCode() ?: 0) + result = 31 * result + (endsWith?.hashCode() ?: 0) + result = 31 * result + (equals?.hashCode() ?: 0) + result = 31 * result + (uncompiledRegex?.hashCode() ?: 0) + result = 31 * result + prefix.hashCode() + result = 31 * result + button.hashCode() + return result + } + + companion object { + private var x = 4 + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt new file mode 100644 index 0000000..5525a51 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabs.kt @@ -0,0 +1,174 @@ +package cc.woverflow.chatting.chat + +import cc.woverflow.chatting.Chatting +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +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 currentTab: ChatTab? = null + set(value) { + if (value != null) { + field = value + if (Minecraft.getMinecraft().theWorld != null) { + Minecraft.getMinecraft().ingameGUI.chatGUI.refreshChat() + } + } + } + private var initialized = false + + private val tabFile = File(Chatting.modDir, "chattabs.json") + + fun initialize() { + if (initialized) { + return + } else { + initialized = true + } + if (!tabFile.exists()) { + generateNewFile() + } else { + try { + val chatTabJson = GSON.fromJson(tabFile.readText(), ChatTabsJson::class.java) + if (chatTabJson.version == 1) { + // ver 2 adds `enabled` + chatTabJson.tabs.forEach { + it.asJsonObject.addProperty("enabled", true) + } + chatTabJson.version = 2 + tabFile.writeText(chatTabJson.toString()) + } + 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() + } + } + tabs.forEach { + it.initialize() + } + currentTab = tabs[0] + } + + fun shouldRender(message: IChatComponent): Boolean { + return currentTab?.shouldRender(message) ?: true + } + + private fun generateNewFile() { + tabFile.createNewFile() + val jsonObject = JsonObject() + val defaultTabs = generateDefaultTabs() + jsonObject.add("tabs", defaultTabs) + jsonObject.addProperty("version", 1) + tabFile.writeText(jsonObject.toString()) + } + + private fun generateDefaultTabs(): JsonArray { + val all = ChatTab(true, "ALL", false, null, null, null, null, null, "") + val party = ChatTab( + true, + "PARTY", + false, + listOf("§r§9Party §8> ", "§r§9P §8> ", "§eThe party was transferred to §r", "§eKicked §r"), + null, + listOf( + "§r§ehas invited you to join their party!", + "§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" + ), + listOf("§cThe party was disbanded because all invites expired and the party was empty§r"), + 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)+(.*)" + ), + "/pc " + ) + val guild = ChatTab( + true, + "GUILD", + true, + listOf("Guild >", "G >"), + null, + null, + null, + null, + "/gc " + ) + val pm = ChatTab( + true, + "PM", + true, + listOf("To ", "From "), + null, + null, + null, + null, + "/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 new file mode 100644 index 0000000..e37ef7f --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/chat/ChatTabsJson.kt @@ -0,0 +1,11 @@ +package cc.woverflow.chatting.chat + +import com.google.gson.JsonArray +import com.google.gson.annotations.SerializedName + +data class ChatTabsJson(@SerializedName("tabs") val tabs: JsonArray, @SerializedName("version") var version: Int) { + + override fun toString(): String { + return "{\"tabs\": $tabs, \"version\": \"$version\"}" + } +} \ 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 new file mode 100644 index 0000000..ba697e1 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/command/ChattingCommand.kt @@ -0,0 +1,17 @@ +package cc.woverflow.chatting.command + +import cc.woverflow.chatting.Chatting +import cc.woverflow.chatting.config.ChattingConfig +import gg.essential.api.EssentialAPI +import gg.essential.api.commands.Command +import gg.essential.api.commands.DefaultHandler + +object ChattingCommand : Command(Chatting.ID, true) { + + override val commandAliases: Set = setOf(Alias("stratus")) + + @DefaultHandler + fun handle() { + EssentialAPI.getGuiUtil().openScreen(ChattingConfig.gui()) + } +} \ 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 new file mode 100644 index 0000000..846297b --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt @@ -0,0 +1,195 @@ +package cc.woverflow.chatting.config + +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.ChatShortcutViewGui +import cc.woverflow.chatting.updater.DownloadGui +import cc.woverflow.chatting.updater.Updater +import gg.essential.api.EssentialAPI +import gg.essential.vigilance.Vigilant +import gg.essential.vigilance.data.Category +import gg.essential.vigilance.data.Property +import gg.essential.vigilance.data.PropertyType +import gg.essential.vigilance.data.SortingBehavior +import java.io.File + +object ChattingConfig : Vigilant(File(Chatting.modDir, "${Chatting.ID}.toml"), Chatting.NAME, sortingBehavior = ConfigSorting) { + + @Property( + type = PropertyType.SELECTOR, + name = "Text Render Type", + description = "Choose the type of rendering for the text.", + category = "General", + options = ["No Shadow", "Shadow", "Full Shadow"] + ) + var textRenderType = 1 + + @Property( + type = PropertyType.SWITCH, + name = "Remove Tooltip Background", + description = "Remove the tooltip background.", + category = "General" + ) + var removeTooltipBackground = false + + @Property( + type = PropertyType.SWITCH, + name = "Custom Chat Height", + description = "Allows you to change the height of chat to heights greater than before.", + category = "Chat Window" + ) + var customChatHeight = true + + @Property( + type = PropertyType.SLIDER, + min = 180, + max = 10000, + name = "Focused Height", + description = "Height in pixels.", + category = "Chat Window" + ) + var focusedHeight = 180 + + @Property( + type = PropertyType.SLIDER, + min = 180, + max = 10000, + name = "Unfocused Height", + description = "Height in pixels.", + category = "Chat Window" + ) + var unfocusedHeight = 180 + + @Property( + type = PropertyType.SELECTOR, + name = "Screenshot Mode", + description = "The mode in which screenshotting will work.", + category = "Screenshotting", + options = [ + "Save To System", + "Add To Clipboard", + "Both" + ] + ) + var copyMode = 0 + + @Property( + type = PropertyType.SWITCH, + name = "Chat Searching", + description = "Add a chat search bar.", + category = "Searching" + ) + var chatSearch = true + + @Property( + type = PropertyType.SWITCH, + name = "Chat Tabs", + description = "Add chat tabs.", + category = "Tabs" + ) + var chatTabs = true + get() { + if (!field) return false + return if (hypixelOnlyChatTabs) { + EssentialAPI.getMinecraftUtil().isHypixel() + } else { + true + } + } + + @Property( + type = PropertyType.SWITCH, + name = "Enable Tabs Only on Hypixel", + description = "Enable chat tabs only in Hypixel.", + category = "Tabs" + ) + var hypixelOnlyChatTabs = true + + @Property( + type = PropertyType.SWITCH, + name = "Chat Shortcuts", + description = "Add chat shortcuts.", + category = "Shortcuts" + ) + var chatShortcuts = false + get() { + if (!field) return false + return if (hypixelOnlyChatShortcuts) { + EssentialAPI.getMinecraftUtil().isHypixel() + } else { + true + } + } + + @Property( + type = PropertyType.SWITCH, + name = "Enable Shortcuts Only on Hypixel", + description = "Enable chat shortcuts only in Hypixel.", + category = "Shortcuts" + ) + var hypixelOnlyChatShortcuts = true + + @Property( + type = PropertyType.BUTTON, + name = "Edit Chat Shortcuts", + description = "Edit chat shortcuts.", + category = "Shortcuts" + ) + fun openChatShortcutsGUI() { + EssentialAPI.getGuiUtil().openScreen(ChatShortcutViewGui()) + } + + @Property( + type = PropertyType.SWITCH, + name = "Show Update Notification", + description = "Show a notification when you start Minecraft informing you of new updates.", + category = "Updater" + ) + var showUpdate = true + + @Property( + type = PropertyType.BUTTON, + name = "Update Now", + description = "Update by clicking the button.", + category = "Updater" + ) + fun update() { + if (Updater.shouldUpdate) EssentialAPI.getGuiUtil() + .openScreen(DownloadGui()) else EssentialAPI.getNotifications() + .push( + Chatting.NAME, + "No update had been detected at startup, and thus the update GUI has not been shown." + ) + } + + init { + initialize() + registerListener("chatTabs") { funny: Boolean -> + chatTabs = funny + ChatTabs.initialize() + if (!funny) { + val dummy = ChatTab(true, "ALL", false, null, null, null, null, null, "") + dummy.initialize() + ChatTabs.currentTab = dummy + } else { + ChatTabs.currentTab = ChatTabs.tabs[0] + } + } + registerListener("chatShortcuts") { funny: Boolean -> + chatShortcuts = funny + ChatShortcuts.initialize() + } + } + + private object ConfigSorting : SortingBehavior() { + override fun getCategoryComparator(): Comparator = Comparator { o1, o2 -> + if (o1.name == "General") return@Comparator -1 + if (o2.name == "General") return@Comparator 1 + else compareValuesBy(o1, o2) { + it.name + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutEditGui.kt b/src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutEditGui.kt new file mode 100644 index 0000000..d75339b --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutEditGui.kt @@ -0,0 +1,90 @@ +package cc.woverflow.chatting.gui + +import cc.woverflow.chatting.chat.ChatShortcuts +import gg.essential.api.EssentialAPI +import gg.essential.api.gui.buildConfirmationModal +import gg.essential.elementa.ElementaVersion +import gg.essential.elementa.WindowScreen +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.constraints.CenterConstraint +import gg.essential.elementa.constraints.SiblingConstraint +import gg.essential.elementa.dsl.childOf +import gg.essential.elementa.dsl.constrain +import gg.essential.elementa.dsl.percent +import gg.essential.elementa.dsl.pixels +import gg.essential.vigilance.gui.VigilancePalette +import gg.essential.vigilance.gui.settings.ButtonComponent +import gg.essential.vigilance.gui.settings.TextComponent + +class ChatShortcutEditGui(private var alias: String, private var command: String, private val editing: Boolean) : + WindowScreen(restoreCurrentGuiOnClose = true, version = ElementaVersion.V1) { + + private val initialAlias = alias + private val initialCommand = command + + override fun initScreen(width: Int, height: Int) { + super.initScreen(width, height) + val block = UIBlock(VigilancePalette.getBackground()).constrain { + this.x = CenterConstraint() + this.y = CenterConstraint() + this.width = 100.pixels() + this.height = 100.pixels() + } childOf window + TextComponent(initialAlias, "Alias", wrap = false, protected = false).constrain { + x = CenterConstraint() + y = 10.percent() + }.childOf(block).onValueChange { + if (it is String) alias = it + } + TextComponent(initialCommand, "Command", wrap = false, protected = false).constrain { + x = CenterConstraint() + y = SiblingConstraint() + }.childOf(block).onValueChange { + if (it is String) command = it + } + if (editing) { + ButtonComponent("Reset") { + EssentialAPI.getGuiUtil().openScreen(ChatShortcutEditGui(initialAlias, initialCommand, editing)) + } constrain { + x = CenterConstraint() + y = 70.percent() + } childOf window + } + ButtonComponent("Save") { + alias = alias.substringAfter("/") + command = command.substringAfter("/") + if (editing) { + ChatShortcuts.removeShortcut(initialAlias) + } + if (alias.isBlank() || command.isBlank()) { + return@ButtonComponent + } + if (ChatShortcuts.shortcuts.any { it.first == alias }) { + EssentialAPI.getGuiUtil().openScreen(ChatShortcutConfirmGui(alias, command)) + return@ButtonComponent + } + ChatShortcuts.writeShortcut(alias, command) + restorePreviousScreen() + } constrain { + x = CenterConstraint() + y = 80.percent() + } childOf window + } + + inner class ChatShortcutConfirmGui(private var alias: String, private var command: String) : + WindowScreen(restoreCurrentGuiOnClose = true, version = ElementaVersion.V1) { + override fun initScreen(width: Int, height: Int) { + super.initScreen(width, height) + EssentialAPI.getEssentialComponentFactory().buildConfirmationModal { + text = "An alias with this name already exists, are you sure you want to overwrite it?" + onConfirm = { + ChatShortcuts.writeShortcut(alias, command) + EssentialAPI.getGuiUtil().openScreen(null) + } + onDeny = { + restorePreviousScreen() + } + } childOf this@ChatShortcutConfirmGui.window + } + } +} diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutViewGui.kt b/src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutViewGui.kt new file mode 100644 index 0000000..a4a9f70 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/gui/ChatShortcutViewGui.kt @@ -0,0 +1,57 @@ +package cc.woverflow.chatting.gui + +import cc.woverflow.chatting.chat.ChatShortcuts +import cc.woverflow.chatting.gui.components.TextBlock +import gg.essential.api.EssentialAPI +import gg.essential.elementa.ElementaVersion +import gg.essential.elementa.WindowScreen +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.constraints.CenterConstraint +import gg.essential.elementa.constraints.RelativeWindowConstraint +import gg.essential.elementa.constraints.SiblingConstraint +import gg.essential.elementa.dsl.* +import gg.essential.vigilance.gui.VigilancePalette +import gg.essential.vigilance.gui.settings.ButtonComponent + +class ChatShortcutViewGui : WindowScreen(version = ElementaVersion.V1) { + override fun initScreen(width: Int, height: Int) { + super.initScreen(width, height) + for ((index, shortcut) in ChatShortcuts.shortcuts.withIndex()) { + val block = UIBlock(VigilancePalette.getBackground()).constrain { + x = 3.percent() + y = (index * 12).percent() + this.width = 94.percent() + this.height = 25.pixels() + } childOf this.window + TextBlock(shortcut.first).constrain { + x = RelativeWindowConstraint(0.05F) + y = CenterConstraint() + } childOf block + TextBlock(shortcut.second).constrain { + x = SiblingConstraint(10F) + y = CenterConstraint() + } childOf block + ButtonComponent("Edit") { + println("${shortcut.first} ${shortcut.second}") + EssentialAPI.getGuiUtil().openScreen(ChatShortcutEditGui(shortcut.first, shortcut.second, true)) + } constrain { + x = SiblingConstraint(20F) + y = CenterConstraint() + } childOf block + ButtonComponent("Delete") { + println("${shortcut.first} ${shortcut.second}") + ChatShortcuts.removeShortcut(shortcut.first) + EssentialAPI.getGuiUtil().openScreen(ChatShortcutViewGui()) + } constrain { + x = SiblingConstraint(5F) + y = CenterConstraint() + } childOf block + } + ButtonComponent("New") { + EssentialAPI.getGuiUtil().openScreen(ChatShortcutEditGui("", "", false)) + } constrain { + x = CenterConstraint() + y = 80.percent() + } childOf window + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt new file mode 100644 index 0000000..b67510a --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/gui/components/CleanButton.kt @@ -0,0 +1,70 @@ +package cc.woverflow.chatting.gui.components + +import cc.woverflow.chatting.Chatting +import club.sk1er.patcher.config.PatcherConfig +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiButton +import net.minecraft.client.renderer.GlStateManager +import java.awt.Color + +/** + * 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) : + 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.invoke() + yPosition = y.invoke() + 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, + if (hovered) hoveredColor else color + ) + } + mouseDragged(mc, mouseX, mouseY) + var j = 14737632 + if (packedFGColour != 0) { + j = packedFGColour + } else if (!enabled) { + j = 10526880 + } else if (hovered) { + j = 16777120 + } + drawCenteredString(fontrenderer, displayString, xPosition + width / 2, yPosition + (height - 8) / 2, j) + } + } + + companion object { + val hoveredColor = Color(255, 255, 255, 128).rgb + val color = Color(0, 0, 0, 128).rgb + } +} \ 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 new file mode 100644 index 0000000..379444c --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/gui/components/ScreenshotButton.kt @@ -0,0 +1,35 @@ +package cc.woverflow.chatting.gui.components + +import cc.woverflow.chatting.Chatting +import cc.woverflow.chatting.mixin.GuiNewChatAccessor +import gg.essential.api.utils.GuiUtil +import gg.essential.universal.UResolution +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 - 28 }, { UResolution.scaledHeight - 27 }, 12, 12, "") { + + override fun onMousePress() { + val chat = Minecraft.getMinecraft().ingameGUI.chatGUI + if (GuiUtil.getOpenedScreen() 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 new file mode 100644 index 0000000..a0f875e --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/gui/components/SearchButton.kt @@ -0,0 +1,69 @@ +package cc.woverflow.chatting.gui.components + +import cc.woverflow.chatting.Chatting +import cc.woverflow.chatting.hook.GuiNewChatHook +import gg.essential.universal.UResolution +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, "") { + val inputField = SearchTextField() + private var chatBox = false + + override fun isEnabled(): Boolean { + return chatBox + } + + override fun onMousePress() { + chatBox = !chatBox + inputField.setEnabled(chatBox) + inputField.isFocused = chatBox + (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).prevText = "" + 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 - 27, + 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() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt new file mode 100644 index 0000000..5342629 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/gui/components/TabButton.kt @@ -0,0 +1,19 @@ +package cc.woverflow.chatting.gui.components + +import cc.woverflow.chatting.chat.ChatTab +import cc.woverflow.chatting.chat.ChatTabs +import gg.essential.universal.UResolution + +class TabButton(buttonId: Int, x: Int, widthIn: Int, heightIn: Int, private val chatTab: ChatTab) : + CleanButton(buttonId, { x }, { + UResolution.scaledHeight - 26 + }, widthIn, heightIn, chatTab.name) { + + override fun onMousePress() { + ChatTabs.currentTab = chatTab + } + + override fun isEnabled(): Boolean { + return chatTab != ChatTabs.currentTab + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/gui/components/TextBlock.kt b/src/main/kotlin/cc/woverflow/chatting/gui/components/TextBlock.kt new file mode 100644 index 0000000..8a4f32a --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/gui/components/TextBlock.kt @@ -0,0 +1,41 @@ +package cc.woverflow.chatting.gui.components + +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIText +import gg.essential.elementa.constraints.ChildBasedSizeConstraint +import gg.essential.elementa.dsl.* +import gg.essential.elementa.effects.OutlineEffect +import gg.essential.elementa.state.BasicState +import gg.essential.vigilance.gui.VigilancePalette +import gg.essential.vigilance.gui.settings.SettingComponent + +/** + * Heavily modified from Vigilance under LGPLv3 (modified to be just a text block) + * https://github.com/Sk1erLLC/Vigilance/blob/master/LICENSE + */ +class TextBlock( + text: String +) : SettingComponent() { + private val textHolder = UIBlock() constrain { + width = ChildBasedSizeConstraint() + 6.pixels() + height = ChildBasedSizeConstraint() + 6.pixels() + color = VigilancePalette.getDarkHighlight().toConstraint() + } childOf this effect OutlineEffect( + VigilancePalette.getDivider(), + 1f + ).bindColor(BasicState(VigilancePalette.getDivider())) + + private val text: UIText = UIText(text) constrain { + x = 3.pixels() + y = 3.pixels() + } + + init { + this.text childOf textHolder + + constrain { + width = ChildBasedSizeConstraint() + height = ChildBasedSizeConstraint() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/updater/DownloadGui.kt b/src/main/kotlin/cc/woverflow/chatting/updater/DownloadGui.kt new file mode 100644 index 0000000..10f2f6f --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/updater/DownloadGui.kt @@ -0,0 +1,54 @@ +package cc.woverflow.chatting.updater + +import cc.woverflow.chatting.Chatting +import gg.essential.api.EssentialAPI +import gg.essential.api.gui.buildConfirmationModal +import gg.essential.api.utils.Multithreading +import gg.essential.elementa.ElementaVersion +import gg.essential.elementa.WindowScreen +import gg.essential.elementa.dsl.childOf +import java.io.File + +class DownloadGui : WindowScreen(ElementaVersion.V1, true, true, true, -1) { + override fun initScreen(width: Int, height: Int) { + super.initScreen(width, height) + EssentialAPI.getEssentialComponentFactory().buildConfirmationModal { + this.text = "Are you sure you want to update?" + this.secondaryText = + "(This will update from v${Chatting.VER} to ${Updater.latestTag})" + this.onConfirm = { + restorePreviousScreen() + Multithreading.runAsync { + if (Updater.download( + Updater.updateUrl, + File( + "mods/${Chatting.NAME}-${ + Updater.latestTag!!.substringAfter("v") + }.jar" + ) + ) && Updater.download( + "https://github.com/Wyvest/Deleter/releases/download/v1.2/Deleter-1.2.jar", + File(Chatting.modDir.parentFile, "Deleter-1.2.jar") + ) + ) { + EssentialAPI.getNotifications() + .push( + Chatting.NAME, + "The ingame updater has successfully installed the newest version." + ) + Updater.addShutdownHook() + Updater.shouldUpdate = false + } else { + EssentialAPI.getNotifications().push( + Chatting.NAME, + "The ingame updater has NOT installed the newest version as something went wrong." + ) + } + } + } + this.onDeny = { + restorePreviousScreen() + } + } childOf this.window + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/updater/Updater.kt b/src/main/kotlin/cc/woverflow/chatting/updater/Updater.kt new file mode 100644 index 0000000..ef1e376 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/updater/Updater.kt @@ -0,0 +1,98 @@ +package cc.woverflow.chatting.updater + +import cc.woverflow.chatting.Chatting +import cc.woverflow.chatting.config.ChattingConfig +import gg.essential.api.EssentialAPI +import gg.essential.api.utils.Multithreading +import gg.essential.api.utils.WebUtil.downloadToFile +import gg.essential.api.utils.WebUtil.fetchJSON +import gg.essential.universal.UDesktop +import net.minecraft.client.Minecraft +import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion +import java.io.File +import java.io.IOException + +object Updater { + var updateUrl = "" + var latestTag: String? = null + var shouldUpdate = false + + fun update() { + Multithreading.runAsync { + try { + val latestRelease = + fetchJSON("https://api.github.com/repos/W-OVERFLOW/${Chatting.ID}/releases/latest").getObject() + latestTag = latestRelease["tag_name"].asString + val currentVersion = + DefaultArtifactVersion(Chatting.VER.substringBefore("-")) + val latestVersion = DefaultArtifactVersion(latestTag!!.substringAfter("v").substringBefore("-")) + if (currentVersion >= latestVersion) { + if (currentVersion != latestVersion || !Chatting.VER.contains("-")) { + return@runAsync + } + } + updateUrl = + latestRelease["assets"].asJsonArray[0].asJsonObject["browser_download_url"] + .asString + if (updateUrl.isNotEmpty()) { + if (ChattingConfig.showUpdate) { + EssentialAPI.getNotifications().push( + Chatting.NAME, + "${Chatting.NAME} has a new update ($latestTag)! Click here to download it automatically!" + ) { EssentialAPI.getGuiUtil().openScreen(DownloadGui()) } + } + shouldUpdate = true + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + fun download(url: String, file: File): Boolean { + var url = url + if (file.exists()) return true + url = url.replace(" ", "%20") + try { + downloadToFile(url, file, "${Chatting.NAME}/${Chatting.VER}") + } catch (e: Exception) { + e.printStackTrace() + return false + } + return file.exists() + } + + fun addShutdownHook() { + EssentialAPI.getShutdownHookUtil().register { + println("Opening Deleter task...") + try { + val runtime = javaRuntime + if (Minecraft.isRunningOnMac) { + UDesktop.open(Chatting.jarFile.parentFile) + } + val file = File(Chatting.modDir.parentFile, "Deleter-1.2.jar") + Runtime.getRuntime() + .exec("\"" + runtime + "\" -jar \"" + file.absolutePath + "\" \"" + Chatting.jarFile.absolutePath + "\"") + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + /** + * Gets the current Java runtime being used. + * + * @link https://stackoverflow.com/a/47925649 + */ + @get:Throws(IOException::class) + val javaRuntime: String + get() { + val os = System.getProperty("os.name") + val java = System.getProperty("java.home") + File.separator + "bin" + File.separator + + if (os != null && os.lowercase().startsWith("windows")) "java.exe" else "java" + if (!File(java).isFile) { + throw IOException("Unable to find suitable java runtime at $java") + } + return java + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/utils/ImageTransferable.kt b/src/main/kotlin/cc/woverflow/chatting/utils/ImageTransferable.kt new file mode 100644 index 0000000..563f17a --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/utils/ImageTransferable.kt @@ -0,0 +1,22 @@ +package cc.woverflow.chatting.utils + +import java.awt.Image +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.Transferable +import java.awt.datatransfer.UnsupportedFlavorException + +data class ImageTransferable(private val image: Image) : Transferable { + + override fun getTransferDataFlavors(): Array { + return arrayOf(DataFlavor.imageFlavor) + } + + override fun isDataFlavorSupported(flavor: DataFlavor?): Boolean { + return DataFlavor.imageFlavor.equals(flavor) + } + + override fun getTransferData(flavor: DataFlavor?): Any { + if (isDataFlavorSupported(flavor)) return image + throw UnsupportedFlavorException(flavor) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cc/woverflow/chatting/utils/ListenableArrayList.kt b/src/main/kotlin/cc/woverflow/chatting/utils/ListenableArrayList.kt new file mode 100644 index 0000000..0cd598c --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/utils/ListenableArrayList.kt @@ -0,0 +1,9 @@ +package cc.woverflow.chatting.utils + +class ListenableArrayList(private val runnable: (ListenableArrayList) -> Unit, vararg elements: T): ArrayList() { + override fun add(element: T): Boolean { + val value = super.add(element) + runnable.invoke(this) + return value + } +} \ 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 new file mode 100644 index 0000000..e69a36b --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/utils/ModCompatHooks.kt @@ -0,0 +1,46 @@ +package cc.woverflow.chatting.utils + +import club.sk1er.patcher.config.PatcherConfig +import com.llamalad7.betterchat.BetterChat +import cc.woverflow.chatting.Chatting.isBetterChat +import cc.woverflow.chatting.Chatting.isPatcher +import cc.woverflow.chatting.config.ChattingConfig.textRenderType +import cc.woverflow.chatting.utils.RenderHelper.drawBorderedString +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.FontRenderer + +// 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 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): Int { + return when (textRenderType) { + 0 -> { + fontRenderer.drawString(text, x, y, color, false) + } + 2 -> { + drawBorderedString(fontRenderer, text, x.toInt(), y.toInt(), color) + } + else -> fontRenderer.drawString(text, x, y, color, true) + } + } +} diff --git a/src/main/kotlin/cc/woverflow/chatting/utils/RenderHelper.kt b/src/main/kotlin/cc/woverflow/chatting/utils/RenderHelper.kt new file mode 100644 index 0000000..53870c1 --- /dev/null +++ b/src/main/kotlin/cc/woverflow/chatting/utils/RenderHelper.kt @@ -0,0 +1,248 @@ +package cc.woverflow.chatting.utils + +import cc.woverflow.chatting.config.ChattingConfig +import cc.woverflow.chatting.hook.GuiNewChatHook +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.FontRenderer +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 + + +object RenderHelper { + private val regex = Regex("(?i)\\u00A7[0-9a-f]") + var bypassWyvtils = false + private set + + /** + * 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 copyBufferedImageToClipboard(bufferedImage: BufferedImage) { + if (SystemUtils.IS_OS_WINDOWS) { + try { + val width = bufferedImage.width + val height = bufferedImage.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 = bufferedImage.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 = bufferedImage.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 = + bufferedImage.getRGB(0, 0, bufferedImage.width, bufferedImage.height, null, 0, bufferedImage.width) + val newImage = BufferedImage(bufferedImage.width, bufferedImage.height, BufferedImage.TYPE_INT_RGB) + newImage.setRGB(0, 0, newImage.width, newImage.height, pixels, 0, newImage.width) + + try { + Toolkit.getDefaultToolkit().systemClipboard.setContents(ImageTransferable(bufferedImage), null) + } catch (e: Exception) { + e.printStackTrace() + } + } + + + /** + * Taken from https://github.com/Moulberry/HyChat + */ + fun screenshotFramebuffer(framebuffer: Framebuffer, file: File): BufferedImage { + val w = framebuffer.framebufferWidth + val h = framebuffer.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(framebuffer.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 = framebuffer.framebufferTextureHeight - framebuffer.framebufferHeight + for (k in j until framebuffer.framebufferTextureHeight) { + for (l in 0 until framebuffer.framebufferWidth) { + bufferedimage.setRGB(l, k - j, pixelValues[k * framebuffer.framebufferTextureWidth + l]) + } + } + if (ChattingConfig.copyMode != 1) { + try { + file.parentFile.mkdirs() + ImageIO.write(bufferedimage, "png", file) + } catch (e: Exception) { + e.printStackTrace() + } + } + return bufferedimage + } + + /** + * 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 + */ + fun drawBorderedString( + fontRendererIn: FontRenderer, + text: String, + x: Int, + y: Int, + color: Int + ): Int { + val noColors = text.replace(regex, "\u00A7r") + var yes = 0 + if (((Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity / 4) > 3) { + bypassWyvtils = true + for (xOff in -2..2) { + for (yOff in -2..2) { + if (xOff * xOff != yOff * yOff) { + yes += fontRendererIn.drawString( + noColors, + (xOff / 2f) + x, (yOff / 2f) + y, ((Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatHook).textOpacity / 4) shl 24, false + ) + } + } + } + bypassWyvtils = false + } + yes += fontRendererIn.drawString(text, x, y, color) + return yes + } +} \ No newline at end of file -- cgit