Compare commits

..
148 Commits
Author SHA1 Message Date
selimaj-dev 23e508138b Screenshot and fixed small animation duration 2026-02-25 18:01:47 +01:00
selimaj-dev 593b9f4760 Merge pull request #30 from saturnclientmc/cloak-physics
Cloak physics #14
2026-02-25 08:51:23 -08:00
selimaj-dev 5b44caa358 Falling physics 2026-02-25 17:47:49 +01:00
selimaj-dev 28d71e63a9 Flat cloak 2026-02-25 17:35:50 +01:00
selimaj-dev 84addc7996 Fixed gaps in curve 2026-02-25 17:11:38 +01:00
selimaj-dev 3f8d68619a Working simulation 2026-02-25 16:52:12 +01:00
selimaj-dev d941334fa3 Fixed gap 2026-02-25 16:34:06 +01:00
selimaj-dev 9c36d33cb2 Working ish system 2026-02-25 16:24:45 +01:00
selimaj-dev 6b03a1441a Fixing the rendering anchoring 2026-02-25 16:06:10 +01:00
selimaj-dev f40d3026e6 Fixed reverse rendering and position 2026-02-25 14:41:22 +01:00
selimaj-dev 2ea122c4a9 Working full cape rendering 2026-02-25 14:17:57 +01:00
selimaj-dev dfce271c37 Cape segment specific rendering 2026-02-25 13:52:07 +01:00
Rudi d40724205c Merge pull request #29 from RudideC/master
bugfixes, changes to animation defaults
2026-02-25 11:05:58 +01:00
RudideC 55afd1e436 changed default font
i think this looks better. thoughts?
2026-02-25 11:03:03 +01:00
RudideC eb3e3d61db changed default animation timings
should be snappier now
2026-02-25 11:02:40 +01:00
RudideC 5da0e3c428 added myself as contributor 2026-02-25 11:02:21 +01:00
RudideC f464d10971 fixed libraries not included 2026-02-25 11:02:08 +01:00
RudideC 6e0bc22c17 bugfix, fixed mod not loading
remap = false makes it so that it works when developing, but not when compiled
2026-02-25 11:01:57 +01:00
selimaj-dev bb95999112 Implementing cape curving 2026-02-25 04:34:57 +01:00
selimaj-dev 9c0f92d034 Fixed title screen 2026-02-25 03:47:05 +01:00
selimaj-dev 9d04958ff2 Merge pull request #28 from saturnclientmc/animation-settings
Animation settings
2026-02-24 18:36:53 -08:00
selimaj-dev cfe0d4a97f Emote wheel and cosmetics animations 2026-02-25 02:07:30 +01:00
selimaj-dev 0995484c09 Emote wheel animation config 2026-02-25 01:28:52 +01:00
selimaj-dev ec2254cf74 Implemented curves for all animations 2026-02-25 01:18:00 +01:00
selimaj-dev 71ae403188 Implementing AnimationConfig directly 2026-02-25 00:54:35 +01:00
selimaj-dev 6689919b3b Fixes and implemented animation curves 2026-02-25 00:39:48 +01:00
selimaj-dev 246e49e521 Animation fix 2026-02-25 00:25:13 +01:00
selimaj-dev eb065b20d7 Animation config 2026-02-25 00:18:16 +01:00
Rudi 038c859133 Merge pull request #27 from RudideC/master
0.0.7-beta1
2026-02-24 21:41:47 +01:00
RudideC 434afa7f62 0.0.7-beta1
all code integrated, beta release is ready
2026-02-24 21:38:40 +01:00
RudideC fd492b91a0 animation fine-tuning, easing change
can change between ease-in-out and ease-out (default)
2026-02-24 21:34:56 +01:00
RudideC 48a41af3b0 changed timings
personal preference, i think these should be a setting
2026-02-24 21:27:46 +01:00
RudideC 6da9eb55ba fixed bug, where modmenu wouldn't have scroll 2026-02-24 21:27:23 +01:00
RudideC 22efd8b6e0 moved to svg
yay
2026-02-24 16:23:34 +01:00
Rudi b71d779900 Merge pull request #25 from RudideC/master
Nametags, Ping, Clock, Tps
2026-02-24 10:05:52 +01:00
Rudi 6c3dbd90a0 Merge branch 'master' into master 2026-02-24 10:05:15 +01:00
RudideC 1b16098121 0.0.7-alpha1
will do some testing and then move towards the beta release
2026-02-24 09:41:50 +01:00
RudideC 092fcfb009 new icons
wow
2026-02-24 09:40:06 +01:00
RudideC 008a0927b6 clock mod 2026-02-24 09:37:37 +01:00
RudideC 3c2f75e438 tps mod
missing the image, will add later
2026-02-24 09:21:23 +01:00
RudideC 09c354337d rename 2026-02-24 09:19:17 +01:00
selimaj-dev dc6387cc27 Merge pull request #24 from saturnclientmc/services-and-ui-tweaks
Services and UI tweaks
2026-02-23 12:32:03 -08:00
selimaj-dev bff18a05d1 Changed ip to the actual server 2026-02-23 21:30:50 +01:00
selimaj-dev 6717d7e901 Fixed UUID and cloak 2026-02-23 18:51:28 +01:00
selimaj-dev bbf4b17494 Attempting to fix sendplayer 2026-02-23 17:46:28 +01:00
selimaj-dev 68b4bfc439 Working peer player 2026-02-23 16:29:48 +01:00
RudideC 77f5350496 forgot to add icon 2026-02-23 11:38:57 +01:00
RudideC 3c1914c30a added ping mod
wow
2026-02-23 11:27:34 +01:00
RudideC ae8ec4b4ef fixed modmenu scroll 2026-02-23 11:27:25 +01:00
RudideC bdd421e1da nametag module finished
yay
2026-02-23 10:20:41 +01:00
RudideC 7b480d1b8b unit
wow
2026-02-23 09:52:42 +01:00
selimaj-dev 2a425335f0 Working emote streaming 2026-02-23 09:34:38 +01:00
RudideC 02f8b486f3 obfuscate names, name color 2026-02-23 09:24:27 +01:00
selimaj-dev f0966c86f3 Fixing emote system 2026-02-23 09:20:10 +01:00
RudideC 65c6c2a284 colors, can hide heart emoji 2026-02-23 09:11:06 +01:00
RudideC 9fe05722f9 player, hostile, passive filter 2026-02-23 08:47:02 +01:00
selimaj-dev 61be7c317f Fixed the hat rendering issue 2026-02-23 07:22:44 +01:00
selimaj-dev df35497af2 Trying to find the hat rendering issue 2026-02-23 07:18:54 +01:00
selimaj-dev 8614e1bdd1 Working service system 2026-02-23 06:47:08 +01:00
selimaj-dev c5c38247cc Improved build.gradle 2026-02-23 03:45:26 +01:00
selimaj-dev f9c0d3fe58 Fixed keystrokes 2026-02-23 00:24:14 +01:00
selimaj-dev 44c0b69c4d Fixed keystrokes 2026-02-22 23:59:25 +01:00
selimaj-dev 0cfa7b81ef SVG mod icons 2026-02-22 23:40:39 +01:00
selimaj-dev 9caf47d81b Fixed svg issues 2026-02-22 18:11:58 +01:00
selimaj-dev 3c27d3b377 Working svg renderer 2026-02-22 16:41:37 +01:00
selimaj-dev 66b96102f8 Working svg renderer 2026-02-22 16:41:26 +01:00
selimaj-dev 7fc05df240 Svg image rendering 2026-02-22 14:35:57 +01:00
selimaj-dev d32f124974 Removed todos 2026-02-22 04:27:30 +01:00
selimaj-dev fe4d999643 Merge pull request #23 from saturnclientmc/new-ui-theme-and-animations
New UI theme and animations
2026-02-21 18:48:24 -08:00
selimaj-dev cd84866ee8 Optional staggering 2026-02-22 03:44:22 +01:00
selimaj-dev cd5cac7f69 Shift menu animations 2026-02-22 03:36:39 +01:00
selimaj-dev 377031c0a9 Fixed click bugs 2026-02-22 03:20:16 +01:00
selimaj-dev 85d4a1de38 Animated mods 2026-02-22 02:44:58 +01:00
selimaj-dev 44d7aa3641 Hoo lee sheet 2026-02-22 02:31:56 +01:00
selimaj-dev 53732c5df3 Animation stagger and a few slide fixes 2026-02-22 02:19:04 +01:00
selimaj-dev 265b44187b Animation stagger delay 2026-02-22 01:38:30 +01:00
selimaj-dev f46f9c474e Animation Stagger element 2026-02-22 01:34:54 +01:00
selimaj-dev 049c92785d Fixed bugs and restructured roles 2026-02-22 01:25:22 +01:00
selimaj-dev 37ec729d1d Blurring 2026-02-22 01:11:12 +01:00
selimaj-dev 2747ad366e Fixed title menu freezing 2026-02-22 01:06:33 +01:00
selimaj-dev 7de3d5c9bc Fixed keybinding crashing when invalid 2026-02-22 00:20:12 +01:00
selimaj-dev f952fa7095 Better animation system 2026-02-22 00:12:13 +01:00
selimaj-dev e62da29a49 Sub namespace loading 2026-02-21 23:11:22 +01:00
selimaj-dev d3258edbc7 Cleaner Config 2026-02-21 22:55:47 +01:00
selimaj-dev 2096925a02 Smaller HexInput 2026-02-21 22:42:21 +01:00
selimaj-dev 3db44bab85 Fixed HexInput and stuff 2026-02-21 22:37:56 +01:00
selimaj-dev bba12146ea HexInput 2026-02-21 22:17:59 +01:00
selimaj-dev 4403a31c93 Fix splashoverlay and saturnmodule 2026-02-21 20:59:07 +01:00
selimaj-dev 6ee2fc3b0e Improved theme usability 2026-02-21 20:42:04 +01:00
selimaj-dev 3b0ab53d86 Fixed Theme issue 2026-02-21 20:35:52 +01:00
selimaj-dev 7683e7b91e Removed NamedProperty 2026-02-21 20:18:52 +01:00
selimaj-dev 3ba6202b43 Improved scroll 2026-02-21 20:09:19 +01:00
selimaj-dev a49212ca64 Attempting to fix scroll 2026-02-21 20:02:12 +01:00
selimaj-dev 88fe53b907 Fixed sidebar with abstractions 2026-02-21 19:57:40 +01:00
selimaj-dev 52f80281bd Fixed a few issues 2026-02-21 19:46:40 +01:00
selimaj-dev 2f93822996 Scrollbar 2026-02-21 19:38:19 +01:00
selimaj-dev 8d8cbc8b28 Implemenented theme 2026-02-21 19:29:36 +01:00
selimaj-dev a7c5f0d3df Fixed font stuff in screens 2026-02-21 19:19:54 +01:00
selimaj-dev 60d83530ae Tabmenu text and texture button 2026-02-21 19:18:42 +01:00
selimaj-dev 569aecde4a Scroll 2026-02-21 18:51:58 +01:00
selimaj-dev 9fd79aadd5 Notification 2026-02-21 18:47:19 +01:00
selimaj-dev e031672d72 Clicakble texture 2026-02-21 18:46:03 +01:00
selimaj-dev 289a2cd78a Button 2026-02-21 18:44:24 +01:00
selimaj-dev aab4f02dc0 Sidebar 2026-02-21 18:40:14 +01:00
selimaj-dev 5b37131bfd Saturn module 2026-02-21 18:34:12 +01:00
selimaj-dev 79ceaf67a1 Cosmetic Preview 2026-02-21 18:28:30 +01:00
selimaj-dev b0dbdeafa3 Toggle 2026-02-21 18:25:17 +01:00
selimaj-dev 3bc21a5b30 Keybinding selector and select 2026-02-21 18:19:44 +01:00
selimaj-dev 953182e56a Theme font, and implemented input 2026-02-21 18:13:36 +01:00
selimaj-dev 39cea14183 Theme padding and radius 2026-02-21 18:10:57 +01:00
selimaj-dev 8baf28fcfe Implemented theme 2026-02-21 18:05:26 +01:00
selimaj-dev 619c4bce21 Fixed role 2026-02-20 21:02:00 +01:00
selimaj-dev cc80fc5239 Merge pull request #21 from saturnclientmc/refactored-services-and-splash
Refactored services and splash
2026-02-20 11:56:48 -08:00
selimaj-dev f9039fdf67 Splash Background 2026-02-20 20:53:27 +01:00
selimaj-dev bb93d72f66 Progress bar 2026-02-20 20:12:17 +01:00
selimaj-dev 86be2eb956 Tested auth and fixed title menu 2026-02-20 19:44:43 +01:00
selimaj-dev eb40903372 SetScreen mixin 2026-02-20 18:11:58 +01:00
selimaj-dev 039d2cd032 SetScreen mixin 2026-02-20 17:35:59 +01:00
selimaj-dev 365063e8d5 Custom splash overlay 2026-02-20 16:41:09 +01:00
RudideC b43b076ca0 bugfix
you can now activate hitboxes without opening emotes
2026-02-20 11:10:00 +01:00
RudideC 71a7721c52 entity types 2026-02-20 10:12:19 +01:00
RudideC 2bcad83409 added nametags logo 2026-02-20 09:34:55 +01:00
selimaj-dev 6750abcc11 Service client refactor finalized 2026-02-20 03:47:32 +01:00
selimaj-dev 46d3408be9 TabList 2026-02-20 03:46:51 +01:00
selimaj-dev 8015a23342 Emote service 2026-02-20 03:42:56 +01:00
selimaj-dev 4e6bdce019 Buy cosmetics service 2026-02-20 03:41:17 +01:00
selimaj-dev 68519ac00c UI service auth refactored 2026-02-20 03:38:05 +01:00
selimaj-dev 50da953dc4 Added myself as the owner again 2026-02-20 03:25:33 +01:00
selimaj-dev f5776450f0 Name tag service 2026-02-20 03:24:09 +01:00
selimaj-dev 3b3283c6b0 Hat service 2026-02-20 03:11:08 +01:00
selimaj-dev 3f06efe8f4 Hat feature renderer 2026-02-20 03:09:50 +01:00
selimaj-dev 2d021e886f Cloaks 2026-02-20 03:07:31 +01:00
selimaj-dev 96f201cdb4 Auth 2026-02-20 02:45:42 +01:00
selimaj-dev 57c1d21407 Updated session version 2026-02-20 02:15:15 +01:00
selimaj-dev 5d8068c964 Before switching session versions 2026-02-20 01:44:03 +01:00
selimaj-dev 418c47f483 New structure 2026-02-20 00:31:19 +01:00
selimaj-dev 08c001afbb Session build 2026-02-19 23:21:03 +01:00
RudideC 052a2ac06c fixed small issue 2026-02-19 13:33:54 +01:00
RudideC 38041b01cf nametags module 2026-02-19 13:22:30 +01:00
Rudi fdda25464b Merge pull request #17 from RudideC/master
added github actions
2026-02-18 12:41:34 +01:00
RudideC 6bfdf66cdd push
will this work?
2026-02-18 12:37:03 +01:00
RudideC 82f8d0a550 maybe this will work?? 2026-02-18 12:21:17 +01:00
RudideC 81bfe41e25 no sources file 2026-02-18 12:06:58 +01:00
RudideC db0e627f5c fixed bug, dont upload sources jar 2026-02-18 10:13:04 +01:00
RudideC da970354e8 fix deprecation, simplified 2026-02-18 10:02:29 +01:00
RudideC 045e5cd5da test 2026-02-18 09:36:58 +01:00
RudideC e517377bdd ts 2026-02-18 09:25:09 +01:00
RudideC 520b5221b5 new version 2026-02-18 09:17:34 +01:00
143 changed files with 3455 additions and 2123 deletions
+45
View File
@@ -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
+52
View File
@@ -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
+131 -27
View File
@@ -1,44 +1,126 @@
/*
* ================================================================
* Saturn Client - Fabric Mod Build Configuration
* ================================================================
*
* This build file configures:
* - Fabric Loom (Minecraft mod development)
* - Dependency management
* - Resource processing
* - Java toolchain (Java 21)
* - Publishing setup
*
* All version values are defined in gradle.properties.
* ================================================================
*/
plugins { plugins {
// Fabric mod development plugin
id 'fabric-loom' version '1.9-SNAPSHOT' id 'fabric-loom' version '1.9-SNAPSHOT'
// Maven publishing support
id 'maven-publish' id 'maven-publish'
} }
/*
* ================================================================
* Project Metadata
* ================================================================
*/
version = project.mod_version version = project.mod_version
group = project.maven_group group = project.maven_group
repositories {} /*
* ================================================================
* Repositories
* ================================================================
* All dependency sources.
*/
repositories {
// Fabric
maven { url 'https://maven.fabricmc.net/' }
// JitPack (GitHub releases)
maven { url 'https://jitpack.io' }
// Parchment mappings (if needed later)
maven { url 'https://maven.parchmentmc.org' }
// Special Model Loader
maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" }
// Player Animation Library
maven { name = "KosmX Maven"; url = 'https://maven.kosmx.dev/' }
// Central repository
mavenCentral()
}
/*
* ================================================================
* Dependencies
* ================================================================
*/
dependencies { dependencies {
// ─────────────────────────────────────────────
// Minecraft Core
// ─────────────────────────────────────────────
minecraft "com.mojang:minecraft:${project.minecraft_version}" minecraft "com.mojang:minecraft:${project.minecraft_version}"
// Switch back to Yarn // Yarn mappings (named mappings for dev environment)
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
// Fabric Loader
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
// ─────────────────────────────────────────────
// Rendering / Model Libraries
// ─────────────────────────────────────────────
// Special Model Loader (included inside your mod jar)
modApi "dev.felnull:special-model-loader:1.4.0+pre.1" modApi "dev.felnull:special-model-loader:1.4.0+pre.1"
include "dev.felnull:special-model-loader:1.4.0+pre.1" include "dev.felnull:special-model-loader:1.4.0+pre.1"
include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") // SVG Rendering Library
//You might want bendy-lib. playerAnimator will wrap it. include implementation("com.formdev:svgSalamander:1.1.4")
// include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}")
// ─────────────────────────────────────────────
// Animation
// ─────────────────────────────────────────────
// Player Animation Library (bundled into your mod)
include modImplementation(
"dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}"
)
// Optional: BendyLib (only if you directly use it)
// include modRuntimeOnly(
// "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}"
// )
// ─────────────────────────────────────────────
// External Libraries
// ─────────────────────────────────────────────
// Session Java (auth/session handling)
include implementation("com.github.selimaj-dev:session-java:0.1.6")
} }
repositories { /*
maven { url "https://maven.parchmentmc.org" } * ================================================================
maven { * Resource Processing
name = "FelNull Maven" * ================================================================
url = "https://maven.felnull.dev" * Injects version values into fabric.mod.json.
} */
maven {
name "KosmX's maven"
url 'https://maven.kosmx.dev/'
}
}
processResources { processResources {
inputs.property "version", project.version inputs.property "version", project.version
inputs.property "minecraft_version", project.minecraft_version inputs.property "minecraft_version", project.minecraft_version
@@ -46,29 +128,42 @@ processResources {
filteringCharset "UTF-8" filteringCharset "UTF-8"
filesMatching("fabric.mod.json") { filesMatching("fabric.mod.json") {
expand "version": project.version, expand(
"version": project.version,
"minecraft_version": project.minecraft_version, "minecraft_version": project.minecraft_version,
"loader_version": project.loader_version "loader_version": project.loader_version
)
} }
} }
/*
* ================================================================
* Java Configuration
* ================================================================
* Targeting Java 21.
*/
def targetJavaVersion = 21 def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8" options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { options.release.set(targetJavaVersion)
it.options.release.set(targetJavaVersion)
}
} }
java { java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
archivesBaseName = project.archives_base_name archivesBaseName = project.archives_base_name
withSourcesJar() withSourcesJar()
} }
/*
* ================================================================
* Jar Packaging
* ================================================================
* Includes LICENSE and NOTICE inside the final jar.
*/
jar { jar {
from("LICENSE") { from("LICENSE") {
rename { "${it}_${project.archivesBaseName}" } rename { "${it}_${project.archivesBaseName}" }
@@ -78,6 +173,13 @@ jar {
} }
} }
/*
* ================================================================
* Publishing
* ================================================================
* Configured but no remote repository defined yet.
*/
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { mavenJava(MavenPublication) {
@@ -85,5 +187,7 @@ publishing {
} }
} }
repositories {} repositories {
// Add publishing repository here when needed
}
} }
+1 -1
View File
@@ -11,7 +11,7 @@ yarn_mappings=1.21.4+build.7
loader_version=0.16.10 loader_version=0.16.10
fabric_version=0.114.0+1.21.4 fabric_version=0.114.0+1.21.4
mod_version = 0.0.5-beta2 mod_version = 0.0.7-beta1
maven_group = org.saturnclient maven_group = org.saturnclient
archives_base_name = saturnclient archives_base_name = saturnclient
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

