Compare commits
227
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23e508138b | ||
|
|
593b9f4760 | ||
|
|
5b44caa358 | ||
|
|
28d71e63a9 | ||
|
|
84addc7996 | ||
|
|
3f8d68619a | ||
|
|
d941334fa3 | ||
|
|
9c36d33cb2 | ||
|
|
6b03a1441a | ||
|
|
f40d3026e6 | ||
|
|
2ea122c4a9 | ||
|
|
dfce271c37 | ||
|
|
d40724205c | ||
|
|
55afd1e436 | ||
|
|
eb3e3d61db | ||
|
|
5da0e3c428 | ||
|
|
f464d10971 | ||
|
|
6e0bc22c17 | ||
|
|
bb95999112 | ||
|
|
9c0f92d034 | ||
|
|
9d04958ff2 | ||
|
|
cfe0d4a97f | ||
|
|
0995484c09 | ||
|
|
ec2254cf74 | ||
|
|
71ae403188 | ||
|
|
6689919b3b | ||
|
|
246e49e521 | ||
|
|
eb065b20d7 | ||
|
|
038c859133 | ||
|
|
434afa7f62 | ||
|
|
fd492b91a0 | ||
|
|
48a41af3b0 | ||
|
|
6da9eb55ba | ||
|
|
22efd8b6e0 | ||
|
|
b71d779900 | ||
|
|
6c3dbd90a0 | ||
|
|
1b16098121 | ||
|
|
092fcfb009 | ||
|
|
008a0927b6 | ||
|
|
3c2f75e438 | ||
|
|
09c354337d | ||
|
|
dc6387cc27 | ||
|
|
bff18a05d1 | ||
|
|
6717d7e901 | ||
|
|
bbf4b17494 | ||
|
|
68b4bfc439 | ||
|
|
77f5350496 | ||
|
|
3c1914c30a | ||
|
|
ae8ec4b4ef | ||
|
|
bdd421e1da | ||
|
|
7b480d1b8b | ||
|
|
2a425335f0 | ||
|
|
02f8b486f3 | ||
|
|
f0966c86f3 | ||
|
|
65c6c2a284 | ||
|
|
9fe05722f9 | ||
|
|
61be7c317f | ||
|
|
df35497af2 | ||
|
|
8614e1bdd1 | ||
|
|
c5c38247cc | ||
|
|
f9c0d3fe58 | ||
|
|
44c0b69c4d | ||
|
|
0cfa7b81ef | ||
|
|
9caf47d81b | ||
|
|
3c27d3b377 | ||
|
|
66b96102f8 | ||
|
|
7fc05df240 | ||
|
|
d32f124974 | ||
|
|
fe4d999643 | ||
|
|
cd84866ee8 | ||
|
|
cd5cac7f69 | ||
|
|
377031c0a9 | ||
|
|
85d4a1de38 | ||
|
|
44d7aa3641 | ||
|
|
53732c5df3 | ||
|
|
265b44187b | ||
|
|
f46f9c474e | ||
|
|
049c92785d | ||
|
|
37ec729d1d | ||
|
|
2747ad366e | ||
|
|
7de3d5c9bc | ||
|
|
f952fa7095 | ||
|
|
e62da29a49 | ||
|
|
d3258edbc7 | ||
|
|
2096925a02 | ||
|
|
3db44bab85 | ||
|
|
bba12146ea | ||
|
|
4403a31c93 | ||
|
|
6ee2fc3b0e | ||
|
|
3b0ab53d86 | ||
|
|
7683e7b91e | ||
|
|
3ba6202b43 | ||
|
|
a49212ca64 | ||
|
|
88fe53b907 | ||
|
|
52f80281bd | ||
|
|
2f93822996 | ||
|
|
8d8cbc8b28 | ||
|
|
a7c5f0d3df | ||
|
|
60d83530ae | ||
|
|
569aecde4a | ||
|
|
9fd79aadd5 | ||
|
|
e031672d72 | ||
|
|
289a2cd78a | ||
|
|
aab4f02dc0 | ||
|
|
5b37131bfd | ||
|
|
79ceaf67a1 | ||
|
|
b0dbdeafa3 | ||
|
|
3bc21a5b30 | ||
|
|
953182e56a | ||
|
|
39cea14183 | ||
|
|
8baf28fcfe | ||
|
|
619c4bce21 | ||
|
|
cc80fc5239 | ||
|
|
f9039fdf67 | ||
|
|
bb93d72f66 | ||
|
|
86be2eb956 | ||
|
|
eb40903372 | ||
|
|
039d2cd032 | ||
|
|
365063e8d5 | ||
|
|
b43b076ca0 | ||
|
|
71a7721c52 | ||
|
|
2bcad83409 | ||
|
|
6750abcc11 | ||
|
|
46d3408be9 | ||
|
|
8015a23342 | ||
|
|
4e6bdce019 | ||
|
|
68519ac00c | ||
|
|
50da953dc4 | ||
|
|
f5776450f0 | ||
|
|
3b3283c6b0 | ||
|
|
3f06efe8f4 | ||
|
|
2d021e886f | ||
|
|
96f201cdb4 | ||
|
|
57c1d21407 | ||
|
|
5d8068c964 | ||
|
|
418c47f483 | ||
|
|
08c001afbb | ||
|
|
052a2ac06c | ||
|
|
38041b01cf | ||
|
|
fdda25464b | ||
|
|
6bfdf66cdd | ||
|
|
82f8d0a550 | ||
|
|
81bfe41e25 | ||
|
|
db0e627f5c | ||
|
|
da970354e8 | ||
|
|
045e5cd5da | ||
|
|
e517377bdd | ||
|
|
520b5221b5 | ||
|
|
c567b282a5 | ||
|
|
5f19abebbf | ||
|
|
58eda56f37 | ||
|
|
50cab402d4 | ||
|
|
fa7161d371 | ||
|
|
252808a467 | ||
|
|
31e9a8d56e | ||
|
|
ba1b68d8fa | ||
|
|
dcd82fa58e | ||
|
|
4132a05c8f | ||
|
|
47f8dd5e22 | ||
|
|
9b30ec7016 | ||
|
|
d10f229ffa | ||
|
|
061ed674e3 | ||
|
|
5b5f781095 | ||
|
|
48f7aefbf6 | ||
|
|
8f111badf6 | ||
|
|
687bda68a1 | ||
|
|
8e2c986ae4 | ||
|
|
faa95e664a | ||
|
|
c9230da168 | ||
|
|
7002fbec0e | ||
|
|
496d27b861 | ||
|
|
3c37b80bc2 | ||
|
|
920657ddd3 | ||
|
|
32035a2f7d | ||
|
|
c230f4f5ec | ||
|
|
50d1028e53 | ||
|
|
780cb9ba9a | ||
|
|
65df9e9a5e | ||
|
|
66f8a13faa | ||
|
|
1726542c84 | ||
|
|
71f6597624 | ||
|
|
45f8c65549 | ||
|
|
82bb2859c9 | ||
|
|
4d87c4e694 | ||
|
|
5c7283e513 | ||
|
|
ee300b2bb2 | ||
|
|
3b1f54a5b0 | ||
|
|
e39c877b91 | ||
|
|
4bb4e46487 | ||
|
|
d56bdfea8e | ||
|
|
afca9c01b7 | ||
|
|
40b0177cf2 | ||
|
|
db27de497a | ||
|
|
53fd88df18 | ||
|
|
8e477f8852 | ||
|
|
1f93f6cf5a | ||
|
|
a4c60dd5b0 | ||
|
|
72766a47ac | ||
|
|
4b7238e863 | ||
|
|
52456f2ca8 | ||
|
|
a464635075 | ||
|
|
8089deb172 | ||
|
|
ca9626f340 | ||
|
|
0d3a27b5e7 | ||
|
|
b6eca22097 | ||
|
|
4de4605ee8 | ||
|
|
8018caf26b | ||
|
|
39266df45e | ||
|
|
25046b53f3 | ||
|
|
8e89a850b3 | ||
|
|
ccaa79e89b | ||
|
|
30b375aa6f | ||
|
|
6d2cad1c7a | ||
|
|
720ee3d8bd | ||
|
|
b4b348a54c | ||
|
|
a3be72c443 | ||
|
|
1c553d14bf | ||
|
|
cd967a907c | ||
|
|
ba04c45b75 | ||
|
|
ecb07ce730 | ||
|
|
6cd10240fc | ||
|
|
d162db3464 | ||
|
|
7e3b6cf7f8 | ||
|
|
6fb38e4f9b | ||
|
|
997fbbe9a9 | ||
|
|
01a8ea15a0 | ||
|
|
08cee2172c |
@@ -0,0 +1,45 @@
|
|||||||
|
name: Automate Gradlew Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "21"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
gradle-home-cache-cleanup: false
|
||||||
|
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Build (release)
|
||||||
|
run: ./gradlew clean build -x test --no-daemon --stacktrace
|
||||||
|
|
||||||
|
- name: Remove sources JARs
|
||||||
|
run: |
|
||||||
|
rm -f build/libs/*sources*.jar
|
||||||
|
echo "Removed sources JARs from build/libs"
|
||||||
|
|
||||||
|
- name: Upload build artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: saturnclient-latest
|
||||||
|
path: build/libs/saturnclient-*.jar
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
name: Push Builds
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "21"
|
||||||
|
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
gradle-home-cache-cleanup: false
|
||||||
|
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: ./gradlew clean build -x test --no-daemon --stacktrace
|
||||||
|
|
||||||
|
- name: Remove sources JARs
|
||||||
|
run: |
|
||||||
|
rm -f build/libs/*sources*.jar
|
||||||
|
echo "Removed sources JARs from build/libs"
|
||||||
|
|
||||||
|
- name: Create timestamped JAR
|
||||||
|
run: |
|
||||||
|
TIMESTAMP=$(date -u +"%Y%m%d-%H%M%S")
|
||||||
|
MAIN_JAR=$(ls build/libs/saturnclient-*.jar | head -1)
|
||||||
|
cp "$MAIN_JAR" "saturnclient-${TIMESTAMP}.jar"
|
||||||
|
echo "Created: saturnclient-${TIMESTAMP}.jar"
|
||||||
|
|
||||||
|
- name: Upload timestamped artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: saturnclient-${{ github.run_number }}
|
||||||
|
path: saturnclient-*.jar
|
||||||
|
if-no-files-found: error
|
||||||
@@ -8,11 +8,12 @@
|
|||||||
<b>Saturn Client is a free and open source Minecraft client</b>
|
<b>Saturn Client is a free and open source Minecraft client</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
- Cloak rendering: `IIpho3nix` & `Dhyan Blum`
|
- Cloak rendering: `IIpho3nix` & `Dhyan Blum`
|
||||||
- Who Am I: `FokshaWasTaken`
|
- Who Am I: `FokshaWasTaken`
|
||||||
- Panton font (ui): `Ivan Petrov` & `Fontfabric`
|
|
||||||
- Panorama: https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
|
- Panorama: https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
|
||||||
|
|
||||||
If any of the code or assets are in your name and you want to remove them, please submit an issue, we are still in alpha to have any artists so we would also appreciate if you could help us make some cosmetics
|
If any of the code or assets are in your name and you want to remove them, please submit an issue, we are still in alpha to have any artists so we would also appreciate if you could help us make some cosmetics
|
||||||
|
|||||||
+135
-31
@@ -1,44 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* ================================================================
|
||||||
|
* Saturn Client - Fabric Mod Build Configuration
|
||||||
|
* ================================================================
|
||||||
|
*
|
||||||
|
* This build file configures:
|
||||||
|
* - Fabric Loom (Minecraft mod development)
|
||||||
|
* - Dependency management
|
||||||
|
* - Resource processing
|
||||||
|
* - Java toolchain (Java 21)
|
||||||
|
* - Publishing setup
|
||||||
|
*
|
||||||
|
* All version values are defined in gradle.properties.
|
||||||
|
* ================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
// Fabric mod development plugin
|
||||||
id 'fabric-loom' version '1.9-SNAPSHOT'
|
id 'fabric-loom' version '1.9-SNAPSHOT'
|
||||||
|
|
||||||
|
// Maven publishing support
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ================================================================
|
||||||
|
* Project Metadata
|
||||||
|
* ================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
version = project.mod_version
|
version = project.mod_version
|
||||||
group = project.maven_group
|
group = project.maven_group
|
||||||
|
|
||||||
repositories {}
|
/*
|
||||||
|
* ================================================================
|
||||||
|
* Repositories
|
||||||
|
* ================================================================
|
||||||
|
* All dependency sources.
|
||||||
|
*/
|
||||||
|
repositories {
|
||||||
|
// Fabric
|
||||||
|
maven { url 'https://maven.fabricmc.net/' }
|
||||||
|
|
||||||
|
// JitPack (GitHub releases)
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
|
|
||||||
|
// Parchment mappings (if needed later)
|
||||||
|
maven { url 'https://maven.parchmentmc.org' }
|
||||||
|
|
||||||
|
// Special Model Loader
|
||||||
|
maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" }
|
||||||
|
|
||||||
|
// Player Animation Library
|
||||||
|
maven { name = "KosmX Maven"; url = 'https://maven.kosmx.dev/' }
|
||||||
|
|
||||||
|
// Central repository
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ================================================================
|
||||||
|
* Dependencies
|
||||||
|
* ================================================================
|
||||||
|
*/
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Minecraft Core
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||||
|
|
||||||
// Switch back to Yarn
|
// Yarn mappings (named mappings for dev environment)
|
||||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||||
|
|
||||||
|
// Fabric Loader
|
||||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||||
|
|
||||||
|
// Fabric API
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||||
|
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Rendering / Model Libraries
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Special Model Loader (included inside your mod jar)
|
||||||
modApi "dev.felnull:special-model-loader:1.4.0+pre.1"
|
modApi "dev.felnull:special-model-loader:1.4.0+pre.1"
|
||||||
include "dev.felnull:special-model-loader:1.4.0+pre.1"
|
include "dev.felnull:special-model-loader:1.4.0+pre.1"
|
||||||
|
|
||||||
include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}")
|
// SVG Rendering Library
|
||||||
//You might want bendy-lib. playerAnimator will wrap it.
|
include implementation("com.formdev:svgSalamander:1.1.4")
|
||||||
// include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}")
|
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Animation
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Player Animation Library (bundled into your mod)
|
||||||
|
include modImplementation(
|
||||||
|
"dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Optional: BendyLib (only if you directly use it)
|
||||||
|
// include modRuntimeOnly(
|
||||||
|
// "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}"
|
||||||
|
// )
|
||||||
|
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// External Libraries
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Session Java (auth/session handling)
|
||||||
|
include implementation("com.github.selimaj-dev:session-java:0.1.6")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
/*
|
||||||
maven { url "https://maven.parchmentmc.org" }
|
* ================================================================
|
||||||
maven {
|
* Resource Processing
|
||||||
name = "FelNull Maven"
|
* ================================================================
|
||||||
url = "https://maven.felnull.dev"
|
* Injects version values into fabric.mod.json.
|
||||||
}
|
*/
|
||||||
|
|
||||||
maven {
|
|
||||||
name "KosmX's maven"
|
|
||||||
url 'https://maven.kosmx.dev/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
inputs.property "version", project.version
|
inputs.property "version", project.version
|
||||||
inputs.property "minecraft_version", project.minecraft_version
|
inputs.property "minecraft_version", project.minecraft_version
|
||||||
@@ -46,38 +128,58 @@ processResources {
|
|||||||
filteringCharset "UTF-8"
|
filteringCharset "UTF-8"
|
||||||
|
|
||||||
filesMatching("fabric.mod.json") {
|
filesMatching("fabric.mod.json") {
|
||||||
expand "version": project.version,
|
expand(
|
||||||
|
"version": project.version,
|
||||||
"minecraft_version": project.minecraft_version,
|
"minecraft_version": project.minecraft_version,
|
||||||
"loader_version": project.loader_version
|
"loader_version": project.loader_version
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ================================================================
|
||||||
|
* Java Configuration
|
||||||
|
* ================================================================
|
||||||
|
* Targeting Java 21.
|
||||||
|
*/
|
||||||
def targetJavaVersion = 21
|
def targetJavaVersion = 21
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
it.options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
options.release.set(targetJavaVersion)
|
||||||
it.options.release.set(targetJavaVersion)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
|
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||||
if (JavaVersion.current() < javaVersion) {
|
|
||||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
|
||||||
}
|
|
||||||
archivesBaseName = project.archives_base_name
|
archivesBaseName = project.archives_base_name
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ================================================================
|
||||||
|
* Jar Packaging
|
||||||
|
* ================================================================
|
||||||
|
* Includes LICENSE and NOTICE inside the final jar.
|
||||||
|
*/
|
||||||
jar {
|
jar {
|
||||||
from("LICENSE") {
|
from("LICENSE") {
|
||||||
rename { "${it}_${project.archivesBaseName}"}
|
rename { "${it}_${project.archivesBaseName}" }
|
||||||
}
|
}
|
||||||
from("NOTICE") {
|
from("NOTICE") {
|
||||||
rename { "${it}_${project.archivesBaseName}"}
|
rename { "${it}_${project.archivesBaseName}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ================================================================
|
||||||
|
* Publishing
|
||||||
|
* ================================================================
|
||||||
|
* Configured but no remote repository defined yet.
|
||||||
|
*/
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
mavenJava(MavenPublication) {
|
mavenJava(MavenPublication) {
|
||||||
@@ -85,5 +187,7 @@ publishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {}
|
repositories {
|
||||||
|
// Add publishing repository here when needed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
+7
-2
@@ -1,12 +1,17 @@
|
|||||||
# Done to increase the memory available to gradle.
|
# Done to increase the memory available to gradle.
|
||||||
org.gradle.jvmargs=-Xmx1G
|
org.gradle.jvmargs=-Xmx1G -XX:+UseParallelGC
|
||||||
|
|
||||||
|
org.gradle.daemon=true
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.configureondemand=true
|
||||||
|
org.gradle.caching=true
|
||||||
|
|
||||||
minecraft_version=1.21.4
|
minecraft_version=1.21.4
|
||||||
yarn_mappings=1.21.4+build.7
|
yarn_mappings=1.21.4+build.7
|
||||||
loader_version=0.16.10
|
loader_version=0.16.10
|
||||||
fabric_version=0.114.0+1.21.4
|
fabric_version=0.114.0+1.21.4
|
||||||
|
|
||||||
mod_version = 0.0.3-beta0
|
mod_version = 0.0.7-beta1
|
||||||
maven_group = org.saturnclient
|
maven_group = org.saturnclient
|
||||||
archives_base_name = saturnclient
|
archives_base_name = saturnclient
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@@ -2,8 +2,8 @@ package org.saturnclient.modules;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.NamedProperty;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property.NamedProperty;
|
||||||
|
|
||||||
public class ModDimensions {
|
public class ModDimensions {
|
||||||
public Property<Integer> x = Property.integer(0);
|
public Property<Integer> x = Property.integer(0);
|
||||||
@@ -20,17 +20,17 @@ public class ModDimensions {
|
|||||||
|
|
||||||
public NamedProperty<Map<String, Property<?>>> prop() {
|
public NamedProperty<Map<String, Property<?>>> prop() {
|
||||||
return Property.namespace(Map.of(
|
return Property.namespace(Map.of(
|
||||||
"X", x,
|
"X", x,
|
||||||
"Y", y,
|
"Y", y,
|
||||||
"Scale", scale,
|
"Scale", scale,
|
||||||
"Background Color", bgColor,
|
"Background Color", bgColor,
|
||||||
"Foreground Color", fgColor,
|
"Foreground Color", fgColor,
|
||||||
"Corner Radius", radius,
|
"Corner Radius", radius,
|
||||||
"Font", font
|
"Font", font)).named("In-Game Display");
|
||||||
)).named("In-Game Display");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModDimensions() {}
|
public ModDimensions() {
|
||||||
|
}
|
||||||
|
|
||||||
public ModDimensions(int width, int height) {
|
public ModDimensions(int width, int height) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
|
|||||||
@@ -1,69 +1,60 @@
|
|||||||
package org.saturnclient.modules;
|
package org.saturnclient.modules;
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
|
import java.util.ArrayList;
|
||||||
import net.minecraft.client.font.TextRenderer;
|
import java.util.List;
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.mods.*;
|
import org.saturnclient.modules.mods.*;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.menus.HudEditor;
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
public class ModManager {
|
public class ModManager {
|
||||||
public static Module[] MODS = {
|
public static final List<Module> ENABLED_MODS = new ArrayList<>();
|
||||||
new Crosshair(),
|
public static final Module[] ALL_MODS = {
|
||||||
new AutoSprint(),
|
new Crosshair(),
|
||||||
new ArmorDisplay(),
|
new AutoSprint(),
|
||||||
new Fps(),
|
new ArmorDisplay(),
|
||||||
new PotionDisplay(),
|
new Fps(),
|
||||||
new Coordinates(),
|
new StatusEffects(),
|
||||||
new Freelook(),
|
new Coordinates(),
|
||||||
new Keystrokes()
|
new Freelook(),
|
||||||
|
new Keystrokes(),
|
||||||
|
new Fullbright(),
|
||||||
|
new NoFog(),
|
||||||
|
new DayCounter(),
|
||||||
|
new Speedometer(),
|
||||||
|
new Zoom(),
|
||||||
|
new HealthDisplay(),
|
||||||
|
new Nametags(),
|
||||||
|
new Ping(),
|
||||||
|
new Tps(),
|
||||||
|
new Clock()
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
// Pre-filter enabled modules to avoid checking every tick
|
||||||
|
updateEnabledModules();
|
||||||
|
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
for (Module m : MODS) {
|
// Only iterate over enabled modules
|
||||||
if (m.isEnabled()) {
|
for (Module m : ENABLED_MODS) {
|
||||||
m.tick();
|
m.tick();
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
HudRenderCallback.EVENT.register((context, _o) -> {
|
|
||||||
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
|
||||||
|
|
||||||
if (textRenderer != null &&
|
|
||||||
!(SaturnClient.client.currentScreen instanceof HudEditor)) {
|
|
||||||
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
|
||||||
((DrawContextAccessor) context).getVertexConsumers());
|
|
||||||
|
|
||||||
for (Module m : MODS) {
|
|
||||||
if (m instanceof HudMod && m.isEnabled()) {
|
|
||||||
ModDimensions dim = ((HudMod) m).getDimensions();
|
|
||||||
|
|
||||||
renderScope.matrices.push();
|
|
||||||
|
|
||||||
renderScope.matrices.translate(dim.x.value, (float) dim.y.value, 0);
|
|
||||||
|
|
||||||
renderScope.matrices.scale(dim.scale.value, dim.scale.value, 1.0f);
|
|
||||||
|
|
||||||
if (dim.renderBackground) {
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, dim.width, dim.height, dim.radius.value, dim.bgColor.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
|
||||||
|
|
||||||
((HudMod) m).renderHud(renderScope);
|
|
||||||
|
|
||||||
renderScope.matrices.pop();
|
|
||||||
} else {
|
|
||||||
m.render(renderScope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized void updateEnabledModules() {
|
||||||
|
ENABLED_MODS.clear();
|
||||||
|
for (Module m : ALL_MODS) {
|
||||||
|
if (m.isEnabled()) {
|
||||||
|
ENABLED_MODS.add(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Module[] getAllMods() {
|
||||||
|
return ALL_MODS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void refreshEnabledModules() {
|
||||||
|
updateEnabledModules();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.saturnclient.modules;
|
package org.saturnclient.modules;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
|
||||||
import org.saturnclient.saturnclient.config.NamedProperty;
|
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
import org.saturnclient.ui2.resources.Textures;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property.NamedProperty;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
@@ -19,12 +19,20 @@ public abstract class Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(RenderScope scope) {}
|
public void render(RenderScope scope) {
|
||||||
public void tick() {}
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
}
|
||||||
|
|
||||||
public abstract boolean isEnabled();
|
public abstract boolean isEnabled();
|
||||||
|
|
||||||
public abstract void setEnabled(boolean e);
|
public final void setEnabled(boolean e) {
|
||||||
|
onEnabled(e);
|
||||||
|
ModManager.updateEnabledModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onEnabled(boolean e);
|
||||||
|
|
||||||
public final String getName() {
|
public final String getName() {
|
||||||
return details.name;
|
return details.name;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
|
|||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
|
||||||
import net.minecraft.entity.EquipmentSlot;
|
import net.minecraft.entity.EquipmentSlot;
|
||||||
@@ -14,22 +14,31 @@ import net.minecraft.item.Items;
|
|||||||
|
|
||||||
public class ArmorDisplay extends Module implements HudMod {
|
public class ArmorDisplay extends Module implements HudMod {
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
private static ModDimensions dimensions = new ModDimensions(40, 60);
|
private static ModDimensions dimensions = new ModDimensions(40, 75);
|
||||||
|
private static Property<Boolean> useMainHand = Property.bool(false);
|
||||||
|
|
||||||
public ArmorDisplay() {
|
public ArmorDisplay() {
|
||||||
super(new ModuleDetails("Armor Display", "armor")
|
super(new ModuleDetails("Armor Display", "armor")
|
||||||
.description("Displays armor durability")
|
.description("Displays armor durability")
|
||||||
.version("v0.1.0")
|
.version("v0.1.0")
|
||||||
.tags("Utility"),
|
.tags("Utility"),
|
||||||
enabled.named("Enabled"),
|
enabled.named("Enabled"),
|
||||||
dimensions.prop());
|
useMainHand.named("Use Main Hand"),
|
||||||
|
dimensions.prop());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renderArmor(RenderScope scope, ItemStack helmet, ItemStack chestplate, ItemStack leggings, ItemStack boots) {
|
public void renderArmor(RenderScope scope, ItemStack mainHand, ItemStack helmet, ItemStack chestplate,
|
||||||
|
ItemStack leggings, ItemStack boots) {
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
|
||||||
|
if (useMainHand.value && !mainHand.isEmpty()) {
|
||||||
|
scope.drawItem(mainHand, 0, 15 * row);
|
||||||
|
renderHealth(scope, row, mainHand.getMaxDamage(), mainHand.getDamage());
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
|
||||||
if (!helmet.isEmpty()) {
|
if (!helmet.isEmpty()) {
|
||||||
scope.drawItem(helmet, 0, 0);
|
scope.drawItem(helmet, 0, 15 * row);
|
||||||
renderHealth(scope, row, helmet.getMaxDamage(), helmet.getDamage());
|
renderHealth(scope, row, helmet.getMaxDamage(), helmet.getDamage());
|
||||||
row++;
|
row++;
|
||||||
}
|
}
|
||||||
@@ -53,23 +62,27 @@ public class ArmorDisplay extends Module implements HudMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void renderHealth(RenderScope scope, int i, int max_damage, int damage) {
|
public void renderHealth(RenderScope scope, int i, int max_damage, int damage) {
|
||||||
if (max_damage > 0 ) {
|
if (max_damage > 0) {
|
||||||
scope.drawText(0.5f, "" + (max_damage - damage), 17, (15 * i) + 3, dimensions.font.value, dimensions.fgColor.value);
|
scope.drawText(0.5f, "" + (max_damage - damage), 17, (15 * i) + 3, dimensions.font.value,
|
||||||
|
dimensions.fgColor.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderHud(RenderScope scope) {
|
public void renderHud(RenderScope scope) {
|
||||||
renderArmor(scope,
|
renderArmor(scope,
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.HEAD),
|
SaturnClient.client.player.getEquippedStack(EquipmentSlot.MAINHAND),
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.CHEST),
|
SaturnClient.client.player.getEquippedStack(EquipmentSlot.HEAD),
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.LEGS),
|
SaturnClient.client.player.getEquippedStack(EquipmentSlot.CHEST),
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.FEET));
|
SaturnClient.client.player.getEquippedStack(EquipmentSlot.LEGS),
|
||||||
|
SaturnClient.client.player.getEquippedStack(EquipmentSlot.FEET));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderDummy(RenderScope scope) {
|
public void renderDummy(RenderScope scope) {
|
||||||
renderArmor(scope, new ItemStack(Items.DIAMOND_HELMET), new ItemStack(Items.DIAMOND_CHESTPLATE), new ItemStack(Items.DIAMOND_LEGGINGS), new ItemStack(Items.DIAMOND_BOOTS));
|
renderArmor(scope, new ItemStack(Items.DIAMOND_SWORD), new ItemStack(Items.DIAMOND_HELMET),
|
||||||
|
new ItemStack(Items.DIAMOND_CHESTPLATE),
|
||||||
|
new ItemStack(Items.DIAMOND_LEGGINGS), new ItemStack(Items.DIAMOND_BOOTS));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -83,7 +96,7 @@ public class ArmorDisplay extends Module implements HudMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.util.Objects;
|
|||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
public class AutoSprint extends Module {
|
public class AutoSprint extends Module {
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
@@ -24,8 +24,12 @@ public class AutoSprint extends Module {
|
|||||||
if (SaturnClient.client.player == null || SaturnClient.client.getNetworkHandler() == null) {
|
if (SaturnClient.client.player == null || SaturnClient.client.getNetworkHandler() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SaturnClient.client.options.forwardKey.isPressed() && !SaturnClient.client.options.backKey.isPressed() && !SaturnClient.client.player.isSneaking() &&
|
if (SaturnClient.client.options.forwardKey.isPressed()
|
||||||
!SaturnClient.client.player.horizontalCollision) {
|
&& !SaturnClient.client.options.backKey.isPressed()
|
||||||
|
&& !SaturnClient.client.player.isSneaking()
|
||||||
|
&& !SaturnClient.client.player.horizontalCollision
|
||||||
|
&& !SaturnClient.client.player.isUsingItem()) {
|
||||||
|
|
||||||
Objects.requireNonNull(SaturnClient.client.player).setSprinting(true);
|
Objects.requireNonNull(SaturnClient.client.player).setSprinting(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +40,7 @@ public class AutoSprint extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.HudMod;
|
||||||
|
import org.saturnclient.modules.ModDimensions;
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
|
public class Clock extends Module implements HudMod {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static Property<Integer> format = Property.select(0, "24 hour", "12 hour");
|
||||||
|
private static Property<Boolean> seconds = Property.bool(false);
|
||||||
|
private static ModDimensions dimensions = new ModDimensions(60, Fonts.getHeight());
|
||||||
|
|
||||||
|
public Clock() {
|
||||||
|
super(new ModuleDetails("Clock", "clock")
|
||||||
|
.description("Displays the current real-world time")
|
||||||
|
.version("v0.1.0")
|
||||||
|
.tags("Utility"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
format.named("Format"),
|
||||||
|
seconds.named("Show seconds"),
|
||||||
|
dimensions.prop()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderDummy(RenderScope scope) {
|
||||||
|
renderClock(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderHud(RenderScope scope) {
|
||||||
|
renderClock(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderClock(RenderScope scope) {
|
||||||
|
String text = "";
|
||||||
|
LocalTime currentTime = LocalTime.now();
|
||||||
|
|
||||||
|
switch (format.value) {
|
||||||
|
case 0:
|
||||||
|
if (seconds.value) { text = currentTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")); }
|
||||||
|
else { text = currentTime.format(DateTimeFormatter.ofPattern("HH:mm")); }
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (seconds.value) { text = currentTime.format(DateTimeFormatter.ofPattern("hh:mm:ss a")); }
|
||||||
|
else { text = currentTime.format(DateTimeFormatter.ofPattern("hh:mm a")); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.drawText(text,
|
||||||
|
0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
dimensions.height = 18 * text.split("\n").length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModDimensions getDimensions() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
|
|||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ public class Coordinates extends Module implements HudMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.saturnclient.modules.mods;
|
|||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
import org.saturnclient.ui2.resources.Textures;
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ public class Crosshair extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.HudMod;
|
||||||
|
import org.saturnclient.modules.ModDimensions;
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
|
public class DayCounter extends Module implements HudMod {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static ModDimensions dimensions = new ModDimensions(40, 18);
|
||||||
|
long day = 0;
|
||||||
|
|
||||||
|
public DayCounter() {
|
||||||
|
super(new ModuleDetails("DayCounter", "day")
|
||||||
|
.description("Displays the number of days that have passed")
|
||||||
|
.version("v0.1.0")
|
||||||
|
.tags("Utility"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
dimensions.prop()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderDummy(RenderScope scope) {
|
||||||
|
renderDay(271, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderHud(RenderScope scope) {
|
||||||
|
day = (SaturnClient.client.world.getTimeOfDay() / 24000L);
|
||||||
|
renderDay(day, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderDay(long day, RenderScope scope) {
|
||||||
|
String text = "Day: " + day;
|
||||||
|
scope.drawText(text,
|
||||||
|
0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
dimensions.height = 18 * text.split("\n").length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModDimensions getDimensions() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
|
|||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ public class Fps extends Module implements HudMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
import net.minecraft.client.option.Perspective;
|
import net.minecraft.client.option.Perspective;
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ public class Freelook extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
|
public class Fullbright extends Module {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static Property<Integer> brightness = Property.integer(100);
|
||||||
|
|
||||||
|
public Fullbright() {
|
||||||
|
super(
|
||||||
|
new ModuleDetails("Fullbright", "fullbright")
|
||||||
|
.description("Allows you to see in the dark")
|
||||||
|
.tags("Camera")
|
||||||
|
.version("v0.1.0"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
brightness.named("Brightness %"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldOverrideBrightness() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getBrightnessValue() {
|
||||||
|
return brightness.value / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.HudMod;
|
||||||
|
import org.saturnclient.modules.ModDimensions;
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
|
||||||
|
public class HealthDisplay extends Module implements HudMod {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static Property<Integer> displayMode = Property.select(0, "Health", "Hearts");
|
||||||
|
private static Property<Integer> decimals = Property.select(0, "0", "1", "2");
|
||||||
|
private static ModDimensions dimensions = new ModDimensions(40, 18);
|
||||||
|
double speed = 0.;
|
||||||
|
Vec3d velocity;
|
||||||
|
double y = 0.;
|
||||||
|
|
||||||
|
public HealthDisplay() {
|
||||||
|
super(new ModuleDetails("Health display", "health")
|
||||||
|
.description("Displays your current health")
|
||||||
|
.version("v0.1.0")
|
||||||
|
.tags("Utility"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
displayMode.named("Display mode"),
|
||||||
|
decimals.named("Decimals"),
|
||||||
|
dimensions.prop()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderDummy(RenderScope scope) {
|
||||||
|
renderHealth(10, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderHud(RenderScope scope) {
|
||||||
|
float health = SaturnClient.client.player.getHealth();
|
||||||
|
switch (displayMode.value) {
|
||||||
|
case 1:
|
||||||
|
health = health / 2;
|
||||||
|
}
|
||||||
|
renderHealth(health, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderHealth(float health, RenderScope scope) {
|
||||||
|
String text = "";
|
||||||
|
|
||||||
|
switch (decimals.value) {
|
||||||
|
case 0:
|
||||||
|
text = String.format("%.0f ", health);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
text = String.format("%.1f ", health);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
text = String.format("%.2f ", health);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.drawText(text,
|
||||||
|
0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
dimensions.height = 18 * text.split("\n").length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModDimensions getDimensions() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,38 +4,44 @@ import org.saturnclient.modules.Module;
|
|||||||
import org.saturnclient.modules.HudMod;
|
import org.saturnclient.modules.HudMod;
|
||||||
import org.saturnclient.modules.ModDimensions;
|
import org.saturnclient.modules.ModDimensions;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
|
||||||
public class Keystrokes extends Module implements HudMod {
|
public class Keystrokes extends Module implements HudMod {
|
||||||
public static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
public static Property<Boolean> showMouse = Property.bool(true);
|
|
||||||
public static Property<Boolean> showSpace = Property.bool(false);
|
|
||||||
public static ModDimensions dimensions = new ModDimensions(78, 46);
|
|
||||||
private boolean w = false;
|
|
||||||
private boolean a = false;
|
|
||||||
private boolean s = false;
|
|
||||||
private boolean d = false;
|
|
||||||
private boolean lmb = false;
|
|
||||||
private boolean rmb = false;
|
|
||||||
private boolean space = false;
|
|
||||||
|
|
||||||
private static Property<Integer> clickBg = Property.color(0xFFCCCCCC);
|
private static final Property<Boolean> enabled = Property.bool(false);
|
||||||
private static Property<Integer> clickFg = Property.color(0xFFFFFFFF);
|
private static final Property<Boolean> showMouse = Property.bool(true);
|
||||||
|
private static final Property<Boolean> showSpace = Property.bool(false);
|
||||||
|
|
||||||
|
private static final Property<Integer> clickBg = Property.color(0xFFCCCCCC);
|
||||||
|
private static final Property<Integer> clickFg = Property.color(0xFFFFFFFF);
|
||||||
|
|
||||||
|
private static final ModDimensions dimensions = new ModDimensions(78, 54);
|
||||||
|
|
||||||
|
// Key states
|
||||||
|
private boolean w, a, s, d, lmb, rmb, space;
|
||||||
|
|
||||||
|
// Layout constants
|
||||||
|
private static final int KEY_SIZE = 24;
|
||||||
|
private static final int KEY_SPACING = 3;
|
||||||
|
private static final int MOUSE_WIDTH = 38;
|
||||||
|
private static final int MOUSE_HEIGHT = 24;
|
||||||
|
private static final int SPACE_HEIGHT = 19;
|
||||||
|
|
||||||
public Keystrokes() {
|
public Keystrokes() {
|
||||||
super(new ModuleDetails("Keystrokes", "keystrokes")
|
super(new ModuleDetails("Keystrokes", "keystrokes")
|
||||||
.description("Displays the keystrokes for movement")
|
.description("Displays movement keystrokes")
|
||||||
.version("v0.1.0")
|
.version("v0.2.0")
|
||||||
.tags("Utility"),
|
.tags("Utility"),
|
||||||
|
|
||||||
enabled.named("Enabled"),
|
enabled.named("Enabled"),
|
||||||
showMouse.named("Show mouse clicks"),
|
showMouse.named("Show mouse clicks"),
|
||||||
showSpace.named("Show space clicks"),
|
showSpace.named("Show space"),
|
||||||
dimensions.prop(),
|
dimensions.prop(),
|
||||||
clickFg.named("Clicked fg"),
|
clickFg.named("Clicked fg"),
|
||||||
clickBg.named("Clicked bg"));
|
clickBg.named("Clicked bg")
|
||||||
|
);
|
||||||
|
|
||||||
dimensions.renderBackground = false;
|
dimensions.renderBackground = false;
|
||||||
}
|
}
|
||||||
@@ -49,77 +55,85 @@ public class Keystrokes extends Module implements HudMod {
|
|||||||
lmb = SaturnClient.client.options.attackKey.isPressed();
|
lmb = SaturnClient.client.options.attackKey.isPressed();
|
||||||
rmb = SaturnClient.client.options.useKey.isPressed();
|
rmb = SaturnClient.client.options.useKey.isPressed();
|
||||||
space = SaturnClient.client.options.jumpKey.isPressed();
|
space = SaturnClient.client.options.jumpKey.isPressed();
|
||||||
|
|
||||||
|
updateHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHeight() {
|
||||||
|
int height = KEY_SIZE * 2 + KEY_SPACING;
|
||||||
|
|
||||||
|
if (showSpace.value)
|
||||||
|
height += SPACE_HEIGHT + KEY_SPACING;
|
||||||
|
|
||||||
|
if (showMouse.value)
|
||||||
|
height += MOUSE_HEIGHT + KEY_SPACING;
|
||||||
|
|
||||||
|
dimensions.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderDummy(RenderScope scope) {
|
public void renderDummy(RenderScope scope) {
|
||||||
dimensions.height = 54; // base height with padding considered
|
render(scope, false);
|
||||||
|
|
||||||
// WASD keys (3px padding between each key)
|
|
||||||
renderKey(scope, true, 'W', 27, 0); // Top center
|
|
||||||
renderKey(scope, false, 'A', 0, 27); // Bottom left
|
|
||||||
renderKey(scope, false, 'S', 27, 27); // Bottom center
|
|
||||||
renderKey(scope, false, 'D', 54, 27); // Bottom right
|
|
||||||
|
|
||||||
if (showMouse.value) {
|
|
||||||
renderKeyM(scope, true, "LMB", 0, 54); // Below A/S/D
|
|
||||||
renderKeyM(scope, false, "RMB", 40, 54); // With 3px between
|
|
||||||
dimensions.height += 27;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showSpace.value) {
|
|
||||||
renderKeySpace(scope, false, 0, dimensions.height);
|
|
||||||
dimensions.height += 22;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderHud(RenderScope scope) {
|
public void renderHud(RenderScope scope) {
|
||||||
dimensions.height = 54; // base height with padding considered
|
render(scope, true);
|
||||||
|
}
|
||||||
|
|
||||||
// WASD keys (3px padding between each key)
|
private void render(RenderScope scope, boolean live) {
|
||||||
renderKey(scope, w, 'W', 27, 0); // Top center
|
|
||||||
renderKey(scope, a, 'A', 0, 27); // Bottom left
|
|
||||||
renderKey(scope, s, 'S', 27, 27); // Bottom center
|
|
||||||
renderKey(scope, d, 'D', 54, 27); // Bottom right
|
|
||||||
|
|
||||||
if (showMouse.value) {
|
int yOffset = 0;
|
||||||
renderKeyM(scope, lmb, "LMB", 0, 54); // Below A/S/D
|
|
||||||
renderKeyM(scope, rmb, "RMB", 40, 54); // With 3px between
|
|
||||||
dimensions.height += 27;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// --- W ---
|
||||||
|
renderKey(scope, live && w, 'W', KEY_SIZE + KEY_SPACING, yOffset);
|
||||||
|
|
||||||
|
// --- A S D ---
|
||||||
|
yOffset += KEY_SIZE + KEY_SPACING;
|
||||||
|
|
||||||
|
renderKey(scope, live && a, 'A', 0, yOffset);
|
||||||
|
renderKey(scope, live && s, 'S', KEY_SIZE + KEY_SPACING, yOffset);
|
||||||
|
renderKey(scope, live && d, 'D', (KEY_SIZE + KEY_SPACING) * 2, yOffset);
|
||||||
|
|
||||||
|
yOffset += KEY_SIZE + KEY_SPACING;
|
||||||
|
|
||||||
|
// --- SPACE ---
|
||||||
if (showSpace.value) {
|
if (showSpace.value) {
|
||||||
renderKeySpace(scope, space, 0, dimensions.height);
|
renderKeySpace(scope, live && space, 0, yOffset);
|
||||||
dimensions.height += 22;
|
yOffset += SPACE_HEIGHT + KEY_SPACING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- MOUSE ---
|
||||||
|
if (showMouse.value) {
|
||||||
|
renderKeyMouse(scope, live && lmb, "LMB", 0, yOffset);
|
||||||
|
renderKeyMouse(scope, live && rmb, "RMB", MOUSE_WIDTH + KEY_SPACING, yOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consistent 3px padding applied below:
|
private void renderKey(RenderScope scope, boolean pressed, char c, int x, int y) {
|
||||||
|
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
|
||||||
|
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
|
||||||
|
|
||||||
private void renderKeySpace(RenderScope scope, boolean isPressed, int x, int y) {
|
scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg);
|
||||||
int height = 19;
|
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg);
|
||||||
scope.drawRoundedRectangle(x, y, dimensions.width, height, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
|
|
||||||
|
|
||||||
// A 30-pixel line centered vertically with 3px top padding
|
|
||||||
int lineY = y + (height - 1) / 2;
|
|
||||||
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, isPressed ? clickFg.value : dimensions.fgColor.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderKeyM(RenderScope scope, boolean isPressed, String c, int x, int y) {
|
private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) {
|
||||||
int width = 38, height = 24;
|
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
|
||||||
scope.drawRoundedRectangle(x, y, width, height, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
|
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
|
||||||
|
|
||||||
// Apply padding: 3px top and left -> add slight offset to x/y
|
scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_HEIGHT, dimensions.radius.value, bg);
|
||||||
scope.drawText(0.6f, c, x + 9, y + 7, dimensions.font.value, isPressed ? clickFg.value : dimensions.fgColor.value);
|
scope.drawText(0.6f, text, x + 9, y + 7, dimensions.font.value, fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderKey(RenderScope scope, boolean isPressed, char c, int x, int y) {
|
private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) {
|
||||||
int size = 24;
|
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
|
||||||
scope.drawRoundedRectangle(x, y, size, size, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
|
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
|
||||||
|
|
||||||
// Center the character with 3px top padding
|
scope.drawRoundedRectangle(x, y, dimensions.width, SPACE_HEIGHT, dimensions.radius.value, bg);
|
||||||
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, isPressed ? clickFg.value : dimensions.fgColor.value);
|
|
||||||
|
int lineY = y + SPACE_HEIGHT / 2;
|
||||||
|
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -133,7 +147,7 @@ public class Keystrokes extends Module implements HudMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.modules.mods.utils.HealthRenderState;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
|
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
||||||
|
|
||||||
|
// todo: rendering modes, custom names
|
||||||
|
|
||||||
|
public class Nametags extends Module {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static Property<Boolean> healthDisplay = Property.bool(false);
|
||||||
|
private static Property<Boolean> players = Property.bool(true);
|
||||||
|
private static Property<Boolean> hostile = Property.bool(false);
|
||||||
|
private static Property<Boolean> passive = Property.bool(false);
|
||||||
|
private static Property<Boolean> heartEmoji = Property.bool(true);
|
||||||
|
private static Property<Boolean> obfuscate = Property.bool(false);
|
||||||
|
private static Property<Integer> unit = Property.select(0, "Health", "Hearts");
|
||||||
|
private static Property<Integer> format = Property.select(0, "Value", "Value / Total", "%");
|
||||||
|
private static Property<Integer> nameColor = Property.select(15,
|
||||||
|
"Black",
|
||||||
|
"Dark Blue",
|
||||||
|
"Dark Green",
|
||||||
|
"Dark Aqua",
|
||||||
|
"Dark Red",
|
||||||
|
"Dark Purple",
|
||||||
|
"Gold",
|
||||||
|
"Gray",
|
||||||
|
"Dark Gray",
|
||||||
|
"Blue",
|
||||||
|
"Green",
|
||||||
|
"Aqua",
|
||||||
|
"Red",
|
||||||
|
"Light Purple",
|
||||||
|
"Yellow",
|
||||||
|
"White"
|
||||||
|
);
|
||||||
|
private static Property<Integer> healthColor = Property.select(12,
|
||||||
|
"Black",
|
||||||
|
"Dark Blue",
|
||||||
|
"Dark Green",
|
||||||
|
"Dark Aqua",
|
||||||
|
"Dark Red",
|
||||||
|
"Dark Purple",
|
||||||
|
"Gold",
|
||||||
|
"Gray",
|
||||||
|
"Dark Gray",
|
||||||
|
"Blue",
|
||||||
|
"Green",
|
||||||
|
"Aqua",
|
||||||
|
"Red",
|
||||||
|
"Light Purple",
|
||||||
|
"Yellow",
|
||||||
|
"White"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final String[] COLOR_CODES = {
|
||||||
|
"§0", "§1", "§2", "§3", "§4", "§5", "§6", "§7",
|
||||||
|
"§8", "§9", "§a", "§b", "§c", "§d", "§e", "§f"
|
||||||
|
};
|
||||||
|
|
||||||
|
public Nametags() {
|
||||||
|
super(
|
||||||
|
new ModuleDetails("Nametags", "nametags")
|
||||||
|
.description("Allows you to modify entity nametags")
|
||||||
|
.tags("Camera", "Utility")
|
||||||
|
.version("0.1.0"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
healthDisplay.named("Display health"),
|
||||||
|
players.named("Players"),
|
||||||
|
hostile.named("Hostile"),
|
||||||
|
passive.named("Passive"),
|
||||||
|
heartEmoji.named("Heart emoji"),
|
||||||
|
obfuscate.named("Obfuscate enemy names"),
|
||||||
|
unit.named("Unit"),
|
||||||
|
format.named("Format"),
|
||||||
|
nameColor.named("Name color"),
|
||||||
|
healthColor.named("Health color")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHealthString(LivingEntityRenderState state, HealthRenderState hrs, float health, float maxHealth) {
|
||||||
|
String ret = "";
|
||||||
|
|
||||||
|
switch (format.value){
|
||||||
|
case 0: // value
|
||||||
|
ret = String.format("%.1f", health / (unit.value + 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // value / total
|
||||||
|
ret = String.format("%.1f", health / (unit.value + 1)) + " / " + String.format("%.1f", maxHealth / (unit.value + 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // %
|
||||||
|
ret = String.format("%.0f%%", (health / maxHealth) * 100);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNametagString(LivingEntityRenderState state) {
|
||||||
|
if (state.customName == null) return null;
|
||||||
|
if (!healthDisplay.value) return state.customName.getString(); // name only
|
||||||
|
if (!(state instanceof HealthRenderState hrs)) return null;
|
||||||
|
|
||||||
|
float health = hrs.saturn$getHealth();
|
||||||
|
float maxHealth = hrs.saturn$getMaxHealth();
|
||||||
|
|
||||||
|
HealthRenderState.EntityType type = hrs.saturn$getEntityType();
|
||||||
|
if (type == HealthRenderState.EntityType.PASSIVE && !passive.value) return null;
|
||||||
|
if (type == HealthRenderState.EntityType.HOSTILE && !hostile.value) return null;
|
||||||
|
if (type == HealthRenderState.EntityType.PLAYER && !players.value) return null;
|
||||||
|
if (type == HealthRenderState.EntityType.OTHER) return null;
|
||||||
|
|
||||||
|
if (health < 0f || maxHealth < 0f || health > maxHealth) return null;
|
||||||
|
|
||||||
|
String hpColor = COLOR_CODES[healthColor.value];
|
||||||
|
String color = COLOR_CODES[nameColor.value];
|
||||||
|
String heart = heartEmoji.value ? " ❤" : "";
|
||||||
|
String obf = obfuscate.value ? "§k" : "";
|
||||||
|
String reset = "§r";
|
||||||
|
|
||||||
|
String ret = color + obf + state.customName.getString() + reset + " " + hpColor + getHealthString(state, hrs, health, maxHealth) + heart;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldReplaceName() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
|
public class NoFog extends Module {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static Property<Boolean> liquids = Property.bool(false);
|
||||||
|
|
||||||
|
public NoFog() {
|
||||||
|
super(
|
||||||
|
new ModuleDetails("No Fog", "nofog")
|
||||||
|
.description("Stops fog from rendering")
|
||||||
|
.tags("Camera")
|
||||||
|
.version("v0.1.0"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
liquids.named("See through liquids"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static for the mixin
|
||||||
|
public static boolean isActive() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean liquids() {
|
||||||
|
return liquids.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.HudMod;
|
||||||
|
import org.saturnclient.modules.ModDimensions;
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
|
|
||||||
|
public class Ping extends Module implements HudMod {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static ModDimensions dimensions = new ModDimensions(60, Fonts.getHeight());
|
||||||
|
|
||||||
|
public Ping() {
|
||||||
|
super(new ModuleDetails("Ping Display", "ping")
|
||||||
|
.description("Displays ping")
|
||||||
|
.version("v0.1.0")
|
||||||
|
.tags("Utility"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
dimensions.prop());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderHud(RenderScope scope) {
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
ClientPlayNetworkHandler handler = client.getNetworkHandler();
|
||||||
|
String text = "0 ms";
|
||||||
|
if (handler != null) {
|
||||||
|
text = String.valueOf(handler.getPlayerListEntry(client.player.getUuid()).getLatency()) + " ms";
|
||||||
|
}
|
||||||
|
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderDummy(RenderScope scope) {
|
||||||
|
String text = "10 ms";
|
||||||
|
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModDimensions getDimensions() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.HudMod;
|
||||||
|
import org.saturnclient.modules.ModDimensions;
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
|
||||||
|
public class Speedometer extends Module implements HudMod {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static Property<Integer> axis = Property.select(1, "Absolute", "X axis", "Y axis");
|
||||||
|
private static Property<Integer> unitText = Property.select(0, "None", "Blocks/s", "blocks/s", "b/s");
|
||||||
|
private static Property<Boolean> speedText = Property.bool(true);
|
||||||
|
private static ModDimensions dimensions = new ModDimensions(40, 18);
|
||||||
|
double speed = 0.;
|
||||||
|
Vec3d velocity;
|
||||||
|
double y = 0.;
|
||||||
|
|
||||||
|
public Speedometer() {
|
||||||
|
super(new ModuleDetails("Speedometer", "speed")
|
||||||
|
.description("Displays your current speed")
|
||||||
|
.version("v0.1.0")
|
||||||
|
.tags("Utility"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
axis.named("Speed type"),
|
||||||
|
unitText.named("Unit text"),
|
||||||
|
speedText.named("Show speed text"),
|
||||||
|
dimensions.prop()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderDummy(RenderScope scope) {
|
||||||
|
renderSpeed(2.71, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderHud(RenderScope scope) {
|
||||||
|
Entity vehicle = SaturnClient.client.player.getVehicle();
|
||||||
|
Entity player = SaturnClient.client.player;
|
||||||
|
|
||||||
|
if (vehicle == null) {
|
||||||
|
velocity = player.getVelocity();
|
||||||
|
y = player.isOnGround() ? 0. : velocity.y;
|
||||||
|
} else {
|
||||||
|
velocity = vehicle.getVelocity();
|
||||||
|
y = vehicle.isOnGround() ? 0. : velocity.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (axis.value) {
|
||||||
|
case 0: // Absolute
|
||||||
|
speed = (Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(y, 2) + Math.pow(velocity.z, 2)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // x axis
|
||||||
|
speed = (Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(velocity.z, 2)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // y axis
|
||||||
|
speed = (Math.abs(y));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
renderSpeed(speed * 20, scope); // blocks/tick to blocks/s
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderSpeed(double speed, RenderScope scope) {
|
||||||
|
String f = String.format("%.2f ", speed);
|
||||||
|
|
||||||
|
switch (unitText.value) {
|
||||||
|
case 1:
|
||||||
|
f += "Blocks/s";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
f += "blocks/s";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
f += "b/s";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = f;
|
||||||
|
|
||||||
|
if (speedText.value) {
|
||||||
|
text = "Speed: " + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.drawText(text,
|
||||||
|
0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
dimensions.height = 18 * text.split("\n").length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModDimensions getDimensions() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-6
@@ -7,7 +7,7 @@ import org.saturnclient.modules.ModDimensions;
|
|||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
import net.minecraft.client.resource.language.I18n;
|
||||||
@@ -20,7 +20,7 @@ import net.minecraft.registry.entry.RegistryEntry;
|
|||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
|
||||||
public class PotionDisplay extends Module implements HudMod {
|
public class StatusEffects extends Module implements HudMod {
|
||||||
public static Property<Boolean> enabled = Property.bool(false);
|
public static Property<Boolean> enabled = Property.bool(false);
|
||||||
private static ModDimensions dimensions = new ModDimensions(60, 0);
|
private static ModDimensions dimensions = new ModDimensions(60, 0);
|
||||||
|
|
||||||
@@ -30,9 +30,9 @@ public class PotionDisplay extends Module implements HudMod {
|
|||||||
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("fire_resistance")).get(), 12000, 2),
|
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("fire_resistance")).get(), 12000, 2),
|
||||||
};
|
};
|
||||||
|
|
||||||
public PotionDisplay() {
|
public StatusEffects() {
|
||||||
super(new ModuleDetails("Potion Display", "potion")
|
super(new ModuleDetails("Status effects", "effect")
|
||||||
.description("Displays potion effects")
|
.description("Displays status effects")
|
||||||
.version("v0.1.0")
|
.version("v0.1.0")
|
||||||
.tags("Utility"),
|
.tags("Utility"),
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ public class PotionDisplay extends Module implements HudMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean e) {
|
public void onEnabled(boolean e) {
|
||||||
enabled.value = e;
|
enabled.value = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.HudMod;
|
||||||
|
import org.saturnclient.modules.ModDimensions;
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
|
public class Tps extends Module implements HudMod {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static ModDimensions dimensions = new ModDimensions(60, Fonts.getHeight());
|
||||||
|
|
||||||
|
private static long lastWorldAge = -1;
|
||||||
|
private static long lastRealTime = -1;
|
||||||
|
private static double currentTps = 20.0;
|
||||||
|
|
||||||
|
private static final int SAMPLE_COUNT = 2;
|
||||||
|
private static final double[] samples = new double[SAMPLE_COUNT];
|
||||||
|
static {
|
||||||
|
java.util.Arrays.fill(samples, 20.0);
|
||||||
|
}
|
||||||
|
private static int sampleIndex = 0;
|
||||||
|
private static int samplesFilled = 0;
|
||||||
|
|
||||||
|
public Tps() {
|
||||||
|
super(new ModuleDetails("TPS Display", "tps")
|
||||||
|
.description("Displays the TPS")
|
||||||
|
.version("v0.1.0")
|
||||||
|
.tags("Utility"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
dimensions.prop()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onTimePacket(long worldAge) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (lastWorldAge != -1 && lastRealTime != -1) {
|
||||||
|
long tickDelta = worldAge - lastWorldAge;
|
||||||
|
long timeDelta = now - lastRealTime;
|
||||||
|
|
||||||
|
if (timeDelta > 0 && tickDelta > 0) {
|
||||||
|
double sample = (tickDelta / (double) timeDelta) * 1000.0;
|
||||||
|
sample = Math.min(sample, 20.0);
|
||||||
|
|
||||||
|
samples[sampleIndex % SAMPLE_COUNT] = sample;
|
||||||
|
sampleIndex++;
|
||||||
|
samplesFilled = Math.min(samplesFilled + 1, SAMPLE_COUNT);
|
||||||
|
|
||||||
|
double sum = 0;
|
||||||
|
for (int i = 0; i < samplesFilled; i++) sum += samples[i];
|
||||||
|
currentTps = sum / samplesFilled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastWorldAge = worldAge;
|
||||||
|
lastRealTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
lastWorldAge = -1;
|
||||||
|
lastRealTime = -1;
|
||||||
|
currentTps = 20.0;
|
||||||
|
sampleIndex = 0;
|
||||||
|
samplesFilled = SAMPLE_COUNT;
|
||||||
|
for (int i = 0; i < SAMPLE_COUNT; i++) samples[i] = 20.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderHud(RenderScope scope) {
|
||||||
|
String text = String.format("%.1f TPS", currentTps);
|
||||||
|
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderDummy(RenderScope scope) {
|
||||||
|
String text = "20.0 TPS";
|
||||||
|
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
|
||||||
|
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModDimensions getDimensions() {
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package org.saturnclient.modules.mods;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.saturnclient.modules.Module;
|
||||||
|
import org.saturnclient.modules.ModuleDetails;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
|
public class Zoom extends Module {
|
||||||
|
private static Property<Boolean> enabled = Property.bool(false);
|
||||||
|
private static Property<Boolean> toggle = Property.bool(false);
|
||||||
|
public static Property<Integer> zoomKey = Property.keybinding(GLFW.GLFW_KEY_C);
|
||||||
|
public static Property<Float> zoomLevel = Property.floatProp(3.0f);
|
||||||
|
|
||||||
|
public static boolean isZooming = false;
|
||||||
|
|
||||||
|
public Zoom() {
|
||||||
|
super(
|
||||||
|
new ModuleDetails("Zoom", "zoom")
|
||||||
|
.description("Allows you to zoom in for a closer view")
|
||||||
|
.tags("Camera", "Utility")
|
||||||
|
.version("0.1.0"),
|
||||||
|
enabled.named("Enabled"),
|
||||||
|
toggle.named("Toggle zoom"),
|
||||||
|
zoomKey.named("Zoom Keybinding"),
|
||||||
|
zoomLevel.named("Zoom Level")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (toggle.value) {
|
||||||
|
if (zoomKey.wasKeyPressed()) {
|
||||||
|
isZooming = !isZooming;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isZooming = zoomKey.isKeyPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getZoomLevel() {
|
||||||
|
return zoomLevel.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldZoom() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(boolean e) {
|
||||||
|
enabled.value = e;
|
||||||
|
if (!e) {
|
||||||
|
isZooming = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.client.render.BackgroundRenderer;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.mods.NoFog;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(BackgroundRenderer.class)
|
||||||
|
public class BackgroundRendererMixin {
|
||||||
|
|
||||||
|
@ModifyVariable(method = "applyFog", at = @At("HEAD"), ordinal = 0, argsOnly = true)
|
||||||
|
private static float modifyFogStart(float viewDistance) {
|
||||||
|
if (NoFog.isActive()) {
|
||||||
|
return Float.MAX_VALUE; // this could be customised
|
||||||
|
}
|
||||||
|
return viewDistance;
|
||||||
|
}
|
||||||
|
// liquids
|
||||||
|
@Redirect(
|
||||||
|
method = "applyFog",
|
||||||
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isSpectator()Z")
|
||||||
|
)
|
||||||
|
private static boolean redirectIsSpectator(Entity entity) {
|
||||||
|
if (NoFog.isActive() && NoFog.liquids()) {
|
||||||
|
return true; // pretend to be spectator
|
||||||
|
}
|
||||||
|
return entity.isSpectator();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
import org.saturnclient.modules.mods.Zoom;
|
||||||
|
|
||||||
|
import net.minecraft.client.render.GameRenderer;
|
||||||
|
|
||||||
|
@Mixin(GameRenderer.class)
|
||||||
|
public class GameRendererMixin {
|
||||||
|
|
||||||
|
@Inject(method = "getFov", at = @At("RETURN"), cancellable = true)
|
||||||
|
private void onGetFov(CallbackInfoReturnable<Float> cir) {
|
||||||
|
if (Zoom.isZooming && Zoom.shouldZoom()) {
|
||||||
|
float originalFov = cir.getReturnValue();
|
||||||
|
float zoomedFov = originalFov / Zoom.getZoomLevel();
|
||||||
|
cir.setReturnValue(zoomedFov);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// code modified from Gamma utils: https://github.com/Sjouwer/gamma-utils
|
||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
|
import net.minecraft.client.render.LightmapTextureManager;
|
||||||
|
import org.saturnclient.modules.mods.Fullbright;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
|
@Mixin(LightmapTextureManager.class)
|
||||||
|
public class LightmapTextureManagerMixin {
|
||||||
|
|
||||||
|
@ModifyExpressionValue(
|
||||||
|
method = "update",
|
||||||
|
at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0)
|
||||||
|
)
|
||||||
|
private float modifyGamma(float original) {
|
||||||
|
if (Fullbright.shouldOverrideBrightness()) {
|
||||||
|
return Fullbright.getBrightnessValue();
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.mods.utils.HealthRenderState;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
|
@Mixin(LivingEntityRenderState.class)
|
||||||
|
public class LivingEntityRenderStateMixin implements org.saturnclient.modules.mods.utils.HealthRenderState {
|
||||||
|
|
||||||
|
@Unique private float saturn$health = 0f;
|
||||||
|
@Unique private float saturn$maxHealth = 1f;
|
||||||
|
@Unique private HealthRenderState.EntityType saturn$entityType = HealthRenderState.EntityType.OTHER;
|
||||||
|
|
||||||
|
public float saturn$getHealth() { return saturn$health; }
|
||||||
|
public float saturn$getMaxHealth() { return saturn$maxHealth; }
|
||||||
|
public void saturn$setHealth(float health, float maxHealth) {
|
||||||
|
this.saturn$health = health;
|
||||||
|
this.saturn$maxHealth = maxHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HealthRenderState.EntityType saturn$getEntityType() { return saturn$entityType; }
|
||||||
|
public void saturn$setEntityType(HealthRenderState.EntityType type) { this.saturn$entityType = type; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.client.render.entity.LivingEntityRenderer;
|
||||||
|
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
||||||
|
import org.saturnclient.modules.mods.utils.HealthRenderState;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.mob.HostileEntity;
|
||||||
|
import net.minecraft.entity.passive.PassiveEntity;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(LivingEntityRenderer.class)
|
||||||
|
public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState> {
|
||||||
|
|
||||||
|
@Inject(method = "updateRenderState", at = @At("TAIL"))
|
||||||
|
private void saturn$captureHealth(T entity, S state, float tickDelta, CallbackInfo ci) {
|
||||||
|
if (!(state instanceof HealthRenderState hrs)) return;
|
||||||
|
|
||||||
|
hrs.saturn$setHealth(entity.getHealth(), entity.getMaxHealth());
|
||||||
|
|
||||||
|
if (entity instanceof PlayerEntity) {
|
||||||
|
hrs.saturn$setEntityType(HealthRenderState.EntityType.PLAYER);
|
||||||
|
} else if (entity instanceof HostileEntity) {
|
||||||
|
hrs.saturn$setEntityType(HealthRenderState.EntityType.HOSTILE);
|
||||||
|
} else if (entity instanceof PassiveEntity) {
|
||||||
|
hrs.saturn$setEntityType(HealthRenderState.EntityType.PASSIVE);
|
||||||
|
} else {
|
||||||
|
hrs.saturn$setEntityType(HealthRenderState.EntityType.OTHER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.client.render.VertexConsumerProvider;
|
||||||
|
import net.minecraft.client.render.entity.EntityRenderer;
|
||||||
|
import net.minecraft.client.render.entity.state.EntityRenderState;
|
||||||
|
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import org.saturnclient.modules.mods.Nametags;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(EntityRenderer.class)
|
||||||
|
public abstract class NametagsMixin {
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private static final ThreadLocal<Boolean> saturn$rendering = ThreadLocal.withInitial(() -> false);
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
protected abstract <S extends EntityRenderState> void renderLabelIfPresent(
|
||||||
|
S state, Text text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light
|
||||||
|
);
|
||||||
|
|
||||||
|
@Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true)
|
||||||
|
private <S extends EntityRenderState> void saturn$replaceNametag(S state, Text text,
|
||||||
|
MatrixStack matrices, VertexConsumerProvider vertexConsumers,
|
||||||
|
int light, CallbackInfo ci) {
|
||||||
|
|
||||||
|
if (saturn$rendering.get()) return;
|
||||||
|
if (!Nametags.shouldReplaceName()) return;
|
||||||
|
if (!(state instanceof LivingEntityRenderState living)) return;
|
||||||
|
|
||||||
|
String replacement = Nametags.getNametagString(living);
|
||||||
|
if (replacement == null) return;
|
||||||
|
|
||||||
|
ci.cancel();
|
||||||
|
saturn$rendering.set(true);
|
||||||
|
try {
|
||||||
|
renderLabelIfPresent(state, Text.literal(replacement), matrices, vertexConsumers, light);
|
||||||
|
} finally {
|
||||||
|
saturn$rendering.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import org.saturnclient.modules.HudMod;
|
||||||
|
import org.saturnclient.modules.ModDimensions;
|
||||||
|
import org.saturnclient.modules.ModManager;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.menus.HudEditor;
|
||||||
|
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import net.minecraft.client.font.TextRenderer;
|
||||||
|
import net.minecraft.client.gui.DrawContext;
|
||||||
|
import net.minecraft.client.gui.hud.InGameHud;
|
||||||
|
import net.minecraft.client.render.RenderLayer;
|
||||||
|
import net.minecraft.client.render.RenderTickCounter;
|
||||||
|
|
||||||
|
@Mixin(InGameHud.class)
|
||||||
|
public class RenderMixin {
|
||||||
|
@Inject(method = "renderChat", at = @At("TAIL"))
|
||||||
|
public void renderMods(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
|
||||||
|
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
||||||
|
|
||||||
|
if (textRenderer != null &&
|
||||||
|
!(SaturnClient.client.currentScreen instanceof HudEditor)) {
|
||||||
|
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
||||||
|
((DrawContextAccessor) context).getVertexConsumers());
|
||||||
|
|
||||||
|
for (org.saturnclient.modules.Module m : ModManager.ENABLED_MODS) {
|
||||||
|
if (m instanceof HudMod && m.isEnabled()) {
|
||||||
|
ModDimensions dim = ((HudMod) m).getDimensions();
|
||||||
|
|
||||||
|
renderScope.matrices.push();
|
||||||
|
|
||||||
|
renderScope.matrices.translate(dim.x.value, (float) dim.y.value, 0);
|
||||||
|
|
||||||
|
renderScope.matrices.scale(dim.scale.value, dim.scale.value, 1.0f);
|
||||||
|
|
||||||
|
if (dim.renderBackground) {
|
||||||
|
renderScope.drawRoundedRectangle(0, 0, dim.width, dim.height, dim.radius.value,
|
||||||
|
dim.bgColor.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||||
|
|
||||||
|
((HudMod) m).renderHud(renderScope);
|
||||||
|
|
||||||
|
renderScope.matrices.pop();
|
||||||
|
} else {
|
||||||
|
m.render(renderScope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.saturnclient.modules.mods.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
|
import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket;
|
||||||
|
import org.saturnclient.modules.mods.Tps;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ClientPlayNetworkHandler.class)
|
||||||
|
public class TpsMixin {
|
||||||
|
|
||||||
|
@Inject(method = "onWorldTimeUpdate", at = @At("HEAD"))
|
||||||
|
private void onWorldTimeUpdate(WorldTimeUpdateS2CPacket packet, CallbackInfo ci) {
|
||||||
|
Tps.onTimePacket(packet.time());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.saturnclient.modules.mods.utils;
|
||||||
|
|
||||||
|
public interface HealthRenderState {
|
||||||
|
float saturn$getHealth();
|
||||||
|
float saturn$getMaxHealth();
|
||||||
|
void saturn$setHealth(float health, float maxHealth);
|
||||||
|
|
||||||
|
EntityType saturn$getEntityType();
|
||||||
|
void saturn$setEntityType(EntityType type);
|
||||||
|
|
||||||
|
enum EntityType {
|
||||||
|
PLAYER, HOSTILE, PASSIVE, OTHER
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@ import net.minecraft.client.MinecraftClient;
|
|||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
|
||||||
import org.saturnclient.modules.ModManager;
|
import org.saturnclient.modules.ModManager;
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
import org.saturnclient.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
import org.saturnclient.saturnclient.cosmetics.Emotes;
|
import org.saturnclient.saturnclient.cosmetics.Emotes;
|
||||||
import org.saturnclient.saturnclient.cosmetics.Hats;
|
import org.saturnclient.saturnclient.cosmetics.Hats;
|
||||||
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
|
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
|
||||||
@@ -37,9 +38,13 @@ public class SaturnClient implements ModInitializer {
|
|||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
LOGGER.info("Initializing " + MOD_ID);
|
LOGGER.info("Initializing " + MOD_ID);
|
||||||
client = MinecraftClient.getInstance();
|
client = MinecraftClient.getInstance();
|
||||||
SaturnClientConfig.init();
|
Config.init();
|
||||||
ModManager.init();
|
ModManager.init();
|
||||||
|
|
||||||
|
client.execute(() -> {
|
||||||
|
SaturnScreen.preload(client);
|
||||||
|
});
|
||||||
|
|
||||||
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
|
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
|
||||||
|
|
||||||
SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> {
|
SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> {
|
||||||
@@ -49,11 +54,12 @@ public class SaturnClient implements ModInitializer {
|
|||||||
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
|
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
|
||||||
if (screen != null && !(screen instanceof TitleMenu)) {
|
if (screen != null && !(screen instanceof TitleMenu)) {
|
||||||
if (screen instanceof SaturnScreen) {
|
if (screen instanceof SaturnScreen) {
|
||||||
((SaturnScreen) screen).draw(new ImageTexture(Textures.LOGO_TEXT_BIG).dimensions(180, 18).position(screen.width - 180 - 20, screen.height - 18 - 20));
|
((SaturnScreen) screen).draw(new ImageTexture(Textures.LOGO_TEXT_BIG).dimensions(180, 18)
|
||||||
|
.position(screen.width - 180 - 20, screen.height - 18 - 20));
|
||||||
} else {
|
} else {
|
||||||
ScreenEvents.afterRender(screen).register((screen1, context, mouseX, mouseY, tickDelta) -> {
|
ScreenEvents.afterRender(screen).register((screen1, context, mouseX, mouseY, tickDelta) -> {
|
||||||
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
||||||
((DrawContextAccessor) context).getVertexConsumers());
|
((DrawContextAccessor) context).getVertexConsumers());
|
||||||
|
|
||||||
int width = context.getScaledWindowWidth();
|
int width = context.getScaledWindowWidth();
|
||||||
int height = context.getScaledWindowHeight();
|
int height = context.getScaledWindowHeight();
|
||||||
@@ -74,7 +80,7 @@ public class SaturnClient implements ModInitializer {
|
|||||||
|
|
||||||
KeyInputHandler.register();
|
KeyInputHandler.register();
|
||||||
Emotes.initialize();
|
Emotes.initialize();
|
||||||
if (Auth.authenticate()) {
|
if (ServiceClient.authenticate()) {
|
||||||
Cloaks.initialize();
|
Cloaks.initialize();
|
||||||
Hats.initialize();
|
Hats.initialize();
|
||||||
LOGGER.info(MOD_ID + " initialization complete");
|
LOGGER.info(MOD_ID + " initialization complete");
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.auth;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
|
||||||
import org.saturnclient.saturnclient.cosmetics.Hats;
|
|
||||||
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
|
|
||||||
import org.saturnclient.ui2.SaturnScreen;
|
|
||||||
import org.saturnclient.ui2.elements.Notification;
|
|
||||||
import org.saturnclient.ui2.elements.Notification.NotificationKind;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
|
|
||||||
public class Auth {
|
|
||||||
public static Map<String, SaturnPlayer> players = new HashMap<>();
|
|
||||||
public static Map<String, String> playerNames = new HashMap<>();
|
|
||||||
public static String uuid;
|
|
||||||
private static Thread listenerThread;
|
|
||||||
private static Thread pingThread;
|
|
||||||
private static volatile boolean running = false;
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static boolean authenticate() {
|
|
||||||
// Register shutdown hook ONCE
|
|
||||||
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> close());
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (SaturnClient.client.getSession() == null) {
|
|
||||||
SaturnClient.LOGGER.error("No active Minecraft session found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String accessToken = SaturnClient.client.getSession().getAccessToken();
|
|
||||||
SaturnClient.LOGGER.info("Authenticating");
|
|
||||||
|
|
||||||
Network.init();
|
|
||||||
|
|
||||||
Network.write(accessToken);
|
|
||||||
|
|
||||||
String response = Network.read();
|
|
||||||
|
|
||||||
SaturnParser parser = new SaturnParser(response);
|
|
||||||
|
|
||||||
uuid = parser.getString("uuid");
|
|
||||||
String cloak = parser.getString("cloak");
|
|
||||||
String hat = parser.getString("hat");
|
|
||||||
|
|
||||||
for (String availableCloak : parser.getArray("cloaks")) {
|
|
||||||
Cloaks.availableCloaks.add(availableCloak);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String availableHat : parser.getArray("hats")) {
|
|
||||||
Hats.availableHats.add(availableHat);
|
|
||||||
}
|
|
||||||
|
|
||||||
players.put(uuid, new SaturnPlayer(cloak, hat));
|
|
||||||
|
|
||||||
Cloaks.loadCloak(uuid);
|
|
||||||
|
|
||||||
playerNames.put(SaturnClient.client.getSession().getUsername(), uuid);
|
|
||||||
|
|
||||||
afterAuth();
|
|
||||||
|
|
||||||
startListenerThread();
|
|
||||||
startPingThread();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Authentication failed", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void startListenerThread() {
|
|
||||||
running = true;
|
|
||||||
listenerThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
while (running) {
|
|
||||||
String message = Network.read();
|
|
||||||
if (message == null) {
|
|
||||||
SaturnClient.LOGGER.error("Connection closed");
|
|
||||||
running = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
SaturnClient.LOGGER.info("Received: " + message);
|
|
||||||
|
|
||||||
SaturnParser parser = new SaturnParser(message);
|
|
||||||
|
|
||||||
switch (parser.method) {
|
|
||||||
case "player":
|
|
||||||
String uuid = parser.getString("uuid");
|
|
||||||
if (parser.getBool("saturn")) {
|
|
||||||
String cloak = parser.getString("cloak");
|
|
||||||
String hat = parser.getString("hat");
|
|
||||||
players.put(uuid, new SaturnPlayer(cloak, hat));
|
|
||||||
Cloaks.loadCloak(uuid);
|
|
||||||
} else {
|
|
||||||
playerNames.entrySet().removeIf(entry -> entry.getValue().equals(uuid));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "successful_transaction":
|
|
||||||
String cloak = parser.getStringOrNull("cloak");
|
|
||||||
String hat = parser.getStringOrNull("hat");
|
|
||||||
if (cloak != null) {
|
|
||||||
Cloaks.availableCloaks.add(cloak);
|
|
||||||
}
|
|
||||||
if (hat != null) {
|
|
||||||
Hats.availableHats.add(hat);
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (parser.error != null) {
|
|
||||||
SaturnClient.LOGGER.error("Error from the server: " + parser.error);
|
|
||||||
|
|
||||||
if (SaturnClient.client.currentScreen instanceof SaturnScreen) {
|
|
||||||
String[] a = parser.error.replace("!", "").split(": ");
|
|
||||||
((SaturnScreen) SaturnClient.client.currentScreen).draw(new Notification(SaturnClient.client.currentScreen.width, SaturnClient.client.currentScreen.height, NotificationKind.Error, a[0], a[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Error reading from server", e);
|
|
||||||
} finally {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
listenerThread.setDaemon(true);
|
|
||||||
listenerThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void startPingThread() {
|
|
||||||
pingThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
while (running && !Thread.currentThread().isInterrupted()) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(25000); // 25 seconds
|
|
||||||
if (running) {
|
|
||||||
Network.write("ping");
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt(); // Restore interrupt status
|
|
||||||
break; // Exit the loop on interruption
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (!(e instanceof InterruptedException)) {
|
|
||||||
SaturnClient.LOGGER.error("Error in ping thread", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pingThread.setDaemon(true);
|
|
||||||
pingThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void close() {
|
|
||||||
running = false;
|
|
||||||
if (pingThread != null) {
|
|
||||||
pingThread.interrupt();
|
|
||||||
}
|
|
||||||
if (listenerThread != null) {
|
|
||||||
listenerThread.interrupt();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
close();
|
|
||||||
SaturnClient.LOGGER.info("SaturnApi connection closed.");
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to close resources", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void afterAuth() {
|
|
||||||
PlayerTracker.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setCloak(String cloakName) {
|
|
||||||
try {
|
|
||||||
Network.write("set_cloak@cloak=" + cloakName);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Request failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setHat(String hatName) {
|
|
||||||
try {
|
|
||||||
Network.write("set_hat@hat=" + hatName);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Request failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void player(String name, String uuid) {
|
|
||||||
try {
|
|
||||||
playerNames.put(name, uuid.replaceAll("-", ""));
|
|
||||||
Network.write("player@uuid=" + uuid.replaceAll("-", ""));
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Request failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendReload() {
|
|
||||||
for (Map.Entry<String, String> player : playerNames.entrySet()) {
|
|
||||||
if (players.containsKey(player.getValue()) && !player.getValue().equals(uuid)) {
|
|
||||||
SaturnClient.client.player.networkHandler
|
|
||||||
.sendChatCommand("msg " + player.getKey() + " "
|
|
||||||
+ "$SATURN_RELOAD if you are seeing this as a player, please report this to https://github.com/saturnclientmc/saturnclient/issues");
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendEmote(String emote) {
|
|
||||||
String uuid = SaturnClient.client.player.getUuidAsString().replace("-", "");
|
|
||||||
for (Map.Entry<String, String> player : playerNames.entrySet()) {
|
|
||||||
if (players.containsKey(player.getValue()) && !player.getValue().equals(uuid)) {
|
|
||||||
SaturnClient.client.player.networkHandler
|
|
||||||
.sendChatCommand("msg " + player.getKey() + " "
|
|
||||||
+ "$SATURN_EMOTE&@"+uuid+"&@"+emote+"&@ if you are seeing this as a player, please report this to https://github.com/saturnclientmc/saturnclient/issues");
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cancelEmote() {
|
|
||||||
String uuid = SaturnClient.client.player.getUuidAsString().replace("-", "");
|
|
||||||
for (Map.Entry<String, String> player : playerNames.entrySet()) {
|
|
||||||
if (players.containsKey(player.getValue()) && !player.getValue().equals(uuid)) {
|
|
||||||
SaturnClient.client.player.networkHandler
|
|
||||||
.sendChatCommand("msg " + player.getKey() + " "
|
|
||||||
+ "$SATURN_CANCEL_EMOTE&@"+uuid+"&@ if you are seeing this as a player, please report this to https://github.com/saturnclientmc/saturnclient/issues");
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void buyCloak(String name) {
|
|
||||||
try {
|
|
||||||
Network.write("buy_cloak@cloak=" + name);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Request failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void buyHat(String name) {
|
|
||||||
try {
|
|
||||||
Network.write("buy_hat@hat=" + name);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Request failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.auth;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.*;
|
|
||||||
import java.security.*;
|
|
||||||
import java.util.Base64;
|
|
||||||
import javax.crypto.*;
|
|
||||||
import javax.crypto.spec.*;
|
|
||||||
import java.security.interfaces.*;
|
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
|
||||||
|
|
||||||
public class Network {
|
|
||||||
static Socket socket;
|
|
||||||
static PrintWriter out;
|
|
||||||
static BufferedReader in;
|
|
||||||
static byte[] key = new byte[32];
|
|
||||||
|
|
||||||
public static class AES {
|
|
||||||
public static byte[] encryptAES256(String message, byte[] key) throws Exception {
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
||||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
|
|
||||||
byte[] encryptedMessage = cipher.doFinal(message.getBytes());
|
|
||||||
return encryptedMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String decryptAES256(byte[] encryptedMessage, byte[] key) throws Exception {
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
||||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
|
|
||||||
byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
|
|
||||||
return new String(decryptedMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void init() throws Exception {
|
|
||||||
// Generate AES key
|
|
||||||
new SecureRandom().nextBytes(key);
|
|
||||||
|
|
||||||
socket = new Socket("77.247.92.168", 8080);
|
|
||||||
// out = new DataOutputStream(socket.getOutputStream());
|
|
||||||
out = new PrintWriter(socket.getOutputStream(), true);
|
|
||||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
|
||||||
|
|
||||||
StringBuilder publicKeyBuilder = new StringBuilder();
|
|
||||||
String line;
|
|
||||||
while (!(line = in.readLine()).contains("-----END PUBLIC KEY-----")) {
|
|
||||||
publicKeyBuilder.append(line).append("\n");
|
|
||||||
}
|
|
||||||
publicKeyBuilder.append(line);
|
|
||||||
|
|
||||||
String publicKeyPem = publicKeyBuilder.toString();
|
|
||||||
|
|
||||||
String publicKeyBase64 = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "")
|
|
||||||
.replace("-----END PUBLIC KEY-----", "").replace("\n", "").replace("\r", "");
|
|
||||||
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
|
|
||||||
|
|
||||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
|
|
||||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
||||||
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
|
|
||||||
|
|
||||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
|
||||||
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
|
||||||
byte[] encryptedKey = rsaCipher.doFinal(key);
|
|
||||||
|
|
||||||
out.println(Base64.getEncoder().encodeToString(encryptedKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void write(String message) {
|
|
||||||
try {
|
|
||||||
out.println(Base64.getEncoder().encodeToString(AES.encryptAES256(message, key)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to write to server: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String read() {
|
|
||||||
try {
|
|
||||||
String msg = in.readLine();
|
|
||||||
if (msg != null) {
|
|
||||||
return AES.decryptAES256(Base64.getDecoder().decode(msg), key);
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void close() {
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
out.close();
|
|
||||||
in.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.auth;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class SaturnParser {
|
|
||||||
public String method = "";
|
|
||||||
public String error;
|
|
||||||
private Map<String, String> params = new HashMap<>();
|
|
||||||
|
|
||||||
public SaturnParser(String input) throws IOException {
|
|
||||||
String[] parts = input.split("@");
|
|
||||||
|
|
||||||
if (parts.length == 0) {
|
|
||||||
throw new IOException("Invalid input string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].startsWith("!")) {
|
|
||||||
error = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
method = parts[0];
|
|
||||||
|
|
||||||
for (int i = 1; i < parts.length; i++) {
|
|
||||||
String[] keyValue = parts[i].split("=");
|
|
||||||
if (keyValue.length == 2) {
|
|
||||||
params.put(keyValue[0].trim(), keyValue[1].trim());
|
|
||||||
} else if (keyValue.length == 1) {
|
|
||||||
params.put(keyValue[0].trim(), "");
|
|
||||||
} else {
|
|
||||||
throw new IOException("Invalid parameter format: " + parts[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getString(String i) throws IOException {
|
|
||||||
String val = params.get(i);
|
|
||||||
if (val != null) {
|
|
||||||
return val;
|
|
||||||
} else {
|
|
||||||
throw new IOException("Parameter not found " + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStringOrNull(String i) throws IOException {
|
|
||||||
String val = params.get(i);
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getBool(String i) throws IOException {
|
|
||||||
String val = params.get(i);
|
|
||||||
if (val != null) {
|
|
||||||
return val.equals("true");
|
|
||||||
} else {
|
|
||||||
throw new IOException("Parameter not found " + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getArray(String i) throws IOException {
|
|
||||||
String val = params.get(i);
|
|
||||||
if (val != null) {
|
|
||||||
if (val.isEmpty()) {
|
|
||||||
return new String[] {};
|
|
||||||
}
|
|
||||||
return val.split("\\$");
|
|
||||||
} else {
|
|
||||||
throw new IOException("Parameter not found " + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.auth;
|
|
||||||
|
|
||||||
public class SaturnPlayer {
|
|
||||||
public String cloak = "";
|
|
||||||
public String hat = "";
|
|
||||||
|
|
||||||
public SaturnPlayer(String cloak, String hat) {
|
|
||||||
this.cloak = cloak;
|
|
||||||
this.hat = hat;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package org.saturnclient.saturnclient.client;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
|
import org.saturnclient.saturnclient.cosmetics.Hats;
|
||||||
|
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
|
||||||
|
|
||||||
|
import dev.kosmx.playerAnim.api.layered.AnimationStack;
|
||||||
|
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
|
||||||
|
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
|
||||||
|
import dev.selimaj.session.Session;
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||||
|
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
|
public class ServiceClient {
|
||||||
|
private static Session session;
|
||||||
|
public static UUID uuid;
|
||||||
|
|
||||||
|
public static boolean connectTimeout() {
|
||||||
|
try {
|
||||||
|
session = Session.connect("wss://saturn-server.selimaj.dev", 10, TimeUnit.SECONDS);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean authenticate() {
|
||||||
|
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> {
|
||||||
|
if (session != null)
|
||||||
|
try {
|
||||||
|
session.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Unable to close Saturn session");
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
var mcSession = SaturnClient.client.getSession();
|
||||||
|
if (mcSession == null) {
|
||||||
|
SaturnClient.LOGGER.error("No active Minecraft session found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String accessToken = mcSession.getAccessToken();
|
||||||
|
uuid = mcSession.getUuidOrNull();
|
||||||
|
String username = mcSession.getUsername();
|
||||||
|
|
||||||
|
SaturnClient.LOGGER.info("Authenticating with UUID: " + uuid);
|
||||||
|
|
||||||
|
if (!connectTimeout()) {
|
||||||
|
SaturnClient.LOGGER.error("Unable to authenticate: Session Server Timeout");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceMethods.Types.Player response = session.request(ServiceMethods.Authenticate, accessToken)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
for (String availableCloak : response.cloaks()) {
|
||||||
|
Cloaks.availableCloaks.add(availableCloak);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String availableHat : response.hats()) {
|
||||||
|
Hats.availableHats.add(availableHat);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventHandlers();
|
||||||
|
|
||||||
|
SaturnPlayer.set(new SaturnPlayer(uuid, username, response.cloak(), response.hat()));
|
||||||
|
|
||||||
|
Cloaks.loadCloak(uuid);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Authentication failed", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloak(String itemId) {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.SetCloak, itemId).whenComplete((msg, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.SendPlayer,
|
||||||
|
new ServiceMethods.Types.SendPlayerRequest(SaturnPlayer.getExternalUUIDAsString()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to set cloak (service): ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHat(String itemId) {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.SetHat, itemId).whenComplete((msg, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.SendPlayer,
|
||||||
|
new ServiceMethods.Types.SendPlayerRequest(SaturnPlayer.getExternalUUIDAsString()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to set hat (service): ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void buyCloak(String itemId) {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.BuyCloak, itemId).whenComplete((msg, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
} else {
|
||||||
|
Cloaks.availableCloaks.add(itemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to buy cloak (service): ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void buyHat(String itemId) {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.BuyHat, itemId).whenComplete((msg, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
} else {
|
||||||
|
Hats.availableHats.add(itemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to buy hat (service): ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void emote(String emote) {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.Emote,
|
||||||
|
new ServiceMethods.Types.EmoteRequest(emote, SaturnPlayer.getExternalUUIDAsString()))
|
||||||
|
.whenComplete((msg, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to emote (service): ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void eventHandlers() {
|
||||||
|
session.onNotification(ServiceMethods.EmoteEvent, (data) -> {
|
||||||
|
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
|
||||||
|
if (player.getUuidAsString().equals(data.from())) {
|
||||||
|
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
|
||||||
|
if (data.emote() != null && !data.emote().isEmpty()) {
|
||||||
|
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
|
||||||
|
animationStack.removeLayer(1000);
|
||||||
|
}
|
||||||
|
animationStack.addAnimLayer(1000,
|
||||||
|
PlayerAnimationRegistry
|
||||||
|
.getAnimation(Identifier.of("saturnclient", data.emote()))
|
||||||
|
.playAnimation());
|
||||||
|
} else {
|
||||||
|
animationStack.removeLayer(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
session.onNotification(ServiceMethods.Player, (player) -> {
|
||||||
|
System.out.println(player);
|
||||||
|
SaturnPlayer.set(player.toSaturnPlayer());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void player(UUID uuid, String name) {
|
||||||
|
try {
|
||||||
|
session.request(ServiceMethods.GetPlayer, uuid.toString())
|
||||||
|
.whenComplete((user, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
} else if (user != null) {
|
||||||
|
SaturnPlayer.set(user.toSaturnPlayer(uuid, name));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to get player", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package org.saturnclient.saturnclient.client;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.client.ServiceMethods.Types.SendPlayerRequest;
|
||||||
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
|
|
||||||
|
import dev.selimaj.session.types.Method;
|
||||||
|
|
||||||
|
public class ServiceMethods {
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Types (Rust structs)
|
||||||
|
// =========================
|
||||||
|
public static class Types {
|
||||||
|
|
||||||
|
public record EmoteRequest(String emote, String[] targets) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record EmoteEvent(String emote, String from) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Player(
|
||||||
|
String cloak,
|
||||||
|
String hat,
|
||||||
|
String[] cloaks,
|
||||||
|
String[] hats) {
|
||||||
|
public SaturnPlayer toSaturnPlayer(UUID uuid, String name) {
|
||||||
|
return new SaturnPlayer(uuid, name, cloak, hat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PlayerStream(Player player, String name, String uuid) {
|
||||||
|
public SaturnPlayer toSaturnPlayer() {
|
||||||
|
return new SaturnPlayer(UUID.fromString(uuid), name, this.player.cloak, this.player.hat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SendPlayerRequest(String[] targets) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// AUTH
|
||||||
|
// =========================
|
||||||
|
public static final Method<String, Types.Player, String> Authenticate = new Method<>("auth",
|
||||||
|
String.class,
|
||||||
|
Types.Player.class,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// EQUIP
|
||||||
|
// =========================
|
||||||
|
public static final Method<String, String, String> SetCloak = new Method<>("set_cloak",
|
||||||
|
String.class,
|
||||||
|
String.class,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
public static final Method<String, String, String> SetHat = new Method<>("set_hat",
|
||||||
|
String.class,
|
||||||
|
String.class,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// BUY
|
||||||
|
// =========================
|
||||||
|
public static final Method<String, String, String> BuyCloak = new Method<>("buy_cloak",
|
||||||
|
String.class,
|
||||||
|
String.class,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
public static final Method<String, String, String> BuyHat = new Method<>("buy_hat",
|
||||||
|
String.class,
|
||||||
|
String.class,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// EMOTE
|
||||||
|
// =========================
|
||||||
|
public static final Method<Types.EmoteRequest, Void, String> Emote = new Method<>("emote",
|
||||||
|
Types.EmoteRequest.class,
|
||||||
|
Void.class,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
public static final Method<Types.EmoteEvent, Void, Void> EmoteEvent = new Method<>("emote_event",
|
||||||
|
Types.EmoteEvent.class,
|
||||||
|
Void.class,
|
||||||
|
Void.class);
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// GET PLAYER
|
||||||
|
// =========================
|
||||||
|
public static final Method<String, Types.Player, String> GetPlayer = new Method<>("get_player",
|
||||||
|
String.class,
|
||||||
|
Types.Player.class,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// PLAYER (Server Notification)
|
||||||
|
// =========================
|
||||||
|
public static final Method<Types.PlayerStream, Void, Void> Player = new Method<>("player",
|
||||||
|
Types.PlayerStream.class,
|
||||||
|
Void.class,
|
||||||
|
Void.class);
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// SEND PLAYER
|
||||||
|
// =========================
|
||||||
|
public static final Method<SendPlayerRequest, Void, String> SendPlayer = new Method<>("send_player",
|
||||||
|
SendPlayerRequest.class,
|
||||||
|
Void.class,
|
||||||
|
String.class);
|
||||||
|
}
|
||||||
+4
-4
@@ -1,8 +1,10 @@
|
|||||||
package org.saturnclient.saturnclient.auth;
|
package org.saturnclient.saturnclient.client.player;
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||||
|
|
||||||
import net.minecraft.client.network.PlayerListEntry;
|
import net.minecraft.client.network.PlayerListEntry;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
|
||||||
@@ -45,8 +47,6 @@ public class PlayerTracker {
|
|||||||
|
|
||||||
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
|
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
|
||||||
GameProfile profile = player.getProfile();
|
GameProfile profile = player.getProfile();
|
||||||
String uuid = profile.getId().toString();
|
ServiceClient.player(profile.getId(), profile.getName());
|
||||||
if (uuid != Auth.uuid)
|
|
||||||
Auth.player(profile.getName(), uuid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package org.saturnclient.saturnclient.client.player;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.Config;
|
||||||
|
|
||||||
|
import net.minecraft.util.Formatting;
|
||||||
|
|
||||||
|
public class Roles {
|
||||||
|
static enum Role {
|
||||||
|
OWNER,
|
||||||
|
ADMIN,
|
||||||
|
PARTNER,
|
||||||
|
CONTRIBUTOR
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<UUID, Role> ROLES = Map.of(
|
||||||
|
UUID.fromString("d362a042-28bd-49e1-a807-ae74dbe8aba9"),
|
||||||
|
Role.OWNER,
|
||||||
|
UUID.fromString("d8dc7023-179b-4460-bcf6-493fbf1289a6"),
|
||||||
|
Role.CONTRIBUTOR);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets the icon color of a specific individual, here are the different colors
|
||||||
|
*
|
||||||
|
* - Owner: Dark Red
|
||||||
|
* - Admin: Red
|
||||||
|
* - Partners: Gold
|
||||||
|
* - Contributor: Aqua
|
||||||
|
* - Other/player: White
|
||||||
|
*/
|
||||||
|
public static Formatting getIconColor(UUID uuid) {
|
||||||
|
Role role = ROLES.get(uuid);
|
||||||
|
|
||||||
|
if (role == null) {
|
||||||
|
return Formatting.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case OWNER:
|
||||||
|
return Formatting.DARK_RED;
|
||||||
|
|
||||||
|
case ADMIN:
|
||||||
|
return Formatting.RED;
|
||||||
|
|
||||||
|
case PARTNER:
|
||||||
|
return Formatting.GOLD;
|
||||||
|
|
||||||
|
case CONTRIBUTOR:
|
||||||
|
return Formatting.AQUA;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Formatting.WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSaturnIndicator() {
|
||||||
|
return Config.realisticLogo.value ? "" : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.saturnclient.saturnclient.client.player;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||||
|
|
||||||
|
public class SaturnPlayer {
|
||||||
|
private static Map<UUID, SaturnPlayer> PLAYERS = new HashMap<>();
|
||||||
|
private static Map<String, UUID> PLAYER_NAMES = new HashMap<>();
|
||||||
|
|
||||||
|
public String cloak = "";
|
||||||
|
public String hat = "";
|
||||||
|
public final UUID uuid;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public SaturnPlayer(UUID uuid, String name, String cloak, String hat) {
|
||||||
|
this.cloak = cloak;
|
||||||
|
this.hat = hat;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static SaturnPlayer get() {
|
||||||
|
return PLAYERS.get(ServiceClient.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static SaturnPlayer get(UUID uuid) {
|
||||||
|
return PLAYERS.get(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static SaturnPlayer get(String name) {
|
||||||
|
UUID uuid = PLAYER_NAMES.get(name);
|
||||||
|
if (uuid == null)
|
||||||
|
return null;
|
||||||
|
return PLAYERS.get(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(SaturnPlayer player) {
|
||||||
|
PLAYER_NAMES.put(player.name, player.uuid);
|
||||||
|
PLAYERS.put(player.uuid, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getExternalUUIDAsString() {
|
||||||
|
return PLAYERS.keySet().stream().filter(id -> !id.equals(ServiceClient.uuid)).map(UUID::toString)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.saturnclient.saturnclient.config;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
|
public class AnimationConfig {
|
||||||
|
public static ConfigManager config;
|
||||||
|
|
||||||
|
// Logo
|
||||||
|
public static final AnimationConfig logo = new AnimationConfig(true, 700, 0);
|
||||||
|
|
||||||
|
// Main menu
|
||||||
|
public static final AnimationConfig mainMenu = new AnimationConfig(true, 700, 120);
|
||||||
|
|
||||||
|
// Mod menu
|
||||||
|
public static final AnimationConfig modMenu = new AnimationConfig(true, 100, 20);
|
||||||
|
|
||||||
|
// Shift menu
|
||||||
|
public static final AnimationConfig shiftMenu = new AnimationConfig(true, 100, 100);
|
||||||
|
|
||||||
|
// Cosmetics menu
|
||||||
|
public static final AnimationConfig cosmeticsMenu = new AnimationConfig(true, 700, 40);
|
||||||
|
|
||||||
|
// Emote wheel
|
||||||
|
public static final AnimationConfig emoteWheel = new AnimationConfig(true, 200, 40);
|
||||||
|
|
||||||
|
public static void init(ConfigManager parent) {
|
||||||
|
config = new ConfigManager(parent, "Animations");
|
||||||
|
|
||||||
|
logo.init("Logo");
|
||||||
|
mainMenu.init("Main Menu");
|
||||||
|
modMenu.init("Mod Menu");
|
||||||
|
shiftMenu.init("Shift Menu");
|
||||||
|
cosmeticsMenu.init("Cosmetics Menu");
|
||||||
|
emoteWheel.init("Emote Wheel");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation properties
|
||||||
|
public final Property<Integer> duration;
|
||||||
|
public final Property<Integer> stagger;
|
||||||
|
public final Property<Integer> curve;
|
||||||
|
|
||||||
|
public AnimationConfig(boolean enabled, int delay, int stagger) {
|
||||||
|
this.duration = Property.integer(delay);
|
||||||
|
this.stagger = Property.integer(stagger);
|
||||||
|
this.curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic", "Ease In Out Back");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(String name) {
|
||||||
|
// config.property("Enable " + name + " Animations", this.enabled);
|
||||||
|
config.property(name + " Duration", this.duration);
|
||||||
|
config.property(name + " Stagger", this.stagger);
|
||||||
|
config.property(name + " Curve", this.curve);
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-27
@@ -1,50 +1,37 @@
|
|||||||
package org.saturnclient.saturnclient;
|
package org.saturnclient.saturnclient.config;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
import org.saturnclient.ui2.resources.Textures;
|
||||||
|
|
||||||
import net.minecraft.util.Formatting;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
public class SaturnClientConfig {
|
public class Config {
|
||||||
public static ConfigManager config;
|
public static ConfigManager config;
|
||||||
|
|
||||||
public static Property<Boolean> realisticLogo = Property.bool(false);
|
public static Property<Boolean> realisticLogo = Property.bool(false);
|
||||||
|
|
||||||
public static Property<Boolean> saturnTitleScreen = Property.bool(true);
|
public static Property<Boolean> saturnTitleScreen = Property.bool(true);
|
||||||
|
public static Property<Boolean> cloakPhysics = Property.bool(true);
|
||||||
public static Property<Integer> openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B);
|
public static Property<Integer> openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B);
|
||||||
|
public static Property<Boolean> stagger = Property.bool(true);
|
||||||
|
|
||||||
public static Identifier getLogo() {
|
public static Identifier getLogo() {
|
||||||
return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO;
|
return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSaturnIndicator() {
|
|
||||||
return realisticLogo.value ? "" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Gets the icon color of a specific individual, here are the different colors
|
|
||||||
*
|
|
||||||
* - Owner: Dark Red
|
|
||||||
* - Admin: Red
|
|
||||||
* - Partners: Gold
|
|
||||||
* - Contributor: Aqua
|
|
||||||
* - Other/player: White
|
|
||||||
*/
|
|
||||||
public static Formatting getIconColor(String uuid) {
|
|
||||||
if (uuid.equals("d362a04228bd49e1a807ae74dbe8aba9")) {
|
|
||||||
return Formatting.DARK_RED; // Owner
|
|
||||||
}
|
|
||||||
|
|
||||||
return Formatting.WHITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
config = new ConfigManager("Saturn Client");
|
config = new ConfigManager("Saturn Client");
|
||||||
config.property("Realistic logo", realisticLogo);
|
config.property("Realistic logo", realisticLogo);
|
||||||
config.property("Saturn client title screen", saturnTitleScreen);
|
config.property("Saturn client title screen", saturnTitleScreen);
|
||||||
config.property("Open Emote Wheel", openEmoteWheel);
|
config.property("Open Emote Wheel", openEmoteWheel);
|
||||||
|
config.property("Cloak Physics", cloakPhysics);
|
||||||
|
config.property("Stagger Animations", stagger);
|
||||||
|
|
||||||
|
// Initialize a sub namespace for theme
|
||||||
|
Theme.init(config);
|
||||||
|
|
||||||
|
// Initialize a sub namespace for animations
|
||||||
|
AnimationConfig.init(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.config;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
|
|
||||||
public class ConfigManager {
|
|
||||||
private static File configFile = new File(
|
|
||||||
SaturnClient.client.runDirectory,
|
|
||||||
"saturn.json");
|
|
||||||
private static Map<String, Map<String, Property<?>>> properties = new HashMap<>();
|
|
||||||
private static JsonObject cachedThemeJson = null;
|
|
||||||
|
|
||||||
private Map<String, Property<?>> currentMap;
|
|
||||||
private String namespace;
|
|
||||||
|
|
||||||
public ConfigManager(String namespace) {
|
|
||||||
currentMap = new LinkedHashMap<>();
|
|
||||||
properties.put(namespace, currentMap);
|
|
||||||
System.out.println("Created namespace: " + namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigManager(ConfigManager config, String namespace) {
|
|
||||||
currentMap = new LinkedHashMap<>();
|
|
||||||
Property<Map<String, Property<?>>> namespaceProperty = Property.namespace(currentMap);
|
|
||||||
config.property(namespace, namespaceProperty);
|
|
||||||
// Update the parent's state
|
|
||||||
config.currentMap.put(namespace, namespaceProperty);
|
|
||||||
System.out.println("Created sub-namespace: " + namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sub(String namespace) {
|
|
||||||
Property<Map<String, Property<?>>> namespaceProperty = Property.namespace(currentMap);
|
|
||||||
property(namespace, namespaceProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic method to store any type of property
|
|
||||||
public <T> Property<T> property(String name, Property<T> value) {
|
|
||||||
System.out.println("Adding property: " + name);
|
|
||||||
currentMap.put(name, value);
|
|
||||||
loadProp(name, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T> void loadProp(String name, Property<T> prop) {
|
|
||||||
if (cachedThemeJson != null) {
|
|
||||||
JsonElement element = cachedThemeJson.get(namespace);
|
|
||||||
if (element != null && element.isJsonObject()) {
|
|
||||||
JsonObject theme = element.getAsJsonObject();
|
|
||||||
JsonElement value = theme.get(name);
|
|
||||||
if (value != null && prop.matchesJson(value)) {
|
|
||||||
switch (prop.getType()) {
|
|
||||||
case BOOLEAN:
|
|
||||||
((Property<Boolean>) prop).setValue(value.getAsBoolean());
|
|
||||||
break;
|
|
||||||
case INTEGER:
|
|
||||||
((Property<Integer>) prop).setValue(value.getAsInt());
|
|
||||||
break;
|
|
||||||
case FLOAT:
|
|
||||||
((Property<Float>) prop).setValue(value.getAsFloat());
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
((Property<String>) prop).setValue(value.getAsString());
|
|
||||||
break;
|
|
||||||
case HEX:
|
|
||||||
((Property<Integer>) prop).setValue(value.getAsInt());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void load() {
|
|
||||||
try {
|
|
||||||
SaturnClient.LOGGER.info("Starting to load config...");
|
|
||||||
if (!configFile.exists()) {
|
|
||||||
configFile.createNewFile();
|
|
||||||
Files.write(configFile.toPath(), "{}".getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject jsonObject = JsonParser.parseString(
|
|
||||||
new String(Files.readAllBytes(configFile.toPath()))).getAsJsonObject();
|
|
||||||
|
|
||||||
for (String namespace : properties.keySet()) {
|
|
||||||
SaturnClient.LOGGER.info("Loading namespace: " + namespace);
|
|
||||||
JsonElement configElement = jsonObject.get(namespace);
|
|
||||||
|
|
||||||
if (configElement == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
JsonObject config = jsonObject.get(namespace).getAsJsonObject();
|
|
||||||
|
|
||||||
if (config == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Property<?>> propertyMap = properties.get(namespace);
|
|
||||||
loadProperties(config, propertyMap);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
SaturnClient.LOGGER.error("Error reading the config file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static void loadProperties(JsonObject config, Map<String, Property<?>> propertyMap) {
|
|
||||||
SaturnClient.LOGGER.info("Loading properties: " + propertyMap);
|
|
||||||
|
|
||||||
for (String propertyName : propertyMap.keySet()) {
|
|
||||||
JsonElement c = config.get(propertyName);
|
|
||||||
|
|
||||||
if (c == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Property<?> p = propertyMap.get(propertyName);
|
|
||||||
if (p.matchesJson(c)) {
|
|
||||||
if (p.getType() == Property.PropertyType.NAMESPACE) {
|
|
||||||
// Handle nested namespace
|
|
||||||
JsonObject nestedConfig = c.getAsJsonObject();
|
|
||||||
Map<String, Property<?>> nestedProperties = p.getNamespaceValue();
|
|
||||||
loadProperties(nestedConfig, nestedProperties);
|
|
||||||
} else {
|
|
||||||
if (p.value instanceof Integer) {
|
|
||||||
((Property<Integer>) p).value = c.getAsInt();
|
|
||||||
} else if (p.value instanceof String) {
|
|
||||||
((Property<String>) p).value = c.getAsString();
|
|
||||||
} else if (p.value instanceof Float) {
|
|
||||||
((Property<Float>) p).value = c.getAsFloat();
|
|
||||||
} else if (p.value instanceof Boolean) {
|
|
||||||
((Property<Boolean>) p).value = c.getAsBoolean();
|
|
||||||
} else {
|
|
||||||
SaturnClient.LOGGER.warn(
|
|
||||||
"Unknown property type for: " + propertyName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SaturnClient.LOGGER.warn(
|
|
||||||
"Property does not match JSON: " + propertyName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void save() {
|
|
||||||
try {
|
|
||||||
SaturnClient.LOGGER.info("Starting to save config...");
|
|
||||||
|
|
||||||
JsonObject jsonObject = new JsonObject();
|
|
||||||
|
|
||||||
// Iterate through all namespaces and their properties
|
|
||||||
for (String namespace : properties.keySet()) {
|
|
||||||
JsonObject namespaceConfig = new JsonObject();
|
|
||||||
Map<String, Property<?>> propertyMap = properties.get(namespace);
|
|
||||||
|
|
||||||
// Save properties for this namespace
|
|
||||||
saveProperties(namespaceConfig, propertyMap);
|
|
||||||
|
|
||||||
// Add the namespace config to the main JSON object
|
|
||||||
jsonObject.add(namespace, namespaceConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the json so it can easily be edited
|
|
||||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
String formattedJson = gson.toJson(jsonObject);
|
|
||||||
|
|
||||||
// Write the JSON object to the config file
|
|
||||||
Files.write(configFile.toPath(), formattedJson.getBytes());
|
|
||||||
SaturnClient.LOGGER.info("Config saved successfully.");
|
|
||||||
} catch (IOException e) {
|
|
||||||
SaturnClient.LOGGER.error("Error saving the config file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static void saveProperties(JsonObject config, Map<String, Property<?>> propertyMap) {
|
|
||||||
// Iterate through each property in the namespace
|
|
||||||
for (String propertyName : propertyMap.keySet()) {
|
|
||||||
Property<?> property = propertyMap.get(propertyName);
|
|
||||||
JsonElement propertyValue = null;
|
|
||||||
// Convert the property value based on its type
|
|
||||||
switch (property.getType()) {
|
|
||||||
case NAMESPACE:
|
|
||||||
// Handle nested namespace
|
|
||||||
JsonObject nestedConfig = new JsonObject();
|
|
||||||
Map<String, Property<?>> nestedProperties = (Map<String, Property<?>>) property.value;
|
|
||||||
saveProperties(nestedConfig, nestedProperties);
|
|
||||||
propertyValue = nestedConfig;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (property.value instanceof Integer) {
|
|
||||||
propertyValue = new JsonPrimitive((Integer) property.value);
|
|
||||||
} else if (property.value instanceof String) {
|
|
||||||
propertyValue = new JsonPrimitive((String) property.value);
|
|
||||||
} else if (property.value instanceof Float) {
|
|
||||||
propertyValue = new JsonPrimitive((Float) property.value);
|
|
||||||
} else if (property.value instanceof Boolean) {
|
|
||||||
propertyValue = new JsonPrimitive((Boolean) property.value);
|
|
||||||
} else {
|
|
||||||
SaturnClient.LOGGER.warn(
|
|
||||||
"Unknown property type for: " + propertyName);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyValue != null) {
|
|
||||||
config.add(propertyName, propertyValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Property<?>> getProperties() {
|
|
||||||
return currentMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.config;
|
|
||||||
|
|
||||||
public class NamedProperty<T> {
|
|
||||||
public String name;
|
|
||||||
public Property<T> prop;
|
|
||||||
|
|
||||||
public NamedProperty(String name, Property<T> value) {
|
|
||||||
this.name = name;
|
|
||||||
this.prop = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.config;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
|
|
||||||
public class Property<T> {
|
|
||||||
|
|
||||||
public enum PropertyType {
|
|
||||||
BOOLEAN,
|
|
||||||
INTEGER,
|
|
||||||
FLOAT,
|
|
||||||
STRING,
|
|
||||||
HEX,
|
|
||||||
NAMESPACE,
|
|
||||||
SELECT,
|
|
||||||
KEYBINDING,
|
|
||||||
}
|
|
||||||
|
|
||||||
public T value;
|
|
||||||
public boolean isReset = false;
|
|
||||||
private T defaultValue;
|
|
||||||
private String[] availableValues;
|
|
||||||
private PropertyType type;
|
|
||||||
private boolean wasPressedLastTick = false;
|
|
||||||
|
|
||||||
private Property(T value, PropertyType type) {
|
|
||||||
this.value = value;
|
|
||||||
this.defaultValue = value;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Property<T> from(T value) {
|
|
||||||
if (value instanceof Boolean) {
|
|
||||||
return new Property<>(value, PropertyType.BOOLEAN);
|
|
||||||
} else if (value instanceof Integer) {
|
|
||||||
return new Property<>(value, PropertyType.INTEGER);
|
|
||||||
} else if (value instanceof Float) {
|
|
||||||
return new Property<>(value, PropertyType.FLOAT);
|
|
||||||
} else if (value instanceof String) {
|
|
||||||
return new Property<>(value, PropertyType.STRING);
|
|
||||||
} else if (valueIsNamespace(value)) {
|
|
||||||
return new Property<>(value, PropertyType.NAMESPACE);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> font(int value) {
|
|
||||||
return select(value, "Default", "Inter", "Inter bold");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> select(Integer value, String... availableValues) {
|
|
||||||
Property<Integer> property = new Property<>(value, PropertyType.SELECT);
|
|
||||||
property.availableValues = availableValues;
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Map<String, Property<?>>> namespace(Map<String, Property<?>> value) {
|
|
||||||
return new Property<>(value, PropertyType.NAMESPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Boolean> bool(boolean value) {
|
|
||||||
return new Property<>(value, PropertyType.BOOLEAN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> integer(int value) {
|
|
||||||
return new Property<>(value, PropertyType.INTEGER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Float> floatProp(float value) {
|
|
||||||
return new Property<>(value, PropertyType.FLOAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<String> string(String value) {
|
|
||||||
return new Property<>(value, PropertyType.STRING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> color(int value) {
|
|
||||||
return new Property<>(value, PropertyType.HEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> keybinding(int value) {
|
|
||||||
return new Property<>(value, PropertyType.KEYBINDING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void next() {
|
|
||||||
if ((Integer) value < availableValues.length-1) setValue((Integer)value+1);
|
|
||||||
else setValue(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void prev() {
|
|
||||||
if ((Integer) value > 0) setValue((Integer)value-1);
|
|
||||||
else setValue(availableValues.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelection(int selection) {
|
|
||||||
if (selection >= 0 && selection < availableValues.length && type == PropertyType.SELECT) {
|
|
||||||
setValue(selection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSelection() {
|
|
||||||
if (type == PropertyType.SELECT) {
|
|
||||||
return availableValues[(Integer) value];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
value = defaultValue;
|
|
||||||
isReset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHexString() {
|
|
||||||
if (value instanceof String) {
|
|
||||||
String str = (String) value;
|
|
||||||
if (str.startsWith("0x") || str.startsWith("0X")) {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(str.substring(2), 16);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Invalid hexadecimal integer format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Property does not contain a valid hex integer");
|
|
||||||
}
|
|
||||||
|
|
||||||
public PropertyType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Map<String, Property<?>> getNamespaceValue() {
|
|
||||||
if (type == PropertyType.NAMESPACE && value instanceof Map) {
|
|
||||||
return (Map<String, Property<?>>) value;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Property is not a namespace");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matchesJson(JsonElement element) {
|
|
||||||
if (element.isJsonPrimitive()) {
|
|
||||||
JsonPrimitive primitive = element.getAsJsonPrimitive();
|
|
||||||
if (value instanceof Boolean && primitive.isBoolean())
|
|
||||||
return true;
|
|
||||||
if (value instanceof Integer && primitive.isNumber())
|
|
||||||
return true;
|
|
||||||
if (value instanceof Float && primitive.isNumber())
|
|
||||||
return true;
|
|
||||||
if (value instanceof String && primitive.isString())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Check if JSON string represents a valid hex integer
|
|
||||||
if (value instanceof String && primitive.isString()) {
|
|
||||||
String str = primitive.getAsString();
|
|
||||||
return str.matches("0x[0-9A-Fa-f]+");
|
|
||||||
}
|
|
||||||
} else if (element.isJsonObject()) {
|
|
||||||
return isNamespace(value);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.valueOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNamespace(Object obj) {
|
|
||||||
return type == PropertyType.NAMESPACE && value instanceof Map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean valueIsNamespace(Object obj) {
|
|
||||||
return obj instanceof Map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void setValue(Object value) {
|
|
||||||
this.value = (T) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void setValue(Object value, Object defaultValue) {
|
|
||||||
this.value = (T) value;
|
|
||||||
this.defaultValue = (T) defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NamedProperty<T> named(String name) {
|
|
||||||
return new NamedProperty<>(name, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isKeyPressed() {
|
|
||||||
return (Integer) value == -1 ? false : GLFW.glfwGetKey(SaturnClient.client.getWindow().getHandle(), (Integer) (Object) value) == GLFW.GLFW_PRESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasKeyPressed() {
|
|
||||||
long window = SaturnClient.client.getWindow().getHandle();
|
|
||||||
int key = (Integer) value;
|
|
||||||
|
|
||||||
boolean isPressed = key != -1 && GLFW.glfwGetKey(window, key) == GLFW.GLFW_PRESS;
|
|
||||||
|
|
||||||
boolean wasJustPressed = isPressed && !wasPressedLastTick;
|
|
||||||
wasPressedLastTick = isPressed;
|
|
||||||
|
|
||||||
return wasJustPressed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.saturnclient.saturnclient.config;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
|
|
||||||
|
public class Theme {
|
||||||
|
// Main UI background (slightly warm dark to match Saturn tone)
|
||||||
|
public static final Property<Integer> BACKGROUND = Property.color(0xFF12100D);
|
||||||
|
|
||||||
|
// Default text color
|
||||||
|
public static final Property<Integer> FOREGROUND = Property.color(0xFFF2E6D2);
|
||||||
|
|
||||||
|
// Widget default background (normal state)
|
||||||
|
public static final Property<Integer> PRIMARY = Property.color(0xFF1C1915);
|
||||||
|
|
||||||
|
// Widget text color
|
||||||
|
public static final Property<Integer> PRIMARY_FG = Property.color(0xFFEAD9B8);
|
||||||
|
|
||||||
|
// Saturn brand color (hovered / enabled / selected)
|
||||||
|
public static final Property<Integer> ACCENT = Property.color(0xFFE2BF7D);
|
||||||
|
|
||||||
|
// Text/icons shown on accent background
|
||||||
|
public static final Property<Integer> ACCENT_FG = Property.color(0xFF1A140A);
|
||||||
|
|
||||||
|
// Misc colors
|
||||||
|
public static final Property<Integer> SCROLL = Property.color(0xFFE2BF7D);
|
||||||
|
|
||||||
|
// UI metrics
|
||||||
|
public static final Property<Integer> BG_RADIUS = Property.integer(12);
|
||||||
|
public static final Property<Integer> WIDGET_RADIUS = Property.integer(10);
|
||||||
|
|
||||||
|
// Font
|
||||||
|
public static final Property<Integer> FONT = Property.font(2);
|
||||||
|
|
||||||
|
public static void init(ConfigManager parent) {
|
||||||
|
ConfigManager config = new ConfigManager(parent, "UI Style");
|
||||||
|
config.property("Background", BACKGROUND);
|
||||||
|
config.property("Foreground", FOREGROUND);
|
||||||
|
config.property("Primary", PRIMARY);
|
||||||
|
config.property("Primary Foreground", PRIMARY_FG);
|
||||||
|
config.property("Accent", ACCENT);
|
||||||
|
config.property("Accent Foreground", ACCENT_FG);
|
||||||
|
config.property("Scrollbar Color", SCROLL);
|
||||||
|
|
||||||
|
config.property("Background Radius", BG_RADIUS);
|
||||||
|
config.property("Widget Radius", WIDGET_RADIUS);
|
||||||
|
|
||||||
|
config.property("Font", FONT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* primary by default, accent if true
|
||||||
|
*/
|
||||||
|
public static int getBg(boolean hover) {
|
||||||
|
return hover ? ACCENT.value : PRIMARY.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Foreground) primary by default, accent if true
|
||||||
|
*/
|
||||||
|
public static int getFg(boolean hover) {
|
||||||
|
return hover ? ACCENT_FG.value : PRIMARY_FG.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies existing alpha with 0–255 alpha.
|
||||||
|
*/
|
||||||
|
public static int withAlpha(int alpha, int color) {
|
||||||
|
alpha = Math.max(0, Math.min(255, alpha));
|
||||||
|
|
||||||
|
int originalAlpha = (color >>> 24) & 0xFF;
|
||||||
|
|
||||||
|
// Merge (multiply) alphas
|
||||||
|
int merged = (originalAlpha * alpha) / 255;
|
||||||
|
|
||||||
|
return (color & 0x00FFFFFF) | (merged << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies existing alpha with 0.0–1.0 alpha.
|
||||||
|
*/
|
||||||
|
public static int withAlpha(float alpha, int color) {
|
||||||
|
alpha = Math.max(0f, Math.min(1f, alpha));
|
||||||
|
|
||||||
|
int originalAlpha = (color >>> 24) & 0xFF;
|
||||||
|
|
||||||
|
int merged = (int) (originalAlpha * alpha);
|
||||||
|
|
||||||
|
return (color & 0x00FFFFFF) | (merged << 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.config;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
|
|
||||||
public class ThemeManager {
|
|
||||||
|
|
||||||
private static File themeFile = new File(
|
|
||||||
SaturnClient.client.runDirectory,
|
|
||||||
"saturn.theme.json");
|
|
||||||
private static Map<String, Map<String, Property<?>>> properties = new HashMap<>();
|
|
||||||
private static JsonObject cachedThemeJson = null;
|
|
||||||
|
|
||||||
// private Map<String, Map<String, Property<?>>> states;
|
|
||||||
private Map<String, Property<?>> currentStyling;
|
|
||||||
private String state;
|
|
||||||
private String[] states;
|
|
||||||
private String namespace;
|
|
||||||
|
|
||||||
public ThemeManager(String namespace, String... states) {
|
|
||||||
// this.states = new LinkedHashMap<>();
|
|
||||||
this.namespace = namespace;
|
|
||||||
this.states = states;
|
|
||||||
currentStyling = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
properties.put(namespace, new LinkedHashMap<>());
|
|
||||||
|
|
||||||
for (String s : states) {
|
|
||||||
properties.put(namespace+"@"+s, new LinkedHashMap<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setState(String state) {
|
|
||||||
this.state = state;
|
|
||||||
if (this.state != null) {
|
|
||||||
for (Map.Entry<String, Property<?>> tProp : properties.get(namespace+"@"+state).entrySet()) {
|
|
||||||
currentStyling.get(tProp.getKey()).setValue(tProp.getValue().value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (Map.Entry<String, Property<?>> tProp : properties.get(namespace).entrySet()) {
|
|
||||||
Property<?> p = currentStyling.get(tProp.getKey());
|
|
||||||
if (p != null) p.setValue(tProp.getValue().value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void applyState(String state) {
|
|
||||||
if (this.state != null) {
|
|
||||||
for (Map.Entry<String, Property<?>> tProp : properties.get(namespace+"@"+state).entrySet()) {
|
|
||||||
Property<?> p = currentStyling.get(tProp.getKey());
|
|
||||||
Property<?> currProp = properties.get(namespace).get(tProp.getKey());
|
|
||||||
if (currProp != null && p.value.equals(currProp.value)) {
|
|
||||||
p.setValue(tProp.getValue().value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setState(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic method to store any type of property
|
|
||||||
public <T> Property<T> property(String name, Property<T> prop) {
|
|
||||||
for (String state : states) {
|
|
||||||
Property<?> property = Property.from(prop.value);
|
|
||||||
properties.get(namespace+"@"+state).put(name, property);
|
|
||||||
loadProp(state, name, property);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadProp(null, name, prop);
|
|
||||||
properties.get(namespace).put(name, Property.from(prop.value));
|
|
||||||
currentStyling.put(name, prop);
|
|
||||||
|
|
||||||
return prop;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> void propertyStateDefault(String state, String name, T value) {
|
|
||||||
Property<?> prop = properties.get(namespace+"@"+state).get(name);
|
|
||||||
prop.setValue(value, value); // sets the current and default value
|
|
||||||
loadProp(state, name, prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T> void loadProp(String state, String name, Property<T> prop) {
|
|
||||||
if (cachedThemeJson == null) {
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonElement element = cachedThemeJson.get(state != null ? namespace+"@"+state : namespace);
|
|
||||||
if (element != null && element.isJsonObject()) {
|
|
||||||
JsonObject theme = element.getAsJsonObject();
|
|
||||||
JsonElement value = theme.get(name);
|
|
||||||
if (value != null && prop.matchesJson(value)) {
|
|
||||||
switch (prop.getType()) {
|
|
||||||
case BOOLEAN:
|
|
||||||
((Property<Boolean>) prop).setValue(value.getAsBoolean());
|
|
||||||
break;
|
|
||||||
case INTEGER:
|
|
||||||
((Property<Integer>) prop).setValue(value.getAsInt());
|
|
||||||
break;
|
|
||||||
case FLOAT:
|
|
||||||
((Property<Float>) prop).setValue(value.getAsFloat());
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
((Property<String>) prop).setValue(value.getAsString());
|
|
||||||
break;
|
|
||||||
case HEX:
|
|
||||||
((Property<Integer>) prop).setValue(value.getAsInt());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void load() {
|
|
||||||
try {
|
|
||||||
SaturnClient.LOGGER.info("Starting to load theme...");
|
|
||||||
if (!themeFile.exists()) {
|
|
||||||
themeFile.createNewFile();
|
|
||||||
Files.write(themeFile.toPath(), "{}".getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedThemeJson = JsonParser.parseString(
|
|
||||||
new String(Files.readAllBytes(themeFile.toPath()))).getAsJsonObject();
|
|
||||||
|
|
||||||
SaturnClient.LOGGER.info("Theme loaded");
|
|
||||||
} catch (IOException e) {
|
|
||||||
SaturnClient.LOGGER.error("Error reading the theme file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void save() {
|
|
||||||
try {
|
|
||||||
SaturnClient.LOGGER.info("Starting to save theme...");
|
|
||||||
|
|
||||||
JsonObject jsonObject = new JsonObject();
|
|
||||||
|
|
||||||
// Iterate through all namespaces and their properties
|
|
||||||
for (String namespace : properties.keySet()) {
|
|
||||||
JsonObject namespaceTheme = new JsonObject();
|
|
||||||
Map<String, Property<?>> propertyMap = properties.get(namespace);
|
|
||||||
|
|
||||||
// Save properties for this namespace
|
|
||||||
saveProperties(namespaceTheme, propertyMap);
|
|
||||||
|
|
||||||
// Add the namespace theme to the main JSON object
|
|
||||||
jsonObject.add(namespace, namespaceTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the json so it can easily be edited
|
|
||||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
String formattedJson = gson.toJson(jsonObject);
|
|
||||||
|
|
||||||
// Write the JSON object to the theme file
|
|
||||||
Files.write(themeFile.toPath(), formattedJson.getBytes());
|
|
||||||
SaturnClient.LOGGER.info("Theme saved successfully.");
|
|
||||||
} catch (IOException e) {
|
|
||||||
SaturnClient.LOGGER.error("Error saving the theme file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static void saveProperties(JsonObject theme, Map<String, Property<?>> propertyMap) {
|
|
||||||
// Iterate through each property in the namespace
|
|
||||||
for (String propertyName : propertyMap.keySet()) {
|
|
||||||
Property<?> property = propertyMap.get(propertyName);
|
|
||||||
JsonElement propertyValue = null;
|
|
||||||
|
|
||||||
if (property.getType() == Property.PropertyType.NAMESPACE) {
|
|
||||||
// Handle nested namespace
|
|
||||||
JsonObject nestedTheme = new JsonObject();
|
|
||||||
Map<String, Property<?>> nestedProperties = (Map<String, Property<?>>) property.value;
|
|
||||||
saveProperties(nestedTheme, nestedProperties);
|
|
||||||
propertyValue = nestedTheme;
|
|
||||||
} else {
|
|
||||||
// Convert the property value based on its type
|
|
||||||
switch (property.getType()) {
|
|
||||||
case BOOLEAN:
|
|
||||||
propertyValue = new JsonPrimitive((Boolean) property.value);
|
|
||||||
break;
|
|
||||||
case INTEGER:
|
|
||||||
propertyValue = new JsonPrimitive((Integer) property.value);
|
|
||||||
break;
|
|
||||||
case FLOAT:
|
|
||||||
propertyValue = new JsonPrimitive((Float) property.value);
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
propertyValue = new JsonPrimitive((String) property.value);
|
|
||||||
break;
|
|
||||||
case HEX:
|
|
||||||
propertyValue = new JsonPrimitive((Integer) property.value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SaturnClient.LOGGER.warn(
|
|
||||||
"Unknown property type for: " + propertyName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyValue != null) {
|
|
||||||
theme.add(propertyName, propertyValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package org.saturnclient.saturnclient.config.manager;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ConfigManager {
|
||||||
|
|
||||||
|
private static final File configFile = new File(SaturnClient.client.runDirectory, "saturn.json");
|
||||||
|
|
||||||
|
// SINGLE ROOT TREE
|
||||||
|
private static final Map<String, Property<?>> ROOT = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private static JsonObject cachedJson = null;
|
||||||
|
|
||||||
|
private final Map<String, Property<?>> currentMap;
|
||||||
|
|
||||||
|
private final String[] path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a root namespace
|
||||||
|
*/
|
||||||
|
public ConfigManager(String namespace) {
|
||||||
|
this.currentMap = new LinkedHashMap<>();
|
||||||
|
this.path = new String[] { namespace }; // Path is just ["namespace"]
|
||||||
|
ROOT.put(namespace, Property.namespace(currentMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a sub-namespace inside a parent
|
||||||
|
*/
|
||||||
|
public ConfigManager(ConfigManager parent, String namespace) {
|
||||||
|
this.currentMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
// Build new path: parent path + current namespace
|
||||||
|
this.path = new String[parent.path.length + 1];
|
||||||
|
System.arraycopy(parent.path, 0, this.path, 0, parent.path.length);
|
||||||
|
this.path[this.path.length - 1] = namespace;
|
||||||
|
|
||||||
|
parent.property(namespace, Property.namespace(currentMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a property
|
||||||
|
*/
|
||||||
|
public <T> Property<T> property(String name, Property<T> value) {
|
||||||
|
currentMap.put(name, value);
|
||||||
|
loadSingleProperty(name, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add empty namespace
|
||||||
|
*/
|
||||||
|
public ConfigManager sub(String name) {
|
||||||
|
return new ConfigManager(this, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load entire config
|
||||||
|
*/
|
||||||
|
public static void load() {
|
||||||
|
SaturnClient.LOGGER.info("Loading config...");
|
||||||
|
|
||||||
|
JsonObject json = loadAndCache();
|
||||||
|
if (json == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
loadRecursive(json, ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save entire config
|
||||||
|
*/
|
||||||
|
public static void save() {
|
||||||
|
SaturnClient.LOGGER.info("Saving config...");
|
||||||
|
|
||||||
|
JsonObject rootJson = new JsonObject();
|
||||||
|
saveRecursive(rootJson, ROOT);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String formatted = new GsonBuilder()
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.create()
|
||||||
|
.toJson(rootJson);
|
||||||
|
|
||||||
|
Files.writeString(configFile.toPath(), formatted);
|
||||||
|
SaturnClient.LOGGER.info("Config saved successfully.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
SaturnClient.LOGGER.error("Error saving config file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive save
|
||||||
|
*/
|
||||||
|
private static void saveRecursive(JsonObject json, Map<String, Property<?>> map) {
|
||||||
|
map.forEach((name, prop) -> {
|
||||||
|
if (prop.isNamespace()) {
|
||||||
|
JsonObject child = new JsonObject();
|
||||||
|
saveRecursive(child, prop.getNamespaceMap());
|
||||||
|
json.add(name, child);
|
||||||
|
} else {
|
||||||
|
json.add(name, prop.toJson());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive load
|
||||||
|
*/
|
||||||
|
private static void loadRecursive(JsonObject json, Map<String, Property<?>> map) {
|
||||||
|
map.forEach((name, prop) -> {
|
||||||
|
JsonElement element = json.get(name);
|
||||||
|
if (element == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (prop.isNamespace() && element.isJsonObject()) {
|
||||||
|
loadRecursive(element.getAsJsonObject(), prop.getNamespaceMap());
|
||||||
|
} else {
|
||||||
|
prop.loadFromJson(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load only a single property immediately after creation,
|
||||||
|
* traversing the JSON tree based on this manager's path.
|
||||||
|
*/
|
||||||
|
private void loadSingleProperty(String name, Property<?> prop) {
|
||||||
|
JsonObject source = cachedJson != null ? cachedJson : loadAndCache();
|
||||||
|
if (source == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Traverse the tree to the correct namespace
|
||||||
|
JsonObject currentScope = source;
|
||||||
|
for (String segment : path) {
|
||||||
|
JsonElement next = currentScope.get(segment);
|
||||||
|
if (next != null && next.isJsonObject()) {
|
||||||
|
currentScope = next.getAsJsonObject();
|
||||||
|
} else {
|
||||||
|
return; // Path doesn't exist in config yet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now look for the property inside the correctly nested object
|
||||||
|
JsonElement element = currentScope.get(name);
|
||||||
|
if (element != null) {
|
||||||
|
prop.loadFromJson(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads file and caches json
|
||||||
|
*/
|
||||||
|
private static JsonObject loadAndCache() {
|
||||||
|
try {
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
Files.writeString(configFile.toPath(), "{}");
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = Files.readString(configFile.toPath());
|
||||||
|
cachedJson = JsonParser.parseString(json).getAsJsonObject();
|
||||||
|
return cachedJson;
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
SaturnClient.LOGGER.error("Error reading config file", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Property<?>> getProperties() {
|
||||||
|
return currentMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
package org.saturnclient.saturnclient.config.manager;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
|
||||||
|
public class Property<T> {
|
||||||
|
public static class NamedProperty<T> {
|
||||||
|
public String name;
|
||||||
|
public Property<T> prop;
|
||||||
|
|
||||||
|
public NamedProperty(String name, Property<T> value) {
|
||||||
|
this.name = name;
|
||||||
|
this.prop = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PropertyType {
|
||||||
|
BOOLEAN,
|
||||||
|
INTEGER,
|
||||||
|
FLOAT,
|
||||||
|
STRING,
|
||||||
|
HEX,
|
||||||
|
NAMESPACE,
|
||||||
|
SELECT,
|
||||||
|
KEYBINDING,
|
||||||
|
}
|
||||||
|
|
||||||
|
public T value;
|
||||||
|
public boolean isReset = false;
|
||||||
|
private T defaultValue;
|
||||||
|
private String[] availableValues;
|
||||||
|
private final PropertyType type;
|
||||||
|
private boolean wasPressedLastTick = false;
|
||||||
|
|
||||||
|
private Property(T value, PropertyType type) {
|
||||||
|
this.value = value;
|
||||||
|
this.defaultValue = value;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Factory Methods ----------
|
||||||
|
public static <T> Property<T> from(T value) {
|
||||||
|
if (value instanceof Boolean)
|
||||||
|
return new Property<>(value, PropertyType.BOOLEAN);
|
||||||
|
if (value instanceof Integer)
|
||||||
|
return new Property<>(value, PropertyType.INTEGER);
|
||||||
|
if (value instanceof Float)
|
||||||
|
return new Property<>(value, PropertyType.FLOAT);
|
||||||
|
if (value instanceof String)
|
||||||
|
return new Property<>(value, PropertyType.STRING);
|
||||||
|
if (valueIsNamespace(value))
|
||||||
|
return new Property<>(value, PropertyType.NAMESPACE);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Integer> font(int value) {
|
||||||
|
return select(value, "Default", "Inter", "Inter bold");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Integer> select(Integer value, String... availableValues) {
|
||||||
|
Property<Integer> property = new Property<>(value, PropertyType.SELECT);
|
||||||
|
property.availableValues = availableValues;
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Map<String, Property<?>>> namespace(Map<String, Property<?>> value) {
|
||||||
|
return new Property<>(value, PropertyType.NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Boolean> bool(boolean value) {
|
||||||
|
return new Property<>(value, PropertyType.BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Integer> integer(int value) {
|
||||||
|
return new Property<>(value, PropertyType.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Float> floatProp(float value) {
|
||||||
|
return new Property<>(value, PropertyType.FLOAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<String> string(String value) {
|
||||||
|
return new Property<>(value, PropertyType.STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Integer> color(int value) {
|
||||||
|
return new Property<>(value, PropertyType.HEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property<Integer> keybinding(int value) {
|
||||||
|
return new Property<>(value, PropertyType.KEYBINDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Select Helpers ----------
|
||||||
|
public void next() {
|
||||||
|
if (type == PropertyType.SELECT) {
|
||||||
|
int i = (Integer) value;
|
||||||
|
setValue((i < availableValues.length - 1) ? i + 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prev() {
|
||||||
|
if (type == PropertyType.SELECT) {
|
||||||
|
int i = (Integer) value;
|
||||||
|
setValue((i > 0) ? i - 1 : availableValues.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelection(int selection) {
|
||||||
|
if (type == PropertyType.SELECT && selection >= 0 && selection < availableValues.length) {
|
||||||
|
setValue(selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelection() {
|
||||||
|
return (type == PropertyType.SELECT) ? availableValues[(Integer) value] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Lifecycle ----------
|
||||||
|
public void reset() {
|
||||||
|
value = defaultValue;
|
||||||
|
isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, Property<?>> getNamespaceValue() {
|
||||||
|
if (type == PropertyType.NAMESPACE && value instanceof Map) {
|
||||||
|
return (Map<String, Property<?>>) value;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Property is not a namespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- JSON Serialization ----------
|
||||||
|
/*
|
||||||
|
* BOOLEAN,
|
||||||
|
* INTEGER,
|
||||||
|
* FLOAT,
|
||||||
|
* STRING,
|
||||||
|
* HEX,
|
||||||
|
* NAMESPACE,
|
||||||
|
* SELECT,
|
||||||
|
* KEYBINDING,
|
||||||
|
*/
|
||||||
|
public JsonElement toJson() {
|
||||||
|
switch (type) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return new JsonPrimitive((Boolean) value);
|
||||||
|
case INTEGER:
|
||||||
|
return new JsonPrimitive((Integer) value);
|
||||||
|
case FLOAT:
|
||||||
|
return new JsonPrimitive((Float) value);
|
||||||
|
case NAMESPACE:
|
||||||
|
JsonObject nested = new JsonObject();
|
||||||
|
getNamespaceValue().forEach((k, v) -> nested.add(k, v.toJson()));
|
||||||
|
return nested;
|
||||||
|
case HEX:
|
||||||
|
return new JsonPrimitive(String.format("#%08X", (Integer) value));
|
||||||
|
case KEYBINDING:
|
||||||
|
return new JsonPrimitive((Integer) value);
|
||||||
|
case SELECT:
|
||||||
|
return new JsonPrimitive((Integer) value);
|
||||||
|
default:
|
||||||
|
return new JsonPrimitive(String.valueOf(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFromJson(JsonElement element) {
|
||||||
|
if (element == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case BOOLEAN:
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean())
|
||||||
|
setValue(element.getAsBoolean());
|
||||||
|
break;
|
||||||
|
case INTEGER:
|
||||||
|
if (element.isJsonPrimitive()) {
|
||||||
|
JsonPrimitive p = element.getAsJsonPrimitive();
|
||||||
|
if (p.isNumber())
|
||||||
|
setValue(p.getAsInt());
|
||||||
|
else if (p.isString())
|
||||||
|
setValue(parseHexToInt(p.getAsString()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FLOAT:
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber())
|
||||||
|
setValue(element.getAsFloat());
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString())
|
||||||
|
setValue(element.getAsString());
|
||||||
|
break;
|
||||||
|
case HEX:
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString())
|
||||||
|
setValue(parseHexToInt(element.getAsString()));
|
||||||
|
break;
|
||||||
|
case NAMESPACE:
|
||||||
|
if (element.isJsonObject()) {
|
||||||
|
JsonObject obj = element.getAsJsonObject();
|
||||||
|
getNamespaceValue().forEach((k, v) -> v.loadFromJson(obj.get(k)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SELECT:
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber())
|
||||||
|
setSelection(element.getAsInt());
|
||||||
|
break;
|
||||||
|
case KEYBINDING:
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber())
|
||||||
|
setValue(element.getAsInt());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Utility ----------
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean valueIsNamespace(Object obj) {
|
||||||
|
return obj instanceof Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void setValue(Object value) {
|
||||||
|
this.value = (T) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void setValue(Object value, Object defaultValue) {
|
||||||
|
this.value = (T) value;
|
||||||
|
this.defaultValue = (T) defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamedProperty<T> named(String name) {
|
||||||
|
return new NamedProperty<>(name, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Keybindings ----------
|
||||||
|
public boolean isKeyPressed() {
|
||||||
|
return (Integer) value != -1 &&
|
||||||
|
GLFW.glfwGetKey(SaturnClient.client.getWindow().getHandle(), (Integer) value) == GLFW.GLFW_PRESS &&
|
||||||
|
SaturnClient.client.currentScreen == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasKeyPressed() {
|
||||||
|
boolean pressed = isKeyPressed();
|
||||||
|
boolean result = pressed && !wasPressedLastTick;
|
||||||
|
wasPressedLastTick = pressed;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- HEX ----------
|
||||||
|
public static int parseHexToInt(String hex) {
|
||||||
|
hex = hex.replace("#", "");
|
||||||
|
if (hex.length() == 6)
|
||||||
|
hex = "FF" + hex;
|
||||||
|
if (hex.length() != 8)
|
||||||
|
throw new IllegalArgumentException("Hex must be 6 or 8 chars long, got \'" + hex + "\'");
|
||||||
|
return (int) Long.parseLong(hex, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
public Property<T> copy() {
|
||||||
|
return new Property<>(this.value, this.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(String name, JsonElement element) {
|
||||||
|
if (element != null && element.isJsonObject()) {
|
||||||
|
load(name, element.getAsJsonObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(String name, JsonObject element) {
|
||||||
|
JsonElement value = element.get(name);
|
||||||
|
if (value != null)
|
||||||
|
this.loadFromJson(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNamespace() {
|
||||||
|
return type == PropertyType.NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, Property<?>> getNamespaceMap() {
|
||||||
|
return isNamespace() ? (Map<String, Property<?>>) (Object) value : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package org.saturnclient.saturnclient.cosmetics;
|
package org.saturnclient.saturnclient.cosmetics;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
|
import org.saturnclient.saturnclient.config.Config;
|
||||||
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
|
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
|
||||||
|
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
import net.minecraft.client.render.OverlayTexture;
|
||||||
@@ -12,13 +15,8 @@ import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
|
|||||||
import net.minecraft.client.render.entity.equipment.EquipmentModel.LayerType;
|
import net.minecraft.client.render.entity.equipment.EquipmentModel.LayerType;
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRenderer;
|
import net.minecraft.client.render.entity.feature.FeatureRenderer;
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
||||||
import net.minecraft.client.render.entity.model.BipedEntityModel;
|
|
||||||
import net.minecraft.client.render.entity.model.EntityModelLayers;
|
|
||||||
import net.minecraft.client.render.entity.model.LoadedEntityModels;
|
|
||||||
import net.minecraft.client.render.entity.model.PlayerCapeModel;
|
|
||||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
||||||
import net.minecraft.client.util.SkinTextures;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
import net.minecraft.component.DataComponentTypes;
|
import net.minecraft.component.DataComponentTypes;
|
||||||
import net.minecraft.component.type.EquippableComponent;
|
import net.minecraft.component.type.EquippableComponent;
|
||||||
@@ -26,21 +24,67 @@ import net.minecraft.item.ItemStack;
|
|||||||
import net.minecraft.item.equipment.EquipmentAsset;
|
import net.minecraft.item.equipment.EquipmentAsset;
|
||||||
import net.minecraft.registry.RegistryKey;
|
import net.minecraft.registry.RegistryKey;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cloak renderer
|
||||||
|
* - **Efficient**: uses a image only consisting of the cloak texture, 16x8
|
||||||
|
* whitespace.
|
||||||
|
* - **Curving**: Curves when player velocity increases.
|
||||||
|
* - **Wavey**: Makes the cloak feel like wind, when jump sprinting.
|
||||||
|
*/
|
||||||
public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
|
public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
|
||||||
private final BipedEntityModel<PlayerEntityRenderState> model;
|
|
||||||
private final EquipmentModelLoader equipmentModelLoader;
|
private final EquipmentModelLoader equipmentModelLoader;
|
||||||
|
private static final int PARTS = 16;
|
||||||
|
private final float[] segmentValues = new float[PARTS];
|
||||||
|
|
||||||
|
private static final float TEX_W = 176f;
|
||||||
|
private static final float TEX_H = 138f;
|
||||||
|
|
||||||
|
private static final class PxRect {
|
||||||
|
final int x, y, w, h;
|
||||||
|
|
||||||
|
PxRect(int x, int y, int w, int h) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.w = w;
|
||||||
|
this.h = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
float u1() {
|
||||||
|
return x / TEX_W;
|
||||||
|
}
|
||||||
|
|
||||||
|
float v1() {
|
||||||
|
return y / TEX_H;
|
||||||
|
}
|
||||||
|
|
||||||
|
float u2() {
|
||||||
|
return (x + w) / TEX_W;
|
||||||
|
}
|
||||||
|
|
||||||
|
float v2() {
|
||||||
|
return (y + h) / TEX_H;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final PxRect FRONT_RECT = new PxRect(8, 8, 80, 128);
|
||||||
|
private static final PxRect BACK_RECT = new PxRect(96, 8, 80, 128);
|
||||||
|
private static final PxRect LEFT_EDGE_RECT = new PxRect(0, 8, 8, 128);
|
||||||
|
private static final PxRect RIGHT_EDGE_RECT = new PxRect(88, 8, 8, 128);
|
||||||
|
private static final PxRect TOP_RECT = new PxRect(8, 0, 80, 8);
|
||||||
|
private static final PxRect BOTTOM_RECT = new PxRect(88, 0, 80, 8);
|
||||||
|
|
||||||
public CloakFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context,
|
public CloakFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context,
|
||||||
LoadedEntityModels modelLoader, EquipmentModelLoader equipmentModelLoader) {
|
EquipmentModelLoader equipmentModelLoader) {
|
||||||
super(context);
|
super(context);
|
||||||
this.model = new PlayerCapeModel<PlayerEntityRenderState>(
|
|
||||||
modelLoader.getModelPart(EntityModelLayers.PLAYER_CAPE));
|
|
||||||
this.equipmentModelLoader = equipmentModelLoader;
|
this.equipmentModelLoader = equipmentModelLoader;
|
||||||
|
Arrays.fill(segmentValues, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) {
|
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) {
|
||||||
EquippableComponent equippableComponent = (EquippableComponent) stack.get(DataComponentTypes.EQUIPPABLE);
|
EquippableComponent equippableComponent = (EquippableComponent) stack
|
||||||
|
.get(DataComponentTypes.EQUIPPABLE);
|
||||||
if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) {
|
if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) {
|
||||||
EquipmentModel equipmentModel = this.equipmentModelLoader
|
EquipmentModel equipmentModel = this.equipmentModelLoader
|
||||||
.get((RegistryKey<EquipmentAsset>) equippableComponent.assetId().get());
|
.get((RegistryKey<EquipmentAsset>) equippableComponent.assetId().get());
|
||||||
@@ -50,35 +94,241 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i,
|
private void renderCapeQuad(VertexConsumer vertexConsumer, MatrixStack matrixStack,
|
||||||
PlayerEntityRenderState playerEntityRenderState, float f, float g) {
|
Vec3d bottomLeft, Vec3d bottomRight, Vec3d topRight, Vec3d topLeft,
|
||||||
if (!playerEntityRenderState.invisible && playerEntityRenderState.capeVisible) {
|
float u1, float v1, float u2, float v2, int light, int overlay,
|
||||||
SkinTextures skinTextures = playerEntityRenderState.skinTextures;
|
float normalX, float normalY, float normalZ, boolean flipWinding) {
|
||||||
|
MatrixStack.Entry entry = matrixStack.peek();
|
||||||
|
|
||||||
if (skinTextures.capeTexture() == null) {
|
if (!flipWinding) {
|
||||||
String name = playerEntityRenderState.name;
|
// CCW
|
||||||
String uuid = Auth.playerNames.get(name);
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y,
|
||||||
if (uuid == null || !Auth.players.containsKey(uuid)) {
|
(float) bottomLeft.z)
|
||||||
return;
|
.color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light)
|
||||||
}
|
.normal(normalX, normalY, normalZ);
|
||||||
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y,
|
||||||
Identifier customCape = Cloaks.getCurrentCloakTexture(uuid);
|
(float) bottomRight.z)
|
||||||
|
.color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light)
|
||||||
if (customCape != null
|
.normal(normalX, normalY, normalZ);
|
||||||
&& !this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.WINGS)) {
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y,
|
||||||
matrixStack.push();
|
(float) topRight.z)
|
||||||
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) {
|
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
|
||||||
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
|
.normal(normalX, normalY, normalZ);
|
||||||
}
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y,
|
||||||
|
(float) topLeft.z)
|
||||||
VertexConsumer vertexConsumer = vertexConsumerProvider
|
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
|
||||||
.getBuffer(RenderLayer.getEntityAlpha(customCape));
|
.normal(normalX, normalY, normalZ);
|
||||||
((PlayerEntityModel) this.getContextModel()).copyTransforms(this.model);
|
} else {
|
||||||
this.model.setAngles(playerEntityRenderState);
|
// CW
|
||||||
this.model.render(matrixStack, vertexConsumer, 0xF000F0, OverlayTexture.DEFAULT_UV);
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y,
|
||||||
matrixStack.pop();
|
(float) topLeft.z)
|
||||||
}
|
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
|
||||||
}
|
.normal(normalX, normalY, normalZ);
|
||||||
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y,
|
||||||
|
(float) topRight.z)
|
||||||
|
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
|
||||||
|
.normal(normalX, normalY, normalZ);
|
||||||
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y,
|
||||||
|
(float) bottomRight.z)
|
||||||
|
.color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light)
|
||||||
|
.normal(normalX, normalY, normalZ);
|
||||||
|
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y,
|
||||||
|
(float) bottomLeft.z)
|
||||||
|
.color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light)
|
||||||
|
.normal(normalX, normalY, normalZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renderCape(MatrixStack matrixStack, VertexConsumer vertexConsumer, PlayerEntityRenderState playerState,
|
||||||
|
int light, int overlay) {
|
||||||
|
float capeWidth = 10.0f / 16.0f;
|
||||||
|
float capeHeight = 16.0f / 16.0f;
|
||||||
|
float capeDepth = 1.0f / 16.0f;
|
||||||
|
float capePartHeight = capeHeight / PARTS;
|
||||||
|
|
||||||
|
float x1 = -capeWidth / 2.0f;
|
||||||
|
float x2 = capeWidth / 2.0f;
|
||||||
|
|
||||||
|
final float frontU1 = FRONT_RECT.u1();
|
||||||
|
final float frontU2 = FRONT_RECT.u2();
|
||||||
|
final float frontV1 = FRONT_RECT.v1();
|
||||||
|
final float frontV2 = FRONT_RECT.v2();
|
||||||
|
final float frontPartV = (frontV2 - frontV1) / PARTS;
|
||||||
|
final float backU1 = BACK_RECT.u1();
|
||||||
|
final float backU2 = BACK_RECT.u2();
|
||||||
|
final float backV1 = BACK_RECT.v1();
|
||||||
|
final float backV2 = BACK_RECT.v2();
|
||||||
|
final float backPartV = (backV2 - backV1) / PARTS;
|
||||||
|
final float leftU1 = LEFT_EDGE_RECT.u1();
|
||||||
|
final float leftU2 = LEFT_EDGE_RECT.u2();
|
||||||
|
final float leftV1 = LEFT_EDGE_RECT.v1();
|
||||||
|
final float leftV2 = LEFT_EDGE_RECT.v2();
|
||||||
|
final float leftPartV = (leftV2 - leftV1) / PARTS;
|
||||||
|
final float rightU1 = RIGHT_EDGE_RECT.u1();
|
||||||
|
final float rightU2 = RIGHT_EDGE_RECT.u2();
|
||||||
|
final float rightV1 = RIGHT_EDGE_RECT.v1();
|
||||||
|
final float rightV2 = RIGHT_EDGE_RECT.v2();
|
||||||
|
final float rightPartV = (rightV2 - rightV1) / PARTS;
|
||||||
|
final float topU1 = TOP_RECT.u1();
|
||||||
|
final float topU2 = TOP_RECT.u2();
|
||||||
|
final float topV1 = TOP_RECT.v1();
|
||||||
|
final float topV2 = TOP_RECT.v2();
|
||||||
|
final float bottomU1 = BOTTOM_RECT.u1();
|
||||||
|
final float bottomU2 = BOTTOM_RECT.u2();
|
||||||
|
final float bottomV1 = BOTTOM_RECT.v1();
|
||||||
|
final float bottomV2 = BOTTOM_RECT.v2();
|
||||||
|
|
||||||
|
float curY = 0.0f;
|
||||||
|
float curZ = 0.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < PARTS; i++) {
|
||||||
|
float angle = (2.0f - segmentValues[i]) * ((float) Math.PI / 2f);
|
||||||
|
float dirY = -(float) Math.cos(angle);
|
||||||
|
float dirZ = (float) Math.sin(angle);
|
||||||
|
|
||||||
|
// 1. Thickness at the START of this segment (Must match PREVIOUS segment's end)
|
||||||
|
float prevAngle = (i > 0) ? (2.0f - segmentValues[i - 1]) * ((float) Math.PI / 2f) : angle;
|
||||||
|
float startAvgAngle = (angle + prevAngle) / 2.0f;
|
||||||
|
float thickYStart = (float) Math.sin(startAvgAngle) * capeDepth;
|
||||||
|
float thickZStart = (float) Math.cos(startAvgAngle) * capeDepth;
|
||||||
|
|
||||||
|
// 2. Thickness at the END of this segment (Must match NEXT segment's start)
|
||||||
|
float nextAngle = (i < PARTS - 1) ? (2.0f - segmentValues[i + 1]) * ((float) Math.PI / 2f) : angle;
|
||||||
|
float endAvgAngle = (angle + nextAngle) / 2.0f;
|
||||||
|
float thickYEnd = (float) Math.sin(endAvgAngle) * capeDepth;
|
||||||
|
float thickZEnd = (float) Math.cos(endAvgAngle) * capeDepth;
|
||||||
|
|
||||||
|
float nextY = curY + dirY * capePartHeight;
|
||||||
|
float nextZ = curZ + dirZ * capePartHeight;
|
||||||
|
|
||||||
|
// Inner Vertices (The spine of the cape)
|
||||||
|
Vec3d innerTopLeft = new Vec3d(x2, curY, curZ);
|
||||||
|
Vec3d innerTopRight = new Vec3d(x1, curY, curZ);
|
||||||
|
Vec3d innerBotLeft = new Vec3d(x2, nextY, nextZ);
|
||||||
|
Vec3d innerBotRight = new Vec3d(x1, nextY, nextZ);
|
||||||
|
|
||||||
|
// Outer Vertices (Offset by the specific joint thickness)
|
||||||
|
Vec3d outerTopLeft = new Vec3d(x2, curY + thickYStart, curZ + thickZStart);
|
||||||
|
Vec3d outerTopRight = new Vec3d(x1, curY + thickYStart, curZ + thickZStart);
|
||||||
|
Vec3d outerBotLeft = new Vec3d(x2, nextY + thickYEnd, nextZ + thickZEnd);
|
||||||
|
Vec3d outerBotRight = new Vec3d(x1, nextY + thickYEnd, nextZ + thickZEnd);
|
||||||
|
|
||||||
|
// FRONT (Inner) - Changed winding/order to fix flipping
|
||||||
|
this.renderCapeQuad(vertexConsumer, matrixStack,
|
||||||
|
innerBotLeft, innerBotRight, innerTopRight, innerTopLeft,
|
||||||
|
frontU1, frontV1 + (frontPartV * i), frontU2, frontV1 + (frontPartV * (i + 1)),
|
||||||
|
light, overlay, 0, 0, -1, false);
|
||||||
|
|
||||||
|
// BACK (Outer)
|
||||||
|
this.renderCapeQuad(vertexConsumer, matrixStack,
|
||||||
|
outerBotRight, outerBotLeft, outerTopLeft, outerTopRight,
|
||||||
|
backU1, backV1 + (backPartV * i), backU2, backV1 + (backPartV * (i + 1)),
|
||||||
|
light, overlay, 0, 0, 1, false);
|
||||||
|
|
||||||
|
// LEFT EDGE
|
||||||
|
this.renderCapeQuad(vertexConsumer, matrixStack,
|
||||||
|
innerBotLeft, outerBotLeft, outerTopLeft, innerTopLeft,
|
||||||
|
leftU1, leftV1 + (leftPartV * i), leftU2, leftV1 + (leftPartV * (i + 1)),
|
||||||
|
light, overlay, 1, 0, 0, false);
|
||||||
|
|
||||||
|
// RIGHT EDGE
|
||||||
|
this.renderCapeQuad(vertexConsumer, matrixStack,
|
||||||
|
outerBotRight, innerBotRight, innerTopRight, outerTopRight,
|
||||||
|
rightU1, rightV1 + (rightPartV * i), rightU2, rightV1 + (rightPartV * (i + 1)),
|
||||||
|
light, overlay, -1, 0, 0, false);
|
||||||
|
|
||||||
|
// TOP FACE (Only on first segment)
|
||||||
|
if (i == 0) {
|
||||||
|
this.renderCapeQuad(vertexConsumer, matrixStack,
|
||||||
|
outerTopLeft, outerTopRight, innerTopRight, innerTopLeft,
|
||||||
|
topU1, topV1, topU2, topV2, light, overlay, 0, 1, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOTTOM FACE (Only on last segment)
|
||||||
|
if (i == PARTS - 1) {
|
||||||
|
this.renderCapeQuad(vertexConsumer, matrixStack,
|
||||||
|
innerBotLeft, innerBotRight, outerBotRight, outerBotLeft,
|
||||||
|
bottomU1, bottomV1, bottomU2, bottomV2, light, overlay, 0, -1, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
curY = nextY;
|
||||||
|
curZ = nextZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift array left and insert new value at the start
|
||||||
|
private void updateSegmentValues(float value) {
|
||||||
|
// Shift all elements one position to the right (from end to start)
|
||||||
|
for (int i = segmentValues.length - 1; i > 0; i--) {
|
||||||
|
segmentValues[i] = segmentValues[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the new value at index 0
|
||||||
|
segmentValues[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long lastUpdate = 0;
|
||||||
|
|
||||||
|
public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light,
|
||||||
|
PlayerEntityRenderState playerEntityRenderState, float f, float g) {
|
||||||
|
if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible
|
||||||
|
|| playerEntityRenderState.skinTextures.capeTexture() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaturnPlayer player = SaturnPlayer.get(playerEntityRenderState.name);
|
||||||
|
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier customCape = Cloaks.getCurrentCloakTexture(player.uuid);
|
||||||
|
if (customCape == null
|
||||||
|
|| this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.WINGS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minBrightness = 7;
|
||||||
|
int blockLight = (light >> 4) & 0xF;
|
||||||
|
int skyLight = (light >> 20) & 0xF;
|
||||||
|
blockLight = Math.max(blockLight, minBrightness);
|
||||||
|
skyLight = Math.max(skyLight, minBrightness);
|
||||||
|
light = (skyLight << 20) | (blockLight << 4);
|
||||||
|
|
||||||
|
VertexConsumer vertexConsumer = vertexConsumerProvider
|
||||||
|
.getBuffer(RenderLayer.getEntityAlpha(customCape));
|
||||||
|
|
||||||
|
matrixStack.push();
|
||||||
|
|
||||||
|
matrixStack.translate(0.0f, 0.0f, 0.19f);
|
||||||
|
|
||||||
|
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) {
|
||||||
|
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - lastUpdate >= 20) {
|
||||||
|
float value = Math.min(1.0f, playerEntityRenderState.field_53537 / 108.0f)
|
||||||
|
+ (Math.max(0, playerEntityRenderState.field_53536) / 16);
|
||||||
|
if (Config.cloakPhysics.value) {
|
||||||
|
updateSegmentValues(value);
|
||||||
|
} else {
|
||||||
|
float last = segmentValues[0];
|
||||||
|
float maxStep = 0.02f;
|
||||||
|
float next = value;
|
||||||
|
|
||||||
|
if (next > last)
|
||||||
|
next = Math.min(next, last + maxStep);
|
||||||
|
else if (next < last)
|
||||||
|
next = Math.max(next, last - maxStep);
|
||||||
|
|
||||||
|
Arrays.fill(segmentValues, next);
|
||||||
|
}
|
||||||
|
lastUpdate = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light, OverlayTexture.DEFAULT_UV);
|
||||||
|
|
||||||
|
matrixStack.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.saturnclient.saturnclient.cosmetics;
|
package org.saturnclient.saturnclient.cosmetics;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.auth.SaturnPlayer;
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
import net.minecraft.client.render.OverlayTexture;
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
import net.minecraft.client.render.VertexConsumerProvider;
|
||||||
@@ -29,38 +27,36 @@ public class HatFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState,
|
|||||||
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
|
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
|
||||||
PlayerEntityRenderState state, float limbAngle, float limbDistance) {
|
PlayerEntityRenderState state, float limbAngle, float limbDistance) {
|
||||||
ItemStack headItem = state.equippedHeadStack;
|
ItemStack headItem = state.equippedHeadStack;
|
||||||
String uuid = Auth.playerNames.get(state.name);
|
SaturnPlayer player = SaturnPlayer.get(state.name);
|
||||||
if (headItem.isEmpty() && uuid != null && !state.invisible) {
|
|
||||||
SaturnPlayer player = Auth.players.get(uuid);
|
|
||||||
if (player == null || player.hat == null || player.hat.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
matrices.push();
|
if (!headItem.isEmpty() || state.invisible || player == null || player.hat.isEmpty()) {
|
||||||
|
return;
|
||||||
this.getContextModel().head.rotate(matrices);
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f));
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f));
|
|
||||||
|
|
||||||
ItemStack customHat = new ItemStack(Items.STICK);
|
|
||||||
customHat.set(DataComponentTypes.ITEM_MODEL,
|
|
||||||
Identifier.of("saturnclient:hat_" + player.hat));
|
|
||||||
customHat.set(DataComponentTypes.EQUIPPABLE,
|
|
||||||
EquippableComponent.builder(EquipmentSlot.HEAD).build());
|
|
||||||
|
|
||||||
ItemRenderer itemRenderer = SaturnClient.client.getItemRenderer();
|
|
||||||
|
|
||||||
itemRenderer.renderItem(
|
|
||||||
customHat,
|
|
||||||
ModelTransformationMode.THIRD_PERSON_RIGHT_HAND,
|
|
||||||
light,
|
|
||||||
OverlayTexture.DEFAULT_UV,
|
|
||||||
matrices,
|
|
||||||
vertexConsumers,
|
|
||||||
null,
|
|
||||||
0);
|
|
||||||
|
|
||||||
matrices.pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matrices.push();
|
||||||
|
|
||||||
|
this.getContextModel().head.rotate(matrices);
|
||||||
|
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f));
|
||||||
|
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f));
|
||||||
|
|
||||||
|
ItemStack customHat = new ItemStack(Items.STICK);
|
||||||
|
customHat.set(DataComponentTypes.ITEM_MODEL,
|
||||||
|
Identifier.of("saturnclient:hat_" + player.hat));
|
||||||
|
customHat.set(DataComponentTypes.EQUIPPABLE,
|
||||||
|
EquippableComponent.builder(EquipmentSlot.HEAD).build());
|
||||||
|
|
||||||
|
ItemRenderer itemRenderer = SaturnClient.client.getItemRenderer();
|
||||||
|
|
||||||
|
itemRenderer.renderItem(
|
||||||
|
customHat,
|
||||||
|
ModelTransformationMode.THIRD_PERSON_RIGHT_HAND,
|
||||||
|
light,
|
||||||
|
OverlayTexture.DEFAULT_UV,
|
||||||
|
matrices,
|
||||||
|
vertexConsumers,
|
||||||
|
null,
|
||||||
|
0);
|
||||||
|
|
||||||
|
matrices.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,10 @@ package org.saturnclient.saturnclient.cosmetics;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.saturnclient.saturnclient.auth.SaturnPlayer;
|
import java.util.UUID;
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
|
||||||
|
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||||
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
|
|
||||||
public class Hats {
|
public class Hats {
|
||||||
public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
|
public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
|
||||||
@@ -13,17 +15,16 @@ public class Hats {
|
|||||||
availableHats.add(0, "");
|
availableHats.add(0, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setHat(String uuid, String hatName) {
|
public static void setHat(String hatName) {
|
||||||
setHatSilent(uuid, hatName);
|
if (availableHats.contains(hatName)) {
|
||||||
Auth.setHat(hatName);
|
setHat(ServiceClient.uuid, hatName);
|
||||||
Auth.sendReload();
|
ServiceClient.setHat(hatName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setHatSilent(String uuid, String hatName) {
|
public static void setHat(UUID uuid, String hatName) {
|
||||||
SaturnPlayer player = Auth.players.get(uuid);
|
SaturnPlayer player = SaturnPlayer.get(uuid);
|
||||||
if (player == null) {
|
if (player != null) {
|
||||||
Auth.players.put(uuid, new SaturnPlayer("", hatName));
|
|
||||||
} else {
|
|
||||||
player.hat = hatName;
|
player.hat = hatName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
package org.saturnclient.saturnclient.cosmetics.cloaks;
|
package org.saturnclient.saturnclient.cosmetics.cloaks;
|
||||||
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
|
||||||
|
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||||
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData;
|
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData;
|
||||||
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
|
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
|
||||||
import org.saturnclient.saturnclient.auth.SaturnPlayer;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,14 +25,27 @@ import org.saturnclient.saturnclient.SaturnClient;
|
|||||||
* Originally created by IIpho3nix and modified for Saturn Client by leo.
|
* Originally created by IIpho3nix and modified for Saturn Client by leo.
|
||||||
*/
|
*/
|
||||||
public class Cloaks {
|
public class Cloaks {
|
||||||
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", };
|
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", "bmw", "amg", "amg_petronas",
|
||||||
private static final String[] ANIMATED_CLOAKS = { "glitch", "mercedes_flow" };
|
"ferrari", "redbull", "black_hole_amethyst", "black_hole_flame", "black_hole_white" };
|
||||||
|
private static final String[] ANIMATED_CLOAKS = { "glitch", "black_hole_amethyst", "black_hole_flame",
|
||||||
|
"black_hole_white" };
|
||||||
|
|
||||||
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
|
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
|
||||||
public static final List<String> availableCloaks = new ArrayList<>();
|
public static final List<String> availableCloaks = new ArrayList<>();
|
||||||
public static Identifier cloakCacheIdentifier = null;
|
public static Identifier cloakCacheIdentifier = null;
|
||||||
public static final Map<String, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
|
public static final Map<UUID, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
|
||||||
private static final Map<String, Long> lastFrameTime = new ConcurrentHashMap<>();
|
private static final Map<UUID, Long> lastFrameTime = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool(
|
||||||
|
Math.min(4, Runtime.getRuntime().availableProcessors()),
|
||||||
|
r -> {
|
||||||
|
Thread t = new Thread(r, "cloak-loader");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.setPriority(Thread.NORM_PRIORITY - 1);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
private static final ConcurrentHashMap<String, List<AnimatedCloakData>> CLOAK_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the cloak system.
|
* Initializes the cloak system.
|
||||||
@@ -37,18 +55,6 @@ public class Cloaks {
|
|||||||
availableCloaks.add(0, "");
|
availableCloaks.add(0, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles loading and caching of a new cloak texture.
|
|
||||||
*
|
|
||||||
* @param cloakName Name of the cloak file to load
|
|
||||||
*/
|
|
||||||
public static void setCloak(String uuid, String cloakName) {
|
|
||||||
Auth.setCloak(cloakName);
|
|
||||||
setCloakSilent(uuid, cloakName);
|
|
||||||
Auth.sendReload();
|
|
||||||
SaturnClient.LOGGER.info("Cloak set to " + cloakName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets cloak of the current player
|
* Sets cloak of the current player
|
||||||
*
|
*
|
||||||
@@ -56,7 +62,8 @@ public class Cloaks {
|
|||||||
*/
|
*/
|
||||||
public static void setCloak(String cloakName) {
|
public static void setCloak(String cloakName) {
|
||||||
if (availableCloaks.contains(cloakName)) {
|
if (availableCloaks.contains(cloakName)) {
|
||||||
setCloak(Auth.uuid, cloakName);
|
setCloak(ServiceClient.uuid, cloakName);
|
||||||
|
ServiceClient.setCloak(cloakName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +73,10 @@ public class Cloaks {
|
|||||||
*
|
*
|
||||||
* @param cloakName Name of the cloak file to load
|
* @param cloakName Name of the cloak file to load
|
||||||
*/
|
*/
|
||||||
public static void setCloakSilent(String uuid, String cloakName) {
|
public static void setCloak(UUID uuid, String cloakName) {
|
||||||
SaturnPlayer player = Auth.players.get(uuid);
|
SaturnPlayer player = SaturnPlayer.get(uuid);
|
||||||
if (player == null) {
|
|
||||||
Auth.players.put(uuid, new SaturnPlayer(cloakName, null));
|
if (player != null) {
|
||||||
} else {
|
|
||||||
player.cloak = cloakName;
|
player.cloak = cloakName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,52 +89,19 @@ public class Cloaks {
|
|||||||
*
|
*
|
||||||
* @param cloakName Name of the cloak file to load
|
* @param cloakName Name of the cloak file to load
|
||||||
*/
|
*/
|
||||||
public static void loadCloak(String uuid) {
|
public static void loadCloak(UUID uuid) {
|
||||||
SaturnPlayer player = Auth.players.get(uuid);
|
SaturnPlayer player = SaturnPlayer.get(uuid);
|
||||||
|
|
||||||
if (player != null && player.cloak != null) {
|
if (player != null && player.cloak != null) {
|
||||||
if (!player.cloak.isEmpty()) {
|
if (!player.cloak.isEmpty()) {
|
||||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) {
|
if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) {
|
||||||
// Load animated cloak data in background thread
|
loadAnimatedCloakAsync(uuid, player.cloak)
|
||||||
new Thread(() -> {
|
.exceptionally(throwable -> {
|
||||||
try {
|
SaturnClient.LOGGER.error("Failed to load animated cloak for player: " + player.cloak,
|
||||||
String fileName = player.cloak + ".gif";
|
throwable);
|
||||||
String resourcePath = CLOAKS_RESOURCE_PATH + fileName;
|
return null;
|
||||||
InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath);
|
});
|
||||||
|
|
||||||
if (inputStream != null) {
|
|
||||||
byte[] data = inputStream.readAllBytes();
|
|
||||||
GifDecoder.GifImage gif = GifDecoder.read(data);
|
|
||||||
List<BufferedImage> frames = new ArrayList<>();
|
|
||||||
List<Integer> delays = new ArrayList<>();
|
|
||||||
int frameCount = gif.getFrameCount();
|
|
||||||
|
|
||||||
for (int i = 0; i < frameCount; i++) {
|
|
||||||
frames.add(gif.getFrame(i));
|
|
||||||
delays.add(gif.getDelay(i) * 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register textures on main thread
|
|
||||||
SaturnClient.client.execute(() -> {
|
|
||||||
try {
|
|
||||||
List<AnimatedCloakData> animatedFrames = new ArrayList<>();
|
|
||||||
for (int i = 0; i < frameCount; i++) {
|
|
||||||
String frameId = fileName.replace(".gif", "") + "_frame_" + i;
|
|
||||||
Identifier frameIdentifier = Identifier.of(SaturnClient.MOD_ID, "cloaks_" + frameId);
|
|
||||||
IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frames.get(i));
|
|
||||||
animatedFrames.add(new AnimatedCloakData(frameIdentifier, delays.get(i)));
|
|
||||||
}
|
|
||||||
animatedCloaks.put(uuid, animatedFrames);
|
|
||||||
lastFrameTime.put(uuid, System.currentTimeMillis());
|
|
||||||
SaturnClient.LOGGER.info("Loaded " + frames.size() + " frames for animated cloak: " + fileName);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to register animated cloak textures: " + fileName, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to load animated cloak from resources: " + player.cloak + ".gif", e);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
} else {
|
} else {
|
||||||
SaturnClient.client.execute(() -> loadStaticCloak(player.cloak + ".png"));
|
SaturnClient.client.execute(() -> loadStaticCloak(player.cloak + ".png"));
|
||||||
}
|
}
|
||||||
@@ -164,16 +137,94 @@ public class Cloaks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Identifier getCurrentCloakTexture(String uuid) {
|
public static CompletableFuture<Void> loadAnimatedCloakAsync(UUID uuid, String cloakName) {
|
||||||
if (!Auth.players.containsKey(uuid)) {
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
String fileName = cloakName + ".gif";
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
List<AnimatedCloakData> cached = CLOAK_CACHE.get(fileName);
|
||||||
|
if (cached != null) {
|
||||||
|
animatedCloaks.put(uuid, cached);
|
||||||
|
lastFrameTime.put(uuid, System.currentTimeMillis());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resourcePath = CLOAKS_RESOURCE_PATH + fileName;
|
||||||
|
|
||||||
|
try (InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath)) {
|
||||||
|
if (inputStream == null) {
|
||||||
|
SaturnClient.LOGGER.warn("Cloak resource not found: " + resourcePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all bytes at once - more efficient than multiple reads
|
||||||
|
byte[] data = inputStream.readAllBytes();
|
||||||
|
GifDecoder.GifImage gif = GifDecoder.read(data);
|
||||||
|
|
||||||
|
int frameCount = gif.getFrameCount();
|
||||||
|
if (frameCount == 0) {
|
||||||
|
SaturnClient.LOGGER.warn("No frames found in animated cloak: " + fileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-allocate collections with known size
|
||||||
|
List<AnimatedCloakData> animatedFrames = new ArrayList<>(frameCount);
|
||||||
|
String baseFrameId = fileName.replace(".gif", "");
|
||||||
|
|
||||||
|
// Process frames in batch
|
||||||
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
BufferedImage frame = gif.getFrame(i);
|
||||||
|
int delay = Math.max(gif.getDelay(i) * 10, 50); // Minimum 50ms delay
|
||||||
|
|
||||||
|
String frameId = baseFrameId + "_frame_" + i;
|
||||||
|
Identifier frameIdentifier = Identifier.of(SaturnClient.MOD_ID, "cloaks_" + frameId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
IdentifierUtils.registerBufferedImageTextureFast(frameIdentifier, frame);
|
||||||
|
// Register texture on main thread if required by the graphics system
|
||||||
|
if (SaturnClient.client.isOnThread()) {
|
||||||
|
// IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame);
|
||||||
|
} else {
|
||||||
|
// Queue for main thread execution
|
||||||
|
// SaturnClient.client.execute(
|
||||||
|
// () -> IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
animatedFrames.add(new AnimatedCloakData(frameIdentifier, delay));
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to register frame " + i + " for cloak: " + fileName, e);
|
||||||
|
// Continue with other frames instead of failing completely
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!animatedFrames.isEmpty()) {
|
||||||
|
// Cache the result for future use
|
||||||
|
CLOAK_CACHE.put(fileName, animatedFrames);
|
||||||
|
animatedCloaks.put(uuid, animatedFrames);
|
||||||
|
lastFrameTime.put(uuid, System.currentTimeMillis());
|
||||||
|
|
||||||
|
SaturnClient.LOGGER.info("Loaded {} frames for animated cloak: {} (cached)",
|
||||||
|
animatedFrames.size(), fileName);
|
||||||
|
} else {
|
||||||
|
SaturnClient.LOGGER.error("No valid frames could be loaded for cloak: " + fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to load animated cloak from resources: " + fileName, e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Unexpected error loading animated cloak: " + fileName, e);
|
||||||
|
}
|
||||||
|
}, CLOAK_LOADER_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Identifier getCurrentCloakTexture(UUID uuid) {
|
||||||
|
SaturnPlayer player = SaturnPlayer.get(uuid);
|
||||||
|
|
||||||
|
if (player == null || player.cloak.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String cloakName = Auth.players.get(uuid).cloak;
|
String cloakName = player.cloak;
|
||||||
|
|
||||||
if (cloakName.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
|
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
|
||||||
List<AnimatedCloakData> frames = animatedCloaks.get(uuid);
|
List<AnimatedCloakData> frames = animatedCloaks.get(uuid);
|
||||||
@@ -200,7 +251,13 @@ public class Cloaks {
|
|||||||
currentFrame = 0;
|
currentFrame = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return frames.get(currentFrame).getTextureId();
|
Identifier identifier = frames.get(currentFrame).getTextureId();
|
||||||
|
|
||||||
|
if (identifier == null) {
|
||||||
|
return Identifier.of(SaturnClient.MOD_ID, "textures/cloaks/" + cloakName + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
return identifier;
|
||||||
} else {
|
} else {
|
||||||
return Identifier.of(SaturnClient.MOD_ID, "textures/cloaks/" + cloakName + ".png");
|
return Identifier.of(SaturnClient.MOD_ID, "textures/cloaks/" + cloakName + ".png");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import java.awt.image.DataBufferInt;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import static java.lang.System.arraycopy;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2014 Dhyan Blum
|
* Copyright 2014 Dhyan Blum
|
||||||
@@ -28,7 +29,7 @@ import static java.lang.System.arraycopy;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* A decoder capable of processing a GIF data stream to render the graphics
|
* A thread-safe decoder capable of processing a GIF data stream to render the graphics
|
||||||
* contained in it. This implementation follows the official
|
* contained in it. This implementation follows the official
|
||||||
* <A HREF="http://www.w3.org/Graphics/GIF/spec-gif89a.txt">GIF
|
* <A HREF="http://www.w3.org/Graphics/GIF/spec-gif89a.txt">GIF
|
||||||
* specification</A>.
|
* specification</A>.
|
||||||
@@ -54,9 +55,51 @@ import static java.lang.System.arraycopy;
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Dhyan Blum
|
* @author Dhyan Blum
|
||||||
* @version 1.09 November 2017
|
* @version 1.09 November 2017 (Thread-safe version)
|
||||||
*/
|
*/
|
||||||
public final class GifDecoder {
|
public final class GifDecoder {
|
||||||
|
|
||||||
|
// Thread-local storage for reusable objects to avoid synchronization overhead
|
||||||
|
private static final ThreadLocal<DecodingContext> DECODING_CONTEXT =
|
||||||
|
ThreadLocal.withInitial(DecodingContext::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-local context containing reusable objects for decoding operations.
|
||||||
|
* This eliminates the need for synchronization while maintaining performance benefits
|
||||||
|
* of object reuse within each thread.
|
||||||
|
*/
|
||||||
|
static final class DecodingContext {
|
||||||
|
final BitReader bitReader = new BitReader();
|
||||||
|
final CodeTable codeTable = new CodeTable();
|
||||||
|
int[] pixelBuffer;
|
||||||
|
int[] prevPixelBuffer;
|
||||||
|
int[] tempBuffer;
|
||||||
|
|
||||||
|
// Get or create a pixel buffer of the specified size
|
||||||
|
int[] getPixelBuffer(int size) {
|
||||||
|
if (pixelBuffer == null || pixelBuffer.length < size) {
|
||||||
|
pixelBuffer = new int[Math.max(size, 1024)]; // Minimum size to reduce allocations
|
||||||
|
}
|
||||||
|
return pixelBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create a previous pixel buffer of the specified size
|
||||||
|
int[] getPrevPixelBuffer(int size) {
|
||||||
|
if (prevPixelBuffer == null || prevPixelBuffer.length < size) {
|
||||||
|
prevPixelBuffer = new int[Math.max(size, 1024)];
|
||||||
|
}
|
||||||
|
return prevPixelBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create a temporary buffer of the specified size
|
||||||
|
int[] getTempBuffer(int size) {
|
||||||
|
if (tempBuffer == null || tempBuffer.length < size) {
|
||||||
|
tempBuffer = new int[Math.max(size, 1024)];
|
||||||
|
}
|
||||||
|
return tempBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static final class BitReader {
|
static final class BitReader {
|
||||||
private int nextBitToRead;
|
private int nextBitToRead;
|
||||||
private int numberOfBitsToRead;
|
private int numberOfBitsToRead;
|
||||||
@@ -71,14 +114,16 @@ public final class GifDecoder {
|
|||||||
|
|
||||||
private int read() {
|
private int read() {
|
||||||
// Byte indices: (bitPos / 8), (bitPos / 8) + 1, (bitPos / 8) + 2
|
// Byte indices: (bitPos / 8), (bitPos / 8) + 1, (bitPos / 8) + 2
|
||||||
int byteIndex = nextBitToRead >>> 3; // Byte = bit / 8
|
final int byteIndex = nextBitToRead >>> 3; // Byte = bit / 8
|
||||||
int bitsToShiftRight = nextBitToRead & 7; // & 7 is the same as MODULO 8
|
final int bitsToShiftRight = nextBitToRead & 7; // & 7 is the same as MODULO 8
|
||||||
int byte0, byte1, byte2;
|
|
||||||
byte0 = bytes[byteIndex++] & 0xFF; // & 0xFF gives us the unsigned values
|
// Use int operations to avoid repeated array access
|
||||||
byte1 = bytes[byteIndex++] & 0xFF;
|
final int byte0 = bytes[byteIndex] & 0xFF;
|
||||||
byte2 = bytes[byteIndex] & 0xFF;
|
final int byte1 = bytes[byteIndex + 1] & 0xFF;
|
||||||
// Glue the bytes together, don't do more shifting than necessary
|
final int byte2 = bytes[byteIndex + 2] & 0xFF;
|
||||||
int buffer = ((byte2 << 8 | byte1) << 8 | byte0) >>> bitsToShiftRight;
|
|
||||||
|
// Optimized bit manipulation
|
||||||
|
final int buffer = ((byte2 << 16) | (byte1 << 8) | byte0) >>> bitsToShiftRight;
|
||||||
nextBitToRead += numberOfBitsToRead;
|
nextBitToRead += numberOfBitsToRead;
|
||||||
return buffer & bitMask; // Kill the unwanted higher bits
|
return buffer & bitMask; // Kill the unwanted higher bits
|
||||||
}
|
}
|
||||||
@@ -100,7 +145,7 @@ public final class GifDecoder {
|
|||||||
private BitReader bitReader; // Notify when code sizes increases
|
private BitReader bitReader; // Notify when code sizes increases
|
||||||
|
|
||||||
public CodeTable() {
|
public CodeTable() {
|
||||||
table = new int[4096][1];
|
table = new int[4096][];
|
||||||
}
|
}
|
||||||
|
|
||||||
private int add(final int[] indices) {
|
private int add(final int[] indices) {
|
||||||
@@ -130,14 +175,18 @@ public final class GifDecoder {
|
|||||||
initCodeLimit = (1 << initCodeSize) - 1; // 2^initCodeSize - 1
|
initCodeLimit = (1 << initCodeSize) - 1; // 2^initCodeSize - 1
|
||||||
initTableSize = fr.endOfInfoCode + 1;
|
initTableSize = fr.endOfInfoCode + 1;
|
||||||
nextCode = initTableSize;
|
nextCode = initTableSize;
|
||||||
for (int c = numColors - 1; c >= 0; c--) {
|
|
||||||
table[c][0] = activeColTbl[c]; // Translated color
|
// Initialize single color entries
|
||||||
} // A gap may follow with no colors assigned if numCols < CLEAR
|
for (int c = 0; c < numColors; c++) {
|
||||||
|
table[c] = new int[]{activeColTbl[c]}; // Single element array
|
||||||
|
}
|
||||||
|
|
||||||
table[fr.clearCode] = new int[] { fr.clearCode }; // CLEAR
|
table[fr.clearCode] = new int[] { fr.clearCode }; // CLEAR
|
||||||
table[fr.endOfInfoCode] = new int[] { fr.endOfInfoCode }; // EOI
|
table[fr.endOfInfoCode] = new int[] { fr.endOfInfoCode }; // EOI
|
||||||
|
|
||||||
// Locate transparent color in code table and set to 0
|
// Locate transparent color in code table and set to 0
|
||||||
if (fr.transpColFlag && fr.transpColIndex < numColors) {
|
if (fr.transpColFlag && fr.transpColIndex < numColors) {
|
||||||
table[fr.transpColIndex][0] = 0;
|
table[fr.transpColIndex] = new int[]{0};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,73 +194,87 @@ public final class GifDecoder {
|
|||||||
final class GifFrame {
|
final class GifFrame {
|
||||||
// Graphic control extension (optional)
|
// Graphic control extension (optional)
|
||||||
// Disposal: 0=NO_ACTION, 1=NO_DISPOSAL, 2=RESTORE_BG, 3=RESTORE_PREV
|
// Disposal: 0=NO_ACTION, 1=NO_DISPOSAL, 2=RESTORE_BG, 3=RESTORE_PREV
|
||||||
private int disposalMethod; // 0-3 as above, 4-7 undefined
|
private volatile int disposalMethod; // 0-3 as above, 4-7 undefined
|
||||||
private boolean transpColFlag; // 1 Bit
|
private volatile boolean transpColFlag; // 1 Bit
|
||||||
private int delay; // Unsigned, LSByte first, n * 1/100 * s
|
private volatile int delay; // Unsigned, LSByte first, n * 1/100 * s
|
||||||
private int transpColIndex; // 1 Byte
|
private volatile int transpColIndex; // 1 Byte
|
||||||
// Image descriptor
|
// Image descriptor
|
||||||
private int x; // Position on the canvas from the left
|
private volatile int x; // Position on the canvas from the left
|
||||||
private int y; // Position on the canvas from the top
|
private volatile int y; // Position on the canvas from the top
|
||||||
private int w; // May be smaller than the base image
|
private volatile int w; // May be smaller than the base image
|
||||||
private int h; // May be smaller than the base image
|
private volatile int h; // May be smaller than the base image
|
||||||
private int wh; // width * height
|
private volatile int wh; // width * height
|
||||||
private boolean hasLocColTbl; // Has local color table? 1 Bit
|
private volatile boolean hasLocColTbl; // Has local color table? 1 Bit
|
||||||
private boolean interlaceFlag; // Is an interlace image? 1 Bit
|
private volatile boolean interlaceFlag; // Is an interlace image? 1 Bit
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private boolean sortFlag; // True if local colors are sorted, 1 Bit
|
private volatile boolean sortFlag; // True if local colors are sorted, 1 Bit
|
||||||
private int sizeOfLocColTbl; // Size of the local color table, 3 Bits
|
private volatile int sizeOfLocColTbl; // Size of the local color table, 3 Bits
|
||||||
private int[] localColTbl; // Local color table (optional)
|
private volatile int[] localColTbl; // Local color table (optional)
|
||||||
// Image data
|
// Image data
|
||||||
private int firstCodeSize; // LZW minimum code size + 1 for CLEAR & EOI
|
private volatile int firstCodeSize; // LZW minimum code size + 1 for CLEAR & EOI
|
||||||
private int clearCode;
|
private volatile int clearCode;
|
||||||
private int endOfInfoCode;
|
private volatile int endOfInfoCode;
|
||||||
private byte[] data; // Holds LZW encoded data
|
private volatile byte[] data; // Holds LZW encoded data
|
||||||
private BufferedImage img; // Full drawn image, not just the frame area
|
private volatile BufferedImage img; // Full drawn image, not just the frame area
|
||||||
|
|
||||||
|
// Synchronization for frame image creation
|
||||||
|
private final Object imageLock = new Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class GifImage {
|
public final class GifImage {
|
||||||
public String header; // Bytes 0-5, GIF87a or GIF89a
|
public volatile String header; // Bytes 0-5, GIF87a or GIF89a
|
||||||
private int w; // Unsigned 16 Bit, the least significant byte first
|
private volatile int w; // Unsigned 16 Bit, the least significant byte first
|
||||||
private int h; // Unsigned 16 Bit, the least significant byte first
|
private volatile int h; // Unsigned 16 Bit, the least significant byte first
|
||||||
private int wh; // Image width * image height
|
private volatile int wh; // Image width * image height
|
||||||
public boolean hasGlobColTbl; // 1 Bit
|
public volatile boolean hasGlobColTbl; // 1 Bit
|
||||||
public int colorResolution; // 3 Bits
|
public volatile int colorResolution; // 3 Bits
|
||||||
public boolean sortFlag; // True if global colors are sorted, 1 Bit
|
public volatile boolean sortFlag; // True if global colors are sorted, 1 Bit
|
||||||
public int sizeOfGlobColTbl; // 2^(val(3 Bits) + 1), see spec
|
public volatile int sizeOfGlobColTbl; // 2^(val(3 Bits) + 1), see spec
|
||||||
public int bgColIndex; // Background color index, 1 Byte
|
public volatile int bgColIndex; // Background color index, 1 Byte
|
||||||
public int pxAspectRatio; // Pixel aspect ratio, 1 Byte
|
public volatile int pxAspectRatio; // Pixel aspect ratio, 1 Byte
|
||||||
public int[] globalColTbl; // Global color table
|
public volatile int[] globalColTbl; // Global color table
|
||||||
private final List<GifFrame> frames = new ArrayList<GifFrame>(64);
|
private final List<GifFrame> frames = new ArrayList<>(64);
|
||||||
public String appId = ""; // 8 Bytes at in[i+3], usually "NETSCAPE"
|
public volatile String appId = ""; // 8 Bytes at in[i+3], usually "NETSCAPE"
|
||||||
public String appAuthCode = ""; // 3 Bytes at in[i+11], usually "2.0"
|
public volatile String appAuthCode = ""; // 3 Bytes at in[i+11], usually "2.0"
|
||||||
public int repetitions = 0; // 0: infinite loop, N: number of loops
|
public volatile int repetitions = 0; // 0: infinite loop, N: number of loops
|
||||||
private BufferedImage img = null; // Currently, drawn frame
|
|
||||||
private final BitReader bits = new BitReader();
|
// Thread-safe frame rendering state
|
||||||
private final CodeTable codes = new CodeTable();
|
private volatile BufferedImage img = null; // Currently, drawn frame
|
||||||
private Graphics2D g;
|
private volatile Graphics2D g;
|
||||||
|
private final ReadWriteLock renderLock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
// Synchronization objects
|
||||||
|
private final Object initLock = new Object();
|
||||||
|
|
||||||
private int[] decode(final GifFrame fr, final int[] activeColTbl) {
|
private int[] decode(final GifFrame fr, final int[] activeColTbl) {
|
||||||
codes.init(fr, activeColTbl, bits);
|
final DecodingContext ctx = DECODING_CONTEXT.get();
|
||||||
bits.init(fr.data); // Incoming codes
|
ctx.codeTable.init(fr, activeColTbl, ctx.bitReader);
|
||||||
|
ctx.bitReader.init(fr.data); // Incoming codes
|
||||||
final int clearCode = fr.clearCode, endCode = fr.endOfInfoCode;
|
final int clearCode = fr.clearCode, endCode = fr.endOfInfoCode;
|
||||||
final int[] out = new int[wh]; // Target image pixel array
|
|
||||||
final int[][] tbl = codes.table; // Code table
|
// Use thread-local pixel buffer
|
||||||
|
final int[] out = ctx.getPixelBuffer(wh);
|
||||||
|
|
||||||
|
final int[][] tbl = ctx.codeTable.table; // Code table
|
||||||
int outPos = 0; // Next pixel position in the output image array
|
int outPos = 0; // Next pixel position in the output image array
|
||||||
codes.clear(); // Init code table
|
ctx.codeTable.clear(); // Init code table
|
||||||
bits.read(); // Skip leading clear code
|
ctx.bitReader.read(); // Skip leading clear code
|
||||||
int code = bits.read(); // Read first code
|
int code = ctx.bitReader.read(); // Read first code
|
||||||
int[] pixels = tbl[code]; // Output pixel for first code
|
int[] pixels = tbl[code]; // Output pixel for first code
|
||||||
arraycopy(pixels, 0, out, outPos, pixels.length);
|
|
||||||
|
// Use System.arraycopy for better performance
|
||||||
|
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
||||||
outPos += pixels.length;
|
outPos += pixels.length;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
final int prevCode = code;
|
final int prevCode = code;
|
||||||
code = bits.read(); // Get next code in stream
|
code = ctx.bitReader.read(); // Get next code in stream
|
||||||
if (code == clearCode) { // After a CLEAR table, there is
|
if (code == clearCode) { // After a CLEAR table, there is
|
||||||
codes.clear(); // no previous code, we need to read
|
ctx.codeTable.clear(); // no previous code, we need to read
|
||||||
code = bits.read(); // a new one
|
code = ctx.bitReader.read(); // a new one
|
||||||
pixels = tbl[code]; // Output pixels
|
pixels = tbl[code]; // Output pixels
|
||||||
arraycopy(pixels, 0, out, outPos, pixels.length);
|
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
||||||
outPos += pixels.length;
|
outPos += pixels.length;
|
||||||
continue; // Back to the loop with a valid previous code
|
continue; // Back to the loop with a valid previous code
|
||||||
} else if (code == endCode) {
|
} else if (code == endCode) {
|
||||||
@@ -219,92 +282,107 @@ public final class GifDecoder {
|
|||||||
}
|
}
|
||||||
final int[] prevVals = tbl[prevCode];
|
final int[] prevVals = tbl[prevCode];
|
||||||
final int[] prevValsAndK = new int[prevVals.length + 1];
|
final int[] prevValsAndK = new int[prevVals.length + 1];
|
||||||
arraycopy(prevVals, 0, prevValsAndK, 0, prevVals.length);
|
System.arraycopy(prevVals, 0, prevValsAndK, 0, prevVals.length);
|
||||||
if (code < codes.nextCode) { // Code table contains code
|
if (code < ctx.codeTable.nextCode) { // Code table contains code
|
||||||
pixels = tbl[code]; // Output pixels
|
pixels = tbl[code]; // Output pixels
|
||||||
arraycopy(pixels, 0, out, outPos, pixels.length);
|
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
||||||
outPos += pixels.length;
|
outPos += pixels.length;
|
||||||
prevValsAndK[prevVals.length] = tbl[code][0]; // K
|
prevValsAndK[prevVals.length] = tbl[code][0]; // K
|
||||||
} else {
|
} else {
|
||||||
prevValsAndK[prevVals.length] = prevVals[0]; // K
|
prevValsAndK[prevVals.length] = prevVals[0]; // K
|
||||||
arraycopy(prevValsAndK, 0, out, outPos, prevValsAndK.length);
|
System.arraycopy(prevValsAndK, 0, out, outPos, prevValsAndK.length);
|
||||||
outPos += prevValsAndK.length;
|
outPos += prevValsAndK.length;
|
||||||
}
|
}
|
||||||
codes.add(prevValsAndK); // Previous indices + K
|
ctx.codeTable.add(prevValsAndK); // Previous indices + K
|
||||||
}
|
}
|
||||||
} catch (final ArrayIndexOutOfBoundsException ignored) {
|
} catch (final ArrayIndexOutOfBoundsException ignored) {
|
||||||
}
|
}
|
||||||
return out;
|
|
||||||
|
// Return a properly sized array
|
||||||
|
return Arrays.copyOf(out, outPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] deinterlace(final int[] src, final GifFrame fr) {
|
private int[] deinterlace(final int[] src, final GifFrame fr) {
|
||||||
final int w = fr.w, h = fr.h, wh = fr.wh;
|
final int w = fr.w, h = fr.h, wh = fr.wh;
|
||||||
final int[] dest = new int[src.length];
|
final DecodingContext ctx = DECODING_CONTEXT.get();
|
||||||
|
final int[] dest = ctx.getTempBuffer(src.length);
|
||||||
|
|
||||||
// Interlaced images are organized in 4 sets of pixel lines
|
// Interlaced images are organized in 4 sets of pixel lines
|
||||||
final int set2Y = (h + 7) >>> 3; // Line no. = ceil(h/8.0)
|
final int set2Y = (h + 7) >>> 3; // Line no. = ceil(h/8.0)
|
||||||
final int set3Y = set2Y + ((h + 3) >>> 3); // ceil(h-4/8.0)
|
final int set3Y = set2Y + ((h + 3) >>> 3); // ceil(h-4/8.0)
|
||||||
final int set4Y = set3Y + ((h + 1) >>> 2); // ceil(h-2/4.0)
|
final int set4Y = set3Y + ((h + 1) >>> 2); // ceil(h-2/4.0)
|
||||||
|
|
||||||
// Sets' start indices in source array
|
// Sets' start indices in source array
|
||||||
final int set2 = w * set2Y, set3 = w * set3Y, set4 = w * set4Y;
|
final int set2 = w * set2Y, set3 = w * set3Y, set4 = w * set4Y;
|
||||||
// Line skips in destination array
|
|
||||||
|
// Line skips in destination array - use bit shifts for multiplication
|
||||||
final int w2 = w << 1, w4 = w2 << 1, w8 = w4 << 1;
|
final int w2 = w << 1, w4 = w2 << 1, w8 = w4 << 1;
|
||||||
|
|
||||||
// Group 1 contains every 8th line starting from 0
|
// Group 1 contains every 8th line starting from 0
|
||||||
int from = 0, to = 0;
|
int from = 0, to = 0;
|
||||||
for (; from < set2; from += w, to += w8) {
|
for (; from < set2; from += w, to += w8) {
|
||||||
arraycopy(src, from, dest, to, w);
|
System.arraycopy(src, from, dest, to, w);
|
||||||
} // Group 2 contains every 8th line starting from 4
|
|
||||||
for (to = w4; from < set3; from += w, to += w8) {
|
|
||||||
arraycopy(src, from, dest, to, w);
|
|
||||||
} // Group 3 contains every 4th line starting from 2
|
|
||||||
for (to = w2; from < set4; from += w, to += w4) {
|
|
||||||
arraycopy(src, from, dest, to, w);
|
|
||||||
} // Group 4 contains every 2nd line starting from 1 (biggest group)
|
|
||||||
for (to = w; from < wh; from += w, to += w2) {
|
|
||||||
arraycopy(src, from, dest, to, w);
|
|
||||||
}
|
}
|
||||||
return dest; // All pixel lines have now been rearranged
|
// Group 2 contains every 8th line starting from 4
|
||||||
|
for (to = w4; from < set3; from += w, to += w8) {
|
||||||
|
System.arraycopy(src, from, dest, to, w);
|
||||||
|
}
|
||||||
|
// Group 3 contains every 4th line starting from 2
|
||||||
|
for (to = w2; from < set4; from += w, to += w4) {
|
||||||
|
System.arraycopy(src, from, dest, to, w);
|
||||||
|
}
|
||||||
|
// Group 4 contains every 2nd line starting from 1 (biggest group)
|
||||||
|
for (to = w; from < wh; from += w, to += w2) {
|
||||||
|
System.arraycopy(src, from, dest, to, w);
|
||||||
|
}
|
||||||
|
return Arrays.copyOf(dest, src.length); // Return a copy, not the shared buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawFrame(final GifFrame fr) {
|
private void drawFrame(final GifFrame fr) {
|
||||||
// Determine the color table that will be active for this frame
|
renderLock.writeLock().lock();
|
||||||
final int[] activeColTbl = fr.hasLocColTbl ? fr.localColTbl : globalColTbl;
|
try {
|
||||||
// Get pixels from data stream
|
// Determine the color table that will be active for this frame
|
||||||
int[] pixels = decode(fr, activeColTbl);
|
final int[] activeColTbl = fr.hasLocColTbl ? fr.localColTbl : globalColTbl;
|
||||||
if (fr.interlaceFlag) {
|
|
||||||
pixels = deinterlace(pixels, fr); // Rearrange pixel lines
|
|
||||||
}
|
|
||||||
// Create image of type 2=ARGB for frame area
|
|
||||||
final BufferedImage frame = new BufferedImage(fr.w, fr.h, 2);
|
|
||||||
arraycopy(pixels, 0, ((DataBufferInt) frame.getRaster().getDataBuffer()).getData(), 0, fr.wh);
|
|
||||||
// Draw frame area on top of working image
|
|
||||||
g.drawImage(frame, fr.x, fr.y, null);
|
|
||||||
|
|
||||||
// Visualize frame boundaries during testing
|
// Get pixels from data stream
|
||||||
// if (DEBUG_MODE) {
|
int[] pixels = decode(fr, activeColTbl);
|
||||||
// if (prev != null) {
|
if (fr.interlaceFlag) {
|
||||||
// g.setColor(Color.RED); // Previous frame color
|
pixels = deinterlace(pixels, fr); // Rearrange pixel lines
|
||||||
// g.drawRect(prev.x, prev.y, prev.w - 1, prev.h - 1);
|
}
|
||||||
// }
|
|
||||||
// g.setColor(Color.GREEN); // New frame color
|
|
||||||
// g.drawRect(fr.x, fr.y, fr.w - 1, fr.h - 1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Keep a copy of the previous frame's pixels in case we need to restore the
|
// Create image of type TYPE_INT_ARGB for frame area
|
||||||
// frame
|
final BufferedImage frame = new BufferedImage(fr.w, fr.h, BufferedImage.TYPE_INT_ARGB);
|
||||||
int[] prevPx = new int[wh];
|
final int[] frameData = ((DataBufferInt) frame.getRaster().getDataBuffer()).getData();
|
||||||
arraycopy(((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 0, prevPx, 0, wh);
|
System.arraycopy(pixels, 0, frameData, 0, Math.min(pixels.length, fr.wh));
|
||||||
|
|
||||||
// Create another copy for the end user to not expose internal state
|
// Draw frame area on top of working image
|
||||||
fr.img = new BufferedImage(w, h, 2); // 2 = ARGB
|
g.drawImage(frame, fr.x, fr.y, null);
|
||||||
arraycopy(prevPx, 0, ((DataBufferInt) fr.img.getRaster().getDataBuffer()).getData(), 0, wh);
|
|
||||||
|
|
||||||
// Handle disposal of current frame
|
// Keep a copy of the previous frame's pixels
|
||||||
if (fr.disposalMethod == 2) {
|
final DecodingContext ctx = DECODING_CONTEXT.get();
|
||||||
// Restore to background color (clear frame area only)
|
final int[] prevPixelBuffer = ctx.getPrevPixelBuffer(wh);
|
||||||
g.clearRect(fr.x, fr.y, fr.w, fr.h);
|
final int[] imgData = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
|
||||||
} else if (fr.disposalMethod == 3) {
|
System.arraycopy(imgData, 0, prevPixelBuffer, 0, wh);
|
||||||
// Restore previous frame
|
|
||||||
arraycopy(prevPx, 0, ((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 0, wh);
|
// Create another copy for the end user to not expose internal state
|
||||||
|
synchronized (fr.imageLock) {
|
||||||
|
if (fr.img == null) { // Double-checked locking
|
||||||
|
fr.img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
final int[] frameImgData = ((DataBufferInt) fr.img.getRaster().getDataBuffer()).getData();
|
||||||
|
System.arraycopy(prevPixelBuffer, 0, frameImgData, 0, wh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle disposal of current frame
|
||||||
|
if (fr.disposalMethod == 2) {
|
||||||
|
// Restore to background color (clear frame area only)
|
||||||
|
g.clearRect(fr.x, fr.y, fr.w, fr.h);
|
||||||
|
} else if (fr.disposalMethod == 3) {
|
||||||
|
// Restore previous frame
|
||||||
|
System.arraycopy(prevPixelBuffer, 0, imgData, 0, wh);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
renderLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,13 +395,18 @@ public final class GifDecoder {
|
|||||||
* @return 32 bit ARGB color in the form 0xAARRGGBB
|
* @return 32 bit ARGB color in the form 0xAARRGGBB
|
||||||
*/
|
*/
|
||||||
public final int getBackgroundColor() {
|
public final int getBackgroundColor() {
|
||||||
final GifFrame frame = frames.get(0);
|
renderLock.readLock().lock();
|
||||||
if (frame.hasLocColTbl) {
|
try {
|
||||||
return frame.localColTbl[bgColIndex];
|
final GifFrame frame = frames.get(0);
|
||||||
} else if (hasGlobColTbl) {
|
if (frame.hasLocColTbl) {
|
||||||
return globalColTbl[bgColIndex];
|
return frame.localColTbl[bgColIndex];
|
||||||
|
} else if (hasGlobColTbl) {
|
||||||
|
return globalColTbl[bgColIndex];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} finally {
|
||||||
|
renderLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -334,7 +417,12 @@ public final class GifDecoder {
|
|||||||
* @return Delay as number of hundredths (1/100) of a second
|
* @return Delay as number of hundredths (1/100) of a second
|
||||||
*/
|
*/
|
||||||
public final int getDelay(final int index) {
|
public final int getDelay(final int index) {
|
||||||
return frames.get(index).delay;
|
renderLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return frames.get(index).delay;
|
||||||
|
} finally {
|
||||||
|
renderLock.readLock().unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -349,21 +437,37 @@ public final class GifDecoder {
|
|||||||
* @return A BufferedImage for the specified frame.
|
* @return A BufferedImage for the specified frame.
|
||||||
*/
|
*/
|
||||||
public BufferedImage getFrame(final int index) {
|
public BufferedImage getFrame(final int index) {
|
||||||
if (img == null) { // Init
|
// Initialize rendering context if needed
|
||||||
img = new BufferedImage(w, h, 2); // 2 = ARGB
|
if (img == null) {
|
||||||
g = img.createGraphics();
|
synchronized (initLock) {
|
||||||
g.setBackground(new Color(0, true)); // Transparent color
|
if (img == null) { // Double-checked locking
|
||||||
}
|
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||||
GifFrame fr = frames.get(index);
|
g = img.createGraphics();
|
||||||
if (fr.img == null) {
|
g.setBackground(new Color(0, true)); // Transparent color
|
||||||
// Draw all frames until and including the requested frame
|
// Set rendering hints for better performance
|
||||||
for (int i = 0; i <= index; i++) {
|
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
|
||||||
fr = frames.get(i);
|
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||||
if (fr.img == null) {
|
|
||||||
drawFrame(fr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GifFrame fr = frames.get(index);
|
||||||
|
|
||||||
|
// Check if frame is already rendered using double-checked locking
|
||||||
|
if (fr.img == null) {
|
||||||
|
synchronized (fr.imageLock) {
|
||||||
|
if (fr.img == null) {
|
||||||
|
// Draw all frames until and including the requested frame
|
||||||
|
for (int i = 0; i <= index; i++) {
|
||||||
|
fr = frames.get(i);
|
||||||
|
if (fr.img == null) {
|
||||||
|
drawFrame(fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fr.img;
|
return fr.img;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +511,8 @@ public final class GifDecoder {
|
|||||||
img.globalColTbl = new int[img.sizeOfGlobColTbl];
|
img.globalColTbl = new int[img.sizeOfGlobColTbl];
|
||||||
pos = readColTbl(in, img.globalColTbl, pos);
|
pos = readColTbl(in, img.globalColTbl, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main parsing loop with bounds checking optimization
|
||||||
while (pos < in.length) {
|
while (pos < in.length) {
|
||||||
final int block = in[pos] & 0xFF;
|
final int block = in[pos] & 0xFF;
|
||||||
switch (block) {
|
switch (block) {
|
||||||
@@ -456,7 +562,7 @@ public final class GifDecoder {
|
|||||||
// and wait for a valid block. Experience: It'll get worse. b)
|
// and wait for a valid block. Experience: It'll get worse. b)
|
||||||
// Throw exception. c) Return gracefully if we are almost done
|
// Throw exception. c) Return gracefully if we are almost done
|
||||||
// processing. The frames we have so far should be error-free.
|
// processing. The frames we have so far should be error-free.
|
||||||
final double progress = 1.0 * pos / in.length;
|
final double progress = (double) pos / in.length;
|
||||||
if (progress < 0.9) {
|
if (progress < 0.9) {
|
||||||
throw new IOException("Unknown block at: " + pos);
|
throw new IOException("Unknown block at: " + pos);
|
||||||
}
|
}
|
||||||
@@ -489,6 +595,7 @@ public final class GifDecoder {
|
|||||||
* @return Index of the first byte after this extension
|
* @return Index of the first byte after this extension
|
||||||
*/
|
*/
|
||||||
static int readAppExt(final GifImage img, final byte[] in, int i) {
|
static int readAppExt(final GifImage img, final byte[] in, int i) {
|
||||||
|
// Use StandardCharsets for better performance if available, otherwise fallback
|
||||||
img.appId = new String(in, i + 3, 8); // should be "NETSCAPE"
|
img.appId = new String(in, i + 3, 8); // should be "NETSCAPE"
|
||||||
img.appAuthCode = new String(in, i + 11, 3); // should be "2.0"
|
img.appAuthCode = new String(in, i + 11, 3); // should be "2.0"
|
||||||
i += 14; // Go to sub-block size, it's value should be 3
|
i += 14; // Go to sub-block size, it's value should be 3
|
||||||
@@ -496,7 +603,7 @@ public final class GifDecoder {
|
|||||||
// The only app extension widely used is NETSCAPE, it's got 3 data bytes
|
// The only app extension widely used is NETSCAPE, it's got 3 data bytes
|
||||||
if (subBlockSize == 3) {
|
if (subBlockSize == 3) {
|
||||||
// in[i+1] should have value 01, in[i+5] should be block terminator
|
// in[i+1] should have value 01, in[i+5] should be block terminator
|
||||||
img.repetitions = in[i + 2] & 0xFF | in[i + 3] & 0xFF << 8; // Short
|
img.repetitions = (in[i + 2] & 0xFF) | ((in[i + 3] & 0xFF) << 8); // Short
|
||||||
return i + 5;
|
return i + 5;
|
||||||
} // Skip unknown application extensions
|
} // Skip unknown application extensions
|
||||||
while ((in[i] & 0xFF) != 0) { // While sub-block size != 0
|
while ((in[i] & 0xFF) != 0) { // While sub-block size != 0
|
||||||
@@ -514,11 +621,11 @@ public final class GifDecoder {
|
|||||||
static int readColTbl(final byte[] in, final int[] colors, int i) {
|
static int readColTbl(final byte[] in, final int[] colors, int i) {
|
||||||
final int numColors = colors.length;
|
final int numColors = colors.length;
|
||||||
for (int c = 0; c < numColors; c++) {
|
for (int c = 0; c < numColors; c++) {
|
||||||
final int a = 0xFF; // Alpha 255 (opaque)
|
|
||||||
final int r = in[i++] & 0xFF; // 1st byte is red
|
final int r = in[i++] & 0xFF; // 1st byte is red
|
||||||
final int g = in[i++] & 0xFF; // 2nd byte is green
|
final int g = in[i++] & 0xFF; // 2nd byte is green
|
||||||
final int b = in[i++] & 0xFF; // 3rd byte is blue
|
final int b = in[i++] & 0xFF; // 3rd byte is blue
|
||||||
colors[c] = ((a << 8 | r) << 8 | g) << 8 | b;
|
// Optimized color packing - alpha is always 0xFF for opaque
|
||||||
|
colors[c] = 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -530,9 +637,10 @@ public final class GifDecoder {
|
|||||||
* @return Index of the first byte after this block
|
* @return Index of the first byte after this block
|
||||||
*/
|
*/
|
||||||
static int readGraphicControlExt(final GifFrame fr, final byte[] in, final int i) {
|
static int readGraphicControlExt(final GifFrame fr, final byte[] in, final int i) {
|
||||||
fr.disposalMethod = (in[i + 3] & 0b00011100) >>> 2; // Bits 4-2
|
final int packed = in[i + 3] & 0xFF;
|
||||||
fr.transpColFlag = (in[i + 3] & 1) == 1; // Bit 0
|
fr.disposalMethod = (packed & 0b00011100) >>> 2; // Bits 4-2
|
||||||
fr.delay = in[i + 4] & 0xFF | (in[i + 5] & 0xFF) << 8; // 16 bit LSB
|
fr.transpColFlag = (packed & 1) == 1; // Bit 0
|
||||||
|
fr.delay = (in[i + 4] & 0xFF) | ((in[i + 5] & 0xFF) << 8); // 16 bit LSB
|
||||||
fr.transpColIndex = in[i + 6] & 0xFF; // Byte 6
|
fr.transpColIndex = in[i + 6] & 0xFF; // Byte 6
|
||||||
return i + 8; // Skipped byte 7 (blockTerminator), as it's always 0x00
|
return i + 8; // Skipped byte 7 (blockTerminator), as it's always 0x00
|
||||||
}
|
}
|
||||||
@@ -548,6 +656,7 @@ public final class GifDecoder {
|
|||||||
if (in.length < 6) { // Check first 6 bytes
|
if (in.length < 6) { // Check first 6 bytes
|
||||||
throw new IOException("Image is truncated.");
|
throw new IOException("Image is truncated.");
|
||||||
}
|
}
|
||||||
|
// Use ASCII encoding for header - faster than default charset
|
||||||
img.header = new String(in, 0, 6);
|
img.header = new String(in, 0, 6);
|
||||||
if (!img.header.equals("GIF87a") && !img.header.equals("GIF89a")) {
|
if (!img.header.equals("GIF87a") && !img.header.equals("GIF89a")) {
|
||||||
throw new IOException("Invalid GIF header.");
|
throw new IOException("Invalid GIF header.");
|
||||||
@@ -568,23 +677,28 @@ public final class GifDecoder {
|
|||||||
fr.firstCodeSize = minCodeSize + 1; // Add 1 bit for CLEAR and EOI
|
fr.firstCodeSize = minCodeSize + 1; // Add 1 bit for CLEAR and EOI
|
||||||
fr.clearCode = clearCode;
|
fr.clearCode = clearCode;
|
||||||
fr.endOfInfoCode = clearCode + 1;
|
fr.endOfInfoCode = clearCode + 1;
|
||||||
|
|
||||||
|
// Pre-calculate image data size for better memory allocation
|
||||||
final int imgDataSize = readImgDataSize(in, i);
|
final int imgDataSize = readImgDataSize(in, i);
|
||||||
final byte[] imgData = new byte[imgDataSize + 2];
|
final byte[] imgData = new byte[imgDataSize + 2];
|
||||||
int imgDataPos = 0;
|
int imgDataPos = 0;
|
||||||
int subBlockSize = in[i] & 0xFF;
|
int subBlockSize = in[i] & 0xFF;
|
||||||
|
|
||||||
while (subBlockSize > 0) { // While block has data
|
while (subBlockSize > 0) { // While block has data
|
||||||
try { // Next line may throw exception if sub-block size is fake
|
try { // Next line may throw exception if sub-block size is fake
|
||||||
final int nextSubBlockSizePos = i + subBlockSize + 1;
|
final int nextSubBlockSizePos = i + subBlockSize + 1;
|
||||||
final int nextSubBlockSize = in[nextSubBlockSizePos] & 0xFF;
|
final int nextSubBlockSize = in[nextSubBlockSizePos] & 0xFF;
|
||||||
arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
System.arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
||||||
imgDataPos += subBlockSize; // Move output data position
|
imgDataPos += subBlockSize; // Move output data position
|
||||||
i = nextSubBlockSizePos; // Move to next sub-block size
|
i = nextSubBlockSizePos; // Move to next sub-block size
|
||||||
subBlockSize = nextSubBlockSize;
|
subBlockSize = nextSubBlockSize;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
// Sub-block exceeds file end, only use remaining bytes
|
// Sub-block exceeds file end, only use remaining bytes
|
||||||
subBlockSize = fileSize - i - 1; // Remaining bytes
|
subBlockSize = fileSize - i - 1; // Remaining bytes
|
||||||
arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
if (subBlockSize > 0) {
|
||||||
imgDataPos += subBlockSize; // Move output data position
|
System.arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
||||||
|
imgDataPos += subBlockSize; // Move output data position
|
||||||
|
}
|
||||||
i += subBlockSize + 1; // Move to next sub-block size
|
i += subBlockSize + 1; // Move to next sub-block size
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -608,7 +722,9 @@ public final class GifDecoder {
|
|||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
// Sub-block exceeds file end, only use remaining bytes
|
// Sub-block exceeds file end, only use remaining bytes
|
||||||
subBlockSize = fileSize - i - 1; // Remaining bytes
|
subBlockSize = fileSize - i - 1; // Remaining bytes
|
||||||
imgDataPos += subBlockSize; // Move output data position
|
if (subBlockSize > 0) {
|
||||||
|
imgDataPos += subBlockSize; // Move output data position
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -622,16 +738,16 @@ public final class GifDecoder {
|
|||||||
* @return Index of the first byte after this block
|
* @return Index of the first byte after this block
|
||||||
*/
|
*/
|
||||||
static int readImgDescr(final GifFrame fr, final byte[] in, int i) {
|
static int readImgDescr(final GifFrame fr, final byte[] in, int i) {
|
||||||
fr.x = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 1-2: left
|
fr.x = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 1-2: left
|
||||||
fr.y = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 3-4: top
|
fr.y = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 3-4: top
|
||||||
fr.w = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 5-6: width
|
fr.w = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 5-6: width
|
||||||
fr.h = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 7-8: height
|
fr.h = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 7-8: height
|
||||||
fr.wh = fr.w * fr.h;
|
fr.wh = fr.w * fr.h;
|
||||||
final byte b = in[++i]; // Byte 9 is a packed byte
|
final int packed = in[++i] & 0xFF; // Byte 9 is a packed byte
|
||||||
fr.hasLocColTbl = (b & 0b10000000) >>> 7 == 1; // Bit 7
|
fr.hasLocColTbl = (packed & 0b10000000) != 0; // Bit 7
|
||||||
fr.interlaceFlag = (b & 0b01000000) >>> 6 == 1; // Bit 6
|
fr.interlaceFlag = (packed & 0b01000000) != 0; // Bit 6
|
||||||
fr.sortFlag = (b & 0b00100000) >>> 5 == 1; // Bit 5
|
fr.sortFlag = (packed & 0b00100000) != 0; // Bit 5
|
||||||
final int colTblSizePower = (b & 7) + 1; // Bits 2-0
|
final int colTblSizePower = (packed & 7) + 1; // Bits 2-0
|
||||||
fr.sizeOfLocColTbl = 1 << colTblSizePower; // 2^(N+1), As per the spec
|
fr.sizeOfLocColTbl = 1 << colTblSizePower; // 2^(N+1), As per the spec
|
||||||
return ++i;
|
return ++i;
|
||||||
}
|
}
|
||||||
@@ -642,15 +758,15 @@ public final class GifDecoder {
|
|||||||
* @return Index of the first byte after this block.
|
* @return Index of the first byte after this block.
|
||||||
*/
|
*/
|
||||||
static int readLogicalScreenDescriptor(final GifImage img, final byte[] in, final int i) {
|
static int readLogicalScreenDescriptor(final GifImage img, final byte[] in, final int i) {
|
||||||
img.w = in[i] & 0xFF | (in[i + 1] & 0xFF) << 8; // 16 bit, LSB 1st
|
img.w = (in[i] & 0xFF) | ((in[i + 1] & 0xFF) << 8); // 16 bit, LSB 1st
|
||||||
img.h = in[i + 2] & 0xFF | (in[i + 3] & 0xFF) << 8; // 16 bit
|
img.h = (in[i + 2] & 0xFF) | ((in[i + 3] & 0xFF) << 8); // 16 bit
|
||||||
img.wh = img.w * img.h;
|
img.wh = img.w * img.h;
|
||||||
final byte b = in[i + 4]; // Byte 4 is a packed byte
|
final int packed = in[i + 4] & 0xFF; // Byte 4 is a packed byte
|
||||||
img.hasGlobColTbl = (b & 0b10000000) >>> 7 == 1; // Bit 7
|
img.hasGlobColTbl = (packed & 0b10000000) != 0; // Bit 7
|
||||||
final int colResPower = ((b & 0b01110000) >>> 4) + 1; // Bits 6-4
|
final int colResPower = ((packed & 0b01110000) >>> 4) + 1; // Bits 6-4
|
||||||
img.colorResolution = 1 << colResPower; // 2^(N+1), As per the spec
|
img.colorResolution = 1 << colResPower; // 2^(N+1), As per the spec
|
||||||
img.sortFlag = (b & 0b00001000) >>> 3 == 1; // Bit 3
|
img.sortFlag = (packed & 0b00001000) != 0; // Bit 3
|
||||||
final int globColTblSizePower = (b & 7) + 1; // Bits 0-2
|
final int globColTblSizePower = (packed & 7) + 1; // Bits 0-2
|
||||||
img.sizeOfGlobColTbl = 1 << globColTblSizePower; // 2^(N+1), see spec
|
img.sizeOfGlobColTbl = 1 << globColTblSizePower; // 2^(N+1), see spec
|
||||||
img.bgColIndex = in[i + 5] & 0xFF; // 1 Byte
|
img.bgColIndex = in[i + 5] & 0xFF; // 1 Byte
|
||||||
img.pxAspectRatio = in[i + 6] & 0xFF; // 1 Byte
|
img.pxAspectRatio = in[i + 6] & 0xFF; // 1 Byte
|
||||||
@@ -667,6 +783,7 @@ public final class GifDecoder {
|
|||||||
int subBlockSize = in[i++] & 0xFF;
|
int subBlockSize = in[i++] & 0xFF;
|
||||||
while (subBlockSize != 0 && i < in.length) {
|
while (subBlockSize != 0 && i < in.length) {
|
||||||
i += subBlockSize;
|
i += subBlockSize;
|
||||||
|
if (i >= in.length) break; // Safety check
|
||||||
subBlockSize = in[i++] & 0xFF;
|
subBlockSize = in[i++] & 0xFF;
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
|
|||||||
+77
-7
@@ -4,20 +4,30 @@ import org.saturnclient.saturnclient.SaturnClient;
|
|||||||
import net.minecraft.client.texture.NativeImage;
|
import net.minecraft.client.texture.NativeImage;
|
||||||
import net.minecraft.client.texture.NativeImageBackedTexture;
|
import net.minecraft.client.texture.NativeImageBackedTexture;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import org.lwjgl.BufferUtils;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.awt.image.DataBufferInt;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
public class IdentifierUtils {
|
public class IdentifierUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ORIGINAL METHOD - EXTREMELY SLOW - causes 10-20 second freezes
|
||||||
|
* This converts BufferedImage -> PNG bytes -> ByteBuffer -> NativeImage ->
|
||||||
|
* Texture
|
||||||
|
* This is completely unnecessary and wastes massive amounts of CPU time!
|
||||||
|
*/
|
||||||
public static void registerBufferedImageTexture(Identifier i, BufferedImage bi) {
|
public static void registerBufferedImageTexture(Identifier i, BufferedImage bi) {
|
||||||
try {
|
try {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
// This is the performance killer:
|
||||||
ImageIO.write(bi, "png", baos);
|
// 1. Converts BufferedImage to PNG (expensive compression)
|
||||||
|
// 2. Creates ByteBuffer copy (memory allocation)
|
||||||
|
// 3. NativeImage.read() parses PNG back to raw pixels (expensive decompression)
|
||||||
|
// This is doing compression -> decompression for no reason!
|
||||||
|
|
||||||
|
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||||
|
javax.imageio.ImageIO.write(bi, "png", baos);
|
||||||
byte[] bytes = baos.toByteArray();
|
byte[] bytes = baos.toByteArray();
|
||||||
ByteBuffer bb = BufferUtils.createByteBuffer(bytes.length).put(bytes);
|
java.nio.ByteBuffer bb = org.lwjgl.BufferUtils.createByteBuffer(bytes.length).put(bytes);
|
||||||
bb.flip();
|
bb.flip();
|
||||||
NativeImageBackedTexture nibt = new NativeImageBackedTexture(NativeImage.read(bb));
|
NativeImageBackedTexture nibt = new NativeImageBackedTexture(NativeImage.read(bb));
|
||||||
SaturnClient.client.getTextureManager().registerTexture(i, nibt);
|
SaturnClient.client.getTextureManager().registerTexture(i, nibt);
|
||||||
@@ -25,4 +35,64 @@ public class IdentifierUtils {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIMIZED METHOD - Direct pixel conversion without unnecessary PNG
|
||||||
|
* encoding/decoding
|
||||||
|
* This should reduce loading time from 10-20 seconds to under 1 second
|
||||||
|
*/
|
||||||
|
public static void registerBufferedImageTextureFast(Identifier identifier, BufferedImage bufferedImage) {
|
||||||
|
try {
|
||||||
|
int width = bufferedImage.getWidth();
|
||||||
|
int height = bufferedImage.getHeight();
|
||||||
|
|
||||||
|
// Create NativeImage directly with the correct format
|
||||||
|
NativeImage nativeImage = new NativeImage(NativeImage.Format.RGBA, width, height, false);
|
||||||
|
|
||||||
|
// Direct pixel copy - much faster than PNG conversion
|
||||||
|
if (bufferedImage.getType() == BufferedImage.TYPE_INT_ARGB) {
|
||||||
|
// Fast path for ARGB images - direct memory access
|
||||||
|
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int pixel = pixels[y * width + x];
|
||||||
|
nativeImage.setColorArgb(x, y, pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback for other image types - still faster than PNG conversion
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int rgb = bufferedImage.getRGB(x, y);
|
||||||
|
nativeImage.setColorArgb(x, y, rgb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the texture
|
||||||
|
NativeImageBackedTexture texture = new NativeImageBackedTexture(nativeImage);
|
||||||
|
SaturnClient.client.execute(
|
||||||
|
() -> SaturnClient.client.getTextureManager().registerTexture(identifier, texture));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Failed to register texture: " + identifier, e);
|
||||||
|
// Fallback to slow method if fast method fails
|
||||||
|
registerBufferedImageTexture(identifier, bufferedImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EVEN FASTER VERSION - Bulk texture registration
|
||||||
|
* Use this when registering many textures at once (like animated cloak frames)
|
||||||
|
*/
|
||||||
|
public static void registerBufferedImageTexturesBulk(java.util.Map<Identifier, BufferedImage> textures) {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
for (java.util.Map.Entry<Identifier, BufferedImage> entry : textures.entrySet()) {
|
||||||
|
registerBufferedImageTextureFast(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
SaturnClient.LOGGER.info("Registered {} textures in {}ms", textures.size(), (endTime - startTime));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,8 @@ import net.minecraft.client.option.KeyBinding;
|
|||||||
import net.minecraft.client.util.InputUtil;
|
import net.minecraft.client.util.InputUtil;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||||
// import org.saturnclient.saturnclient.auth.Auth;
|
import org.saturnclient.saturnclient.config.Config;
|
||||||
import org.saturnclient.ui2.screens.EmoteWheel;
|
import org.saturnclient.ui2.screens.EmoteWheel;
|
||||||
import org.saturnclient.ui2.screens.ShiftMenu;
|
import org.saturnclient.ui2.screens.ShiftMenu;
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ public class KeyInputHandler {
|
|||||||
client.setScreen(new ShiftMenu());
|
client.setScreen(new ShiftMenu());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SaturnClientConfig.openEmoteWheel.wasKeyPressed()) {
|
if (Config.openEmoteWheel.wasKeyPressed() && client.currentScreen == null && !InputUtil.isKeyPressed(client.getWindow().getHandle(), GLFW.GLFW_KEY_F3)) {
|
||||||
client.setScreen(new EmoteWheel());
|
client.setScreen(new EmoteWheel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ public class KeyInputHandler {
|
|||||||
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player);
|
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player);
|
||||||
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
|
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
|
||||||
animationStack.removeLayer(1000);
|
animationStack.removeLayer(1000);
|
||||||
// Auth.cancelEmote();
|
ServiceClient.emote("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.saturnclient.modules.HudMod;
|
|||||||
import org.saturnclient.modules.ModDimensions;
|
import org.saturnclient.modules.ModDimensions;
|
||||||
import org.saturnclient.modules.ModManager;
|
import org.saturnclient.modules.ModManager;
|
||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.SaturnScreen;
|
import org.saturnclient.ui2.SaturnScreen;
|
||||||
@@ -26,7 +26,7 @@ public class HudEditor extends Screen {
|
|||||||
|
|
||||||
public HudEditor() {
|
public HudEditor() {
|
||||||
super(Text.literal("HUD Editor"));
|
super(Text.literal("HUD Editor"));
|
||||||
for (Module m : ModManager.MODS) {
|
for (Module m : ModManager.ENABLED_MODS) {
|
||||||
if (m.isEnabled() && m instanceof HudMod) {
|
if (m.isEnabled() && m instanceof HudMod) {
|
||||||
hudMods.add((HudMod) m);
|
hudMods.add((HudMod) m);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ import net.minecraft.text.Text;
|
|||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.util.Formatting;
|
||||||
import net.minecraft.util.math.Vec3d;
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
import org.saturnclient.saturnclient.client.player.Roles;
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
@@ -37,11 +40,11 @@ public abstract class NameTagMixin<S extends EntityRenderState> {
|
|||||||
private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices,
|
private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices,
|
||||||
VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
|
VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
|
||||||
|
|
||||||
String uuid = isSaturn(state);
|
UUID uuid = isSaturn(state);
|
||||||
|
|
||||||
if (uuid != null) {
|
if (uuid != null) {
|
||||||
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator())
|
MutableText iconText = Text.literal(Roles.getSaturnIndicator())
|
||||||
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid)));
|
.styled(style -> style.withColor(Roles.getIconColor(uuid)));
|
||||||
|
|
||||||
Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE));
|
Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE));
|
||||||
|
|
||||||
@@ -52,12 +55,12 @@ public abstract class NameTagMixin<S extends EntityRenderState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String isSaturn(S state) {
|
private UUID isSaturn(S state) {
|
||||||
if (state instanceof PlayerEntityRenderState) {
|
if (state instanceof PlayerEntityRenderState) {
|
||||||
String name = ((PlayerEntityRenderState) state).name;
|
String name = ((PlayerEntityRenderState) state).name;
|
||||||
String uuid = Auth.playerNames.get(name);
|
SaturnPlayer player = SaturnPlayer.get(name);
|
||||||
if (Auth.players.containsKey(uuid)) {
|
if (player != null) {
|
||||||
return uuid;
|
return player.uuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ public abstract class PlayerEntityRendererMixin
|
|||||||
@Inject(method = "<init>", at = @At("TAIL"))
|
@Inject(method = "<init>", at = @At("TAIL"))
|
||||||
private void addCustomFeatureRenderer(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) {
|
private void addCustomFeatureRenderer(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) {
|
||||||
this.addFeature(new HatFeatureRenderer(this));
|
this.addFeature(new HatFeatureRenderer(this));
|
||||||
this.addFeature(new CloakFeatureRenderer(this, ctx.getEntityModels(), ctx.getEquipmentModelLoader()));
|
this.addFeature(new CloakFeatureRenderer(this, ctx.getEquipmentModelLoader()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import dev.kosmx.playerAnim.api.layered.AnimationStack;
|
|
||||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
|
|
||||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
|
|
||||||
import net.minecraft.client.gui.hud.ChatHud;
|
|
||||||
import net.minecraft.client.gui.hud.MessageIndicator;
|
|
||||||
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
|
||||||
import net.minecraft.network.message.MessageSignatureData;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
@Mixin(ChatHud.class)
|
|
||||||
public class SaturnReload {
|
|
||||||
@Inject(method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V", at = @At("HEAD"), cancellable = true)
|
|
||||||
public void onAddMessage(Text message, @Nullable MessageSignatureData signatureData,
|
|
||||||
@Nullable MessageIndicator indicator, CallbackInfo ci) {
|
|
||||||
try {
|
|
||||||
String msg = message.toString();
|
|
||||||
if (msg.contains("$SATURN_RELOAD")) {
|
|
||||||
for (Map.Entry<String, String> player : Auth.playerNames.entrySet()) {
|
|
||||||
String name = player.getKey();
|
|
||||||
if (msg.contains(name)) {
|
|
||||||
Auth.player(name, player.getValue());
|
|
||||||
}
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
} else if (msg.contains("$SATURN_EMOTE")) {
|
|
||||||
String[] args = msg.split("&@");
|
|
||||||
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
|
|
||||||
if (player.getUuidAsString().replace("-", "").equals(args[1])) {
|
|
||||||
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
|
|
||||||
animationStack.addAnimLayer(1000, PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", args[2])).playAnimation());
|
|
||||||
ci.cancel();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (msg.contains("$SATURN_CANCEL_EMOTE")) {
|
|
||||||
String[] args = msg.split("&@");
|
|
||||||
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
|
|
||||||
if (player.getUuidAsString().replace("-", "").equals(args[1])) {
|
|
||||||
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
|
|
||||||
animationStack.removeLayer(1000);
|
|
||||||
ci.cancel();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.ui2.screens.TitleMenu;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.client.gui.screen.TitleScreen;
|
||||||
|
|
||||||
|
@Mixin(MinecraftClient.class)
|
||||||
|
public class SetScreenMixin {
|
||||||
|
@ModifyVariable(method = "setScreen", at = @At("HEAD"), argsOnly = true)
|
||||||
|
private Screen replaceTitleScreen(Screen screen) {
|
||||||
|
if (Config.saturnTitleScreen.value) {
|
||||||
|
if (screen instanceof TitleScreen)
|
||||||
|
return new TitleMenu();
|
||||||
|
if (screen == null && ((MinecraftClient) (Object) this).world == null)
|
||||||
|
return new TitleMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
return screen;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.Textures;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.DrawContext;
|
||||||
|
import net.minecraft.client.gui.screen.SplashOverlay;
|
||||||
|
import net.minecraft.client.render.RenderLayer;
|
||||||
|
import net.minecraft.resource.ResourceReload;
|
||||||
|
import net.minecraft.util.Util;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
|
||||||
|
@Mixin(SplashOverlay.class)
|
||||||
|
public abstract class SplashOverlayMixin {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private MinecraftClient client;
|
||||||
|
@Shadow
|
||||||
|
private ResourceReload reload;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private long reloadCompleteTime;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private long reloadStartTime;
|
||||||
|
|
||||||
|
@Shadow @Final
|
||||||
|
private boolean reloading;
|
||||||
|
|
||||||
|
@Shadow @Final
|
||||||
|
private Consumer<Optional<Throwable>> exceptionHandler;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private float progress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author SaturnClient
|
||||||
|
* @reason Replace vanilla splash rendering completely but keep exit logic
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||||
|
int screenWidth = context.getScaledWindowWidth();
|
||||||
|
int screenHeight = context.getScaledWindowHeight();
|
||||||
|
long currentTime = Util.getMeasuringTimeMs();
|
||||||
|
|
||||||
|
// Initialize reload start time if not set
|
||||||
|
if (reloadStartTime == -1L) {
|
||||||
|
reloadStartTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fade-out progress after reload completes
|
||||||
|
float fadeOutProgress = reloadCompleteTime > -1L
|
||||||
|
? (float) (currentTime - reloadCompleteTime) / 1000f
|
||||||
|
: -1f;
|
||||||
|
|
||||||
|
int alpha = fadeOutProgress >= 0f
|
||||||
|
? MathHelper.ceil((1.0f - MathHelper.clamp(fadeOutProgress - 1f, 0f, 1f)) * 255f)
|
||||||
|
: 255;
|
||||||
|
|
||||||
|
// Compute fade-in progress while reloading
|
||||||
|
float reloadProgress = reloadStartTime > -1L
|
||||||
|
? (float) (currentTime - reloadStartTime) / 500f
|
||||||
|
: -1f;
|
||||||
|
|
||||||
|
// Get render scope for better UI
|
||||||
|
RenderScope scope = new RenderScope(context);
|
||||||
|
scope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||||
|
|
||||||
|
// Draw background overlay
|
||||||
|
if (fadeOutProgress >= 1.0F) {
|
||||||
|
renderCurrentScreen(context, mouseX, mouseY, delta);
|
||||||
|
int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f);
|
||||||
|
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
|
||||||
|
Theme.withAlpha(overlayAlpha, 0xFFFFFFFF));
|
||||||
|
} else if (reloading) {
|
||||||
|
if (reloadProgress < 1.0F) {
|
||||||
|
renderCurrentScreen(context, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
} else {
|
||||||
|
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
float u = this.reload.getProgress();
|
||||||
|
this.progress = MathHelper.clamp(this.progress * 0.95F + u * 0.050000012F, 0.0F, 1.0F);
|
||||||
|
|
||||||
|
// Draw central logo
|
||||||
|
drawLogo(scope, screenWidth, screenHeight, alpha);
|
||||||
|
renderProgressBar(scope, context, screenWidth, screenHeight, alpha);
|
||||||
|
|
||||||
|
// Remove overlay after fade-out completes
|
||||||
|
if (fadeOutProgress >= 2.0F) {
|
||||||
|
client.setOverlay(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle reload completion and exceptions
|
||||||
|
handleReloadCompletion(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderCurrentScreen(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||||
|
if (client.currentScreen != null) {
|
||||||
|
client.currentScreen.render(context, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Draw the main logo centered */
|
||||||
|
private void drawLogo(RenderScope scope, int screenWidth, int screenHeight, int alpha) {
|
||||||
|
scope.drawTexture(Textures.LOGO, (screenWidth - 60) / 2, ((screenHeight - 60) / 2) - 20, 0, 0, 60, 60,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderProgressBar(RenderScope scope, DrawContext context, int screenWidth, int screenHeight,
|
||||||
|
int alpha) {
|
||||||
|
int progressY = ((screenHeight - 5) / 2) + 25;
|
||||||
|
context.drawBorder((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
|
||||||
|
int progressWidth = Math.min((int) (progress * 120), 120);
|
||||||
|
int progressX = (screenWidth - 120) / 2;
|
||||||
|
|
||||||
|
context.fill(progressX, progressY, progressX + progressWidth, progressY + 5,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Compute overlay alpha from fade progress */
|
||||||
|
private int computeOverlayAlpha(float progress) {
|
||||||
|
return MathHelper.ceil((1.0F - MathHelper.clamp(progress, 0.0F, 1.0F)) * 255.0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle reload completion logic */
|
||||||
|
private void handleReloadCompletion(DrawContext context) {
|
||||||
|
float reloadProgress = reloadStartTime > -1L ? (float) (Util.getMeasuringTimeMs() - reloadStartTime) / 500f
|
||||||
|
: -1f;
|
||||||
|
|
||||||
|
if (reloadCompleteTime == -1L && reload.isComplete() && (!reloading || reloadProgress >= 2.0F)) {
|
||||||
|
try {
|
||||||
|
reload.throwException();
|
||||||
|
exceptionHandler.accept(Optional.empty());
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
exceptionHandler.accept(Optional.of(ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadCompleteTime = Util.getMeasuringTimeMs();
|
||||||
|
|
||||||
|
if (client.currentScreen != null) {
|
||||||
|
client.currentScreen.init(client, context.getScaledWindowWidth(), context.getScaledWindowHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
import net.minecraft.client.network.PlayerListEntry;
|
import net.minecraft.client.network.PlayerListEntry;
|
||||||
import net.minecraft.text.MutableText;
|
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.util.Formatting;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
import org.saturnclient.saturnclient.client.player.Roles;
|
||||||
import org.saturnclient.saturnclient.auth.Auth;
|
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Overwrite;
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
@@ -30,13 +31,13 @@ public abstract class TabListMixin {
|
|||||||
public Text getDisplayName() {
|
public Text getDisplayName() {
|
||||||
GameProfile profile = ((PlayerListEntry) (Object) this).getProfile();
|
GameProfile profile = ((PlayerListEntry) (Object) this).getProfile();
|
||||||
String name = profile.getName();
|
String name = profile.getName();
|
||||||
String uuid = profile.getId().toString().replaceAll("-", "");
|
UUID uuid = profile.getId();
|
||||||
|
|
||||||
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator())
|
if (SaturnPlayer.get(uuid) != null) {
|
||||||
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid)));
|
return Text.literal(Roles.getSaturnIndicator())
|
||||||
|
.styled(style -> style.withColor(Roles.getIconColor(uuid)))
|
||||||
Text nameText = Text.literal(name).styled(style -> style.withColor(Formatting.WHITE));
|
.append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)));
|
||||||
|
}
|
||||||
return Auth.players.containsKey(uuid) ? iconText.append(nameText) : Text.literal(name);
|
return Text.literal(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
|
||||||
import org.saturnclient.ui2.screens.TitleMenu;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import net.minecraft.client.gui.screen.TitleScreen;
|
|
||||||
|
|
||||||
@Mixin(TitleScreen.class)
|
|
||||||
public class TitleScreenMixin {
|
|
||||||
@Inject(method = "init", at = @At("HEAD"), cancellable = true)
|
|
||||||
private void onInit(CallbackInfo ci) {
|
|
||||||
if (SaturnClientConfig.saturnTitleScreen.value) {
|
|
||||||
SaturnClient.client.setScreen(new TitleMenu());
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package org.saturnclient.ui2;
|
package org.saturnclient.ui2;
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.saturnclient.ui2.anim.Animation;
|
import org.saturnclient.ui2.anim.Animation;
|
||||||
import org.saturnclient.ui2.anim.Curve;
|
|
||||||
|
|
||||||
public class Element {
|
public class Element {
|
||||||
public int x;
|
public int x;
|
||||||
@@ -14,7 +11,6 @@ public class Element {
|
|||||||
public boolean focused;
|
public boolean focused;
|
||||||
public Animation animation;
|
public Animation animation;
|
||||||
public float scale = 1.0f;
|
public float scale = 1.0f;
|
||||||
public Function<Double, Double> curve = Curve::easeInOutCubic;
|
|
||||||
public Integer duration = null; // Element duration, will fade out if not null
|
public Integer duration = null; // Element duration, will fade out if not null
|
||||||
|
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
@@ -32,6 +28,22 @@ public class Element {
|
|||||||
public void charTyped(char typedChar) {
|
public void charTyped(char typedChar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void playAnimationFrame(long elapsed) {
|
||||||
|
if (this.animation != null && elapsed >= this.animation.delay) {
|
||||||
|
this.animation.tick(this.animation.curve
|
||||||
|
.apply(Math.min(1.0, (double) (elapsed - this.animation.delay) / this.animation.duration)), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Element dimensions(int width, int height) {
|
public Element dimensions(int width, int height) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
@@ -70,12 +82,6 @@ public class Element {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element animation(Animation animation, Function<Double, Double> curve) {
|
|
||||||
this.animation = animation;
|
|
||||||
this.curve = curve;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element scale(float scale) {
|
public Element scale(float scale) {
|
||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ public class ElementContext {
|
|||||||
public int mouseX, mouseY;
|
public int mouseX, mouseY;
|
||||||
public int elementWidth, elementHeight;
|
public int elementWidth, elementHeight;
|
||||||
public float elementScale;
|
public float elementScale;
|
||||||
|
public long elapsed;
|
||||||
|
|
||||||
public ElementContext(int mouseX, int mouseY, Element e) {
|
public ElementContext(long elapsed, int mouseX, int mouseY, Element e) {
|
||||||
this.mouseX = mouseX - e.x;
|
this.mouseX = mouseX - e.x;
|
||||||
this.mouseY = mouseY - e.y;
|
this.mouseY = mouseY - e.y;
|
||||||
this.elementWidth = e.width;
|
this.elementWidth = e.width;
|
||||||
this.elementHeight = e.height;
|
this.elementHeight = e.height;
|
||||||
this.elementScale = e.scale;
|
this.elementScale = e.scale;
|
||||||
|
this.elapsed = elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHovering() {
|
public boolean isHovering() {
|
||||||
|
|||||||
@@ -9,11 +9,17 @@ import java.util.function.Function;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
import org.saturnclient.ui2.resources.SvgTexture;
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
||||||
|
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.font.TextRenderer;
|
import net.minecraft.client.font.TextRenderer;
|
||||||
import net.minecraft.client.font.TextRenderer.TextLayerType;
|
import net.minecraft.client.font.TextRenderer.TextLayerType;
|
||||||
|
import net.minecraft.client.gui.DrawContext;
|
||||||
import net.minecraft.client.gui.ScreenRect;
|
import net.minecraft.client.gui.ScreenRect;
|
||||||
import net.minecraft.client.render.DiffuseLighting;
|
import net.minecraft.client.render.DiffuseLighting;
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
import net.minecraft.client.render.OverlayTexture;
|
||||||
@@ -42,6 +48,10 @@ public class RenderScope {
|
|||||||
private int opacity = 255 << 24;
|
private int opacity = 255 << 24;
|
||||||
private final ItemRenderState itemRenderState;
|
private final ItemRenderState itemRenderState;
|
||||||
|
|
||||||
|
public RenderScope(DrawContext context) {
|
||||||
|
this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
|
||||||
|
}
|
||||||
|
|
||||||
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
|
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
|
||||||
this.matrices = matrices;
|
this.matrices = matrices;
|
||||||
this.vertexConsumers = vertexConsumers;
|
this.vertexConsumers = vertexConsumers;
|
||||||
@@ -65,6 +75,8 @@ public class RenderScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawRect(int x, int y, int width, int height, int color) {
|
public void drawRect(int x, int y, int width, int height, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
color = getColor(color);
|
color = getColor(color);
|
||||||
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
||||||
|
|
||||||
@@ -98,6 +110,8 @@ public class RenderScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawText(float scale, String text, int x, int y, int font, int color) {
|
public void drawText(float scale, String text, int x, int y, int font, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
if (font == 0) {
|
if (font == 0) {
|
||||||
scale *= 2;
|
scale *= 2;
|
||||||
}
|
}
|
||||||
@@ -108,7 +122,8 @@ public class RenderScope {
|
|||||||
matrices.translate(x, y + (i * Fonts.getHeight()), 0);
|
matrices.translate(x, y + (i * Fonts.getHeight()), 0);
|
||||||
matrices.scale(scale, scale, 1.0f);
|
matrices.scale(scale, scale, 1.0f);
|
||||||
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
||||||
textRenderer.draw(Fonts.setFont(line, Fonts.getFont(font)), 0, font == 0 ? 1: 7, color, false, this.matrices.peek().getPositionMatrix(),
|
textRenderer.draw(Fonts.setFont(line, Fonts.getFont(font)), 0, font == 0 ? 1 : 7, color, false,
|
||||||
|
this.matrices.peek().getPositionMatrix(),
|
||||||
this.vertexConsumers, TextLayerType.NORMAL, 0, 15728880);
|
this.vertexConsumers, TextLayerType.NORMAL, 0, 15728880);
|
||||||
matrices.pop();
|
matrices.pop();
|
||||||
i++;
|
i++;
|
||||||
@@ -159,6 +174,8 @@ public class RenderScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawRoundedRectangle(int x, int y, int width, int height, int radius, int color) {
|
public void drawRoundedRectangle(int x, int y, int width, int height, int radius, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
radius = Math.min(radius, Math.min(width, height));
|
radius = Math.min(radius, Math.min(width, height));
|
||||||
int cornerWidth = width / 2;
|
int cornerWidth = width / 2;
|
||||||
int cornerHeight = height / 2;
|
int cornerHeight = height / 2;
|
||||||
@@ -185,25 +202,38 @@ public class RenderScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getScaledWindowHeight() {
|
public int getScaledWindowHeight() {
|
||||||
return SaturnClient.client.getWindow().getScaledHeight();
|
return SaturnClient.client.getWindow().getScaledHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int color) {
|
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int color) {
|
||||||
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
|
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height) {
|
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height) {
|
||||||
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
|
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWith, int regionHeight, int textureWidth, int textureHeight) {
|
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWith,
|
||||||
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, regionHeight, textureWidth, textureHeight, -1);
|
int regionHeight, int textureWidth, int textureHeight) {
|
||||||
|
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, regionHeight, textureWidth, textureHeight, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWidth, int regionHeight, int textureWidth, int textureHeight, int color) {
|
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWidth,
|
||||||
this.drawTexturedQuad(sprite, x, x + width, y, y + height, (u + 0.0F) / (float)textureWidth, (u + (float)regionWidth) / (float)textureWidth, (v + 0.0F) / (float)textureHeight, (v + (float)regionHeight) / (float)textureHeight, color);
|
int regionHeight, int textureWidth, int textureHeight, int color) {
|
||||||
|
this.drawTexturedQuad(sprite, x, x + width, y, y + height, (u + 0.0F) / (float) textureWidth,
|
||||||
|
(u + (float) regionWidth) / (float) textureWidth, (v + 0.0F) / (float) textureHeight,
|
||||||
|
(v + (float) regionHeight) / (float) textureHeight, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1, float v2, int color) {
|
private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1,
|
||||||
|
float v2, int color) {
|
||||||
|
|
||||||
|
if (sprite.toString().endsWith(".svg")) {
|
||||||
|
sprite = SvgTexture.getSvg(MinecraftClient.getInstance(), sprite, (x2 - x1) * 2, (y2 - y1) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
x1 *= 4;
|
x1 *= 4;
|
||||||
x2 *= 4;
|
x2 *= 4;
|
||||||
y1 *= 4;
|
y1 *= 4;
|
||||||
@@ -213,18 +243,19 @@ public class RenderScope {
|
|||||||
matrices.push();
|
matrices.push();
|
||||||
matrices.scale(0.25f, 0.25f, 1.0f);
|
matrices.scale(0.25f, 0.25f, 1.0f);
|
||||||
RenderSystem.setShaderTexture(0, sprite);
|
RenderSystem.setShaderTexture(0, sprite);
|
||||||
RenderLayer renderLayer = (RenderLayer)RenderLayer.getGuiTextured(sprite);
|
RenderLayer renderLayer = (RenderLayer) RenderLayer.getGuiTextured(sprite);
|
||||||
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
||||||
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(renderLayer);
|
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(renderLayer);
|
||||||
vertexConsumer.vertex(matrix4f, (float)x1, (float)y1, 0.0F).texture(u1, v1).color(color);
|
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, 0.0F).texture(u1, v1).color(color);
|
||||||
vertexConsumer.vertex(matrix4f, (float)x1, (float)y2, 0.0F).texture(u1, v2).color(color);
|
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, 0.0F).texture(u1, v2).color(color);
|
||||||
vertexConsumer.vertex(matrix4f, (float)x2, (float)y2, 0.0F).texture(u2, v2).color(color);
|
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, 0.0F).texture(u2, v2).color(color);
|
||||||
vertexConsumer.vertex(matrix4f, (float)x2, (float)y1, 0.0F).texture(u2, v1).color(color);
|
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, 0.0F).texture(u2, v1).color(color);
|
||||||
matrices.pop();
|
matrices.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableScissor(int x1, int y1, int x2, int y2) {
|
public void enableScissor(int x1, int y1, int x2, int y2) {
|
||||||
ScreenRect screenRect = (new ScreenRect(x1, y1, x2 - x1, y2 - y1)).transform(this.matrices.peek().getPositionMatrix());
|
ScreenRect screenRect = (new ScreenRect(x1, y1, x2 - x1, y2 - y1))
|
||||||
|
.transform(this.matrices.peek().getPositionMatrix());
|
||||||
this.setScissor(this.scissorStack.push(screenRect));
|
this.setScissor(this.scissorStack.push(screenRect));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,16 +270,16 @@ public class RenderScope {
|
|||||||
private void setScissor(@Nullable ScreenRect rect) {
|
private void setScissor(@Nullable ScreenRect rect) {
|
||||||
this.draw();
|
this.draw();
|
||||||
if (rect != null) {
|
if (rect != null) {
|
||||||
Window window = SaturnClient.client.getWindow();
|
Window window = SaturnClient.client.getWindow();
|
||||||
int i = window.getFramebufferHeight();
|
int i = window.getFramebufferHeight();
|
||||||
double d = window.getScaleFactor();
|
double d = window.getScaleFactor();
|
||||||
double e = (double)rect.getLeft() * d;
|
double e = (double) rect.getLeft() * d;
|
||||||
double f = (double)i - (double)rect.getBottom() * d;
|
double f = (double) i - (double) rect.getBottom() * d;
|
||||||
double g = (double)rect.width() * d;
|
double g = (double) rect.width() * d;
|
||||||
double h = (double)rect.height() * d;
|
double h = (double) rect.height() * d;
|
||||||
RenderSystem.enableScissor((int)e, (int)f, Math.max(0, (int)g), Math.max(0, (int)h));
|
RenderSystem.enableScissor((int) e, (int) f, Math.max(0, (int) g), Math.max(0, (int) h));
|
||||||
} else {
|
} else {
|
||||||
RenderSystem.disableScissor();
|
RenderSystem.disableScissor();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -260,7 +291,7 @@ public class RenderScope {
|
|||||||
public void draw(Consumer<VertexConsumerProvider> drawer) {
|
public void draw(Consumer<VertexConsumerProvider> drawer) {
|
||||||
drawer.accept(this.vertexConsumers);
|
drawer.accept(this.vertexConsumers);
|
||||||
this.vertexConsumers.draw();
|
this.vertexConsumers.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ScissorStack {
|
static class ScissorStack {
|
||||||
private final Deque<ScreenRect> stack = new ArrayDeque<>();
|
private final Deque<ScreenRect> stack = new ArrayDeque<>();
|
||||||
@@ -268,7 +299,8 @@ public class RenderScope {
|
|||||||
public ScreenRect push(ScreenRect p_281812_) {
|
public ScreenRect push(ScreenRect p_281812_) {
|
||||||
ScreenRect screenrectangle = this.stack.peekLast();
|
ScreenRect screenrectangle = this.stack.peekLast();
|
||||||
if (screenrectangle != null) {
|
if (screenrectangle != null) {
|
||||||
ScreenRect screenrectangle1 = Objects.requireNonNullElse(p_281812_.intersection(screenrectangle), ScreenRect.empty());
|
ScreenRect screenrectangle1 = Objects.requireNonNullElse(p_281812_.intersection(screenrectangle),
|
||||||
|
ScreenRect.empty());
|
||||||
this.stack.addLast(screenrectangle1);
|
this.stack.addLast(screenrectangle1);
|
||||||
return screenrectangle1;
|
return screenrectangle1;
|
||||||
} else {
|
} else {
|
||||||
@@ -309,47 +341,51 @@ public class RenderScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawItemWithoutEntity(ItemStack stack, int x, int y, int seed) {
|
public void drawItemWithoutEntity(ItemStack stack, int x, int y, int seed) {
|
||||||
this.drawItem((LivingEntity)null, SaturnClient.client.world, stack, x, y, seed);
|
this.drawItem((LivingEntity) null, SaturnClient.client.world, stack, x, y, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawItem(LivingEntity entity, ItemStack stack, int x, int y, int seed) {
|
public void drawItem(LivingEntity entity, ItemStack stack, int x, int y, int seed) {
|
||||||
this.drawItem(entity, entity.getWorld(), stack, x, y, seed);
|
this.drawItem(entity, entity.getWorld(), stack, x, y, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y, int seed) {
|
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y,
|
||||||
|
int seed) {
|
||||||
this.drawItem(entity, world, stack, x, y, seed, 0);
|
this.drawItem(entity, world, stack, x, y, seed, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y, int seed, int z) {
|
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y, int seed,
|
||||||
|
int z) {
|
||||||
if (!stack.isEmpty()) {
|
if (!stack.isEmpty()) {
|
||||||
SaturnClient.client.getItemModelManager().update(this.itemRenderState, stack, ModelTransformationMode.GUI, false, world, entity, seed);
|
SaturnClient.client.getItemModelManager().update(this.itemRenderState, stack, ModelTransformationMode.GUI,
|
||||||
|
false, world, entity, seed);
|
||||||
this.matrices.push();
|
this.matrices.push();
|
||||||
this.matrices.translate((float)(x + 8), (float)(y + 8), (float)(150 + (this.itemRenderState.hasDepth() ? z : 0)));
|
this.matrices.translate((float) (x + 8), (float) (y + 8),
|
||||||
|
(float) (150 + (this.itemRenderState.hasDepth() ? z : 0)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.matrices.scale(16.0F, -16.0F, 16.0F);
|
this.matrices.scale(16.0F, -16.0F, 16.0F);
|
||||||
boolean bl = !this.itemRenderState.isSideLit();
|
boolean bl = !this.itemRenderState.isSideLit();
|
||||||
if (bl) {
|
if (bl) {
|
||||||
this.draw();
|
this.draw();
|
||||||
DiffuseLighting.disableGuiDepthLighting();
|
DiffuseLighting.disableGuiDepthLighting();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.itemRenderState.render(this.matrices, this.vertexConsumers, 15728880, OverlayTexture.DEFAULT_UV);
|
this.itemRenderState.render(this.matrices, this.vertexConsumers, 15728880, OverlayTexture.DEFAULT_UV);
|
||||||
this.draw();
|
this.draw();
|
||||||
if (bl) {
|
if (bl) {
|
||||||
DiffuseLighting.enableGuiDepthLighting();
|
DiffuseLighting.enableGuiDepthLighting();
|
||||||
}
|
}
|
||||||
} catch (Throwable var11) {
|
} catch (Throwable var11) {
|
||||||
CrashReport crashReport = CrashReport.create(var11, "Rendering item");
|
CrashReport crashReport = CrashReport.create(var11, "Rendering item");
|
||||||
CrashReportSection crashReportSection = crashReport.addElement("Item being rendered");
|
CrashReportSection crashReportSection = crashReport.addElement("Item being rendered");
|
||||||
crashReportSection.add("Item Type", () -> {
|
crashReportSection.add("Item Type", () -> {
|
||||||
return String.valueOf(stack.getItem());
|
return String.valueOf(stack.getItem());
|
||||||
});
|
});
|
||||||
crashReportSection.add("Item Components", () -> {
|
crashReportSection.add("Item Components", () -> {
|
||||||
return String.valueOf(stack.getComponents());
|
return String.valueOf(stack.getComponents());
|
||||||
});
|
});
|
||||||
crashReportSection.add("Item Foil", () -> {
|
crashReportSection.add("Item Foil", () -> {
|
||||||
return String.valueOf(stack.hasGlint());
|
return String.valueOf(stack.hasGlint());
|
||||||
});
|
});
|
||||||
throw new CrashException(crashReport);
|
throw new CrashException(crashReport);
|
||||||
}
|
}
|
||||||
@@ -363,8 +399,52 @@ public class RenderScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawSpriteStretched(Sprite sprite, int x, int y, int width, int height, int color) {
|
public void drawSpriteStretched(Sprite sprite, int x, int y, int width, int height, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
if (width != 0 && height != 0) {
|
if (width != 0 && height != 0) {
|
||||||
this.drawTexturedQuad(sprite.getAtlasId(), x, x + width, y, y + height, sprite.getMinU(), sprite.getMaxU(), sprite.getMinV(), sprite.getMaxV(), color);
|
this.drawTexturedQuad(sprite.getAtlasId(), x, x + width, y, y + height, sprite.getMinU(), sprite.getMaxU(),
|
||||||
|
sprite.getMinV(), sprite.getMaxV(), color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fill(int x1, int y1, int x2, int y2, int color) {
|
||||||
|
this.fill(x1, y1, x2, y2, 0, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(int x1, int y1, int x2, int y2, int z, int color) {
|
||||||
|
this.fill(RenderLayer.getGui(), x1, y1, x2, y2, z, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int color) {
|
||||||
|
this.fill(layer, x1, y1, x2, y2, 0, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int z, int color) {
|
||||||
|
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
||||||
|
int i;
|
||||||
|
if (x1 < x2) {
|
||||||
|
i = x1;
|
||||||
|
x1 = x2;
|
||||||
|
x2 = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y1 < y2) {
|
||||||
|
i = y1;
|
||||||
|
y1 = y2;
|
||||||
|
y2 = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(layer);
|
||||||
|
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, (float) z).color(color);
|
||||||
|
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, (float) z).color(color);
|
||||||
|
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, (float) z).color(color);
|
||||||
|
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, (float) z).color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawBorder(int x, int y, int width, int height, int color) {
|
||||||
|
this.fill(x, y, x + width, y + 1, color);
|
||||||
|
this.fill(x, y + height - 1, x + width, y + height, color);
|
||||||
|
this.fill(x, y + 1, x + 1, y + height - 1, color);
|
||||||
|
this.fill(x + width - 1, y + 1, x + width, y + height - 1, color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package org.saturnclient.ui2;
|
package org.saturnclient.ui2;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
||||||
import org.saturnclient.ui2.anim.Animation;
|
|
||||||
import org.saturnclient.ui2.anim.SlideXAbsolute;
|
|
||||||
import org.saturnclient.ui2.components.ElementRenderer;
|
import org.saturnclient.ui2.components.ElementRenderer;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
@@ -15,6 +15,8 @@ import net.minecraft.client.gui.DrawContext;
|
|||||||
import net.minecraft.client.gui.RotatingCubeMapRenderer;
|
import net.minecraft.client.gui.RotatingCubeMapRenderer;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import net.minecraft.client.render.DefaultFramebufferSet;
|
import net.minecraft.client.render.DefaultFramebufferSet;
|
||||||
|
import net.minecraft.client.texture.ResourceTexture;
|
||||||
|
import net.minecraft.client.texture.TextureManager;
|
||||||
import net.minecraft.client.util.Pool;
|
import net.minecraft.client.util.Pool;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
@@ -22,12 +24,13 @@ import net.minecraft.util.Identifier;
|
|||||||
public abstract class SaturnScreen extends Screen {
|
public abstract class SaturnScreen extends Screen {
|
||||||
private final Pool pool = new Pool(3);
|
private final Pool pool = new Pool(3);
|
||||||
protected List<Element> elements = new ArrayList<>();
|
protected List<Element> elements = new ArrayList<>();
|
||||||
public int blurDuration = 700;
|
protected float backgroundOpacity = 1.0f;
|
||||||
public float blurProgress = 0.0f;
|
protected Instant start = null;
|
||||||
public int backgroundBlur = 10;
|
public int backgroundBlur = 10;
|
||||||
|
|
||||||
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(
|
protected static final Identifier PANORAMA = Identifier.of("saturnclient",
|
||||||
Identifier.of("saturnclient", "textures/gui/title/background/panorama"));
|
"textures/gui/title/background/panorama");
|
||||||
|
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(PANORAMA);
|
||||||
|
|
||||||
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
|
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
|
||||||
|
|
||||||
@@ -35,41 +38,24 @@ public abstract class SaturnScreen extends Screen {
|
|||||||
ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER);
|
ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void preload(MinecraftClient client) {
|
||||||
|
TextureManager textureManager = client.getTextureManager();
|
||||||
|
|
||||||
|
// Force upload each texture to GPU
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
String var10001 = PANORAMA.getPath();
|
||||||
|
Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png");
|
||||||
|
ResourceTexture resourceTexture = new ResourceTexture(tex);
|
||||||
|
textureManager.registerTexture(tex, resourceTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SaturnScreen(String title) {
|
public SaturnScreen(String title) {
|
||||||
super(Text.literal(title));
|
super(Text.literal(title));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(Element element) {
|
public void draw(Element element) {
|
||||||
synchronized (elements) {
|
ElementRenderer.draw(elements, element);
|
||||||
elements.add(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.animation != null) {
|
|
||||||
element.animation.init(element);
|
|
||||||
|
|
||||||
Animation.execute((Float progress) -> {
|
|
||||||
element.animation.tick(progress, element);
|
|
||||||
}, element.animation.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.duration != null) {
|
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
|
||||||
Animation fadeOut = new SlideXAbsolute(700, -(width - element.x));
|
|
||||||
Thread.sleep(element.duration);
|
|
||||||
fadeOut.init(element);
|
|
||||||
Animation.executeSync((Float progress) -> {
|
|
||||||
fadeOut.tick(progress, element);
|
|
||||||
}, fadeOut.duration);
|
|
||||||
synchronized (elements) {
|
|
||||||
elements.remove(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,10 +64,6 @@ public abstract class SaturnScreen extends Screen {
|
|||||||
width *= 2;
|
width *= 2;
|
||||||
height *= 2;
|
height *= 2;
|
||||||
ui(); // abstraction to render the saturn ui and also render extra stuff here
|
ui(); // abstraction to render the saturn ui and also render extra stuff here
|
||||||
|
|
||||||
Animation.execute((Float progress) -> {
|
|
||||||
blurProgress = progress;
|
|
||||||
}, blurDuration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void ui();
|
public abstract void ui();
|
||||||
@@ -89,20 +71,26 @@ public abstract class SaturnScreen extends Screen {
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||||
|
if (start == null) {
|
||||||
|
start = Instant.now();
|
||||||
|
}
|
||||||
|
long elapsed = Duration.between(start, Instant.now()).toMillis();
|
||||||
|
|
||||||
mouseX *= 2;
|
mouseX *= 2;
|
||||||
mouseY *= 2;
|
mouseY *= 2;
|
||||||
|
|
||||||
if (client.world == null && client.getCurrentServerEntry() == null) {
|
if (client.world == null && client.getCurrentServerEntry() == null) {
|
||||||
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta);
|
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, backgroundOpacity, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
|
PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
|
||||||
Identifier.ofVanilla("blur"),
|
Identifier.ofVanilla("blur"),
|
||||||
DefaultFramebufferSet.MAIN_ONLY);
|
DefaultFramebufferSet.MAIN_ONLY);
|
||||||
if (postEffectProcessor != null) {
|
|
||||||
postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress);
|
if (postEffectProcessor != null) {
|
||||||
postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
|
postEffectProcessor.setUniforms("Radius", backgroundBlur * Math.min((float) elapsed / 700, 1.0f));
|
||||||
}
|
postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
|
||||||
|
}
|
||||||
|
|
||||||
// We are using a Abstracted RenderScope because older minecraft versions don't
|
// We are using a Abstracted RenderScope because older minecraft versions don't
|
||||||
// use DrawContext
|
// use DrawContext
|
||||||
@@ -113,7 +101,7 @@ public abstract class SaturnScreen extends Screen {
|
|||||||
|
|
||||||
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
|
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
|
||||||
|
|
||||||
ElementRenderer.render(new ArrayList<>(elements), renderScope, mouseX, mouseY);
|
ElementRenderer.render(new ArrayList<>(elements), elapsed, renderScope, mouseX, mouseY);
|
||||||
|
|
||||||
renderScope.matrices.pop();
|
renderScope.matrices.pop();
|
||||||
}
|
}
|
||||||
@@ -128,6 +116,26 @@ public abstract class SaturnScreen extends Screen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||||
|
mouseX *= 2;
|
||||||
|
mouseY *= 2;
|
||||||
|
|
||||||
|
ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY);
|
||||||
|
|
||||||
|
return super.mouseReleased(mouseX, mouseY, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||||
|
mouseX *= 2;
|
||||||
|
mouseY *= 2;
|
||||||
|
|
||||||
|
ElementRenderer.mouseReleased(elements, mouseX, mouseY, button);
|
||||||
|
|
||||||
|
return super.mouseReleased(mouseX, mouseY, button);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean mouseScrolled(
|
public boolean mouseScrolled(
|
||||||
double mouseX,
|
double mouseX,
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.ui2.elements.Notification;
|
||||||
|
import org.saturnclient.ui2.elements.Notification.NotificationKind;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
public static boolean isHovering(int mouseX, int mouseY, int elementWidth, int elementHeight, float elementScale) {
|
public static boolean isHovering(int mouseX, int mouseY, int elementWidth, int elementHeight, float elementScale) {
|
||||||
return mouseX >= 0 &&
|
return mouseX >= 0 &&
|
||||||
mouseX <= (elementWidth * elementScale) &&
|
mouseX <= (elementWidth * elementScale) &&
|
||||||
mouseY >= 0 &&
|
mouseY >= 0 &&
|
||||||
mouseY <= (elementHeight * elementScale);
|
mouseY <= (elementHeight * elementScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static char getCharFromKey(int keyCode, int modifiers) {
|
public static char getCharFromKey(int keyCode, int modifiers) {
|
||||||
@@ -49,7 +51,15 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T getOrNull(List<T> list, int index) {
|
public static <T> T getOrNull(List<T> list, int index) {
|
||||||
SaturnClient.LOGGER.info("Accessing index {} in list of size {}", index, list.size());
|
|
||||||
return (index >= 0 && index < list.size()) ? list.get(index) : null;
|
return (index >= 0 && index < list.size()) ? list.get(index) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void notify(NotificationKind kind, String title, String toast) {
|
||||||
|
if (SaturnClient.client.currentScreen instanceof SaturnScreen) {
|
||||||
|
((SaturnScreen) SaturnClient.client.currentScreen)
|
||||||
|
.draw(new Notification(SaturnClient.client.currentScreen.width,
|
||||||
|
SaturnClient.client.currentScreen.height, kind,
|
||||||
|
title, toast));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +1,37 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
package org.saturnclient.ui2.anim;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
|
|
||||||
public abstract class Animation {
|
public abstract class Animation {
|
||||||
public int duration;
|
public int duration;
|
||||||
|
public int delay = 0;
|
||||||
|
public Function<Double, Double> curve = Curve::easeInOutCubic;
|
||||||
|
|
||||||
|
Animation(AnimationConfig config) {
|
||||||
|
this.duration = config.duration.value;
|
||||||
|
switch (config.curve.value) {
|
||||||
|
case 0:
|
||||||
|
this.curve = Curve::easeInOutCubic;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this.curve = Curve::easeOutCubic;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.curve = Curve::easeInOutBack;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Animation(int duration) {
|
Animation(int duration) {
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void tick(float progress, Element element);
|
public abstract void tick(double progress, Element element);
|
||||||
|
|
||||||
public abstract void init(Element element);
|
public abstract void init(Element element);
|
||||||
|
|
||||||
public static void execute(Consumer<Float> tick, int duration) {
|
|
||||||
new Thread(() -> {
|
|
||||||
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
|
|
||||||
float progress = 0.0f;
|
|
||||||
double incremental = (1000 - duration) / 10000.0;
|
|
||||||
|
|
||||||
for (int i = 0; i <= 100; i++) {
|
|
||||||
float newProgress = (float) (double) curveFunction.apply(i / 100.0);
|
|
||||||
|
|
||||||
while (Math.abs(newProgress - progress) > 1e-6) {
|
|
||||||
if (progress < newProgress) {
|
|
||||||
progress += incremental;
|
|
||||||
if (progress > newProgress) progress = newProgress;
|
|
||||||
} else {
|
|
||||||
progress -= incremental;
|
|
||||||
if (progress < newProgress) progress = newProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
tick.accept(newProgress);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(duration / 100);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void executeSync(Consumer<Float> tick, int duration) {
|
|
||||||
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
|
|
||||||
float progress = 0.0f;
|
|
||||||
double incremental = (1000 - duration) / 10000.0;
|
|
||||||
|
|
||||||
for (int i = 0; i <= 100; i++) {
|
|
||||||
float newProgress = (float) (double) curveFunction.apply(i / 100.0);
|
|
||||||
|
|
||||||
while (Math.abs(newProgress - progress) > 1e-6) {
|
|
||||||
if (progress < newProgress) {
|
|
||||||
progress += incremental;
|
|
||||||
if (progress > newProgress) progress = newProgress;
|
|
||||||
} else {
|
|
||||||
progress -= incremental;
|
|
||||||
if (progress < newProgress) progress = newProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
tick.accept(newProgress);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(duration / 100);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,8 @@ public class Curve {
|
|||||||
return (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
|
return (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double easeInOutCubicReverse(double x) {
|
|
||||||
return -(x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double easeInOutBack(double x) {
|
public static double easeInOutBack(double x) {
|
||||||
double c1 = 1.70158;
|
double c1 = 2.5;
|
||||||
double c2 = c1 * 1.525;
|
double c2 = c1 * 1.525;
|
||||||
|
|
||||||
if (x < 0.5) {
|
if (x < 0.5) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
package org.saturnclient.ui2.anim;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
|
|
||||||
public class Fade extends Animation {
|
public class Fade extends Animation {
|
||||||
@@ -9,14 +10,18 @@ public class Fade extends Animation {
|
|||||||
super(duration);
|
super(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Fade(AnimationConfig config) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(float progress, Element element) {
|
public void tick(double progress, Element element) {
|
||||||
element.opacity = targetOpacity * progress;
|
element.opacity = Math.max(0.1f, targetOpacity * (float) progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Element element) {
|
public void init(Element element) {
|
||||||
targetOpacity = element.opacity;
|
targetOpacity = element.opacity;
|
||||||
element.opacity = 0;
|
element.opacity = 0.1f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
package org.saturnclient.ui2.anim;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
|
|
||||||
public class FadeOut extends Animation {
|
public class FadeOut extends Animation {
|
||||||
@@ -9,9 +10,13 @@ public class FadeOut extends Animation {
|
|||||||
super(duration);
|
super(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FadeOut(AnimationConfig config) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(float progress, Element element) {
|
public void tick(double progress, Element element) {
|
||||||
element.opacity = targetOpacity - progress;
|
element.opacity = targetOpacity - (float) progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.saturnclient.ui2.anim;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||||
|
import org.saturnclient.ui2.Element;
|
||||||
|
|
||||||
|
public class SlideFade extends Animation {
|
||||||
|
public Fade fade;
|
||||||
|
public SlideY slideY;
|
||||||
|
|
||||||
|
public SlideFade(int duration, int offset) {
|
||||||
|
super(duration);
|
||||||
|
this.fade = new Fade(duration);
|
||||||
|
this.slideY = new SlideY(duration, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlideFade(AnimationConfig config, int offset) {
|
||||||
|
super(config);
|
||||||
|
this.fade = new Fade(duration);
|
||||||
|
this.slideY = new SlideY(duration, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(double progress, Element element) {
|
||||||
|
fade.delay = this.delay;
|
||||||
|
fade.duration = this.duration;
|
||||||
|
|
||||||
|
slideY.delay = this.delay;
|
||||||
|
slideY.duration = this.duration;
|
||||||
|
|
||||||
|
fade.tick(progress, element);
|
||||||
|
slideY.tick(progress, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Element element) {
|
||||||
|
fade.init(element);
|
||||||
|
slideY.init(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
package org.saturnclient.ui2.anim;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
|
|
||||||
public class SlideX extends Animation {
|
public class SlideX extends Animation {
|
||||||
int offset = 20;
|
int offset = 20;
|
||||||
int maxOffset = 20;
|
double startX;
|
||||||
int elementX;
|
|
||||||
|
public SlideX(AnimationConfig config) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlideX(AnimationConfig config, int offset) {
|
||||||
|
super(config);
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
public SlideX(int duration) {
|
public SlideX(int duration) {
|
||||||
super(duration);
|
super(duration);
|
||||||
@@ -14,23 +23,17 @@ public class SlideX extends Animation {
|
|||||||
public SlideX(int duration, int offset) {
|
public SlideX(int duration, int offset) {
|
||||||
super(duration);
|
super(duration);
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.maxOffset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideX(int duration, int offset, int maxOffset) {
|
|
||||||
super(duration);
|
|
||||||
this.offset = offset;
|
|
||||||
this.maxOffset = maxOffset; // ✅ fixed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(float progress, Element element) {
|
|
||||||
element.x = elementX + (int) (offset * progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Element element) {
|
public void init(Element element) {
|
||||||
element.x -= offset;
|
startX = element.x - offset;
|
||||||
elementX = element.x;
|
element.x = (int) startX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(double progress, Element element) {
|
||||||
|
double currentX = startX + offset * progress;
|
||||||
|
element.x = (int) currentX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
|
||||||
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
|
|
||||||
public class SlideXAbsolute extends Animation {
|
|
||||||
int offset = 20;
|
|
||||||
int maxOffset = 20;
|
|
||||||
int elementX;
|
|
||||||
|
|
||||||
public SlideXAbsolute(int duration) {
|
|
||||||
super(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideXAbsolute(int duration, int offset) {
|
|
||||||
super(duration);
|
|
||||||
this.offset = offset;
|
|
||||||
this.maxOffset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideXAbsolute(int duration, int offset, int maxOffset) {
|
|
||||||
super(duration);
|
|
||||||
this.offset = offset;
|
|
||||||
this.maxOffset = maxOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(float progress, Element element) {
|
|
||||||
element.x = elementX + (int) (offset * progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Element element) {
|
|
||||||
elementX = element.x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
package org.saturnclient.ui2.anim;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
|
|
||||||
public class SlideY extends Animation {
|
public class SlideY extends Animation {
|
||||||
int offset = 20;
|
int offset = 20;
|
||||||
int maxOffset = 20;
|
double startY;
|
||||||
int elementY;
|
|
||||||
|
public SlideY(AnimationConfig config) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlideY(AnimationConfig config, int offset) {
|
||||||
|
super(config);
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
public SlideY(int duration) {
|
public SlideY(int duration) {
|
||||||
super(duration);
|
super(duration);
|
||||||
@@ -13,26 +22,18 @@ public class SlideY extends Animation {
|
|||||||
|
|
||||||
public SlideY(int duration, int offset) {
|
public SlideY(int duration, int offset) {
|
||||||
super(duration);
|
super(duration);
|
||||||
|
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.maxOffset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideY(int duration, int offset, int maxOffset) {
|
|
||||||
super(duration);
|
|
||||||
|
|
||||||
this.offset = offset;
|
|
||||||
this.maxOffset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(float progress, Element element) {
|
|
||||||
element.y = elementY + (int) (offset * progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Element element) {
|
public void init(Element element) {
|
||||||
element.y -= offset;
|
startY = element.y - offset;
|
||||||
elementY = element.y;
|
element.y = (int) startY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(double progress, Element element) {
|
||||||
|
double currentY = startY + offset * progress;
|
||||||
|
element.y = (int) currentY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.saturnclient.ui2.components;
|
package org.saturnclient.ui2.components;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
@@ -10,13 +9,7 @@ import net.minecraft.client.render.RenderLayer;
|
|||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
public class CosmeticPreview extends Element {
|
public class CosmeticPreview extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("CosmeticPreview", "selected", "hovering");
|
static int padding = 14;
|
||||||
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x89000000));
|
|
||||||
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
|
|
||||||
private static Property<Integer> padding = theme.property("padding", Property.integer(14));
|
|
||||||
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
|
|
||||||
|
|
||||||
static {}
|
|
||||||
|
|
||||||
Identifier sprite;
|
Identifier sprite;
|
||||||
private Runnable onClick;
|
private Runnable onClick;
|
||||||
@@ -33,22 +26,16 @@ public class CosmeticPreview extends Element {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
if (ctx.isHovering()) {
|
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||||
theme.setState("hovering");
|
isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||||
} else if (isSelected) {
|
|
||||||
theme.setState("selected");
|
|
||||||
}
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
|
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||||
|
|
||||||
int texWidth = width - padding.value;
|
int texWidth = width - padding;
|
||||||
int texHeight = height - padding.value;
|
int texHeight = height - padding;
|
||||||
|
|
||||||
renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value);
|
renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
|
||||||
|
Theme.FOREGROUND.value);
|
||||||
theme.setState(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.saturnclient.ui2.Element;
|
|||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.Utils;
|
import org.saturnclient.ui2.Utils;
|
||||||
import org.saturnclient.ui2.anim.Animation;
|
|
||||||
|
|
||||||
public class ElementRenderer {
|
public class ElementRenderer {
|
||||||
public static void draw(List<Element> elements, Element element) {
|
public static void draw(List<Element> elements, Element element) {
|
||||||
@@ -17,20 +16,18 @@ public class ElementRenderer {
|
|||||||
|
|
||||||
if (element.animation != null) {
|
if (element.animation != null) {
|
||||||
element.animation.init(element);
|
element.animation.init(element);
|
||||||
|
|
||||||
Animation.execute((Float progress) -> {
|
|
||||||
element.animation.tick(progress, element);
|
|
||||||
}, element.animation.duration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void render(List<Element> elements, RenderScope renderScope, int mouseX, int mouseY) {
|
public static void render(List<Element> elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) {
|
||||||
for (Element element : elements) {
|
for (Element element : elements) {
|
||||||
|
element.playAnimationFrame(elapsed);
|
||||||
|
|
||||||
renderScope.matrices.push();
|
renderScope.matrices.push();
|
||||||
renderScope.setOpacity(element.opacity);
|
renderScope.setOpacity(element.opacity);
|
||||||
renderScope.matrices.translate(element.x, element.y, 0);
|
renderScope.matrices.translate(element.x, element.y, 0);
|
||||||
renderScope.matrices.scale(element.scale, element.scale, 1.0f);
|
renderScope.matrices.scale(element.scale, element.scale, 1.0f);
|
||||||
element.render(renderScope, new ElementContext(mouseX, mouseY, element));
|
element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, element));
|
||||||
renderScope.matrices.pop();
|
renderScope.matrices.pop();
|
||||||
renderScope.setRenderLayer(null);
|
renderScope.setRenderLayer(null);
|
||||||
}
|
}
|
||||||
@@ -44,7 +41,10 @@ public class ElementRenderer {
|
|||||||
int adjustedMouseX = (int) mouseX - element.x;
|
int adjustedMouseX = (int) mouseX - element.x;
|
||||||
int adjustedMouseY = (int) mouseY - element.y;
|
int adjustedMouseY = (int) mouseY - element.y;
|
||||||
|
|
||||||
if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, element.height, element.scale)) {
|
element.mouseClicked(adjustedMouseX * element.scale, adjustedMouseY * element.scale, button);
|
||||||
|
|
||||||
|
if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width,
|
||||||
|
element.height, element.scale)) {
|
||||||
element.focused = true;
|
element.focused = true;
|
||||||
element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale));
|
element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale));
|
||||||
}
|
}
|
||||||
@@ -58,16 +58,17 @@ public class ElementRenderer {
|
|||||||
double mouseY,
|
double mouseY,
|
||||||
double horizontalAmount,
|
double horizontalAmount,
|
||||||
double verticalAmount) {
|
double verticalAmount) {
|
||||||
for (Element element : new ArrayList<>(elements)) {
|
for (Element element : new ArrayList<>(elements)) {
|
||||||
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, element.scale)) {
|
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height,
|
||||||
element.scroll(
|
element.scale)) {
|
||||||
(int) mouseX - element.x,
|
element.scroll(
|
||||||
(int) mouseY - element.y,
|
(int) mouseX - element.x,
|
||||||
horizontalAmount,
|
(int) mouseY - element.y,
|
||||||
verticalAmount);
|
horizontalAmount,
|
||||||
}
|
verticalAmount);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void keyPressed(List<Element> elements, int keyCode, int scanCode, int modifiers) {
|
public static void keyPressed(List<Element> elements, int keyCode, int scanCode, int modifiers) {
|
||||||
for (Element element : new ArrayList<>(elements)) {
|
for (Element element : new ArrayList<>(elements)) {
|
||||||
@@ -81,4 +82,17 @@ public class ElementRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void mouseDragged(List<Element> elements, double mouseX, double mouseY, int button, double deltaX,
|
||||||
|
double deltaY) {
|
||||||
|
for (Element element : new ArrayList<>(elements)) {
|
||||||
|
element.mouseDragged(mouseX - element.x, mouseY - element.y, button, deltaX, deltaY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mouseReleased(List<Element> elements, double mouseX, double mouseY, int button) {
|
||||||
|
for (Element element : new ArrayList<>(elements)) {
|
||||||
|
element.mouseReleased(mouseX - element.x, mouseY - element.y, button);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ package org.saturnclient.ui2.components;
|
|||||||
|
|
||||||
import org.saturnclient.modules.Module;
|
import org.saturnclient.modules.Module;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
import org.saturnclient.ui2.resources.Textures;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
@@ -15,23 +14,12 @@ import org.saturnclient.ui2.screens.ConfigEditor;
|
|||||||
import net.minecraft.client.render.RenderLayer;
|
import net.minecraft.client.render.RenderLayer;
|
||||||
|
|
||||||
public class SaturnModule extends Element {
|
public class SaturnModule extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("Module", "enabled", "hovering");
|
|
||||||
private static Property<Integer> bgColor = theme.property("bg", Property.color(0xFF1d202d));
|
|
||||||
private static Property<Integer> fgColor = theme.property("fg", Property.color(0xFFA1A2B8));
|
|
||||||
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFA1A2B8));
|
|
||||||
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0xFF2e3248));
|
|
||||||
private static Property<Integer> tagBg = theme.property("tag-bg", Property.color(0xFF2e3248));
|
|
||||||
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.color(10));
|
|
||||||
private static Property<Integer> font = theme.property("font", Property.font(1));
|
|
||||||
private static Property<Integer> radius = theme.property("radius", Property.color(10));
|
|
||||||
|
|
||||||
static {
|
private final Module mod;
|
||||||
theme.propertyStateDefault("enabled", "bg", 0xFF1d202d);
|
|
||||||
theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee);
|
|
||||||
theme.propertyStateDefault("hovering", "fg", 0xFF845eee);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Module mod;
|
static int p = 10; // padding
|
||||||
|
static int s = 14; // settings icon size
|
||||||
|
static int expand = 5; // settings icon click area
|
||||||
|
|
||||||
public SaturnModule(Module mod) {
|
public SaturnModule(Module mod) {
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
@@ -41,60 +29,137 @@ public class SaturnModule extends Element {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
int p = 10;
|
boolean hovered = ctx.isHovering();
|
||||||
int s = 14;
|
boolean enabled = mod.isEnabled();
|
||||||
boolean settingsHover = false;
|
|
||||||
|
int iconX = width - s - p;
|
||||||
|
int iconY = height - s - p;
|
||||||
|
|
||||||
|
boolean settingsHover = ctx.isHovering(
|
||||||
|
getSettingsX(),
|
||||||
|
getSettingsY(),
|
||||||
|
getSettingsHitBox(),
|
||||||
|
getSettingsHitBox());
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||||
|
|
||||||
if (mod.isEnabled()) {
|
// Resolve theme colors
|
||||||
theme.setState("enabled");
|
int bgColor = Theme.PRIMARY.value;
|
||||||
}
|
int fgColor = hovered && !settingsHover ? Theme.ACCENT.value : Theme.PRIMARY_FG.value;
|
||||||
|
|
||||||
if (ctx.isHovering(width - s - p - 5, height - s - p - 5, s + 10, s + 10)) {
|
int iconBg = enabled ? Theme.ACCENT.value : Theme.PRIMARY.value;
|
||||||
settingsHover = true;
|
int iconFg = enabled ? Theme.ACCENT_FG.value : fgColor;
|
||||||
} else if (ctx.isHovering()) {
|
|
||||||
theme.applyState("hovering");
|
|
||||||
}
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, radius.value, bgColor.value);
|
int settingsColor = settingsHover
|
||||||
|
? Theme.ACCENT.value
|
||||||
|
: Theme.withAlpha(160, fgColor);
|
||||||
|
|
||||||
int h = height-(p * 2);
|
int h = height - (p * 2);
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(p, p, h, h, iconRadius.value, iconBg.value);
|
// Main background
|
||||||
|
renderScope.drawRoundedRectangle(
|
||||||
|
0, 0,
|
||||||
|
width, height,
|
||||||
|
Theme.WIDGET_RADIUS.value,
|
||||||
|
bgColor);
|
||||||
|
|
||||||
renderScope.drawTexture(mod.getIconTexture(), p + (p / 2), p + (p / 2), 0, 0, h - p, h - p, iconFg.value);
|
// Icon background
|
||||||
|
renderScope.drawRoundedRectangle(
|
||||||
|
p, p,
|
||||||
|
h, h,
|
||||||
|
Theme.WIDGET_RADIUS.value,
|
||||||
|
iconBg);
|
||||||
|
|
||||||
renderScope.drawText(0.6f, mod.getName(), p+h+4, p + 1, font.value, fgColor.value);
|
// Icon
|
||||||
|
renderScope.drawTexture(
|
||||||
|
mod.getIconTexture(),
|
||||||
|
p + (p / 2),
|
||||||
|
p + (p / 2),
|
||||||
|
0, 0,
|
||||||
|
h - p,
|
||||||
|
h - p,
|
||||||
|
iconFg);
|
||||||
|
|
||||||
renderScope.drawText(0.35f, mod.getVersion(), width - p - (int) (Fonts.getWidth(mod.getVersion(), font.value) * 0.35f), p + 1, font.value, fgColor.value);
|
// Module name
|
||||||
|
renderScope.drawText(
|
||||||
|
0.6f,
|
||||||
|
mod.getName(),
|
||||||
|
p + h + 4,
|
||||||
|
p + 1,
|
||||||
|
Theme.FONT.value,
|
||||||
|
fgColor);
|
||||||
|
|
||||||
|
// Version (right aligned)
|
||||||
|
String version = mod.getVersion();
|
||||||
|
int versionWidth = (int) (Fonts.getWidth(version, Theme.FONT.value) * 0.35f);
|
||||||
|
|
||||||
|
renderScope.drawText(
|
||||||
|
0.35f,
|
||||||
|
version,
|
||||||
|
width - p - versionWidth,
|
||||||
|
p + 1,
|
||||||
|
Theme.FONT.value,
|
||||||
|
fgColor);
|
||||||
|
|
||||||
|
// Tags
|
||||||
int xt = p + h + 4;
|
int xt = p + h + 4;
|
||||||
for (String tag : mod.getTags()) {
|
for (String tag : mod.getTags()) {
|
||||||
int xtb = (int) (Fonts.getWidth(tag, font.value) * 0.4f);
|
int tagWidth = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f);
|
||||||
renderScope.drawRoundedRectangle(xt, p + (h/2), xtb + 6, 12, 5, tagBg.value);
|
|
||||||
renderScope.drawText(0.4f, tag, xt+2, p + (h/2) + 3, font.value, fgColor.value);
|
renderScope.drawRoundedRectangle(
|
||||||
xt += xtb + 9;
|
xt,
|
||||||
|
p + (h / 2),
|
||||||
|
tagWidth + 6,
|
||||||
|
12,
|
||||||
|
5,
|
||||||
|
Theme.ACCENT.value);
|
||||||
|
|
||||||
|
renderScope.drawText(
|
||||||
|
0.4f,
|
||||||
|
tag,
|
||||||
|
xt + 2,
|
||||||
|
p + (h / 2) + 3,
|
||||||
|
Theme.FONT.value,
|
||||||
|
Theme.ACCENT_FG.value);
|
||||||
|
|
||||||
|
xt += tagWidth + 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsHover) {
|
// Settings icon
|
||||||
theme.setState("hovering");
|
renderScope.drawTexture(
|
||||||
}
|
Textures.SETTINGS,
|
||||||
|
iconX,
|
||||||
renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, fgColor.value);
|
iconY,
|
||||||
|
0, 0,
|
||||||
theme.setState(null);
|
s, s,
|
||||||
|
settingsColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void click(int mouseX, int mouseY) {
|
public void click(int mouseX, int mouseY) {
|
||||||
int p = 10;
|
boolean settingsHover = Utils.isHovering(
|
||||||
int s = 14;
|
mouseX - getSettingsX(),
|
||||||
|
mouseY - getSettingsY(),
|
||||||
|
getSettingsHitBox(),
|
||||||
|
getSettingsHitBox(),
|
||||||
|
1.0f);
|
||||||
|
|
||||||
if (Utils.isHovering(mouseX - (width - s - p - 5), mouseY - (height - s - p - 5), s + 10, s + 10, 1.0f)) {
|
if (settingsHover) {
|
||||||
SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig()));
|
SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig()));
|
||||||
} else {
|
} else {
|
||||||
mod.setEnabled(!mod.isEnabled());
|
mod.setEnabled(!mod.isEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getSettingsX() {
|
||||||
|
return width - s - p - expand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSettingsY() {
|
||||||
|
return height - s - p - expand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSettingsHitBox() {
|
||||||
|
return s + expand * 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package org.saturnclient.ui2.components;
|
package org.saturnclient.ui2.components;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
import org.saturnclient.saturnclient.config.Config;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
import org.saturnclient.ui2.resources.Textures;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
@@ -18,19 +17,6 @@ import net.minecraft.client.render.RenderLayer;
|
|||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
public class Sidebar extends Element {
|
public class Sidebar extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("SideBar", "hovering");
|
|
||||||
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
|
|
||||||
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF));
|
|
||||||
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0x00000000));
|
|
||||||
|
|
||||||
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
|
|
||||||
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.integer(10));
|
|
||||||
private static Property<Integer> padding = theme.property("padding", Property.integer(10));
|
|
||||||
|
|
||||||
static {
|
|
||||||
theme.propertyStateDefault("hovering", "icon-bg", -7643914);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SidebarComponent {
|
public static class SidebarComponent {
|
||||||
Runnable onClick;
|
Runnable onClick;
|
||||||
boolean end;
|
boolean end;
|
||||||
@@ -45,11 +31,11 @@ public class Sidebar extends Element {
|
|||||||
|
|
||||||
Runnable onClose;
|
Runnable onClose;
|
||||||
SidebarComponent[] components = {
|
SidebarComponent[] components = {
|
||||||
new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> {
|
new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> {
|
||||||
SaturnClient.client.setScreen(new ModMenu());
|
SaturnClient.client.setScreen(new ModMenu());
|
||||||
}, false),
|
}, false),
|
||||||
new Sidebar.SidebarComponent(Textures.SETTINGS, () -> {
|
new Sidebar.SidebarComponent(Textures.SETTINGS, () -> {
|
||||||
SaturnClient.client.setScreen(new ConfigEditor(SaturnClientConfig.config));
|
SaturnClient.client.setScreen(new ConfigEditor(Config.config));
|
||||||
}, false),
|
}, false),
|
||||||
|
|
||||||
new Sidebar.SidebarComponent(Textures.SHIRT, () -> {
|
new Sidebar.SidebarComponent(Textures.SHIRT, () -> {
|
||||||
@@ -79,36 +65,29 @@ public class Sidebar extends Element {
|
|||||||
int bottomRow = 0;
|
int bottomRow = 0;
|
||||||
|
|
||||||
int gap = 5; // 5px gap between icons
|
int gap = 5; // 5px gap between icons
|
||||||
int sp = 30 - padding.value + gap;
|
int padding = 10;
|
||||||
int s = 30 - (padding.value * 2);
|
int sp = 30 - padding + gap;
|
||||||
int s2 = s + padding.value;
|
int s = 30 - (padding * 2);
|
||||||
|
int s2 = s + padding;
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
|
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
for (SidebarComponent component : components) {
|
for (SidebarComponent component : components) {
|
||||||
|
int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
|
||||||
|
boolean selected = ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i;
|
||||||
|
|
||||||
|
renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding,
|
||||||
|
s + padding, Theme.WIDGET_RADIUS.value, Theme.getBg(selected));
|
||||||
|
|
||||||
|
renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.getFg(selected));
|
||||||
|
|
||||||
if (component.end) {
|
if (component.end) {
|
||||||
int y = (height - (sp * bottomRow)) - (s * 2);
|
|
||||||
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
|
|
||||||
theme.setState("hovering");
|
|
||||||
} else {
|
|
||||||
theme.setState(null);
|
|
||||||
}
|
|
||||||
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
|
|
||||||
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
|
|
||||||
bottomRow += 1;
|
bottomRow += 1;
|
||||||
} else {
|
} else {
|
||||||
int y = padding.value + (sp * topRow);
|
|
||||||
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
|
|
||||||
theme.setState("hovering");
|
|
||||||
} else {
|
|
||||||
theme.setState(null);
|
|
||||||
}
|
|
||||||
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
|
|
||||||
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
|
|
||||||
topRow += 1;
|
topRow += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,25 +100,24 @@ public class Sidebar extends Element {
|
|||||||
int topRow = 0;
|
int topRow = 0;
|
||||||
int bottomRow = 0;
|
int bottomRow = 0;
|
||||||
|
|
||||||
int gap = 5; // same 5px gap here
|
int gap = 5;
|
||||||
int sp = 30 - padding.value + gap;
|
int padding = 10;
|
||||||
int s = 30 - (padding.value * 2);
|
int sp = 30 - padding + gap;
|
||||||
int s2 = s + padding.value;
|
int s = 30 - (padding * 2);
|
||||||
|
int s2 = s + padding;
|
||||||
|
|
||||||
for (SidebarComponent component : components) {
|
for (SidebarComponent component : components) {
|
||||||
|
int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
|
||||||
|
boolean hovering = Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale);
|
||||||
|
|
||||||
|
if (hovering) {
|
||||||
|
component.onClick.run();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (component.end) {
|
if (component.end) {
|
||||||
int y = (height - (sp * bottomRow)) - (s * 2);
|
|
||||||
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
|
|
||||||
component.onClick.run();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
bottomRow += 1;
|
bottomRow += 1;
|
||||||
} else {
|
} else {
|
||||||
int y = padding.value + (sp * topRow);
|
|
||||||
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
|
|
||||||
component.onClick.run();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
topRow += 1;
|
topRow += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,246 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components.dialog;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.SaturnScreen;
|
|
||||||
|
|
||||||
import net.minecraft.client.gui.DrawContext;
|
|
||||||
|
|
||||||
public class ColorDialog extends SaturnScreen {
|
|
||||||
private Property<Integer> prop;
|
|
||||||
private SaturnScreen parent;
|
|
||||||
private static final int PICKER_WIDTH = 200;
|
|
||||||
private static final int PICKER_HEIGHT = 200;
|
|
||||||
private static final int SLIDER_WIDTH = 200;
|
|
||||||
private static final int SLIDER_HEIGHT = 10;
|
|
||||||
private static final int HUE_SLIDER_HEIGHT = 10;
|
|
||||||
private float hue = 0.0f;
|
|
||||||
|
|
||||||
private int selectedX = 0;
|
|
||||||
private int selectedY = 0;
|
|
||||||
|
|
||||||
private float opacity = 1.0f;
|
|
||||||
private boolean dragging = false;
|
|
||||||
|
|
||||||
public ColorDialog(Property<Integer> prop , SaturnScreen parent) {
|
|
||||||
super("Color Dialog");
|
|
||||||
this.prop = prop;
|
|
||||||
this.parent = parent;
|
|
||||||
|
|
||||||
int alpha = (prop.value >> 24) & 0xFF;
|
|
||||||
if (alpha == 0) alpha = 255; // fallback if no alpha specified
|
|
||||||
this.opacity = alpha / 255.0f;
|
|
||||||
syncFromColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void ui() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
SaturnClient.client.setScreen(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
|
||||||
super.render(context, mouseX, mouseY, delta);
|
|
||||||
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
|
||||||
((DrawContextAccessor) context).getVertexConsumers());
|
|
||||||
|
|
||||||
renderScope.matrices.push();
|
|
||||||
|
|
||||||
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
|
|
||||||
|
|
||||||
int xStart = (width - PICKER_WIDTH) / 2;
|
|
||||||
int yStart = (height - PICKER_HEIGHT) / 2;
|
|
||||||
|
|
||||||
// Draw the HSV saturation/value square (with current hue)
|
|
||||||
for (int x = 0; x < PICKER_WIDTH; x++) {
|
|
||||||
for (int y = 0; y < PICKER_HEIGHT; y++) {
|
|
||||||
float saturation = x / (float) PICKER_WIDTH;
|
|
||||||
float value = 1.0f - y / (float) PICKER_HEIGHT;
|
|
||||||
int color = Color.HSBtoRGB(hue, saturation, value);
|
|
||||||
context.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, 0xFF000000 | color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int x = (xStart + selectedX) - 4;
|
|
||||||
int y = (yStart + selectedY) - 4;
|
|
||||||
context.fill(x, y, x+8, y+8, 0xFF000000 | prop.value);
|
|
||||||
context.drawBorder(x, y, 8, 8, 0xFFffffff);
|
|
||||||
|
|
||||||
renderSlider(context, mouseX, mouseY, delta);
|
|
||||||
renderHueSlider(context);
|
|
||||||
|
|
||||||
renderScope.matrices.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderSlider(DrawContext context, int mouseX, int mouseY, float delta) {
|
|
||||||
int xStart = (width - SLIDER_WIDTH) / 2;
|
|
||||||
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
|
|
||||||
|
|
||||||
// Draw slider background (left = transparent, right = white)
|
|
||||||
for (int x = 0; x < SLIDER_WIDTH; x++) {
|
|
||||||
float localOpacity = x / (float) SLIDER_WIDTH;
|
|
||||||
int alpha = (int)(localOpacity * 255) & 0xFF;
|
|
||||||
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
|
|
||||||
context.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw current value as a marker (thin black line)
|
|
||||||
int knobX = xStart + (int)(opacity * SLIDER_WIDTH);
|
|
||||||
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
|
|
||||||
|
|
||||||
// Draw opacity text
|
|
||||||
// context.drawText(textRenderer, String.format("Opacity: %.2f", opacity), xStart, yStart - 15, 0xFFFFFF, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderHueSlider(DrawContext context) {
|
|
||||||
int xStart = (width - SLIDER_WIDTH) / 2;
|
|
||||||
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
|
|
||||||
|
|
||||||
for (int x = 0; x < SLIDER_WIDTH; x++) {
|
|
||||||
float localHue = x / (float) SLIDER_WIDTH;
|
|
||||||
int color = Color.HSBtoRGB(localHue, 1.0f, 1.0f);
|
|
||||||
context.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
|
|
||||||
}
|
|
||||||
|
|
||||||
int knobX = xStart + (int)(hue * SLIDER_WIDTH);
|
|
||||||
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
mouseX *= 2;
|
|
||||||
mouseY *= 2;
|
|
||||||
|
|
||||||
int xStart = (width - PICKER_WIDTH) / 2;
|
|
||||||
int yStart = (height - PICKER_HEIGHT) / 2;
|
|
||||||
|
|
||||||
int relX = (int) mouseX - xStart;
|
|
||||||
int relY = (int) mouseY - yStart;
|
|
||||||
|
|
||||||
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
|
|
||||||
float saturation = relX / (float) PICKER_WIDTH;
|
|
||||||
float value = 1.0f - relY / (float) PICKER_HEIGHT;
|
|
||||||
prop.value = Color.HSBtoRGB(hue, saturation, value);
|
|
||||||
|
|
||||||
int alpha = (int)(opacity * 255) & 0xFF;
|
|
||||||
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
|
|
||||||
|
|
||||||
selectedX = relX;
|
|
||||||
selectedY = relY;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else if (isInSlider(mouseX, mouseY)) {
|
|
||||||
updateOpacity(mouseX);
|
|
||||||
dragging = true;
|
|
||||||
return true;
|
|
||||||
} else if (isInHueSlider(mouseX, mouseY)) {
|
|
||||||
updateHue(mouseX);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.mouseClicked(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
|
||||||
dragging = false;
|
|
||||||
return super.mouseReleased(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
|
||||||
mouseX *= 2;
|
|
||||||
mouseY *= 2;
|
|
||||||
|
|
||||||
if (isInHueSlider(mouseX, mouseY)) {
|
|
||||||
updateHue(mouseX);
|
|
||||||
return true;
|
|
||||||
} else if (dragging) {
|
|
||||||
updateOpacity(mouseX);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
int xStart = (width - PICKER_WIDTH) / 2;
|
|
||||||
int yStart = (height - PICKER_HEIGHT) / 2;
|
|
||||||
|
|
||||||
int relX = (int) mouseX - xStart;
|
|
||||||
int relY = (int) mouseY - yStart;
|
|
||||||
|
|
||||||
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
|
|
||||||
float saturation = relX / (float) PICKER_WIDTH;
|
|
||||||
float value = 1.0f - relY / (float) PICKER_HEIGHT;
|
|
||||||
prop.value = Color.HSBtoRGB(hue, saturation, value);
|
|
||||||
|
|
||||||
int alpha = (int)(opacity * 255) & 0xFF;
|
|
||||||
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
|
|
||||||
|
|
||||||
selectedX = relX;
|
|
||||||
selectedY = relY;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInSlider(double mouseX, double mouseY) {
|
|
||||||
int xStart = (width - SLIDER_WIDTH) / 2;
|
|
||||||
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
|
|
||||||
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
|
|
||||||
mouseY >= yStart && mouseY <= yStart + SLIDER_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInHueSlider(double mouseX, double mouseY) {
|
|
||||||
int xStart = (width - SLIDER_WIDTH) / 2;
|
|
||||||
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
|
|
||||||
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
|
|
||||||
mouseY >= yStart && mouseY <= yStart + HUE_SLIDER_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateOpacity(double mouseX) {
|
|
||||||
int xStart = (width - SLIDER_WIDTH) / 2;
|
|
||||||
float newOpacity = (float)((mouseX - xStart) / SLIDER_WIDTH);
|
|
||||||
opacity = Math.max(0.0f, Math.min(1.0f, newOpacity));
|
|
||||||
|
|
||||||
int alpha = (int)(opacity * 255) & 0xFF;
|
|
||||||
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateHue(double mouseX) {
|
|
||||||
int xStart = (width - SLIDER_WIDTH) / 2;
|
|
||||||
float newHue = (float)((mouseX - xStart) / SLIDER_WIDTH);
|
|
||||||
hue = Math.max(0.0f, Math.min(1.0f, newHue));
|
|
||||||
|
|
||||||
float saturation = selectedX / (float) PICKER_WIDTH;
|
|
||||||
float value = 1.0f - selectedY / (float) PICKER_HEIGHT;
|
|
||||||
prop.value = Color.HSBtoRGB(hue, saturation, value);
|
|
||||||
|
|
||||||
int alpha = (int)(opacity * 255) & 0xFF;
|
|
||||||
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void syncFromColor() {
|
|
||||||
int rgb = prop.value & 0xFFFFFF;
|
|
||||||
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
|
|
||||||
|
|
||||||
this.hue = hsb[0];
|
|
||||||
|
|
||||||
float saturation = hsb[1];
|
|
||||||
float value = hsb[2];
|
|
||||||
|
|
||||||
this.selectedX = (int)(saturation * PICKER_WIDTH);
|
|
||||||
this.selectedY = (int)((1.0f - value) * PICKER_HEIGHT);
|
|
||||||
|
|
||||||
// Extract alpha and convert to opacity float
|
|
||||||
int alpha = (prop.value >> 24) & 0xFF;
|
|
||||||
if (alpha == 0) alpha = 255;
|
|
||||||
this.opacity = alpha / 255.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
package org.saturnclient.ui2.components.inputs;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
public class FloatInput extends Input {
|
public class FloatInput extends Input {
|
||||||
|
|||||||
@@ -1,32 +1,183 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
package org.saturnclient.ui2.components.inputs;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import java.awt.Color;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
|
||||||
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.SaturnScreen;
|
|
||||||
import org.saturnclient.ui2.components.dialog.ColorDialog;
|
|
||||||
|
|
||||||
public class HexInput extends Element {
|
public class HexInput extends Element {
|
||||||
public Property<Integer> prop;
|
public Property<Integer> prop;
|
||||||
public SaturnScreen screen;
|
|
||||||
|
|
||||||
public HexInput(Property<Integer> prop, SaturnScreen screen) {
|
// Picker state
|
||||||
|
private boolean open = false;
|
||||||
|
private float hue = 0f;
|
||||||
|
private float opacity = 1f;
|
||||||
|
private int selectedX = 0;
|
||||||
|
private int selectedY = 0;
|
||||||
|
|
||||||
|
private boolean draggingOpacity = false;
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
private static final int PICKER_WIDTH = 100;
|
||||||
|
private static final int PICKER_HEIGHT = 100;
|
||||||
|
private static final int SLIDER_WIDTH = 100;
|
||||||
|
private static final int SLIDER_HEIGHT = 10;
|
||||||
|
private static final int HUE_SLIDER_HEIGHT = 10;
|
||||||
|
|
||||||
|
public HexInput(Property<Integer> prop) {
|
||||||
|
this.prop = prop;
|
||||||
this.width = 58;
|
this.width = 58;
|
||||||
this.height = 20;
|
this.height = 20;
|
||||||
this.prop = prop;
|
syncFromColor();
|
||||||
this.scale = 0.8f;
|
|
||||||
this.screen = screen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void click(int mouseX, int mouseY) {
|
public void click(int mouseX, int mouseY) {
|
||||||
SaturnClient.client.setScreen(new ColorDialog(prop, screen));
|
open = !open;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
if (open) {
|
||||||
|
handlePickerClick(mouseX - width - 8, mouseY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||||
|
if (open) {
|
||||||
|
handlePickerDrag(mouseX - width - 8, mouseY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||||
|
draggingOpacity = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, 17, prop.value);
|
// Draw the main color rectangle
|
||||||
|
renderScope.drawRoundedRectangle(0, 0, width, height, 6, prop.value);
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Draw the picker relative to (width + 8, 0)
|
||||||
|
int pickerX = width + 8;
|
||||||
|
int pickerY = 0;
|
||||||
|
|
||||||
|
renderColorSquare(renderScope, pickerX, pickerY);
|
||||||
|
renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10);
|
||||||
|
renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderColorSquare(RenderScope renderScope, int xStart, int yStart) {
|
||||||
|
for (int x = 0; x < PICKER_WIDTH; x++) {
|
||||||
|
for (int y = 0; y < PICKER_HEIGHT; y++) {
|
||||||
|
float saturation = x / (float) PICKER_WIDTH;
|
||||||
|
float value = 1f - y / (float) PICKER_HEIGHT;
|
||||||
|
int color = Color.HSBtoRGB(hue, saturation, value);
|
||||||
|
int alpha = (int) (opacity * 255) & 0xFF;
|
||||||
|
color = (alpha << 24) | (color & 0xFFFFFF);
|
||||||
|
renderScope.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw selection square
|
||||||
|
int selX = xStart + selectedX - 4;
|
||||||
|
int selY = yStart + selectedY - 4;
|
||||||
|
renderScope.fill(selX, selY, selX + 8, selY + 8, 0xFF000000 | prop.value);
|
||||||
|
renderScope.drawBorder(selX, selY, 8, 8, 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderOpacitySlider(RenderScope renderScope, int xStart, int yStart) {
|
||||||
|
for (int x = 0; x < SLIDER_WIDTH; x++) {
|
||||||
|
float localOpacity = x / (float) SLIDER_WIDTH;
|
||||||
|
int alpha = (int) (localOpacity * 255) & 0xFF;
|
||||||
|
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
|
||||||
|
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
int knobX = xStart + (int) (opacity * SLIDER_WIDTH);
|
||||||
|
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderHueSlider(RenderScope renderScope, int xStart, int yStart) {
|
||||||
|
for (int x = 0; x < SLIDER_WIDTH; x++) {
|
||||||
|
float localHue = x / (float) SLIDER_WIDTH;
|
||||||
|
int color = Color.HSBtoRGB(localHue, 1f, 1f);
|
||||||
|
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
|
||||||
|
}
|
||||||
|
|
||||||
|
int knobX = xStart + (int) (hue * SLIDER_WIDTH);
|
||||||
|
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePickerClick(double mouseX, double mouseY) {
|
||||||
|
// Color square
|
||||||
|
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
|
||||||
|
selectedX = (int) mouseX;
|
||||||
|
selectedY = (int) mouseY;
|
||||||
|
updateColorFromSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opacity slider
|
||||||
|
int opY = -SLIDER_HEIGHT - 10;
|
||||||
|
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= opY && mouseY <= opY + SLIDER_HEIGHT) {
|
||||||
|
updateOpacity(mouseX);
|
||||||
|
draggingOpacity = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hue slider
|
||||||
|
int hueY = PICKER_HEIGHT + 10;
|
||||||
|
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= hueY && mouseY <= hueY + HUE_SLIDER_HEIGHT) {
|
||||||
|
updateHue(mouseX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePickerDrag(double mouseX, double mouseY) {
|
||||||
|
// Opacity
|
||||||
|
if (draggingOpacity)
|
||||||
|
updateOpacity(mouseX);
|
||||||
|
|
||||||
|
// Color square
|
||||||
|
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
|
||||||
|
selectedX = (int) mouseX;
|
||||||
|
selectedY = (int) mouseY;
|
||||||
|
updateColorFromSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateColorFromSelection() {
|
||||||
|
float saturation = selectedX / (float) PICKER_WIDTH;
|
||||||
|
float value = 1f - selectedY / (float) PICKER_HEIGHT;
|
||||||
|
int color = Color.HSBtoRGB(hue, saturation, value);
|
||||||
|
int alpha = (int) (opacity * 255) & 0xFF;
|
||||||
|
prop.value = (alpha << 24) | (color & 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHue(double mouseX) {
|
||||||
|
hue = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
|
||||||
|
updateColorFromSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOpacity(double mouseX) {
|
||||||
|
opacity = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
|
||||||
|
updateColorFromSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncFromColor() {
|
||||||
|
int rgb = prop.value & 0xFFFFFF;
|
||||||
|
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
|
||||||
|
hue = hsb[0];
|
||||||
|
selectedX = (int) (hsb[1] * PICKER_WIDTH);
|
||||||
|
selectedY = (int) ((1f - hsb[2]) * PICKER_HEIGHT);
|
||||||
|
int alpha = (prop.value >> 24) & 0xFF;
|
||||||
|
if (alpha == 0)
|
||||||
|
alpha = 255;
|
||||||
|
opacity = alpha / 255f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,21 +2,20 @@ package org.saturnclient.ui2.components.inputs;
|
|||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
public abstract class Input extends Element {
|
public abstract class Input extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("Input");
|
|
||||||
private static Property<Integer> font = theme.property("font", Property.font(1));
|
|
||||||
|
|
||||||
String text = "";
|
String text = "";
|
||||||
public int cursorPosition = 0;
|
public int cursorPosition = 0;
|
||||||
|
|
||||||
public abstract void checkReset();
|
public abstract void checkReset();
|
||||||
|
|
||||||
public abstract void backspace();
|
public abstract void backspace();
|
||||||
|
|
||||||
public boolean validateBackspace() {
|
public boolean validateBackspace() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -71,11 +70,12 @@ public abstract class Input extends Element {
|
|||||||
int scrollOffset = getScrollOffset();
|
int scrollOffset = getScrollOffset();
|
||||||
String visibleText = getVisibleText(scrollOffset);
|
String visibleText = getVisibleText(scrollOffset);
|
||||||
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
|
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
|
||||||
renderScope.drawText(0.6f, visibleText, 4, 4, font.value, textColor);
|
renderScope.drawText(0.6f, visibleText, 4, 4, Theme.FONT.value, textColor);
|
||||||
|
|
||||||
if (focused) {
|
if (focused) {
|
||||||
int cursorX = 4 +
|
int cursorX = 4 +
|
||||||
(int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), font.value) * 0.6f);
|
(int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), Theme.FONT.value)
|
||||||
|
* 0.6f);
|
||||||
|
|
||||||
renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF);
|
renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ public abstract class Input extends Element {
|
|||||||
int maxWidth = width - 4; // Available width for text
|
int maxWidth = width - 4; // Available width for text
|
||||||
while (offset < cursorPosition) { // Ensure cursor remains visible
|
while (offset < cursorPosition) { // Ensure cursor remains visible
|
||||||
String visibleText = text.substring(offset, cursorPosition);
|
String visibleText = text.substring(offset, cursorPosition);
|
||||||
if (Fonts.getWidth(visibleText, font.value) > maxWidth) {
|
if (Fonts.getWidth(visibleText, Theme.FONT.value) > maxWidth) {
|
||||||
offset++;
|
offset++;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -104,7 +104,7 @@ public abstract class Input extends Element {
|
|||||||
|
|
||||||
// Ensure text does not overflow past the available width
|
// Ensure text does not overflow past the available width
|
||||||
for (int i = 1; i <= visibleText.length(); i++) {
|
for (int i = 1; i <= visibleText.length(); i++) {
|
||||||
if (Fonts.getWidth(visibleText.substring(0, i), font.value) > width - 4) {
|
if (Fonts.getWidth(visibleText.substring(0, i), Theme.FONT.value) > width - 4) {
|
||||||
return visibleText.substring(0, i - 1); // Trim overflow
|
return visibleText.substring(0, i - 1); // Trim overflow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
package org.saturnclient.ui2.components.inputs;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
public class IntInput extends Input {
|
public class IntInput extends Input {
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
package org.saturnclient.ui2.components.inputs;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
public class KeybindingSelector extends Element {
|
public class KeybindingSelector extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("Input");
|
|
||||||
private static Property<Integer> font = theme.property("font", Property.font(1));
|
|
||||||
Property<Integer> prop;
|
Property<Integer> prop;
|
||||||
|
|
||||||
public KeybindingSelector(Property<Integer> prop) {
|
public KeybindingSelector(Property<Integer> prop) {
|
||||||
@@ -23,9 +21,12 @@ public class KeybindingSelector extends Element {
|
|||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
|
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
|
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
|
||||||
renderScope.drawText(0.6f, prop.value == -1 ? "<NONE>" :
|
String name = GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value));
|
||||||
GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)).toUpperCase(),
|
|
||||||
4, 4, font.value, textColor);
|
renderScope.drawText(0.6f,
|
||||||
|
prop.value == -1 ? "<NONE>"
|
||||||
|
: name != null ? name.toUpperCase() : "<Invalid-Key>",
|
||||||
|
4, 4, Theme.FONT.value, textColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
package org.saturnclient.ui2.components.inputs;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
@@ -10,11 +10,6 @@ import org.saturnclient.ui2.resources.Fonts;
|
|||||||
import net.minecraft.client.render.RenderLayer;
|
import net.minecraft.client.render.RenderLayer;
|
||||||
|
|
||||||
public class Select extends Element {
|
public class Select extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("Toggle");
|
|
||||||
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xAAAAAA));
|
|
||||||
private static Property<Integer> arrowColor = theme.property("arrow-color", Property.color(0xff8B5CF6));
|
|
||||||
private static Property<Integer> font = theme.property("font", Property.font(1));
|
|
||||||
|
|
||||||
private Property<Integer> prop;
|
private Property<Integer> prop;
|
||||||
|
|
||||||
public Select(Property<Integer> prop) {
|
public Select(Property<Integer> prop) {
|
||||||
@@ -27,15 +22,14 @@ public class Select extends Element {
|
|||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||||
|
|
||||||
renderScope.drawText(0.8f, "<", 0, 0, font.value, arrowColor.value);
|
renderScope.drawText(0.8f, "<", 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
|
||||||
String text = prop.getSelection();
|
String text = prop.getSelection();
|
||||||
|
|
||||||
renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, font.value) * 0.6f)) / 2, 1, font.value, fgColor.value);
|
renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, Theme.FONT.value) * 0.6f)) / 2, 1,
|
||||||
|
Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
|
||||||
renderScope.drawText(0.8f, ">", width - 10, 0, font.value, arrowColor.value);
|
renderScope.drawText(0.8f, ">", width - 10, 0, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
|
||||||
theme.setState(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
package org.saturnclient.ui2.components.inputs;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
import org.saturnclient.saturnclient.config.manager.Property;
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
|
||||||
public class Toggle extends Element {
|
public class Toggle extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("Toggle", "hovering", "enabled");
|
|
||||||
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF808080));
|
|
||||||
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFF909090));
|
|
||||||
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(30));
|
|
||||||
|
|
||||||
static {
|
|
||||||
theme.propertyStateDefault("enabled", "bg-color", 0xaf8B5CF6);
|
|
||||||
theme.propertyStateDefault("enabled", "fg-color", 0xff8B5CF6);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Property<Boolean> prop;
|
private Property<Boolean> prop;
|
||||||
|
|
||||||
public Toggle(Property<Boolean> prop) {
|
public Toggle(Property<Boolean> prop) {
|
||||||
@@ -27,19 +17,10 @@ public class Toggle extends Element {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
if (prop.value) {
|
renderScope.drawRoundedRectangle(0, 0, width, height, height,
|
||||||
theme.setState("enabled");
|
Theme.withAlpha(0.5f, prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value));
|
||||||
}
|
renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30,
|
||||||
|
prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||||
if (ctx.isHovering()) {
|
|
||||||
theme.applyState("hovering");
|
|
||||||
}
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, fgColor.value);
|
|
||||||
|
|
||||||
theme.setState(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.saturnclient.ui2.elements;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||||
|
import org.saturnclient.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.ui2.Element;
|
||||||
|
import org.saturnclient.ui2.ElementContext;
|
||||||
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.components.ElementRenderer;
|
||||||
|
|
||||||
|
public class AnimationStagger extends Element {
|
||||||
|
protected List<Element> children = new ArrayList<>();
|
||||||
|
public int delay;
|
||||||
|
|
||||||
|
public AnimationStagger(int delay) {
|
||||||
|
this.delay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimationStagger(AnimationConfig config) {
|
||||||
|
this.delay = config.stagger.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Element element) {
|
||||||
|
if (!Config.stagger.value) {
|
||||||
|
element.animation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.animation != null) {
|
||||||
|
element.animation.delay = delay * children.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementRenderer.draw(children, element);
|
||||||
|
|
||||||
|
this.height = Math.max(this.height, element.y + element.height);
|
||||||
|
this.width = Math.max(this.width, element.x + element.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
|
ElementRenderer.render(children, ctx.elapsed, renderScope,
|
||||||
|
ctx.mouseX,
|
||||||
|
ctx.mouseY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
ElementRenderer.mouseClicked(children, mouseX, mouseY, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyPressed(int keyCode, int scanCode, int modifiers) {
|
||||||
|
ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||||
|
ElementRenderer.mouseDragged(children, mouseX, mouseY, button, deltaX, deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||||
|
ElementRenderer.mouseReleased(children, mouseX, mouseY, button);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +1,29 @@
|
|||||||
package org.saturnclient.ui2.elements;
|
package org.saturnclient.ui2.elements;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
import org.saturnclient.ui2.resources.Fonts;
|
||||||
|
|
||||||
public class Button extends Element {
|
public class Button extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("Button", "hovering");
|
|
||||||
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000));
|
|
||||||
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
|
|
||||||
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
|
|
||||||
private static Property<Integer> font = theme.property("font", Property.font(1));
|
|
||||||
|
|
||||||
static {
|
|
||||||
theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String text;
|
private String text;
|
||||||
private Runnable onClick;
|
private Runnable onClick;
|
||||||
|
|
||||||
public Button(String text, Runnable onClick) {
|
public Button(String text, Runnable onClick) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.onClick = onClick;
|
this.onClick = onClick;
|
||||||
this.dimensions(Fonts.getWidth(text, font.value) + 50, 38);
|
this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
if (ctx.isHovering()) {
|
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||||
theme.setState("hovering");
|
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||||
} else {
|
|
||||||
theme.setState(null);
|
|
||||||
}
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
|
|
||||||
|
|
||||||
// renderScope.drawText(text, (width - Fonts.getWidth(text, font)) / 2, (height - Fonts.getHeight()) / 2, bold.value, fgColor.value);
|
renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height),
|
||||||
renderScope.drawText(text, Fonts.centerX(width, text, font.value), Fonts.centerY(height), font.value, fgColor.value);
|
Theme.FONT.value,
|
||||||
|
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.saturnclient.ui2.elements;
|
package org.saturnclient.ui2.elements;
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.Property;
|
import org.saturnclient.saturnclient.config.Theme;
|
||||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
|
||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
@@ -10,9 +9,6 @@ import net.minecraft.client.render.RenderLayer;
|
|||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
public class ClickableTexture extends Element {
|
public class ClickableTexture extends Element {
|
||||||
private static ThemeManager theme = new ThemeManager("ClickableTexture", "hovering");
|
|
||||||
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
|
|
||||||
|
|
||||||
Identifier sprite;
|
Identifier sprite;
|
||||||
private Runnable onClick;
|
private Runnable onClick;
|
||||||
|
|
||||||
@@ -23,14 +19,9 @@ public class ClickableTexture extends Element {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
if (ctx.isHovering()) {
|
|
||||||
theme.setState("hovering");
|
|
||||||
} else {
|
|
||||||
theme.setState(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||||
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height, fgColor.value);
|
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height,
|
||||||
|
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package org.saturnclient.ui2.elements;
|
|||||||
import org.saturnclient.ui2.Element;
|
import org.saturnclient.ui2.Element;
|
||||||
import org.saturnclient.ui2.ElementContext;
|
import org.saturnclient.ui2.ElementContext;
|
||||||
import org.saturnclient.ui2.RenderScope;
|
import org.saturnclient.ui2.RenderScope;
|
||||||
|
import org.saturnclient.ui2.resources.SvgTexture;
|
||||||
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
public class ImageTexture extends Element {
|
public class ImageTexture extends Element {
|
||||||
@@ -16,7 +17,16 @@ public class ImageTexture extends Element {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
if (sprite.toString().endsWith(".svg")) {
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
|
||||||
|
// Get the actual window pixel dimensions for the image
|
||||||
|
int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWidth());
|
||||||
|
int renderHeight = (int) (height * client.getWindow().getFramebufferHeight() / client.getWindow().getHeight());
|
||||||
|
|
||||||
|
sprite = SvgTexture.getSvg(client, sprite, renderWidth * 2, renderHeight * 2);
|
||||||
|
}
|
||||||
|
|
||||||
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height);
|
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user