├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── docs ├── chattabs.md └── docs.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── root.gradle.kts ├── settings.gradle.kts ├── src ├── dummy │ └── java │ │ ├── cc │ │ └── polyfrost │ │ │ └── oneconfig │ │ │ └── internal │ │ │ └── hud │ │ │ └── HudCore.java │ │ ├── club │ │ └── sk1er │ │ │ └── patcher │ │ │ └── config │ │ │ ├── OldPatcherConfig.java │ │ │ └── PatcherConfig.java │ │ └── com │ │ └── llamalad7 │ │ └── betterchat │ │ ├── BetterChat.java │ │ └── ChatSettings.java └── main │ ├── java │ └── org │ │ └── polyfrost │ │ └── chatting │ │ ├── hook │ │ ├── ChatHook.java │ │ ├── ChatLineHook.java │ │ ├── GuiChatHook.java │ │ └── GuiNewChatHook.java │ │ └── mixin │ │ ├── ChatLineMixin.java │ │ ├── ChatStyleMixin.java │ │ ├── ClientCommandHandlerMixin.java │ │ ├── EntityPlayerSPMixin.java │ │ ├── EntityRendererMixin.java │ │ ├── GameSettingsMixin.java │ │ ├── GuiChatMixin.java │ │ ├── GuiContainerCreativeMixin.java │ │ ├── GuiIngameForgeAccessor.java │ │ ├── GuiIngameForgeMixin.java │ │ ├── GuiNewChatAccessor.java │ │ ├── GuiNewChatMixin.java │ │ ├── GuiNewChatMixin_ChatHeight.java │ │ ├── GuiNewChatMixin_ChatPeek.java │ │ ├── GuiNewChatMixin_ChatSearching.java │ │ ├── GuiNewChatMixin_ChatTabs.java │ │ ├── GuiNewChatMixin_Movable.java │ │ ├── GuiNewChatMixin_Scrolling.java │ │ ├── GuiNewChatMixin_SmoothMessages.java │ │ ├── GuiNewChatMixin_TextRendering.java │ │ ├── GuiTextFieldMixin.java │ │ ├── GuiUtilsMixin.java │ │ ├── HUDUtilsMixin.java │ │ ├── InventoryPlayerMixin.java │ │ └── compat │ │ ├── ChatPeekMixin_SkyHanni.java │ │ ├── ChatTabsMixin_SkytilsCopyChat.java │ │ └── EssentialKeybindingRegistryMixin.java │ ├── kotlin │ └── org │ │ └── polyfrost │ │ └── chatting │ │ ├── Chatting.kt │ │ ├── chat │ │ ├── ChatHooks.kt │ │ ├── ChatInputBox.kt │ │ ├── ChatRegexes.kt │ │ ├── ChatScrollingHook.kt │ │ ├── ChatSearchingManager.kt │ │ ├── ChatShortcuts.kt │ │ ├── ChatSpamBlock.kt │ │ ├── ChatTab.kt │ │ ├── ChatTabs.kt │ │ ├── ChatTabsJson.kt │ │ └── ChatWindow.kt │ │ ├── command │ │ └── ChattingCommand.kt │ │ ├── config │ │ └── ChattingConfig.kt │ │ ├── gui │ │ └── components │ │ │ ├── CleanButton.kt │ │ │ ├── ClearButton.kt │ │ │ ├── RenderType.kt │ │ │ ├── ScreenshotButton.kt │ │ │ ├── SearchButton.kt │ │ │ └── TabButton.kt │ │ └── utils │ │ ├── EaseOutQuad.kt │ │ ├── EaseOutQuart.kt │ │ ├── ModCompatHooks.kt │ │ └── RenderUtils.kt │ └── resources │ ├── assets │ └── chatting │ │ ├── copy.png │ │ ├── delete.png │ │ ├── reply.png │ │ ├── screenshot.png │ │ └── search.png │ ├── chatting_dark.svg │ ├── mcmod.info │ ├── mixins.chatting.json │ └── spamInfo.json └── versions └── mainProject /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: '*' 6 | paths-ignore: 7 | - 'README.md' 8 | - 'LICENSE' 9 | - '.gitignore' 10 | pull_request: 11 | branches: '*' 12 | paths-ignore: 13 | - 'README.md' 14 | - 'LICENSE' 15 | - '.gitignore' 16 | workflow_dispatch: 17 | 18 | concurrency: 19 | # Maximum of one running workflow per pull request source branch 20 | # or branch and run number combination (cancels old run if action is rerun) 21 | group: ${{ github.head_ref || format('{0}-{1}', github.ref, github.run_number) }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | build: 26 | name: "Build" 27 | runs-on: "ubuntu-latest" 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | 32 | - uses: gradle/wrapper-validation-action@v1 33 | 34 | - uses: actions/setup-java@v2 35 | with: 36 | distribution: "temurin" 37 | java-version: "17" 38 | 39 | - uses: actions/cache@v2 40 | with: 41 | path: | 42 | ~/.gradle/caches 43 | ~/.gradle/wrapper 44 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 45 | restore-keys: | 46 | ${{ runner.os }}-gradle- 47 | - run: chmod +x ./gradlew 48 | - run: ./gradlew --no-daemon build 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | eclipse 3 | bin 4 | *.launch 5 | .settings 6 | .metadata 7 | .classpath 8 | .project 9 | 10 | # idea 11 | out 12 | classes 13 | *.ipr 14 | *.iws 15 | *.iml 16 | .idea 17 | 18 | # gradle 19 | build 20 | .gradle 21 | 22 | #Netbeans 23 | .nb-gradle 24 | .nb-gradle-properties 25 | 26 | # other 27 | run 28 | .DS_Store 29 | Thumbs.db 30 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chatting 2 | ![Compact Powered by OneConfig](https://polyfrost.org/img/compact_vector.svg) 3 | ![Dev Workflow Status](https://img.shields.io/github/v/release/Polyfrost/Chatting.svg?style=for-the-badge&color=1452cc&label=release) 4 | 5 | Chatting completely revamps the Minecraft chat with many quality-of-life improvements while remaining unintrusive, and functional. 6 | 7 | **Features** 8 | - Tabs 9 | - Screenshot messages 10 | - Customize the appearance of almost every element 11 | - Search for messages 12 | - Chat heads 13 | - Native OneConfig support 14 | 15 | ## Chat tabs 16 | ![chatScreenshot](https://user-images.githubusercontent.com/62163840/228093031-cc449511-9bc5-4147-a489-e5b9c0734494.png) 17 | 18 | ## Screenshots 19 | ![2023-03-27_17 02 01](https://user-images.githubusercontent.com/62163840/228093177-551e91fc-acf4-4144-a20b-736d164ef561.png) 20 | 21 | ## Customize appearance 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | ## OneConfig support 31 | ![chattingOneConfig](https://user-images.githubusercontent.com/62163840/228095008-312f1a60-4c64-4549-bcd6-5eedab85659b.png) 32 | 33 | ## Links and support 34 | * [Docs for Chatting chat tabs](https://github.com/Polyfrost/Chatting/blob/main/docs/docs.md) 35 | * Did you run into a bug? [Open a bug report](https://polyfrost.cc/discord) 36 | * Service interruptions? [Checkout Polyfrost Status](https://status.polyfrost.cc/) 37 | * Feeling social? [Join our discord community](https://polyfrost.cc/discord) 38 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage", "PropertyName") 2 | 3 | import org.polyfrost.gradle.util.noServerRunConfigs 4 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 5 | 6 | // Adds support for kotlin, and adds the Polyfrost Gradle Toolkit 7 | // which we use to prepare the environment. 8 | plugins { 9 | kotlin("jvm") 10 | id("org.polyfrost.multi-version") 11 | id("org.polyfrost.defaults.repo") 12 | id("org.polyfrost.defaults.java") 13 | id("org.polyfrost.defaults.loom") 14 | id("com.github.johnrengelman.shadow") 15 | id("net.kyori.blossom") version "1.3.2" 16 | id("signing") 17 | java 18 | } 19 | 20 | // Gets the mod name, version and id from the `gradle.properties` file. 21 | val mod_name: String by project 22 | val mod_version: String by project 23 | val mod_id: String by project 24 | val mod_archives_name: String by project 25 | 26 | // Sets up the variables for when we preprocess to other Minecraft versions. 27 | preprocess { 28 | vars.put("MODERN", if (project.platform.mcMinor >= 16) 1 else 0) 29 | } 30 | 31 | // Replaces the variables in `ExampleMod.java` to the ones specified in `gradle.properties`. 32 | blossom { 33 | replaceToken("@VER@", mod_version) 34 | replaceToken("@NAME@", mod_name) 35 | replaceToken("@ID@", mod_id) 36 | } 37 | 38 | // Sets the mod version to the one specified in `gradle.properties`. Make sure to change this following semver! 39 | version = mod_version 40 | // Sets the group, make sure to change this to your own. It can be a website you own backwards or your GitHub username. 41 | // e.g. com.github. or com. 42 | group = "org.polyfrost" 43 | 44 | // Sets the name of the output jar (the one you put in your mods folder and send to other people) 45 | // It outputs all versions of the mod into the `build` directory. 46 | base { 47 | archivesName.set("$mod_archives_name-$platform") 48 | } 49 | 50 | // Configures the Polyfrost Loom, our plugin fork to easily set up the programming environment. 51 | loom { 52 | // Removes the server configs from IntelliJ IDEA, leaving only client runs. 53 | // If you're developing a server-side mod, you can remove this line. 54 | noServerRunConfigs() 55 | 56 | // Adds the tweak class if we are building legacy version of forge as per the documentation (https://docs.polyfrost.org) 57 | if (project.platform.isLegacyForge) { 58 | runConfigs { 59 | "client" { 60 | programArgs("--tweakClass", "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker") 61 | property("fml.coreMods.load", "org.polyfrost.craftycrashes.plugin.LegacyCraftyCrashesLoadingPlugin") 62 | property("mixin.debug.export", "true") 63 | } 64 | } 65 | } 66 | // Configures the mixins if we are building for forge, useful for when we are dealing with cross-platform projects. 67 | if (project.platform.isForge) { 68 | forge { 69 | mixinConfig("mixins.${mod_id}.json") 70 | } 71 | } 72 | // Configures the name of the mixin "refmap" using an experimental loom api. 73 | mixin.defaultRefmapName.set("mixins.${mod_id}.refmap.json") 74 | } 75 | 76 | // Creates the shade/shadow configuration, so we can include libraries inside our mod, rather than having to add them separately. 77 | val shade: Configuration by configurations.creating { 78 | configurations.implementation.get().extendsFrom(this) 79 | } 80 | 81 | // Configures the output directory for when building from the `src/resources` directory. 82 | sourceSets { 83 | val dummy by creating 84 | main { 85 | dummy.compileClasspath += compileClasspath 86 | compileClasspath += dummy.output 87 | output.setResourcesDir(java.classesDirectory) 88 | } 89 | } 90 | 91 | // Adds the Polyfrost maven repository so that we can get the libraries necessary to develop the mod. 92 | repositories { 93 | mavenLocal() 94 | maven("https://repo.polyfrost.org/releases") 95 | } 96 | 97 | // Configures the libraries/dependencies for your mod. 98 | dependencies { 99 | // Adds the OneConfig library, so we can develop with it. 100 | modCompileOnly("cc.polyfrost:oneconfig-$platform:0.2.2-alpha+") 101 | 102 | modRuntimeOnly("me.djtheredstoner:DevAuth-${if (platform.isFabric) "fabric" else if (platform.isLegacyForge) "forge-legacy" else "forge-latest"}:1.1.2") 103 | 104 | compileOnly("cc.polyfrost:universalcraft-1.8.9-forge:246") 105 | 106 | // If we are building for legacy forge, includes the launch wrapper with `shade` as we configured earlier. 107 | if (platform.isLegacyForge) { 108 | compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") 109 | shade("cc.polyfrost:oneconfig-wrapper-launchwrapper:1.0.0-beta17") 110 | } 111 | } 112 | 113 | tasks { 114 | // Processes the `src/resources/mcmod.info or fabric.mod.json` and replaces 115 | // the mod id, name and version with the ones in `gradle.properties` 116 | processResources { 117 | inputs.property("id", mod_id) 118 | inputs.property("name", mod_name) 119 | val java = if (project.platform.mcMinor >= 18) { 120 | 17 // If we are playing on version 1.18, set the java version to 17 121 | } else { 122 | // Else if we are playing on version 1.17, use java 16. 123 | if (project.platform.mcMinor == 17) 124 | 16 125 | else 126 | 8 // For all previous versions, we **need** java 8 (for Forge support). 127 | } 128 | val compatLevel = "JAVA_${java}" 129 | inputs.property("java", java) 130 | inputs.property("java_level", compatLevel) 131 | inputs.property("version", mod_version) 132 | inputs.property("mcVersionStr", project.platform.mcVersionStr) 133 | filesMatching(listOf("mcmod.info", "mixins.${mod_id}.json", "mods.toml")) { 134 | expand( 135 | mapOf( 136 | "id" to mod_id, 137 | "name" to mod_name, 138 | "java" to java, 139 | "java_level" to compatLevel, 140 | "version" to mod_version, 141 | "mcVersionStr" to project.platform.mcVersionStr 142 | ) 143 | ) 144 | } 145 | filesMatching("fabric.mod.json") { 146 | expand( 147 | mapOf( 148 | "id" to mod_id, 149 | "name" to mod_name, 150 | "java" to java, 151 | "java_level" to compatLevel, 152 | "version" to mod_version, 153 | "mcVersionStr" to project.platform.mcVersionStr.substringBeforeLast(".") + ".x" 154 | ) 155 | ) 156 | } 157 | } 158 | 159 | // Configures the resources to include if we are building for forge or fabric. 160 | withType(Jar::class.java) { 161 | if (project.platform.isFabric) { 162 | exclude("mcmod.info", "mods.toml") 163 | } else { 164 | exclude("fabric.mod.json") 165 | if (project.platform.isLegacyForge) { 166 | exclude("mods.toml") 167 | } else { 168 | exclude("mcmod.info") 169 | } 170 | } 171 | } 172 | 173 | // Configures our shadow/shade configuration, so we can 174 | // include some dependencies within our mod jar file. 175 | named("shadowJar") { 176 | archiveClassifier.set("dev") // TODO: machete gets confused by the `dev` prefix. 177 | configurations = listOf(shade) 178 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 179 | } 180 | 181 | remapJar { 182 | inputFile.set(shadowJar.get().archiveFile) 183 | archiveClassifier.set("") 184 | } 185 | 186 | jar { 187 | // Sets the jar manifest attributes. 188 | if (platform.isLegacyForge) { 189 | manifest.attributes += mapOf( 190 | "ModSide" to "CLIENT", // We aren't developing a server-side mod, so this is fine. 191 | "ForceLoadAsMod" to true, // We want to load this jar as a mod, so we force Forge to do so. 192 | "TweakOrder" to "0", // Makes sure that the OneConfig launch wrapper is loaded as soon as possible. 193 | "MixinConfigs" to "mixins.${mod_id}.json", // We want to use our mixin configuration, so we specify it here. 194 | "TweakClass" to "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker" // Loads the OneConfig launch wrapper. 195 | ) 196 | } 197 | dependsOn(shadowJar) 198 | archiveClassifier.set("") 199 | enabled = false 200 | } 201 | } -------------------------------------------------------------------------------- /docs/chattabs.md: -------------------------------------------------------------------------------- 1 | # Chatting Chat Tabs 2 | 3 | ## Syntax 4 | 5 | The file for Chat Tabs is in `{MINECRAFT DIRECTORY}/OneConfig/profiles/{PROFILE}/chattabs.json`. However, Chatting versions below 1.4.2-beta5 use `{MINECRAFT DIRECTORY}/W-OVERFLOW/Chatting/chattabs.json`. The default file will look 6 | something like this: 7 | 8 | ```json 9 | { 10 | "tabs": [ 11 | { 12 | "enabled": true, 13 | "name": "ALL", 14 | "unformatted": false, 15 | "lowercase": false, 16 | "color": 14737632, 17 | "hovered_color": 16777120, 18 | "selected_color": 10526880 19 | }, 20 | { 21 | "enabled": true, 22 | "name": "PARTY", 23 | "unformatted": false, 24 | "lowercase": false, 25 | "starts": [ 26 | "§r§9Party §8> ", 27 | "§r§9P §8> ", 28 | "§eThe party was transferred to §r", 29 | "§eKicked §r" 30 | ], 31 | "contains": [ 32 | "§r§ehas invited you to join their party!" 33 | ], 34 | "ends": [ 35 | "§r§eto the party! They have §r§c60 §r§eseconds to accept.§r", 36 | "§r§ehas disbanded the party!§r", 37 | "§r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before they are removed from the party.§r", 38 | " §r§ejoined the party.§r", 39 | " §r§ehas left the party.§r", 40 | " §r§ehas been removed from the party.§r", 41 | "§r§e because they were offline.§r" 42 | ], 43 | "equals": [ 44 | "§cThe party was disbanded because all invites expired and the party was empty§r" 45 | ], 46 | "regex": [ 47 | "(§r)*(§9Party §8>)+(.*)", 48 | "(?:(?:§[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", 49 | "(?:(?:§[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", 50 | "(?:(?:§[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", 51 | "§eYou left the party\\.§r", 52 | "§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_]+)'s §r§eparty!§r", 53 | "§cThe party was disbanded because all invites expired and the party was empty§r", 54 | "§cYou cannot invite that player since they're not online\\.§r", 55 | "§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's house\\.§r", 56 | "§eSkyBlock Party Warp §r§7\\([0-9]+ players?\\)§r", 57 | "§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", 58 | "§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", 59 | "§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", 60 | "(?:(?:§[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", 61 | "(?:(?:§[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", 62 | "§cThe party is now muted\\. §r", 63 | "§aThe party is no longer muted\\.§r", 64 | "§cThere are no offline players to remove\\.§r", 65 | "(?:(?:§[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", 66 | "§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", 67 | "(?:(?:§[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", 68 | "(?:(?:§[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", 69 | "(?:(?:§[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", 70 | "(?:(?:§[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", 71 | "§cYou can't demote yourself!§r", 72 | "§6Party Members \\([0-9]+\\)§r", 73 | "§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", 74 | "§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)+", 75 | "§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)+", 76 | "§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", 77 | "(?:(?:§[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", 78 | "(?:(?:§[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", 79 | "§cYou cannot invite that player\\.§r", 80 | "§cYou are not allowed to invite players\\.§r", 81 | "§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", 82 | "(?:(?:§[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", 83 | "§cYou are not in a party right now\\.§r", 84 | "§cThis party is currently muted\\.§r", 85 | "(§r)*(§9P §8>)+(.*)" 86 | ], 87 | "color": 14737632, 88 | "hovered_color": 16777120, 89 | "selected_color": 10526880, 90 | "prefix": "/pc " 91 | }, 92 | { 93 | "enabled": true, 94 | "name": "GUILD", 95 | "unformatted": true, 96 | "lowercase": false, 97 | "starts": [ 98 | "Guild >", 99 | "G >" 100 | ], 101 | "color": 14737632, 102 | "hovered_color": 16777120, 103 | "selected_color": 10526880, 104 | "prefix": "/gc " 105 | }, 106 | { 107 | "enabled": true, 108 | "name": "PM", 109 | "unformatted": false, 110 | "lowercase": false, 111 | "regex": [ 112 | "^(?§dTo|§dFrom) (?.+): §r(?§7.*)(?:§r)?$" 113 | ], 114 | "color": 14737632, 115 | "hovered_color": 16777120, 116 | "selected_color": 10526880, 117 | "prefix": "/r " 118 | } 119 | ], 120 | "version": 6 121 | } 122 | ``` 123 | 124 | The `version` property stores the version number of the Chat Tabs JSON file. This should not be touched unless you have 125 | an older Chat Tab JSON and would like Chatting to automatically migrate to the newer version. The current version is `4` 126 | . 127 | 128 | The `tabs` property stores all the chat tabs, in the order they should be displayed in. By default, there are 4 chat 129 | tabs - ALL, PARTY, GUILD, and PM. ALL simply shows all messages; nothing is filtered. PARTY shows only party messages, 130 | GUILD shows only guild messages, and PM only shows private messages. These can be modified to the user's free will. 131 | 132 | ### Tab Syntax 133 | 134 | This is the default PARTY chat tab. We will be using this as an example, as it utilizes most of the chat tab features 135 | that you may want to use. 136 | 137 | ```json 138 | { 139 | "enabled": true, 140 | "name": "PARTY", 141 | "unformatted": false, 142 | "starts": [ 143 | "§r§9Party §8> ", 144 | "§r§9P §8> ", 145 | "§eThe party was transferred to §r", 146 | "§eKicked §r" 147 | ], 148 | "contains": [ 149 | "§r§ehas invited you to join their party!" 150 | ], 151 | "ends": [ 152 | "§r§eto the party! They have §r§c60 §r§eseconds to accept.§r", 153 | ... 154 | "§r§e because they were offline.§r" 155 | ], 156 | "equals": [ 157 | "§cThe party was disbanded because all invites expired and the party was empty§r" 158 | ], 159 | "regex": [ 160 | "(§r)*(§9Party §8>)+(.*)", 161 | "(?:(?:§[a-zA-Z0-9])*\\[(...seconds to accept\\.§r", 162 | "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)... §r§ehas left the party\\.§r", 163 | "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP...joined the party\\.§r", 164 | ... 165 | "(§r)*(§9P §8>)+(.*)" 166 | ], 167 | "color": 14737632, 168 | "hovered_color": 16777120, 169 | "selected_color": 10526880, 170 | "prefix": "/pc " 171 | } 172 | ``` 173 | 174 | The `enabled` property allows you to disable a chat tab without removing the tab from the JSON file. Users will be able 175 | to directly manage this property via a GUI in the future. 176 | 177 | The `name` property allows you to customize the display name of the tab. 178 | 179 | The `unformatted` property allows you to toggle whether the filters go through the raw message sent or the message 180 | without color / formatting codes. For example... 181 | 182 | BEFORE 183 | 184 | ```json 185 | { 186 | "enabled": true, 187 | "name": "EXAMPLE", 188 | "unformatted": false, 189 | "starts": [ 190 | "§r§9Message §8> " 191 | ] 192 | } 193 | ``` 194 | 195 | AFTER 196 | 197 | ```json 198 | { 199 | "enabled": true, 200 | "name": "EXAMPLE", 201 | "unformatted": true, 202 | "starts": [ 203 | "Message > " 204 | ] 205 | } 206 | ``` 207 | 208 | The `starts` property allows you to only allow a message if it starts with a string in the `starts` property. For 209 | example, if a message which contents were "Hello!", it would not be allowed, as it does not start anything in 210 | the `starts` property. 211 | 212 | The `contains` property allows you to only allow a message if it contains a string in the `contains` property. 213 | 214 | The `ends` property does a similar function, except only allowing a message if it **ends** with anything in the `ends` 215 | property rather than if it starts with anything. 216 | 217 | The `equals` property allows you to only allow a message if it equals with a string in the `equals` property. **_THIS IS 218 | CASE SENSITIVE._** 219 | 220 | The `regex` property allows you to only allow a message if it matches a regex in the `regex` property. 221 | 222 | You can append `ignore_` to `starts`, `ends`, `equals`, or `regex` to ignore messages that match rather than allow, and 223 | of course use both at the same time. 224 | 225 | The `color` property allows you to change the color of the chat tab text. It is in RGBA format. 226 | 227 | The `hovered_color` property allows you to change the color of the chat tab text while being hovered. This takes 228 | priority over the `color` property. Like the `color` property, it is in RGBA format. 229 | 230 | The `selected_color` property allows you to change the color of the chat tab text while selected. This takes priority 231 | over all the other color properties. Like the other color properties, it is in RGBA format. 232 | 233 | The `prefix` property appends the prefix to any message sent while in the specific chat tab **if it is not a command**. 234 | This can be used to automatically send messages in a specific channel in servers, like in Hypixel. This is no longer required as of version 5. 235 | 236 | The `lowercase` property makes the message trigger lowercase. 237 | 238 | ## Chat Tabs JSON Changelogs 239 | 240 | ### Version 6 (Chatting 1.4.2 beta5) 241 | - Changed PM tab to use regex instead of starts and sets `unformatted` to false 242 | - Changed directory of Chat Tabs JSON to `{MINECRAFT DIRECTORY}/OneConfig/profiles/{PROFILE}/chattabs.json` 243 | 244 | ### Version 5 (Chatting 1.4.0 [04363f5]) 245 | - The `prefix` property is no longer a required property. 246 | - Added `lowercase` property 247 | 248 | ### Version 4 (Chatting 1.4.0 [eece3cb]) 249 | - Added color text options (`color`, `hovered_color`, and `selected_color`) 250 | - `version` is now actually an integer 251 | 252 | ### Version 3 (Chatting 1.4.0-alpha1) 253 | - Added `ignore_` options (`ignore_starts`, `ignore_ends`, `ignore_equals`, and `ignore_regex`) 254 | 255 | ### Version 2 (1.0.0) 256 | - Added `enabled` property -------------------------------------------------------------------------------- /docs/docs.md: -------------------------------------------------------------------------------- 1 | # Chatting Docs 2 | 3 | ## - [Chat Tabs](chattabs.md) -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | mod_name = Chatting 2 | mod_id = chatting 3 | mod_version = 2.0.6 4 | mod_archives_name = Chatting 5 | 6 | # Gradle Configuration -- DO NOT TOUCH THESE VALUES. 7 | polyfrost.defaults.loom=3 8 | org.gradle.daemon=true 9 | org.gradle.parallel=true 10 | org.gradle.configureoncommand=true 11 | org.gradle.parallel.threads=4 12 | org.gradle.jvmargs=-Xmx2G -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Chatting/f543d5e0a2aa2107f48cdc01f0e67e65b6d4d862/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /root.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.9.10" apply false 3 | id("org.polyfrost.multi-version.root") 4 | id("com.github.johnrengelman.shadow") version "8.1.1" apply false 5 | } 6 | 7 | preprocess { 8 | "1.8.9-forge"(10809, "srg") {} 9 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("PropertyName") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | mavenCentral() 7 | maven("https://repo.polyfrost.org/releases") // Adds the Polyfrost maven repository to get Polyfrost Gradle Toolkit 8 | } 9 | plugins { 10 | val pgtVersion = "0.6.5" // Sets the default versions for Polyfrost Gradle Toolkit 11 | id("org.polyfrost.multi-version.root") version pgtVersion 12 | } 13 | } 14 | 15 | val mod_name: String by settings 16 | 17 | // Configures the root project Gradle name based on the value in `gradle.properties` 18 | rootProject.name = mod_name 19 | rootProject.buildFileName = "root.gradle.kts" 20 | 21 | // Adds all of our build target versions to the classpath if we need to add version-specific code. 22 | listOf( 23 | "1.8.9-forge" 24 | ).forEach { version -> 25 | include(":$version") 26 | project(":$version").apply { 27 | projectDir = file("versions/$version") 28 | buildFileName = "../../build.gradle.kts" 29 | } 30 | } -------------------------------------------------------------------------------- /src/dummy/java/cc/polyfrost/oneconfig/internal/hud/HudCore.java: -------------------------------------------------------------------------------- 1 | package cc.polyfrost.oneconfig.internal.hud; 2 | 3 | import cc.polyfrost.oneconfig.config.elements.BasicOption; 4 | import java.util.ArrayList; 5 | 6 | public class HudCore { 7 | 8 | public static boolean editing; 9 | public static final ArrayList hudOptions = new ArrayList<>(); 10 | } -------------------------------------------------------------------------------- /src/dummy/java/club/sk1er/patcher/config/OldPatcherConfig.java: -------------------------------------------------------------------------------- 1 | package club.sk1er.patcher.config; 2 | 3 | public class OldPatcherConfig { 4 | public static boolean transparentChat; // Chatting 5 | public static boolean transparentChatOnlyWhenClosed; // Chatting 6 | public static boolean transparentChatInputField; // Chatting 7 | } 8 | -------------------------------------------------------------------------------- /src/dummy/java/club/sk1er/patcher/config/PatcherConfig.java: -------------------------------------------------------------------------------- 1 | package club.sk1er.patcher.config; 2 | 3 | public class PatcherConfig { 4 | public static boolean transparentChatInputField; 5 | public static boolean transparentChat; 6 | public static boolean chatPosition; 7 | public static boolean extendedChatLength; 8 | } 9 | -------------------------------------------------------------------------------- /src/dummy/java/com/llamalad7/betterchat/BetterChat.java: -------------------------------------------------------------------------------- 1 | package com.llamalad7.betterchat; 2 | 3 | public class BetterChat { 4 | public static ChatSettings getSettings() { 5 | throw new AssertionError("Wyvest"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/dummy/java/com/llamalad7/betterchat/ChatSettings.java: -------------------------------------------------------------------------------- 1 | package com.llamalad7.betterchat; 2 | 3 | public class ChatSettings { 4 | public int xOffset; 5 | public int yOffset; 6 | public boolean smooth; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/hook/ChatHook.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.hook; 2 | 3 | import net.minecraft.client.gui.ChatLine; 4 | 5 | public class ChatHook { 6 | public static ChatLine currentLine = null; 7 | public static boolean lineVisible = false; 8 | } -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/hook/ChatLineHook.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.hook; 2 | 3 | import net.minecraft.client.gui.ChatLine; 4 | import net.minecraft.client.network.NetworkPlayerInfo; 5 | 6 | import java.lang.ref.WeakReference; 7 | import java.util.HashSet; 8 | 9 | public interface ChatLineHook { 10 | HashSet> chatting$chatLines = new HashSet<>(); 11 | boolean chatting$hasDetected(); 12 | NetworkPlayerInfo chatting$getPlayerInfo(); 13 | 14 | void chatting$updatePlayerInfo(); 15 | 16 | long chatting$getUniqueId(); 17 | 18 | ChatLine chatting$getFullMessage(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/hook/GuiChatHook.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.hook; 2 | 3 | public interface GuiChatHook { 4 | void chatting$triggerButtonReset(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/hook/GuiNewChatHook.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.hook; 2 | 3 | import net.minecraft.client.gui.ChatLine; 4 | 5 | import java.awt.datatransfer.Transferable; 6 | 7 | public interface GuiNewChatHook { 8 | int chatting$getRight(); 9 | 10 | boolean chatting$isHovering(); 11 | 12 | ChatLine chatting$getHoveredLine(int mouseY); 13 | 14 | Transferable chatting$getChattingChatComponent(int mouseY, int mouseButton); 15 | 16 | int chatting$getTextOpacity(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/ChatLineMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is from chat_heads is licensed under MPL-2.0, which can be found at https://www.mozilla.org/en-US/MPL/2.0/ 3 | * See: https://github.com/dzwdz/chat_heads/blob/fabric-1.16.x/LICENSE 4 | */ 5 | 6 | package org.polyfrost.chatting.mixin; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.polyfrost.chatting.config.ChattingConfig; 10 | import org.polyfrost.chatting.hook.ChatHook; 11 | import org.polyfrost.chatting.hook.ChatLineHook; 12 | import net.minecraft.client.Minecraft; 13 | import net.minecraft.client.gui.ChatLine; 14 | import net.minecraft.client.network.NetHandlerPlayClient; 15 | import net.minecraft.client.network.NetworkPlayerInfo; 16 | import net.minecraft.util.IChatComponent; 17 | import org.jetbrains.annotations.Nullable; 18 | import org.spongepowered.asm.mixin.Mixin; 19 | import org.spongepowered.asm.mixin.Unique; 20 | import org.spongepowered.asm.mixin.injection.At; 21 | import org.spongepowered.asm.mixin.injection.Inject; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | import java.lang.ref.WeakReference; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | import java.util.regex.Pattern; 28 | 29 | @Mixin(ChatLine.class) 30 | public class ChatLineMixin implements ChatLineHook { 31 | @Unique 32 | private boolean chatting$detected = false; 33 | @Unique 34 | private boolean chatting$first = true; 35 | @Unique 36 | private NetworkPlayerInfo chatting$playerInfo; 37 | @Unique 38 | private NetworkPlayerInfo chatting$detectedPlayerInfo; 39 | @Unique 40 | private static NetworkPlayerInfo chatting$lastPlayerInfo; 41 | @Unique 42 | private static long chatting$lastUniqueId = 0; 43 | @Unique 44 | private long chatting$uniqueId = 0; 45 | @Unique 46 | private ChatLine chatting$fullMessage = null; 47 | @Unique 48 | private static ChatLine chatting$lastChatLine = null; 49 | @Unique 50 | private static final Pattern chatting$pattern = Pattern.compile("(§.)|\\W"); 51 | 52 | @Inject(method = "", at = @At("RETURN")) 53 | private void onInit(int i, IChatComponent iChatComponent, int chatId, CallbackInfo ci) { 54 | chatting$lastUniqueId++; 55 | chatting$uniqueId = chatting$lastUniqueId; 56 | chatting$fullMessage = ChatHook.currentLine; 57 | if (chatting$lastChatLine == ChatHook.currentLine) { 58 | if (chatting$lastPlayerInfo != null) { 59 | return; 60 | } 61 | } 62 | chatting$lastChatLine = chatting$fullMessage; 63 | chatting$chatLines.add(new WeakReference<>((ChatLine) (Object) this)); 64 | NetHandlerPlayClient netHandler = Minecraft.getMinecraft().getNetHandler(); 65 | if (netHandler == null) return; 66 | Map nicknameCache = new HashMap<>(); 67 | try { 68 | for (String word : chatting$pattern.split(StringUtils.substringBefore(iChatComponent.getFormattedText(), ":"))) { 69 | if (word.isEmpty()) continue; 70 | chatting$playerInfo = netHandler.getPlayerInfo(word); 71 | if (chatting$playerInfo == null) { 72 | chatting$playerInfo = chatting$getPlayerFromNickname(word, netHandler, nicknameCache); 73 | } 74 | if (chatting$playerInfo != null) { 75 | chatting$detectedPlayerInfo = chatting$playerInfo; 76 | chatting$detected = true; 77 | if (ChatHook.lineVisible) { 78 | if (chatting$lastPlayerInfo != null && chatting$playerInfo.getGameProfile() == chatting$lastPlayerInfo.getGameProfile()) { 79 | chatting$first = false; 80 | if (ChattingConfig.INSTANCE.getHideChatHeadOnConsecutiveMessages()) { 81 | chatting$playerInfo = null; 82 | } 83 | } 84 | chatting$lastPlayerInfo = chatting$detectedPlayerInfo; 85 | } 86 | return; 87 | } 88 | } 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | 94 | @Unique 95 | @Nullable 96 | private static NetworkPlayerInfo chatting$getPlayerFromNickname(String word, NetHandlerPlayClient connection, Map nicknameCache) { 97 | if (nicknameCache.isEmpty()) { 98 | for (NetworkPlayerInfo p : connection.getPlayerInfoMap()) { 99 | IChatComponent displayName = p.getDisplayName(); 100 | if (displayName != null) { 101 | String nickname = displayName.getUnformattedTextForChat(); 102 | if (word.equals(nickname)) { 103 | nicknameCache.clear(); 104 | return p; 105 | } 106 | 107 | nicknameCache.put(nickname, p); 108 | } 109 | } 110 | } else { 111 | // use prepared cache 112 | return nicknameCache.get(word); 113 | } 114 | 115 | return null; 116 | } 117 | 118 | @Override 119 | public boolean chatting$hasDetected() { 120 | return chatting$detected; 121 | } 122 | 123 | @Override 124 | public NetworkPlayerInfo chatting$getPlayerInfo() { 125 | return chatting$playerInfo; 126 | } 127 | 128 | @Override 129 | public void chatting$updatePlayerInfo() { 130 | if (ChattingConfig.INSTANCE.getHideChatHeadOnConsecutiveMessages() && !chatting$first) { 131 | chatting$playerInfo = null; 132 | } else { 133 | chatting$playerInfo = chatting$detectedPlayerInfo; 134 | } 135 | } 136 | 137 | @Override 138 | public long chatting$getUniqueId() { 139 | return chatting$uniqueId; 140 | } 141 | 142 | @Override 143 | public ChatLine chatting$getFullMessage() { 144 | return chatting$fullMessage; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/ChatStyleMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.event.ClickEvent; 4 | import net.minecraft.util.ChatStyle; 5 | import net.minecraft.util.EnumChatFormatting; 6 | import org.polyfrost.chatting.config.ChattingConfig; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(ChatStyle.class) 15 | public class ChatStyleMixin { 16 | @Shadow 17 | private ClickEvent chatClickEvent; 18 | 19 | @Unique 20 | private boolean chatting$hasURL() { 21 | return ChattingConfig.INSTANCE.getUnderlinedLinks() 22 | && chatClickEvent != null 23 | && chatClickEvent.getAction() == ClickEvent.Action.OPEN_URL; 24 | } 25 | 26 | @Inject(method = "getUnderlined", at = @At("HEAD"), cancellable = true) 27 | private void linkUnderline(CallbackInfoReturnable cir) { 28 | if (chatting$hasURL()) { 29 | cir.setReturnValue(true); 30 | } 31 | } 32 | 33 | @Inject(method = "getColor", at = @At("HEAD"), cancellable = true) 34 | private void linkColor(CallbackInfoReturnable cir) { 35 | if (chatting$hasURL()) { 36 | cir.setReturnValue(EnumChatFormatting.BLUE); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/ClientCommandHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import kotlin.Pair; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.command.CommandHandler; 6 | import net.minecraft.command.ICommandSender; 7 | import net.minecraft.util.BlockPos; 8 | import net.minecraftforge.client.ClientCommandHandler; 9 | import net.minecraftforge.fml.client.FMLClientHandler; 10 | import org.polyfrost.chatting.chat.ChatShortcuts; 11 | import org.polyfrost.chatting.config.ChattingConfig; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Redirect; 15 | 16 | import java.util.List; 17 | 18 | @Mixin(value = ClientCommandHandler.class, remap = false) 19 | public class ClientCommandHandlerMixin extends CommandHandler { 20 | @Redirect(method = "autoComplete", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/ClientCommandHandler;getTabCompletionOptions(Lnet/minecraft/command/ICommandSender;Ljava/lang/String;Lnet/minecraft/util/BlockPos;)Ljava/util/List;")) 21 | private List addChatShortcuts(ClientCommandHandler instance, ICommandSender iCommandSender, String leftOfCursor, BlockPos blockPos) { 22 | Minecraft mc = FMLClientHandler.instance().getClient(); 23 | List autocompleteList = instance.getTabCompletionOptions(mc.thePlayer, leftOfCursor, mc.thePlayer.getPosition()); 24 | if (ChattingConfig.INSTANCE.getChatShortcuts()) { 25 | for (Pair pair : ChatShortcuts.INSTANCE.getShortcuts()) { 26 | if (pair.getFirst().startsWith(leftOfCursor)) { 27 | autocompleteList.add(pair.getFirst()); 28 | } 29 | } 30 | } 31 | return autocompleteList; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/EntityPlayerSPMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.entity.EntityPlayerSP; 4 | import net.minecraft.client.network.NetHandlerPlayClient; 5 | import net.minecraft.network.Packet; 6 | import net.minecraft.network.play.client.C01PacketChatMessage; 7 | import org.polyfrost.chatting.chat.ChatTab; 8 | import org.polyfrost.chatting.chat.ChatTabs; 9 | import org.polyfrost.chatting.config.ChattingConfig; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Redirect; 15 | 16 | @Mixin(value = EntityPlayerSP.class, priority = 0) 17 | public class EntityPlayerSPMixin { 18 | @Shadow @Final public NetHandlerPlayClient sendQueue; 19 | 20 | @Redirect(method = "sendChatMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/NetHandlerPlayClient;addToSendQueue(Lnet/minecraft/network/Packet;)V")) 21 | private void handleSentMessages(NetHandlerPlayClient instance, Packet packet, String value) { 22 | if (value.startsWith("/")) { 23 | sendQueue.addToSendQueue(packet); 24 | return; 25 | } 26 | if (ChattingConfig.INSTANCE.getChatTabs() && !ChatTabs.INSTANCE.getCurrentTabs().isEmpty()) { 27 | boolean sent = false; 28 | for (ChatTab tab : ChatTabs.INSTANCE.getCurrentTabs()) { 29 | if (tab.getPrefix() != null && !tab.getPrefix().isEmpty()) { 30 | sendQueue.addToSendQueue(new C01PacketChatMessage(tab.getPrefix() + value)); 31 | sent = true; 32 | } 33 | } 34 | if (!sent) { 35 | sendQueue.addToSendQueue(packet); 36 | } 37 | } else { 38 | sendQueue.addToSendQueue(packet); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/EntityRendererMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.UResolution; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.renderer.EntityRenderer; 6 | import org.polyfrost.chatting.config.ChattingConfig; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(EntityRenderer.class) 13 | public class EntityRendererMixin { 14 | @Inject(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiIngame;renderGameOverlay(F)V", shift = At.Shift.AFTER)) 15 | private void draw(float partialTicks, long nanoTime, CallbackInfo ci) { 16 | ChattingConfig.INSTANCE.getChatWindow().setGuiIngame(false); 17 | ((GuiIngameForgeAccessor) Minecraft.getMinecraft().ingameGUI).drawChat(UResolution.getScaledWidth(), UResolution.getScaledHeight()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GameSettingsMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.settings.GameSettings; 5 | import org.objectweb.asm.Opcodes; 6 | import org.polyfrost.chatting.config.ChattingConfig; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(GameSettings.class) 14 | public class GameSettingsMixin { 15 | @Shadow protected Minecraft mc; 16 | 17 | @Inject(method = "setOptionFloatValue", at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;chatScale:F", opcode = Opcodes.PUTFIELD, shift = At.Shift.AFTER)) 18 | private void onChatScaleChange(GameSettings.Options settingsOption, float value, CallbackInfo ci) { 19 | ChattingConfig.INSTANCE.getChatWindow().updateMCChatScale(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiChatMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.*; 4 | import com.google.common.collect.Lists; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.*; 7 | import net.minecraft.client.renderer.GlStateManager; 8 | import net.minecraftforge.fml.client.config.GuiUtils; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.lwjgl.input.Keyboard; 11 | import org.lwjgl.input.Mouse; 12 | import org.polyfrost.chatting.chat.*; 13 | import org.polyfrost.chatting.config.ChattingConfig; 14 | import org.polyfrost.chatting.gui.components.*; 15 | import org.polyfrost.chatting.hook.*; 16 | import org.polyfrost.chatting.utils.ModCompatHooks; 17 | import org.spongepowered.asm.mixin.*; 18 | import org.spongepowered.asm.mixin.injection.*; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | import java.awt.*; 22 | import java.awt.datatransfer.Transferable; 23 | import java.util.List; 24 | 25 | @Mixin(GuiChat.class) 26 | public abstract class GuiChatMixin extends GuiScreen implements GuiChatHook { 27 | @Shadow 28 | protected GuiTextField inputField; 29 | 30 | @Shadow 31 | public abstract void drawScreen(int mouseX, int mouseY, float partialTicks); 32 | 33 | @Shadow 34 | private String defaultInputFieldText; 35 | 36 | /** 37 | * Gets the modifier key name depending on the operating system 38 | * 39 | * @return "OPTION" if macOS, otherwise, "ALT" 40 | */ 41 | @Unique 42 | private static String chatting$getModifierKey() { 43 | return (UDesktop.isMac()) ? "OPTION" : "ALT"; 44 | } 45 | 46 | @Unique 47 | private static final List COPY_TOOLTIP = Lists.newArrayList( 48 | "§e§lCopy To Clipboard", 49 | "§b§lNORMAL CLICK§r §8- §7Full Message", 50 | "§b§lCTRL CLICK§r §8- §7Single Line", 51 | "§b§lSHIFT CLICK§r §8- §7Screenshot Message", 52 | "", 53 | "§e§lModifiers", 54 | "§b§l" + chatting$getModifierKey() + "§r §8- §7Formatting Codes" 55 | ); 56 | 57 | @Unique 58 | private static final List DELETE_TOOLTIP = Lists.newArrayList( 59 | "§b§lNORMAL CLICK§r §8- §7Full Message", 60 | "§b§lCTRL CLICK§r §8- §7Single Line" 61 | ); 62 | 63 | @Unique 64 | private SearchButton chatting$searchButton; 65 | @Unique 66 | private ScreenshotButton chatting$screenshotButton; 67 | @Unique 68 | private ClearButton chatting$clearButton; 69 | 70 | @Inject(method = "initGui", at = @At("TAIL")) 71 | private void init(CallbackInfo ci) { 72 | chatting$initButtons(); 73 | if (ChattingConfig.INSTANCE.getChatInput().getInputFieldDraft()) { 74 | String command = (ChatHooks.INSTANCE.getCommandDraft().startsWith("/") ? "" : "/") + ChatHooks.INSTANCE.getCommandDraft(); 75 | inputField.setText(inputField.getText().startsWith("/") ? command : ChatHooks.INSTANCE.getDraft()); 76 | } 77 | ChatHooks.INSTANCE.setTextField(inputField); 78 | } 79 | 80 | @Inject(method = "updateScreen", at = @At("HEAD")) 81 | private void updateScreen(CallbackInfo ci) { 82 | if (ChattingConfig.INSTANCE.getChatSearch() && chatting$searchButton.isEnabled()) { 83 | chatting$searchButton.getInputField().updateCursorCounter(); 84 | } 85 | } 86 | 87 | @Inject(method = "keyTyped", at = @At("HEAD"), cancellable = true) 88 | private void keyTyped(char typedChar, int keyCode, CallbackInfo ci) { 89 | if (ChattingConfig.INSTANCE.getChatSearch() && chatting$searchButton.isEnabled()) { 90 | ci.cancel(); 91 | if (keyCode == 1) { 92 | chatting$searchButton.onMousePress(); 93 | return; 94 | } 95 | chatting$searchButton.getInputField().textboxKeyTyped(typedChar, keyCode); 96 | ChatSearchingManager.INSTANCE.setLastSearch(chatting$searchButton.getInputField().getText()); 97 | } else if ((Keyboard.isKeyDown(219) || Keyboard.isKeyDown(220) || Keyboard.isKeyDown(29) || Keyboard.isKeyDown(157)) && keyCode == UKeyboard.KEY_TAB) { // either macos super key or ctrl key for any os 98 | ChatHooks.INSTANCE.switchTab(); 99 | } 100 | } 101 | 102 | @Inject(method = "drawScreen", at = @At("HEAD")) 103 | private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) { 104 | boolean copy = ChattingConfig.INSTANCE.getChatCopy(); 105 | boolean delete = ChattingConfig.INSTANCE.getChatDelete(); 106 | if (!copy && !delete) return; 107 | GuiNewChatHook hook = ((GuiNewChatHook) Minecraft.getMinecraft().ingameGUI.getChatGUI()); 108 | ChatWindow hud = ChattingConfig.INSTANCE.getChatWindow(); 109 | int scale = new ScaledResolution(mc).getScaleFactor(); 110 | int x = Mouse.getX(); 111 | int right = (int) ((hook.chatting$getRight() + ModCompatHooks.getXOffset() + 1 + hud.getPaddingX() * (ChattingConfig.INSTANCE.getExtendBG() ? 1f : 2f)) * hud.getScale() + (int) hud.position.getX()); 112 | delete = delete && chatting$hovered(hook, x, right + (int) ((copy ? 10 : 0) * hud.getScale()), scale, hud); 113 | copy = copy && chatting$hovered(hook, x, right, scale, hud); 114 | 115 | if (copy || delete) { 116 | List tooltip = delete ? DELETE_TOOLTIP : COPY_TOOLTIP; 117 | GuiUtils.drawHoveringText(tooltip, mouseX, mouseY, width, height, -1, fontRendererObj); 118 | GlStateManager.disableLighting(); 119 | } 120 | } 121 | 122 | @Unique 123 | private boolean chatting$hovered(GuiNewChatHook hook, int x, int right, int scale, ChatWindow hud) { 124 | return hook.chatting$isHovering() && x > right * scale && x < (right + 9 * hud.getScale()) * scale; 125 | } 126 | 127 | @Redirect(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiChat;drawRect(IIIII)V")) 128 | private void cancelBG(int left, int top, int right, int bottom, int color) { 129 | ChattingConfig config = ChattingConfig.INSTANCE; 130 | ChatHooks.INSTANCE.setInputBoxRight(config.getChatInput().getCompactInputBox() ? Math.max((int) config.getChatWindow().getWidth() + 2, ChatHooks.INSTANCE.getInputRight() + (inputField.getText().length() < ModCompatHooks.getChatInputLimit() ? 8 : 2)) : width - 2); 131 | config.getChatInput().drawBG(); 132 | } 133 | 134 | @Inject(method = "mouseClicked", at = @At("HEAD")) 135 | private void mouseClicked(int mouseX, int mouseY, int mouseButton, CallbackInfo ci) { 136 | GuiNewChatHook hook = ((GuiNewChatHook) Minecraft.getMinecraft().ingameGUI.getChatGUI()); 137 | ChatWindow hud = ChattingConfig.INSTANCE.getChatWindow(); 138 | int scale = new ScaledResolution(mc).getScaleFactor(); 139 | int x = Mouse.getX(); 140 | if (hook.chatting$isHovering()) { 141 | boolean copy = ChattingConfig.INSTANCE.getChatCopy(); 142 | int right = (int) ((hook.chatting$getRight() + ModCompatHooks.getXOffset() + 1 + hud.getPaddingX() * (ChattingConfig.INSTANCE.getExtendBG() ? 1f : 2f)) * hud.getScale() + (int) hud.position.getX()) * scale; 143 | if (copy && x > right && x < right + 9 * hud.getScale() * scale || (mouseButton == 1 && ChattingConfig.INSTANCE.getRightClickCopy() && (!ChattingConfig.INSTANCE.getRightClickCopyCtrl() || UKeyboard.isCtrlKeyDown()))) { 144 | Transferable message = hook.chatting$getChattingChatComponent(Mouse.getY(), mouseButton); 145 | if (message == null) return; 146 | try { 147 | Toolkit.getDefaultToolkit().getSystemClipboard().setContents(message, null); 148 | } catch (Exception e) { 149 | e.printStackTrace(); 150 | } 151 | } else if (ChattingConfig.INSTANCE.getChatDelete() && x > right + (copy ? 10 : 0) * hud.getScale() * scale && x < right + ((copy ? 10 : 0) + 9) * hud.getScale() * scale) { 152 | ChatLine chatLine = hook.chatting$getHoveredLine(Mouse.getY()); 153 | if (chatLine == null) return; 154 | ModCompatHooks.getDrawnChatLines().removeIf(line -> chatting$remove(line, chatLine)); 155 | ModCompatHooks.getChatLines().removeIf(line -> chatting$remove(line, chatLine)); 156 | } 157 | } 158 | } 159 | 160 | @Unique 161 | private boolean chatting$remove(ChatLine line, ChatLine chatLine) { 162 | return UKeyboard.isCtrlKeyDown() ? 163 | ((ChatLineHook) line).chatting$getUniqueId() == ((ChatLineHook) chatLine).chatting$getUniqueId() : 164 | ((ChatLineHook) ((ChatLineHook) line).chatting$getFullMessage()).chatting$getUniqueId() == ((ChatLineHook) ((ChatLineHook) chatLine).chatting$getFullMessage()).chatting$getUniqueId(); 165 | } 166 | 167 | @ModifyArg(method = "keyTyped", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiChat;sendChatMessage(Ljava/lang/String;)V"), index = 0) 168 | private String modifySentMessage(String original) { 169 | if (ChattingConfig.INSTANCE.getChatShortcuts()) { 170 | if (original.startsWith("/")) { 171 | return "/" + ChatShortcuts.INSTANCE.handleSentCommand(StringUtils.substringAfter(original, "/")); 172 | } 173 | } 174 | return original; 175 | } 176 | 177 | @Inject(method = "keyTyped", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiChat;sendChatMessage(Ljava/lang/String;)V")) 178 | private void clearDraft(CallbackInfo ci) { 179 | if (ChattingConfig.INSTANCE.getChatInput().getInputFieldDraft()) { 180 | inputField.setText(inputField.getText().startsWith("/") ? "/" : ""); 181 | } 182 | } 183 | 184 | @Inject(method = "onGuiClosed", at = @At("HEAD")) 185 | private void saveDraft(CallbackInfo ci) { 186 | if (ChattingConfig.INSTANCE.getChatInput().getInputFieldDraft()) { 187 | if (inputField.getText().startsWith("/")) { 188 | ChatHooks.INSTANCE.setCommandDraft(inputField.getText()); 189 | } else { 190 | if (inputField.getText().isEmpty() && defaultInputFieldText.equals("/")) return; 191 | ChatHooks.INSTANCE.setDraft(inputField.getText()); 192 | } 193 | } 194 | } 195 | 196 | 197 | @Inject(method = "handleMouseInput", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiNewChat;scroll(I)V")) 198 | private void handleMouseInput(CallbackInfo ci) { 199 | ChatScrollingHook.INSTANCE.setShouldSmooth(true); 200 | } 201 | 202 | @Unique 203 | private void chatting$initButtons() { 204 | chatting$searchButton = new SearchButton(); 205 | if (ChattingConfig.INSTANCE.getChatSearch()) { 206 | buttonList.add(chatting$searchButton); 207 | } 208 | chatting$screenshotButton = new ScreenshotButton(); 209 | if (ChattingConfig.INSTANCE.getChatScreenshot()) { 210 | buttonList.add(chatting$screenshotButton); 211 | } 212 | chatting$clearButton = new ClearButton(); 213 | if (ChattingConfig.INSTANCE.getChatDeleteHistory()) { 214 | buttonList.add(chatting$clearButton); 215 | } 216 | if (ChattingConfig.INSTANCE.getChatTabs()) { 217 | for (ChatTab chatTab : ChatTabs.INSTANCE.getTabs()) { 218 | buttonList.add(chatTab.getButton()); 219 | } 220 | } 221 | } 222 | 223 | @Override 224 | public void chatting$triggerButtonReset() { 225 | buttonList.removeIf(button -> button instanceof CleanButton); 226 | chatting$initButtons(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiContainerCreativeMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.gui.inventory.GuiContainerCreative; 4 | import org.polyfrost.chatting.Chatting; 5 | import org.polyfrost.chatting.config.ChattingConfig; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(GuiContainerCreative.class) 12 | public class GuiContainerCreativeMixin { 13 | 14 | @Inject(method = "handleMouseInput", at = @At(value = "INVOKE", target = "Lorg/lwjgl/input/Mouse;getEventDWheel()I"), cancellable = true) 15 | private void cancelScrolling(CallbackInfo ci) { 16 | if (Chatting.INSTANCE.getPeeking() && ChattingConfig.INSTANCE.getPeekScrolling()) { 17 | ci.cancel(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiIngameForgeAccessor.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraftforge.client.GuiIngameForge; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Invoker; 6 | 7 | @Mixin(GuiIngameForge.class) 8 | public interface GuiIngameForgeAccessor { 9 | @Invoker("renderChat") 10 | void drawChat(int width, int height); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiIngameForgeMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraftforge.client.GuiIngameForge; 4 | import org.polyfrost.chatting.config.ChattingConfig; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.ModifyArgs; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | import org.spongepowered.asm.mixin.injection.invoke.arg.Args; 11 | 12 | @Mixin(GuiIngameForge.class) 13 | public class GuiIngameForgeMixin { 14 | 15 | @ModifyArgs(method = "renderChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;translate(FFF)V")) 16 | private void cancelTranslate(Args args) { 17 | args.set(1, 0f); 18 | } 19 | 20 | @Inject(method = "renderChat", at = @At(value = "HEAD"), cancellable = true, remap = false) 21 | private void cancelChat(int width, int height, CallbackInfo ci) { 22 | if (!ChattingConfig.INSTANCE.getChatWindow().canShow()) ci.cancel(); 23 | } 24 | 25 | @Inject(method = "renderGameOverlay", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/GuiIngameForge;renderChat(II)V")) 26 | private void setBypass(float partialTicks, CallbackInfo ci) { 27 | ChattingConfig.INSTANCE.getChatWindow().setGuiIngame(true); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatAccessor.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.gui.ChatLine; 4 | import net.minecraft.client.gui.GuiNewChat; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | import java.util.List; 9 | 10 | @Mixin(GuiNewChat.class) 11 | public interface GuiNewChatAccessor { 12 | @Accessor 13 | List getDrawnChatLines(); 14 | 15 | @Accessor 16 | List getChatLines(); 17 | 18 | @Accessor 19 | int getScrollPos(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_ChatHeight.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.gui.GuiNewChat; 4 | import org.polyfrost.chatting.Chatting; 5 | import org.polyfrost.chatting.config.ChattingConfig; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | 12 | @Mixin(GuiNewChat.class) 13 | public abstract class GuiNewChatMixin_ChatHeight { 14 | @Shadow public abstract boolean getChatOpen(); 15 | 16 | @Inject(method = "getChatHeight", at = @At("HEAD"), cancellable = true) 17 | private void customHeight_getChatHeight(CallbackInfoReturnable cir) { 18 | if (ChattingConfig.INSTANCE.getChatWindow().getCustomChatHeight()) 19 | cir.setReturnValue(Chatting.INSTANCE.getChatHeight(getChatOpen())); 20 | } 21 | 22 | @Inject(method = "getChatWidth", at = @At("HEAD"), cancellable = true) 23 | private void customWidth_getChatWidth(CallbackInfoReturnable cir) { 24 | if (ChattingConfig.INSTANCE.getChatWindow().getCustomChatWidth()) 25 | cir.setReturnValue(Chatting.INSTANCE.getChatWidth()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_ChatPeek.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.gui.GuiNewChat; 4 | import org.polyfrost.chatting.Chatting; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 9 | 10 | @Mixin(value = GuiNewChat.class, priority = 1100) 11 | public class GuiNewChatMixin_ChatPeek { 12 | 13 | @Inject(method = "getChatOpen", at = @At("HEAD"), cancellable = true) 14 | private void chatPeek(CallbackInfoReturnable cir) { 15 | if (Chatting.INSTANCE.getPeeking()) cir.setReturnValue(true); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_ChatSearching.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.gui.ChatLine; 4 | import net.minecraft.client.gui.GuiNewChat; 5 | import net.minecraft.util.ChatComponentText; 6 | import net.minecraft.util.IChatComponent; 7 | import org.objectweb.asm.Opcodes; 8 | import org.polyfrost.chatting.chat.ChatSearchingManager; 9 | import org.polyfrost.chatting.chat.ChatTabs; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | @Mixin(GuiNewChat.class) 22 | public class GuiNewChatMixin_ChatSearching { 23 | @Shadow @Final private List drawnChatLines; 24 | 25 | @Inject(method = "setChatLine", at = @At("HEAD")) 26 | private void handleSetChatLine(IChatComponent chatComponent, int chatLineId, int updateCounter, boolean displayOnly, CallbackInfo ci) { 27 | ChatSearchingManager.getCache().invalidateAll(); 28 | } 29 | 30 | @Redirect(method = "drawChat", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/GuiNewChat;drawnChatLines:Ljava/util/List;", opcode = Opcodes.GETFIELD)) 31 | private List injected(GuiNewChat instance) { 32 | return ChatSearchingManager.filterMessages(ChatSearchingManager.INSTANCE.getLastSearch(), drawnChatLines); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_ChatTabs.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import org.polyfrost.chatting.chat.ChatTabs; 4 | import org.polyfrost.chatting.config.ChattingConfig; 5 | import org.polyfrost.chatting.utils.ModCompatHooks; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.gui.ChatLine; 8 | import net.minecraft.client.gui.GuiNewChat; 9 | import net.minecraft.util.IChatComponent; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | import java.util.List; 18 | 19 | @Mixin(value = GuiNewChat.class, priority = 990) 20 | public abstract class GuiNewChatMixin_ChatTabs { 21 | @Shadow @Final private Minecraft mc; 22 | 23 | @Shadow public abstract void deleteChatLine(int id); 24 | 25 | @Shadow @Final private List chatLines; 26 | 27 | @Inject(method = "printChatMessageWithOptionalDeletion", at = @At("HEAD"), cancellable = true) 28 | private void handlePrintChatMessage(IChatComponent chatComponent, int chatLineId, CallbackInfo ci) { 29 | handleChatTabMessage(chatComponent, chatLineId, mc.ingameGUI.getUpdateCounter(), false, ci); 30 | } 31 | 32 | @Inject(method = "setChatLine", at = @At("HEAD"), cancellable = true) 33 | private void handleSetChatLine(IChatComponent chatComponent, int chatLineId, int updateCounter, boolean displayOnly, CallbackInfo ci) { 34 | handleChatTabMessage(chatComponent, chatLineId, updateCounter, displayOnly, ci); 35 | } 36 | 37 | private void handleChatTabMessage(IChatComponent chatComponent, int chatLineId, int updateCounter, boolean displayOnly, CallbackInfo ci) { 38 | if (ChattingConfig.INSTANCE.getChatTabs()) { 39 | ChatTabs.INSTANCE.setHasCancelledAnimation(!ChatTabs.INSTANCE.shouldRender(chatComponent)); 40 | if (!ChatTabs.INSTANCE.shouldRender(chatComponent)) { 41 | if (chatLineId != 0) { 42 | deleteChatLine(chatLineId); 43 | } 44 | if (!displayOnly) { 45 | chatLines.add(0, new ChatLine(updateCounter, chatComponent, chatLineId)); 46 | while (this.chatLines.size() > (100 + ModCompatHooks.getExtendedChatLength())) { 47 | this.chatLines.remove(this.chatLines.size() - 1); 48 | } 49 | } 50 | ci.cancel(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_Movable.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import cc.polyfrost.oneconfig.hud.Position; 4 | import cc.polyfrost.oneconfig.internal.hud.HudCore; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.ChatLine; 7 | import net.minecraft.client.gui.GuiNewChat; 8 | import net.minecraft.client.gui.ScaledResolution; 9 | import net.minecraft.util.MathHelper; 10 | import org.polyfrost.chatting.chat.ChatWindow; 11 | import org.polyfrost.chatting.config.ChattingConfig; 12 | import org.polyfrost.chatting.hook.ChatLineHook; 13 | import org.polyfrost.chatting.utils.ModCompatHooks; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Shadow; 16 | import org.spongepowered.asm.mixin.Unique; 17 | import org.spongepowered.asm.mixin.injection.*; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | import org.spongepowered.asm.mixin.injection.invoke.arg.Args; 20 | 21 | @Mixin(GuiNewChat.class) 22 | public abstract class GuiNewChatMixin_Movable { 23 | 24 | @Shadow public abstract int getChatWidth(); 25 | 26 | @Unique private static ChatLine chatting$currentLine; 27 | 28 | @ModifyArgs(method = "drawChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;translate(FFF)V", ordinal = 0)) 29 | private void translate(Args args) { 30 | ChatWindow hud = ChattingConfig.INSTANCE.getChatWindow(); 31 | Position position = hud.position; 32 | args.set(0, position.getX() + hud.getPaddingX() * hud.getScale()); 33 | args.set(1, position.getBottomY() - hud.getPaddingY() * hud.getScale()); 34 | } 35 | 36 | @ModifyVariable(method = "drawChat", at = @At(value = "STORE", ordinal = 0), ordinal = 4) 37 | private int width(int value) { 38 | return MathHelper.ceiling_float_int((float)this.getChatWidth()); 39 | } 40 | 41 | @ModifyConstant(method = "drawChat", constant = @Constant(intValue = 9, ordinal = 0)) 42 | private int chatMode(int constant) { 43 | return constant; 44 | } 45 | 46 | @Inject(method = "drawChat", at = @At(value = "HEAD"), cancellable = true) 47 | private void exampleChat(int updateCounter, CallbackInfo ci) { 48 | if (HudCore.editing) ci.cancel(); 49 | } 50 | 51 | @ModifyConstant(method = "getChatComponent", constant = @Constant(intValue = 3)) 52 | private int mouseX(int constant) { 53 | ChattingConfig config = ChattingConfig.INSTANCE; 54 | ChatWindow hud = config.getChatWindow(); 55 | return (int) ((hud.position.getX()) + (hud.getPaddingX() + 1) * hud.getScale()); 56 | } 57 | 58 | @ModifyConstant(method = "getChatComponent", constant = @Constant(intValue = 27)) 59 | private int mouseY(int constant) { 60 | int height = new ScaledResolution(Minecraft.getMinecraft()).getScaledHeight(); 61 | ChatWindow hud = ChattingConfig.INSTANCE.getChatWindow(); 62 | return height - (int) (hud.position.getBottomY() - hud.getPaddingY() * hud.getScale() + ModCompatHooks.getChatPosition()); 63 | } 64 | 65 | @ModifyVariable(method = "getChatComponent", at = @At("STORE"), ordinal = 0) 66 | private ChatLine capture(ChatLine chatLine) { 67 | chatting$currentLine = chatLine; 68 | return chatLine; 69 | } 70 | 71 | @ModifyConstant(method = "getChatComponent", constant = @Constant(intValue = 0)) 72 | private int offset(int value) { 73 | return ((ChatLineHook) chatting$currentLine).chatting$hasDetected() || ChattingConfig.INSTANCE.getOffsetNonPlayerMessages() ? ModCompatHooks.getChatHeadOffset() : 0; 74 | } 75 | 76 | @ModifyArg(method = "getChatComponent", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/MathHelper;floor_float(F)I", ordinal = 2)) 77 | private float width(float value) { 78 | ChatWindow hud = ChattingConfig.INSTANCE.getChatWindow(); 79 | return hud.position.getX() + hud.getWidth(); 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_Scrolling.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import cc.polyfrost.oneconfig.gui.animations.Animation; 4 | import cc.polyfrost.oneconfig.gui.animations.DummyAnimation; 5 | import org.polyfrost.chatting.chat.ChatScrollingHook; 6 | import net.minecraft.client.gui.Gui; 7 | import net.minecraft.client.gui.GuiNewChat; 8 | import org.polyfrost.chatting.config.ChattingConfig; 9 | import org.polyfrost.chatting.utils.EaseOutQuad; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.Unique; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(GuiNewChat.class) 19 | public abstract class GuiNewChatMixin_Scrolling extends Gui { 20 | @Shadow 21 | private int scrollPos; 22 | 23 | @Unique 24 | private Animation chatting$scrollingAnimation = new DummyAnimation(0f); 25 | 26 | @Inject(method = "drawChat", at = @At("HEAD")) 27 | private void chatting$scrollingAnimationStart(int updateCounter, CallbackInfo ci) { 28 | boolean shouldSmooth = ChatScrollingHook.INSTANCE.getShouldSmooth(); 29 | if (shouldSmooth) ChatScrollingHook.INSTANCE.setShouldSmooth(false); 30 | if (ChattingConfig.INSTANCE.getSmoothScrolling()) { 31 | if (chatting$scrollingAnimation.getEnd() != scrollPos) { 32 | if (Math.abs(chatting$scrollingAnimation.getEnd() - scrollPos) > 1 && shouldSmooth) { 33 | chatting$scrollingAnimation = new EaseOutQuad((int) (ChattingConfig.INSTANCE.getScrollingSpeed() * 1000), chatting$scrollingAnimation.get(), scrollPos, false); 34 | } else { 35 | chatting$scrollingAnimation = new DummyAnimation(scrollPos); 36 | } 37 | } 38 | } 39 | } 40 | 41 | @Redirect(method = "drawChat", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/GuiNewChat;scrollPos:I")) 42 | private int redirectPos(GuiNewChat instance) { 43 | return ChattingConfig.INSTANCE.getSmoothScrolling() ? (int) chatting$scrollingAnimation.get() : scrollPos; 44 | } 45 | 46 | @Redirect(method = "drawChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiNewChat;drawRect(IIIII)V", ordinal = 1)) 47 | private void redirectScrollBar(int left, int top, int right, int bottom, int color) { 48 | if (!ChattingConfig.INSTANCE.getRemoveScrollBar()) { 49 | drawRect(left, top, right, bottom, color); 50 | } 51 | } 52 | 53 | @Redirect(method = "drawChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiNewChat;drawRect(IIIII)V", ordinal = 2)) 54 | private void redirectScrollBar2(int left, int top, int right, int bottom, int color) { 55 | if (!ChattingConfig.INSTANCE.getRemoveScrollBar()) { 56 | drawRect(left, top, right, bottom, color); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_SmoothMessages.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import org.polyfrost.chatting.Chatting; 4 | import org.polyfrost.chatting.chat.ChatSearchingManager; 5 | import org.polyfrost.chatting.chat.ChatTabs; 6 | import org.polyfrost.chatting.config.ChattingConfig; 7 | import org.polyfrost.chatting.utils.EaseOutQuart; 8 | import org.polyfrost.chatting.utils.ModCompatHooks; 9 | import net.minecraft.client.gui.GuiNewChat; 10 | import net.minecraft.client.renderer.GlStateManager; 11 | import net.minecraft.util.EnumChatFormatting; 12 | import net.minecraft.util.IChatComponent; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.ModifyArg; 19 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | 22 | import java.util.List; 23 | import java.util.Locale; 24 | 25 | /** 26 | * Taken from BetterChat under LGPL 3.0 27 | * https://github.com/LlamaLad7/Better-Chat/blob/1.8.9/LICENSE 28 | */ 29 | @Mixin(GuiNewChat.class) 30 | public abstract class GuiNewChatMixin_SmoothMessages { 31 | @Shadow 32 | private boolean isScrolled; 33 | 34 | @Shadow 35 | public abstract float getChatScale(); 36 | @Unique 37 | private int chatting$newLines; 38 | 39 | @Unique 40 | private EaseOutQuart chatting$easeOutQuart; 41 | @Unique 42 | private float chatting$animationPercent; 43 | @Unique 44 | private int chatting$lineBeingDrawn; 45 | 46 | @Inject(method = "drawChat", at = @At("HEAD")) 47 | private void modifyChatRendering(CallbackInfo ci) { 48 | if (chatting$easeOutQuart != null) { 49 | if (chatting$easeOutQuart.isFinished()) { 50 | chatting$easeOutQuart = null; 51 | } else { 52 | chatting$animationPercent = chatting$easeOutQuart.get(); 53 | } 54 | } else { 55 | chatting$animationPercent = 1; 56 | } 57 | } 58 | 59 | @Inject(method = "drawChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;pushMatrix()V", ordinal = 0, shift = At.Shift.AFTER)) 60 | private void translate(CallbackInfo ci) { 61 | float y = 0; 62 | if (ChattingConfig.INSTANCE.getSmoothChat() && !this.isScrolled) { 63 | y += (9 - 9 * chatting$animationPercent) * this.getChatScale(); 64 | } 65 | GlStateManager.translate(0, y, 0); 66 | } 67 | 68 | @ModifyArg(method = "drawChat", at = @At(value = "INVOKE", target = "Ljava/util/List;get(I)Ljava/lang/Object;", ordinal = 0, remap = false), index = 0) 69 | private int getLineBeingDrawn(int line) { 70 | chatting$lineBeingDrawn = line; 71 | return line; 72 | } 73 | 74 | @ModifyArg(method = "drawChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/FontRenderer;drawStringWithShadow(Ljava/lang/String;FFI)I")) 75 | private int modifyTextOpacity(int original) { 76 | if (ChattingConfig.INSTANCE.getSmoothChat() && chatting$lineBeingDrawn <= chatting$newLines) { 77 | int opacity = (original >> 24) & 0xFF; 78 | opacity *= chatting$animationPercent; 79 | return (original & ~(0xFF << 24)) | (opacity << 24); 80 | } else { 81 | return original; 82 | } 83 | } 84 | 85 | @Inject(method = "printChatMessageWithOptionalDeletion", at = @At("HEAD")) 86 | private void resetPercentage(IChatComponent chatComponent, int chatLineId, CallbackInfo ci) { 87 | if (chatLineId != 0 && ChattingConfig.INSTANCE.getDisableSmoothEdits()) { 88 | return; 89 | } 90 | if (!EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.getUnformattedText()).toLowerCase(Locale.ENGLISH).contains(ChatSearchingManager.INSTANCE.getLastSearch().toLowerCase(Locale.ENGLISH))) { 91 | return; 92 | } 93 | if (ModCompatHooks.getBetterChatSmoothMessages()) { 94 | return; 95 | } 96 | if (ChatTabs.INSTANCE.getHasCancelledAnimation()) { 97 | ChatTabs.INSTANCE.setHasCancelledAnimation(false); 98 | return; 99 | } 100 | chatting$easeOutQuart = new EaseOutQuart((1.0f - ChattingConfig.INSTANCE.getMessageSpeed()) * 1000f, 0f, 1f, false); 101 | } 102 | 103 | @ModifyVariable(method = "setChatLine", at = @At("STORE"), ordinal = 0) 104 | private List setNewLines(List original) { 105 | chatting$newLines = original.size() - 1; 106 | return original; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiNewChatMixin_TextRendering.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.gui.ChatLine; 4 | import net.minecraft.client.gui.FontRenderer; 5 | import net.minecraft.client.gui.GuiNewChat; 6 | import org.polyfrost.chatting.utils.ModCompatHooks; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 14 | 15 | @Mixin(value = GuiNewChat.class, priority = 990) 16 | public class GuiNewChatMixin_TextRendering { 17 | @Unique 18 | private ChatLine chatting$drawingLine = null; 19 | 20 | @Inject(method = "drawChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ChatLine;getChatComponent()Lnet/minecraft/util/IChatComponent;"), locals = LocalCapture.CAPTURE_FAILSOFT) 21 | private void captureChatLine(int updateCounter, CallbackInfo ci, int i, boolean bl, int j, int k, float f, float g, int l, int m, ChatLine chatLine, int n, double d, int o, int p, int q) { 22 | chatting$drawingLine = chatLine; 23 | } 24 | 25 | @Redirect(method = "drawChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/FontRenderer;drawStringWithShadow(Ljava/lang/String;FFI)I")) 26 | private int redirectDrawString(FontRenderer instance, String text, float x, float y, int color) { 27 | return ModCompatHooks.redirectDrawString(text, x, y, color, chatting$drawingLine); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiTextFieldMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.client.gui.GuiTextField; 4 | import org.polyfrost.chatting.chat.ChatHooks; 5 | import org.polyfrost.chatting.config.ChattingConfig; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 9 | 10 | @Mixin(GuiTextField.class) 11 | public abstract class GuiTextFieldMixin { 12 | 13 | @ModifyVariable(method = "drawTextBox", at = @At(value = "STORE"), ordinal = 5) 14 | private int getRight(int right) { 15 | if (ChatHooks.INSTANCE.checkField(this)) ChatHooks.INSTANCE.setInputRight(right); 16 | return right; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/GuiUtilsMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import cc.polyfrost.oneconfig.renderer.TextRenderer; 4 | import net.minecraft.client.gui.FontRenderer; 5 | import net.minecraftforge.fml.client.config.GuiUtils; 6 | import org.polyfrost.chatting.config.ChattingConfig; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | @Mixin(value = GuiUtils.class, remap = false) 13 | public class GuiUtilsMixin { 14 | @Shadow 15 | public static void drawGradientRect(int zLevel, int left, int top, int right, int bottom, int startColor, int endColor) { 16 | } 17 | 18 | @Redirect(method = "drawHoveringText", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/client/config/GuiUtils;drawGradientRect(IIIIIII)V")) 19 | private static void redirectBackground(int zLevel, int left, int top, int right, int bottom, int startColor, int endColor) { 20 | if (!ChattingConfig.INSTANCE.getRemoveTooltipBackground()) { 21 | drawGradientRect(zLevel, left, top, right, bottom, startColor, endColor); 22 | } 23 | } 24 | 25 | @Redirect(method = "drawHoveringText", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/FontRenderer;drawStringWithShadow(Ljava/lang/String;FFI)I")) 26 | private static int redirectText(FontRenderer instance, String text, float x, float y, int color) { 27 | switch (ChattingConfig.INSTANCE.getTooltipTextRenderType()) { 28 | case 0: 29 | return instance.drawString(text, x, y, color, false); 30 | case 2: 31 | return TextRenderer.drawBorderedText(text, x, y, color, 255); 32 | default: 33 | return instance.drawStringWithShadow(text, x, y, color); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/HUDUtilsMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import cc.polyfrost.oneconfig.config.Config; 4 | import cc.polyfrost.oneconfig.config.annotations.HUD; 5 | import cc.polyfrost.oneconfig.config.core.ConfigUtils; 6 | import cc.polyfrost.oneconfig.config.elements.*; 7 | import cc.polyfrost.oneconfig.hud.*; 8 | import cc.polyfrost.oneconfig.internal.hud.HudCore; 9 | import net.minecraft.client.Minecraft; 10 | import org.polyfrost.chatting.chat.*; 11 | import org.polyfrost.chatting.config.ChattingConfig; 12 | import org.spongepowered.asm.mixin.*; 13 | import org.spongepowered.asm.mixin.injection.*; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | import java.lang.reflect.Field; 17 | 18 | @Mixin(value = HUDUtils.class, remap = false) 19 | public class HUDUtilsMixin { 20 | 21 | @Inject(method = "addHudOptions", at = @At("TAIL")) 22 | private static void hudUtils$modifyOptions(OptionPage page, Field field, Object instance, Config config, CallbackInfo ci) { 23 | Hud hud = (Hud) ConfigUtils.getField(field, instance); 24 | if (!(hud instanceof ChatWindow) && !(hud instanceof ChatInputBox)) return; 25 | HUD hudAnnotation = field.getAnnotation(HUD.class); 26 | HudCore.hudOptions.removeIf(HUDUtilsMixin::hudUtils$shouldRemove); 27 | ConfigUtils.getSubCategory(page, hudAnnotation.category(), hudAnnotation.subcategory()).options.removeIf(HUDUtilsMixin::hudUtils$shouldRemove); 28 | } 29 | 30 | private static boolean hudUtils$shouldRemove(BasicOption option) { 31 | String fieldName = option.getField().getName(); 32 | Object hud = option.getParent(); 33 | boolean isChatWindow = hud instanceof ChatWindow; 34 | boolean isInputBox = hud instanceof ChatInputBox; 35 | if (!isChatWindow && !isInputBox) return false; 36 | switch (fieldName) { 37 | case "showInChat": 38 | return true; 39 | case "enabled": 40 | if (isInputBox) try { 41 | option.getField().set(hud, true); 42 | } catch (Exception ignored) { 43 | } 44 | case "paddingX": 45 | case "paddingY": 46 | case "showInGuis": 47 | case "showInDebug": 48 | case "positionAlignment": 49 | case "scale": 50 | case "locked": 51 | case "ignoreCaching": 52 | case "resetPosition": 53 | if (isInputBox) return true; 54 | break; 55 | case "inputFieldDraft": 56 | option.addListener(ChatHooks.INSTANCE::resetDraft); 57 | break; 58 | case "focusedHeight": 59 | case "unfocusedHeight": 60 | option.addDependency("Custom Chat Height", () -> ChattingConfig.INSTANCE.getChatWindow().getCustomChatHeight()); 61 | option.addListener(() -> Minecraft.getMinecraft().ingameGUI.getChatGUI().refreshChat()); 62 | break; 63 | case "customWidth": 64 | option.addDependency("Custom Chat Width", () -> ChattingConfig.INSTANCE.getChatWindow().getCustomChatWidth()); 65 | option.addListener(() -> Minecraft.getMinecraft().ingameGUI.getChatGUI().refreshChat()); 66 | break; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/InventoryPlayerMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin; 2 | 3 | import net.minecraft.entity.player.InventoryPlayer; 4 | import org.polyfrost.chatting.Chatting; 5 | import org.polyfrost.chatting.config.ChattingConfig; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(InventoryPlayer.class) 12 | public class InventoryPlayerMixin { 13 | @Inject(method = "changeCurrentItem", at = @At("HEAD"), cancellable = true) 14 | private void cancelHotbarScrolling(int direction, CallbackInfo ci) { 15 | if (Chatting.INSTANCE.getPeeking() && ChattingConfig.INSTANCE.getPeekScrolling()) { 16 | ci.cancel(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/compat/ChatPeekMixin_SkyHanni.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin.compat; 2 | 3 | import cc.polyfrost.oneconfig.utils.Notifications; 4 | import org.polyfrost.chatting.config.ChattingConfig; 5 | import org.spongepowered.asm.mixin.Dynamic; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Pseudo; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 12 | 13 | @Pseudo 14 | @Mixin(targets = {"at.hannibal2.skyhanni.features.chat.ChatPeek"}) 15 | public class ChatPeekMixin_SkyHanni { 16 | 17 | @Unique 18 | private static long chatting$lastNotify = System.currentTimeMillis(); 19 | 20 | @Dynamic("SkyHanni") 21 | @Inject(method = "peek", at = @At("RETURN"), cancellable = true) 22 | private static void cancel(CallbackInfoReturnable cir) { 23 | if (!ChattingConfig.INSTANCE.getChatPeek() && cir.getReturnValue()) { 24 | if (System.currentTimeMillis() - chatting$lastNotify >= 1000) { 25 | Notifications.INSTANCE.send("Chatting", "SkyHanni's chat peek has been replaced by Chatting. You can configure this via OneConfig, by clicking the right shift key on your keyboard, or by typing /chatting in your chat."); 26 | chatting$lastNotify = System.currentTimeMillis(); 27 | } 28 | } 29 | cir.setReturnValue(false); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/compat/ChatTabsMixin_SkytilsCopyChat.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin.compat; 2 | 3 | import cc.polyfrost.oneconfig.utils.Notifications; 4 | import net.minecraft.client.gui.GuiScreen; 5 | import org.polyfrost.chatting.config.ChattingConfig; 6 | import org.spongepowered.asm.mixin.Dynamic; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Pseudo; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | 13 | @Pseudo 14 | @Mixin(targets = "gg.skytils.skytilsmod.features.impl.handlers.ChatTabs") 15 | public class ChatTabsMixin_SkytilsCopyChat { 16 | 17 | @Unique 18 | private static long chatting$lastNotify = System.currentTimeMillis(); 19 | 20 | @Dynamic("Skytils") 21 | @Redirect(method = "onAttemptCopy", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiScreen;isCtrlKeyDown()Z")) 22 | private boolean onAttemptCopy() { 23 | boolean isCtrlKeyDown = GuiScreen.isCtrlKeyDown(); 24 | if (!ChattingConfig.INSTANCE.getRightClickCopy() && isCtrlKeyDown) { 25 | if (System.currentTimeMillis() - chatting$lastNotify >= 1000) { 26 | Notifications.INSTANCE.send("Chatting", "Skytils' Copy Chat has been replaced by Chatting. You can configure this via OneConfig, by clicking the right shift key on your keyboard, or by typing /chatting in your chat."); 27 | chatting$lastNotify = System.currentTimeMillis(); 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | @Dynamic("Skytils") 34 | @Redirect(method = "onAttemptCopy", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiScreen;isShiftKeyDown()Z")) 35 | private boolean onAttemptCopyShift() { 36 | boolean isShiftKeyDown = GuiScreen.isShiftKeyDown(); 37 | if (!ChattingConfig.INSTANCE.getRightClickCopy() && isShiftKeyDown) { 38 | if (System.currentTimeMillis() - chatting$lastNotify >= 1000) { 39 | Notifications.INSTANCE.send("Chatting", "Skytils' Copy Chat has been replaced by Chatting. You can configure this via OneConfig, by clicking the right shift key on your keyboard, or by typing /chatting in your chat."); 40 | chatting$lastNotify = System.currentTimeMillis(); 41 | } 42 | } 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/polyfrost/chatting/mixin/compat/EssentialKeybindingRegistryMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.mixin.compat; 2 | 3 | import cc.polyfrost.oneconfig.utils.Notifications; 4 | import org.polyfrost.chatting.config.ChattingConfig; 5 | import org.spongepowered.asm.mixin.Dynamic; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Pseudo; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 12 | 13 | @Pseudo 14 | @Mixin(targets = {"gg.essential.key.EssentialKeybindingRegistry"}) 15 | public class EssentialKeybindingRegistryMixin { 16 | 17 | @Unique private long chatting$lastNotify = System.currentTimeMillis(); 18 | 19 | @Dynamic("Essential") 20 | @Inject(method = "isHoldingChatPeek", at = @At("RETURN"), cancellable = true) 21 | private void overrideChatPeek(CallbackInfoReturnable cir) { 22 | if (!ChattingConfig.INSTANCE.getChatPeek() && cir.getReturnValue()) { 23 | if (System.currentTimeMillis() - chatting$lastNotify >= 1000) { 24 | Notifications.INSTANCE.send("Chatting", "Essential's chat peek has been replaced by Chatting. You can configure this via OneConfig, by clicking the right shift key on your keyboard, or by typing /chatting in your chat."); 25 | chatting$lastNotify = System.currentTimeMillis(); 26 | } 27 | } 28 | cir.setReturnValue(false); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/Chatting.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.UDesktop 4 | import cc.polyfrost.oneconfig.utils.Notifications 5 | import cc.polyfrost.oneconfig.utils.commands.CommandManager 6 | import cc.polyfrost.oneconfig.utils.dsl.browseLink 7 | import cc.polyfrost.oneconfig.utils.dsl.mc 8 | import net.minecraft.client.gui.* 9 | import net.minecraft.client.renderer.GlStateManager 10 | import net.minecraft.client.renderer.OpenGlHelper 11 | import net.minecraft.client.settings.KeyBinding 12 | import net.minecraftforge.common.MinecraftForge.EVENT_BUS 13 | import net.minecraftforge.fml.client.registry.ClientRegistry 14 | import net.minecraftforge.fml.common.Loader 15 | import net.minecraftforge.fml.common.Mod 16 | import net.minecraftforge.fml.common.event.FMLInitializationEvent 17 | import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent 18 | import net.minecraftforge.fml.common.event.FMLPostInitializationEvent 19 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent 20 | import net.minecraftforge.fml.common.gameevent.TickEvent 21 | import org.lwjgl.input.Keyboard 22 | import org.lwjgl.input.Mouse 23 | import org.polyfrost.chatting.chat.ChatScrollingHook.shouldSmooth 24 | import org.polyfrost.chatting.chat.ChatSearchingManager 25 | import org.polyfrost.chatting.chat.ChatShortcuts 26 | import org.polyfrost.chatting.chat.ChatSpamBlock 27 | import org.polyfrost.chatting.chat.ChatTabs 28 | import org.polyfrost.chatting.command.ChattingCommand 29 | import org.polyfrost.chatting.config.ChattingConfig 30 | import org.polyfrost.chatting.hook.ChatLineHook 31 | import org.polyfrost.chatting.mixin.GuiNewChatAccessor 32 | import org.polyfrost.chatting.utils.ModCompatHooks 33 | import org.polyfrost.chatting.utils.copyToClipboard 34 | import org.polyfrost.chatting.utils.createBindFramebuffer 35 | import org.polyfrost.chatting.utils.screenshot 36 | import java.awt.image.BufferedImage 37 | import java.io.File 38 | import java.text.SimpleDateFormat 39 | import java.util.* 40 | 41 | 42 | @Mod( 43 | modid = Chatting.ID, 44 | name = Chatting.NAME, 45 | version = Chatting.VER, 46 | modLanguageAdapter = "cc.polyfrost.oneconfig.utils.KotlinLanguageAdapter" 47 | ) 48 | object Chatting { 49 | 50 | val keybind = KeyBinding("Screenshot Chat", Keyboard.KEY_NONE, "Chatting") 51 | const val NAME = "@NAME@" 52 | const val VER = "@VER@" 53 | const val ID = "@ID@" 54 | var doTheThing = false 55 | var isPatcher = false 56 | private set 57 | var isBetterChat = false 58 | private set 59 | var isSkytils = false 60 | private set 61 | var isHychat = false 62 | private set 63 | 64 | private var lastPressed = false; 65 | var peeking = false 66 | get() = ChattingConfig.chatPeek && field 67 | 68 | private val fileFormatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd_HH.mm.ss'.png'") 69 | 70 | val oldModDir = File(File(mc.mcDataDir, "W-OVERFLOW"), NAME) 71 | 72 | @Mod.EventHandler 73 | fun onInitialization(event: FMLInitializationEvent) { 74 | ChattingConfig 75 | if (!ChattingConfig.enabled) { 76 | ChattingConfig.enabled = true 77 | ChattingConfig.save() 78 | } 79 | if (!ChattingConfig.chatWindow.transferOverScale) { 80 | ChattingConfig.chatWindow.normalScale = ChattingConfig.chatWindow.scale 81 | ChattingConfig.chatWindow.transferOverScale = true 82 | ChattingConfig.save() 83 | } 84 | ChattingConfig.chatWindow.updateMCChatScale() 85 | CommandManager.INSTANCE.registerCommand(ChattingCommand()) 86 | ClientRegistry.registerKeyBinding(keybind) 87 | EVENT_BUS.register(this) 88 | EVENT_BUS.register(ChatSpamBlock) 89 | ChatTabs.initialize() 90 | ChatShortcuts.initialize() 91 | } 92 | 93 | @Mod.EventHandler 94 | fun onPostInitialization(event: FMLPostInitializationEvent) { 95 | isPatcher = Loader.isModLoaded("patcher") 96 | isBetterChat = Loader.isModLoaded("betterchat") 97 | isSkytils = Loader.isModLoaded("skytils") 98 | isHychat = Loader.isModLoaded("hychat") 99 | } 100 | 101 | @Mod.EventHandler 102 | fun onForgeLoad(event: FMLLoadCompleteEvent) { 103 | if (ChattingConfig.informForAlternatives) { 104 | if (isHychat) { 105 | Notifications.INSTANCE.send( 106 | NAME, 107 | "Hychat can be removed as it is replaced by Chatting. Click here for more information.", 108 | Runnable { 109 | UDesktop.browseLink("https://microcontrollersdev.github.io/Alternatives/1.8.9/hychat") 110 | }) 111 | } 112 | if (isSkytils) { 113 | try { 114 | skytilsCompat(Class.forName("gg.skytils.skytilsmod.core.Config")) 115 | } catch (e: Exception) { 116 | e.printStackTrace() 117 | try { 118 | skytilsCompat(Class.forName("skytils.skytilsmod.core.Config")) 119 | } catch (e: Exception) { 120 | e.printStackTrace() 121 | } 122 | } 123 | } 124 | if (isBetterChat) { 125 | Notifications.INSTANCE.send( 126 | NAME, 127 | "BetterChat can be removed as it is replaced by Chatting. Click here to open your mods folder to delete the BetterChat file.", 128 | Runnable { 129 | UDesktop.open(File("./mods")) 130 | }) 131 | } 132 | } 133 | } 134 | 135 | private fun skytilsCompat(skytilsClass: Class<*>) { 136 | val instance = skytilsClass.getDeclaredField("INSTANCE").get(null) 137 | val chatTabs = skytilsClass.getDeclaredField("chatTabs") 138 | chatTabs.isAccessible = true 139 | if (chatTabs.getBoolean(instance)) { 140 | Notifications.INSTANCE.send( 141 | NAME, 142 | "Skytils' chat tabs can be disabled as it is replace by Chatting.\nClick here to automatically do this.", 143 | Runnable { 144 | chatTabs.setBoolean(instance, false) 145 | ChattingConfig.chatTabs = true 146 | ChattingConfig.hypixelOnlyChatTabs = true 147 | ChattingConfig.save() 148 | skytilsClass.getMethod("markDirty").invoke(instance) 149 | skytilsClass.getMethod("writeData").invoke(instance) 150 | }) 151 | } 152 | val copyChat = skytilsClass.getDeclaredField("copyChat") 153 | copyChat.isAccessible = true 154 | if (copyChat.getBoolean(instance)) { 155 | Notifications.INSTANCE.send( 156 | NAME, 157 | "Skytils' copy chat messages can be disabled as it is replace by Chatting.\nClick here to automatically do this.", 158 | Runnable { 159 | copyChat.setBoolean(instance, false) 160 | skytilsClass.getMethod("markDirty").invoke(instance) 161 | skytilsClass.getMethod("writeData").invoke(instance) 162 | }) 163 | } 164 | } 165 | 166 | @SubscribeEvent 167 | fun onTickEvent(event: TickEvent.ClientTickEvent) { 168 | if (event.phase == TickEvent.Phase.START && mc.theWorld != null && mc.thePlayer != null) { 169 | if ((mc.currentScreen == null || mc.currentScreen is GuiChat)) { 170 | if (doTheThing) { 171 | screenshotChat() 172 | doTheThing = false 173 | } 174 | } 175 | 176 | var canScroll = true 177 | 178 | val key = ChattingConfig.chatPeekBind 179 | if (key.isActive != lastPressed && ChattingConfig.chatPeek) { 180 | lastPressed = key.isActive 181 | if (key.isActive) { 182 | peeking = !peeking 183 | } else if (!ChattingConfig.peekMode) { 184 | peeking = false 185 | } 186 | canScroll = false 187 | if (!peeking) mc.ingameGUI.chatGUI.resetScroll() 188 | } 189 | 190 | if (mc.currentScreen is GuiChat) peeking = false 191 | 192 | if (peeking && ChattingConfig.peekScrolling) { 193 | var i = Mouse.getDWheel().coerceIn(-1..1) 194 | 195 | if (i != 0) { 196 | 197 | if (!GuiScreen.isShiftKeyDown()) { 198 | i *= 7 199 | } 200 | 201 | shouldSmooth = true 202 | if (canScroll) mc.ingameGUI.chatGUI.scroll(i) 203 | } 204 | } 205 | } 206 | } 207 | 208 | fun getChatHeight(opened: Boolean): Int { 209 | return if (opened) ChattingConfig.chatWindow.focusedHeight else ChattingConfig.chatWindow.unfocusedHeight 210 | } 211 | 212 | fun getChatWidth(): Int { 213 | return ChattingConfig.chatWindow.customWidth 214 | } 215 | 216 | fun screenshotLine(line: ChatLine): BufferedImage? { 217 | return screenshot( 218 | linkedMapOf().also { map -> 219 | val fullMessage = (line as ChatLineHook).`chatting$getFullMessage`() 220 | for (chatLine in (mc.ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines) { 221 | if ((chatLine as ChatLineHook).`chatting$getFullMessage`() == fullMessage) { 222 | map[chatLine] = chatLine.chatComponent.formattedText 223 | } 224 | } 225 | } 226 | ) 227 | } 228 | 229 | private fun screenshotChat() { 230 | screenshotChat(0) 231 | } 232 | 233 | fun screenshotChat(scrollPos: Int) { 234 | val hud = mc.ingameGUI 235 | val chat = hud.chatGUI 236 | val chatLines = LinkedHashMap() 237 | ChatSearchingManager.filterMessages( 238 | ChatSearchingManager.lastSearch, 239 | (chat as GuiNewChatAccessor).drawnChatLines 240 | )?.let { drawnLines -> 241 | val chatHeight = 242 | if (ChattingConfig.chatWindow.customChatHeight) getChatHeight(true) / 9 else GuiNewChat.calculateChatboxHeight( 243 | mc.gameSettings.chatHeightFocused / 9 244 | ) 245 | for (i in scrollPos until drawnLines.size.coerceAtMost(scrollPos + chatHeight)) { 246 | chatLines[drawnLines[i]] = drawnLines[i].chatComponent.formattedText 247 | } 248 | 249 | screenshot(chatLines)?.copyToClipboard() 250 | } 251 | } 252 | 253 | private fun screenshot(messages: HashMap): BufferedImage? { 254 | if (messages.isEmpty()) { 255 | Notifications.INSTANCE.send("Chatting", "Chat window is empty.") 256 | return null 257 | } 258 | if (!OpenGlHelper.isFramebufferEnabled()) { 259 | Notifications.INSTANCE.send( 260 | "Chatting", 261 | "Screenshot failed, please disable “Fast Render” in OptiFine’s “Performance” tab." 262 | ) 263 | return null 264 | } 265 | 266 | val fr = ModCompatHooks.fontRenderer 267 | val border = ChattingConfig.textRenderType == 2 268 | val offset = if (border) 1 else 0 269 | val width = messages.maxOf { fr.getStringWidth(it.value) + (if (ChattingConfig.showChatHeads && ((it.key as ChatLineHook).`chatting$hasDetected`() || ChattingConfig.offsetNonPlayerMessages)) 10 else 0) } + if (border) 2 else 1 270 | val fb = createBindFramebuffer(width * 2, (messages.size * 9 + offset) * 2) 271 | val file = File(mc.mcDataDir, "screenshots/chat/" + fileFormatter.format(Date())) 272 | 273 | GlStateManager.scale(2f, 2f, 1f) 274 | messages.entries.forEachIndexed { i: Int, entry: MutableMap.MutableEntry -> 275 | ModCompatHooks.redirectDrawString(entry.value, offset.toFloat(), (messages.size - 1 - i) * 9f + offset.toFloat(), 0xFFFFFFFF.toInt(), entry.key) 276 | } 277 | 278 | val image = fb.screenshot(file) 279 | mc.entityRenderer.setupOverlayRendering() 280 | mc.framebuffer.bindFramebuffer(true) 281 | Notifications.INSTANCE.send( 282 | "Chatting", 283 | "Chat screenshotted successfully." + (if (ChattingConfig.copyMode != 1) "\nClick to open." else ""), 284 | Runnable { 285 | if (!UDesktop.open(file)) { 286 | Notifications.INSTANCE.send("Chatting", "Could not browse!") 287 | } 288 | }) 289 | return image 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatHooks.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import net.minecraft.client.gui.GuiTextField 4 | 5 | object ChatHooks { 6 | var draft = "" 7 | 8 | var commandDraft = "" 9 | 10 | var inputBoxRight = 0 11 | 12 | var inputRight = 0 13 | 14 | var textField: GuiTextField? = null 15 | 16 | fun resetDraft() { 17 | draft = "" 18 | commandDraft = "" 19 | } 20 | 21 | fun checkField(field: Any): Boolean { 22 | return field == textField 23 | } 24 | 25 | fun switchTab() { 26 | val current = ChatTabs.currentTabs.firstOrNull() 27 | if (current == null) { 28 | ChatTabs.currentTabs.add(ChatTabs.tabs[0]) 29 | } else { 30 | val nextIndex = (ChatTabs.tabs.indexOf(current) + 1) % ChatTabs.tabs.size 31 | ChatTabs.currentTabs.clear() 32 | ChatTabs.currentTabs.add(ChatTabs.tabs[nextIndex]) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatInputBox.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import cc.polyfrost.oneconfig.config.annotations.Switch 4 | import cc.polyfrost.oneconfig.hud.BasicHud 5 | import cc.polyfrost.oneconfig.libs.universal.UMatrixStack 6 | import cc.polyfrost.oneconfig.libs.universal.UResolution 7 | import net.minecraft.client.renderer.GlStateManager 8 | import org.polyfrost.chatting.chat.ChatHooks.inputBoxRight 9 | import org.polyfrost.chatting.utils.ModCompatHooks 10 | 11 | class ChatInputBox : BasicHud(true, -100f, -100f) { 12 | 13 | init { 14 | scale = 1f 15 | paddingX = 0f 16 | paddingY = 0f 17 | } 18 | 19 | @Switch( 20 | name = "Compact Input Box", 21 | description = "Make the chat input box the same width as the chat box." 22 | ) 23 | var compactInputBox = false 24 | 25 | @Switch( 26 | name = "Input Field Draft", 27 | description = "Drafts the text you wrote in the input field after closing the chat and backs it up when opening the chat again." 28 | ) 29 | var inputFieldDraft = false 30 | 31 | fun drawBG() { 32 | if (!ModCompatHooks.shouldDrawInputBox) return 33 | GlStateManager.enableAlpha() 34 | GlStateManager.enableBlend() 35 | val scale = UResolution.scaleFactor.toFloat() 36 | drawBackground(2f, UResolution.scaledHeight - 14f + (if (UResolution.windowHeight % 2 == 1) scale - 1 else 0f) / scale, inputBoxRight - 2f, 12f, 1f) 37 | GlStateManager.disableBlend() 38 | GlStateManager.disableAlpha() 39 | } 40 | 41 | override fun isEnabled(): Boolean { 42 | return true 43 | } 44 | 45 | override fun draw(matrices: UMatrixStack?, x: Float, y: Float, scale: Float, example: Boolean) { 46 | } 47 | 48 | override fun shouldShow(): Boolean { 49 | return false 50 | } 51 | 52 | override fun getWidth(scale: Float, example: Boolean): Float { 53 | return 0f 54 | } 55 | 56 | override fun getHeight(scale: Float, example: Boolean): Float { 57 | return 0f 58 | } 59 | 60 | fun setBackground(boolean: Boolean) { 61 | background = boolean 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | data class ChatRegexes(val regexList: List?) { 4 | val compiledRegexList: MutableList = arrayListOf() 5 | 6 | init { 7 | regexList?.forEach { 8 | compiledRegexList.add(Regex(it)) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | object ChatScrollingHook { 4 | var shouldSmooth = false 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import cc.polyfrost.oneconfig.libs.caffeine.cache.Cache 4 | import cc.polyfrost.oneconfig.libs.caffeine.cache.Caffeine 5 | import cc.polyfrost.oneconfig.libs.universal.wrappers.message.UTextComponent 6 | import net.minecraft.client.gui.ChatLine 7 | import net.minecraft.util.ChatComponentText 8 | import org.polyfrost.chatting.chat.ChatTabs.currentTabs 9 | import org.polyfrost.chatting.hook.ChatHook 10 | import java.util.concurrent.LinkedBlockingQueue 11 | import java.util.concurrent.ThreadPoolExecutor 12 | import java.util.concurrent.TimeUnit 13 | import java.util.concurrent.atomic.AtomicInteger 14 | 15 | object ChatSearchingManager { 16 | private var counter: AtomicInteger = AtomicInteger(0) 17 | private var POOL: ThreadPoolExecutor = ThreadPoolExecutor( 18 | 50, 50, 19 | 0L, TimeUnit.SECONDS, 20 | LinkedBlockingQueue() 21 | ) { r -> 22 | Thread( 23 | r, 24 | "Chat Filter Cache Thread ${counter.incrementAndGet()}" 25 | ) 26 | } 27 | 28 | @JvmStatic 29 | val cache: Cache> = Caffeine.newBuilder().executor(POOL).maximumSize(5000).build() 30 | 31 | var lastSearch = "" 32 | 33 | @JvmStatic 34 | fun filterMessages(text: String, list: List): List? { 35 | val chatTabMessages = filterChatTabMessages(lastSearch) 36 | if (chatTabMessages != null) { 37 | return chatTabMessages 38 | } 39 | return filterMessages2(text, list) 40 | } 41 | 42 | @JvmStatic 43 | fun filterMessages2(text: String, list: List): List? { 44 | if (text.isBlank()) return list 45 | val cached = cache.getIfPresent(text) 46 | return cached ?: run { 47 | cache.put(text, list.filter { 48 | UTextComponent.stripFormatting(it.chatComponent.unformattedText).lowercase() 49 | .contains(text.lowercase()) 50 | }) 51 | cache.getIfPresent(text) 52 | } 53 | } 54 | 55 | @JvmStatic 56 | fun filterChatTabMessages(text: String): List? { 57 | val currentTabs = currentTabs.firstOrNull() 58 | if (currentTabs?.messages?.isEmpty() == false) { 59 | val list: MutableList = ArrayList() 60 | for (message in currentTabs.messages?: emptyList()) { 61 | ChatHook.lineVisible = true 62 | list.add(ChatLine(0, ChatComponentText(message), 0)) 63 | ChatHook.lineVisible = false 64 | } 65 | return filterMessages2(text, list) 66 | } 67 | return null 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import cc.polyfrost.oneconfig.config.core.ConfigUtils 4 | import org.polyfrost.chatting.Chatting 5 | import com.google.gson.JsonObject 6 | import com.google.gson.JsonParser 7 | import java.io.File 8 | 9 | object ChatShortcuts { 10 | private val oldShortcutsFile = File(Chatting.oldModDir, "chatshortcuts.json") 11 | private val shortcutsFile = ConfigUtils.getProfileFile("chatshortcuts.json") 12 | private val PARSER = JsonParser() 13 | 14 | private var initialized = false 15 | 16 | val shortcuts = object : ArrayList>() { 17 | private val comparator = Comparator> { o1, o2 -> 18 | return@Comparator o2.first.length.compareTo(o1.first.length) 19 | } 20 | 21 | override fun add(element: Pair): Boolean { 22 | val value = super.add(element) 23 | sortWith(comparator) 24 | return value 25 | } 26 | } 27 | 28 | fun initialize() { 29 | if (initialized) { 30 | return 31 | } else { 32 | initialized = true 33 | } 34 | if (shortcutsFile.exists()) { 35 | try { 36 | val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject 37 | for (shortcut in jsonObj.entrySet()) { 38 | shortcuts.add(shortcut.key to shortcut.value.asString) 39 | } 40 | return 41 | } catch (_: Throwable) { 42 | shortcutsFile.renameTo(File(shortcutsFile.parentFile, "chatshortcuts.json.bak")) 43 | } 44 | } 45 | shortcutsFile.createNewFile() 46 | if (oldShortcutsFile.exists()) { 47 | shortcutsFile.writeText( 48 | oldShortcutsFile.readText() 49 | ) 50 | } else { 51 | shortcutsFile.writeText(JsonObject().toString()) 52 | } 53 | } 54 | 55 | fun removeShortcut(key: String) { 56 | shortcuts.removeIf { it.first == key } 57 | val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject 58 | jsonObj.remove(key) 59 | shortcutsFile.writeText(jsonObj.toString()) 60 | } 61 | 62 | fun writeShortcut(key: String, value: String) { 63 | shortcuts.add(key to value) 64 | val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject 65 | jsonObj.addProperty(key, value) 66 | shortcutsFile.writeText(jsonObj.toString()) 67 | } 68 | 69 | fun handleSentCommand(command: String): String { 70 | shortcuts.forEach { 71 | if (command == it.first || (command.startsWith(it.first) && command.substringAfter(it.first) 72 | .startsWith(" ")) 73 | ) { 74 | return command.replaceFirst(it.first, it.second) 75 | } 76 | } 77 | return command 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import org.polyfrost.chatting.config.ChattingConfig 4 | import com.google.gson.JsonObject 5 | import com.google.gson.JsonParser 6 | import java.text.Normalizer 7 | import net.minecraft.util.ChatComponentText 8 | import net.minecraft.util.EnumChatFormatting 9 | import net.minecraftforge.client.event.ClientChatReceivedEvent 10 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent 11 | 12 | object ChatSpamBlock { 13 | /* 14 | Made by @KTibow 15 | Based off of Unspam (also by @KTibow) 16 | Algorithm based off of https://paulgraham.com/spam.html 17 | */ 18 | private val PLAYER_MESSAGE = Regex("^(\\[VIP\\+?\\] |\\[MVP\\+?\\+?\\] |)(\\w{2,16}): (.*)$") 19 | 20 | @SubscribeEvent 21 | fun onChat(event: ClientChatReceivedEvent) { 22 | val message = event.message.unformattedText.replace(Regex("\u00A7."), "") 23 | if (!PLAYER_MESSAGE.matches(message)) return 24 | 25 | val (rank, player, content) = PLAYER_MESSAGE.matchEntire(message)!!.destructured 26 | 27 | if (ChattingConfig.spamThreshold != 100) { 28 | val tokens = tokenize(content) 29 | val spamProb = findSpamProbability(tokens) 30 | if (spamProb * 100 > ChattingConfig.spamThreshold) { 31 | if (ChattingConfig.hideSpam) { 32 | event.isCanceled = true 33 | } else { 34 | var newMessage = 35 | EnumChatFormatting.DARK_GRAY.toString() + 36 | EnumChatFormatting.STRIKETHROUGH.toString() 37 | if (!ChattingConfig.customChatFormatting) { 38 | newMessage += rank 39 | } 40 | newMessage += "$player${EnumChatFormatting.DARK_GRAY}: $content" 41 | event.message = ChatComponentText(newMessage) 42 | } 43 | return 44 | } 45 | } 46 | if (ChattingConfig.customChatFormatting) { 47 | val coloredPlayer = findRankColor(rank) + player + EnumChatFormatting.RESET.toString() 48 | event.message = ChatComponentText("$coloredPlayer: $content") 49 | } 50 | } 51 | 52 | private fun tokenize(message: String): MutableList { 53 | val strippedMessage = 54 | Normalizer.normalize(message, Normalizer.Form.NFKC) 55 | .replace(Regex("[^\\w\\s/]"), " ") 56 | .lowercase() 57 | .trim() 58 | val tokens = strippedMessage.split(Regex("\\s+")).toMutableList() 59 | if (tokens.size <= 2) { 60 | tokens.add("TINY_LENGTH") 61 | } else if (tokens.size <= 4) { 62 | tokens.add("SMALL_LENGTH") 63 | } else if (tokens.size <= 7) { 64 | tokens.add("MEDIUM_LENGTH") 65 | } else { 66 | tokens.add("LONG_LENGTH") 67 | } 68 | if (message.replace(Regex("[\\w\\s]"), "").length > 2) { 69 | tokens.add("SPECIAL_CHARS") 70 | } else if (message.replace(Regex("[\\w\\s]"), "").isNotEmpty()) { 71 | tokens.add("SPECIAL_CHAR") 72 | } else { 73 | tokens.add("LOW_SPECIAL_CHARS") 74 | } 75 | if (message.replace(Regex("[^A-Z]"), "").length >= message.length / 4) { 76 | tokens.add("HIGH_CAPS") 77 | } else { 78 | tokens.add("LOW_CAPS") 79 | } 80 | return tokens 81 | } 82 | 83 | private fun findSpamProbability(tokens: MutableList): Double { 84 | val tokenProbs = mutableMapOf() 85 | for (token in tokens) { 86 | if (!spamInfoJson.has(token)) continue 87 | val spamInToken = spamInfoJson.get(token).asJsonObject.get("spam").asDouble 88 | val fineInToken = spamInfoJson.get(token).asJsonObject.get("fine").asDouble 89 | tokenProbs[token] = 90 | ((spamInToken / messageCountsJson.get("spam").asInt) / 91 | (fineInToken / messageCountsJson.get("fine").asInt + 92 | spamInToken / messageCountsJson.get("spam").asInt)) 93 | } 94 | val spamProbs = tokenProbs.values.toMutableList() 95 | val fineProbs = tokenProbs.values.map { 1 - it }.toMutableList() 96 | val spamProbability = spamProbs.reduce { a, b -> a * b } 97 | val fineProbability = fineProbs.reduce { a, b -> a * b } 98 | return spamProbability / (spamProbability + fineProbability) 99 | } 100 | 101 | private fun findRankColor(rank: String): String { 102 | println(rank) 103 | return when (rank) { 104 | "[VIP] ", 105 | "[VIP+] " -> EnumChatFormatting.GREEN.toString() 106 | "[MVP] ", 107 | "[MVP+] " -> EnumChatFormatting.AQUA.toString() 108 | "[MVP++] " -> EnumChatFormatting.GOLD.toString() 109 | else -> EnumChatFormatting.GRAY.toString() 110 | } 111 | } 112 | 113 | private fun getResourceAsText(path: String): String? = 114 | object {}.javaClass.getResource(path)?.readText() 115 | private val spamInfoJson: JsonObject 116 | private val messageCountsJson: JsonObject 117 | 118 | init { 119 | // Load the file spamInfo.json from resources/ 120 | val spamInfo = getResourceAsText("/spamInfo.json") 121 | spamInfoJson = JsonParser().parse(spamInfo).asJsonObject 122 | messageCountsJson = JsonParser().parse(" { \"fine\": 668, \"spam\": 230 }").asJsonObject 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.ChatColor 4 | import cc.polyfrost.oneconfig.utils.dsl.mc 5 | import org.polyfrost.chatting.gui.components.TabButton 6 | import com.google.gson.annotations.SerializedName 7 | import net.minecraft.client.Minecraft 8 | import net.minecraft.client.gui.ChatLine 9 | import net.minecraft.util.ChatComponentText 10 | import net.minecraft.util.EnumChatFormatting 11 | import net.minecraft.util.IChatComponent 12 | import org.polyfrost.chatting.mixin.GuiNewChatAccessor 13 | import java.util.* 14 | 15 | data class ChatTab( 16 | val enabled: Boolean, 17 | val name: String, 18 | val unformatted: Boolean, 19 | val lowercase: Boolean?, 20 | @SerializedName("starts") val startsWith: List?, 21 | val contains: List?, 22 | @SerializedName("ends") val endsWith: List?, 23 | val equals: List?, 24 | @SerializedName("regex") val uncompiledRegex: List?, 25 | @SerializedName("ignore_starts") val ignoreStartsWith: List?, 26 | @SerializedName("ignore_contains") val ignoreContains: List?, 27 | @SerializedName("ignore_ends") val ignoreEndsWith: List?, 28 | @SerializedName("ignore_equals") val ignoreEquals: List?, 29 | @SerializedName("ignore_regex") val uncompiledIgnoreRegex: List?, 30 | val color: Int?, 31 | @SerializedName("hovered_color") val hoveredColor: Int?, 32 | @SerializedName("selected_color") val selectedColor: Int?, 33 | val prefix: String? 34 | ) { 35 | lateinit var button: TabButton 36 | lateinit var compiledRegex: ChatRegexes 37 | lateinit var compiledIgnoreRegex: ChatRegexes 38 | @Transient var messages: List? = ArrayList() 39 | 40 | //Ugly hack to make GSON not make button / regex null 41 | fun initialize() { 42 | compiledRegex = ChatRegexes(uncompiledRegex) 43 | compiledIgnoreRegex = ChatRegexes(uncompiledIgnoreRegex) 44 | val width = mc.fontRendererObj.getStringWidth(name) 45 | button = TabButton(653452, run { 46 | val returnValue = x - 2 47 | x += 6 + width 48 | return@run returnValue 49 | }, width + 4, 12, this) 50 | } 51 | 52 | fun shouldRender(chatComponent: IChatComponent): Boolean { 53 | val message = 54 | (if (unformatted) EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.unformattedText) else chatComponent.formattedText).let { 55 | if (lowercase == true) it.lowercase( 56 | Locale.ENGLISH 57 | ) else it 58 | } 59 | ignoreStartsWith?.forEach { 60 | if (message.startsWith(it)) { 61 | return false 62 | } 63 | } 64 | ignoreEquals?.forEach { 65 | if (message == it) { 66 | return false 67 | } 68 | } 69 | ignoreEndsWith?.forEach { 70 | if (message.endsWith(it)) { 71 | return false 72 | } 73 | } 74 | ignoreContains?.forEach { 75 | if (message.contains(it)) { 76 | return false 77 | } 78 | } 79 | compiledIgnoreRegex.compiledRegexList.forEach { 80 | if (it.matches(message)) { 81 | return false 82 | } 83 | } 84 | if (startsWith.isNullOrEmpty() && equals.isNullOrEmpty() && endsWith.isNullOrEmpty() && contains.isNullOrEmpty() && uncompiledRegex.isNullOrEmpty()) { 85 | return true 86 | } 87 | equals?.forEach { 88 | if (message == it) { 89 | return true 90 | } 91 | } 92 | startsWith?.forEach { 93 | if (message.startsWith(it)) { 94 | return true 95 | } 96 | } 97 | endsWith?.forEach { 98 | if (message.endsWith(it)) { 99 | return true 100 | } 101 | } 102 | contains?.forEach { 103 | if (message.contains(it)) { 104 | return true 105 | } 106 | } 107 | compiledRegex.compiledRegexList.forEach { 108 | if (it.matches(message)) { 109 | return true 110 | } 111 | } 112 | return false 113 | } 114 | 115 | companion object { 116 | private var x = 4 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import com.google.gson.JsonArray 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class ChatTabsJson(@SerializedName("tabs") val tabs: JsonArray, var version: Int) { 7 | 8 | override fun toString(): String { 9 | return "{\"tabs\": $tabs, \"version\": $version}" 10 | } 11 | 12 | companion object { 13 | const val VERSION = 6 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/chat/ChatWindow.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.chat 2 | 3 | import cc.polyfrost.oneconfig.config.annotations.Button 4 | import cc.polyfrost.oneconfig.config.annotations.Exclude 5 | import cc.polyfrost.oneconfig.config.annotations.Slider 6 | import cc.polyfrost.oneconfig.config.annotations.Switch 7 | import cc.polyfrost.oneconfig.config.core.OneColor 8 | import cc.polyfrost.oneconfig.gui.animations.Animation 9 | import cc.polyfrost.oneconfig.gui.animations.DummyAnimation 10 | import cc.polyfrost.oneconfig.hud.BasicHud 11 | import cc.polyfrost.oneconfig.internal.hud.HudCore 12 | import cc.polyfrost.oneconfig.libs.universal.UGraphics.GL 13 | import cc.polyfrost.oneconfig.libs.universal.UMatrixStack 14 | import cc.polyfrost.oneconfig.libs.universal.UResolution 15 | import cc.polyfrost.oneconfig.platform.Platform 16 | import cc.polyfrost.oneconfig.renderer.NanoVGHelper 17 | import cc.polyfrost.oneconfig.utils.dsl.mc 18 | import cc.polyfrost.oneconfig.utils.dsl.nanoVG 19 | import cc.polyfrost.oneconfig.utils.dsl.setAlpha 20 | import club.sk1er.patcher.config.PatcherConfig 21 | import net.minecraft.client.gui.ChatLine 22 | import net.minecraft.client.gui.GuiChat 23 | import net.minecraft.client.gui.GuiNewChat 24 | import net.minecraft.client.renderer.GlStateManager 25 | import net.minecraft.util.ChatComponentText 26 | import org.polyfrost.chatting.Chatting 27 | import org.polyfrost.chatting.config.ChattingConfig 28 | import org.polyfrost.chatting.utils.EaseOutQuart 29 | import org.polyfrost.chatting.utils.ModCompatHooks 30 | 31 | class ChatWindow : BasicHud(true, 2f, 1080 - 27f - 45f - 12f, 32 | 1f, true, true, 6f, 5f, 5f, OneColor(0, 0, 0, 120), false, 2f, OneColor(0, 0, 0)) { 33 | 34 | @Exclude 35 | private val exampleList: List = listOf( 36 | ChatLine(0, ChatComponentText("§bChatting"), 0), 37 | ChatLine(0, ChatComponentText(""), 0), 38 | ChatLine(0, ChatComponentText("§aThis is a movable chat"), 0), 39 | ChatLine(0, ChatComponentText("§eDrag me around!"), 0), 40 | ChatLine(0, ChatComponentText("Click to drag"), 0) 41 | ) 42 | 43 | @Exclude 44 | var widthAnimation: Animation = DummyAnimation(0f) 45 | 46 | @Exclude 47 | var heightAnimation: Animation = DummyAnimation(0f) 48 | 49 | @Exclude 50 | var width = 0f 51 | 52 | @Exclude 53 | var height = 0 54 | 55 | @Exclude 56 | var animationWidth = 0f 57 | 58 | @Exclude 59 | var animationHeight = 0f 60 | 61 | @Exclude 62 | var previousAnimationWidth = 0f 63 | 64 | @Exclude 65 | var previousAnimationHeight = 0f 66 | 67 | @Exclude 68 | var isGuiIngame = false 69 | 70 | @Exclude 71 | var wasInChatGui = false 72 | 73 | var normalScale = 1f 74 | var lastChatGuiScale = -1f 75 | var transferOverScale = false 76 | 77 | @Switch( 78 | name = "Custom Chat Height", 79 | description = "Set a custom height for the chat window. Allows for more customization than the vanilla chat height options." 80 | ) 81 | var customChatHeight = false 82 | 83 | @Slider( 84 | min = 20F, max = 2160F, name = "Focused Height (px)", 85 | description = "The height of the chat window when focused." 86 | ) 87 | var focusedHeight = 180 88 | get() = field.coerceIn(20, 2160) 89 | 90 | @Slider( 91 | min = 20F, max = 2160F, name = "Unfocused Height (px)", 92 | description = "The height of the chat window when unfocused." 93 | ) 94 | var unfocusedHeight = 90 95 | get() = field.coerceIn(20, 2160) 96 | 97 | @Switch( 98 | name = "Custom Chat Width", 99 | description = "Set a custom width for the chat window. Allows for more customization than the vanilla chat width options." 100 | ) 101 | var customChatWidth = false 102 | 103 | @Slider( 104 | min = 20F, max = 2160F, name = "Custom Width (px)", 105 | description = "The width of the chat window when focused." 106 | ) 107 | var customWidth = 320 108 | get() = field.coerceIn(20, 2160) 109 | 110 | @Switch( 111 | name = "Different Opacity When Open", 112 | description = "Change the opacity of the chat window when it is open." 113 | ) 114 | var differentOpacity = false 115 | 116 | @Slider( 117 | min = 0F, max = 255F, name = "Open Background Opacity", 118 | description = "The opacity of the chat window when it is open." 119 | ) 120 | var openOpacity = 120 121 | get() = field.coerceIn(0, 255) 122 | 123 | @Slider( 124 | min = 0F, max = 255F, name = "Open Border Opacity", 125 | description = "The opacity of the chat window border when it is open." 126 | ) 127 | var openBorderOpacity = 255 128 | get() = field.coerceIn(0, 255) 129 | 130 | @Button( 131 | name = "Revert to Vanilla Chat Window", 132 | description = "Revert the chat window to the vanilla chat window, instead of the Chattings custom chat window.", 133 | text = "Revert" 134 | ) 135 | var revertToVanilla = Runnable { 136 | rounded = false 137 | paddingX = 0f 138 | paddingY = 0f 139 | ChattingConfig.smoothBG = false 140 | } 141 | 142 | @Button( 143 | name = "Revert to Chatting Chat Window", 144 | description = "Revert the chat window to the Chatting custom chat window, instead of the vanilla chat window.", 145 | text = "Revert" 146 | ) 147 | var revertToChatting = Runnable { 148 | rounded = true 149 | paddingX = 5f 150 | paddingY = 5f 151 | ChattingConfig.smoothBG = true 152 | } 153 | 154 | init { 155 | showInDebug = true 156 | ignoreCaching = true 157 | } 158 | 159 | override fun draw(matrices: UMatrixStack?, x: Float, y: Float, scale: Float, example: Boolean) { 160 | if (!example) return 161 | GL.pushMatrix() 162 | GL.translate(x, y + scale, 0f) 163 | GL.scale(scale, scale, 1f) 164 | for (chat in exampleList) { 165 | ModCompatHooks.redirectDrawString(chat.chatComponent.formattedText, 0f, 0f, -1, chat) 166 | GL.translate(0f, 9f, 0f) 167 | } 168 | GL.popMatrix() 169 | } 170 | 171 | override fun drawBackground(x: Float, y: Float, width: Float, height: Float, scale: Float) { 172 | if (Chatting.isPatcher && PatcherConfig.transparentChat) return 173 | val animatingOpacity = wasInChatGui && (ChattingConfig.smoothBG && (previousAnimationWidth != width || previousAnimationHeight != height)) 174 | wasInChatGui = mc.currentScreen is GuiChat || animatingOpacity 175 | previousAnimationWidth = width 176 | previousAnimationHeight = height 177 | val bgOpacity = openOpacity 178 | val borderOpacity = openBorderOpacity 179 | val tempBgAlpha = bgColor.alpha 180 | val tempBorderAlpha = borderColor.alpha 181 | bgColor.alpha = if (differentOpacity && wasInChatGui) bgOpacity else bgColor.alpha 182 | borderColor.alpha = if (differentOpacity && wasInChatGui) borderOpacity else borderColor.alpha 183 | super.drawBackground(x, y, width, height, scale) 184 | bgColor.alpha = tempBgAlpha 185 | borderColor.alpha = tempBorderAlpha 186 | } 187 | 188 | fun drawBG() { 189 | animationWidth = widthAnimation.get() 190 | animationHeight = heightAnimation.get() 191 | width = position.width + (if (mc.ingameGUI.chatGUI.chatOpen && !Chatting.peeking && ChattingConfig.extendBG) ModCompatHooks.chatButtonOffset else 0) * scale 192 | val heightEnd = if (height == 0) 0f else (height + paddingY * 2f) * scale 193 | val duration = ChattingConfig.bgDuration 194 | GlStateManager.enableAlpha() 195 | GlStateManager.enableBlend() 196 | if (width != widthAnimation.end) { 197 | if (ChattingConfig.smoothBG) { 198 | widthAnimation = EaseOutQuart(duration, animationWidth, width, false) 199 | } else { 200 | animationWidth = width 201 | } 202 | } 203 | if (heightEnd != heightAnimation.end) { 204 | if (ChattingConfig.smoothBG) { 205 | heightAnimation = EaseOutQuart(duration, animationHeight, heightEnd, false) 206 | } else { 207 | animationHeight = heightEnd 208 | } 209 | } 210 | if (animationHeight <= 0.3f || !background || HudCore.editing) return 211 | nanoVG(true) { 212 | val scale = UResolution.scaleFactor.toFloat() 213 | drawBackground(position.x, position.bottomY - animationHeight + (if (UResolution.windowHeight % 2 == 1) scale - 1 else 0f) / scale, animationWidth, animationHeight, this@ChatWindow.scale) 214 | } 215 | GlStateManager.disableAlpha() 216 | } 217 | 218 | fun canShow(): Boolean { 219 | showInChat = true 220 | return isEnabled && (shouldShow() || Platform.getGuiPlatform().isInChat) && (isGuiIngame xor isCachingIgnored) 221 | } 222 | 223 | fun getPaddingX() = paddingX 224 | 225 | fun getPaddingY() = paddingY 226 | 227 | override fun shouldDrawBackground(): Boolean { 228 | return HudCore.editing 229 | } 230 | 231 | override fun getWidth(scale: Float, example: Boolean): Float { 232 | return ((if (customChatWidth) Chatting.getChatWidth() else GuiNewChat.calculateChatboxWidth(mc.gameSettings.chatWidth)) + 4 + ModCompatHooks.chatHeadOffset) * scale 233 | } 234 | 235 | override fun getHeight(scale: Float, example: Boolean): Float { 236 | return 9f * 5 * scale 237 | } 238 | 239 | fun setBackground(boolean: Boolean) { 240 | background = boolean 241 | } 242 | 243 | fun getBackgroundColor(): OneColor { 244 | return bgColor 245 | } 246 | 247 | fun setBackgroundColor(color: OneColor) { 248 | bgColor = color 249 | } 250 | 251 | override fun setScale(scale: Float, example: Boolean) { 252 | super.setScale(scale, example) 253 | normalScale = scale 254 | } 255 | 256 | fun updateMCChatScale() { 257 | if (ChattingConfig.chatWindow.lastChatGuiScale != mc.gameSettings.chatScale) { 258 | ChattingConfig.chatWindow.lastChatGuiScale = mc.gameSettings.chatScale 259 | ChattingConfig.chatWindow.scale = ChattingConfig.chatWindow.normalScale * mc.gameSettings.chatScale 260 | } 261 | } 262 | 263 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/command/ChattingCommand.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.command 2 | 3 | import cc.polyfrost.oneconfig.utils.commands.annotations.Command 4 | import cc.polyfrost.oneconfig.utils.commands.annotations.Main 5 | import org.polyfrost.chatting.Chatting 6 | import org.polyfrost.chatting.config.ChattingConfig 7 | 8 | @Command(value = Chatting.ID, description = "Access the " + Chatting.NAME + " GUI.") 9 | class ChattingCommand { 10 | @Main 11 | fun main() { 12 | ChattingConfig.openGui() 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/config/ChattingConfig.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.config 2 | 3 | import cc.polyfrost.oneconfig.config.Config 4 | import cc.polyfrost.oneconfig.config.annotations.* 5 | import cc.polyfrost.oneconfig.config.core.OneColor 6 | import cc.polyfrost.oneconfig.config.core.OneKeyBind 7 | import cc.polyfrost.oneconfig.config.data.InfoType 8 | import cc.polyfrost.oneconfig.config.data.Mod 9 | import cc.polyfrost.oneconfig.config.data.ModType 10 | import cc.polyfrost.oneconfig.config.migration.VigilanceMigrator 11 | import cc.polyfrost.oneconfig.libs.universal.UKeyboard 12 | import cc.polyfrost.oneconfig.utils.dsl.mc 13 | import cc.polyfrost.oneconfig.utils.hypixel.HypixelUtils 14 | import club.sk1er.patcher.config.OldPatcherConfig 15 | import org.polyfrost.chatting.Chatting 16 | import org.polyfrost.chatting.chat.* 17 | import org.polyfrost.chatting.gui.components.TabButton 18 | import org.polyfrost.chatting.hook.ChatLineHook 19 | import org.polyfrost.chatting.hook.GuiChatHook 20 | import org.polyfrost.chatting.utils.ModCompatHooks 21 | import java.io.File 22 | 23 | object ChattingConfig : Config( 24 | Mod( 25 | Chatting.NAME, 26 | ModType.UTIL_QOL, 27 | "/chatting_dark.svg", 28 | VigilanceMigrator(File(Chatting.oldModDir, Chatting.ID + ".toml").toPath().toString()) 29 | ), "chatting.json", true, false 30 | ) { 31 | 32 | @Dropdown( 33 | name = "Text Render Type", category = "General", options = ["No Shadow", "Shadow", "Full Shadow"], 34 | description = "Specifies how text should be rendered in the chat. Full Shadow displays a shadow on all sides of the text, while Shadow only displays a shadow on the right and bottom sides of the text." 35 | ) 36 | var textRenderType = 1 37 | 38 | @Color( 39 | name = "Hover Message Background Color", category = "General", 40 | description = "The color of the chat background when hovering over a message." 41 | ) 42 | var hoveredChatBackgroundColor = OneColor(80, 80, 80, 128) 43 | 44 | @Checkbox( 45 | name = "Message Fade" 46 | ) 47 | var fade = true 48 | 49 | @Slider( 50 | name = "Time Before Fade", 51 | min = 0f, max = 20f 52 | ) 53 | var fadeTime = 10f 54 | 55 | @Switch( 56 | name = "Inform Outdated Mods", category = "General", 57 | description = "Inform the user when a mod can be replaced by Chatting.", 58 | size = 2 59 | ) 60 | var informForAlternatives = true 61 | 62 | @Switch( 63 | name = "Chat Peek", 64 | description = "Allows you to view / scroll chat while moving around." 65 | ) 66 | var chatPeek = false 67 | 68 | @Switch( 69 | name = "Chat Peek Scrolling", 70 | ) 71 | var peekScrolling = true 72 | 73 | @KeyBind( 74 | name = "Peek KeyBind" 75 | ) 76 | var chatPeekBind = OneKeyBind(UKeyboard.KEY_Z) 77 | 78 | @DualOption( 79 | name = "Peek Mode", 80 | left = "Held", 81 | right = "Toggle" 82 | ) 83 | var peekMode = false 84 | 85 | @Switch( 86 | name = "Underlined Links", category = "General", 87 | description = "Makes clickable links in chat blue and underlined.", 88 | size = 1 89 | ) 90 | var underlinedLinks = false 91 | 92 | @Switch( 93 | name = "Smooth Chat Messages", 94 | category = "Animations", subcategory = "Messages", 95 | description = "Smoothly animate chat messages when they appear." 96 | ) 97 | var smoothChat = true 98 | 99 | @Slider( 100 | name = "Message Animation Speed", 101 | category = "Animations", subcategory = "Messages", 102 | min = 0.0f, max = 1.0f, 103 | description = "The speed at which chat messages animate." 104 | ) 105 | var messageSpeed = 0.5f 106 | 107 | @Switch( 108 | name = "Disable for Edits", 109 | category = "Animations", subcategory = "Messages", 110 | description = "Disable smooth animations for edited messages." 111 | ) 112 | var disableSmoothEdits = true 113 | 114 | @Switch( 115 | name = "Smooth Chat Background", 116 | category = "Animations", subcategory = "Background", 117 | description = "Smoothly animate chat background." 118 | ) 119 | var smoothBG = true 120 | 121 | @Slider( 122 | name = "Background Animation Duration", 123 | category = "Animations", subcategory = "Background", 124 | min = 50f, max = 1000f, 125 | description = "The speed at which chat background animate." 126 | ) 127 | var bgDuration = 400f 128 | 129 | @Switch( 130 | name = "Smooth Chat Scrolling", 131 | category = "Animations", subcategory = "Scrolling", 132 | description = "Smoothly animate scrolling when scrolling through the chat." 133 | ) 134 | var smoothScrolling = true 135 | 136 | @Slider( 137 | name = "Scrolling Animation Speed", 138 | category = "Animations", subcategory = "Scrolling", 139 | min = 0.0f, max = 1.0f, 140 | description = "The speed at which scrolling animates." 141 | ) 142 | var scrollingSpeed = 0.15f 143 | 144 | @Switch( 145 | name = "Remove Scroll Bar", 146 | category = "Animations", subcategory = "Scrolling", 147 | description = "Removes the vanilla scroll bar from the chat." 148 | ) 149 | var removeScrollBar = true 150 | 151 | @Color( 152 | name = "Chat Button Color", category = "Buttons", 153 | description = "The color of the chat button." 154 | ) 155 | var chatButtonColor = OneColor(255, 255, 255, 255) 156 | 157 | @Color( 158 | name = "Chat Button Hovered Color", category = "Buttons", 159 | description = "The color of the chat button when hovered." 160 | ) 161 | var chatButtonHoveredColor = OneColor(255, 255, 160, 255) 162 | 163 | @Color( 164 | name = "Chat Button Background Color", category = "Buttons", 165 | description = "The color of the chat button background." 166 | ) 167 | var chatButtonBackgroundColor = OneColor(0, 0, 0, 128) 168 | 169 | @Color( 170 | name = "Chat Button Hovered Background Color", category = "Buttons", 171 | description = "The color of the chat button background when hovered." 172 | ) 173 | var chatButtonHoveredBackgroundColor = OneColor(255, 255, 255, 128) 174 | 175 | @Switch( 176 | name = "Button Shadow", category = "Buttons", 177 | description = "Enable button shadow." 178 | ) 179 | var buttonShadow = true 180 | 181 | @Switch( 182 | name = "Extend Chat Background", 183 | category = "Buttons", 184 | description = "Extends the chat background if buttons are enabled." 185 | ) 186 | var extendBG = true 187 | 188 | @Switch( 189 | name = "Chat Copying Button", category = "Buttons", 190 | description = "Enable copying chat messages via a button." 191 | ) 192 | var chatCopy = true 193 | 194 | @Switch( 195 | name = "Right Click to Copy Chat Message", category = "Buttons", 196 | description = "Enable right clicking on a chat message to copy it." 197 | ) 198 | var rightClickCopy = false 199 | 200 | @Switch( 201 | name = "Only Click Copy Chat Message when Holding CTRL", category = "Buttons", 202 | description = "Only allow right clicking on a chat message to copy it when holding CTRL." 203 | ) 204 | var rightClickCopyCtrl = true 205 | 206 | @Switch( 207 | name = "Delete Chat Message Button", category = "Buttons", 208 | description = "Enable deleting individual chat messages via a button." 209 | ) 210 | var chatDelete = true 211 | 212 | @Switch( 213 | name = "Delete Chat History Button", category = "Buttons", 214 | description = "Enable deleting chat history via a button." 215 | ) 216 | var chatDeleteHistory = true 217 | 218 | @Switch( 219 | name = "Chat Screenshot Button", category = "Buttons", 220 | description = "Enable taking a screenshot of the chat via a button." 221 | ) 222 | var chatScreenshot = true 223 | 224 | @Switch( 225 | name = "Chat Searching", category = "Buttons", 226 | description = "Enable searching through chat messages." 227 | ) 228 | var chatSearch = true 229 | 230 | @Switch( 231 | name = "Show Chat Heads", description = "Show the chat heads of players in chat", category = "Chat Heads", 232 | ) 233 | var showChatHeads = true 234 | 235 | @Switch( 236 | name = "Offset Non-Player Messages", 237 | description = "Offset all messages, even if a player has not been detected.", 238 | category = "Chat Heads" 239 | ) 240 | var offsetNonPlayerMessages = false 241 | 242 | @Switch( 243 | name = "Hide Chat Head on Consecutive Messages", 244 | description = "Hide the chat head if the previous message was from the same player.", 245 | category = "Chat Heads" 246 | ) 247 | var hideChatHeadOnConsecutiveMessages = true 248 | 249 | /*/ 250 | @Property( 251 | type = PropertyType.SWITCH, 252 | name = "Show Timestamp", 253 | description = "Show message timestamp.", 254 | category = "General" 255 | ) 256 | var showTimestamp = false 257 | 258 | @Property( 259 | type = PropertyType.SWITCH, 260 | name = "Timestamp Only On Hover", 261 | description = "Show timestamp only on mouse hover.", 262 | category = "General" 263 | ) 264 | var showTimestampHover = true 265 | 266 | */ 267 | 268 | @Info( 269 | text = "If Chatting detects a public chat message that seems like spam, and the probability is higher than this, it will hide it.", 270 | size = 2, 271 | category = "Player Chats", 272 | type = InfoType.INFO 273 | ) 274 | var ignored = false 275 | 276 | @Info( 277 | text = "Made for Hypixel Skyblock. Set to 100% to disable. 95% is a reasonable threshold to use it at. May not be accurate.", 278 | size = 2, 279 | category = "Player Chats", 280 | type = InfoType.INFO 281 | ) 282 | var ignored1 = false 283 | 284 | @Slider( 285 | min = 80F, max = 100F, name = "Spam Blocker Threshold", category = "Player Chats" 286 | ) 287 | var spamThreshold = 100 288 | 289 | @Switch( 290 | name = "Custom SkyBlock Chat Formatting (remove ranks)", category = "Player Chats" 291 | ) 292 | var customChatFormatting = false 293 | 294 | @Switch( 295 | name = "Completely Hide Spam", category = "Player Chats" 296 | ) 297 | var hideSpam = false 298 | 299 | @HUD( 300 | name = "Chat Window", category = "Chat Window" 301 | ) 302 | var chatWindow = ChatWindow() 303 | 304 | @HUD( 305 | name = "Chat Input Box", category = "Input Box" 306 | ) 307 | var chatInput = ChatInputBox() 308 | 309 | @Dropdown( 310 | name = "Screenshot Mode", category = "Screenshotting", options = ["Save To System", "Add To Clipboard", "Both"], 311 | description = "What to do when taking a screenshot." 312 | ) 313 | var copyMode = 0 314 | 315 | @Switch( 316 | name = "Chat Tabs", category = "Tabs", 317 | description = "Allow filtering chat messages by a tab." 318 | ) 319 | var chatTabs = true 320 | get() { 321 | if (!field) return false 322 | return if (hypixelOnlyChatTabs) { 323 | HypixelUtils.INSTANCE.isHypixel 324 | } else { 325 | true 326 | } 327 | } 328 | 329 | @Checkbox( 330 | name = "Enable Tabs Only on Hypixel", category = "Tabs", 331 | description = "Only enable chat tabs on Hypixel" 332 | ) 333 | var hypixelOnlyChatTabs = true 334 | 335 | @Info( 336 | category = "Tabs", 337 | type = InfoType.INFO, 338 | text = "You can use the SHIFT key to select multiple tabs, as well as CTRL + TAB to switch to the next tab.", 339 | size = 2 340 | ) 341 | @Transient 342 | var ignored2 = true 343 | 344 | @Switch( 345 | name = "Chat Shortcuts", category = "Shortcuts" 346 | ) 347 | var chatShortcuts = false 348 | get() { 349 | if (!field) return false 350 | return if (hypixelOnlyChatShortcuts) { 351 | HypixelUtils.INSTANCE.isHypixel 352 | } else { 353 | true 354 | } 355 | } 356 | 357 | @Checkbox( 358 | name = "Enable Shortcuts Only on Hypixel", category = "Shortcuts" 359 | ) 360 | var hypixelOnlyChatShortcuts = true 361 | 362 | @Switch( 363 | name = "Remove Tooltip Background", category = "Tooltips", 364 | description = "Removes the background from tooltips." 365 | ) 366 | var removeTooltipBackground = false 367 | 368 | @Dropdown( 369 | name = "Tooltip Text Render Type", category = "Tooltips", options = ["No Shadow", "Shadow", "Full Shadow"], 370 | description = "The type of shadow to render on tooltips." 371 | ) 372 | var tooltipTextRenderType = 1 373 | 374 | var isPatcherMigrated = false 375 | var isPatcherMigratedPt2CauseImStupid = false 376 | 377 | init { 378 | initialize() 379 | 380 | try { 381 | Class.forName("club.sk1er.patcher.config.OldPatcherConfig") 382 | if (!isPatcherMigrated) { 383 | if (OldPatcherConfig.transparentChat) { 384 | if (OldPatcherConfig.transparentChatOnlyWhenClosed) { 385 | chatWindow.setBackgroundColor(chatWindow.getBackgroundColor().also { it.alpha = 0 }) 386 | chatWindow.differentOpacity = true 387 | chatWindow.openOpacity = 0 388 | } else { 389 | chatWindow.setBackground(false) 390 | } 391 | } 392 | if (OldPatcherConfig.transparentChatInputField) { 393 | chatInput.setBackground(false) 394 | } 395 | isPatcherMigrated = true 396 | 397 | save() 398 | } 399 | if (!isPatcherMigratedPt2CauseImStupid) { 400 | if (OldPatcherConfig.transparentChat) { 401 | if (OldPatcherConfig.transparentChatOnlyWhenClosed && chatWindow.openOpacity == 255) { 402 | chatWindow.openOpacity = 0 403 | } 404 | } 405 | 406 | isPatcherMigratedPt2CauseImStupid = true 407 | 408 | save() 409 | } 410 | } catch (_: ClassNotFoundException) {} 411 | 412 | addDependency("rightClickCopyCtrl", "rightClickCopy") 413 | addDependency("fadeTime", "fade") 414 | addDependency("offsetNonPlayerMessages", "showChatHeads") 415 | addDependency("hideChatHeadOnConsecutiveMessages", "showChatHeads") 416 | addDependency("hypixelOnlyChatTabs", "chatTabs") 417 | addDependency("hypixelOnlyChatShortcuts", "chatShortcuts") 418 | addDependency("scrollingSpeed", "smoothScrolling") 419 | addDependency("messageSpeed", "smoothChat") 420 | addDependency("disableSmoothEdits", "smoothChat") 421 | addDependency("bgDuration", "smoothBG") 422 | addDependency("peekScrolling", "chatPeek") 423 | addDependency("chatPeekBind", "chatPeek") 424 | addDependency("peekMode", "chatPeek") 425 | addDependency("smoothChat", "BetterChat Smooth Chat") { 426 | !ModCompatHooks.betterChatSmoothMessages 427 | } 428 | addListener("peekMode") { 429 | Chatting.peeking = false 430 | } 431 | addListener("hideChatHeadOnConsecutiveMessages") { 432 | ChatLineHook.`chatting$chatLines`.map { it.get() as ChatLineHook? }.forEach { it?.`chatting$updatePlayerInfo`() } 433 | } 434 | addListener("chatTabs") { 435 | ChatTabs.initialize() 436 | if (!chatTabs) { 437 | val dummy = ChatTab( 438 | true, 439 | "ALL", 440 | unformatted = false, 441 | lowercase = false, 442 | startsWith = null, 443 | contains = null, 444 | endsWith = null, 445 | equals = null, 446 | uncompiledRegex = null, 447 | ignoreStartsWith = null, 448 | ignoreContains = null, 449 | ignoreEndsWith = null, 450 | ignoreEquals = null, 451 | uncompiledIgnoreRegex = null, 452 | color = TabButton.color, 453 | hoveredColor = TabButton.hoveredColor, 454 | selectedColor = TabButton.selectedColor, 455 | prefix = "" 456 | ) 457 | dummy.initialize() 458 | ChatTabs.currentTabs.clear() 459 | ChatTabs.currentTabs.add(dummy) 460 | } else { 461 | ChatTabs.currentTabs.clear() 462 | ChatTabs.currentTabs.add(ChatTabs.tabs[0]) 463 | } 464 | } 465 | addListener("chatShortcuts") { 466 | ChatShortcuts.initialize() 467 | } 468 | listOf( 469 | "chatSearch", 470 | "chatScreenshot", 471 | "chatDeleteHistory", 472 | "chatTabs" 473 | ).forEach { 474 | addListener(it) { 475 | mc.currentScreen?.let { screen -> 476 | if (screen is GuiChatHook) { 477 | screen.`chatting$triggerButtonReset`() 478 | } 479 | } 480 | } } 481 | // addDependency("showTimestampHover", "showTimestamp") 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/gui/components/CleanButton.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.gui.components 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.UResolution 4 | import cc.polyfrost.oneconfig.renderer.TextRenderer 5 | import org.polyfrost.chatting.Chatting 6 | import org.polyfrost.chatting.config.ChattingConfig 7 | import club.sk1er.patcher.config.PatcherConfig 8 | import net.minecraft.client.Minecraft 9 | import net.minecraft.client.gui.GuiButton 10 | import net.minecraft.client.renderer.GlStateManager 11 | import org.polyfrost.chatting.chat.ChatHooks 12 | import org.polyfrost.chatting.hook.GuiNewChatHook 13 | 14 | /** 15 | * Taken from ChatShortcuts under MIT License 16 | * https://github.com/P0keDev/ChatShortcuts/blob/master/LICENSE 17 | * @author P0keDev 18 | */ 19 | open class CleanButton( 20 | buttonId: Int, 21 | private val x: () -> Int, 22 | widthIn: Int, 23 | heightIn: Int, 24 | name: String, 25 | private val renderType: () -> RenderType, 26 | private val textColor: (packedFGColour: Int, enabled: Boolean, hovered: Boolean) -> Int = { packedFGColour: Int, enabled: Boolean, hovered: Boolean -> 27 | var j = 14737632 28 | if (packedFGColour != 0) { 29 | j = packedFGColour 30 | } else if (!enabled) { 31 | j = 10526880 32 | } else if (hovered) { 33 | j = 16777120 34 | } 35 | j 36 | }, 37 | ) : 38 | GuiButton(buttonId, x.invoke(), 0, widthIn, heightIn, name) { 39 | 40 | open fun isEnabled(): Boolean { 41 | return false 42 | } 43 | 44 | open fun onMousePress() { 45 | 46 | } 47 | 48 | open fun setPositionY() { 49 | yPosition = UResolution.scaledHeight - 27 + if (ChattingConfig.chatInput.compactInputBox && xPosition - ChatHooks.inputBoxRight >= 1) 13 else 0 50 | } 51 | 52 | override fun mousePressed(mc: Minecraft, mouseX: Int, mouseY: Int): Boolean { 53 | val isPressed = 54 | visible && mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height 55 | if (isPressed) { 56 | onMousePress() 57 | } 58 | return isPressed 59 | } 60 | 61 | override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { 62 | enabled = isEnabled() 63 | xPosition = x() 64 | setPositionY() 65 | if (visible) { 66 | val fontrenderer = mc.fontRendererObj 67 | GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) 68 | GlStateManager.enableAlpha() 69 | GlStateManager.enableBlend() 70 | GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) 71 | GlStateManager.blendFunc(770, 771) 72 | hovered = 73 | mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height 74 | if (!Chatting.isPatcher || !PatcherConfig.transparentChatInputField) { 75 | drawRect( 76 | xPosition, 77 | yPosition, 78 | xPosition + width, 79 | yPosition + height, 80 | getBackgroundColor(hovered) 81 | ) 82 | } 83 | mouseDragged(mc, mouseX, mouseY) 84 | val j = textColor(packedFGColour, enabled, hovered) 85 | when (renderType()) { 86 | RenderType.NONE, RenderType.SHADOW -> { 87 | drawCenteredString( 88 | fontrenderer, 89 | displayString, 90 | xPosition + width / 2, 91 | yPosition + (height - 8) / 2, 92 | j 93 | ) 94 | } 95 | 96 | RenderType.FULL -> { 97 | TextRenderer.drawBorderedText( 98 | displayString, 99 | ((xPosition + width / 2) - (fontrenderer.getStringWidth(displayString) / 2)).toFloat(), 100 | (yPosition + (height - 8) / 2).toFloat(), 101 | j, 102 | (mc.ingameGUI.chatGUI as GuiNewChatHook).`chatting$getTextOpacity`() 103 | ) 104 | } 105 | } 106 | } 107 | } 108 | 109 | private fun getBackgroundColor(hovered: Boolean) = 110 | if (hovered) ChattingConfig.chatButtonHoveredBackgroundColor.rgb 111 | else ChattingConfig.chatButtonBackgroundColor.rgb 112 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/gui/components/ClearButton.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.gui.components 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.ChatColor 4 | import cc.polyfrost.oneconfig.libs.universal.UChat 5 | import cc.polyfrost.oneconfig.libs.universal.UResolution 6 | import cc.polyfrost.oneconfig.utils.Multithreading 7 | import cc.polyfrost.oneconfig.utils.dsl.mc 8 | import net.minecraft.client.Minecraft 9 | import net.minecraft.client.gui.Gui 10 | import net.minecraft.client.renderer.GlStateManager 11 | import net.minecraft.util.ResourceLocation 12 | import org.polyfrost.chatting.Chatting 13 | import org.polyfrost.chatting.config.ChattingConfig 14 | import org.polyfrost.chatting.config.ChattingConfig.chatButtonColor 15 | import org.polyfrost.chatting.config.ChattingConfig.chatButtonHoveredColor 16 | 17 | class ClearButton : 18 | CleanButton(13379014, { if (ChattingConfig.chatSearch) UResolution.scaledWidth - 28 else UResolution.scaledWidth - 14 }, 12, 12, "", 19 | { RenderType.NONE }) { 20 | 21 | var times = 0 22 | 23 | override fun onMousePress() { 24 | ++times 25 | if (times > 1) { 26 | times = 0 27 | mc.ingameGUI.chatGUI.clearChatMessages() 28 | } else { 29 | UChat.chat(ChatColor.RED + ChatColor.BOLD.toString() + "Click again to clear the chat!") 30 | Multithreading.runAsync { 31 | Thread.sleep(3000) 32 | times = 0 33 | } 34 | } 35 | } 36 | 37 | override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { 38 | super.drawButton(mc, mouseX, mouseY) 39 | if (visible) { 40 | GlStateManager.pushMatrix() 41 | GlStateManager.enableAlpha() 42 | GlStateManager.enableBlend() 43 | GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) 44 | GlStateManager.blendFunc(770, 771) 45 | mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "delete.png")) 46 | val color = if (hovered) chatButtonHoveredColor else chatButtonColor 47 | if (ChattingConfig.buttonShadow) { 48 | GlStateManager.color(0f, 0f, 0f, color.getAlpha() / 255f) 49 | Gui.drawModalRectWithCustomSizedTexture(xPosition + 2, yPosition + 2, 0f, 0f, 10, 10, 10f, 10f) 50 | } 51 | GlStateManager.color(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f) 52 | Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) 53 | GlStateManager.popMatrix() 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/gui/components/RenderType.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.gui.components 2 | 3 | enum class RenderType { 4 | NONE, 5 | SHADOW, 6 | FULL 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/gui/components/ScreenshotButton.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.gui.components 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.UResolution 4 | import cc.polyfrost.oneconfig.libs.universal.UScreen 5 | import cc.polyfrost.oneconfig.utils.dsl.mc 6 | import org.polyfrost.chatting.Chatting 7 | import org.polyfrost.chatting.mixin.GuiNewChatAccessor 8 | import net.minecraft.client.Minecraft 9 | import net.minecraft.client.gui.Gui 10 | import net.minecraft.client.gui.GuiChat 11 | import net.minecraft.client.renderer.GlStateManager 12 | import net.minecraft.util.ResourceLocation 13 | import org.polyfrost.chatting.config.ChattingConfig 14 | 15 | class ScreenshotButton : 16 | CleanButton(448318, { 17 | if (ChattingConfig.chatSearch && ChattingConfig.chatDeleteHistory) UResolution.scaledWidth - 42 else if (ChattingConfig.chatSearch || ChattingConfig.chatDeleteHistory) UResolution.scaledWidth - 28 else UResolution.scaledWidth - 14 18 | }, 12, 12, "", 19 | { RenderType.NONE }) { 20 | 21 | override fun onMousePress() { 22 | if (UScreen.currentScreen is GuiChat) { 23 | Chatting.screenshotChat((mc.ingameGUI.chatGUI as GuiNewChatAccessor).scrollPos) 24 | } 25 | } 26 | 27 | override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { 28 | super.drawButton(mc, mouseX, mouseY) 29 | if (visible) { 30 | GlStateManager.pushMatrix() 31 | GlStateManager.enableAlpha() 32 | GlStateManager.enableBlend() 33 | GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) 34 | GlStateManager.blendFunc(770, 771) 35 | mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "screenshot.png")) 36 | val color = if (hovered) ChattingConfig.chatButtonHoveredColor else ChattingConfig.chatButtonColor 37 | if (ChattingConfig.buttonShadow) { 38 | GlStateManager.color(0f, 0f, 0f, color.alpha / 255f) 39 | Gui.drawModalRectWithCustomSizedTexture(xPosition + 2, yPosition + 2, 0f, 0f, 10, 10, 10f, 10f) 40 | } 41 | GlStateManager.color(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f) 42 | Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) 43 | GlStateManager.popMatrix() 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/gui/components/SearchButton.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.gui.components 2 | 3 | import cc.polyfrost.oneconfig.config.core.OneColor 4 | import cc.polyfrost.oneconfig.libs.universal.UResolution 5 | import cc.polyfrost.oneconfig.utils.dsl.mc 6 | import org.polyfrost.chatting.Chatting 7 | import org.polyfrost.chatting.chat.ChatSearchingManager 8 | import net.minecraft.client.Minecraft 9 | import net.minecraft.client.gui.Gui 10 | import net.minecraft.client.gui.GuiTextField 11 | import net.minecraft.client.renderer.GlStateManager 12 | import net.minecraft.util.ResourceLocation 13 | import org.polyfrost.chatting.config.ChattingConfig 14 | 15 | class SearchButton() : 16 | CleanButton(3993935, { UResolution.scaledWidth - 14 }, 12, 12, "", 17 | { RenderType.NONE }) { 18 | val inputField = SearchTextField() 19 | private var chatBox = false 20 | 21 | override fun isEnabled(): Boolean { 22 | return chatBox 23 | } 24 | 25 | override fun onMousePress() { 26 | chatBox = !chatBox 27 | inputField.setEnabled(chatBox) 28 | inputField.isFocused = chatBox 29 | ChatSearchingManager.lastSearch = "" 30 | inputField.text = "" 31 | } 32 | 33 | override fun drawButton(mc: Minecraft, mouseX: Int, mouseY: Int) { 34 | 35 | inputField.drawTextBox() 36 | super.drawButton(mc, mouseX, mouseY) 37 | if (visible) { 38 | GlStateManager.pushMatrix() 39 | GlStateManager.enableAlpha() 40 | GlStateManager.enableBlend() 41 | GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) 42 | GlStateManager.blendFunc(770, 771) 43 | mc.textureManager.bindTexture(ResourceLocation(Chatting.ID, "search.png")) 44 | val color = if (isEnabled()) OneColor(200, 200, 200, 255) else if (hovered) ChattingConfig.chatButtonHoveredColor else ChattingConfig.chatButtonColor 45 | if (ChattingConfig.buttonShadow) { 46 | GlStateManager.color(0f, 0f, 0f, color.alpha / 255f) 47 | Gui.drawModalRectWithCustomSizedTexture(xPosition + 2, yPosition + 2, 0f, 0f, 10, 10, 10f, 10f) 48 | } 49 | GlStateManager.color(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f) 50 | Gui.drawModalRectWithCustomSizedTexture(xPosition + 1, yPosition + 1, 0f, 0f, 10, 10, 10f, 10f) 51 | GlStateManager.popMatrix() 52 | } 53 | } 54 | 55 | inner class SearchTextField : GuiTextField( 56 | 69420, 57 | mc.fontRendererObj, 58 | UResolution.scaledWidth * 4 / 5 - 60, 59 | UResolution.scaledHeight - 26, 60 | UResolution.scaledWidth / 5, 61 | 12 62 | ) { 63 | 64 | init { 65 | maxStringLength = 100 66 | enableBackgroundDrawing = true 67 | isFocused = false 68 | text = "" 69 | setCanLoseFocus(true) 70 | } 71 | 72 | override fun drawTextBox() { 73 | if (isEnabled()) { 74 | if (!isFocused) isFocused = true 75 | super.drawTextBox() 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/gui/components/TabButton.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.gui.components 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.UKeyboard 4 | import cc.polyfrost.oneconfig.libs.universal.UResolution 5 | import org.polyfrost.chatting.chat.ChatTab 6 | import org.polyfrost.chatting.chat.ChatTabs 7 | import org.polyfrost.chatting.config.ChattingConfig 8 | 9 | class TabButton(buttonId: Int, x: Int, widthIn: Int, heightIn: Int, private val chatTab: ChatTab) : 10 | CleanButton(buttonId, { x }, widthIn, heightIn, chatTab.name, { RenderType.values()[ChattingConfig.textRenderType] }, { packedFGColour: Int, enabled: Boolean, hovered: Boolean -> 11 | var j = chatTab.color ?: color 12 | if (packedFGColour != 0) { 13 | j = packedFGColour 14 | } else if (!enabled) { 15 | j = chatTab.selectedColor ?: selectedColor 16 | } else if (hovered) { 17 | j = chatTab.hoveredColor ?: hoveredColor 18 | } 19 | j 20 | }) { 21 | 22 | override fun onMousePress() { 23 | if (UKeyboard.isShiftKeyDown()) { 24 | if (ChatTabs.currentTabs.contains(chatTab)) { 25 | ChatTabs.currentTabs.remove(chatTab) 26 | } else { 27 | ChatTabs.currentTabs.add(chatTab) 28 | } 29 | } else { 30 | ChatTabs.currentTabs.clear() 31 | ChatTabs.currentTabs.add(chatTab) 32 | } 33 | } 34 | 35 | override fun setPositionY() { 36 | yPosition = UResolution.scaledHeight - 26 37 | } 38 | 39 | override fun isEnabled(): Boolean { 40 | return ChatTabs.currentTabs.contains(chatTab) 41 | } 42 | 43 | companion object { 44 | const val color: Int = 14737632 45 | const val hoveredColor: Int = 16777120 46 | const val selectedColor: Int = 10526880 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/utils/EaseOutQuad.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.utils 2 | 3 | import cc.polyfrost.oneconfig.gui.animations.Animation 4 | import net.minecraft.client.Minecraft 5 | 6 | class EaseOutQuad(duration: Float, start: Float, end: Float, reverse: Boolean): Animation(duration, start, end, reverse) { 7 | var startTime = 0L 8 | 9 | init { 10 | startTime = Minecraft.getSystemTime() 11 | } 12 | 13 | override fun get(): Float { 14 | timePassed = (Minecraft.getSystemTime() - startTime).toFloat() 15 | if (timePassed >= duration) return start + change 16 | return animate(timePassed / duration) * change + start 17 | } 18 | 19 | override fun isFinished(): Boolean { 20 | return (Minecraft.getSystemTime() - startTime).toFloat() >= duration 21 | } 22 | 23 | override fun animate(x: Float): Float = 1 - (1 - x) * (1 - x) 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/utils/EaseOutQuart.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.utils 2 | 3 | import cc.polyfrost.oneconfig.gui.animations.Animation 4 | import net.minecraft.client.Minecraft 5 | 6 | class EaseOutQuart(duration: Float, start: Float, end: Float, reverse: Boolean): Animation(duration, start, end, reverse) { 7 | var startTime = 0L 8 | 9 | init { 10 | startTime = Minecraft.getSystemTime() 11 | } 12 | 13 | override fun get(): Float { 14 | timePassed = (Minecraft.getSystemTime() - startTime).toFloat() 15 | if (timePassed >= duration) return start + change 16 | return animate(timePassed / duration) * change + start 17 | } 18 | 19 | override fun isFinished(): Boolean { 20 | return (Minecraft.getSystemTime() - startTime).toFloat() >= duration 21 | } 22 | 23 | override fun animate(x: Float) = -1 * (x - 1) * (x - 1) * (x - 1) * (x - 1) + 1 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/utils/ModCompatHooks.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.chatting.utils 2 | 3 | import cc.polyfrost.oneconfig.platform.Platform 4 | import cc.polyfrost.oneconfig.renderer.TextRenderer 5 | import cc.polyfrost.oneconfig.utils.dsl.getAlpha 6 | import cc.polyfrost.oneconfig.utils.dsl.mc 7 | import club.sk1er.patcher.config.PatcherConfig 8 | import com.llamalad7.betterchat.BetterChat 9 | import net.minecraft.client.gui.ChatLine 10 | import net.minecraft.client.gui.FontRenderer 11 | import net.minecraft.client.gui.Gui 12 | import net.minecraft.client.renderer.GlStateManager 13 | import org.polyfrost.chatting.Chatting.isBetterChat 14 | import org.polyfrost.chatting.Chatting.isPatcher 15 | import org.polyfrost.chatting.config.ChattingConfig 16 | import org.polyfrost.chatting.config.ChattingConfig.offsetNonPlayerMessages 17 | import org.polyfrost.chatting.config.ChattingConfig.showChatHeads 18 | import org.polyfrost.chatting.hook.ChatLineHook 19 | import org.polyfrost.chatting.mixin.GuiNewChatAccessor 20 | 21 | // This exists because mixin doesn't like dummy classes 22 | object ModCompatHooks { 23 | @JvmStatic 24 | val xOffset 25 | get() = if (isBetterChat) BetterChat.getSettings().xOffset else 0 26 | 27 | @JvmStatic 28 | val yOffset 29 | get() = if (isBetterChat) BetterChat.getSettings().yOffset else 0 30 | 31 | @JvmStatic 32 | val chatPosition 33 | get() = if (isPatcher && PatcherConfig.chatPosition) 12 else 0 34 | 35 | @JvmStatic 36 | val betterChatSmoothMessages 37 | get() = if (isBetterChat) BetterChat.getSettings().smooth else false 38 | 39 | @JvmStatic 40 | val extendedChatLength 41 | get() = if (isPatcher) 32667 else 0 42 | 43 | @JvmStatic 44 | val fontRenderer: FontRenderer 45 | get() = mc.fontRendererObj 46 | 47 | @JvmStatic 48 | val chatLines: List 49 | get() = (mc.ingameGUI.chatGUI as GuiNewChatAccessor).chatLines 50 | 51 | @JvmStatic 52 | val drawnChatLines: List 53 | get() = (mc.ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines 54 | 55 | @JvmStatic 56 | val chatHeadOffset 57 | get() = if (showChatHeads) 10 else 0 58 | 59 | @JvmStatic 60 | val chatButtonOffset 61 | get() = (if (ChattingConfig.chatCopy) 10 else 0) + (if (ChattingConfig.chatDelete) 10 else 0) 62 | 63 | @JvmStatic 64 | val chatInputLimit 65 | get() = if (isPatcher && PatcherConfig.extendedChatLength) 256 else 100 66 | 67 | @JvmStatic 68 | val shouldDrawInputBox 69 | get() = !isPatcher || !PatcherConfig.transparentChatInputField 70 | 71 | @JvmStatic 72 | fun redirectDrawString(text: String, x: Float, y: Float, color: Int, chatLine: ChatLine): Int { 73 | var actualX = x 74 | if (showChatHeads) { 75 | val hook = chatLine as ChatLineHook 76 | if (hook.`chatting$hasDetected`() || offsetNonPlayerMessages) { 77 | actualX += 10f 78 | } 79 | val networkPlayerInfo = hook.`chatting$getPlayerInfo`() 80 | if (networkPlayerInfo != null) { 81 | GlStateManager.enableBlend() 82 | GlStateManager.enableAlpha() 83 | GlStateManager.enableTexture2D() 84 | mc.textureManager.bindTexture(networkPlayerInfo.locationSkin) 85 | GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) 86 | GlStateManager.color(1.0f, 1.0f, 1.0f, color.getAlpha() / 255f) 87 | Gui.drawScaledCustomSizeModalRect( 88 | (x).toInt(), 89 | (y - 1f).toInt(), 90 | 8.0f, 91 | 8.0f, 92 | 8, 93 | 8, 94 | 8, 95 | 8, 96 | 64.0f, 97 | 64.0f 98 | ) 99 | Gui.drawScaledCustomSizeModalRect( 100 | (x).toInt(), 101 | (y - 1f).toInt(), 102 | 40.0f, 103 | 8.0f, 104 | 8, 105 | 8, 106 | 8, 107 | 8, 108 | 64.0f, 109 | 64.0f 110 | ) 111 | GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) 112 | } 113 | } 114 | return when (ChattingConfig.textRenderType) { 115 | 0 -> Platform.getGLPlatform().drawText(text, actualX, y, color, false).toInt() 116 | 1 -> Platform.getGLPlatform().drawText(text, actualX, y, color, true).toInt() 117 | 2 -> TextRenderer.drawBorderedText(text, actualX, y, color, color.getAlpha()) 118 | else -> fontRenderer.drawString(text, actualX, y, color, true) 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/kotlin/org/polyfrost/chatting/utils/RenderUtils.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("RenderUtils") 2 | 3 | package org.polyfrost.chatting.utils 4 | 5 | import cc.polyfrost.oneconfig.utils.IOUtils 6 | import org.polyfrost.chatting.config.ChattingConfig 7 | import net.minecraft.client.renderer.GlStateManager 8 | import net.minecraft.client.renderer.texture.TextureUtil 9 | import net.minecraft.client.shader.Framebuffer 10 | import org.apache.commons.lang3.SystemUtils 11 | import org.lwjgl.BufferUtils 12 | import org.lwjgl.opengl.GL11 13 | import org.lwjgl.opengl.GL12 14 | import sun.awt.datatransfer.DataTransferer 15 | import sun.awt.datatransfer.SunClipboard 16 | import java.awt.Toolkit 17 | import java.awt.image.BufferedImage 18 | import java.io.File 19 | import java.lang.reflect.Field 20 | import java.lang.reflect.Method 21 | import java.nio.ByteBuffer 22 | import java.nio.ByteOrder 23 | import javax.imageio.ImageIO 24 | 25 | /** 26 | * Taken from https://github.com/Moulberry/HyChat 27 | */ 28 | fun createBindFramebuffer(w: Int, h: Int): Framebuffer { 29 | val framebuffer = Framebuffer(w, h, false) 30 | framebuffer.framebufferColor[0] = 0x36 / 255f 31 | framebuffer.framebufferColor[1] = 0x39 / 255f 32 | framebuffer.framebufferColor[2] = 0x3F / 255f 33 | framebuffer.framebufferClear() 34 | GlStateManager.matrixMode(5889) 35 | GlStateManager.loadIdentity() 36 | GlStateManager.ortho(0.0, w.toDouble(), h.toDouble(), 0.0, 1000.0, 3000.0) 37 | GlStateManager.matrixMode(5888) 38 | GlStateManager.loadIdentity() 39 | GlStateManager.translate(0.0f, 0.0f, -2000.0f) 40 | framebuffer.bindFramebuffer(true) 41 | return framebuffer 42 | } 43 | 44 | /** 45 | * Taken from https://github.com/Moulberry/HyChat 46 | * Modified so if not on Windows just in case it will switch it to RGB and remove the transparent background. 47 | */ 48 | fun BufferedImage.copyToClipboard() { 49 | if (SystemUtils.IS_OS_WINDOWS) { 50 | try { 51 | val width = this.width 52 | val height = this.height 53 | val hdrSize = 0x28 54 | val buffer: ByteBuffer = ByteBuffer.allocate(hdrSize + width * height * 4) 55 | buffer.order(ByteOrder.LITTLE_ENDIAN) 56 | //Header size 57 | buffer.putInt(hdrSize) 58 | //Width 59 | buffer.putInt(width) 60 | //Int32 biHeight; 61 | buffer.putInt(height) 62 | //Int16 biPlanes; 63 | buffer.put(1.toByte()) 64 | buffer.put(0.toByte()) 65 | //Int16 biBitCount; 66 | buffer.put(32.toByte()) 67 | buffer.put(0.toByte()) 68 | //Compression 69 | buffer.putInt(0) 70 | //Int32 biSizeImage; 71 | buffer.putInt(width * height * 4) 72 | buffer.putInt(0) 73 | buffer.putInt(0) 74 | buffer.putInt(0) 75 | buffer.putInt(0) 76 | 77 | //Image data 78 | for (y in 0 until height) { 79 | for (x in 0 until width) { 80 | val argb: Int = this.getRGB(x, height - y - 1) 81 | if (argb shr 24 and 0xFF == 0) { 82 | buffer.putInt(0x00000000) 83 | } else { 84 | buffer.putInt(argb) 85 | } 86 | } 87 | } 88 | buffer.flip() 89 | val hdrSizev5 = 0x7C 90 | val bufferv5: ByteBuffer = ByteBuffer.allocate(hdrSizev5 + width * height * 4) 91 | bufferv5.order(ByteOrder.LITTLE_ENDIAN) 92 | //Header size 93 | bufferv5.putInt(hdrSizev5) 94 | //Width 95 | bufferv5.putInt(width) 96 | //Int32 biHeight; 97 | bufferv5.putInt(height) 98 | //Int16 biPlanes; 99 | bufferv5.put(1.toByte()) 100 | bufferv5.put(0.toByte()) 101 | //Int16 biBitCount; 102 | bufferv5.put(32.toByte()) 103 | bufferv5.put(0.toByte()) 104 | //Compression 105 | bufferv5.putInt(0) 106 | //Int32 biSizeImage; 107 | bufferv5.putInt(width * height * 4) 108 | bufferv5.putInt(0) 109 | bufferv5.putInt(0) 110 | bufferv5.putInt(0) 111 | bufferv5.putInt(0) 112 | bufferv5.order(ByteOrder.BIG_ENDIAN) 113 | bufferv5.putInt(-0x1000000) 114 | bufferv5.putInt(0x00FF0000) 115 | bufferv5.putInt(0x0000FF00) 116 | bufferv5.putInt(0x000000FF) 117 | bufferv5.order(ByteOrder.LITTLE_ENDIAN) 118 | 119 | //BGRs 120 | bufferv5.put(0x42.toByte()) 121 | bufferv5.put(0x47.toByte()) 122 | bufferv5.put(0x52.toByte()) 123 | bufferv5.put(0x73.toByte()) 124 | for (i in bufferv5.position() until hdrSizev5) { 125 | bufferv5.put(0.toByte()) 126 | } 127 | 128 | //Image data 129 | for (y in 0 until height) { 130 | for (x in 0 until width) { 131 | val argb: Int = this.getRGB(x, height - y - 1) 132 | val a = argb shr 24 and 0xFF 133 | var r = argb shr 16 and 0xFF 134 | var g = argb shr 8 and 0xFF 135 | var b = argb and 0xFF 136 | r = r * a / 0xFF 137 | g = g * a / 0xFF 138 | b = b * a / 0xFF 139 | bufferv5.putInt(a shl 24 or (r shl 16) or (g shl 8) or b) 140 | } 141 | } 142 | bufferv5.flip() 143 | val clip = Toolkit.getDefaultToolkit().systemClipboard 144 | val dt = DataTransferer.getInstance() 145 | val f: Field = dt.javaClass.getDeclaredField("CF_DIB") 146 | f.isAccessible = true 147 | val format: Long = f.getLong(null) 148 | val openClipboard: Method = clip.javaClass.getDeclaredMethod("openClipboard", SunClipboard::class.java) 149 | openClipboard.isAccessible = true 150 | openClipboard.invoke(clip, clip) 151 | val publishClipboardData: Method = clip.javaClass.getDeclaredMethod( 152 | "publishClipboardData", 153 | Long::class.javaPrimitiveType, 154 | ByteArray::class.java 155 | ) 156 | publishClipboardData.isAccessible = true 157 | val arr: ByteArray = buffer.array() 158 | publishClipboardData.invoke(clip, format, arr) 159 | val closeClipboard: Method = clip.javaClass.getDeclaredMethod("closeClipboard") 160 | closeClipboard.isAccessible = true 161 | closeClipboard.invoke(clip) 162 | return 163 | } catch (e: Exception) { 164 | e.printStackTrace() 165 | } 166 | } 167 | val pixels: IntArray = 168 | this.getRGB(0, 0, this.width, this.height, null, 0, this.width) 169 | val newImage = BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB) 170 | newImage.setRGB(0, 0, newImage.width, newImage.height, pixels, 0, newImage.width) 171 | 172 | try { 173 | IOUtils.copyImageToClipboard(this) 174 | } catch (e: Exception) { 175 | e.printStackTrace() 176 | } 177 | } 178 | 179 | /** 180 | * Taken from https://github.com/Moulberry/HyChat 181 | */ 182 | fun Framebuffer.screenshot(file: File): BufferedImage { 183 | val w = this.framebufferWidth 184 | val h = this.framebufferHeight 185 | val i = w * h 186 | val pixelBuffer = BufferUtils.createIntBuffer(i) 187 | val pixelValues = IntArray(i) 188 | GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1) 189 | GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1) 190 | GlStateManager.bindTexture(this.framebufferTexture) 191 | GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer) 192 | pixelBuffer[pixelValues] //Load buffer into array 193 | TextureUtil.processPixelValues(pixelValues, w, h) //Flip vertically 194 | val bufferedimage = BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) 195 | val j = this.framebufferTextureHeight - this.framebufferHeight 196 | for (k in j until this.framebufferTextureHeight) { 197 | for (l in 0 until this.framebufferWidth) { 198 | bufferedimage.setRGB(l, k - j, pixelValues[k * this.framebufferTextureWidth + l]) 199 | } 200 | } 201 | if (ChattingConfig.copyMode != 1) { 202 | try { 203 | file.parentFile.mkdirs() 204 | ImageIO.write(bufferedimage, "png", file) 205 | } catch (e: Exception) { 206 | e.printStackTrace() 207 | } 208 | } 209 | return bufferedimage 210 | } 211 | /*/ 212 | private val timePattern = Regex("\\[\\d+:\\d+:\\d+]") 213 | private var lastLines = mutableListOf() 214 | fun timestampPre() { 215 | if (!ChattingConfig.showTimestampHover) return 216 | val drawnChatLines = (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines 217 | val chatLine = getChatLineOverMouse(UMouse.getTrueX().roundToInt(), UMouse.getTrueY().roundToInt()) 218 | 219 | lastLines.clear() 220 | for (line in drawnChatLines) { 221 | val chatComponent = line.chatComponent.createCopy() 222 | val newline = ChatLine(line.updatedCounter, chatComponent, line.chatLineID) 223 | lastLines.add(newline) 224 | } 225 | 226 | drawnChatLines.map { 227 | if (it != chatLine) it.chatComponent.siblings.removeAll { itt -> 228 | timePattern.find(ChatColor.stripControlCodes(itt.unformattedText)!!) != null 229 | } 230 | } 231 | } 232 | 233 | fun timestampPost() { 234 | if (!ChattingConfig.showTimestampHover) return 235 | val drawnChatLines = (Minecraft.getMinecraft().ingameGUI.chatGUI as GuiNewChatAccessor).drawnChatLines 236 | drawnChatLines.clear() 237 | drawnChatLines.addAll(lastLines) 238 | } 239 | 240 | private fun getChatLineOverMouse(mouseX: Int, mouseY: Int): ChatLine? { 241 | val chat = Minecraft.getMinecraft().ingameGUI.chatGUI 242 | if (!chat.chatOpen) return null 243 | val scaledResolution = ScaledResolution(Minecraft.getMinecraft()) 244 | val i = scaledResolution.scaleFactor 245 | val f = chat.chatScale 246 | val j = MathHelper.floor_float((mouseX / i - 3).toFloat() / f) 247 | val k = MathHelper.floor_float((mouseY / i - 27).toFloat() / f) 248 | if (j < 0 || k < 0) return null 249 | val drawnChatLines = (chat as GuiNewChatAccessor).drawnChatLines 250 | val l = chat.lineCount.coerceAtMost(drawnChatLines.size) 251 | if (j <= MathHelper.floor_float(chat.chatWidth.toFloat() / f) && k < fontRenderer.FONT_HEIGHT * l + l) { 252 | val m = k / Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT + chat.scrollPos 253 | if (m >= 0 && m < drawnChatLines.size) 254 | return drawnChatLines[m] 255 | } 256 | return null 257 | } 258 | 259 | */ -------------------------------------------------------------------------------- /src/main/resources/assets/chatting/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Chatting/f543d5e0a2aa2107f48cdc01f0e67e65b6d4d862/src/main/resources/assets/chatting/copy.png -------------------------------------------------------------------------------- /src/main/resources/assets/chatting/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Chatting/f543d5e0a2aa2107f48cdc01f0e67e65b6d4d862/src/main/resources/assets/chatting/delete.png -------------------------------------------------------------------------------- /src/main/resources/assets/chatting/reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Chatting/f543d5e0a2aa2107f48cdc01f0e67e65b6d4d862/src/main/resources/assets/chatting/reply.png -------------------------------------------------------------------------------- /src/main/resources/assets/chatting/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Chatting/f543d5e0a2aa2107f48cdc01f0e67e65b6d4d862/src/main/resources/assets/chatting/screenshot.png -------------------------------------------------------------------------------- /src/main/resources/assets/chatting/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Chatting/f543d5e0a2aa2107f48cdc01f0e67e65b6d4d862/src/main/resources/assets/chatting/search.png -------------------------------------------------------------------------------- /src/main/resources/chatting_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "${id}", 4 | "name": "${name}", 5 | "description": "A chat mod adding utilities such as extremely customizable chat tabs, chat shortcuts, chat screenshots, and message copying.", 6 | "version": "${version}", 7 | "mcversion": "1.8.9", 8 | "url": "", 9 | "updateUrl": "", 10 | "authorList": [ 11 | "Polyfrost" 12 | ], 13 | "credits": "Mo2men#2806 for chat icons, Pablo", 14 | "logoFile": "", 15 | "screenshots": [], 16 | "dependencies": [] 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /src/main/resources/mixins.chatting.json: -------------------------------------------------------------------------------- 1 | { 2 | "compatibilityLevel": "JAVA_8", 3 | "minVersion": "0.7", 4 | "package": "org.polyfrost.chatting.mixin", 5 | "refmap": "mixins.${id}.refmap.json", 6 | "verbose": true, 7 | "client": [ 8 | "ChatLineMixin", 9 | "ChatStyleMixin", 10 | "ClientCommandHandlerMixin", 11 | "EntityPlayerSPMixin", 12 | "EntityRendererMixin", 13 | "GameSettingsMixin", 14 | "GuiChatMixin", 15 | "GuiContainerCreativeMixin", 16 | "GuiIngameForgeAccessor", 17 | "GuiIngameForgeMixin", 18 | "GuiNewChatAccessor", 19 | "GuiNewChatMixin", 20 | "GuiNewChatMixin_ChatHeight", 21 | "GuiNewChatMixin_ChatPeek", 22 | "GuiNewChatMixin_ChatSearching", 23 | "GuiNewChatMixin_ChatTabs", 24 | "GuiNewChatMixin_Movable", 25 | "GuiNewChatMixin_Scrolling", 26 | "GuiNewChatMixin_SmoothMessages", 27 | "GuiNewChatMixin_TextRendering", 28 | "GuiTextFieldMixin", 29 | "GuiUtilsMixin", 30 | "HUDUtilsMixin", 31 | "InventoryPlayerMixin", 32 | "compat.ChatPeekMixin_SkyHanni", 33 | "compat.ChatTabsMixin_SkytilsCopyChat", 34 | "compat.EssentialKeybindingRegistryMixin" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /versions/mainProject: -------------------------------------------------------------------------------- 1 | 1.8.9-forge --------------------------------------------------------------------------------