@@ -2,8 +2,8 @@ package org.saturnclient.modules;
import java.util.Map; import java.util.Map;
import org.saturnclient.saturnclient.config.NamedProperty; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property.NamedProperty;
public class ModDimensions { public class ModDimensions {
public Property<Integer> x = Property.integer(0); public Property<Integer> x = Property.integer(0);
@@ -26,11 +26,11 @@ public class ModDimensions {
"Background Color", bgColor, "Background Color", bgColor,
"Foreground Color", fgColor, "Foreground Color", fgColor,
"Corner Radius", radius, "Corner Radius", radius,
"Font", font "Font", font)).named("In-Game Display");
)).named("In-Game Display");
} }
public ModDimensions() {} public ModDimensions() {
}
public ModDimensions(int width, int height) { public ModDimensions(int width, int height) {
this.width = width; this.width = width;
@@ -22,7 +22,11 @@ public class ModManager {
new DayCounter(), new DayCounter(),
new Speedometer(), new Speedometer(),
new Zoom(), new Zoom(),
new HealthDisplay() new HealthDisplay(),
new Nametags(),
new Ping(),
new Tps(),
new Clock()
}; };
public static void init() { public static void init() {
@@ -1,8 +1,8 @@
package org.saturnclient.modules; package org.saturnclient.modules;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.NamedProperty;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.config.manager.Property.NamedProperty;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@@ -19,8 +19,11 @@ public abstract class Module {
} }
} }
public void render(RenderScope scope) {} public void render(RenderScope scope) {
public void tick() {} }
public void tick() {
}
public abstract boolean isEnabled(); public abstract boolean isEnabled();
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.EquipmentSlot;
@@ -5,7 +5,7 @@ import java.util.Objects;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
public class AutoSprint extends Module { public class AutoSprint extends Module {
private static Property<Boolean> enabled = Property.bool(false); private static Property<Boolean> enabled = Property.bool(false);
@@ -0,0 +1,77 @@
package org.saturnclient.modules.mods;
import java.time.format.DateTimeFormatter;
import java.time.LocalTime;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
public class Clock extends Module implements HudMod {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> format = Property.select(0, "24 hour", "12 hour");
private static Property<Boolean> seconds = Property.bool(false);
private static ModDimensions dimensions = new ModDimensions(60, Fonts.getHeight());
public Clock() {
super(new ModuleDetails("Clock", "clock")
.description("Displays the current real-world time")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
format.named("Format"),
seconds.named("Show seconds"),
dimensions.prop()
);
}
@Override
public void renderDummy(RenderScope scope) {
renderClock(scope);
}
@Override
public void renderHud(RenderScope scope) {
renderClock(scope);
}
public void renderClock(RenderScope scope) {
String text = "";
LocalTime currentTime = LocalTime.now();
switch (format.value) {
case 0:
if (seconds.value) { text = currentTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")); }
else { text = currentTime.format(DateTimeFormatter.ofPattern("HH:mm")); }
break;
case 1:
if (seconds.value) { text = currentTime.format(DateTimeFormatter.ofPattern("hh:mm:ss a")); }
else { text = currentTime.format(DateTimeFormatter.ofPattern("hh:mm a")); }
break;
}
scope.drawText(text,
0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
@@ -3,7 +3,7 @@ package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
@@ -4,7 +4,7 @@ import org.lwjgl.glfw.GLFW;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import net.minecraft.client.option.Perspective; import net.minecraft.client.option.Perspective;
@@ -2,7 +2,7 @@ package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
public class Fullbright extends Module { public class Fullbright extends Module {
private static Property<Boolean> enabled = Property.bool(false); private static Property<Boolean> enabled = Property.bool(false);
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
@@ -34,7 +34,7 @@ public class HealthDisplay extends Module implements HudMod {
@Override @Override
public void renderDummy(RenderScope scope) { public void renderDummy(RenderScope scope) {
renderSpeed(10, scope); renderHealth(10, scope);
} }
@Override @Override
@@ -44,10 +44,10 @@ public class HealthDisplay extends Module implements HudMod {
case 1: case 1:
health = health / 2; health = health / 2;
} }
renderSpeed(health, scope); renderHealth(health, scope);
} }
public void renderSpeed(float health, RenderScope scope) { public void renderHealth(float health, RenderScope scope) {
String text = ""; String text = "";
switch (decimals.value) { switch (decimals.value) {
@@ -4,51 +4,50 @@ import org.saturnclient.modules.Module;
import org.saturnclient.modules.HudMod; import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions; import org.saturnclient.modules.ModDimensions;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
public class Keystrokes extends Module implements HudMod { public class Keystrokes extends Module implements HudMod {
public static Property<Boolean> enabled = Property.bool(false);
public static Property<Boolean> showMouse = Property.bool(true);
public static Property<Boolean> showSpace = Property.bool(false);
public static ModDimensions dimensions = new ModDimensions(78, 46);
// Cache key states to avoid repeated calls private static final Property<Boolean> enabled = Property.bool(false);
private boolean w = false; private static final Property<Boolean> showMouse = Property.bool(true);
private boolean a = false; private static final Property<Boolean> showSpace = Property.bool(false);
private boolean s = false;
private boolean d = false;
private boolean lmb = false;
private boolean rmb = false;
private boolean space = false;
// Cache dimensions to avoid recalculation private static final Property<Integer> clickBg = Property.color(0xFFCCCCCC);
private int cachedHeight = 54; private static final Property<Integer> clickFg = Property.color(0xFFFFFFFF);
private boolean dimensionsDirty = true;
private static Property<Integer> clickBg = Property.color(0xFFCCCCCC); private static final ModDimensions dimensions = new ModDimensions(78, 54);
private static Property<Integer> clickFg = Property.color(0xFFFFFFFF);
// Key states
private boolean w, a, s, d, lmb, rmb, space;
// Layout constants
private static final int KEY_SIZE = 24;
private static final int KEY_SPACING = 3;
private static final int MOUSE_WIDTH = 38;
private static final int MOUSE_HEIGHT = 24;
private static final int SPACE_HEIGHT = 19;
public Keystrokes() { public Keystrokes() {
super(new ModuleDetails("Keystrokes", "keystrokes") super(new ModuleDetails("Keystrokes", "keystrokes")
.description("Displays the keystrokes for movement") .description("Displays movement keystrokes")
.version("v0.1.0") .version("v0.2.0")
.tags("Utility"), .tags("Utility"),
enabled.named("Enabled"), enabled.named("Enabled"),
showMouse.named("Show mouse clicks"), showMouse.named("Show mouse clicks"),
showSpace.named("Show space clicks"), showSpace.named("Show space"),
dimensions.prop(), dimensions.prop(),
clickFg.named("Clicked fg"), clickFg.named("Clicked fg"),
clickBg.named("Clicked bg")); clickBg.named("Clicked bg")
);
dimensions.renderBackground = false; dimensions.renderBackground = false;
} }
@Override @Override
public void tick() { public void tick() {
// Cache key states to reduce method calls
w = SaturnClient.client.options.forwardKey.isPressed(); w = SaturnClient.client.options.forwardKey.isPressed();
a = SaturnClient.client.options.leftKey.isPressed(); a = SaturnClient.client.options.leftKey.isPressed();
s = SaturnClient.client.options.backKey.isPressed(); s = SaturnClient.client.options.backKey.isPressed();
@@ -57,103 +56,84 @@ public class Keystrokes extends Module implements HudMod {
rmb = SaturnClient.client.options.useKey.isPressed(); rmb = SaturnClient.client.options.useKey.isPressed();
space = SaturnClient.client.options.jumpKey.isPressed(); space = SaturnClient.client.options.jumpKey.isPressed();
// Mark dimensions as dirty if mouse or space visibility changed updateHeight();
if (dimensionsDirty) {
updateDimensions();
dimensionsDirty = false;
}
} }
private void updateDimensions() { private void updateHeight() {
cachedHeight = 54; // base height with padding considered int height = KEY_SIZE * 2 + KEY_SPACING;
if (showMouse.value) { if (showSpace.value)
cachedHeight += 27; height += SPACE_HEIGHT + KEY_SPACING;
}
if (showSpace.value) { if (showMouse.value)
cachedHeight += 22; height += MOUSE_HEIGHT + KEY_SPACING;
}
dimensions.height = cachedHeight; dimensions.height = height;
} }
@Override @Override
public void renderDummy(RenderScope scope) { public void renderDummy(RenderScope scope) {
// Use cached dimensions render(scope, false);
dimensions.height = cachedHeight;
// WASD keys (3px padding between each key)
renderKey(scope, false, '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, false, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, false, "RMB", 40, 54); // With 3px between
}
if (showSpace.value) {
renderKeySpace(scope, false, 0, cachedHeight - (showSpace.value ? 22 : 0));
}
} }
@Override @Override
public void renderHud(RenderScope scope) { public void renderHud(RenderScope scope) {
// Use cached dimensions render(scope, true);
dimensions.height = cachedHeight;
// 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
if (showMouse.value) {
renderKeyM(scope, lmb, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, rmb, "RMB", 40, 54); // With 3px between
} }
private void render(RenderScope scope, boolean live) {
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) { if (showSpace.value) {
renderKeySpace(scope, space, 0, cachedHeight - (showSpace.value ? 22 : 0)); 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) { scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg);
int height = 19; scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg);
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, dimensions.width, height, dimensions.radius.value, bgColor);
// 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, fgColor);
} }
private void renderKeyM(RenderScope scope, boolean isPressed, String c, int x, int y) { private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) {
int width = 38, height = 24; int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; int fg = pressed ? clickFg.value : dimensions.fgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, width, height, dimensions.radius.value, bgColor); 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);
// Apply padding: 3px top and left -> add slight offset to x/y
scope.drawText(0.6f, c, x + 9, y + 7, dimensions.font.value, fgColor);
} }
private void renderKey(RenderScope scope, boolean isPressed, char c, int x, int y) { private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) {
int size = 24; int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; int fg = pressed ? clickFg.value : dimensions.fgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, size, size, dimensions.radius.value, bgColor); scope.drawRoundedRectangle(x, y, dimensions.width, SPACE_HEIGHT, dimensions.radius.value, bg);
// Center the character with 3px top padding int lineY = y + SPACE_HEIGHT / 2;
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fgColor); scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg);
} }
@Override @Override
@@ -169,6 +149,5 @@ public class Keystrokes extends Module implements HudMod {
@Override @Override
public void onEnabled(boolean e) { public void onEnabled(boolean e) {
enabled.value = e; enabled.value = e;
dimensionsDirty = true;
} }
} }
@@ -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;
}
}
@@ -2,7 +2,7 @@ package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
public class NoFog extends Module { public class NoFog extends Module {
private static Property<Boolean> enabled = Property.bool(false); private static Property<Boolean> enabled = Property.bool(false);
@@ -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;
}
}
@@ -5,7 +5,7 @@ import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
@@ -7,7 +7,7 @@ import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import net.minecraft.client.resource.language.I18n; import net.minecraft.client.resource.language.I18n;
@@ -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;
}
}
@@ -3,7 +3,7 @@ package org.saturnclient.modules.mods;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
public class Zoom extends Module { public class Zoom extends Module {
private static Property<Boolean> enabled = Property.bool(false); private static Property<Boolean> enabled = Property.bool(false);
@@ -0,0 +1,26 @@
package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import org.saturnclient.modules.mods.utils.HealthRenderState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(LivingEntityRenderState.class)
public class LivingEntityRenderStateMixin implements org.saturnclient.modules.mods.utils.HealthRenderState {
@Unique private float saturn$health = 0f;
@Unique private float saturn$maxHealth = 1f;
@Unique private HealthRenderState.EntityType saturn$entityType = HealthRenderState.EntityType.OTHER;
public float saturn$getHealth() { return saturn$health; }
public float saturn$getMaxHealth() { return saturn$maxHealth; }
public void saturn$setHealth(float health, float maxHealth) {
this.saturn$health = health;
this.saturn$maxHealth = maxHealth;
}
public HealthRenderState.EntityType saturn$getEntityType() { return saturn$entityType; }
public void saturn$setEntityType(HealthRenderState.EntityType type) { this.saturn$entityType = type; }
}
@@ -0,0 +1,34 @@
package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import org.saturnclient.modules.mods.utils.HealthRenderState;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.passive.PassiveEntity;
import net.minecraft.entity.player.PlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntityRenderer.class)
public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState> {
@Inject(method = "updateRenderState", at = @At("TAIL"))
private void saturn$captureHealth(T entity, S state, float tickDelta, CallbackInfo ci) {
if (!(state instanceof HealthRenderState hrs)) return;
hrs.saturn$setHealth(entity.getHealth(), entity.getMaxHealth());
if (entity instanceof PlayerEntity) {
hrs.saturn$setEntityType(HealthRenderState.EntityType.PLAYER);
} else if (entity instanceof HostileEntity) {
hrs.saturn$setEntityType(HealthRenderState.EntityType.HOSTILE);
} else if (entity instanceof PassiveEntity) {
hrs.saturn$setEntityType(HealthRenderState.EntityType.PASSIVE);
} else {
hrs.saturn$setEntityType(HealthRenderState.EntityType.OTHER);
}
}
}
@@ -0,0 +1,48 @@
package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.EntityRenderer;
import net.minecraft.client.render.entity.state.EntityRenderState;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.saturnclient.modules.mods.Nametags;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(EntityRenderer.class)
public abstract class NametagsMixin {
@Unique
private static final ThreadLocal<Boolean> saturn$rendering = ThreadLocal.withInitial(() -> false);
@Shadow
protected abstract <S extends EntityRenderState> void renderLabelIfPresent(
S state, Text text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light
);
@Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true)
private <S extends EntityRenderState> void saturn$replaceNametag(S state, Text text,
MatrixStack matrices, VertexConsumerProvider vertexConsumers,
int light, CallbackInfo ci) {
if (saturn$rendering.get()) return;
if (!Nametags.shouldReplaceName()) return;
if (!(state instanceof LivingEntityRenderState living)) return;
String replacement = Nametags.getNametagString(living);
if (replacement == null) return;
ci.cancel();
saturn$rendering.set(true);
try {
renderLabelIfPresent(state, Text.literal(replacement), matrices, vertexConsumers, light);
} finally {
saturn$rendering.set(false);
}
}
}
@@ -0,0 +1,18 @@
package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket;
import org.saturnclient.modules.mods.Tps;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class)
public class TpsMixin {
@Inject(method = "onWorldTimeUpdate", at = @At("HEAD"))
private void onWorldTimeUpdate(WorldTimeUpdateS2CPacket packet, CallbackInfo ci) {
Tps.onTimePacket(packet.time());
}
}
@@ -0,0 +1,14 @@
package org.saturnclient.modules.mods.utils;
public interface HealthRenderState {
float saturn$getHealth();
float saturn$getMaxHealth();
void saturn$setHealth(float health, float maxHealth);
EntityType saturn$getEntityType();
void saturn$setEntityType(EntityType type);
enum EntityType {
PLAYER, HOSTILE, PASSIVE, OTHER
}
}
@@ -7,8 +7,9 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import org.saturnclient.modules.ModManager; import org.saturnclient.modules.ModManager;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.Config;
import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.saturnclient.cosmetics.Emotes;
import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
@@ -37,9 +38,13 @@ public class SaturnClient implements ModInitializer {
public void onInitialize() { public void onInitialize() {
LOGGER.info("Initializing " + MOD_ID); LOGGER.info("Initializing " + MOD_ID);
client = MinecraftClient.getInstance(); client = MinecraftClient.getInstance();
SaturnClientConfig.init(); Config.init();
ModManager.init(); ModManager.init();
client.execute(() -> {
SaturnScreen.preload(client);
});
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save()); ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> { SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> {
@@ -75,7 +80,7 @@ public class SaturnClient implements ModInitializer {
KeyInputHandler.register(); KeyInputHandler.register();
Emotes.initialize(); Emotes.initialize();
if (Auth.authenticate()) { if (ServiceClient.authenticate()) {
Cloaks.initialize(); Cloaks.initialize();
Hats.initialize(); Hats.initialize();
LOGGER.info(MOD_ID + " initialization complete"); LOGGER.info(MOD_ID + " initialization complete");
@@ -1,51 +0,0 @@
package org.saturnclient.saturnclient;
import org.saturnclient.saturnclient.config.Property;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.ui2.resources.Textures;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
public class SaturnClientConfig {
public static ConfigManager config;
public static Property<Boolean> realisticLogo = Property.bool(false);
public static Property<Boolean> saturnTitleScreen = Property.bool(true);
public static Property<Boolean> bendyCloaks = Property.bool(true);
public static Property<Integer> openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B);
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("Bendy Cloaks", bendyCloaks);
}
}
@@ -1,283 +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.Utils;
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;
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) {
String[] a = parser.error.replace("!", "").split(": ");
Utils.notify(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,105 +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];
private static final int CONNECTION_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
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();
socket.connect(new InetSocketAddress("77.247.92.168", 8080), CONNECTION_TIMEOUT);
socket.setSoTimeout(READ_TIMEOUT);
// 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);
}
@@ -1,8 +1,10 @@
package org.saturnclient.saturnclient.auth; package org.saturnclient.saturnclient.client.player;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.client.ServiceClient;
import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@@ -45,8 +47,6 @@ public class PlayerTracker {
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) { private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
GameProfile profile = player.getProfile(); GameProfile profile = player.getProfile();
String uuid = profile.getId().toString(); ServiceClient.player(profile.getId(), profile.getName());
if (uuid != Auth.uuid)
Auth.player(profile.getName(), uuid);
} }
} }
@@ -0,0 +1,61 @@
package org.saturnclient.saturnclient.client.player;
import java.util.Map;
import java.util.UUID;
import org.saturnclient.saturnclient.config.Config;
import net.minecraft.util.Formatting;
public class Roles {
static enum Role {
OWNER,
ADMIN,
PARTNER,
CONTRIBUTOR
}
private static final Map<UUID, Role> ROLES = Map.of(
UUID.fromString("d362a042-28bd-49e1-a807-ae74dbe8aba9"),
Role.OWNER,
UUID.fromString("d8dc7023-179b-4460-bcf6-493fbf1289a6"),
Role.CONTRIBUTOR);
/*
* Gets the icon color of a specific individual, here are the different colors
*
* - Owner: Dark Red
* - Admin: Red
* - Partners: Gold
* - Contributor: Aqua
* - Other/player: White
*/
public static Formatting getIconColor(UUID uuid) {
Role role = ROLES.get(uuid);
if (role == null) {
return Formatting.WHITE;
}
switch (role) {
case OWNER:
return Formatting.DARK_RED;
case ADMIN:
return Formatting.RED;
case PARTNER:
return Formatting.GOLD;
case CONTRIBUTOR:
return Formatting.AQUA;
default:
return Formatting.WHITE;
}
}
public static String getSaturnIndicator() {
return Config.realisticLogo.value ? "" : "";
}
}
@@ -0,0 +1,54 @@
package org.saturnclient.saturnclient.client.player;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.client.ServiceClient;
public class SaturnPlayer {
private static Map<UUID, SaturnPlayer> PLAYERS = new HashMap<>();
private static Map<String, UUID> PLAYER_NAMES = new HashMap<>();
public String cloak = "";
public String hat = "";
public final UUID uuid;
public final String name;
public SaturnPlayer(UUID uuid, String name, String cloak, String hat) {
this.cloak = cloak;
this.hat = hat;
this.uuid = uuid;
this.name = name;
}
@Nullable
public static SaturnPlayer get() {
return PLAYERS.get(ServiceClient.uuid);
}
@Nullable
public static SaturnPlayer get(UUID uuid) {
return PLAYERS.get(uuid);
}
@Nullable
public static SaturnPlayer get(String name) {
UUID uuid = PLAYER_NAMES.get(name);
if (uuid == null)
return null;
return PLAYERS.get(uuid);
}
public static void set(SaturnPlayer player) {
PLAYER_NAMES.put(player.name, player.uuid);
PLAYERS.put(player.uuid, player);
}
public static String[] getExternalUUIDAsString() {
return PLAYERS.keySet().stream().filter(id -> !id.equals(ServiceClient.uuid)).map(UUID::toString)
.toArray(String[]::new);
}
}
@@ -0,0 +1,55 @@
package org.saturnclient.saturnclient.config;
import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.config.manager.Property;
public class AnimationConfig {
public static ConfigManager config;
// Logo
public static final AnimationConfig logo = new AnimationConfig(true, 700, 0);
// Main menu
public static final AnimationConfig mainMenu = new AnimationConfig(true, 700, 120);
// Mod menu
public static final AnimationConfig modMenu = new AnimationConfig(true, 100, 20);
// Shift menu
public static final AnimationConfig shiftMenu = new AnimationConfig(true, 100, 100);
// Cosmetics menu
public static final AnimationConfig cosmeticsMenu = new AnimationConfig(true, 700, 40);
// Emote wheel
public static final AnimationConfig emoteWheel = new AnimationConfig(true, 200, 40);
public static void init(ConfigManager parent) {
config = new ConfigManager(parent, "Animations");
logo.init("Logo");
mainMenu.init("Main Menu");
modMenu.init("Mod Menu");
shiftMenu.init("Shift Menu");
cosmeticsMenu.init("Cosmetics Menu");
emoteWheel.init("Emote Wheel");
}
// Animation properties
public final Property<Integer> duration;
public final Property<Integer> stagger;
public final Property<Integer> curve;
public AnimationConfig(boolean enabled, int delay, int stagger) {
this.duration = Property.integer(delay);
this.stagger = Property.integer(stagger);
this.curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic", "Ease In Out Back");
}
public void init(String name) {
// config.property("Enable " + name + " Animations", this.enabled);
config.property(name + " Duration", this.duration);
config.property(name + " Stagger", this.stagger);
config.property(name + " Curve", this.curve);
}
}
@@ -0,0 +1,37 @@
package org.saturnclient.saturnclient.config;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.resources.Textures;
import net.minecraft.util.Identifier;
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 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,119 +0,0 @@
package org.saturnclient.saturnclient.config;
import com.google.gson.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import org.saturnclient.saturnclient.SaturnClient;
public class ConfigManager {
private static final File configFile = new File(SaturnClient.client.runDirectory, "saturn.json");
private static final Map<String, Map<String, Property<?>>> properties = new HashMap<>();
private static JsonObject cachedThemeJson = null;
private final Map<String, Property<?>> currentMap;
private final String namespace;
public ConfigManager(String namespace) {
this.namespace = namespace;
this.currentMap = new LinkedHashMap<>();
properties.put(namespace, currentMap);
System.out.println("Created namespace: " + namespace);
}
public ConfigManager(ConfigManager parent, String namespace) {
this.namespace = namespace;
this.currentMap = new LinkedHashMap<>();
Property<Map<String, Property<?>>> nsProp = Property.namespace(currentMap);
parent.property(namespace, nsProp);
System.out.println("Created sub-namespace: " + namespace);
}
public void sub(String name) {
property(name, Property.namespace(new LinkedHashMap<>()));
}
public <T> Property<T> property(String name, Property<T> value) {
currentMap.put(name, value);
loadProp(name, value);
return value;
}
private <T> void loadProp(String name, Property<T> prop) {
JsonObject source = cachedThemeJson != null ? cachedThemeJson : loadAndCache();
if (source == null)
return;
JsonElement namespaceJson = source.get(namespace);
if (namespaceJson == null || !namespaceJson.isJsonObject())
return;
JsonElement value = namespaceJson.getAsJsonObject().get(name);
if (value != null)
prop.loadFromJson(value);
}
private static JsonObject loadAndCache() {
try {
if (!configFile.exists()) {
Files.writeString(configFile.toPath(), "{}");
return new JsonObject();
}
String json = Files.readString(configFile.toPath());
cachedThemeJson = JsonParser.parseString(json).getAsJsonObject();
return cachedThemeJson;
} catch (IOException e) {
SaturnClient.LOGGER.error("Error reading config file", e);
return null;
}
}
public static void load() {
SaturnClient.LOGGER.info("Loading config...");
JsonObject json = loadAndCache();
if (json == null)
return;
properties.forEach((namespace, map) -> {
JsonElement nsElement = json.get(namespace);
if (nsElement != null && nsElement.isJsonObject()) {
loadProperties(nsElement.getAsJsonObject(), map);
}
});
}
private static void loadProperties(JsonObject json, Map<String, Property<?>> props) {
props.forEach((name, prop) -> {
JsonElement el = json.get(name);
if (el != null)
prop.loadFromJson(el);
});
}
public static void save() {
SaturnClient.LOGGER.info("Saving config...");
JsonObject root = new JsonObject();
properties.forEach((namespace, map) -> {
JsonObject nsJson = new JsonObject();
saveProperties(nsJson, map);
root.add(namespace, nsJson);
});
try {
String formatted = new GsonBuilder().setPrettyPrinting().create().toJson(root);
Files.writeString(configFile.toPath(), formatted);
SaturnClient.LOGGER.info("Config saved successfully.");
} catch (IOException e) {
SaturnClient.LOGGER.error("Error saving config file", e);
}
}
private static void saveProperties(JsonObject json, Map<String, Property<?>> props) {
props.forEach((name, prop) -> json.add(name, prop.toJson()));
}
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;
}
}
@@ -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;
}
}
@@ -1,4 +1,4 @@
package org.saturnclient.saturnclient.config; package org.saturnclient.saturnclient.config.manager;
import java.util.Map; import java.util.Map;
@@ -10,6 +10,15 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
public class Property<T> { 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 { public enum PropertyType {
BOOLEAN, BOOLEAN,
@@ -277,4 +286,13 @@ public class Property<T> {
if (value != null) if (value != null)
this.loadFromJson(value); 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,7 +1,9 @@
package org.saturnclient.saturnclient.cosmetics; package org.saturnclient.saturnclient.cosmetics;
import org.saturnclient.saturnclient.SaturnClientConfig; import java.util.Arrays;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.config.Config;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
@@ -22,7 +24,6 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.equipment.EquipmentAsset; import net.minecraft.item.equipment.EquipmentAsset;
import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKey;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
/** /**
@@ -35,7 +36,7 @@ import net.minecraft.util.math.Vec3d;
public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> { public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
private final EquipmentModelLoader equipmentModelLoader; private final EquipmentModelLoader equipmentModelLoader;
private static final int PARTS = 16; private static final int PARTS = 16;
private float currentVelocity = 0.0f; private final float[] segmentValues = new float[PARTS];
private static final float TEX_W = 176f; private static final float TEX_W = 176f;
private static final float TEX_H = 138f; private static final float TEX_H = 138f;
@@ -78,6 +79,7 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
EquipmentModelLoader equipmentModelLoader) { EquipmentModelLoader equipmentModelLoader) {
super(context); super(context);
this.equipmentModelLoader = equipmentModelLoader; this.equipmentModelLoader = equipmentModelLoader;
Arrays.fill(segmentValues, 0.0f);
} }
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) { private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) {
@@ -137,168 +139,152 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
} }
} }
private float getCurve(int i, float curveMagnitude, PlayerEntityRenderState playerState) { private void renderCape(MatrixStack matrixStack, VertexConsumer vertexConsumer, PlayerEntityRenderState playerState,
float powCurve = (float) Math.pow((double) (PARTS - i) / PARTS, 2) * curveMagnitude; int light, int overlay) {
float waveFreq = 0.6f;
float phase = (playerState.age + playerState.handSwingProgress) * 0.4f;
float sineAmplitude = 0.02f;
float sineCurve = (float) Math.sin(i * waveFreq + phase) * sineAmplitude;
float t = Math.min(1.0f, Math.max(0.0f, (curveMagnitude - 0.3f) / 0.2f));
t = t * t * (3 - 2 * t);
return (1.0f - t) * powCurve + t * (powCurve + sineCurve);
}
private void renderCape(MatrixStack matrixStack, VertexConsumer vertexConsumer,
PlayerEntityRenderState playerState, int light, int overlay,
float rotation, float curveMagnitude) {
float capeWidth = 10.0f / 16.0f; float capeWidth = 10.0f / 16.0f;
float capeHeight = 16.0f / 16.0f; float capeHeight = 16.0f / 16.0f;
float capeDepth = 1.0f / 16.0f; float capeDepth = 1.0f / 16.0f;
float capePartHeight = capeHeight / PARTS; float capePartHeight = capeHeight / PARTS;
matrixStack.push();
matrixStack.translate(0.0f, 1.25, 0.125f);
matrixStack.translate(0, -capeHeight, 0);
matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(rotation));
matrixStack.translate(0, capeHeight, 0);
float x1 = -capeWidth / 2.0f; float x1 = -capeWidth / 2.0f;
float x2 = capeWidth / 2.0f; float x2 = capeWidth / 2.0f;
float y2 = -capeHeight;
float z1 = 0.0f;
float z2 = capeDepth;
final float frontU1 = FRONT_RECT.u1(); final float frontU1 = FRONT_RECT.u1();
final float frontU2 = FRONT_RECT.u2(); final float frontU2 = FRONT_RECT.u2();
final float frontV1 = FRONT_RECT.v1(); final float frontV1 = FRONT_RECT.v1();
final float frontV2 = FRONT_RECT.v2(); final float frontV2 = FRONT_RECT.v2();
final float frontPartV = (frontV2 - frontV1) / PARTS; final float frontPartV = (frontV2 - frontV1) / PARTS;
final float backU1 = BACK_RECT.u1(); final float backU1 = BACK_RECT.u1();
final float backU2 = BACK_RECT.u2(); final float backU2 = BACK_RECT.u2();
final float backV1 = BACK_RECT.v1(); final float backV1 = BACK_RECT.v1();
final float backV2 = BACK_RECT.v2(); final float backV2 = BACK_RECT.v2();
final float backPartV = (backV2 - backV1) / PARTS; final float backPartV = (backV2 - backV1) / PARTS;
final float leftU1 = LEFT_EDGE_RECT.u1(); final float leftU1 = LEFT_EDGE_RECT.u1();
final float leftU2 = LEFT_EDGE_RECT.u2(); final float leftU2 = LEFT_EDGE_RECT.u2();
final float leftV1 = LEFT_EDGE_RECT.v1(); final float leftV1 = LEFT_EDGE_RECT.v1();
final float leftV2 = LEFT_EDGE_RECT.v2(); final float leftV2 = LEFT_EDGE_RECT.v2();
final float leftPartV = (leftV2 - leftV1) / PARTS; final float leftPartV = (leftV2 - leftV1) / PARTS;
final float rightU1 = RIGHT_EDGE_RECT.u1(); final float rightU1 = RIGHT_EDGE_RECT.u1();
final float rightU2 = RIGHT_EDGE_RECT.u2(); final float rightU2 = RIGHT_EDGE_RECT.u2();
final float rightV1 = RIGHT_EDGE_RECT.v1(); final float rightV1 = RIGHT_EDGE_RECT.v1();
final float rightV2 = RIGHT_EDGE_RECT.v2(); final float rightV2 = RIGHT_EDGE_RECT.v2();
final float rightPartV = (rightV2 - rightV1) / PARTS; final float rightPartV = (rightV2 - rightV1) / PARTS;
final float topU1 = TOP_RECT.u1(); final float topU1 = TOP_RECT.u1();
final float topU2 = TOP_RECT.u2(); final float topU2 = TOP_RECT.u2();
final float topV1 = TOP_RECT.v1(); final float topV1 = TOP_RECT.v1();
final float topV2 = TOP_RECT.v2(); final float topV2 = TOP_RECT.v2();
final float bottomU1 = BOTTOM_RECT.u1(); final float bottomU1 = BOTTOM_RECT.u1();
final float bottomU2 = BOTTOM_RECT.u2(); final float bottomU2 = BOTTOM_RECT.u2();
final float bottomV1 = BOTTOM_RECT.v1(); final float bottomV1 = BOTTOM_RECT.v1();
final float bottomV2 = BOTTOM_RECT.v2(); final float bottomV2 = BOTTOM_RECT.v2();
float curY = 0.0f;
float curZ = 0.0f;
for (int i = 0; i < PARTS; i++) { for (int i = 0; i < PARTS; i++) {
float y3 = -(capePartHeight * i); float angle = (2.0f - segmentValues[i]) * ((float) Math.PI / 2f);
float y4 = -(capePartHeight * (i + 1)); float dirY = -(float) Math.cos(angle);
float dirZ = (float) Math.sin(angle);
float curveOffsetTop = getCurve(i, curveMagnitude, playerState); // 1. Thickness at the START of this segment (Must match PREVIOUS segment's end)
float curveOffsetBottom = getCurve(i + 1, curveMagnitude, playerState); 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;
// FRONT FACE (outside) // 2. Thickness at the END of this segment (Must match NEXT segment's start)
this.renderCapeQuad( float nextAngle = (i < PARTS - 1) ? (2.0f - segmentValues[i + 1]) * ((float) Math.PI / 2f) : angle;
vertexConsumer, matrixStack, float endAvgAngle = (angle + nextAngle) / 2.0f;
new Vec3d(x2, y4, z2 + curveOffsetBottom), float thickYEnd = (float) Math.sin(endAvgAngle) * capeDepth;
new Vec3d(x1, y4, z2 + curveOffsetBottom), float thickZEnd = (float) Math.cos(endAvgAngle) * capeDepth;
new Vec3d(x1, y3, z2 + curveOffsetTop), new Vec3d(x2, y3, z2 + curveOffsetTop),
frontU1, frontV2 - (frontPartV * i),
frontU2, frontV2 - (frontPartV * (i + 1)),
light, overlay,
0.0f, 0.0f, 1.0f, false);
// BACK FACE (inside) float nextY = curY + dirY * capePartHeight;
this.renderCapeQuad( float nextZ = curZ + dirZ * capePartHeight;
vertexConsumer, matrixStack,
new Vec3d(x1, y4, z1 + curveOffsetBottom),
new Vec3d(x2, y4, z1 + curveOffsetBottom),
new Vec3d(x2, y3, z1 + curveOffsetTop), new Vec3d(x1, y3, z1 + curveOffsetTop),
backU1, backV2 - (backPartV * i),
backU2, backV2 - (backPartV * (i + 1)),
light, overlay,
0.0f, 0.0f, 1.0f, false);
// LEFT FACE (thickness at x2) // Inner Vertices (The spine of the cape)
this.renderCapeQuad( Vec3d innerTopLeft = new Vec3d(x2, curY, curZ);
vertexConsumer, matrixStack, Vec3d innerTopRight = new Vec3d(x1, curY, curZ);
new Vec3d(x2, y4, z1 + curveOffsetBottom), Vec3d innerBotLeft = new Vec3d(x2, nextY, nextZ);
new Vec3d(x2, y4, z2 + curveOffsetBottom), Vec3d innerBotRight = new Vec3d(x1, nextY, nextZ);
new Vec3d(x2, y3, z2 + curveOffsetTop), new Vec3d(x2, y3, z1 + curveOffsetTop),
leftU1, leftV2 - (leftPartV * i),
leftU2, leftV2 - (leftPartV * (i + 1)),
light, overlay,
1.0f, 0.0f, 0.0f, false);
// RIGHT FACE (thickness at x1) // Outer Vertices (Offset by the specific joint thickness)
this.renderCapeQuad( Vec3d outerTopLeft = new Vec3d(x2, curY + thickYStart, curZ + thickZStart);
vertexConsumer, matrixStack, Vec3d outerTopRight = new Vec3d(x1, curY + thickYStart, curZ + thickZStart);
new Vec3d(x1, y4, z2 + curveOffsetBottom), Vec3d outerBotLeft = new Vec3d(x2, nextY + thickYEnd, nextZ + thickZEnd);
new Vec3d(x1, y4, z1 + curveOffsetBottom), Vec3d outerBotRight = new Vec3d(x1, nextY + thickYEnd, nextZ + thickZEnd);
new Vec3d(x1, y3, z1 + curveOffsetTop), new Vec3d(x1, y3, z2 + curveOffsetTop),
rightU1, rightV2 - (rightPartV * i), // FRONT (Inner) - Changed winding/order to fix flipping
rightU2, rightV2 - (rightPartV * (i + 1)), this.renderCapeQuad(vertexConsumer, matrixStack,
light, overlay, innerBotLeft, innerBotRight, innerTopRight, innerTopLeft,
-1.0f, 0.0f, 0.0f, false); 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);
} }
// TOP FACE (shoulders edge) – use its dedicated strip // BOTTOM FACE (Only on last segment)
matrixStack.push(); if (i == PARTS - 1) {
matrixStack.translate(0.0f, y2 + capeDepth, 0.0f); this.renderCapeQuad(vertexConsumer, matrixStack,
matrixStack.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(90.0f)); innerBotLeft, innerBotRight, outerBotRight, outerBotLeft,
matrixStack.translate(0.0f, -y2, 0.0f); bottomU1, bottomV1, bottomU2, bottomV2, light, overlay, 0, -1, 0, false);
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x2, y2 + capeDepth, z2), new Vec3d(x1, y2 + capeDepth, z2),
new Vec3d(x1, y2, z2), new Vec3d(x2, y2, z2),
topU1, topV1, topU2, topV2,
light, overlay,
0.0f, 1.0f, 0.0f, true);
matrixStack.pop();
// BOTTOM FACE – use bottom strip
float curveOffset = getCurve(0, curveMagnitude, playerState);
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x2, 0.0f, z2 + curveOffset), new Vec3d(x1, 0.0f, z2 + curveOffset),
new Vec3d(x1, 0.0f, z1 + curveOffset), new Vec3d(x2, 0.0f, z1 + curveOffset),
bottomU1, bottomV1, bottomU2, bottomV2,
light, overlay,
0.0f, 0.0f, 1.0f, false);
matrixStack.pop();
} }
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, public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light,
PlayerEntityRenderState playerEntityRenderState, float f, float g) { PlayerEntityRenderState playerEntityRenderState, float f, float g) {
if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible) { if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible
|| playerEntityRenderState.skinTextures.capeTexture() != null) {
return; return;
} }
if (playerEntityRenderState.skinTextures.capeTexture() != null) { SaturnPlayer player = SaturnPlayer.get(playerEntityRenderState.name);
return;
} if (player == null) {
String name = playerEntityRenderState.name;
String uuid = Auth.playerNames.get(name);
if (uuid == null || !Auth.players.containsKey(uuid)) {
return; return;
} }
Identifier customCape = Cloaks.getCurrentCloakTexture(uuid); Identifier customCape = Cloaks.getCurrentCloakTexture(player.uuid);
if (customCape == null || this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, if (customCape == null
LayerType.WINGS)) { || this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.WINGS)) {
return; return;
} }
@@ -309,44 +295,39 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
skyLight = Math.max(skyLight, minBrightness); skyLight = Math.max(skyLight, minBrightness);
light = (skyLight << 20) | (blockLight << 4); light = (skyLight << 20) | (blockLight << 4);
matrixStack.push();
VertexConsumer vertexConsumer = vertexConsumerProvider VertexConsumer vertexConsumer = vertexConsumerProvider
.getBuffer(RenderLayer.getEntityAlpha(customCape)); .getBuffer(RenderLayer.getEntityAlpha(customCape));
float targetVelocity = ((6.0F + playerEntityRenderState.field_53537 / 2.0F matrixStack.push();
+ playerEntityRenderState.field_53536) * 0.02f);
targetVelocity = Math.max(0.0f, targetVelocity - 0.1f);
float accelerationRate = 0.02f; matrixStack.translate(0.0f, 0.0f, 0.19f);
this.currentVelocity = this.currentVelocity
+ (targetVelocity - this.currentVelocity) * accelerationRate;
float rotation = this.currentVelocity * 72.0f; if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) {
float curve = this.currentVelocity * 0.4f;
if (playerEntityRenderState.sneaking && playerEntityRenderState.isInSneakingPose) {
matrixStack.translate(0, 0.24f, 0);
matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(27.0f));
curve += 0.2f;
}
matrixStack.translate(0.0f, -0.25f, 0.0f);
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack,
LayerType.HUMANOID)) {
matrixStack.translate(0.0F, -0.053125F, 0.06875F); matrixStack.translate(0.0F, -0.053125F, 0.06875F);
} }
if (playerEntityRenderState.isSwimming) { long now = System.currentTimeMillis();
curve = 0.0f; if (now - lastUpdate >= 20) {
rotation = 0.0f; 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;
} }
if (!SaturnClientConfig.bendyCloaks.value) { renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light, OverlayTexture.DEFAULT_UV);
curve = 0.0f;
}
renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light,
OverlayTexture.DEFAULT_UV, Math.min(rotation, 60.0f), Math.min(curve, 0.5f));
matrixStack.pop(); matrixStack.pop();
} }
@@ -1,8 +1,6 @@
package org.saturnclient.saturnclient.cosmetics; package org.saturnclient.saturnclient.cosmetics;
import org.saturnclient.saturnclient.auth.SaturnPlayer; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
@@ -29,10 +27,9 @@ public class HatFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState,
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
PlayerEntityRenderState state, float limbAngle, float limbDistance) { PlayerEntityRenderState state, float limbAngle, float limbDistance) {
ItemStack headItem = state.equippedHeadStack; ItemStack headItem = state.equippedHeadStack;
String uuid = Auth.playerNames.get(state.name); SaturnPlayer player = SaturnPlayer.get(state.name);
if (headItem.isEmpty() && uuid != null && !state.invisible) {
SaturnPlayer player = Auth.players.get(uuid); if (!headItem.isEmpty() || state.invisible || player == null || player.hat.isEmpty()) {
if (player == null || player.hat == null || player.hat.isEmpty()) {
return; return;
} }
@@ -63,4 +60,3 @@ public class HatFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState,
matrices.pop(); matrices.pop();
} }
} }
}
@@ -2,8 +2,10 @@ package org.saturnclient.saturnclient.cosmetics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.saturnclient.saturnclient.auth.SaturnPlayer; import java.util.UUID;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
public class Hats { public class Hats {
public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" }; public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
@@ -13,16 +15,16 @@ public class Hats {
availableHats.add(0, ""); availableHats.add(0, "");
} }
public static void setHat(String uuid, String hatName) { public static void setHat(String hatName) {
setHatSilent(uuid, hatName); if (availableHats.contains(hatName)) {
Auth.setHat(hatName); setHat(ServiceClient.uuid, hatName);
ServiceClient.setHat(hatName);
}
} }
public static void setHatSilent(String uuid, String hatName) { public static void setHat(UUID uuid, String hatName) {
SaturnPlayer player = Auth.players.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null) { if (player != null) {
Auth.players.put(uuid, new SaturnPlayer("", hatName));
} else {
player.hat = hatName; player.hat = hatName;
} }
} }
@@ -1,10 +1,11 @@
package org.saturnclient.saturnclient.cosmetics.cloaks; package org.saturnclient.saturnclient.cosmetics.cloaks;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@@ -32,8 +33,8 @@ public class Cloaks {
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/"; private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
public static final List<String> availableCloaks = new ArrayList<>(); public static final List<String> availableCloaks = new ArrayList<>();
public static Identifier cloakCacheIdentifier = null; public static Identifier cloakCacheIdentifier = null;
public static final Map<String, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>(); public static final Map<UUID, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
private static final Map<String, Long> lastFrameTime = new ConcurrentHashMap<>(); private static final Map<UUID, Long> lastFrameTime = new ConcurrentHashMap<>();
private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool( private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool(
Math.min(4, Runtime.getRuntime().availableProcessors()), Math.min(4, Runtime.getRuntime().availableProcessors()),
@@ -54,17 +55,6 @@ public class Cloaks {
availableCloaks.add(0, ""); availableCloaks.add(0, "");
} }
/**
* Handles loading and caching of a new cloak texture.
*
* @param cloakName Name of the cloak file to load
*/
public static void setCloak(String uuid, String cloakName) {
Auth.setCloak(cloakName);
setCloakSilent(uuid, cloakName);
SaturnClient.LOGGER.info("Cloak set to " + cloakName);
}
/** /**
* Sets cloak of the current player * Sets cloak of the current player
* *
@@ -72,7 +62,8 @@ public class Cloaks {
*/ */
public static void setCloak(String cloakName) { public static void setCloak(String cloakName) {
if (availableCloaks.contains(cloakName)) { if (availableCloaks.contains(cloakName)) {
setCloak(Auth.uuid, cloakName); setCloak(ServiceClient.uuid, cloakName);
ServiceClient.setCloak(cloakName);
} }
} }
@@ -82,11 +73,10 @@ public class Cloaks {
* *
* @param cloakName Name of the cloak file to load * @param cloakName Name of the cloak file to load
*/ */
public static void setCloakSilent(String uuid, String cloakName) { public static void setCloak(UUID uuid, String cloakName) {
SaturnPlayer player = Auth.players.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null) {
Auth.players.put(uuid, new SaturnPlayer(cloakName, null)); if (player != null) {
} else {
player.cloak = cloakName; player.cloak = cloakName;
} }
@@ -99,8 +89,9 @@ public class Cloaks {
* *
* @param cloakName Name of the cloak file to load * @param cloakName Name of the cloak file to load
*/ */
public static void loadCloak(String uuid) { public static void loadCloak(UUID uuid) {
SaturnPlayer player = Auth.players.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player != null && player.cloak != null) { if (player != null && player.cloak != null) {
if (!player.cloak.isEmpty()) { if (!player.cloak.isEmpty()) {
if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) { if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) {
@@ -146,7 +137,7 @@ public class Cloaks {
} }
} }
public static CompletableFuture<Void> loadAnimatedCloakAsync(String uuid, String cloakName) { public static CompletableFuture<Void> loadAnimatedCloakAsync(UUID uuid, String cloakName) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
String fileName = cloakName + ".gif"; String fileName = cloakName + ".gif";
@@ -226,16 +217,14 @@ public class Cloaks {
}, CLOAK_LOADER_EXECUTOR); }, CLOAK_LOADER_EXECUTOR);
} }
public static Identifier getCurrentCloakTexture(String uuid) { public static Identifier getCurrentCloakTexture(UUID uuid) {
if (!Auth.players.containsKey(uuid)) { SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null || player.cloak.isEmpty()) {
return null; return null;
} }
String cloakName = Auth.players.get(uuid).cloak; String cloakName = player.cloak;
if (cloakName.isEmpty()) {
return null;
}
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) { if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
List<AnimatedCloakData> frames = animatedCloaks.get(uuid); List<AnimatedCloakData> frames = animatedCloaks.get(uuid);
@@ -6,8 +6,8 @@ import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.InputUtil;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.config.Config;
import org.saturnclient.ui2.screens.EmoteWheel; import org.saturnclient.ui2.screens.EmoteWheel;
import org.saturnclient.ui2.screens.ShiftMenu; import org.saturnclient.ui2.screens.ShiftMenu;
@@ -39,7 +39,7 @@ public class KeyInputHandler {
client.setScreen(new ShiftMenu()); client.setScreen(new ShiftMenu());
} }
if (SaturnClientConfig.openEmoteWheel.wasKeyPressed() && client.currentScreen == null) { if (Config.openEmoteWheel.wasKeyPressed() && client.currentScreen == null && !InputUtil.isKeyPressed(client.getWindow().getHandle(), GLFW.GLFW_KEY_F3)) {
client.setScreen(new EmoteWheel()); client.setScreen(new EmoteWheel());
} }
@@ -47,7 +47,7 @@ public class KeyInputHandler {
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player);
if (animationStack.isActive() && animationStack.getPriority() == 1000) { if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000); animationStack.removeLayer(1000);
Auth.emote(""); ServiceClient.emote("");
} }
} }
}); });
@@ -9,7 +9,7 @@ import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions; import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.ModManager; import org.saturnclient.modules.ModManager;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor; import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
@@ -14,8 +14,11 @@ import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.Roles;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import java.util.UUID;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
@@ -37,11 +40,11 @@ public abstract class NameTagMixin<S extends EntityRenderState> {
private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices, private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
String uuid = isSaturn(state); UUID uuid = isSaturn(state);
if (uuid != null) { if (uuid != null) {
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator()) MutableText iconText = Text.literal(Roles.getSaturnIndicator())
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); .styled(style -> style.withColor(Roles.getIconColor(uuid)));
Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE)); Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE));
@@ -52,12 +55,12 @@ public abstract class NameTagMixin<S extends EntityRenderState> {
} }
} }
private String isSaturn(S state) { private UUID isSaturn(S state) {
if (state instanceof PlayerEntityRenderState) { if (state instanceof PlayerEntityRenderState) {
String name = ((PlayerEntityRenderState) state).name; String name = ((PlayerEntityRenderState) state).name;
String uuid = Auth.playerNames.get(name); SaturnPlayer player = SaturnPlayer.get(name);
if (Auth.players.containsKey(uuid)) { if (player != null) {
return uuid; return player.uuid;
} }
} }
@@ -0,0 +1,26 @@
package org.saturnclient.saturnclient.mixin;
import org.saturnclient.saturnclient.config.Config;
import org.saturnclient.ui2.screens.TitleMenu;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.TitleScreen;
@Mixin(MinecraftClient.class)
public class SetScreenMixin {
@ModifyVariable(method = "setScreen", at = @At("HEAD"), argsOnly = true)
private Screen replaceTitleScreen(Screen screen) {
if (Config.saturnTitleScreen.value) {
if (screen instanceof TitleScreen)
return new TitleMenu();
if (screen == null && ((MinecraftClient) (Object) this).world == null)
return new TitleMenu();
}
return screen;
}
}
@@ -0,0 +1,160 @@
package org.saturnclient.saturnclient.mixin;
import java.util.Optional;
import java.util.function.Consumer;
import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Textures;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Final;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.SplashOverlay;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.resource.ResourceReload;
import net.minecraft.util.Util;
import net.minecraft.util.math.MathHelper;
@Mixin(SplashOverlay.class)
public abstract class SplashOverlayMixin {
@Shadow
private MinecraftClient client;
@Shadow
private ResourceReload reload;
@Shadow
private long reloadCompleteTime;
@Shadow
private long reloadStartTime;
@Shadow @Final
private boolean reloading;
@Shadow @Final
private Consumer<Optional<Throwable>> exceptionHandler;
@Shadow
private float progress;
/**
* @author SaturnClient
* @reason Replace vanilla splash rendering completely but keep exit logic
*/
@Overwrite
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
int screenWidth = context.getScaledWindowWidth();
int screenHeight = context.getScaledWindowHeight();
long currentTime = Util.getMeasuringTimeMs();
// Initialize reload start time if not set
if (reloadStartTime == -1L) {
reloadStartTime = currentTime;
}
// Compute fade-out progress after reload completes
float fadeOutProgress = reloadCompleteTime > -1L
? (float) (currentTime - reloadCompleteTime) / 1000f
: -1f;
int alpha = fadeOutProgress >= 0f
? MathHelper.ceil((1.0f - MathHelper.clamp(fadeOutProgress - 1f, 0f, 1f)) * 255f)
: 255;
// Compute fade-in progress while reloading
float reloadProgress = reloadStartTime > -1L
? (float) (currentTime - reloadStartTime) / 500f
: -1f;
// Get render scope for better UI
RenderScope scope = new RenderScope(context);
scope.setRenderLayer(RenderLayer::getGuiTextured);
// Draw background overlay
if (fadeOutProgress >= 1.0F) {
renderCurrentScreen(context, mouseX, mouseY, delta);
int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f);
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
Theme.withAlpha(overlayAlpha, 0xFFFFFFFF));
} else if (reloading) {
if (reloadProgress < 1.0F) {
renderCurrentScreen(context, mouseX, mouseY, delta);
}
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
Theme.withAlpha(alpha, 0xFFFFFFFF));
} else {
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
Theme.withAlpha(alpha, 0xFFFFFFFF));
}
float u = this.reload.getProgress();
this.progress = MathHelper.clamp(this.progress * 0.95F + u * 0.050000012F, 0.0F, 1.0F);
// Draw central logo
drawLogo(scope, screenWidth, screenHeight, alpha);
renderProgressBar(scope, context, screenWidth, screenHeight, alpha);
// Remove overlay after fade-out completes
if (fadeOutProgress >= 2.0F) {
client.setOverlay(null);
}
// Handle reload completion and exceptions
handleReloadCompletion(context);
}
private void renderCurrentScreen(DrawContext context, int mouseX, int mouseY, float delta) {
if (client.currentScreen != null) {
client.currentScreen.render(context, mouseX, mouseY, delta);
}
}
/** Draw the main logo centered */
private void drawLogo(RenderScope scope, int screenWidth, int screenHeight, int alpha) {
scope.drawTexture(Textures.LOGO, (screenWidth - 60) / 2, ((screenHeight - 60) / 2) - 20, 0, 0, 60, 60,
Theme.withAlpha(alpha, 0xFFFFFFFF));
}
private void renderProgressBar(RenderScope scope, DrawContext context, int screenWidth, int screenHeight,
int alpha) {
int progressY = ((screenHeight - 5) / 2) + 25;
context.drawBorder((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
int progressWidth = Math.min((int) (progress * 120), 120);
int progressX = (screenWidth - 120) / 2;
context.fill(progressX, progressY, progressX + progressWidth, progressY + 5,
Theme.withAlpha(alpha, 0xFFFFFFFF));
}
/** Compute overlay alpha from fade progress */
private int computeOverlayAlpha(float progress) {
return MathHelper.ceil((1.0F - MathHelper.clamp(progress, 0.0F, 1.0F)) * 255.0F);
}
/** Handle reload completion logic */
private void handleReloadCompletion(DrawContext context) {
float reloadProgress = reloadStartTime > -1L ? (float) (Util.getMeasuringTimeMs() - reloadStartTime) / 500f
: -1f;
if (reloadCompleteTime == -1L && reload.isComplete() && (!reloading || reloadProgress >= 2.0F)) {
try {
reload.throwException();
exceptionHandler.accept(Optional.empty());
} catch (Throwable ex) {
exceptionHandler.accept(Optional.of(ex));
}
reloadCompleteTime = Util.getMeasuringTimeMs();
if (client.currentScreen != null) {
client.currentScreen.init(client, context.getScaledWindowWidth(), context.getScaledWindowHeight());
}
}
}
}
@@ -1,13 +1,14 @@
package org.saturnclient.saturnclient.mixin; package org.saturnclient.saturnclient.mixin;
import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import java.util.UUID;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.Roles;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@@ -30,13 +31,13 @@ public abstract class TabListMixin {
public Text getDisplayName() { public Text getDisplayName() {
GameProfile profile = ((PlayerListEntry) (Object) this).getProfile(); GameProfile profile = ((PlayerListEntry) (Object) this).getProfile();
String name = profile.getName(); String name = profile.getName();
String uuid = profile.getId().toString().replaceAll("-", ""); UUID uuid = profile.getId();
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator()) if (SaturnPlayer.get(uuid) != null) {
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); return Text.literal(Roles.getSaturnIndicator())
.styled(style -> style.withColor(Roles.getIconColor(uuid)))
Text nameText = Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)); .append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)));
}
return Auth.players.containsKey(uuid) ? iconText.append(nameText) : Text.literal(name); return Text.literal(name);
} }
} }
@@ -1,22 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui2.screens.TitleMenu;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.gui.screen.TitleScreen;
@Mixin(TitleScreen.class)
public class TitleScreenMixin {
@Inject(method = "init", at = @At("HEAD"), cancellable = true)
private void onInit(CallbackInfo ci) {
if (SaturnClientConfig.saturnTitleScreen.value) {
SaturnClient.client.setScreen(new TitleMenu());
ci.cancel();
}
}
}
+16 -10
View File
@@ -1,9 +1,6 @@
package org.saturnclient.ui2; package org.saturnclient.ui2;
import java.util.function.Function;
import org.saturnclient.ui2.anim.Animation; import org.saturnclient.ui2.anim.Animation;
import org.saturnclient.ui2.anim.Curve;
public class Element { public class Element {
public int x; public int x;
@@ -14,7 +11,6 @@ public class Element {
public boolean focused; public boolean focused;
public Animation animation; public Animation animation;
public float scale = 1.0f; public float scale = 1.0f;
public Function<Double, Double> curve = Curve::easeInOutCubic;
public Integer duration = null; // Element duration, will fade out if not null public Integer duration = null; // Element duration, will fade out if not null
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
@@ -32,6 +28,22 @@ public class Element {
public void charTyped(char typedChar) { public void charTyped(char typedChar) {
} }
public void mouseClicked(double mouseX, double mouseY, int button) {
}
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
}
public void mouseReleased(double mouseX, double mouseY, int button) {
}
public void playAnimationFrame(long elapsed) {
if (this.animation != null && elapsed >= this.animation.delay) {
this.animation.tick(this.animation.curve
.apply(Math.min(1.0, (double) (elapsed - this.animation.delay) / this.animation.duration)), this);
}
}
public Element dimensions(int width, int height) { public Element dimensions(int width, int height) {
this.width = width; this.width = width;
this.height = height; this.height = height;
@@ -70,12 +82,6 @@ public class Element {
return this; return this;
} }
public Element animation(Animation animation, Function<Double, Double> curve) {
this.animation = animation;
this.curve = curve;
return this;
}
public Element scale(float scale) { public Element scale(float scale) {
this.scale = scale; this.scale = scale;
return this; return this;
@@ -4,13 +4,15 @@ public class ElementContext {
public int mouseX, mouseY; public int mouseX, mouseY;
public int elementWidth, elementHeight; public int elementWidth, elementHeight;
public float elementScale; public float elementScale;
public long elapsed;
public ElementContext(int mouseX, int mouseY, Element e) { public ElementContext(long elapsed, int mouseX, int mouseY, Element e) {
this.mouseX = mouseX - e.x; this.mouseX = mouseX - e.x;
this.mouseY = mouseY - e.y; this.mouseY = mouseY - e.y;
this.elementWidth = e.width; this.elementWidth = e.width;
this.elementHeight = e.height; this.elementHeight = e.height;
this.elementScale = e.scale; this.elementScale = e.scale;
this.elapsed = elapsed;
} }
public boolean isHovering() { public boolean isHovering() {
@@ -9,11 +9,17 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.SvgTexture;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.font.TextRenderer.TextLayerType; import net.minecraft.client.font.TextRenderer.TextLayerType;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.ScreenRect; import net.minecraft.client.gui.ScreenRect;
import net.minecraft.client.render.DiffuseLighting; import net.minecraft.client.render.DiffuseLighting;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
@@ -42,6 +48,10 @@ public class RenderScope {
private int opacity = 255 << 24; private int opacity = 255 << 24;
private final ItemRenderState itemRenderState; private final ItemRenderState itemRenderState;
public RenderScope(DrawContext context) {
this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
}
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) { public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
this.matrices = matrices; this.matrices = matrices;
this.vertexConsumers = vertexConsumers; this.vertexConsumers = vertexConsumers;
@@ -217,6 +227,11 @@ public class RenderScope {
private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1, private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1,
float v2, int color) { float v2, int color) {
if (sprite.toString().endsWith(".svg")) {
sprite = SvgTexture.getSvg(MinecraftClient.getInstance(), sprite, (x2 - x1) * 2, (y2 - y1) * 2);
}
if (color == 0) if (color == 0)
return; return;
x1 *= 4; x1 *= 4;
@@ -391,4 +406,45 @@ public class RenderScope {
sprite.getMinV(), sprite.getMaxV(), color); sprite.getMinV(), sprite.getMaxV(), color);
} }
} }
public void fill(int x1, int y1, int x2, int y2, int color) {
this.fill(x1, y1, x2, y2, 0, color);
}
public void fill(int x1, int y1, int x2, int y2, int z, int color) {
this.fill(RenderLayer.getGui(), x1, y1, x2, y2, z, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int color) {
this.fill(layer, x1, y1, x2, y2, 0, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int z, int color) {
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
int i;
if (x1 < x2) {
i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
i = y1;
y1 = y2;
y2 = i;
}
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(layer);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, (float) z).color(color);
}
public void drawBorder(int x, int y, int width, int height, int color) {
this.fill(x, y, x + width, y + 1, color);
this.fill(x, y + height - 1, x + width, y + height, color);
this.fill(x, y + 1, x + 1, y + height - 1, color);
this.fill(x + width - 1, y + 1, x + width, y + height - 1, color);
}
} }
@@ -1,11 +1,11 @@
package org.saturnclient.ui2; package org.saturnclient.ui2;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor; import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.anim.Animation;
import org.saturnclient.ui2.anim.SlideXAbsolute;
import org.saturnclient.ui2.components.ElementRenderer; import org.saturnclient.ui2.components.ElementRenderer;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@@ -15,6 +15,8 @@ import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.RotatingCubeMapRenderer; import net.minecraft.client.gui.RotatingCubeMapRenderer;
import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.render.DefaultFramebufferSet; import net.minecraft.client.render.DefaultFramebufferSet;
import net.minecraft.client.texture.ResourceTexture;
import net.minecraft.client.texture.TextureManager;
import net.minecraft.client.util.Pool; import net.minecraft.client.util.Pool;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@@ -22,12 +24,13 @@ import net.minecraft.util.Identifier;
public abstract class SaturnScreen extends Screen { public abstract class SaturnScreen extends Screen {
private final Pool pool = new Pool(3); private final Pool pool = new Pool(3);
protected List<Element> elements = new ArrayList<>(); protected List<Element> elements = new ArrayList<>();
public int blurDuration = 700; protected float backgroundOpacity = 1.0f;
public float blurProgress = 0.0f; protected Instant start = null;
public int backgroundBlur = 10; public int backgroundBlur = 10;
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer( protected static final Identifier PANORAMA = Identifier.of("saturnclient",
Identifier.of("saturnclient", "textures/gui/title/background/panorama")); "textures/gui/title/background/panorama");
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(PANORAMA);
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER; public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
@@ -35,41 +38,24 @@ public abstract class SaturnScreen extends Screen {
ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER); ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER);
} }
public static void preload(MinecraftClient client) {
TextureManager textureManager = client.getTextureManager();
// Force upload each texture to GPU
for (int i = 0; i < 6; i++) {
String var10001 = PANORAMA.getPath();
Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png");
ResourceTexture resourceTexture = new ResourceTexture(tex);
textureManager.registerTexture(tex, resourceTexture);
}
}
public SaturnScreen(String title) { public SaturnScreen(String title) {
super(Text.literal(title)); super(Text.literal(title));
} }
public void draw(Element element) { public void draw(Element element) {
synchronized (elements) { ElementRenderer.draw(elements, element);
elements.add(element);
}
if (element.animation != null) {
element.animation.init(element);
Animation.execute((Float progress) -> {
element.animation.tick(progress, element);
}, element.animation.duration);
}
if (element.duration != null) {
new Thread(() -> {
try {
Animation fadeOut = new SlideXAbsolute(700, -(width - element.x));
Thread.sleep(element.duration);
fadeOut.init(element);
Animation.executeSync((Float progress) -> {
fadeOut.tick(progress, element);
}, fadeOut.duration);
synchronized (elements) {
elements.remove(element);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
} }
@Override @Override
@@ -78,10 +64,6 @@ public abstract class SaturnScreen extends Screen {
width *= 2; width *= 2;
height *= 2; height *= 2;
ui(); // abstraction to render the saturn ui and also render extra stuff here ui(); // abstraction to render the saturn ui and also render extra stuff here
Animation.execute((Float progress) -> {
blurProgress = progress;
}, blurDuration);
} }
public abstract void ui(); public abstract void ui();
@@ -89,18 +71,24 @@ public abstract class SaturnScreen extends Screen {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) { public void render(DrawContext context, int mouseX, int mouseY, float delta) {
if (start == null) {
start = Instant.now();
}
long elapsed = Duration.between(start, Instant.now()).toMillis();
mouseX *= 2; mouseX *= 2;
mouseY *= 2; mouseY *= 2;
if (client.world == null && client.getCurrentServerEntry() == null) { if (client.world == null && client.getCurrentServerEntry() == null) {
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta); ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, backgroundOpacity, delta);
} }
PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect( PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
Identifier.ofVanilla("blur"), Identifier.ofVanilla("blur"),
DefaultFramebufferSet.MAIN_ONLY); DefaultFramebufferSet.MAIN_ONLY);
if (postEffectProcessor != null) { if (postEffectProcessor != null) {
postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress); postEffectProcessor.setUniforms("Radius", backgroundBlur * Math.min((float) elapsed / 700, 1.0f));
postEffectProcessor.render(this.client.getFramebuffer(), this.pool); postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
} }
@@ -113,7 +101,7 @@ public abstract class SaturnScreen extends Screen {
renderScope.matrices.scale(0.5f, 0.5f, 0.5f); renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
ElementRenderer.render(new ArrayList<>(elements), renderScope, mouseX, mouseY); ElementRenderer.render(new ArrayList<>(elements), elapsed, renderScope, mouseX, mouseY);
renderScope.matrices.pop(); renderScope.matrices.pop();
} }
@@ -128,6 +116,26 @@ public abstract class SaturnScreen extends Screen {
return false; return false;
} }
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
mouseX *= 2;
mouseY *= 2;
ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY);
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
mouseX *= 2;
mouseY *= 2;
ElementRenderer.mouseReleased(elements, mouseX, mouseY, button);
return super.mouseReleased(mouseX, mouseY, button);
}
@Override @Override
public boolean mouseScrolled( public boolean mouseScrolled(
double mouseX, double mouseX,
@@ -51,7 +51,6 @@ public class Utils {
} }
public static <T> T getOrNull(List<T> list, int index) { public static <T> T getOrNull(List<T> list, int index) {
SaturnClient.LOGGER.info("Accessing index {} in list of size {}", index, list.size());
return (index >= 0 && index < list.size()) ? list.get(index) : null; return (index >= 0 && index < list.size()) ? list.get(index) : null;
} }
@@ -1,75 +1,37 @@
package org.saturnclient.ui2.anim; package org.saturnclient.ui2.anim;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
public abstract class Animation { public abstract class Animation {
public int duration; public int duration;
public int delay = 0;
public Function<Double, Double> curve = Curve::easeInOutCubic;
Animation(AnimationConfig config) {
this.duration = config.duration.value;
switch (config.curve.value) {
case 0:
this.curve = Curve::easeInOutCubic;
break;
case 1:
this.curve = Curve::easeOutCubic;
break;
case 2:
this.curve = Curve::easeInOutBack;
break;
default:
break;
}
}
Animation(int duration) { Animation(int duration) {
this.duration = duration; this.duration = duration;
} }
public abstract void tick(float progress, Element element); public abstract void tick(double progress, Element element);
public abstract void init(Element element); public abstract void init(Element element);
public static void execute(Consumer<Float> tick, int duration) {
new Thread(() -> {
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
float progress = 0.0f;
double incremental = (1000 - duration) / 10000.0;
for (int i = 0; i <= 100; i++) {
float newProgress = (float) (double) curveFunction.apply(i / 100.0);
while (Math.abs(newProgress - progress) > 1e-6) {
if (progress < newProgress) {
progress += incremental;
if (progress > newProgress) progress = newProgress;
} else {
progress -= incremental;
if (progress < newProgress) progress = newProgress;
}
tick.accept(newProgress);
try {
Thread.sleep(duration / 100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
public static void executeSync(Consumer<Float> tick, int duration) {
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
float progress = 0.0f;
double incremental = (1000 - duration) / 10000.0;
for (int i = 0; i <= 100; i++) {
float newProgress = (float) (double) curveFunction.apply(i / 100.0);
while (Math.abs(newProgress - progress) > 1e-6) {
if (progress < newProgress) {
progress += incremental;
if (progress > newProgress) progress = newProgress;
} else {
progress -= incremental;
if (progress < newProgress) progress = newProgress;
}
tick.accept(newProgress);
try {
Thread.sleep(duration / 100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
} }
@@ -9,12 +9,8 @@ public class Curve {
return (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); return (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
} }
public static double easeInOutCubicReverse(double x) {
return -(x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
}
public static double easeInOutBack(double x) { public static double easeInOutBack(double x) {
double c1 = 1.70158; double c1 = 2.5;
double c2 = c1 * 1.525; double c2 = c1 * 1.525;
if (x < 0.5) { if (x < 0.5) {
@@ -1,5 +1,6 @@
package org.saturnclient.ui2.anim; package org.saturnclient.ui2.anim;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
public class Fade extends Animation { public class Fade extends Animation {
@@ -9,14 +10,18 @@ public class Fade extends Animation {
super(duration); super(duration);
} }
public Fade(AnimationConfig config) {
super(config);
}
@Override @Override
public void tick(float progress, Element element) { public void tick(double progress, Element element) {
element.opacity = targetOpacity * progress; element.opacity = Math.max(0.1f, targetOpacity * (float) progress);
} }
@Override @Override
public void init(Element element) { public void init(Element element) {
targetOpacity = element.opacity; targetOpacity = element.opacity;
element.opacity = 0; element.opacity = 0.1f;
} }
} }
@@ -1,5 +1,6 @@
package org.saturnclient.ui2.anim; package org.saturnclient.ui2.anim;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
public class FadeOut extends Animation { public class FadeOut extends Animation {
@@ -9,9 +10,13 @@ public class FadeOut extends Animation {
super(duration); super(duration);
} }
public FadeOut(AnimationConfig config) {
super(config);
}
@Override @Override
public void tick(float progress, Element element) { public void tick(double progress, Element element) {
element.opacity = targetOpacity - progress; element.opacity = targetOpacity - (float) progress;
} }
@Override @Override
@@ -0,0 +1,39 @@
package org.saturnclient.ui2.anim;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.ui2.Element;
public class SlideFade extends Animation {
public Fade fade;
public SlideY slideY;
public SlideFade(int duration, int offset) {
super(duration);
this.fade = new Fade(duration);
this.slideY = new SlideY(duration, offset);
}
public SlideFade(AnimationConfig config, int offset) {
super(config);
this.fade = new Fade(duration);
this.slideY = new SlideY(duration, offset);
}
@Override
public void tick(double progress, Element element) {
fade.delay = this.delay;
fade.duration = this.duration;
slideY.delay = this.delay;
slideY.duration = this.duration;
fade.tick(progress, element);
slideY.tick(progress, element);
}
@Override
public void init(Element element) {
fade.init(element);
slideY.init(element);
}
}
@@ -1,11 +1,20 @@
package org.saturnclient.ui2.anim; package org.saturnclient.ui2.anim;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
public class SlideX extends Animation { public class SlideX extends Animation {
int offset = 20; int offset = 20;
int maxOffset = 20; double startX;
int elementX;
public SlideX(AnimationConfig config) {
super(config);
}
public SlideX(AnimationConfig config, int offset) {
super(config);
this.offset = offset;
}
public SlideX(int duration) { public SlideX(int duration) {
super(duration); super(duration);
@@ -14,23 +23,17 @@ public class SlideX extends Animation {
public SlideX(int duration, int offset) { public SlideX(int duration, int offset) {
super(duration); super(duration);
this.offset = offset; this.offset = offset;
this.maxOffset = offset;
}
public SlideX(int duration, int offset, int maxOffset) {
super(duration);
this.offset = offset;
this.maxOffset = maxOffset; // ✅ fixed
}
@Override
public void tick(float progress, Element element) {
element.x = elementX + (int) (offset * progress);
} }
@Override @Override
public void init(Element element) { public void init(Element element) {
element.x -= offset; startX = element.x - offset;
elementX = element.x; element.x = (int) startX;
}
@Override
public void tick(double progress, Element element) {
double currentX = startX + offset * progress;
element.x = (int) currentX;
} }
} }
@@ -1,35 +0,0 @@
package org.saturnclient.ui2.anim;
import org.saturnclient.ui2.Element;
public class SlideXAbsolute extends Animation {
int offset = 20;
int maxOffset = 20;
int elementX;
public SlideXAbsolute(int duration) {
super(duration);
}
public SlideXAbsolute(int duration, int offset) {
super(duration);
this.offset = offset;
this.maxOffset = offset;
}
public SlideXAbsolute(int duration, int offset, int maxOffset) {
super(duration);
this.offset = offset;
this.maxOffset = maxOffset;
}
@Override
public void tick(float progress, Element element) {
element.x = elementX + (int) (offset * progress);
}
@Override
public void init(Element element) {
elementX = element.x;
}
}
@@ -1,11 +1,20 @@
package org.saturnclient.ui2.anim; package org.saturnclient.ui2.anim;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
public class SlideY extends Animation { public class SlideY extends Animation {
int offset = 20; int offset = 20;
int maxOffset = 20; double startY;
int elementY;
public SlideY(AnimationConfig config) {
super(config);
}
public SlideY(AnimationConfig config, int offset) {
super(config);
this.offset = offset;
}
public SlideY(int duration) { public SlideY(int duration) {
super(duration); super(duration);
@@ -13,26 +22,18 @@ public class SlideY extends Animation {
public SlideY(int duration, int offset) { public SlideY(int duration, int offset) {
super(duration); super(duration);
this.offset = offset; this.offset = offset;
this.maxOffset = offset;
}
public SlideY(int duration, int offset, int maxOffset) {
super(duration);
this.offset = offset;
this.maxOffset = offset;
}
@Override
public void tick(float progress, Element element) {
element.y = elementY + (int) (offset * progress);
} }
@Override @Override
public void init(Element element) { public void init(Element element) {
element.y -= offset; startY = element.y - offset;
elementY = element.y; element.y = (int) startY;
}
@Override
public void tick(double progress, Element element) {
double currentY = startY + offset * progress;
element.y = (int) currentY;
} }
} }
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.components; package org.saturnclient.ui2.components;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,13 +9,7 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class CosmeticPreview extends Element { public class CosmeticPreview extends Element {
private static ThemeManager theme = new ThemeManager("CosmeticPreview", "selected", "hovering"); static int padding = 14;
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x89000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> padding = theme.property("padding", Property.integer(14));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {}
Identifier sprite; Identifier sprite;
private Runnable onClick; private Runnable onClick;
@@ -33,22 +26,16 @@ public class CosmeticPreview extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
theme.setState("hovering"); isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value);
} else if (isSelected) {
theme.setState("selected");
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
int texWidth = width - padding.value; int texWidth = width - padding;
int texHeight = height - padding.value; int texHeight = height - padding;
renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value); renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
Theme.FOREGROUND.value);
theme.setState(null);
} }
@Override @Override
@@ -7,7 +7,6 @@ import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.Utils; import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.anim.Animation;
public class ElementRenderer { public class ElementRenderer {
public static void draw(List<Element> elements, Element element) { public static void draw(List<Element> elements, Element element) {
@@ -17,20 +16,18 @@ public class ElementRenderer {
if (element.animation != null) { if (element.animation != null) {
element.animation.init(element); element.animation.init(element);
Animation.execute((Float progress) -> {
element.animation.tick(progress, element);
}, element.animation.duration);
} }
} }
public static void render(List<Element> elements, RenderScope renderScope, int mouseX, int mouseY) { public static void render(List<Element> elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) {
for (Element element : elements) { for (Element element : elements) {
element.playAnimationFrame(elapsed);
renderScope.matrices.push(); renderScope.matrices.push();
renderScope.setOpacity(element.opacity); renderScope.setOpacity(element.opacity);
renderScope.matrices.translate(element.x, element.y, 0); renderScope.matrices.translate(element.x, element.y, 0);
renderScope.matrices.scale(element.scale, element.scale, 1.0f); renderScope.matrices.scale(element.scale, element.scale, 1.0f);
element.render(renderScope, new ElementContext(mouseX, mouseY, element)); element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, element));
renderScope.matrices.pop(); renderScope.matrices.pop();
renderScope.setRenderLayer(null); renderScope.setRenderLayer(null);
} }
@@ -44,7 +41,10 @@ public class ElementRenderer {
int adjustedMouseX = (int) mouseX - element.x; int adjustedMouseX = (int) mouseX - element.x;
int adjustedMouseY = (int) mouseY - element.y; int adjustedMouseY = (int) mouseY - element.y;
if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, element.height, element.scale)) { element.mouseClicked(adjustedMouseX * element.scale, adjustedMouseY * element.scale, button);
if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width,
element.height, element.scale)) {
element.focused = true; element.focused = true;
element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale)); element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale));
} }
@@ -59,7 +59,8 @@ public class ElementRenderer {
double horizontalAmount, double horizontalAmount,
double verticalAmount) { double verticalAmount) {
for (Element element : new ArrayList<>(elements)) { for (Element element : new ArrayList<>(elements)) {
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, element.scale)) { if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height,
element.scale)) {
element.scroll( element.scroll(
(int) mouseX - element.x, (int) mouseX - element.x,
(int) mouseY - element.y, (int) mouseY - element.y,
@@ -81,4 +82,17 @@ public class ElementRenderer {
} }
} }
} }
public static void mouseDragged(List<Element> elements, double mouseX, double mouseY, int button, double deltaX,
double deltaY) {
for (Element element : new ArrayList<>(elements)) {
element.mouseDragged(mouseX - element.x, mouseY - element.y, button, deltaX, deltaY);
}
}
public static void mouseReleased(List<Element> elements, double mouseX, double mouseY, int button) {
for (Element element : new ArrayList<>(elements)) {
element.mouseReleased(mouseX - element.x, mouseY - element.y, button);
}
}
} }
@@ -2,8 +2,7 @@ package org.saturnclient.ui2.components;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
@@ -15,23 +14,12 @@ import org.saturnclient.ui2.screens.ConfigEditor;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayer;
public class SaturnModule extends Element { public class SaturnModule extends Element {
private static ThemeManager theme = new ThemeManager("Module", "enabled", "hovering");
private static Property<Integer> bgColor = theme.property("bg", Property.color(0xFF1d202d));
private static Property<Integer> fgColor = theme.property("fg", Property.color(0xFFA1A2B8));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFA1A2B8));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0xFF2e3248));
private static Property<Integer> tagBg = theme.property("tag-bg", Property.color(0xFF2e3248));
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.color(10));
private static Property<Integer> font = theme.property("font", Property.font(1));
private static Property<Integer> radius = theme.property("radius", Property.color(10));
static { private final Module mod;
theme.propertyStateDefault("enabled", "bg", 0xFF1d202d);
theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee);
theme.propertyStateDefault("hovering", "fg", 0xFF845eee);
}
private Module mod; static int p = 10; // padding
static int s = 14; // settings icon size
static int expand = 5; // settings icon click area
public SaturnModule(Module mod) { public SaturnModule(Module mod) {
this.mod = mod; this.mod = mod;
@@ -41,60 +29,137 @@ public class SaturnModule extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
int p = 10; boolean hovered = ctx.isHovering();
int s = 14; boolean enabled = mod.isEnabled();
boolean settingsHover = false;
int iconX = width - s - p;
int iconY = height - s - p;
boolean settingsHover = ctx.isHovering(
getSettingsX(),
getSettingsY(),
getSettingsHitBox(),
getSettingsHitBox());
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
if (mod.isEnabled()) { // Resolve theme colors
theme.setState("enabled"); int bgColor = Theme.PRIMARY.value;
} int fgColor = hovered && !settingsHover ? Theme.ACCENT.value : Theme.PRIMARY_FG.value;
if (ctx.isHovering(width - s - p - 5, height - s - p - 5, s + 10, s + 10)) { int iconBg = enabled ? Theme.ACCENT.value : Theme.PRIMARY.value;
settingsHover = true; int iconFg = enabled ? Theme.ACCENT_FG.value : fgColor;
} else if (ctx.isHovering()) {
theme.applyState("hovering");
}
renderScope.drawRoundedRectangle(0, 0, width, height, radius.value, bgColor.value); int settingsColor = settingsHover
? Theme.ACCENT.value
: Theme.withAlpha(160, fgColor);
int h = height - (p * 2); int h = height - (p * 2);
renderScope.drawRoundedRectangle(p, p, h, h, iconRadius.value, iconBg.value); // Main background
renderScope.drawRoundedRectangle(
0, 0,
width, height,
Theme.WIDGET_RADIUS.value,
bgColor);
renderScope.drawTexture(mod.getIconTexture(), p + (p / 2), p + (p / 2), 0, 0, h - p, h - p, iconFg.value); // Icon background
renderScope.drawRoundedRectangle(
p, p,
h, h,
Theme.WIDGET_RADIUS.value,
iconBg);
renderScope.drawText(0.6f, mod.getName(), p+h+4, p + 1, font.value, fgColor.value); // Icon
renderScope.drawTexture(
mod.getIconTexture(),
p + (p / 2),
p + (p / 2),
0, 0,
h - p,
h - p,
iconFg);
renderScope.drawText(0.35f, mod.getVersion(), width - p - (int) (Fonts.getWidth(mod.getVersion(), font.value) * 0.35f), p + 1, font.value, fgColor.value); // Module name
renderScope.drawText(
0.6f,
mod.getName(),
p + h + 4,
p + 1,
Theme.FONT.value,
fgColor);
// Version (right aligned)
String version = mod.getVersion();
int versionWidth = (int) (Fonts.getWidth(version, Theme.FONT.value) * 0.35f);
renderScope.drawText(
0.35f,
version,
width - p - versionWidth,
p + 1,
Theme.FONT.value,
fgColor);
// Tags
int xt = p + h + 4; int xt = p + h + 4;
for (String tag : mod.getTags()) { for (String tag : mod.getTags()) {
int xtb = (int) (Fonts.getWidth(tag, font.value) * 0.4f); int tagWidth = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f);
renderScope.drawRoundedRectangle(xt, p + (h/2), xtb + 6, 12, 5, tagBg.value);
renderScope.drawText(0.4f, tag, xt+2, p + (h/2) + 3, font.value, fgColor.value); renderScope.drawRoundedRectangle(
xt += xtb + 9; xt,
p + (h / 2),
tagWidth + 6,
12,
5,
Theme.ACCENT.value);
renderScope.drawText(
0.4f,
tag,
xt + 2,
p + (h / 2) + 3,
Theme.FONT.value,
Theme.ACCENT_FG.value);
xt += tagWidth + 9;
} }
if (settingsHover) { // Settings icon
theme.setState("hovering"); renderScope.drawTexture(
} Textures.SETTINGS,
iconX,
renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, fgColor.value); iconY,
0, 0,
theme.setState(null); s, s,
settingsColor);
} }
@Override @Override
public void click(int mouseX, int mouseY) { public void click(int mouseX, int mouseY) {
int p = 10; boolean settingsHover = Utils.isHovering(
int s = 14; mouseX - getSettingsX(),
mouseY - getSettingsY(),
getSettingsHitBox(),
getSettingsHitBox(),
1.0f);
if (Utils.isHovering(mouseX - (width - s - p - 5), mouseY - (height - s - p - 5), s + 10, s + 10, 1.0f)) { if (settingsHover) {
SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig())); SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig()));
} else { } else {
mod.setEnabled(!mod.isEnabled()); mod.setEnabled(!mod.isEnabled());
} }
} }
private int getSettingsX() {
return width - s - p - expand;
}
private int getSettingsY() {
return height - s - p - expand;
}
private int getSettingsHitBox() {
return s + expand * 2;
}
} }
@@ -1,9 +1,8 @@
package org.saturnclient.ui2.components; package org.saturnclient.ui2.components;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.config.Config;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
@@ -18,19 +17,6 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class Sidebar extends Element { public class Sidebar extends Element {
private static ThemeManager theme = new ThemeManager("SideBar", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0x00000000));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.integer(10));
private static Property<Integer> padding = theme.property("padding", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "icon-bg", -7643914);
}
public static class SidebarComponent { public static class SidebarComponent {
Runnable onClick; Runnable onClick;
boolean end; boolean end;
@@ -49,7 +35,7 @@ public class Sidebar extends Element {
SaturnClient.client.setScreen(new ModMenu()); SaturnClient.client.setScreen(new ModMenu());
}, false), }, false),
new Sidebar.SidebarComponent(Textures.SETTINGS, () -> { new Sidebar.SidebarComponent(Textures.SETTINGS, () -> {
SaturnClient.client.setScreen(new ConfigEditor(SaturnClientConfig.config)); SaturnClient.client.setScreen(new ConfigEditor(Config.config));
}, false), }, false),
new Sidebar.SidebarComponent(Textures.SHIRT, () -> { new Sidebar.SidebarComponent(Textures.SHIRT, () -> {
@@ -79,36 +65,29 @@ public class Sidebar extends Element {
int bottomRow = 0; int bottomRow = 0;
int gap = 5; // 5px gap between icons int gap = 5; // 5px gap between icons
int sp = 30 - padding.value + gap; int padding = 10;
int s = 30 - (padding.value * 2); int sp = 30 - padding + gap;
int s2 = s + padding.value; int s = 30 - (padding * 2);
int s2 = s + padding;
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value);
int i = 0; int i = 0;
for (SidebarComponent component : components) { for (SidebarComponent component : components) {
int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
boolean selected = ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i;
renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding,
s + padding, Theme.WIDGET_RADIUS.value, Theme.getBg(selected));
renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.getFg(selected));
if (component.end) { if (component.end) {
int y = (height - (sp * bottomRow)) - (s * 2);
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
bottomRow += 1; bottomRow += 1;
} else { } else {
int y = padding.value + (sp * topRow);
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
topRow += 1; topRow += 1;
} }
@@ -121,25 +100,24 @@ public class Sidebar extends Element {
int topRow = 0; int topRow = 0;
int bottomRow = 0; int bottomRow = 0;
int gap = 5; // same 5px gap here int gap = 5;
int sp = 30 - padding.value + gap; int padding = 10;
int s = 30 - (padding.value * 2); int sp = 30 - padding + gap;
int s2 = s + padding.value; int s = 30 - (padding * 2);
int s2 = s + padding;
for (SidebarComponent component : components) { for (SidebarComponent component : components) {
if (component.end) { int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
int y = (height - (sp * bottomRow)) - (s * 2); boolean hovering = Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale);
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
if (hovering) {
component.onClick.run(); component.onClick.run();
break; break;
} }
if (component.end) {
bottomRow += 1; bottomRow += 1;
} else { } else {
int y = padding.value + (sp * topRow);
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
component.onClick.run();
break;
}
topRow += 1; topRow += 1;
} }
} }
@@ -1,246 +0,0 @@
package org.saturnclient.ui2.components.dialog;
import java.awt.Color;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
import net.minecraft.client.gui.DrawContext;
public class ColorDialog extends SaturnScreen {
private Property<Integer> prop;
private SaturnScreen parent;
private static final int PICKER_WIDTH = 200;
private static final int PICKER_HEIGHT = 200;
private static final int SLIDER_WIDTH = 200;
private static final int SLIDER_HEIGHT = 10;
private static final int HUE_SLIDER_HEIGHT = 10;
private float hue = 0.0f;
private int selectedX = 0;
private int selectedY = 0;
private float opacity = 1.0f;
private boolean dragging = false;
public ColorDialog(Property<Integer> prop , SaturnScreen parent) {
super("Color Dialog");
this.prop = prop;
this.parent = parent;
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0) alpha = 255; // fallback if no alpha specified
this.opacity = alpha / 255.0f;
syncFromColor();
}
@Override
public void ui() {}
@Override
public void close() {
SaturnClient.client.setScreen(parent);
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
RenderScope renderScope = new RenderScope(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
renderScope.matrices.push();
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
// Draw the HSV saturation/value square (with current hue)
for (int x = 0; x < PICKER_WIDTH; x++) {
for (int y = 0; y < PICKER_HEIGHT; y++) {
float saturation = x / (float) PICKER_WIDTH;
float value = 1.0f - y / (float) PICKER_HEIGHT;
int color = Color.HSBtoRGB(hue, saturation, value);
context.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, 0xFF000000 | color);
}
}
int x = (xStart + selectedX) - 4;
int y = (yStart + selectedY) - 4;
context.fill(x, y, x+8, y+8, 0xFF000000 | prop.value);
context.drawBorder(x, y, 8, 8, 0xFFffffff);
renderSlider(context, mouseX, mouseY, delta);
renderHueSlider(context);
renderScope.matrices.pop();
}
public void renderSlider(DrawContext context, int mouseX, int mouseY, float delta) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
// Draw slider background (left = transparent, right = white)
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localOpacity = x / (float) SLIDER_WIDTH;
int alpha = (int)(localOpacity * 255) & 0xFF;
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
context.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
}
// Draw current value as a marker (thin black line)
int knobX = xStart + (int)(opacity * SLIDER_WIDTH);
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
// Draw opacity text
// context.drawText(textRenderer, String.format("Opacity: %.2f", opacity), xStart, yStart - 15, 0xFFFFFF, false);
}
private void renderHueSlider(DrawContext context) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localHue = x / (float) SLIDER_WIDTH;
int color = Color.HSBtoRGB(localHue, 1.0f, 1.0f);
context.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
}
int knobX = xStart + (int)(hue * SLIDER_WIDTH);
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
mouseX *= 2;
mouseY *= 2;
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
int relX = (int) mouseX - xStart;
int relY = (int) mouseY - yStart;
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
float saturation = relX / (float) PICKER_WIDTH;
float value = 1.0f - relY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
selectedX = relX;
selectedY = relY;
return true;
} else if (isInSlider(mouseX, mouseY)) {
updateOpacity(mouseX);
dragging = true;
return true;
} else if (isInHueSlider(mouseX, mouseY)) {
updateHue(mouseX);
return true;
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
dragging = false;
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
mouseX *= 2;
mouseY *= 2;
if (isInHueSlider(mouseX, mouseY)) {
updateHue(mouseX);
return true;
} else if (dragging) {
updateOpacity(mouseX);
return true;
} else {
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
int relX = (int) mouseX - xStart;
int relY = (int) mouseY - yStart;
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
float saturation = relX / (float) PICKER_WIDTH;
float value = 1.0f - relY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
selectedX = relX;
selectedY = relY;
return true;
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
private boolean isInSlider(double mouseX, double mouseY) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
mouseY >= yStart && mouseY <= yStart + SLIDER_HEIGHT;
}
private boolean isInHueSlider(double mouseX, double mouseY) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
mouseY >= yStart && mouseY <= yStart + HUE_SLIDER_HEIGHT;
}
private void updateOpacity(double mouseX) {
int xStart = (width - SLIDER_WIDTH) / 2;
float newOpacity = (float)((mouseX - xStart) / SLIDER_WIDTH);
opacity = Math.max(0.0f, Math.min(1.0f, newOpacity));
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
}
private void updateHue(double mouseX) {
int xStart = (width - SLIDER_WIDTH) / 2;
float newHue = (float)((mouseX - xStart) / SLIDER_WIDTH);
hue = Math.max(0.0f, Math.min(1.0f, newHue));
float saturation = selectedX / (float) PICKER_WIDTH;
float value = 1.0f - selectedY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
}
private void syncFromColor() {
int rgb = prop.value & 0xFFFFFF;
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
this.hue = hsb[0];
float saturation = hsb[1];
float value = hsb[2];
this.selectedX = (int)(saturation * PICKER_WIDTH);
this.selectedY = (int)((1.0f - value) * PICKER_HEIGHT);
// Extract alpha and convert to opacity float
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0) alpha = 255;
this.opacity = alpha / 255.0f;
}
}
@@ -1,7 +1,7 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class FloatInput extends Input { public class FloatInput extends Input {
@@ -1,32 +1,183 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.SaturnClient; import java.awt.Color;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.dialog.ColorDialog;
public class HexInput extends Element { public class HexInput extends Element {
public Property<Integer> prop; public Property<Integer> prop;
public SaturnScreen screen;
public HexInput(Property<Integer> prop, SaturnScreen screen) { // Picker state
private boolean open = false;
private float hue = 0f;
private float opacity = 1f;
private int selectedX = 0;
private int selectedY = 0;
private boolean draggingOpacity = false;
// Dimensions
private static final int PICKER_WIDTH = 100;
private static final int PICKER_HEIGHT = 100;
private static final int SLIDER_WIDTH = 100;
private static final int SLIDER_HEIGHT = 10;
private static final int HUE_SLIDER_HEIGHT = 10;
public HexInput(Property<Integer> prop) {
this.prop = prop;
this.width = 58; this.width = 58;
this.height = 20; this.height = 20;
this.prop = prop; syncFromColor();
this.scale = 0.8f;
this.screen = screen;
} }
@Override @Override
public void click(int mouseX, int mouseY) { public void click(int mouseX, int mouseY) {
SaturnClient.client.setScreen(new ColorDialog(prop, screen)); open = !open;
}
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
if (open) {
handlePickerClick(mouseX - width - 8, mouseY);
}
}
@Override
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
if (open) {
handlePickerDrag(mouseX - width - 8, mouseY);
}
}
@Override
public void mouseReleased(double mouseX, double mouseY, int button) {
draggingOpacity = false;
} }
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, 17, prop.value); // Draw the main color rectangle
renderScope.drawRoundedRectangle(0, 0, width, height, 6, prop.value);
if (!open)
return;
// Draw the picker relative to (width + 8, 0)
int pickerX = width + 8;
int pickerY = 0;
renderColorSquare(renderScope, pickerX, pickerY);
renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10);
renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10);
}
private void renderColorSquare(RenderScope renderScope, int xStart, int yStart) {
for (int x = 0; x < PICKER_WIDTH; x++) {
for (int y = 0; y < PICKER_HEIGHT; y++) {
float saturation = x / (float) PICKER_WIDTH;
float value = 1f - y / (float) PICKER_HEIGHT;
int color = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int) (opacity * 255) & 0xFF;
color = (alpha << 24) | (color & 0xFFFFFF);
renderScope.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, color);
}
}
// Draw selection square
int selX = xStart + selectedX - 4;
int selY = yStart + selectedY - 4;
renderScope.fill(selX, selY, selX + 8, selY + 8, 0xFF000000 | prop.value);
renderScope.drawBorder(selX, selY, 8, 8, 0xFFFFFFFF);
}
private void renderOpacitySlider(RenderScope renderScope, int xStart, int yStart) {
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localOpacity = x / (float) SLIDER_WIDTH;
int alpha = (int) (localOpacity * 255) & 0xFF;
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
}
int knobX = xStart + (int) (opacity * SLIDER_WIDTH);
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
}
private void renderHueSlider(RenderScope renderScope, int xStart, int yStart) {
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localHue = x / (float) SLIDER_WIDTH;
int color = Color.HSBtoRGB(localHue, 1f, 1f);
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
}
int knobX = xStart + (int) (hue * SLIDER_WIDTH);
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
}
private void handlePickerClick(double mouseX, double mouseY) {
// Color square
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
selectedX = (int) mouseX;
selectedY = (int) mouseY;
updateColorFromSelection();
}
// Opacity slider
int opY = -SLIDER_HEIGHT - 10;
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= opY && mouseY <= opY + SLIDER_HEIGHT) {
updateOpacity(mouseX);
draggingOpacity = true;
}
// Hue slider
int hueY = PICKER_HEIGHT + 10;
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= hueY && mouseY <= hueY + HUE_SLIDER_HEIGHT) {
updateHue(mouseX);
}
}
private void handlePickerDrag(double mouseX, double mouseY) {
// Opacity
if (draggingOpacity)
updateOpacity(mouseX);
// Color square
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
selectedX = (int) mouseX;
selectedY = (int) mouseY;
updateColorFromSelection();
}
}
private void updateColorFromSelection() {
float saturation = selectedX / (float) PICKER_WIDTH;
float value = 1f - selectedY / (float) PICKER_HEIGHT;
int color = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int) (opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (color & 0xFFFFFF);
}
private void updateHue(double mouseX) {
hue = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
updateColorFromSelection();
}
private void updateOpacity(double mouseX) {
opacity = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
updateColorFromSelection();
}
private void syncFromColor() {
int rgb = prop.value & 0xFFFFFF;
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
hue = hsb[0];
selectedX = (int) (hsb[1] * PICKER_WIDTH);
selectedY = (int) ((1f - hsb[2]) * PICKER_HEIGHT);
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0)
alpha = 255;
opacity = alpha / 255f;
} }
} }
@@ -2,21 +2,20 @@ package org.saturnclient.ui2.components.inputs;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public abstract class Input extends Element { public abstract class Input extends Element {
private static ThemeManager theme = new ThemeManager("Input");
private static Property<Integer> font = theme.property("font", Property.font(1));
String text = ""; String text = "";
public int cursorPosition = 0; public int cursorPosition = 0;
public abstract void checkReset(); public abstract void checkReset();
public abstract void backspace(); public abstract void backspace();
public boolean validateBackspace() { public boolean validateBackspace() {
return true; return true;
} }
@@ -71,11 +70,12 @@ public abstract class Input extends Element {
int scrollOffset = getScrollOffset(); int scrollOffset = getScrollOffset();
String visibleText = getVisibleText(scrollOffset); String visibleText = getVisibleText(scrollOffset);
int textColor = focused ? 0xFFFFFF : 0xAAAAAA; int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
renderScope.drawText(0.6f, visibleText, 4, 4, font.value, textColor); renderScope.drawText(0.6f, visibleText, 4, 4, Theme.FONT.value, textColor);
if (focused) { if (focused) {
int cursorX = 4 + int cursorX = 4 +
(int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), font.value) * 0.6f); (int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), Theme.FONT.value)
* 0.6f);
renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF); renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF);
} }
@@ -90,7 +90,7 @@ public abstract class Input extends Element {
int maxWidth = width - 4; // Available width for text int maxWidth = width - 4; // Available width for text
while (offset < cursorPosition) { // Ensure cursor remains visible while (offset < cursorPosition) { // Ensure cursor remains visible
String visibleText = text.substring(offset, cursorPosition); String visibleText = text.substring(offset, cursorPosition);
if (Fonts.getWidth(visibleText, font.value) > maxWidth) { if (Fonts.getWidth(visibleText, Theme.FONT.value) > maxWidth) {
offset++; offset++;
} else { } else {
break; break;
@@ -104,7 +104,7 @@ public abstract class Input extends Element {
// Ensure text does not overflow past the available width // Ensure text does not overflow past the available width
for (int i = 1; i <= visibleText.length(); i++) { for (int i = 1; i <= visibleText.length(); i++) {
if (Fonts.getWidth(visibleText.substring(0, i), font.value) > width - 4) { if (Fonts.getWidth(visibleText.substring(0, i), Theme.FONT.value) > width - 4) {
return visibleText.substring(0, i - 1); // Trim overflow return visibleText.substring(0, i - 1); // Trim overflow
} }
} }
@@ -1,7 +1,7 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class IntInput extends Input { public class IntInput extends Input {
@@ -1,16 +1,14 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class KeybindingSelector extends Element { public class KeybindingSelector extends Element {
private static ThemeManager theme = new ThemeManager("Input");
private static Property<Integer> font = theme.property("font", Property.font(1));
Property<Integer> prop; Property<Integer> prop;
public KeybindingSelector(Property<Integer> prop) { public KeybindingSelector(Property<Integer> prop) {
@@ -23,9 +21,12 @@ public class KeybindingSelector extends Element {
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
int textColor = focused ? 0xFFFFFF : 0xAAAAAA; int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000); renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
renderScope.drawText(0.6f, prop.value == -1 ? "<NONE>" : String name = GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value));
GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)).toUpperCase(),
4, 4, font.value, textColor); renderScope.drawText(0.6f,
prop.value == -1 ? "<NONE>"
: name != null ? name.toUpperCase() : "<Invalid-Key>",
4, 4, Theme.FONT.value, textColor);
} }
@Override @Override
@@ -1,7 +1,7 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,11 +10,6 @@ import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayer;
public class Select extends Element { public class Select extends Element {
private static ThemeManager theme = new ThemeManager("Toggle");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xAAAAAA));
private static Property<Integer> arrowColor = theme.property("arrow-color", Property.color(0xff8B5CF6));
private static Property<Integer> font = theme.property("font", Property.font(1));
private Property<Integer> prop; private Property<Integer> prop;
public Select(Property<Integer> prop) { public Select(Property<Integer> prop) {
@@ -27,15 +22,14 @@ public class Select extends Element {
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawText(0.8f, "<", 0, 0, font.value, arrowColor.value); renderScope.drawText(0.8f, "<", 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
String text = prop.getSelection(); String text = prop.getSelection();
renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, font.value) * 0.6f)) / 2, 1, font.value, fgColor.value); renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, Theme.FONT.value) * 0.6f)) / 2, 1,
Theme.FONT.value, Theme.FOREGROUND.value);
renderScope.drawText(0.8f, ">", width - 10, 0, font.value, arrowColor.value); renderScope.drawText(0.8f, ">", width - 10, 0, Theme.FONT.value, Theme.FOREGROUND.value);
theme.setState(null);
} }
@Override @Override
@@ -1,22 +1,12 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
public class Toggle extends Element { public class Toggle extends Element {
private static ThemeManager theme = new ThemeManager("Toggle", "hovering", "enabled");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF808080));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFF909090));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(30));
static {
theme.propertyStateDefault("enabled", "bg-color", 0xaf8B5CF6);
theme.propertyStateDefault("enabled", "fg-color", 0xff8B5CF6);
}
private Property<Boolean> prop; private Property<Boolean> prop;
public Toggle(Property<Boolean> prop) { public Toggle(Property<Boolean> prop) {
@@ -27,19 +17,10 @@ public class Toggle extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
theme.setState(null); renderScope.drawRoundedRectangle(0, 0, width, height, height,
Theme.withAlpha(0.5f, prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value));
if (prop.value) { renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30,
theme.setState("enabled"); prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value);
}
if (ctx.isHovering()) {
theme.applyState("hovering");
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, fgColor.value);
} }
@Override @Override
@@ -0,0 +1,66 @@
package org.saturnclient.ui2.elements;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.saturnclient.config.Config;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.components.ElementRenderer;
public class AnimationStagger extends Element {
protected List<Element> children = new ArrayList<>();
public int delay;
public AnimationStagger(int delay) {
this.delay = delay;
}
public AnimationStagger(AnimationConfig config) {
this.delay = config.stagger.value;
}
public void draw(Element element) {
if (!Config.stagger.value) {
element.animation = null;
}
if (element.animation != null) {
element.animation.delay = delay * children.size();
}
ElementRenderer.draw(children, element);
this.height = Math.max(this.height, element.y + element.height);
this.width = Math.max(this.width, element.x + element.width);
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
ElementRenderer.render(children, ctx.elapsed, renderScope,
ctx.mouseX,
ctx.mouseY);
}
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
ElementRenderer.mouseClicked(children, mouseX, mouseY, button);
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers);
}
@Override
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
ElementRenderer.mouseDragged(children, mouseX, mouseY, button, deltaX, deltaY);
}
@Override
public void mouseReleased(double mouseX, double mouseY, int button) {
ElementRenderer.mouseReleased(children, mouseX, mouseY, button);
}
}
@@ -1,43 +1,29 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class Button extends Element { public class Button extends Element {
private static ThemeManager theme = new ThemeManager("Button", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
private static Property<Integer> font = theme.property("font", Property.font(1));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee);
}
private String text; private String text;
private Runnable onClick; private Runnable onClick;
public Button(String text, Runnable onClick) { public Button(String text, Runnable onClick) {
this.text = text; this.text = text;
this.onClick = onClick; this.onClick = onClick;
this.dimensions(Fonts.getWidth(text, font.value) + 50, 38); this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38);
} }
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
theme.setState("hovering"); ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
// renderScope.drawText(text, (width - Fonts.getWidth(text, font)) / 2, (height - Fonts.getHeight()) / 2, bold.value, fgColor.value); renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height),
renderScope.drawText(text, Fonts.centerX(width, text, font.value), Fonts.centerY(height), font.value, fgColor.value); Theme.FONT.value,
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
} }
@Override @Override
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,9 +9,6 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class ClickableTexture extends Element { public class ClickableTexture extends Element {
private static ThemeManager theme = new ThemeManager("ClickableTexture", "hovering");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
Identifier sprite; Identifier sprite;
private Runnable onClick; private Runnable onClick;
@@ -23,14 +19,9 @@ public class ClickableTexture extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height, fgColor.value); renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height,
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
} }
@Override @Override
@@ -3,8 +3,9 @@ package org.saturnclient.ui2.elements;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.SvgTexture;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class ImageTexture extends Element { public class ImageTexture extends Element {
@@ -16,7 +17,16 @@ public class ImageTexture extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured); if (sprite.toString().endsWith(".svg")) {
MinecraftClient client = MinecraftClient.getInstance();
// Get the actual window pixel dimensions for the image
int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWidth());
int renderHeight = (int) (height * client.getWindow().getFramebufferHeight() / client.getWindow().getHeight());
sprite = SvgTexture.getSvg(client, sprite, renderWidth * 2, renderHeight * 2);
}
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height); renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height);
} }
} }
@@ -1,7 +1,7 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,11 +10,9 @@ import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
public class Notification extends Element { public class Notification extends Element {
private static ThemeManager theme = new ThemeManager("Notification"); private static Property<Integer> error = Property.color(0xFFbf212f);
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000)); private static Property<Integer> info = Property.color(0xFF264b96);
private static Property<Integer> error = theme.property("error", Property.color(0xFFbf212f)); private static Property<Integer> success = Property.color(0xFF27b376);
private static Property<Integer> info = theme.property("info", Property.color(0xFF264b96));
private static Property<Integer> success = theme.property("success", Property.color(0xFF27b376));
public static enum NotificationKind { public static enum NotificationKind {
Error, Error,
@@ -40,7 +38,7 @@ public class Notification extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { 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) { switch (kind) {
case Error: case Error:
renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value); renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value);
@@ -3,22 +3,17 @@ package org.saturnclient.ui2.elements;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.components.ElementRenderer; import org.saturnclient.ui2.components.ElementRenderer;
public class Scroll extends Element { public class Scroll extends Element {
private static ThemeManager theme = new ThemeManager("Scroll"); private static Property<Integer> scrollBarRadius = Property.integer(10);
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000)); private static Property<Integer> scrollBarWidth = Property.integer(5);
private static Property<Integer> scrollBarColor = theme.property("scrollbar-color", Property.color(-7643914)); 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; int padding = 0;
@@ -30,18 +25,37 @@ public class Scroll extends Element {
this.padding = padding; this.padding = padding;
} }
public void draw(Element element) {
ElementRenderer.draw(children, element);
}
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { 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.enableScissor(padding, padding, width - padding, height - padding);
renderScope.matrices.push(); renderScope.matrices.push();
renderScope.matrices.translate(padding, (-scroll) + padding, 0); renderScope.matrices.translate(padding, -scroll + padding, 0);
ElementRenderer.render(children, renderScope, ctx.mouseX - padding, ctx.mouseY - padding + scroll);
ElementRenderer.render(children, ctx.elapsed, renderScope,
ctx.mouseX - padding,
ctx.mouseY - padding + scroll);
renderScope.matrices.pop(); renderScope.matrices.pop();
renderScope.disableScissor(); 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 @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 @Override
public Element dimensions(int width, int height) { public Element dimensions(int width, int height) {
super.dimensions(width, height);
maxScroll = 0;
for (Element element : children) { 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() { int calculateScrollBarHeight() {
if (maxScroll <= 0) return height - (scrollBarPadding.value * 2); // No scrolling needed if (maxScroll <= 0)
return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); // At least 20px tall return height - (scrollBarPadding.value * 2);
return Math.max(20, (height * height) / (height + maxScroll + 10)) - (scrollBarPadding.value * 2);
} }
int calculateScrollBarY() { int calculateScrollBarY() {
int scrollBarHeight = calculateScrollBarHeight(); int scrollBarHeight = calculateScrollBarHeight();
if (maxScroll <= 0) return scrollBarPadding.value; 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 @Override
public void keyPressed(int keyCode, int scanCode, int modifiers) { public void keyPressed(int keyCode, int scanCode, int modifiers) {
ElementRenderer.keyPressed(children, keyCode, scanCode, 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);
}
} }
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,16 +9,6 @@ import org.saturnclient.ui2.Utils;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class TabMenu extends Element { public class TabMenu extends Element {
private static ThemeManager theme = new ThemeManager("TabMenu", "hovering", "selected");
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));
static {
theme.propertyStateDefault("hovering", "icon-bg", -7643914);
}
public static class TabMenuComponent { public static class TabMenuComponent {
Runnable onClick; Runnable onClick;
Identifier sprite; Identifier sprite;
@@ -42,19 +31,21 @@ public class TabMenu extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value);
int col = 0; int col = 0;
for (TabMenuComponent component : components) { for (TabMenuComponent component : components) {
int x = 25 * col + 5; int x = 25 * col + 5;
if (ctx.isHovering(x, 5, 20, 20) || selectedScreen == col) {
theme.setState("hovering"); boolean hovering = ctx.isHovering(x, 5, 20, 20) || selectedScreen == col;
if (hovering) {
renderScope.drawRoundedRectangle(x, 5, 20, 20, Theme.WIDGET_RADIUS.value, Theme.ACCENT.value);
} }
renderScope.drawRoundedRectangle(x, 5, 20, 20, cornerRadius.value, iconBg.value); renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10,
renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10, iconFg.value); hovering ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
theme.setState(null);
col++; col++;
} }
} }
@@ -1,27 +1,22 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class Text extends Element { public class Text extends Element {
private static ThemeManager theme = new ThemeManager("Text");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
public static Property<Integer> font = theme.property("font", Property.font(1));
private String text; private String text;
public Text(String text) { public Text(String text) {
this.text = text; this.text = text;
width = Fonts.getWidth(text, font.value); width = Fonts.getWidth(text, Theme.FONT.value);
height = Fonts.getHeight(); height = Fonts.getHeight();
} }
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawText(this.text, 0, 0, font.value, fgColor.value); renderScope.drawText(this.text, 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
} }
} }
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,19 +9,9 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class TextureButton extends Element { public class TextureButton extends Element {
private static ThemeManager theme = new ThemeManager("TextureButton", "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> padding = theme.property("padding", Property.integer(14));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee);
}
Identifier sprite; Identifier sprite;
private Runnable onClick; private Runnable onClick;
static int padding = 14;
public TextureButton(Identifier sprite, Runnable onClick) { public TextureButton(Identifier sprite, Runnable onClick) {
this.sprite = sprite; this.sprite = sprite;
@@ -31,20 +20,16 @@ public class TextureButton extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
theme.setState("hovering"); ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
int texWidth = width - padding.value; int texWidth = width - padding;
int texHeight = height - padding.value; int texHeight = height - padding;
renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value); renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
} }
@Override @Override
@@ -0,0 +1,80 @@
package org.saturnclient.ui2.resources;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGUniverse;
import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
public class SvgTexture {
private static List<Identifier> svgCache = new ArrayList<>();
private static BufferedImage renderSvg(InputStream svgStream, int width, int height) throws Exception {
// Load SVG
SVGUniverse universe = new SVGUniverse();
java.net.URI uri = universe.loadSVG(svgStream, "tempSvg");
SVGDiagram diagram = universe.getDiagram(uri);
// Get intrinsic size of SVG
float svgWidth = (float) diagram.getWidth();
float svgHeight = (float) diagram.getHeight();
// Compute scale factors
float scaleX = width / svgWidth;
float scaleY = height / svgHeight;
// Create output image
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// Enable anti-aliasing for smooth scaling
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// Apply scaling transform
g.scale(scaleX, scaleY);
// Render SVG
diagram.render(g);
g.dispose();
return image;
}
@Nullable
public static Identifier getSvg(MinecraftClient client, Identifier svgImage, int width, int height) {
Identifier id = Identifier
.of(svgImage.toString().replaceAll("\\.svg$", (width + "_" + height).toString() + ".png"));
if (svgCache.contains(id)) {
return id;
}
try (InputStream svgStream = client
.getResourceManager()
.getResource(svgImage).get()
.getInputStream()) {
BufferedImage image = renderSvg(svgStream, width, height);
IdentifierUtils.registerBufferedImageTexture(id, image);
return id;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
@@ -28,6 +28,7 @@ public class Textures {
public static final Identifier REALISTIC_LOGO = Identifier.of("saturnclient", "textures/logo/realistic.png"); public static final Identifier REALISTIC_LOGO = Identifier.of("saturnclient", "textures/logo/realistic.png");
public static final Identifier LOGO_TEXT = Identifier.of("saturnclient", "textures/logo/text.png"); public static final Identifier LOGO_TEXT = Identifier.of("saturnclient", "textures/logo/text.png");
public static final Identifier LOGO_TEXT_BIG = Identifier.of("saturnclient", "textures/logo/text_big.png"); public static final Identifier LOGO_TEXT_BIG = Identifier.of("saturnclient", "textures/logo/text_big.png");
public static final Identifier SPLASH = Identifier.of("saturnclient", "textures/gui/splash.png");
// Widgets // Widgets
public static final Identifier BUTTON_BORDER = Identifier.of("saturnclient", "button_border"); public static final Identifier BUTTON_BORDER = Identifier.of("saturnclient", "button_border");
@@ -52,7 +53,7 @@ public class Textures {
} }
public static Identifier getModIcon(String modId) { public static Identifier getModIcon(String modId) {
return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".png"); return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".svg");
} }
} }
@@ -2,8 +2,9 @@ package org.saturnclient.ui2.screens;
import java.util.Map; import java.util.Map;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.manager.ConfigManager;
import org.saturnclient.saturnclient.config.manager.Property;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.Sidebar; import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.components.inputs.FloatInput; import org.saturnclient.ui2.components.inputs.FloatInput;
@@ -60,13 +61,12 @@ public class ConfigEditor extends SaturnScreen {
if (prop.getType() == Property.PropertyType.NAMESPACE) { if (prop.getType() == Property.PropertyType.NAMESPACE) {
configScroll.draw( configScroll.draw(
new Text( new Text(
propName propName).position(Fonts.centerX(520, propName, Theme.FONT.value), modY + 1)
).position(Fonts.centerX(520, propName, Text.font.value), modY + 1).scale(0.7f)); .scale(0.7f));
} else { } else {
configScroll.draw( configScroll.draw(
new Text( new Text(
propName propName).position(modX, modY + 1).scale(0.7f));
).position(modX, modY + 1).scale(0.7f));
} }
switch (prop.getType()) { switch (prop.getType()) {
@@ -78,7 +78,7 @@ public class ConfigEditor extends SaturnScreen {
break; break;
case HEX: case HEX:
configScroll.draw(new HexInput((Property<Integer>) prop, this).position(w / 2, modY)); configScroll.draw(new HexInput((Property<Integer>) prop).position(w / 2, modY));
break; break;
case INTEGER: case INTEGER:
@@ -1,14 +1,15 @@
package org.saturnclient.ui2.screens; package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.config.AnimationConfig;
// import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.config.Config;
import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.saturnclient.cosmetics.Emotes;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.Utils; import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.anim.Fade; import org.saturnclient.ui2.anim.Fade;
import org.saturnclient.ui2.anim.SlideY; import org.saturnclient.ui2.anim.SlideY;
import org.saturnclient.ui2.elements.AnimationStagger;
import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.TextureButton; import org.saturnclient.ui2.elements.TextureButton;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
@@ -30,10 +31,23 @@ public class EmoteWheel extends SaturnScreen {
int row = 0; int row = 0;
int col = 0; int col = 0;
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -105) // =============================
.animation(new Fade(700))); // Logo
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -149) // =============================
.animation(new SlideY(700, -20)));
draw(new ImageTexture(Textures.LOGO_TEXT)
.dimensions(98, 10)
.centerOffset(width, height, 0, -105)
.animation(new Fade(AnimationConfig.logo.duration.value)));
draw(new ImageTexture(Config.getLogo())
.dimensions(98, 98)
.centerOffset(width, height, 0, -149)
.animation(new SlideY(AnimationConfig.logo, -20)));
// =============================
// Navigation Buttons
// =============================
draw(new TextureButton(Textures.LEFT, () -> { draw(new TextureButton(Textures.LEFT, () -> {
if (page > 1) { if (page > 1) {
@@ -41,18 +55,29 @@ public class EmoteWheel extends SaturnScreen {
elements.clear(); elements.clear();
ui(); ui();
} }
}).dimensions(30, 30).centerOffset(width, height, -20, 45).animation(new Fade(700))); }).dimensions(30, 30)
.centerOffset(width, height, -50, 105)
.animation(new Fade(500)));
draw(new TextureButton(Textures.RIGHT, () -> { draw(new TextureButton(Textures.RIGHT, () -> {
if ((8 * page) < Emotes.availableEmotes.size()) { if ((page * 8) < Emotes.availableEmotes.size()) {
page++; page++;
elements.clear(); elements.clear();
ui(); ui();
} }
}).dimensions(30, 30).centerOffset(width, height, 20, 45).animation(new Fade(700))); }).dimensions(30, 30)
.centerOffset(width, height, 50, 105)
.animation(new Fade(500)));
// =============================
// Emote Grid (Staggered)
// =============================
AnimationStagger emoteStagger = new AnimationStagger(AnimationConfig.emoteWheel);
while (row < 3) { while (row < 3) {
int idx = (row * 3 + col) * page;
int idx = (row * 3 + col) + ((page - 1) * 8);
if (row == 1 && col == 1) { if (row == 1 && col == 1) {
col++; col++;
@@ -60,21 +85,35 @@ public class EmoteWheel extends SaturnScreen {
String emote = Utils.getOrNull(Emotes.availableEmotes, idx); String emote = Utils.getOrNull(Emotes.availableEmotes, idx);
draw(new TextureButton(Textures.getEmotePreview(emote), () -> { emoteStagger.draw(
if (emote == null) { new TextureButton(Textures.getEmotePreview(emote), () -> {
if (emote == null)
return; return;
}
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(
if (animationStack.isActive() && animationStack.getPriority() == 1000) { SaturnClient.client.player);
if (animationStack.isActive()
&& animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000); animationStack.removeLayer(1000);
} }
animationStack.addAnimLayer(1000,
PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", animationStack.addAnimLayer(
emote)).playAnimation()); 1000,
Auth.emote(emote); PlayerAnimationRegistry
.getAnimation(
Identifier.of("saturnclient", emote))
.playAnimation());
ServiceClient.emote(emote);
close(); close();
}).dimensions(70, 70).centerOffset(width, height, -80 + (col * 80), -35 + (row * 80)) })
.animation(new Fade(700))); .dimensions(70, 70)
.centerOffset(width, height,
-80 + (col * 80),
-35 + (row * 80))
.animation(new SlideY(AnimationConfig.emoteWheel, 12)));
if (col < 2) { if (col < 2) {
col++; col++;
@@ -83,6 +122,8 @@ public class EmoteWheel extends SaturnScreen {
row++; row++;
} }
} }
draw(emoteStagger);
} }
@Override @Override
@@ -2,9 +2,12 @@ package org.saturnclient.ui2.screens;
import org.saturnclient.modules.ModManager; import org.saturnclient.modules.ModManager;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.config.AnimationConfig;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.anim.SlideFade;
import org.saturnclient.ui2.components.SaturnModule; import org.saturnclient.ui2.components.SaturnModule;
import org.saturnclient.ui2.components.Sidebar; import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.elements.AnimationStagger;
import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.Scroll; import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
@@ -24,8 +27,13 @@ public class ModMenu extends SaturnScreen {
int row = 0; int row = 0;
int col = 0; int col = 0;
AnimationStagger moduleStagger = new AnimationStagger(AnimationConfig.modMenu);
for (Module mod : ModManager.ALL_MODS) { for (Module mod : ModManager.ALL_MODS) {
scroll.draw(new SaturnModule(mod).position((160 + g) * col, (50 + g) * row)); moduleStagger.draw(
new SaturnModule(mod)
.position((160 + g) * col, (50 + g) * row)
.animation(new SlideFade(AnimationConfig.modMenu, -12)));
if (col == 2) { if (col == 2) {
col = 0; col = 0;
@@ -35,6 +43,8 @@ public class ModMenu extends SaturnScreen {
} }
} }
scroll.draw(moduleStagger);
int scrollWidth = 480 + 10 + (g * 2) + (p * 2); int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).center(width, height)); draw(scroll.dimensions(scrollWidth, 350).center(width, height));

Some files were not shown because too many files have changed in this diff Show More