Compare commits
203
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 |
@@ -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
|
||||
+137
-33
@@ -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 {
|
||||
// Fabric mod development plugin
|
||||
id 'fabric-loom' version '1.9-SNAPSHOT'
|
||||
|
||||
// Maven publishing support
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
/*
|
||||
* ================================================================
|
||||
* Project Metadata
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
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 {
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Minecraft Core
|
||||
// ─────────────────────────────────────────────
|
||||
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"
|
||||
|
||||
|
||||
// Fabric Loader
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
|
||||
// Fabric API
|
||||
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"
|
||||
include "dev.felnull:special-model-loader:1.4.0+pre.1"
|
||||
|
||||
include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}")
|
||||
//You might want bendy-lib. playerAnimator will wrap it.
|
||||
// include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}")
|
||||
// SVG Rendering Library
|
||||
include implementation("com.formdev:svgSalamander:1.1.4")
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 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 {
|
||||
name = "FelNull Maven"
|
||||
url = "https://maven.felnull.dev"
|
||||
}
|
||||
|
||||
maven {
|
||||
name "KosmX's maven"
|
||||
url 'https://maven.kosmx.dev/'
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ================================================================
|
||||
* Resource Processing
|
||||
* ================================================================
|
||||
* Injects version values into fabric.mod.json.
|
||||
*/
|
||||
processResources {
|
||||
inputs.property "version", project.version
|
||||
inputs.property "minecraft_version", project.minecraft_version
|
||||
@@ -46,38 +128,58 @@ processResources {
|
||||
filteringCharset "UTF-8"
|
||||
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand "version": project.version,
|
||||
expand(
|
||||
"version": project.version,
|
||||
"minecraft_version": project.minecraft_version,
|
||||
"loader_version": project.loader_version
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ================================================================
|
||||
* Java Configuration
|
||||
* ================================================================
|
||||
* Targeting Java 21.
|
||||
*/
|
||||
def targetJavaVersion = 21
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
it.options.encoding = "UTF-8"
|
||||
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
||||
it.options.release.set(targetJavaVersion)
|
||||
}
|
||||
options.encoding = "UTF-8"
|
||||
options.release.set(targetJavaVersion)
|
||||
}
|
||||
|
||||
java {
|
||||
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
|
||||
if (JavaVersion.current() < javaVersion) {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||
}
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||
|
||||
archivesBaseName = project.archives_base_name
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ================================================================
|
||||
* Jar Packaging
|
||||
* ================================================================
|
||||
* Includes LICENSE and NOTICE inside the final jar.
|
||||
*/
|
||||
jar {
|
||||
from("LICENSE") {
|
||||
rename { "${it}_${project.archivesBaseName}"}
|
||||
rename { "${it}_${project.archivesBaseName}" }
|
||||
}
|
||||
from("NOTICE") {
|
||||
rename { "${it}_${project.archivesBaseName}"}
|
||||
rename { "${it}_${project.archivesBaseName}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ================================================================
|
||||
* Publishing
|
||||
* ================================================================
|
||||
* Configured but no remote repository defined yet.
|
||||
*/
|
||||
publishing {
|
||||
publications {
|
||||
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.
|
||||
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
|
||||
yarn_mappings=1.21.4+build.7
|
||||
loader_version=0.16.10
|
||||
fabric_version=0.114.0+1.21.4
|
||||
|
||||
mod_version = 0.0.4-beta2
|
||||
mod_version = 0.0.7-beta1
|
||||
maven_group = org.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 org.saturnclient.saturnclient.config.NamedProperty;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.Property.NamedProperty;
|
||||
|
||||
public class ModDimensions {
|
||||
public Property<Integer> x = Property.integer(0);
|
||||
@@ -20,17 +20,17 @@ public class ModDimensions {
|
||||
|
||||
public NamedProperty<Map<String, Property<?>>> prop() {
|
||||
return Property.namespace(Map.of(
|
||||
"X", x,
|
||||
"Y", y,
|
||||
"Scale", scale,
|
||||
"Background Color", bgColor,
|
||||
"Foreground Color", fgColor,
|
||||
"Corner Radius", radius,
|
||||
"Font", font
|
||||
)).named("In-Game Display");
|
||||
"X", x,
|
||||
"Y", y,
|
||||
"Scale", scale,
|
||||
"Background Color", bgColor,
|
||||
"Foreground Color", fgColor,
|
||||
"Corner Radius", radius,
|
||||
"Font", font)).named("In-Game Display");
|
||||
}
|
||||
|
||||
public ModDimensions() {}
|
||||
public ModDimensions() {
|
||||
}
|
||||
|
||||
public ModDimensions(int width, int height) {
|
||||
this.width = width;
|
||||
|
||||
@@ -1,28 +1,60 @@
|
||||
package org.saturnclient.modules;
|
||||
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.modules.mods.*;
|
||||
|
||||
public class ModManager {
|
||||
public static Module[] MODS = {
|
||||
public static final List<Module> ENABLED_MODS = new ArrayList<>();
|
||||
public static final Module[] ALL_MODS = {
|
||||
new Crosshair(),
|
||||
new AutoSprint(),
|
||||
new ArmorDisplay(),
|
||||
new Fps(),
|
||||
new PotionDisplay(),
|
||||
new StatusEffects(),
|
||||
new Coordinates(),
|
||||
new Freelook(),
|
||||
new Keystrokes()
|
||||
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() {
|
||||
// Pre-filter enabled modules to avoid checking every tick
|
||||
updateEnabledModules();
|
||||
|
||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||
for (Module m : MODS) {
|
||||
if (m.isEnabled()) {
|
||||
m.tick();
|
||||
}
|
||||
// Only iterate over enabled modules
|
||||
for (Module m : ENABLED_MODS) {
|
||||
m.tick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.saturnclient.config.NamedProperty;
|
||||
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 net.minecraft.util.Identifier;
|
||||
@@ -19,17 +19,25 @@ public abstract class Module {
|
||||
}
|
||||
}
|
||||
|
||||
public void render(RenderScope scope) {}
|
||||
public void tick() {}
|
||||
public void render(RenderScope scope) {
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
}
|
||||
|
||||
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() {
|
||||
return details.name;
|
||||
}
|
||||
|
||||
|
||||
public Identifier getIconTexture() {
|
||||
return Textures.getModIcon(details.namespace);
|
||||
}
|
||||
@@ -41,11 +49,11 @@ public abstract class Module {
|
||||
public String getDescription() {
|
||||
return details.description;
|
||||
}
|
||||
|
||||
|
||||
public String[] getTags() {
|
||||
return details.tags;
|
||||
}
|
||||
|
||||
|
||||
public String getVersion() {
|
||||
return details.version;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
|
||||
import net.minecraft.entity.EquipmentSlot;
|
||||
@@ -14,22 +14,31 @@ import net.minecraft.item.Items;
|
||||
|
||||
public class ArmorDisplay extends Module implements HudMod {
|
||||
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() {
|
||||
super(new ModuleDetails("Armor Display", "armor")
|
||||
.description("Displays armor durability")
|
||||
.version("v0.1.0")
|
||||
.tags("Utility"),
|
||||
enabled.named("Enabled"),
|
||||
dimensions.prop());
|
||||
.description("Displays armor durability")
|
||||
.version("v0.1.0")
|
||||
.tags("Utility"),
|
||||
enabled.named("Enabled"),
|
||||
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;
|
||||
|
||||
if (useMainHand.value && !mainHand.isEmpty()) {
|
||||
scope.drawItem(mainHand, 0, 15 * row);
|
||||
renderHealth(scope, row, mainHand.getMaxDamage(), mainHand.getDamage());
|
||||
row++;
|
||||
}
|
||||
|
||||
if (!helmet.isEmpty()) {
|
||||
scope.drawItem(helmet, 0, 0);
|
||||
scope.drawItem(helmet, 0, 15 * row);
|
||||
renderHealth(scope, row, helmet.getMaxDamage(), helmet.getDamage());
|
||||
row++;
|
||||
}
|
||||
@@ -53,23 +62,27 @@ public class ArmorDisplay extends Module implements HudMod {
|
||||
}
|
||||
|
||||
public void renderHealth(RenderScope scope, int i, int max_damage, int damage) {
|
||||
if (max_damage > 0 ) {
|
||||
scope.drawText(0.5f, "" + (max_damage - damage), 17, (15 * i) + 3, dimensions.font.value, dimensions.fgColor.value);
|
||||
if (max_damage > 0) {
|
||||
scope.drawText(0.5f, "" + (max_damage - damage), 17, (15 * i) + 3, dimensions.font.value,
|
||||
dimensions.fgColor.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHud(RenderScope scope) {
|
||||
renderArmor(scope,
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.HEAD),
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.CHEST),
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.LEGS),
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.FEET));
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.MAINHAND),
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.HEAD),
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.CHEST),
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.LEGS),
|
||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.FEET));
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
@@ -83,7 +96,7 @@ public class ArmorDisplay extends Module implements HudMod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean e) {
|
||||
enabled.value = e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.Objects;
|
||||
import org.saturnclient.modules.Module;
|
||||
import org.saturnclient.modules.ModuleDetails;
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
|
||||
public class AutoSprint extends Module {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
if (SaturnClient.client.options.forwardKey.isPressed() && !SaturnClient.client.options.backKey.isPressed() && !SaturnClient.client.player.isSneaking() &&
|
||||
!SaturnClient.client.player.horizontalCollision) {
|
||||
if (SaturnClient.client.options.forwardKey.isPressed()
|
||||
&& !SaturnClient.client.options.backKey.isPressed()
|
||||
&& !SaturnClient.client.player.isSneaking()
|
||||
&& !SaturnClient.client.player.horizontalCollision
|
||||
&& !SaturnClient.client.player.isUsingItem()) {
|
||||
|
||||
Objects.requireNonNull(SaturnClient.client.player).setSprinting(true);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +40,7 @@ public class AutoSprint extends Module {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean 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.ModuleDetails;
|
||||
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.resources.Fonts;
|
||||
|
||||
@@ -69,7 +69,7 @@ public class Coordinates extends Module implements HudMod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean e) {
|
||||
enabled.value = e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.saturnclient.modules.mods;
|
||||
import org.saturnclient.modules.Module;
|
||||
import org.saturnclient.modules.ModuleDetails;
|
||||
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.resources.Textures;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class Crosshair extends Module {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean 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.ModuleDetails;
|
||||
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.resources.Fonts;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class Fps extends Module implements HudMod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean e) {
|
||||
enabled.value = e;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.lwjgl.glfw.GLFW;
|
||||
import org.saturnclient.modules.Module;
|
||||
import org.saturnclient.modules.ModuleDetails;
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
|
||||
import net.minecraft.client.option.Perspective;
|
||||
|
||||
@@ -60,7 +60,7 @@ public class Freelook extends Module {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean 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,42 +4,48 @@ import org.saturnclient.modules.Module;
|
||||
import org.saturnclient.modules.HudMod;
|
||||
import org.saturnclient.modules.ModDimensions;
|
||||
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.modules.ModuleDetails;
|
||||
|
||||
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 Property<Integer> clickFg = Property.color(0xFFFFFFFF);
|
||||
private static final Property<Boolean> enabled = Property.bool(false);
|
||||
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() {
|
||||
super(new ModuleDetails("Keystrokes", "keystrokes")
|
||||
.description("Displays the keystrokes for movement")
|
||||
.version("v0.1.0")
|
||||
.tags("Utility"),
|
||||
.description("Displays movement keystrokes")
|
||||
.version("v0.2.0")
|
||||
.tags("Utility"),
|
||||
|
||||
enabled.named("Enabled"),
|
||||
showMouse.named("Show mouse clicks"),
|
||||
showSpace.named("Show space clicks"),
|
||||
dimensions.prop(),
|
||||
clickFg.named("Clicked fg"),
|
||||
clickBg.named("Clicked bg"));
|
||||
enabled.named("Enabled"),
|
||||
showMouse.named("Show mouse clicks"),
|
||||
showSpace.named("Show space"),
|
||||
dimensions.prop(),
|
||||
clickFg.named("Clicked fg"),
|
||||
clickBg.named("Clicked bg")
|
||||
);
|
||||
|
||||
dimensions.renderBackground = false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
w = SaturnClient.client.options.forwardKey.isPressed();
|
||||
@@ -49,79 +55,87 @@ public class Keystrokes extends Module implements HudMod {
|
||||
lmb = SaturnClient.client.options.attackKey.isPressed();
|
||||
rmb = SaturnClient.client.options.useKey.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
|
||||
public void renderDummy(RenderScope scope) {
|
||||
dimensions.height = 54; // base height with padding considered
|
||||
|
||||
// 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;
|
||||
}
|
||||
render(scope, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHud(RenderScope scope) {
|
||||
dimensions.height = 54; // base height with padding considered
|
||||
render(scope, true);
|
||||
}
|
||||
|
||||
// WASD keys (3px padding between each key)
|
||||
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
|
||||
private void render(RenderScope scope, boolean live) {
|
||||
|
||||
if (showMouse.value) {
|
||||
renderKeyM(scope, lmb, "LMB", 0, 54); // Below A/S/D
|
||||
renderKeyM(scope, rmb, "RMB", 40, 54); // With 3px between
|
||||
dimensions.height += 27;
|
||||
}
|
||||
int yOffset = 0;
|
||||
|
||||
// --- 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) {
|
||||
renderKeySpace(scope, space, 0, dimensions.height);
|
||||
dimensions.height += 22;
|
||||
renderKeySpace(scope, live && space, 0, yOffset);
|
||||
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) {
|
||||
int height = 19;
|
||||
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);
|
||||
scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg);
|
||||
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg);
|
||||
}
|
||||
|
||||
private void renderKeyM(RenderScope scope, boolean isPressed, String c, int x, int y) {
|
||||
int width = 38, height = 24;
|
||||
scope.drawRoundedRectangle(x, y, width, height, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
|
||||
private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) {
|
||||
int bg = pressed ? 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.drawText(0.6f, c, x + 9, y + 7, dimensions.font.value, isPressed ? clickFg.value : dimensions.fgColor.value);
|
||||
scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_HEIGHT, dimensions.radius.value, bg);
|
||||
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) {
|
||||
int size = 24;
|
||||
scope.drawRoundedRectangle(x, y, size, size, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
|
||||
private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) {
|
||||
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
|
||||
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
|
||||
|
||||
// Center the character with 3px top padding
|
||||
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, isPressed ? clickFg.value : dimensions.fgColor.value);
|
||||
scope.drawRoundedRectangle(x, y, dimensions.width, SPACE_HEIGHT, dimensions.radius.value, bg);
|
||||
|
||||
int lineY = y + SPACE_HEIGHT / 2;
|
||||
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled.value;
|
||||
@@ -133,7 +147,7 @@ public class Keystrokes extends Module implements HudMod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean 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.ModuleDetails;
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
|
||||
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.math.MathHelper;
|
||||
|
||||
public class PotionDisplay extends Module implements HudMod {
|
||||
public class StatusEffects extends Module implements HudMod {
|
||||
public static Property<Boolean> enabled = Property.bool(false);
|
||||
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),
|
||||
};
|
||||
|
||||
public PotionDisplay() {
|
||||
super(new ModuleDetails("Potion Display", "potion")
|
||||
.description("Displays potion effects")
|
||||
public StatusEffects() {
|
||||
super(new ModuleDetails("Status effects", "effect")
|
||||
.description("Displays status effects")
|
||||
.version("v0.1.0")
|
||||
.tags("Utility"),
|
||||
|
||||
@@ -99,7 +99,7 @@ public class PotionDisplay extends Module implements HudMod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean e) {
|
||||
public void onEnabled(boolean 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class RenderMixin {
|
||||
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
||||
((DrawContextAccessor) context).getVertexConsumers());
|
||||
|
||||
for (org.saturnclient.modules.Module m : ModManager.MODS) {
|
||||
for (org.saturnclient.modules.Module m : ModManager.ENABLED_MODS) {
|
||||
if (m instanceof HudMod && m.isEnabled()) {
|
||||
ModDimensions dim = ((HudMod) m).getDimensions();
|
||||
|
||||
|
||||
@@ -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 org.saturnclient.modules.ModManager;
|
||||
import org.saturnclient.saturnclient.auth.Auth;
|
||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||
import org.saturnclient.saturnclient.config.Config;
|
||||
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||
import org.saturnclient.saturnclient.cosmetics.Emotes;
|
||||
import org.saturnclient.saturnclient.cosmetics.Hats;
|
||||
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
|
||||
@@ -37,9 +38,13 @@ public class SaturnClient implements ModInitializer {
|
||||
public void onInitialize() {
|
||||
LOGGER.info("Initializing " + MOD_ID);
|
||||
client = MinecraftClient.getInstance();
|
||||
SaturnClientConfig.init();
|
||||
Config.init();
|
||||
ModManager.init();
|
||||
|
||||
client.execute(() -> {
|
||||
SaturnScreen.preload(client);
|
||||
});
|
||||
|
||||
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
|
||||
|
||||
SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> {
|
||||
@@ -75,7 +80,7 @@ public class SaturnClient implements ModInitializer {
|
||||
|
||||
KeyInputHandler.register();
|
||||
Emotes.initialize();
|
||||
if (Auth.authenticate()) {
|
||||
if (ServiceClient.authenticate()) {
|
||||
Cloaks.initialize();
|
||||
Hats.initialize();
|
||||
LOGGER.info(MOD_ID + " initialization complete");
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
package org.saturnclient.saturnclient.auth;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
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 dev.kosmx.playerAnim.api.layered.AnimationStack;
|
||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
|
||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
case "update_cloak":
|
||||
String targetUuid = parser.getStringOrNull("uuid");
|
||||
String targetCloak = parser.getStringOrNull("cloak");
|
||||
if (targetUuid != null && targetCloak != null) {
|
||||
SaturnPlayer player = players.get(targetUuid);
|
||||
if (player != null) {
|
||||
player.cloak = targetCloak;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "update_hat":
|
||||
targetUuid = parser.getStringOrNull("uuid");
|
||||
String targetHat = parser.getStringOrNull("hat");
|
||||
if (targetUuid != null && targetHat != null) {
|
||||
SaturnPlayer player = players.get(targetUuid);
|
||||
if (player != null) {
|
||||
player.hat = targetHat;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "emote":
|
||||
targetUuid = parser.getStringOrNull("uuid");
|
||||
String emote = parser.getStringOrNull("name");
|
||||
|
||||
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
|
||||
if (player.getUuidAsString().replace("-", "").equals(targetUuid)) {
|
||||
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
|
||||
if (emote != null && !emote.isEmpty()) {
|
||||
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
|
||||
animationStack.removeLayer(1000);
|
||||
}
|
||||
animationStack.addAnimLayer(1000,
|
||||
PlayerAnimationRegistry
|
||||
.getAnimation(Identifier.of("saturnclient", emote))
|
||||
.playAnimation());
|
||||
} else {
|
||||
animationStack.removeLayer(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
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 + "@notify=" + stringifyPlayers());
|
||||
} catch (Exception e) {
|
||||
SaturnClient.LOGGER.error("Request failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setHat(String hatName) {
|
||||
try {
|
||||
Network.write("set_hat@hat=" + hatName + "@notify=" + stringifyPlayers());
|
||||
} catch (Exception e) {
|
||||
SaturnClient.LOGGER.error("Request failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void emote(String name) {
|
||||
try {
|
||||
Network.write("emote@name=" + name + "@notify=" + stringifyPlayers());
|
||||
} catch (Exception e) {
|
||||
SaturnClient.LOGGER.error("Request failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String stringifyPlayers() {
|
||||
return players.keySet().stream()
|
||||
.filter(key -> key != uuid)
|
||||
.collect(Collectors.joining("$"));
|
||||
}
|
||||
|
||||
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 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.event.lifecycle.v1.ClientTickEvents;
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||
|
||||
import net.minecraft.client.network.PlayerListEntry;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
||||
@@ -45,8 +47,6 @@ public class PlayerTracker {
|
||||
|
||||
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
|
||||
GameProfile profile = player.getProfile();
|
||||
String uuid = profile.getId().toString();
|
||||
if (uuid != Auth.uuid)
|
||||
Auth.player(profile.getName(), uuid);
|
||||
ServiceClient.player(profile.getId(), profile.getName());
|
||||
}
|
||||
}
|
||||
@@ -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.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 net.minecraft.util.Formatting;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public class SaturnClientConfig {
|
||||
public class Config {
|
||||
public static ConfigManager config;
|
||||
|
||||
public static Property<Boolean> realisticLogo = Property.bool(false);
|
||||
|
||||
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<Boolean> stagger = Property.bool(true);
|
||||
|
||||
public static Identifier getLogo() {
|
||||
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() {
|
||||
config = new ConfigManager("Saturn Client");
|
||||
config.property("Realistic logo", realisticLogo);
|
||||
config.property("Saturn client title screen", saturnTitleScreen);
|
||||
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,226 +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) {
|
||||
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()) {
|
||||
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) {
|
||||
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,258 +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 Property<T> copy() {
|
||||
return new Property<>(this.value, this.type);
|
||||
}
|
||||
|
||||
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;
|
||||
if (type == PropertyType.HEX && primitive.isString())
|
||||
return true;
|
||||
} 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 && SaturnClient.client.currentScreen == null;
|
||||
}
|
||||
|
||||
public boolean wasKeyPressed() {
|
||||
boolean isPressed = this.isKeyPressed();
|
||||
wasPressedLastTick = isPressed && !wasPressedLastTick;
|
||||
return wasPressedLastTick;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void load(String name, JsonElement element) {
|
||||
if (element != null && element.isJsonObject()) {
|
||||
JsonElement value = element.getAsJsonObject().get(name);
|
||||
if (value != null) {
|
||||
switch (this.getType()) {
|
||||
case BOOLEAN:
|
||||
((Property<Boolean>) this).setValue(value.getAsBoolean());
|
||||
break;
|
||||
case INTEGER:
|
||||
JsonPrimitive primitive = value.getAsJsonPrimitive();
|
||||
if (primitive.isNumber())
|
||||
((Property<Integer>) this).setValue(value.getAsInt());
|
||||
else if (primitive.isString())
|
||||
((Property<Integer>) this).setValue(parseHexToInt(value.getAsString()));
|
||||
break;
|
||||
case FLOAT:
|
||||
((Property<Float>) this).setValue(value.getAsFloat());
|
||||
break;
|
||||
case STRING:
|
||||
((Property<String>) this).setValue(value.getAsString());
|
||||
break;
|
||||
case HEX:
|
||||
((Property<Integer>) this).setValue(parseHexToInt(value.getAsString()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int parseHexToInt(String hex) {
|
||||
hex = hex.replaceAll("#", "");
|
||||
|
||||
if (hex.length() == 6) {
|
||||
hex = "FF" + hex;
|
||||
} else if (hex.length() != 8) {
|
||||
throw new IllegalArgumentException("Hex string must be 6 or 8 characters long");
|
||||
}
|
||||
|
||||
return (int) Long.parseLong(hex, 16);
|
||||
}
|
||||
}
|
||||
@@ -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,93 +0,0 @@
|
||||
package org.saturnclient.saturnclient.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
public class ThemeManager {
|
||||
private static File themeFile = new File(
|
||||
SaturnClient.client.runDirectory,
|
||||
"saturn.theme.json");
|
||||
private static List<ThemeManager> theme = new ArrayList<>();
|
||||
private static JsonObject cachedThemeJson = null;
|
||||
|
||||
private Map<String, Map<String, Object>> states;
|
||||
private Map<String, Property<?>> currentStyling;
|
||||
private String namespace;
|
||||
|
||||
public ThemeManager(String namespace, String... states) {
|
||||
this.namespace = namespace;
|
||||
this.states = new LinkedHashMap<>();
|
||||
this.currentStyling = new LinkedHashMap<>();
|
||||
this.states.put(null, new LinkedHashMap<>());
|
||||
for (String s : states) {
|
||||
this.states.put(s, new LinkedHashMap<>());
|
||||
}
|
||||
theme.add(this);
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
for (Map.Entry<String, Object> tProp : states.get(state).entrySet()) {
|
||||
currentStyling.get(tProp.getKey()).setValue(tProp.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void applyState(String state) {
|
||||
for (Map.Entry<String, Object> tProp : states.get(state).entrySet()) {
|
||||
if (!states.get(null).get(tProp.getKey()).equals(tProp.getValue()))
|
||||
currentStyling.get(tProp.getKey()).setValue(tProp.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public <T> Property<T> property(String name, Property<T> prop) {
|
||||
for (Map.Entry<String, Map<String, Object>> state : this.states.entrySet()) {
|
||||
Property<?> property = prop.copy();
|
||||
if (cachedThemeJson == null) {
|
||||
load();
|
||||
}
|
||||
String stateName = state.getKey();
|
||||
property.load(name, cachedThemeJson.get(stateName != null ? namespace + "@" + stateName : namespace));
|
||||
state.getValue().put(name, property.value);
|
||||
}
|
||||
|
||||
currentStyling.put(name, prop);
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
public <T> void propertyStateDefault(String state, String name, T value) {
|
||||
Property<?> prop = currentStyling.get(name).copy();
|
||||
prop.setValue(value, value);
|
||||
if (cachedThemeJson == null) {
|
||||
load();
|
||||
}
|
||||
prop.load(name, cachedThemeJson.get(state != null ? namespace + "@" + state : namespace));
|
||||
states.get(state).put(name, prop.value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
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 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.feature.FeatureRenderer;
|
||||
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.state.PlayerEntityRenderState;
|
||||
import net.minecraft.client.util.SkinTextures;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.component.DataComponentTypes;
|
||||
import net.minecraft.component.type.EquippableComponent;
|
||||
@@ -26,21 +24,67 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.equipment.EquipmentAsset;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
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> {
|
||||
private final BipedEntityModel<PlayerEntityRenderState> model;
|
||||
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,
|
||||
LoadedEntityModels modelLoader, EquipmentModelLoader equipmentModelLoader) {
|
||||
EquipmentModelLoader equipmentModelLoader) {
|
||||
super(context);
|
||||
this.model = new PlayerCapeModel<PlayerEntityRenderState>(
|
||||
modelLoader.getModelPart(EntityModelLayers.PLAYER_CAPE));
|
||||
this.equipmentModelLoader = equipmentModelLoader;
|
||||
Arrays.fill(segmentValues, 0.0f);
|
||||
}
|
||||
|
||||
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()) {
|
||||
EquipmentModel equipmentModel = this.equipmentModelLoader
|
||||
.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,
|
||||
PlayerEntityRenderState playerEntityRenderState, float f, float g) {
|
||||
if (!playerEntityRenderState.invisible && playerEntityRenderState.capeVisible) {
|
||||
SkinTextures skinTextures = playerEntityRenderState.skinTextures;
|
||||
private void renderCapeQuad(VertexConsumer vertexConsumer, MatrixStack matrixStack,
|
||||
Vec3d bottomLeft, Vec3d bottomRight, Vec3d topRight, Vec3d topLeft,
|
||||
float u1, float v1, float u2, float v2, int light, int overlay,
|
||||
float normalX, float normalY, float normalZ, boolean flipWinding) {
|
||||
MatrixStack.Entry entry = matrixStack.peek();
|
||||
|
||||
if (skinTextures.capeTexture() == null) {
|
||||
String name = playerEntityRenderState.name;
|
||||
String uuid = Auth.playerNames.get(name);
|
||||
if (uuid == null || !Auth.players.containsKey(uuid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Identifier customCape = Cloaks.getCurrentCloakTexture(uuid);
|
||||
|
||||
if (customCape != null
|
||||
&& !this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.WINGS)) {
|
||||
matrixStack.push();
|
||||
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) {
|
||||
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
|
||||
}
|
||||
|
||||
VertexConsumer vertexConsumer = vertexConsumerProvider
|
||||
.getBuffer(RenderLayer.getEntityAlpha(customCape));
|
||||
((PlayerEntityModel) this.getContextModel()).copyTransforms(this.model);
|
||||
this.model.setAngles(playerEntityRenderState);
|
||||
this.model.render(matrixStack, vertexConsumer, 0xF000F0, OverlayTexture.DEFAULT_UV);
|
||||
matrixStack.pop();
|
||||
}
|
||||
}
|
||||
if (!flipWinding) {
|
||||
// CCW
|
||||
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);
|
||||
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) 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) topLeft.x, (float) topLeft.y,
|
||||
(float) topLeft.z)
|
||||
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
|
||||
.normal(normalX, normalY, normalZ);
|
||||
} else {
|
||||
// CW
|
||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y,
|
||||
(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;
|
||||
|
||||
import org.saturnclient.saturnclient.auth.SaturnPlayer;
|
||||
import org.saturnclient.saturnclient.auth.Auth;
|
||||
|
||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import net.minecraft.client.render.OverlayTexture;
|
||||
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,
|
||||
PlayerEntityRenderState state, float limbAngle, float limbDistance) {
|
||||
ItemStack headItem = state.equippedHeadStack;
|
||||
String uuid = Auth.playerNames.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;
|
||||
}
|
||||
SaturnPlayer player = SaturnPlayer.get(state.name);
|
||||
|
||||
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();
|
||||
if (!headItem.isEmpty() || state.invisible || player == null || player.hat.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.List;
|
||||
import org.saturnclient.saturnclient.auth.SaturnPlayer;
|
||||
import org.saturnclient.saturnclient.auth.Auth;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||
|
||||
public class Hats {
|
||||
public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
|
||||
@@ -13,16 +15,16 @@ public class Hats {
|
||||
availableHats.add(0, "");
|
||||
}
|
||||
|
||||
public static void setHat(String uuid, String hatName) {
|
||||
setHatSilent(uuid, hatName);
|
||||
Auth.setHat(hatName);
|
||||
public static void setHat(String hatName) {
|
||||
if (availableHats.contains(hatName)) {
|
||||
setHat(ServiceClient.uuid, hatName);
|
||||
ServiceClient.setHat(hatName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setHatSilent(String uuid, String hatName) {
|
||||
SaturnPlayer player = Auth.players.get(uuid);
|
||||
if (player == null) {
|
||||
Auth.players.put(uuid, new SaturnPlayer("", hatName));
|
||||
} else {
|
||||
public static void setHat(UUID uuid, String hatName) {
|
||||
SaturnPlayer player = SaturnPlayer.get(uuid);
|
||||
if (player != null) {
|
||||
player.hat = hatName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package org.saturnclient.saturnclient.cosmetics.cloaks;
|
||||
|
||||
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.IdentifierUtils;
|
||||
import org.saturnclient.saturnclient.auth.SaturnPlayer;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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.
|
||||
*/
|
||||
public class Cloaks {
|
||||
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", "bmw", "amg", "amg_petronas", "ferrari", "redbull" };
|
||||
private static final String[] ANIMATED_CLOAKS = { "glitch" };
|
||||
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", "bmw", "amg", "amg_petronas",
|
||||
"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/";
|
||||
public static final List<String> availableCloaks = new ArrayList<>();
|
||||
public static Identifier cloakCacheIdentifier = null;
|
||||
public static final Map<String, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Long> lastFrameTime = new ConcurrentHashMap<>();
|
||||
public static final Map<UUID, List<AnimatedCloakData>> animatedCloaks = 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.
|
||||
@@ -37,17 +55,6 @@ public class Cloaks {
|
||||
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);
|
||||
SaturnClient.LOGGER.info("Cloak set to " + cloakName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cloak of the current player
|
||||
*
|
||||
@@ -55,7 +62,8 @@ public class Cloaks {
|
||||
*/
|
||||
public static void setCloak(String cloakName) {
|
||||
if (availableCloaks.contains(cloakName)) {
|
||||
setCloak(Auth.uuid, cloakName);
|
||||
setCloak(ServiceClient.uuid, cloakName);
|
||||
ServiceClient.setCloak(cloakName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +73,10 @@ public class Cloaks {
|
||||
*
|
||||
* @param cloakName Name of the cloak file to load
|
||||
*/
|
||||
public static void setCloakSilent(String uuid, String cloakName) {
|
||||
SaturnPlayer player = Auth.players.get(uuid);
|
||||
if (player == null) {
|
||||
Auth.players.put(uuid, new SaturnPlayer(cloakName, null));
|
||||
} else {
|
||||
public static void setCloak(UUID uuid, String cloakName) {
|
||||
SaturnPlayer player = SaturnPlayer.get(uuid);
|
||||
|
||||
if (player != null) {
|
||||
player.cloak = cloakName;
|
||||
}
|
||||
|
||||
@@ -82,57 +89,19 @@ public class Cloaks {
|
||||
*
|
||||
* @param cloakName Name of the cloak file to load
|
||||
*/
|
||||
public static void loadCloak(String uuid) {
|
||||
SaturnPlayer player = Auth.players.get(uuid);
|
||||
public static void loadCloak(UUID uuid) {
|
||||
SaturnPlayer player = SaturnPlayer.get(uuid);
|
||||
|
||||
if (player != null && player.cloak != null) {
|
||||
if (!player.cloak.isEmpty()) {
|
||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) {
|
||||
// Load animated cloak data in background thread
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String fileName = player.cloak + ".gif";
|
||||
String resourcePath = CLOAKS_RESOURCE_PATH + fileName;
|
||||
InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath);
|
||||
loadAnimatedCloakAsync(uuid, player.cloak)
|
||||
.exceptionally(throwable -> {
|
||||
SaturnClient.LOGGER.error("Failed to load animated cloak for player: " + player.cloak,
|
||||
throwable);
|
||||
return null;
|
||||
});
|
||||
|
||||
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 {
|
||||
SaturnClient.client.execute(() -> loadStaticCloak(player.cloak + ".png"));
|
||||
}
|
||||
@@ -168,16 +137,94 @@ public class Cloaks {
|
||||
}
|
||||
}
|
||||
|
||||
public static Identifier getCurrentCloakTexture(String uuid) {
|
||||
if (!Auth.players.containsKey(uuid)) {
|
||||
public static CompletableFuture<Void> loadAnimatedCloakAsync(UUID uuid, String cloakName) {
|
||||
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;
|
||||
}
|
||||
|
||||
String cloakName = Auth.players.get(uuid).cloak;
|
||||
|
||||
if (cloakName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String cloakName = player.cloak;
|
||||
|
||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
|
||||
List<AnimatedCloakData> frames = animatedCloaks.get(uuid);
|
||||
@@ -204,7 +251,13 @@ public class Cloaks {
|
||||
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 {
|
||||
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.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/*
|
||||
* Copyright 2014 Dhyan Blum
|
||||
@@ -28,7 +29,7 @@ import static java.lang.System.arraycopy;
|
||||
|
||||
/**
|
||||
* <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
|
||||
* <A HREF="http://www.w3.org/Graphics/GIF/spec-gif89a.txt">GIF
|
||||
* specification</A>.
|
||||
@@ -54,9 +55,51 @@ import static java.lang.System.arraycopy;
|
||||
* </p>
|
||||
*
|
||||
* @author Dhyan Blum
|
||||
* @version 1.09 November 2017
|
||||
* @version 1.09 November 2017 (Thread-safe version)
|
||||
*/
|
||||
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 {
|
||||
private int nextBitToRead;
|
||||
private int numberOfBitsToRead;
|
||||
@@ -71,14 +114,16 @@ public final class GifDecoder {
|
||||
|
||||
private int read() {
|
||||
// Byte indices: (bitPos / 8), (bitPos / 8) + 1, (bitPos / 8) + 2
|
||||
int byteIndex = nextBitToRead >>> 3; // Byte = bit / 8
|
||||
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
|
||||
byte1 = bytes[byteIndex++] & 0xFF;
|
||||
byte2 = bytes[byteIndex] & 0xFF;
|
||||
// Glue the bytes together, don't do more shifting than necessary
|
||||
int buffer = ((byte2 << 8 | byte1) << 8 | byte0) >>> bitsToShiftRight;
|
||||
final int byteIndex = nextBitToRead >>> 3; // Byte = bit / 8
|
||||
final int bitsToShiftRight = nextBitToRead & 7; // & 7 is the same as MODULO 8
|
||||
|
||||
// Use int operations to avoid repeated array access
|
||||
final int byte0 = bytes[byteIndex] & 0xFF;
|
||||
final int byte1 = bytes[byteIndex + 1] & 0xFF;
|
||||
final int byte2 = bytes[byteIndex + 2] & 0xFF;
|
||||
|
||||
// Optimized bit manipulation
|
||||
final int buffer = ((byte2 << 16) | (byte1 << 8) | byte0) >>> bitsToShiftRight;
|
||||
nextBitToRead += numberOfBitsToRead;
|
||||
return buffer & bitMask; // Kill the unwanted higher bits
|
||||
}
|
||||
@@ -100,7 +145,7 @@ public final class GifDecoder {
|
||||
private BitReader bitReader; // Notify when code sizes increases
|
||||
|
||||
public CodeTable() {
|
||||
table = new int[4096][1];
|
||||
table = new int[4096][];
|
||||
}
|
||||
|
||||
private int add(final int[] indices) {
|
||||
@@ -130,14 +175,18 @@ public final class GifDecoder {
|
||||
initCodeLimit = (1 << initCodeSize) - 1; // 2^initCodeSize - 1
|
||||
initTableSize = fr.endOfInfoCode + 1;
|
||||
nextCode = initTableSize;
|
||||
for (int c = numColors - 1; c >= 0; c--) {
|
||||
table[c][0] = activeColTbl[c]; // Translated color
|
||||
} // A gap may follow with no colors assigned if numCols < CLEAR
|
||||
|
||||
// Initialize single color entries
|
||||
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.endOfInfoCode] = new int[] { fr.endOfInfoCode }; // EOI
|
||||
|
||||
// Locate transparent color in code table and set to 0
|
||||
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 {
|
||||
// Graphic control extension (optional)
|
||||
// Disposal: 0=NO_ACTION, 1=NO_DISPOSAL, 2=RESTORE_BG, 3=RESTORE_PREV
|
||||
private int disposalMethod; // 0-3 as above, 4-7 undefined
|
||||
private boolean transpColFlag; // 1 Bit
|
||||
private int delay; // Unsigned, LSByte first, n * 1/100 * s
|
||||
private int transpColIndex; // 1 Byte
|
||||
private volatile int disposalMethod; // 0-3 as above, 4-7 undefined
|
||||
private volatile boolean transpColFlag; // 1 Bit
|
||||
private volatile int delay; // Unsigned, LSByte first, n * 1/100 * s
|
||||
private volatile int transpColIndex; // 1 Byte
|
||||
// Image descriptor
|
||||
private int x; // Position on the canvas from the left
|
||||
private int y; // Position on the canvas from the top
|
||||
private int w; // May be smaller than the base image
|
||||
private int h; // May be smaller than the base image
|
||||
private int wh; // width * height
|
||||
private boolean hasLocColTbl; // Has local color table? 1 Bit
|
||||
private boolean interlaceFlag; // Is an interlace image? 1 Bit
|
||||
private volatile int x; // Position on the canvas from the left
|
||||
private volatile int y; // Position on the canvas from the top
|
||||
private volatile int w; // May be smaller than the base image
|
||||
private volatile int h; // May be smaller than the base image
|
||||
private volatile int wh; // width * height
|
||||
private volatile boolean hasLocColTbl; // Has local color table? 1 Bit
|
||||
private volatile boolean interlaceFlag; // Is an interlace image? 1 Bit
|
||||
@SuppressWarnings("unused")
|
||||
private boolean sortFlag; // True if local colors are sorted, 1 Bit
|
||||
private int sizeOfLocColTbl; // Size of the local color table, 3 Bits
|
||||
private int[] localColTbl; // Local color table (optional)
|
||||
private volatile boolean sortFlag; // True if local colors are sorted, 1 Bit
|
||||
private volatile int sizeOfLocColTbl; // Size of the local color table, 3 Bits
|
||||
private volatile int[] localColTbl; // Local color table (optional)
|
||||
// Image data
|
||||
private int firstCodeSize; // LZW minimum code size + 1 for CLEAR & EOI
|
||||
private int clearCode;
|
||||
private int endOfInfoCode;
|
||||
private byte[] data; // Holds LZW encoded data
|
||||
private BufferedImage img; // Full drawn image, not just the frame area
|
||||
private volatile int firstCodeSize; // LZW minimum code size + 1 for CLEAR & EOI
|
||||
private volatile int clearCode;
|
||||
private volatile int endOfInfoCode;
|
||||
private volatile byte[] data; // Holds LZW encoded data
|
||||
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 String header; // Bytes 0-5, GIF87a or GIF89a
|
||||
private int w; // Unsigned 16 Bit, the least significant byte first
|
||||
private int h; // Unsigned 16 Bit, the least significant byte first
|
||||
private int wh; // Image width * image height
|
||||
public boolean hasGlobColTbl; // 1 Bit
|
||||
public int colorResolution; // 3 Bits
|
||||
public boolean sortFlag; // True if global colors are sorted, 1 Bit
|
||||
public int sizeOfGlobColTbl; // 2^(val(3 Bits) + 1), see spec
|
||||
public int bgColIndex; // Background color index, 1 Byte
|
||||
public int pxAspectRatio; // Pixel aspect ratio, 1 Byte
|
||||
public int[] globalColTbl; // Global color table
|
||||
private final List<GifFrame> frames = new ArrayList<GifFrame>(64);
|
||||
public String appId = ""; // 8 Bytes at in[i+3], usually "NETSCAPE"
|
||||
public String appAuthCode = ""; // 3 Bytes at in[i+11], usually "2.0"
|
||||
public int repetitions = 0; // 0: infinite loop, N: number of loops
|
||||
private BufferedImage img = null; // Currently, drawn frame
|
||||
private final BitReader bits = new BitReader();
|
||||
private final CodeTable codes = new CodeTable();
|
||||
private Graphics2D g;
|
||||
public volatile String header; // Bytes 0-5, GIF87a or GIF89a
|
||||
private volatile int w; // Unsigned 16 Bit, the least significant byte first
|
||||
private volatile int h; // Unsigned 16 Bit, the least significant byte first
|
||||
private volatile int wh; // Image width * image height
|
||||
public volatile boolean hasGlobColTbl; // 1 Bit
|
||||
public volatile int colorResolution; // 3 Bits
|
||||
public volatile boolean sortFlag; // True if global colors are sorted, 1 Bit
|
||||
public volatile int sizeOfGlobColTbl; // 2^(val(3 Bits) + 1), see spec
|
||||
public volatile int bgColIndex; // Background color index, 1 Byte
|
||||
public volatile int pxAspectRatio; // Pixel aspect ratio, 1 Byte
|
||||
public volatile int[] globalColTbl; // Global color table
|
||||
private final List<GifFrame> frames = new ArrayList<>(64);
|
||||
public volatile String appId = ""; // 8 Bytes at in[i+3], usually "NETSCAPE"
|
||||
public volatile String appAuthCode = ""; // 3 Bytes at in[i+11], usually "2.0"
|
||||
public volatile int repetitions = 0; // 0: infinite loop, N: number of loops
|
||||
|
||||
// Thread-safe frame rendering state
|
||||
private volatile BufferedImage img = null; // Currently, drawn frame
|
||||
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) {
|
||||
codes.init(fr, activeColTbl, bits);
|
||||
bits.init(fr.data); // Incoming codes
|
||||
final DecodingContext ctx = DECODING_CONTEXT.get();
|
||||
ctx.codeTable.init(fr, activeColTbl, ctx.bitReader);
|
||||
ctx.bitReader.init(fr.data); // Incoming codes
|
||||
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
|
||||
codes.clear(); // Init code table
|
||||
bits.read(); // Skip leading clear code
|
||||
int code = bits.read(); // Read first code
|
||||
ctx.codeTable.clear(); // Init code table
|
||||
ctx.bitReader.read(); // Skip leading clear code
|
||||
int code = ctx.bitReader.read(); // Read 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;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
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
|
||||
codes.clear(); // no previous code, we need to read
|
||||
code = bits.read(); // a new one
|
||||
ctx.codeTable.clear(); // no previous code, we need to read
|
||||
code = ctx.bitReader.read(); // a new one
|
||||
pixels = tbl[code]; // Output pixels
|
||||
arraycopy(pixels, 0, out, outPos, pixels.length);
|
||||
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
||||
outPos += pixels.length;
|
||||
continue; // Back to the loop with a valid previous code
|
||||
} else if (code == endCode) {
|
||||
@@ -219,92 +282,107 @@ public final class GifDecoder {
|
||||
}
|
||||
final int[] prevVals = tbl[prevCode];
|
||||
final int[] prevValsAndK = new int[prevVals.length + 1];
|
||||
arraycopy(prevVals, 0, prevValsAndK, 0, prevVals.length);
|
||||
if (code < codes.nextCode) { // Code table contains code
|
||||
System.arraycopy(prevVals, 0, prevValsAndK, 0, prevVals.length);
|
||||
if (code < ctx.codeTable.nextCode) { // Code table contains code
|
||||
pixels = tbl[code]; // Output pixels
|
||||
arraycopy(pixels, 0, out, outPos, pixels.length);
|
||||
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
||||
outPos += pixels.length;
|
||||
prevValsAndK[prevVals.length] = tbl[code][0]; // K
|
||||
} else {
|
||||
prevValsAndK[prevVals.length] = prevVals[0]; // K
|
||||
arraycopy(prevValsAndK, 0, out, outPos, prevValsAndK.length);
|
||||
System.arraycopy(prevValsAndK, 0, out, outPos, prevValsAndK.length);
|
||||
outPos += prevValsAndK.length;
|
||||
}
|
||||
codes.add(prevValsAndK); // Previous indices + K
|
||||
ctx.codeTable.add(prevValsAndK); // Previous indices + K
|
||||
}
|
||||
} 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) {
|
||||
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
|
||||
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 set4Y = set3Y + ((h + 1) >>> 2); // ceil(h-2/4.0)
|
||||
|
||||
// Sets' start indices in source array
|
||||
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;
|
||||
|
||||
// Group 1 contains every 8th line starting from 0
|
||||
int from = 0, to = 0;
|
||||
for (; from < set2; from += w, to += w8) {
|
||||
arraycopy(src, from, dest, to, w);
|
||||
} // Group 2 contains every 8th line starting from 4
|
||||
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
|
||||
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) {
|
||||
arraycopy(src, from, dest, to, w);
|
||||
} // Group 4 contains every 2nd line starting from 1 (biggest group)
|
||||
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) {
|
||||
arraycopy(src, from, dest, to, w);
|
||||
System.arraycopy(src, from, dest, to, w);
|
||||
}
|
||||
return dest; // All pixel lines have now been rearranged
|
||||
return Arrays.copyOf(dest, src.length); // Return a copy, not the shared buffer
|
||||
}
|
||||
|
||||
private void drawFrame(final GifFrame fr) {
|
||||
// Determine the color table that will be active for this frame
|
||||
final int[] activeColTbl = fr.hasLocColTbl ? fr.localColTbl : globalColTbl;
|
||||
// Get pixels from data stream
|
||||
int[] pixels = decode(fr, activeColTbl);
|
||||
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);
|
||||
renderLock.writeLock().lock();
|
||||
try {
|
||||
// Determine the color table that will be active for this frame
|
||||
final int[] activeColTbl = fr.hasLocColTbl ? fr.localColTbl : globalColTbl;
|
||||
|
||||
// Get pixels from data stream
|
||||
int[] pixels = decode(fr, activeColTbl);
|
||||
if (fr.interlaceFlag) {
|
||||
pixels = deinterlace(pixels, fr); // Rearrange pixel lines
|
||||
}
|
||||
|
||||
// Create image of type TYPE_INT_ARGB for frame area
|
||||
final BufferedImage frame = new BufferedImage(fr.w, fr.h, BufferedImage.TYPE_INT_ARGB);
|
||||
final int[] frameData = ((DataBufferInt) frame.getRaster().getDataBuffer()).getData();
|
||||
System.arraycopy(pixels, 0, frameData, 0, Math.min(pixels.length, fr.wh));
|
||||
|
||||
// Draw frame area on top of working image
|
||||
g.drawImage(frame, fr.x, fr.y, null);
|
||||
|
||||
// Visualize frame boundaries during testing
|
||||
// if (DEBUG_MODE) {
|
||||
// if (prev != null) {
|
||||
// g.setColor(Color.RED); // Previous frame color
|
||||
// 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
|
||||
final DecodingContext ctx = DECODING_CONTEXT.get();
|
||||
final int[] prevPixelBuffer = ctx.getPrevPixelBuffer(wh);
|
||||
final int[] imgData = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
|
||||
System.arraycopy(imgData, 0, prevPixelBuffer, 0, wh);
|
||||
|
||||
// Keep a copy of the previous frame's pixels in case we need to restore the
|
||||
// frame
|
||||
int[] prevPx = new int[wh];
|
||||
arraycopy(((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 0, prevPx, 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Create another copy for the end user to not expose internal state
|
||||
fr.img = new BufferedImage(w, h, 2); // 2 = ARGB
|
||||
arraycopy(prevPx, 0, ((DataBufferInt) fr.img.getRaster().getDataBuffer()).getData(), 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
|
||||
arraycopy(prevPx, 0, ((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 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
|
||||
*/
|
||||
public final int getBackgroundColor() {
|
||||
final GifFrame frame = frames.get(0);
|
||||
if (frame.hasLocColTbl) {
|
||||
return frame.localColTbl[bgColIndex];
|
||||
} else if (hasGlobColTbl) {
|
||||
return globalColTbl[bgColIndex];
|
||||
renderLock.readLock().lock();
|
||||
try {
|
||||
final GifFrame frame = frames.get(0);
|
||||
if (frame.hasLocColTbl) {
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public BufferedImage getFrame(final int index) {
|
||||
if (img == null) { // Init
|
||||
img = new BufferedImage(w, h, 2); // 2 = ARGB
|
||||
g = img.createGraphics();
|
||||
g.setBackground(new Color(0, true)); // Transparent color
|
||||
}
|
||||
GifFrame fr = frames.get(index);
|
||||
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);
|
||||
// Initialize rendering context if needed
|
||||
if (img == null) {
|
||||
synchronized (initLock) {
|
||||
if (img == null) { // Double-checked locking
|
||||
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
g = img.createGraphics();
|
||||
g.setBackground(new Color(0, true)); // Transparent color
|
||||
// Set rendering hints for better performance
|
||||
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
|
||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -407,6 +511,8 @@ public final class GifDecoder {
|
||||
img.globalColTbl = new int[img.sizeOfGlobColTbl];
|
||||
pos = readColTbl(in, img.globalColTbl, pos);
|
||||
}
|
||||
|
||||
// Main parsing loop with bounds checking optimization
|
||||
while (pos < in.length) {
|
||||
final int block = in[pos] & 0xFF;
|
||||
switch (block) {
|
||||
@@ -456,7 +562,7 @@ public final class GifDecoder {
|
||||
// and wait for a valid block. Experience: It'll get worse. b)
|
||||
// Throw exception. c) Return gracefully if we are almost done
|
||||
// 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) {
|
||||
throw new IOException("Unknown block at: " + pos);
|
||||
}
|
||||
@@ -489,6 +595,7 @@ public final class GifDecoder {
|
||||
* @return Index of the first byte after this extension
|
||||
*/
|
||||
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.appAuthCode = new String(in, i + 11, 3); // should be "2.0"
|
||||
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
|
||||
if (subBlockSize == 3) {
|
||||
// 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;
|
||||
} // Skip unknown application extensions
|
||||
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) {
|
||||
final int numColors = colors.length;
|
||||
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 g = in[i++] & 0xFF; // 2nd byte is green
|
||||
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;
|
||||
}
|
||||
@@ -530,9 +637,10 @@ public final class GifDecoder {
|
||||
* @return Index of the first byte after this block
|
||||
*/
|
||||
static int readGraphicControlExt(final GifFrame fr, final byte[] in, final int i) {
|
||||
fr.disposalMethod = (in[i + 3] & 0b00011100) >>> 2; // Bits 4-2
|
||||
fr.transpColFlag = (in[i + 3] & 1) == 1; // Bit 0
|
||||
fr.delay = in[i + 4] & 0xFF | (in[i + 5] & 0xFF) << 8; // 16 bit LSB
|
||||
final int packed = in[i + 3] & 0xFF;
|
||||
fr.disposalMethod = (packed & 0b00011100) >>> 2; // Bits 4-2
|
||||
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
|
||||
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
|
||||
throw new IOException("Image is truncated.");
|
||||
}
|
||||
// Use ASCII encoding for header - faster than default charset
|
||||
img.header = new String(in, 0, 6);
|
||||
if (!img.header.equals("GIF87a") && !img.header.equals("GIF89a")) {
|
||||
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.clearCode = clearCode;
|
||||
fr.endOfInfoCode = clearCode + 1;
|
||||
|
||||
// Pre-calculate image data size for better memory allocation
|
||||
final int imgDataSize = readImgDataSize(in, i);
|
||||
final byte[] imgData = new byte[imgDataSize + 2];
|
||||
int imgDataPos = 0;
|
||||
int subBlockSize = in[i] & 0xFF;
|
||||
|
||||
while (subBlockSize > 0) { // While block has data
|
||||
try { // Next line may throw exception if sub-block size is fake
|
||||
final int nextSubBlockSizePos = i + subBlockSize + 1;
|
||||
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
|
||||
i = nextSubBlockSizePos; // Move to next sub-block size
|
||||
subBlockSize = nextSubBlockSize;
|
||||
} catch (final Exception e) {
|
||||
// Sub-block exceeds file end, only use remaining bytes
|
||||
subBlockSize = fileSize - i - 1; // Remaining bytes
|
||||
arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
||||
imgDataPos += subBlockSize; // Move output data position
|
||||
if (subBlockSize > 0) {
|
||||
System.arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
||||
imgDataPos += subBlockSize; // Move output data position
|
||||
}
|
||||
i += subBlockSize + 1; // Move to next sub-block size
|
||||
break;
|
||||
}
|
||||
@@ -608,7 +722,9 @@ public final class GifDecoder {
|
||||
} catch (final Exception e) {
|
||||
// Sub-block exceeds file end, only use remaining bytes
|
||||
subBlockSize = fileSize - i - 1; // Remaining bytes
|
||||
imgDataPos += subBlockSize; // Move output data position
|
||||
if (subBlockSize > 0) {
|
||||
imgDataPos += subBlockSize; // Move output data position
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -622,16 +738,16 @@ public final class GifDecoder {
|
||||
* @return Index of the first byte after this block
|
||||
*/
|
||||
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.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.h = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 7-8: height
|
||||
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.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.wh = fr.w * fr.h;
|
||||
final byte b = in[++i]; // Byte 9 is a packed byte
|
||||
fr.hasLocColTbl = (b & 0b10000000) >>> 7 == 1; // Bit 7
|
||||
fr.interlaceFlag = (b & 0b01000000) >>> 6 == 1; // Bit 6
|
||||
fr.sortFlag = (b & 0b00100000) >>> 5 == 1; // Bit 5
|
||||
final int colTblSizePower = (b & 7) + 1; // Bits 2-0
|
||||
final int packed = in[++i] & 0xFF; // Byte 9 is a packed byte
|
||||
fr.hasLocColTbl = (packed & 0b10000000) != 0; // Bit 7
|
||||
fr.interlaceFlag = (packed & 0b01000000) != 0; // Bit 6
|
||||
fr.sortFlag = (packed & 0b00100000) != 0; // Bit 5
|
||||
final int colTblSizePower = (packed & 7) + 1; // Bits 2-0
|
||||
fr.sizeOfLocColTbl = 1 << colTblSizePower; // 2^(N+1), As per the spec
|
||||
return ++i;
|
||||
}
|
||||
@@ -642,15 +758,15 @@ public final class GifDecoder {
|
||||
* @return Index of the first byte after this block.
|
||||
*/
|
||||
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.h = in[i + 2] & 0xFF | (in[i + 3] & 0xFF) << 8; // 16 bit
|
||||
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.wh = img.w * img.h;
|
||||
final byte b = in[i + 4]; // Byte 4 is a packed byte
|
||||
img.hasGlobColTbl = (b & 0b10000000) >>> 7 == 1; // Bit 7
|
||||
final int colResPower = ((b & 0b01110000) >>> 4) + 1; // Bits 6-4
|
||||
final int packed = in[i + 4] & 0xFF; // Byte 4 is a packed byte
|
||||
img.hasGlobColTbl = (packed & 0b10000000) != 0; // Bit 7
|
||||
final int colResPower = ((packed & 0b01110000) >>> 4) + 1; // Bits 6-4
|
||||
img.colorResolution = 1 << colResPower; // 2^(N+1), As per the spec
|
||||
img.sortFlag = (b & 0b00001000) >>> 3 == 1; // Bit 3
|
||||
final int globColTblSizePower = (b & 7) + 1; // Bits 0-2
|
||||
img.sortFlag = (packed & 0b00001000) != 0; // Bit 3
|
||||
final int globColTblSizePower = (packed & 7) + 1; // Bits 0-2
|
||||
img.sizeOfGlobColTbl = 1 << globColTblSizePower; // 2^(N+1), see spec
|
||||
img.bgColIndex = in[i + 5] & 0xFF; // 1 Byte
|
||||
img.pxAspectRatio = in[i + 6] & 0xFF; // 1 Byte
|
||||
@@ -667,6 +783,7 @@ public final class GifDecoder {
|
||||
int subBlockSize = in[i++] & 0xFF;
|
||||
while (subBlockSize != 0 && i < in.length) {
|
||||
i += subBlockSize;
|
||||
if (i >= in.length) break; // Safety check
|
||||
subBlockSize = in[i++] & 0xFF;
|
||||
}
|
||||
return i;
|
||||
|
||||
+77
-7
@@ -4,20 +4,30 @@ import org.saturnclient.saturnclient.SaturnClient;
|
||||
import net.minecraft.client.texture.NativeImage;
|
||||
import net.minecraft.client.texture.NativeImageBackedTexture;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.awt.image.DataBufferInt;
|
||||
|
||||
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) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(bi, "png", baos);
|
||||
// This is the performance killer:
|
||||
// 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();
|
||||
ByteBuffer bb = BufferUtils.createByteBuffer(bytes.length).put(bytes);
|
||||
java.nio.ByteBuffer bb = org.lwjgl.BufferUtils.createByteBuffer(bytes.length).put(bytes);
|
||||
bb.flip();
|
||||
NativeImageBackedTexture nibt = new NativeImageBackedTexture(NativeImage.read(bb));
|
||||
SaturnClient.client.getTextureManager().registerTexture(i, nibt);
|
||||
@@ -25,4 +35,64 @@ public class IdentifierUtils {
|
||||
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 org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
||||
import org.saturnclient.saturnclient.auth.Auth;
|
||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
||||
import org.saturnclient.saturnclient.config.Config;
|
||||
import org.saturnclient.ui2.screens.EmoteWheel;
|
||||
import org.saturnclient.ui2.screens.ShiftMenu;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class KeyInputHandler {
|
||||
client.setScreen(new ShiftMenu());
|
||||
}
|
||||
|
||||
if (SaturnClientConfig.openEmoteWheel.wasKeyPressed() && client.currentScreen == null) {
|
||||
if (Config.openEmoteWheel.wasKeyPressed() && client.currentScreen == null && !InputUtil.isKeyPressed(client.getWindow().getHandle(), GLFW.GLFW_KEY_F3)) {
|
||||
client.setScreen(new EmoteWheel());
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class KeyInputHandler {
|
||||
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player);
|
||||
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
|
||||
animationStack.removeLayer(1000);
|
||||
Auth.emote("");
|
||||
ServiceClient.emote("");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.saturnclient.modules.HudMod;
|
||||
import org.saturnclient.modules.ModDimensions;
|
||||
import org.saturnclient.modules.ModManager;
|
||||
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.ui2.RenderScope;
|
||||
import org.saturnclient.ui2.SaturnScreen;
|
||||
@@ -26,7 +26,7 @@ public class HudEditor extends Screen {
|
||||
|
||||
public HudEditor() {
|
||||
super(Text.literal("HUD Editor"));
|
||||
for (Module m : ModManager.MODS) {
|
||||
for (Module m : ModManager.ENABLED_MODS) {
|
||||
if (m.isEnabled() && m instanceof HudMod) {
|
||||
hudMods.add((HudMod) m);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,11 @@ import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
||||
import org.saturnclient.saturnclient.auth.Auth;
|
||||
import org.saturnclient.saturnclient.client.player.Roles;
|
||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
@@ -37,12 +40,12 @@ public abstract class NameTagMixin<S extends EntityRenderState> {
|
||||
private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices,
|
||||
VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
|
||||
|
||||
String uuid = isSaturn(state);
|
||||
UUID uuid = isSaturn(state);
|
||||
|
||||
if (uuid != null) {
|
||||
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator())
|
||||
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid)));
|
||||
|
||||
MutableText iconText = Text.literal(Roles.getSaturnIndicator())
|
||||
.styled(style -> style.withColor(Roles.getIconColor(uuid)));
|
||||
|
||||
Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE));
|
||||
|
||||
renderLabel(state, iconText.append(nameText), matrices,
|
||||
@@ -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) {
|
||||
String name = ((PlayerEntityRenderState) state).name;
|
||||
String uuid = Auth.playerNames.get(name);
|
||||
if (Auth.players.containsKey(uuid)) {
|
||||
return uuid;
|
||||
SaturnPlayer player = SaturnPlayer.get(name);
|
||||
if (player != null) {
|
||||
return player.uuid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,6 @@ public abstract class PlayerEntityRendererMixin
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void addCustomFeatureRenderer(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) {
|
||||
this.addFeature(new HatFeatureRenderer(this));
|
||||
this.addFeature(new CloakFeatureRenderer(this, ctx.getEntityModels(), ctx.getEquipmentModelLoader()));
|
||||
this.addFeature(new CloakFeatureRenderer(this, ctx.getEquipmentModelLoader()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
import net.minecraft.client.network.PlayerListEntry;
|
||||
import net.minecraft.text.MutableText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
||||
import org.saturnclient.saturnclient.auth.Auth;
|
||||
import org.saturnclient.saturnclient.client.player.Roles;
|
||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
@@ -30,13 +31,13 @@ public abstract class TabListMixin {
|
||||
public Text getDisplayName() {
|
||||
GameProfile profile = ((PlayerListEntry) (Object) this).getProfile();
|
||||
String name = profile.getName();
|
||||
String uuid = profile.getId().toString().replaceAll("-", "");
|
||||
UUID uuid = profile.getId();
|
||||
|
||||
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator())
|
||||
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid)));
|
||||
|
||||
Text nameText = Text.literal(name).styled(style -> style.withColor(Formatting.WHITE));
|
||||
|
||||
return Auth.players.containsKey(uuid) ? iconText.append(nameText) : Text.literal(name);
|
||||
if (SaturnPlayer.get(uuid) != null) {
|
||||
return Text.literal(Roles.getSaturnIndicator())
|
||||
.styled(style -> style.withColor(Roles.getIconColor(uuid)))
|
||||
.append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)));
|
||||
}
|
||||
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;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.saturnclient.ui2.anim.Animation;
|
||||
import org.saturnclient.ui2.anim.Curve;
|
||||
|
||||
public class Element {
|
||||
public int x;
|
||||
@@ -14,7 +11,6 @@ public class Element {
|
||||
public boolean focused;
|
||||
public Animation animation;
|
||||
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 void render(RenderScope renderScope, ElementContext ctx) {
|
||||
@@ -32,6 +28,22 @@ public class Element {
|
||||
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) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
@@ -70,12 +82,6 @@ public class Element {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Element animation(Animation animation, Function<Double, Double> curve) {
|
||||
this.animation = animation;
|
||||
this.curve = curve;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Element scale(float scale) {
|
||||
this.scale = scale;
|
||||
return this;
|
||||
|
||||
@@ -4,13 +4,15 @@ public class ElementContext {
|
||||
public int mouseX, mouseY;
|
||||
public int elementWidth, elementHeight;
|
||||
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.mouseY = mouseY - e.y;
|
||||
this.elementWidth = e.width;
|
||||
this.elementHeight = e.height;
|
||||
this.elementScale = e.scale;
|
||||
this.elapsed = elapsed;
|
||||
}
|
||||
|
||||
public boolean isHovering() {
|
||||
|
||||
@@ -9,11 +9,17 @@ import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.saturnclient.ui2.resources.Fonts;
|
||||
import org.saturnclient.ui2.resources.SvgTexture;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
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.TextLayerType;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.ScreenRect;
|
||||
import net.minecraft.client.render.DiffuseLighting;
|
||||
import net.minecraft.client.render.OverlayTexture;
|
||||
@@ -42,6 +48,10 @@ public class RenderScope {
|
||||
private int opacity = 255 << 24;
|
||||
private final ItemRenderState itemRenderState;
|
||||
|
||||
public RenderScope(DrawContext context) {
|
||||
this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
|
||||
}
|
||||
|
||||
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
|
||||
this.matrices = matrices;
|
||||
this.vertexConsumers = vertexConsumers;
|
||||
@@ -58,13 +68,15 @@ public class RenderScope {
|
||||
int newAlpha = (opacity >>> 24) & 0xFF;
|
||||
int mixedAlpha = (originalAlpha * newAlpha) / 255;
|
||||
return (mixedAlpha << 24) | (color & 0x00FFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRenderLayer(Function<Identifier, RenderLayer> renderLayers) {
|
||||
// this.renderLayers = renderLayers;
|
||||
}
|
||||
|
||||
public void drawRect(int x, int y, int width, int height, int color) {
|
||||
if (color == 0)
|
||||
return;
|
||||
color = getColor(color);
|
||||
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) {
|
||||
if (color == 0)
|
||||
return;
|
||||
if (font == 0) {
|
||||
scale *= 2;
|
||||
}
|
||||
@@ -108,7 +122,8 @@ public class RenderScope {
|
||||
matrices.translate(x, y + (i * Fonts.getHeight()), 0);
|
||||
matrices.scale(scale, scale, 1.0f);
|
||||
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);
|
||||
matrices.pop();
|
||||
i++;
|
||||
@@ -118,19 +133,19 @@ public class RenderScope {
|
||||
private void drawRoundedCorner(int width, int height, int radius, int color) {
|
||||
int w = width * 20;
|
||||
int h = height * 20;
|
||||
|
||||
|
||||
for (int y = 0; y < h; y++) {
|
||||
int startX = 0;
|
||||
|
||||
|
||||
if (y < radius) {
|
||||
double dy = radius - y - 0.5;
|
||||
double dx = Math.sqrt(Math.max(0, radius * radius - dy * dy));
|
||||
startX = radius - (int) dx;
|
||||
}
|
||||
|
||||
|
||||
this.drawRect(startX, y, w - startX, 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawRoundedSide(int cornerWidth, int cornerHeight, int radius, int color) {
|
||||
// Top
|
||||
@@ -159,6 +174,8 @@ public class RenderScope {
|
||||
}
|
||||
|
||||
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));
|
||||
int cornerWidth = width / 2;
|
||||
int cornerHeight = height / 2;
|
||||
@@ -183,27 +200,40 @@ public class RenderScope {
|
||||
public int getScaledWindowWidth() {
|
||||
return SaturnClient.client.getWindow().getScaledWidth();
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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 regionWith,
|
||||
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) {
|
||||
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);
|
||||
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) {
|
||||
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;
|
||||
x2 *= 4;
|
||||
y1 *= 4;
|
||||
@@ -213,44 +243,45 @@ public class RenderScope {
|
||||
matrices.push();
|
||||
matrices.scale(0.25f, 0.25f, 1.0f);
|
||||
RenderSystem.setShaderTexture(0, sprite);
|
||||
RenderLayer renderLayer = (RenderLayer)RenderLayer.getGuiTextured(sprite);
|
||||
RenderLayer renderLayer = (RenderLayer) RenderLayer.getGuiTextured(sprite);
|
||||
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
||||
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)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)y1, 0.0F).texture(u2, 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) x2, (float) y2, 0.0F).texture(u2, v2).color(color);
|
||||
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, 0.0F).texture(u2, v1).color(color);
|
||||
matrices.pop();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
public void disableScissor() {
|
||||
this.setScissor(this.scissorStack.pop());
|
||||
}
|
||||
|
||||
|
||||
public boolean scissorContains(int x, int y) {
|
||||
return this.scissorStack.containsPoint(x, y);
|
||||
}
|
||||
|
||||
|
||||
private void setScissor(@Nullable ScreenRect rect) {
|
||||
this.draw();
|
||||
if (rect != null) {
|
||||
Window window = SaturnClient.client.getWindow();
|
||||
int i = window.getFramebufferHeight();
|
||||
double d = window.getScaleFactor();
|
||||
double e = (double)rect.getLeft() * d;
|
||||
double f = (double)i - (double)rect.getBottom() * d;
|
||||
double g = (double)rect.width() * d;
|
||||
double h = (double)rect.height() * d;
|
||||
RenderSystem.enableScissor((int)e, (int)f, Math.max(0, (int)g), Math.max(0, (int)h));
|
||||
Window window = SaturnClient.client.getWindow();
|
||||
int i = window.getFramebufferHeight();
|
||||
double d = window.getScaleFactor();
|
||||
double e = (double) rect.getLeft() * d;
|
||||
double f = (double) i - (double) rect.getBottom() * d;
|
||||
double g = (double) rect.width() * d;
|
||||
double h = (double) rect.height() * d;
|
||||
RenderSystem.enableScissor((int) e, (int) f, Math.max(0, (int) g), Math.max(0, (int) h));
|
||||
} else {
|
||||
RenderSystem.disableScissor();
|
||||
RenderSystem.disableScissor();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
@@ -260,7 +291,7 @@ public class RenderScope {
|
||||
public void draw(Consumer<VertexConsumerProvider> drawer) {
|
||||
drawer.accept(this.vertexConsumers);
|
||||
this.vertexConsumers.draw();
|
||||
}
|
||||
}
|
||||
|
||||
static class ScissorStack {
|
||||
private final Deque<ScreenRect> stack = new ArrayDeque<>();
|
||||
@@ -268,7 +299,8 @@ public class RenderScope {
|
||||
public ScreenRect push(ScreenRect p_281812_) {
|
||||
ScreenRect screenrectangle = this.stack.peekLast();
|
||||
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);
|
||||
return screenrectangle1;
|
||||
} else {
|
||||
@@ -309,47 +341,51 @@ public class RenderScope {
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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()) {
|
||||
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.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 {
|
||||
this.matrices.scale(16.0F, -16.0F, 16.0F);
|
||||
boolean bl = !this.itemRenderState.isSideLit();
|
||||
if (bl) {
|
||||
this.draw();
|
||||
DiffuseLighting.disableGuiDepthLighting();
|
||||
this.draw();
|
||||
DiffuseLighting.disableGuiDepthLighting();
|
||||
}
|
||||
|
||||
this.itemRenderState.render(this.matrices, this.vertexConsumers, 15728880, OverlayTexture.DEFAULT_UV);
|
||||
this.draw();
|
||||
if (bl) {
|
||||
DiffuseLighting.enableGuiDepthLighting();
|
||||
DiffuseLighting.enableGuiDepthLighting();
|
||||
}
|
||||
} catch (Throwable var11) {
|
||||
CrashReport crashReport = CrashReport.create(var11, "Rendering item");
|
||||
CrashReportSection crashReportSection = crashReport.addElement("Item being rendered");
|
||||
crashReportSection.add("Item Type", () -> {
|
||||
return String.valueOf(stack.getItem());
|
||||
return String.valueOf(stack.getItem());
|
||||
});
|
||||
crashReportSection.add("Item Components", () -> {
|
||||
return String.valueOf(stack.getComponents());
|
||||
return String.valueOf(stack.getComponents());
|
||||
});
|
||||
crashReportSection.add("Item Foil", () -> {
|
||||
return String.valueOf(stack.hasGlint());
|
||||
return String.valueOf(stack.hasGlint());
|
||||
});
|
||||
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) {
|
||||
if (color == 0)
|
||||
return;
|
||||
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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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 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.screen.Screen;
|
||||
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.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
@@ -22,12 +24,13 @@ import net.minecraft.util.Identifier;
|
||||
public abstract class SaturnScreen extends Screen {
|
||||
private final Pool pool = new Pool(3);
|
||||
protected List<Element> elements = new ArrayList<>();
|
||||
public int blurDuration = 700;
|
||||
public float blurProgress = 0.0f;
|
||||
protected float backgroundOpacity = 1.0f;
|
||||
protected Instant start = null;
|
||||
public int backgroundBlur = 10;
|
||||
|
||||
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(
|
||||
Identifier.of("saturnclient", "textures/gui/title/background/panorama"));
|
||||
protected static final Identifier PANORAMA = Identifier.of("saturnclient",
|
||||
"textures/gui/title/background/panorama");
|
||||
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(PANORAMA);
|
||||
|
||||
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
|
||||
|
||||
@@ -35,41 +38,24 @@ public abstract class SaturnScreen extends Screen {
|
||||
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) {
|
||||
super(Text.literal(title));
|
||||
}
|
||||
|
||||
public void draw(Element element) {
|
||||
synchronized (elements) {
|
||||
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();
|
||||
}
|
||||
ElementRenderer.draw(elements, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,10 +64,6 @@ public abstract class SaturnScreen extends Screen {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
ui(); // abstraction to render the saturn ui and also render extra stuff here
|
||||
|
||||
Animation.execute((Float progress) -> {
|
||||
blurProgress = progress;
|
||||
}, blurDuration);
|
||||
}
|
||||
|
||||
public abstract void ui();
|
||||
@@ -89,20 +71,26 @@ public abstract class SaturnScreen extends Screen {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
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;
|
||||
mouseY *= 2;
|
||||
|
||||
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(
|
||||
Identifier.ofVanilla("blur"),
|
||||
DefaultFramebufferSet.MAIN_ONLY);
|
||||
if (postEffectProcessor != null) {
|
||||
postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress);
|
||||
postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
|
||||
}
|
||||
Identifier.ofVanilla("blur"),
|
||||
DefaultFramebufferSet.MAIN_ONLY);
|
||||
|
||||
if (postEffectProcessor != null) {
|
||||
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
|
||||
// use DrawContext
|
||||
@@ -113,7 +101,7 @@ public abstract class SaturnScreen extends Screen {
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -128,6 +116,26 @@ public abstract class SaturnScreen extends Screen {
|
||||
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
|
||||
public boolean mouseScrolled(
|
||||
double mouseX,
|
||||
|
||||
@@ -4,13 +4,15 @@ import java.util.List;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.ui2.elements.Notification;
|
||||
import org.saturnclient.ui2.elements.Notification.NotificationKind;
|
||||
|
||||
public class Utils {
|
||||
public static boolean isHovering(int mouseX, int mouseY, int elementWidth, int elementHeight, float elementScale) {
|
||||
return mouseX >= 0 &&
|
||||
mouseX <= (elementWidth * elementScale) &&
|
||||
mouseY >= 0 &&
|
||||
mouseY <= (elementHeight * elementScale);
|
||||
mouseX <= (elementWidth * elementScale) &&
|
||||
mouseY >= 0 &&
|
||||
mouseY <= (elementHeight * elementScale);
|
||||
}
|
||||
|
||||
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) {
|
||||
SaturnClient.LOGGER.info("Accessing index {} in list of size {}", index, list.size());
|
||||
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;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui2.Element;
|
||||
|
||||
public abstract class Animation {
|
||||
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) {
|
||||
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 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
double c1 = 1.70158;
|
||||
double c1 = 2.5;
|
||||
double c2 = c1 * 1.525;
|
||||
|
||||
if (x < 0.5) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.saturnclient.ui2.anim;
|
||||
|
||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui2.Element;
|
||||
|
||||
public class Fade extends Animation {
|
||||
@@ -9,14 +10,18 @@ public class Fade extends Animation {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public Fade(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(float progress, Element element) {
|
||||
element.opacity = targetOpacity * progress;
|
||||
public void tick(double progress, Element element) {
|
||||
element.opacity = Math.max(0.1f, targetOpacity * (float) progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Element element) {
|
||||
targetOpacity = element.opacity;
|
||||
element.opacity = 0;
|
||||
element.opacity = 0.1f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.saturnclient.ui2.anim;
|
||||
|
||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui2.Element;
|
||||
|
||||
public class FadeOut extends Animation {
|
||||
@@ -9,9 +10,13 @@ public class FadeOut extends Animation {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public FadeOut(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(float progress, Element element) {
|
||||
element.opacity = targetOpacity - progress;
|
||||
public void tick(double progress, Element element) {
|
||||
element.opacity = targetOpacity - (float) progress;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui2.Element;
|
||||
|
||||
public class SlideX extends Animation {
|
||||
int offset = 20;
|
||||
int maxOffset = 20;
|
||||
int elementX;
|
||||
double startX;
|
||||
|
||||
public SlideX(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public SlideX(AnimationConfig config, int offset) {
|
||||
super(config);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public SlideX(int duration) {
|
||||
super(duration);
|
||||
@@ -14,23 +23,17 @@ public class SlideX extends Animation {
|
||||
public SlideX(int duration, int offset) {
|
||||
super(duration);
|
||||
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
|
||||
public void init(Element element) {
|
||||
element.x -= offset;
|
||||
elementX = element.x;
|
||||
startX = element.x - offset;
|
||||
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;
|
||||
|
||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui2.Element;
|
||||
|
||||
public class SlideY extends Animation {
|
||||
int offset = 20;
|
||||
int maxOffset = 20;
|
||||
int elementY;
|
||||
double startY;
|
||||
|
||||
public SlideY(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public SlideY(AnimationConfig config, int offset) {
|
||||
super(config);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public SlideY(int duration) {
|
||||
super(duration);
|
||||
@@ -13,26 +22,18 @@ public class SlideY extends Animation {
|
||||
|
||||
public SlideY(int duration, int offset) {
|
||||
super(duration);
|
||||
|
||||
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
|
||||
public void init(Element element) {
|
||||
element.y -= offset;
|
||||
elementY = element.y;
|
||||
startY = element.y - offset;
|
||||
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;
|
||||
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
@@ -10,13 +9,7 @@ import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public class CosmeticPreview extends Element {
|
||||
private static ThemeManager theme = new ThemeManager("CosmeticPreview", "selected", "hovering");
|
||||
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 {}
|
||||
static int padding = 14;
|
||||
|
||||
Identifier sprite;
|
||||
private Runnable onClick;
|
||||
@@ -30,25 +23,19 @@ public class CosmeticPreview extends Element {
|
||||
this.width = 50;
|
||||
this.height = 111;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
if (ctx.isHovering()) {
|
||||
theme.setState("hovering");
|
||||
} else if (isSelected) {
|
||||
theme.setState("selected");
|
||||
}
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
|
||||
|
||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||
|
||||
int texWidth = width - padding.value;
|
||||
int texHeight = height - padding.value;
|
||||
|
||||
renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value);
|
||||
int texWidth = width - padding;
|
||||
int texHeight = height - padding;
|
||||
|
||||
theme.setState(null);
|
||||
renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
|
||||
Theme.FOREGROUND.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,30 +7,27 @@ import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
import org.saturnclient.ui2.Utils;
|
||||
import org.saturnclient.ui2.anim.Animation;
|
||||
|
||||
public class ElementRenderer {
|
||||
public static void draw(List<Element> elements, Element element) {
|
||||
synchronized (elements) {
|
||||
elements.add(element);
|
||||
}
|
||||
|
||||
|
||||
if (element.animation != null) {
|
||||
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) {
|
||||
element.playAnimationFrame(elapsed);
|
||||
|
||||
renderScope.matrices.push();
|
||||
renderScope.setOpacity(element.opacity);
|
||||
renderScope.matrices.translate(element.x, element.y, 0);
|
||||
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.setRenderLayer(null);
|
||||
}
|
||||
@@ -44,7 +41,10 @@ public class ElementRenderer {
|
||||
int adjustedMouseX = (int) mouseX - element.x;
|
||||
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.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale));
|
||||
}
|
||||
@@ -58,16 +58,17 @@ public class ElementRenderer {
|
||||
double mouseY,
|
||||
double horizontalAmount,
|
||||
double verticalAmount) {
|
||||
for (Element element : new ArrayList<>(elements)) {
|
||||
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, element.scale)) {
|
||||
element.scroll(
|
||||
(int) mouseX - element.x,
|
||||
(int) mouseY - element.y,
|
||||
horizontalAmount,
|
||||
verticalAmount);
|
||||
}
|
||||
}
|
||||
for (Element element : new ArrayList<>(elements)) {
|
||||
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height,
|
||||
element.scale)) {
|
||||
element.scroll(
|
||||
(int) mouseX - element.x,
|
||||
(int) mouseY - element.y,
|
||||
horizontalAmount,
|
||||
verticalAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void keyPressed(List<Element> elements, int keyCode, int scanCode, int modifiers) {
|
||||
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.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui2.resources.Fonts;
|
||||
import org.saturnclient.ui2.resources.Textures;
|
||||
import org.saturnclient.ui2.Element;
|
||||
@@ -15,23 +14,12 @@ import org.saturnclient.ui2.screens.ConfigEditor;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
|
||||
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 {
|
||||
theme.propertyStateDefault("enabled", "bg", 0xFF1d202d);
|
||||
theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee);
|
||||
theme.propertyStateDefault("hovering", "fg", 0xFF845eee);
|
||||
}
|
||||
private final Module mod;
|
||||
|
||||
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) {
|
||||
this.mod = mod;
|
||||
@@ -41,60 +29,137 @@ public class SaturnModule extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
int p = 10;
|
||||
int s = 14;
|
||||
boolean settingsHover = false;
|
||||
boolean hovered = ctx.isHovering();
|
||||
boolean enabled = mod.isEnabled();
|
||||
|
||||
int iconX = width - s - p;
|
||||
int iconY = height - s - p;
|
||||
|
||||
boolean settingsHover = ctx.isHovering(
|
||||
getSettingsX(),
|
||||
getSettingsY(),
|
||||
getSettingsHitBox(),
|
||||
getSettingsHitBox());
|
||||
|
||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
||||
|
||||
if (mod.isEnabled()) {
|
||||
theme.setState("enabled");
|
||||
}
|
||||
// Resolve theme colors
|
||||
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)) {
|
||||
settingsHover = true;
|
||||
} else if (ctx.isHovering()) {
|
||||
theme.applyState("hovering");
|
||||
}
|
||||
int iconBg = enabled ? Theme.ACCENT.value : Theme.PRIMARY.value;
|
||||
int iconFg = enabled ? Theme.ACCENT_FG.value : fgColor;
|
||||
|
||||
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;
|
||||
for (String tag : mod.getTags()) {
|
||||
int xtb = (int) (Fonts.getWidth(tag, 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);
|
||||
xt += xtb + 9;
|
||||
int tagWidth = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f);
|
||||
|
||||
renderScope.drawRoundedRectangle(
|
||||
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) {
|
||||
theme.setState("hovering");
|
||||
}
|
||||
|
||||
renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, fgColor.value);
|
||||
|
||||
theme.setState(null);
|
||||
// Settings icon
|
||||
renderScope.drawTexture(
|
||||
Textures.SETTINGS,
|
||||
iconX,
|
||||
iconY,
|
||||
0, 0,
|
||||
s, s,
|
||||
settingsColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
int p = 10;
|
||||
int s = 14;
|
||||
boolean settingsHover = Utils.isHovering(
|
||||
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()));
|
||||
} else {
|
||||
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;
|
||||
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.SaturnClientConfig;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Config;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui2.resources.Textures;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
@@ -18,19 +17,6 @@ import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
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 {
|
||||
Runnable onClick;
|
||||
boolean end;
|
||||
@@ -45,11 +31,11 @@ public class Sidebar extends Element {
|
||||
|
||||
Runnable onClose;
|
||||
SidebarComponent[] components = {
|
||||
new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> {
|
||||
new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> {
|
||||
SaturnClient.client.setScreen(new ModMenu());
|
||||
}, false),
|
||||
new Sidebar.SidebarComponent(Textures.SETTINGS, () -> {
|
||||
SaturnClient.client.setScreen(new ConfigEditor(SaturnClientConfig.config));
|
||||
SaturnClient.client.setScreen(new ConfigEditor(Config.config));
|
||||
}, false),
|
||||
|
||||
new Sidebar.SidebarComponent(Textures.SHIRT, () -> {
|
||||
@@ -79,36 +65,29 @@ public class Sidebar extends Element {
|
||||
int bottomRow = 0;
|
||||
|
||||
int gap = 5; // 5px gap between icons
|
||||
int sp = 30 - padding.value + gap;
|
||||
int s = 30 - (padding.value * 2);
|
||||
int s2 = s + padding.value;
|
||||
int padding = 10;
|
||||
int sp = 30 - padding + gap;
|
||||
int s = 30 - (padding * 2);
|
||||
int s2 = s + padding;
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -121,25 +100,24 @@ public class Sidebar extends Element {
|
||||
int topRow = 0;
|
||||
int bottomRow = 0;
|
||||
|
||||
int gap = 5; // same 5px gap here
|
||||
int sp = 30 - padding.value + gap;
|
||||
int s = 30 - (padding.value * 2);
|
||||
int s2 = s + padding.value;
|
||||
int gap = 5;
|
||||
int padding = 10;
|
||||
int sp = 30 - padding + gap;
|
||||
int s = 30 - (padding * 2);
|
||||
int s2 = s + padding;
|
||||
|
||||
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) {
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.resources.Fonts;
|
||||
|
||||
public class FloatInput extends Input {
|
||||
|
||||
@@ -1,32 +1,183 @@
|
||||
package org.saturnclient.ui2.components.inputs;
|
||||
|
||||
import org.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import java.awt.Color;
|
||||
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
import org.saturnclient.ui2.SaturnScreen;
|
||||
import org.saturnclient.ui2.components.dialog.ColorDialog;
|
||||
|
||||
public class HexInput extends Element {
|
||||
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.height = 20;
|
||||
this.prop = prop;
|
||||
this.scale = 0.8f;
|
||||
this.screen = screen;
|
||||
syncFromColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
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.saturnclient.saturnclient.SaturnClient;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
import org.saturnclient.ui2.resources.Fonts;
|
||||
|
||||
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 = "";
|
||||
public int cursorPosition = 0;
|
||||
|
||||
public abstract void checkReset();
|
||||
|
||||
public abstract void backspace();
|
||||
|
||||
public boolean validateBackspace() {
|
||||
return true;
|
||||
}
|
||||
@@ -71,11 +70,12 @@ public abstract class Input extends Element {
|
||||
int scrollOffset = getScrollOffset();
|
||||
String visibleText = getVisibleText(scrollOffset);
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
@@ -90,7 +90,7 @@ public abstract class Input extends Element {
|
||||
int maxWidth = width - 4; // Available width for text
|
||||
while (offset < cursorPosition) { // Ensure cursor remains visible
|
||||
String visibleText = text.substring(offset, cursorPosition);
|
||||
if (Fonts.getWidth(visibleText, font.value) > maxWidth) {
|
||||
if (Fonts.getWidth(visibleText, Theme.FONT.value) > maxWidth) {
|
||||
offset++;
|
||||
} else {
|
||||
break;
|
||||
@@ -104,7 +104,7 @@ public abstract class Input extends Element {
|
||||
|
||||
// Ensure text does not overflow past the available width
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.saturnclient.ui2.components.inputs;
|
||||
|
||||
import org.saturnclient.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.resources.Fonts;
|
||||
|
||||
public class IntInput extends Input {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package org.saturnclient.ui2.components.inputs;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
import org.saturnclient.ui2.resources.Fonts;
|
||||
|
||||
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;
|
||||
|
||||
public KeybindingSelector(Property<Integer> prop) {
|
||||
@@ -23,9 +21,12 @@ public class KeybindingSelector extends Element {
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
|
||||
renderScope.drawText(0.6f, prop.value == -1 ? "<NONE>" :
|
||||
GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)).toUpperCase(),
|
||||
4, 4, font.value, textColor);
|
||||
String name = GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value));
|
||||
|
||||
renderScope.drawText(0.6f,
|
||||
prop.value == -1 ? "<NONE>"
|
||||
: name != null ? name.toUpperCase() : "<Invalid-Key>",
|
||||
4, 4, Theme.FONT.value, textColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.saturnclient.ui2.components.inputs;
|
||||
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
@@ -10,11 +10,6 @@ import org.saturnclient.ui2.resources.Fonts;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
|
||||
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;
|
||||
|
||||
public Select(Property<Integer> prop) {
|
||||
@@ -27,15 +22,14 @@ public class Select extends Element {
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
theme.setState(null);
|
||||
renderScope.drawText(0.8f, ">", width - 10, 0, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
package org.saturnclient.ui2.components.inputs;
|
||||
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
|
||||
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;
|
||||
|
||||
public Toggle(Property<Boolean> prop) {
|
||||
@@ -27,19 +17,10 @@ public class Toggle extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
theme.setState(null);
|
||||
|
||||
if (prop.value) {
|
||||
theme.setState("enabled");
|
||||
}
|
||||
|
||||
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);
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, height,
|
||||
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);
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
import org.saturnclient.ui2.resources.Fonts;
|
||||
|
||||
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 Runnable onClick;
|
||||
|
||||
public Button(String text, Runnable onClick) {
|
||||
this.text = text;
|
||||
this.onClick = onClick;
|
||||
this.dimensions(Fonts.getWidth(text, font.value) + 50, 38);
|
||||
this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
if (ctx.isHovering()) {
|
||||
theme.setState("hovering");
|
||||
} else {
|
||||
theme.setState(null);
|
||||
}
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.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, font.value), Fonts.centerY(height), font.value, fgColor.value);
|
||||
renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height),
|
||||
Theme.FONT.value,
|
||||
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.saturnclient.ui2.elements;
|
||||
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
@@ -10,9 +9,6 @@ import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
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;
|
||||
private Runnable onClick;
|
||||
|
||||
@@ -20,17 +16,12 @@ public class ClickableTexture extends Element {
|
||||
this.sprite = sprite;
|
||||
this.onClick = onClick;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
if (ctx.isHovering()) {
|
||||
theme.setState("hovering");
|
||||
} else {
|
||||
theme.setState(null);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -3,8 +3,9 @@ package org.saturnclient.ui2.elements;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
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;
|
||||
|
||||
public class ImageTexture extends Element {
|
||||
@@ -16,7 +17,16 @@ public class ImageTexture extends Element {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.saturnclient.ui2.elements;
|
||||
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
@@ -10,11 +10,9 @@ import org.saturnclient.ui2.resources.Fonts;
|
||||
import org.saturnclient.ui2.resources.Textures;
|
||||
|
||||
public class Notification extends Element {
|
||||
private static ThemeManager theme = new ThemeManager("Notification");
|
||||
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000));
|
||||
private static Property<Integer> error = theme.property("error", Property.color(0xFFbf212f));
|
||||
private static Property<Integer> info = theme.property("info", Property.color(0xFF264b96));
|
||||
private static Property<Integer> success = theme.property("success", Property.color(0xFF27b376));
|
||||
private static Property<Integer> error = Property.color(0xFFbf212f);
|
||||
private static Property<Integer> info = Property.color(0xFF264b96);
|
||||
private static Property<Integer> success = Property.color(0xFF27b376);
|
||||
|
||||
public static enum NotificationKind {
|
||||
Error,
|
||||
@@ -40,7 +38,7 @@ public class Notification extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, bgColor.value);
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, Theme.PRIMARY.value);
|
||||
switch (kind) {
|
||||
case Error:
|
||||
renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value);
|
||||
|
||||
@@ -3,23 +3,18 @@ package org.saturnclient.ui2.elements;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.saturnclient.config.Property;
|
||||
import org.saturnclient.saturnclient.config.ThemeManager;
|
||||
import org.saturnclient.saturnclient.config.Theme;
|
||||
import org.saturnclient.saturnclient.config.manager.Property;
|
||||
import org.saturnclient.ui2.Element;
|
||||
import org.saturnclient.ui2.ElementContext;
|
||||
import org.saturnclient.ui2.RenderScope;
|
||||
import org.saturnclient.ui2.components.ElementRenderer;
|
||||
|
||||
public class Scroll extends Element {
|
||||
private static ThemeManager theme = new ThemeManager("Scroll");
|
||||
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
|
||||
private static Property<Integer> scrollBarColor = theme.property("scrollbar-color", Property.color(-7643914));
|
||||
private static Property<Integer> scrollBarRadius = Property.integer(10);
|
||||
private static Property<Integer> scrollBarWidth = Property.integer(5);
|
||||
private static Property<Integer> scrollBarPadding = Property.integer(5);
|
||||
|
||||
private static Property<Integer> scrollBarRadius = theme.property("scrollbar-radius", Property.integer(10));
|
||||
private static Property<Integer> scrollBarWidth = theme.property("scrollbar-width", Property.integer(5));
|
||||
private static Property<Integer> scrollBarPadding = theme.property("scrollbar-padding", Property.integer(5));
|
||||
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
|
||||
|
||||
int padding = 0;
|
||||
|
||||
protected List<Element> children = new ArrayList<>();
|
||||
@@ -30,18 +25,37 @@ public class Scroll extends Element {
|
||||
this.padding = padding;
|
||||
}
|
||||
|
||||
public void draw(Element element) {
|
||||
ElementRenderer.draw(children, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
|
||||
calculateMaxScroll();
|
||||
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height,
|
||||
Theme.BG_RADIUS.value, Theme.BACKGROUND.value);
|
||||
|
||||
renderScope.enableScissor(padding, padding, width - padding, height - padding);
|
||||
renderScope.matrices.push();
|
||||
renderScope.matrices.translate(padding, (-scroll) + padding, 0);
|
||||
ElementRenderer.render(children, renderScope, ctx.mouseX - padding, ctx.mouseY - padding + scroll);
|
||||
renderScope.matrices.translate(padding, -scroll + padding, 0);
|
||||
|
||||
ElementRenderer.render(children, ctx.elapsed, renderScope,
|
||||
ctx.mouseX - padding,
|
||||
ctx.mouseY - padding + scroll);
|
||||
|
||||
renderScope.matrices.pop();
|
||||
renderScope.disableScissor();
|
||||
|
||||
renderScope.drawRoundedRectangle(width - scrollBarWidth.value - scrollBarPadding.value, calculateScrollBarY(), scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, scrollBarColor.value);
|
||||
if (maxScroll > 0) {
|
||||
renderScope.drawRoundedRectangle(
|
||||
width - scrollBarWidth.value - scrollBarPadding.value,
|
||||
calculateScrollBarY(),
|
||||
scrollBarWidth.value,
|
||||
calculateScrollBarHeight(),
|
||||
scrollBarRadius.value,
|
||||
Theme.SCROLL.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,36 +68,63 @@ public class Scroll extends Element {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, 0);
|
||||
}
|
||||
|
||||
public void draw(Element element) {
|
||||
children.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element dimensions(int width, int height) {
|
||||
super.dimensions(width, height);
|
||||
|
||||
maxScroll = 0;
|
||||
for (Element element : children) {
|
||||
maxScroll = Math.max(maxScroll, element.y + element.height - height);
|
||||
maxScroll = Math.max(maxScroll, (element.y + element.height + padding) - (height - padding * 2));
|
||||
}
|
||||
return super.dimensions(width, height);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
int calculateScrollBarHeight() {
|
||||
if (maxScroll <= 0) return height - (scrollBarPadding.value * 2); // No scrolling needed
|
||||
return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); // At least 20px tall
|
||||
if (maxScroll <= 0)
|
||||
return height - (scrollBarPadding.value * 2);
|
||||
return Math.max(20, (height * height) / (height + maxScroll + 10)) - (scrollBarPadding.value * 2);
|
||||
}
|
||||
|
||||
int calculateScrollBarY() {
|
||||
int scrollBarHeight = calculateScrollBarHeight();
|
||||
if (maxScroll <= 0) return scrollBarPadding.value;
|
||||
return (scroll * (height - scrollBarHeight)) / maxScroll + scrollBarPadding.value;
|
||||
int y = (scroll * (height - scrollBarHeight - scrollBarPadding.value * 2)) / maxScroll + scrollBarPadding.value;
|
||||
return Math.min(y, height - scrollBarHeight - scrollBarPadding.value);
|
||||
}
|
||||
|
||||
private void calculateMaxScroll() {
|
||||
maxScroll = 0;
|
||||
|
||||
for (Element element : children) {
|
||||
int bottom = element.y + element.height;
|
||||
maxScroll = Math.max(maxScroll, bottom - height + padding);
|
||||
}
|
||||
|
||||
if (maxScroll > 0) {
|
||||
maxScroll = maxScroll + 10;
|
||||
} else {
|
||||
maxScroll = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(int keyCode, int scanCode, int modifiers) {
|
||||
ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||
ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
ElementRenderer.mouseDragged(children, mouseX - padding, mouseY - padding + scroll, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||
ElementRenderer.mouseReleased(children, mouseX - padding, mouseY - padding + scroll, button);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user