├── .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 | 
3 | 
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 | 
17 |
18 | ## Screenshots
19 | 
20 |
21 | ## Customize appearance
22 |
23 |
24 |
25 |  |
26 |  |
27 |
28 |
29 |
30 | ## OneConfig support
31 | 
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 |
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
--------------------------------------------------------------------------------