Starting fresh
This commit is contained in:
@@ -1,12 +0,0 @@
|
|||||||
<factorypath>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/io.github.llamalad7/mixinextras-fabric/0.4.1/8d1a9e96afb990367fa1f904d17580d164da72e3/mixinextras-fabric-0.4.1.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-analysis/9.7.1/f97a3b319f0ed6a8cd944dc79060d3912a28985f/asm-analysis-9.7.1.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/net.fabricmc/fabric-mixin-compile-extensions/0.6.0/6fda0c85c419f0a259da017a1424f27507eb30f0/fabric-mixin-compile-extensions-0.6.0.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/net.fabricmc/mapping-io/0.2.1/2a1aeedce104191bbb9e39e26abfbfeaf9b0590b/mapping-io-0.2.1.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-tree/9.7.1/3a53139787663b139de76b627fca0084ab60d32c/asm-tree-9.7.1.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/fabric-loom/1.21.4/net.fabricmc.yarn.1_21_4.1.21.4+build.7-v2/mappings.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/net.fabricmc/sponge-mixin/0.15.4+mixin.0.8.7/6a12aacc794f1078458433116e9ed42c1cc98096/sponge-mixin-0.15.4+mixin.0.8.7.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.7.1/f0ed132a49244b042cd0e15702ab9f2ce3cc8436/asm-9.7.1.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-util/9.7.1/9e23359b598ec6b74b23e53110dd5c577adf2243/asm-util-9.7.1.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-commons/9.7.1/406c6a2225cfe1819f102a161e54cc16a5c24f75/asm-commons-9.7.1.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
</factorypath>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Licenses
|
# Licenses
|
||||||
|
|
||||||
This project is under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0), but a lot of code taken from other open source projects that are under other licenses that need to be mentioned due to legal reasons.
|
This project is under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0), but a lot of code taken from other open source projects that are under other licenses that need to be mentioned due to legal or ethical reasons.
|
||||||
|
|
||||||
| File | License |
|
| File | License |
|
||||||
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Version guide
|
||||||
|
|
||||||
|
This guide will help you to add or edit minecraft versions.
|
||||||
+10
-178
@@ -1,190 +1,22 @@
|
|||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* 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.6-SNAPSHOT" apply false
|
||||||
id 'fabric-loom' version '1.9-SNAPSHOT'
|
|
||||||
|
|
||||||
// Maven publishing support
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
subprojects {
|
||||||
* ================================================================
|
repositories {
|
||||||
* Project Metadata
|
mavenCentral()
|
||||||
* ================================================================
|
maven { url "https://maven.fabricmc.net/" }
|
||||||
*/
|
}
|
||||||
|
|
||||||
version = project.mod_version
|
dependencies {
|
||||||
group = project.maven_group
|
// OBJ Library
|
||||||
|
|
||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* 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' }
|
|
||||||
|
|
||||||
// Player Animation Library
|
|
||||||
maven { name = "KoSmX Maven"; url = 'https://maven.kosmx.dev/' }
|
|
||||||
|
|
||||||
// Central repository
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* Dependencies
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
// Minecraft Core
|
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
|
||||||
|
|
||||||
// Yarn mappings (named mappings for dev environment)
|
|
||||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
|
||||||
|
|
||||||
// Fabric Loader
|
|
||||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
|
||||||
|
|
||||||
// Fabric API
|
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
|
||||||
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
// Rendering / Model Libraries
|
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
// OBJ Reading Library
|
|
||||||
implementation "de.javagl:obj:0.4.0"
|
implementation "de.javagl:obj:0.4.0"
|
||||||
include "de.javagl:obj:0.4.0"
|
include "de.javagl:obj:0.4.0"
|
||||||
|
|
||||||
// SVG Rendering Library
|
// SVG Library
|
||||||
include implementation("com.formdev:svgSalamander:1.1.4")
|
include implementation("com.formdev:svgSalamander:1.1.4")
|
||||||
|
|
||||||
|
// Session (Saturn auth/session handling)
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
// Animation
|
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Player Animation Library
|
|
||||||
include modImplementation(
|
|
||||||
"dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Optional: BendyLib
|
|
||||||
// include modRuntimeOnly(
|
|
||||||
// "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}"
|
|
||||||
// )
|
|
||||||
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
// External Libraries
|
|
||||||
// ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Session Java (Saturn auth/session handling)
|
|
||||||
include implementation("com.github.selimaj-dev:session-java:0.1.6")
|
include implementation("com.github.selimaj-dev:session-java:0.1.6")
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* Resource Processing
|
|
||||||
* ================================================================
|
|
||||||
* Injects version values into fabric.mod.json.
|
|
||||||
*/
|
|
||||||
processResources {
|
|
||||||
inputs.property "version", project.version
|
|
||||||
inputs.property "minecraft_version", project.minecraft_version
|
|
||||||
inputs.property "loader_version", project.loader_version
|
|
||||||
filteringCharset "UTF-8"
|
|
||||||
|
|
||||||
filesMatching("fabric.mod.json") {
|
|
||||||
expand(
|
|
||||||
"version": project.version,
|
|
||||||
"minecraft_version": project.minecraft_version,
|
|
||||||
"loader_version": project.loader_version
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* Java Configuration
|
|
||||||
* ================================================================
|
|
||||||
* Targeting Java 21.
|
|
||||||
*/
|
|
||||||
def targetJavaVersion = 21
|
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
|
||||||
options.encoding = "UTF-8"
|
|
||||||
options.release.set(targetJavaVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
|
||||||
|
|
||||||
archivesBaseName = project.archives_base_name
|
|
||||||
withSourcesJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* Jar Packaging
|
|
||||||
* ================================================================
|
|
||||||
* Includes LICENSE and NOTICE inside the final jar.
|
|
||||||
*/
|
|
||||||
jar {
|
|
||||||
from("LICENSE") {
|
|
||||||
rename { "${it}_${project.archivesBaseName}" }
|
|
||||||
}
|
|
||||||
from("NOTICE") {
|
|
||||||
rename { "${it}_${project.archivesBaseName}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* Publishing
|
|
||||||
* ================================================================
|
|
||||||
* Configured but no remote repository defined yet.
|
|
||||||
*/
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
mavenJava(MavenPublication) {
|
|
||||||
from components.java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
// Add publishing repository here when needed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,18 +0,0 @@
|
|||||||
# Done to increase the memory available to gradle.
|
|
||||||
org.gradle.jvmargs=-Xmx1G -XX:+UseParallelGC
|
|
||||||
|
|
||||||
org.gradle.daemon=true
|
|
||||||
org.gradle.parallel=true
|
|
||||||
org.gradle.configureondemand=true
|
|
||||||
org.gradle.caching=true
|
|
||||||
|
|
||||||
minecraft_version=1.21.4
|
|
||||||
yarn_mappings=1.21.4+build.7
|
|
||||||
loader_version=0.16.10
|
|
||||||
fabric_version=0.114.0+1.21.4
|
|
||||||
|
|
||||||
mod_version = 0.0.7-beta1
|
|
||||||
maven_group = org.saturnclient
|
|
||||||
archives_base_name = saturnclient
|
|
||||||
|
|
||||||
player_anim = 2.0.3+1.21.4
|
|
||||||
Vendored
BIN
Binary file not shown.
-7
@@ -1,7 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright © 2015-2021 the original authors.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
|
||||||
#
|
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
app_path=$0
|
|
||||||
|
|
||||||
# Need this for daisy-chained symlinks.
|
|
||||||
while
|
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
|
||||||
[ -h "$app_path" ]
|
|
||||||
do
|
|
||||||
ls=$( ls -ld "$app_path" )
|
|
||||||
link=${ls#*' -> '}
|
|
||||||
case $link in #(
|
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# This is normally unused
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD=maximum
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
nonstop=false
|
|
||||||
case "$( uname )" in #(
|
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
|
||||||
Darwin* ) darwin=true ;; #(
|
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
|
||||||
NONSTOP* ) nonstop=true ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
|
||||||
else
|
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD=java
|
|
||||||
if ! command -v java >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|
||||||
case $MAX_FD in #(
|
|
||||||
max*)
|
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC2039,SC3045
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
|
||||||
warn "Could not query maximum file descriptor limit"
|
|
||||||
esac
|
|
||||||
case $MAX_FD in #(
|
|
||||||
'' | soft) :;; #(
|
|
||||||
*)
|
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC2039,SC3045
|
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
|
||||||
# * args from the command line
|
|
||||||
# * the main class name
|
|
||||||
# * -classpath
|
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if "$cygwin" || "$msys" ; then
|
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
for arg do
|
|
||||||
if
|
|
||||||
case $arg in #(
|
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
|
||||||
# and any embedded shellness will be escaped.
|
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
|
||||||
# treated as '${Hostname}' itself on the command line.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
Vendored
-94
@@ -1,94 +0,0 @@
|
|||||||
@rem
|
|
||||||
@rem Copyright 2015 the original author or authors.
|
|
||||||
@rem
|
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
@rem you may not use this file except in compliance with the License.
|
|
||||||
@rem You may obtain a copy of the License at
|
|
||||||
@rem
|
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@rem
|
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
@rem See the License for the specific language governing permissions and
|
|
||||||
@rem limitations under the License.
|
|
||||||
@rem
|
|
||||||
@rem SPDX-License-Identifier: Apache-2.0
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
|
||||||
@rem This is normally unused
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
|
||||||
|
|
||||||
echo. 1>&2
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
|
||||||
echo. 1>&2
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
|
||||||
echo location of your Java installation. 1>&2
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo. 1>&2
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
|
||||||
echo. 1>&2
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
|
||||||
echo location of your Java installation. 1>&2
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
+22
-8
@@ -1,9 +1,23 @@
|
|||||||
pluginManagement {
|
rootProject.name = "saturn-client"
|
||||||
repositories {
|
|
||||||
maven {
|
include("common")
|
||||||
name = 'Fabric'
|
|
||||||
url = 'https://maven.fabricmc.net/'
|
def versions = [
|
||||||
}
|
"1.21",
|
||||||
gradlePluginPortal()
|
"1.21.1",
|
||||||
}
|
"1.21.2",
|
||||||
|
"1.21.3",
|
||||||
|
"1.21.4",
|
||||||
|
"1.21.5",
|
||||||
|
"1.21.6",
|
||||||
|
"1.21.7",
|
||||||
|
"1.21.8",
|
||||||
|
"1.21.9",
|
||||||
|
"1.21.10",
|
||||||
|
"1.21.11"
|
||||||
|
]
|
||||||
|
|
||||||
|
versions.each { v ->
|
||||||
|
include("mc-$v")
|
||||||
|
project(":mc-$v").projectDir = file("versions/$v")
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Emotes {
|
|
||||||
public static List<String> availableEmotes = new ArrayList<>();
|
|
||||||
|
|
||||||
public static void initialize() {
|
|
||||||
availableEmotes.add("tpose");
|
|
||||||
availableEmotes.add("back_flip");
|
|
||||||
availableEmotes.add("facepalm");
|
|
||||||
availableEmotes.add("cry");
|
|
||||||
availableEmotes.add("giga_chad");
|
|
||||||
availableEmotes.add("front_flip");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.cloak;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
|
||||||
import org.saturnclient.saturnclient.config.Config;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModel;
|
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
|
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModel.LayerType;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRenderer;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
|
||||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.component.DataComponentTypes;
|
|
||||||
import net.minecraft.component.type.EquippableComponent;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.item.equipment.EquipmentAsset;
|
|
||||||
import net.minecraft.registry.RegistryKey;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import net.minecraft.util.math.RotationAxis;
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cloak renderer
|
|
||||||
* - **Efficient**: uses a image only consisting of the cloak texture, 16x8
|
|
||||||
* whitespace.
|
|
||||||
* - **Curving**: Curves when player velocity increases.
|
|
||||||
* - **Wavey**: Makes the cloak feel like wind, when jump sprinting.
|
|
||||||
*/
|
|
||||||
public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
|
|
||||||
private final EquipmentModelLoader equipmentModelLoader;
|
|
||||||
private static final int PARTS = 24;
|
|
||||||
private final float[] segmentValues = new float[PARTS];
|
|
||||||
|
|
||||||
private static final float TEX_W = 176f;
|
|
||||||
private static final float TEX_H = 138f;
|
|
||||||
|
|
||||||
private static final class PxRect {
|
|
||||||
final int x, y, w, h;
|
|
||||||
|
|
||||||
PxRect(int x, int y, int w, int h) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.w = w;
|
|
||||||
this.h = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
float u1() {
|
|
||||||
return x / TEX_W;
|
|
||||||
}
|
|
||||||
|
|
||||||
float v1() {
|
|
||||||
return y / TEX_H;
|
|
||||||
}
|
|
||||||
|
|
||||||
float u2() {
|
|
||||||
return (x + w) / TEX_W;
|
|
||||||
}
|
|
||||||
|
|
||||||
float v2() {
|
|
||||||
return (y + h) / TEX_H;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final PxRect FRONT_RECT = new PxRect(8, 8, 80, 128);
|
|
||||||
private static final PxRect BACK_RECT = new PxRect(96, 8, 80, 128);
|
|
||||||
private static final PxRect LEFT_EDGE_RECT = new PxRect(0, 8, 8, 128);
|
|
||||||
private static final PxRect RIGHT_EDGE_RECT = new PxRect(88, 8, 8, 128);
|
|
||||||
private static final PxRect TOP_RECT = new PxRect(8, 0, 80, 8);
|
|
||||||
private static final PxRect BOTTOM_RECT = new PxRect(88, 0, 80, 8);
|
|
||||||
|
|
||||||
public CloakFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context,
|
|
||||||
EquipmentModelLoader equipmentModelLoader) {
|
|
||||||
super(context);
|
|
||||||
this.equipmentModelLoader = equipmentModelLoader;
|
|
||||||
Arrays.fill(segmentValues, 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) {
|
|
||||||
EquippableComponent equippableComponent = (EquippableComponent) stack
|
|
||||||
.get(DataComponentTypes.EQUIPPABLE);
|
|
||||||
if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) {
|
|
||||||
EquipmentModel equipmentModel = this.equipmentModelLoader
|
|
||||||
.get((RegistryKey<EquipmentAsset>) equippableComponent.assetId().get());
|
|
||||||
return !equipmentModel.getLayers(layerType).isEmpty();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderCapeQuad(VertexConsumer vertexConsumer, MatrixStack matrixStack,
|
|
||||||
Vec3d bottomLeft, Vec3d bottomRight, Vec3d topRight, Vec3d topLeft,
|
|
||||||
float u1, float v1, float u2, float v2, int light, int overlay,
|
|
||||||
float normalX, float normalY, float normalZ, boolean flipWinding) {
|
|
||||||
MatrixStack.Entry entry = matrixStack.peek();
|
|
||||||
|
|
||||||
if (!flipWinding) {
|
|
||||||
// CCW
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y,
|
|
||||||
(float) bottomLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y,
|
|
||||||
(float) bottomRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y,
|
|
||||||
(float) topRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y,
|
|
||||||
(float) topLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
} else {
|
|
||||||
// CW
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y,
|
|
||||||
(float) topLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y,
|
|
||||||
(float) topRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y,
|
|
||||||
(float) bottomRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y,
|
|
||||||
(float) bottomLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderCape(MatrixStack matrixStack, VertexConsumer vertexConsumer, PlayerEntityRenderState playerState,
|
|
||||||
int light, int overlay) {
|
|
||||||
float capeWidth = 10.0f / 16.0f;
|
|
||||||
float capeHeight = 16.0f / 16.0f;
|
|
||||||
float capeDepth = 1.0f / 16.0f;
|
|
||||||
float capePartHeight = capeHeight / PARTS;
|
|
||||||
|
|
||||||
float x1 = -capeWidth / 2.0f;
|
|
||||||
float x2 = capeWidth / 2.0f;
|
|
||||||
|
|
||||||
final float frontU1 = FRONT_RECT.u1();
|
|
||||||
final float frontU2 = FRONT_RECT.u2();
|
|
||||||
final float frontV1 = FRONT_RECT.v1();
|
|
||||||
final float frontV2 = FRONT_RECT.v2();
|
|
||||||
final float frontPartV = (frontV2 - frontV1) / PARTS;
|
|
||||||
final float backU1 = BACK_RECT.u1();
|
|
||||||
final float backU2 = BACK_RECT.u2();
|
|
||||||
final float backV1 = BACK_RECT.v1();
|
|
||||||
final float backV2 = BACK_RECT.v2();
|
|
||||||
final float backPartV = (backV2 - backV1) / PARTS;
|
|
||||||
final float leftU1 = LEFT_EDGE_RECT.u1();
|
|
||||||
final float leftU2 = LEFT_EDGE_RECT.u2();
|
|
||||||
final float leftV1 = LEFT_EDGE_RECT.v1();
|
|
||||||
final float leftV2 = LEFT_EDGE_RECT.v2();
|
|
||||||
final float leftPartV = (leftV2 - leftV1) / PARTS;
|
|
||||||
final float rightU1 = RIGHT_EDGE_RECT.u1();
|
|
||||||
final float rightU2 = RIGHT_EDGE_RECT.u2();
|
|
||||||
final float rightV1 = RIGHT_EDGE_RECT.v1();
|
|
||||||
final float rightV2 = RIGHT_EDGE_RECT.v2();
|
|
||||||
final float rightPartV = (rightV2 - rightV1) / PARTS;
|
|
||||||
final float topU1 = TOP_RECT.u1();
|
|
||||||
final float topU2 = TOP_RECT.u2();
|
|
||||||
final float topV1 = TOP_RECT.v1();
|
|
||||||
final float topV2 = TOP_RECT.v2();
|
|
||||||
final float bottomU1 = BOTTOM_RECT.u1();
|
|
||||||
final float bottomU2 = BOTTOM_RECT.u2();
|
|
||||||
final float bottomV1 = BOTTOM_RECT.v1();
|
|
||||||
final float bottomV2 = BOTTOM_RECT.v2();
|
|
||||||
|
|
||||||
float curY = 0.0f;
|
|
||||||
float curZ = 0.0f;
|
|
||||||
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
|
||||||
float angle = (2.0f - segmentValues[i]) * ((float) Math.PI / 2f);
|
|
||||||
float dirY = -(float) Math.cos(angle);
|
|
||||||
float dirZ = (float) Math.sin(angle);
|
|
||||||
|
|
||||||
// 1. Thickness at the START of this segment (Must match PREVIOUS segment's end)
|
|
||||||
float prevAngle = (i > 0) ? (2.0f - segmentValues[i - 1]) * ((float) Math.PI / 2f) : angle;
|
|
||||||
float startAvgAngle = (angle + prevAngle) / 2.0f;
|
|
||||||
float thickYStart = (float) Math.sin(startAvgAngle) * capeDepth;
|
|
||||||
float thickZStart = (float) Math.cos(startAvgAngle) * capeDepth;
|
|
||||||
|
|
||||||
// 2. Thickness at the END of this segment (Must match NEXT segment's start)
|
|
||||||
float nextAngle = (i < PARTS - 1) ? (2.0f - segmentValues[i + 1]) * ((float) Math.PI / 2f) : angle;
|
|
||||||
float endAvgAngle = (angle + nextAngle) / 2.0f;
|
|
||||||
float thickYEnd = (float) Math.sin(endAvgAngle) * capeDepth;
|
|
||||||
float thickZEnd = (float) Math.cos(endAvgAngle) * capeDepth;
|
|
||||||
|
|
||||||
float nextY = curY + dirY * capePartHeight;
|
|
||||||
float nextZ = curZ + dirZ * capePartHeight;
|
|
||||||
|
|
||||||
// Inner Vertices (The spine of the cape)
|
|
||||||
Vec3d innerTopLeft = new Vec3d(x2, curY, curZ);
|
|
||||||
Vec3d innerTopRight = new Vec3d(x1, curY, curZ);
|
|
||||||
Vec3d innerBotLeft = new Vec3d(x2, nextY, nextZ);
|
|
||||||
Vec3d innerBotRight = new Vec3d(x1, nextY, nextZ);
|
|
||||||
|
|
||||||
// Outer Vertices (Offset by the specific joint thickness)
|
|
||||||
Vec3d outerTopLeft = new Vec3d(x2, curY - thickYStart, curZ - thickZStart);
|
|
||||||
Vec3d outerTopRight = new Vec3d(x1, curY - thickYStart, curZ - thickZStart);
|
|
||||||
Vec3d outerBotLeft = new Vec3d(x2, nextY - thickYEnd, nextZ - thickZEnd);
|
|
||||||
Vec3d outerBotRight = new Vec3d(x1, nextY - thickYEnd, nextZ - thickZEnd);
|
|
||||||
|
|
||||||
// FRONT (facing camera)
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
outerBotRight, outerBotLeft, outerTopLeft, outerTopRight,
|
|
||||||
frontU2, frontV1 + (frontPartV * i), frontU1, frontV1 + (frontPartV * (i + 1)),
|
|
||||||
light, overlay, 0, 0, 1, false);
|
|
||||||
|
|
||||||
// BACK (Facing player)
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
innerBotLeft, innerBotRight, innerTopRight, innerTopLeft,
|
|
||||||
backU2, backV1 + (backPartV * i), backU1, backV1 + (backPartV * (i + 1)),
|
|
||||||
light, overlay, 0, 0, -1, false);
|
|
||||||
|
|
||||||
// LEFT EDGE
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
innerBotLeft, outerBotLeft, outerTopLeft, innerTopLeft,
|
|
||||||
leftU1, leftV1 + (leftPartV * i), leftU2, leftV1 + (leftPartV * (i + 1)),
|
|
||||||
light, overlay, 1, 0, 0, false);
|
|
||||||
|
|
||||||
// RIGHT EDGE
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
outerBotRight, innerBotRight, innerTopRight, outerTopRight,
|
|
||||||
rightU1, rightV1 + (rightPartV * i), rightU2, rightV1 + (rightPartV * (i + 1)),
|
|
||||||
light, overlay, -1, 0, 0, false);
|
|
||||||
|
|
||||||
// TOP FACE (Only on first segment)
|
|
||||||
if (i == 0) {
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
outerTopLeft, outerTopRight, innerTopRight, innerTopLeft,
|
|
||||||
topU1, topV1, topU2, topV2, light, overlay, 0, 1, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BOTTOM FACE (Only on last segment)
|
|
||||||
if (i == PARTS - 1) {
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
innerBotLeft, innerBotRight, outerBotRight, outerBotLeft,
|
|
||||||
bottomU1, bottomV1, bottomU2, bottomV2, light, overlay, 0, -1, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
curY = nextY;
|
|
||||||
curZ = nextZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift array left and insert new value at the start
|
|
||||||
private void updateSegmentValues(float value) {
|
|
||||||
// Shift all elements one position to the right (from end to start)
|
|
||||||
for (int i = segmentValues.length - 1; i > 0; i--) {
|
|
||||||
segmentValues[i] = segmentValues[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert the new value at index 0
|
|
||||||
segmentValues[0] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long lastUpdate = 0;
|
|
||||||
|
|
||||||
public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light,
|
|
||||||
PlayerEntityRenderState playerEntityRenderState, float f, float g) {
|
|
||||||
if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible
|
|
||||||
|| playerEntityRenderState.skinTextures.capeTexture() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(playerEntityRenderState.name);
|
|
||||||
|
|
||||||
if (player == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Identifier customCape = Cloaks.getCurrentCloakTexture(player.uuid);
|
|
||||||
if (customCape == null
|
|
||||||
|| this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.WINGS)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int minBrightness = 7;
|
|
||||||
int blockLight = (light >> 4) & 0xF;
|
|
||||||
int skyLight = (light >> 20) & 0xF;
|
|
||||||
blockLight = Math.max(blockLight, minBrightness);
|
|
||||||
skyLight = Math.max(skyLight, minBrightness);
|
|
||||||
light = (skyLight << 20) | (blockLight << 4);
|
|
||||||
|
|
||||||
VertexConsumer vertexConsumer = vertexConsumerProvider
|
|
||||||
.getBuffer(RenderLayer.getEntityAlpha(customCape));
|
|
||||||
|
|
||||||
matrixStack.push();
|
|
||||||
|
|
||||||
matrixStack.translate(0.0f, 0.0f, 0.12f);
|
|
||||||
|
|
||||||
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) {
|
|
||||||
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playerEntityRenderState.isInSneakingPose) {
|
|
||||||
matrixStack.translate(0, 0.16f, 0.0f);
|
|
||||||
matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(29.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastUpdate >= 20) {
|
|
||||||
float velX = Math.min(1.0f, playerEntityRenderState.field_53537 / 108.0f);
|
|
||||||
float rawVelY = playerEntityRenderState.field_53536;
|
|
||||||
float velY = (rawVelY > 4.0f ? rawVelY : 0.0f) / 16;
|
|
||||||
|
|
||||||
float value = playerEntityRenderState.isSwimming ? 0.0f : velX + velY;
|
|
||||||
|
|
||||||
if (Config.cloakPhysics.value) {
|
|
||||||
updateSegmentValues(value);
|
|
||||||
} else {
|
|
||||||
float last = segmentValues[0];
|
|
||||||
float maxStep = 0.02f;
|
|
||||||
float next = value;
|
|
||||||
|
|
||||||
if (next > last)
|
|
||||||
next = Math.min(next, last + maxStep);
|
|
||||||
else if (next < last)
|
|
||||||
next = Math.max(next, last - maxStep);
|
|
||||||
|
|
||||||
Arrays.fill(segmentValues, next);
|
|
||||||
}
|
|
||||||
lastUpdate = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light, OverlayTexture.DEFAULT_UV);
|
|
||||||
|
|
||||||
matrixStack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.cloak;
|
|
||||||
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
|
||||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import org.saturnclient.cosmetics.cloak.utils.AnimatedCloakData;
|
|
||||||
import org.saturnclient.cosmetics.cloak.utils.IdentifierUtils;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages the cloak system for Saturn Client.
|
|
||||||
* Handles loading and caching of player cloaks.
|
|
||||||
* Originally created by IIpho3nix and modified for Saturn Client by leo.
|
|
||||||
*/
|
|
||||||
public class Cloaks {
|
|
||||||
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", "bmw", "amg", "amg_petronas",
|
|
||||||
"ferrari", "redbull", "black_hole_amethyst", "black_hole_flame", "black_hole_white" };
|
|
||||||
private static final String[] ANIMATED_CLOAKS = { "glitch", "black_hole_amethyst", "black_hole_flame",
|
|
||||||
"black_hole_white" };
|
|
||||||
|
|
||||||
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
|
|
||||||
public static final List<String> availableCloaks = new ArrayList<>();
|
|
||||||
public static Identifier cloakCacheIdentifier = null;
|
|
||||||
public static final Map<UUID, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
|
|
||||||
private static final Map<UUID, Long> lastFrameTime = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool(
|
|
||||||
Math.min(4, Runtime.getRuntime().availableProcessors()),
|
|
||||||
r -> {
|
|
||||||
Thread t = new Thread(r, "cloak-loader");
|
|
||||||
t.setDaemon(true);
|
|
||||||
t.setPriority(Thread.NORM_PRIORITY - 1);
|
|
||||||
return t;
|
|
||||||
});
|
|
||||||
|
|
||||||
private static final ConcurrentHashMap<String, List<AnimatedCloakData>> CLOAK_CACHE = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the cloak system.
|
|
||||||
* Loads cloak textures from resources.
|
|
||||||
*/
|
|
||||||
public static void initialize() {
|
|
||||||
availableCloaks.add(0, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets cloak of the current player
|
|
||||||
*
|
|
||||||
* @param cloakName Name of the cloak file to load
|
|
||||||
*/
|
|
||||||
public static void setCloak(String cloakName) {
|
|
||||||
if (availableCloaks.contains(cloakName)) {
|
|
||||||
setCloak(ServiceClient.uuid, cloakName);
|
|
||||||
ServiceClient.setCloak(cloakName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles loading and caching of a new cloak texture, without connecting to the
|
|
||||||
* server.
|
|
||||||
*
|
|
||||||
* @param cloakName Name of the cloak file to load
|
|
||||||
*/
|
|
||||||
public static void setCloak(UUID uuid, String cloakName) {
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(uuid);
|
|
||||||
|
|
||||||
if (player != null) {
|
|
||||||
player.cloak = cloakName;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadCloak(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles loading and caching of a new cloak texture, without connecting to the
|
|
||||||
* server.
|
|
||||||
*
|
|
||||||
* @param cloakName Name of the cloak file to load
|
|
||||||
*/
|
|
||||||
public static void loadCloak(UUID uuid) {
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(uuid);
|
|
||||||
|
|
||||||
if (player != null && player.cloak != null) {
|
|
||||||
if (!player.cloak.isEmpty()) {
|
|
||||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) {
|
|
||||||
loadAnimatedCloakAsync(uuid, player.cloak)
|
|
||||||
.exceptionally(throwable -> {
|
|
||||||
SaturnClient.LOGGER.error("Failed to load animated cloak for player: " + player.cloak,
|
|
||||||
throwable);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
SaturnClient.client.execute(() -> loadStaticCloak(player.cloak + ".png"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads and processes a static cloak from a PNG file in resources.
|
|
||||||
*
|
|
||||||
* @param fileName Name of the PNG file to load
|
|
||||||
*/
|
|
||||||
private static void loadStaticCloak(String fileName) {
|
|
||||||
try {
|
|
||||||
String resourcePath = CLOAKS_RESOURCE_PATH + fileName;
|
|
||||||
InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath);
|
|
||||||
|
|
||||||
if (inputStream != null) {
|
|
||||||
BufferedImage image = ImageIO.read(inputStream);
|
|
||||||
|
|
||||||
// Add debug logging for static cloak
|
|
||||||
SaturnClient.LOGGER.info("Static cloak dimensions: " + image.getWidth() + "x" + image.getHeight());
|
|
||||||
|
|
||||||
String safeFileName = fileName.toLowerCase(Locale.ROOT)
|
|
||||||
.replace(' ', '_')
|
|
||||||
.replaceAll("[^a-z0-9/._-]", "");
|
|
||||||
|
|
||||||
cloakCacheIdentifier = Identifier.of(SaturnClient.MOD_ID, "cloaks_" + safeFileName);
|
|
||||||
IdentifierUtils.registerBufferedImageTexture(cloakCacheIdentifier, image);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to load static cloak from resources: " + fileName, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CompletableFuture<Void> loadAnimatedCloakAsync(UUID uuid, String cloakName) {
|
|
||||||
return CompletableFuture.runAsync(() -> {
|
|
||||||
String fileName = cloakName + ".gif";
|
|
||||||
|
|
||||||
// Check cache first
|
|
||||||
List<AnimatedCloakData> cached = CLOAK_CACHE.get(fileName);
|
|
||||||
if (cached != null) {
|
|
||||||
animatedCloaks.put(uuid, cached);
|
|
||||||
lastFrameTime.put(uuid, System.currentTimeMillis());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String resourcePath = CLOAKS_RESOURCE_PATH + fileName;
|
|
||||||
|
|
||||||
try (InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath)) {
|
|
||||||
if (inputStream == null) {
|
|
||||||
SaturnClient.LOGGER.warn("Cloak resource not found: " + resourcePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all bytes at once - more efficient than multiple reads
|
|
||||||
byte[] data = inputStream.readAllBytes();
|
|
||||||
GifDecoder.GifImage gif = GifDecoder.read(data);
|
|
||||||
|
|
||||||
int frameCount = gif.getFrameCount();
|
|
||||||
if (frameCount == 0) {
|
|
||||||
SaturnClient.LOGGER.warn("No frames found in animated cloak: " + fileName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-allocate collections with known size
|
|
||||||
List<AnimatedCloakData> animatedFrames = new ArrayList<>(frameCount);
|
|
||||||
String baseFrameId = fileName.replace(".gif", "");
|
|
||||||
|
|
||||||
// Process frames in batch
|
|
||||||
for (int i = 0; i < frameCount; i++) {
|
|
||||||
BufferedImage frame = gif.getFrame(i);
|
|
||||||
int delay = Math.max(gif.getDelay(i) * 10, 50); // Minimum 50ms delay
|
|
||||||
|
|
||||||
String frameId = baseFrameId + "_frame_" + i;
|
|
||||||
Identifier frameIdentifier = Identifier.of(SaturnClient.MOD_ID, "cloaks_" + frameId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
IdentifierUtils.registerBufferedImageTextureFast(frameIdentifier, frame);
|
|
||||||
// Register texture on main thread if required by the graphics system
|
|
||||||
if (SaturnClient.client.isOnThread()) {
|
|
||||||
// IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame);
|
|
||||||
} else {
|
|
||||||
// Queue for main thread execution
|
|
||||||
// SaturnClient.client.execute(
|
|
||||||
// () -> IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame));
|
|
||||||
}
|
|
||||||
|
|
||||||
animatedFrames.add(new AnimatedCloakData(frameIdentifier, delay));
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to register frame " + i + " for cloak: " + fileName, e);
|
|
||||||
// Continue with other frames instead of failing completely
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!animatedFrames.isEmpty()) {
|
|
||||||
// Cache the result for future use
|
|
||||||
CLOAK_CACHE.put(fileName, animatedFrames);
|
|
||||||
animatedCloaks.put(uuid, animatedFrames);
|
|
||||||
lastFrameTime.put(uuid, System.currentTimeMillis());
|
|
||||||
|
|
||||||
SaturnClient.LOGGER.info("Loaded {} frames for animated cloak: {} (cached)",
|
|
||||||
animatedFrames.size(), fileName);
|
|
||||||
} else {
|
|
||||||
SaturnClient.LOGGER.error("No valid frames could be loaded for cloak: " + fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to load animated cloak from resources: " + fileName, e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Unexpected error loading animated cloak: " + fileName, e);
|
|
||||||
}
|
|
||||||
}, CLOAK_LOADER_EXECUTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Identifier getCurrentCloakTexture(UUID uuid) {
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(uuid);
|
|
||||||
|
|
||||||
if (player == null || player.cloak.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String cloakName = player.cloak;
|
|
||||||
|
|
||||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
|
|
||||||
List<AnimatedCloakData> frames = animatedCloaks.get(uuid);
|
|
||||||
if (frames == null || frames.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
long lastTime = lastFrameTime.getOrDefault(uuid, currentTime);
|
|
||||||
int currentFrame = 0;
|
|
||||||
|
|
||||||
long elapsedTime = currentTime - lastTime;
|
|
||||||
long totalTime = 0;
|
|
||||||
for (int i = 0; i < frames.size(); i++) {
|
|
||||||
totalTime += frames.get(i).getDelayMs();
|
|
||||||
if (elapsedTime < totalTime) {
|
|
||||||
currentFrame = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elapsedTime >= totalTime) {
|
|
||||||
lastFrameTime.put(uuid, currentTime);
|
|
||||||
currentFrame = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Identifier identifier = frames.get(currentFrame).getTextureId();
|
|
||||||
|
|
||||||
if (identifier == null) {
|
|
||||||
return Identifier.of(SaturnClient.MOD_ID, "textures/cloaks/" + cloakName + ".png");
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier;
|
|
||||||
} else {
|
|
||||||
return Identifier.of(SaturnClient.MOD_ID, "textures/cloaks/" + cloakName + ".png");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,791 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.cloak;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.DataBufferInt;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2014 Dhyan Blum
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* A thread-safe decoder capable of processing a GIF data stream to render the graphics
|
|
||||||
* contained in it. This implementation follows the official
|
|
||||||
* <A HREF="http://www.w3.org/Graphics/GIF/spec-gif89a.txt">GIF
|
|
||||||
* specification</A>.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Example usage:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* final GifImage gifImage = GifDecoder.read(int[] data);
|
|
||||||
* final int width = gifImage.getWidth();
|
|
||||||
* final int height = gifImage.getHeight();
|
|
||||||
* final int frameCount = gifImage.getFrameCount();
|
|
||||||
* for (int i = 0; i < frameCount; i++) {
|
|
||||||
* final BufferedImage image = gifImage.getFrame(i);
|
|
||||||
* final int delay = gif.getDelay(i);
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author Dhyan Blum
|
|
||||||
* @version 1.09 November 2017 (Thread-safe version)
|
|
||||||
*/
|
|
||||||
public final class GifDecoder {
|
|
||||||
|
|
||||||
// Thread-local storage for reusable objects to avoid synchronization overhead
|
|
||||||
private static final ThreadLocal<DecodingContext> DECODING_CONTEXT =
|
|
||||||
ThreadLocal.withInitial(DecodingContext::new);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread-local context containing reusable objects for decoding operations.
|
|
||||||
* This eliminates the need for synchronization while maintaining performance benefits
|
|
||||||
* of object reuse within each thread.
|
|
||||||
*/
|
|
||||||
static final class DecodingContext {
|
|
||||||
final BitReader bitReader = new BitReader();
|
|
||||||
final CodeTable codeTable = new CodeTable();
|
|
||||||
int[] pixelBuffer;
|
|
||||||
int[] prevPixelBuffer;
|
|
||||||
int[] tempBuffer;
|
|
||||||
|
|
||||||
// Get or create a pixel buffer of the specified size
|
|
||||||
int[] getPixelBuffer(int size) {
|
|
||||||
if (pixelBuffer == null || pixelBuffer.length < size) {
|
|
||||||
pixelBuffer = new int[Math.max(size, 1024)]; // Minimum size to reduce allocations
|
|
||||||
}
|
|
||||||
return pixelBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get or create a previous pixel buffer of the specified size
|
|
||||||
int[] getPrevPixelBuffer(int size) {
|
|
||||||
if (prevPixelBuffer == null || prevPixelBuffer.length < size) {
|
|
||||||
prevPixelBuffer = new int[Math.max(size, 1024)];
|
|
||||||
}
|
|
||||||
return prevPixelBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get or create a temporary buffer of the specified size
|
|
||||||
int[] getTempBuffer(int size) {
|
|
||||||
if (tempBuffer == null || tempBuffer.length < size) {
|
|
||||||
tempBuffer = new int[Math.max(size, 1024)];
|
|
||||||
}
|
|
||||||
return tempBuffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class BitReader {
|
|
||||||
private int nextBitToRead;
|
|
||||||
private int numberOfBitsToRead;
|
|
||||||
private int bitMask; // Used to kill unwanted higher bits
|
|
||||||
private byte[] bytes; // Data array
|
|
||||||
|
|
||||||
// To avoid costly bounds checks, 'in' needs 2 more 0-bytes at the end
|
|
||||||
private void init(final byte[] bytes) {
|
|
||||||
this.bytes = bytes;
|
|
||||||
nextBitToRead = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int read() {
|
|
||||||
// Byte indices: (bitPos / 8), (bitPos / 8) + 1, (bitPos / 8) + 2
|
|
||||||
final int byteIndex = nextBitToRead >>> 3; // Byte = bit / 8
|
|
||||||
final int bitsToShiftRight = nextBitToRead & 7; // & 7 is the same as MODULO 8
|
|
||||||
|
|
||||||
// Use int operations to avoid repeated array access
|
|
||||||
final int byte0 = bytes[byteIndex] & 0xFF;
|
|
||||||
final int byte1 = bytes[byteIndex + 1] & 0xFF;
|
|
||||||
final int byte2 = bytes[byteIndex + 2] & 0xFF;
|
|
||||||
|
|
||||||
// Optimized bit manipulation
|
|
||||||
final int buffer = ((byte2 << 16) | (byte1 << 8) | byte0) >>> bitsToShiftRight;
|
|
||||||
nextBitToRead += numberOfBitsToRead;
|
|
||||||
return buffer & bitMask; // Kill the unwanted higher bits
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNumberOfBitsToRead(final int numberOfBitsToRead) {
|
|
||||||
this.numberOfBitsToRead = numberOfBitsToRead;
|
|
||||||
bitMask = (1 << numberOfBitsToRead) - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class CodeTable {
|
|
||||||
private final int[][] table; // Maps codes to lists of colors
|
|
||||||
private int initTableSize; // Number of colors +2 for CLEAR + EOI
|
|
||||||
private int initCodeSize; // Initial code size
|
|
||||||
private int initCodeLimit; // First code limit
|
|
||||||
private int codeSize; // Current code size, maximum is 12 bits
|
|
||||||
private int nextCode; // Next available code for a new entry
|
|
||||||
private int nextCodeLimit; // Increase codeSize when nextCode == limit
|
|
||||||
private BitReader bitReader; // Notify when code sizes increases
|
|
||||||
|
|
||||||
public CodeTable() {
|
|
||||||
table = new int[4096][];
|
|
||||||
}
|
|
||||||
|
|
||||||
private int add(final int[] indices) {
|
|
||||||
if (nextCode < 4096) {
|
|
||||||
if (nextCode == nextCodeLimit && codeSize < 12) {
|
|
||||||
codeSize++; // Max code size is 12
|
|
||||||
bitReader.setNumberOfBitsToRead(codeSize);
|
|
||||||
nextCodeLimit = (1 << codeSize) - 1; // 2^codeSize - 1
|
|
||||||
}
|
|
||||||
table[nextCode++] = indices;
|
|
||||||
}
|
|
||||||
return codeSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int clear() {
|
|
||||||
codeSize = initCodeSize;
|
|
||||||
bitReader.setNumberOfBitsToRead(codeSize);
|
|
||||||
nextCodeLimit = initCodeLimit;
|
|
||||||
nextCode = initTableSize; // Don't recreate table, reset pointer
|
|
||||||
return codeSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(final GifFrame fr, final int[] activeColTbl, final BitReader br) {
|
|
||||||
this.bitReader = br;
|
|
||||||
final int numColors = activeColTbl.length;
|
|
||||||
initCodeSize = fr.firstCodeSize;
|
|
||||||
initCodeLimit = (1 << initCodeSize) - 1; // 2^initCodeSize - 1
|
|
||||||
initTableSize = fr.endOfInfoCode + 1;
|
|
||||||
nextCode = initTableSize;
|
|
||||||
|
|
||||||
// Initialize single color entries
|
|
||||||
for (int c = 0; c < numColors; c++) {
|
|
||||||
table[c] = new int[]{activeColTbl[c]}; // Single element array
|
|
||||||
}
|
|
||||||
|
|
||||||
table[fr.clearCode] = new int[] { fr.clearCode }; // CLEAR
|
|
||||||
table[fr.endOfInfoCode] = new int[] { fr.endOfInfoCode }; // EOI
|
|
||||||
|
|
||||||
// Locate transparent color in code table and set to 0
|
|
||||||
if (fr.transpColFlag && fr.transpColIndex < numColors) {
|
|
||||||
table[fr.transpColIndex] = new int[]{0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class GifFrame {
|
|
||||||
// Graphic control extension (optional)
|
|
||||||
// Disposal: 0=NO_ACTION, 1=NO_DISPOSAL, 2=RESTORE_BG, 3=RESTORE_PREV
|
|
||||||
private volatile int disposalMethod; // 0-3 as above, 4-7 undefined
|
|
||||||
private volatile boolean transpColFlag; // 1 Bit
|
|
||||||
private volatile int delay; // Unsigned, LSByte first, n * 1/100 * s
|
|
||||||
private volatile int transpColIndex; // 1 Byte
|
|
||||||
// Image descriptor
|
|
||||||
private volatile int x; // Position on the canvas from the left
|
|
||||||
private volatile int y; // Position on the canvas from the top
|
|
||||||
private volatile int w; // May be smaller than the base image
|
|
||||||
private volatile int h; // May be smaller than the base image
|
|
||||||
private volatile int wh; // width * height
|
|
||||||
private volatile boolean hasLocColTbl; // Has local color table? 1 Bit
|
|
||||||
private volatile boolean interlaceFlag; // Is an interlace image? 1 Bit
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private volatile boolean sortFlag; // True if local colors are sorted, 1 Bit
|
|
||||||
private volatile int sizeOfLocColTbl; // Size of the local color table, 3 Bits
|
|
||||||
private volatile int[] localColTbl; // Local color table (optional)
|
|
||||||
// Image data
|
|
||||||
private volatile int firstCodeSize; // LZW minimum code size + 1 for CLEAR & EOI
|
|
||||||
private volatile int clearCode;
|
|
||||||
private volatile int endOfInfoCode;
|
|
||||||
private volatile byte[] data; // Holds LZW encoded data
|
|
||||||
private volatile BufferedImage img; // Full drawn image, not just the frame area
|
|
||||||
|
|
||||||
// Synchronization for frame image creation
|
|
||||||
private final Object imageLock = new Object();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class GifImage {
|
|
||||||
public volatile String header; // Bytes 0-5, GIF87a or GIF89a
|
|
||||||
private volatile int w; // Unsigned 16 Bit, the least significant byte first
|
|
||||||
private volatile int h; // Unsigned 16 Bit, the least significant byte first
|
|
||||||
private volatile int wh; // Image width * image height
|
|
||||||
public volatile boolean hasGlobColTbl; // 1 Bit
|
|
||||||
public volatile int colorResolution; // 3 Bits
|
|
||||||
public volatile boolean sortFlag; // True if global colors are sorted, 1 Bit
|
|
||||||
public volatile int sizeOfGlobColTbl; // 2^(val(3 Bits) + 1), see spec
|
|
||||||
public volatile int bgColIndex; // Background color index, 1 Byte
|
|
||||||
public volatile int pxAspectRatio; // Pixel aspect ratio, 1 Byte
|
|
||||||
public volatile int[] globalColTbl; // Global color table
|
|
||||||
private final List<GifFrame> frames = new ArrayList<>(64);
|
|
||||||
public volatile String appId = ""; // 8 Bytes at in[i+3], usually "NETSCAPE"
|
|
||||||
public volatile String appAuthCode = ""; // 3 Bytes at in[i+11], usually "2.0"
|
|
||||||
public volatile int repetitions = 0; // 0: infinite loop, N: number of loops
|
|
||||||
|
|
||||||
// Thread-safe frame rendering state
|
|
||||||
private volatile BufferedImage img = null; // Currently, drawn frame
|
|
||||||
private volatile Graphics2D g;
|
|
||||||
private final ReadWriteLock renderLock = new ReentrantReadWriteLock();
|
|
||||||
|
|
||||||
// Synchronization objects
|
|
||||||
private final Object initLock = new Object();
|
|
||||||
|
|
||||||
private int[] decode(final GifFrame fr, final int[] activeColTbl) {
|
|
||||||
final DecodingContext ctx = DECODING_CONTEXT.get();
|
|
||||||
ctx.codeTable.init(fr, activeColTbl, ctx.bitReader);
|
|
||||||
ctx.bitReader.init(fr.data); // Incoming codes
|
|
||||||
final int clearCode = fr.clearCode, endCode = fr.endOfInfoCode;
|
|
||||||
|
|
||||||
// Use thread-local pixel buffer
|
|
||||||
final int[] out = ctx.getPixelBuffer(wh);
|
|
||||||
|
|
||||||
final int[][] tbl = ctx.codeTable.table; // Code table
|
|
||||||
int outPos = 0; // Next pixel position in the output image array
|
|
||||||
ctx.codeTable.clear(); // Init code table
|
|
||||||
ctx.bitReader.read(); // Skip leading clear code
|
|
||||||
int code = ctx.bitReader.read(); // Read first code
|
|
||||||
int[] pixels = tbl[code]; // Output pixel for first code
|
|
||||||
|
|
||||||
// Use System.arraycopy for better performance
|
|
||||||
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
|
||||||
outPos += pixels.length;
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
final int prevCode = code;
|
|
||||||
code = ctx.bitReader.read(); // Get next code in stream
|
|
||||||
if (code == clearCode) { // After a CLEAR table, there is
|
|
||||||
ctx.codeTable.clear(); // no previous code, we need to read
|
|
||||||
code = ctx.bitReader.read(); // a new one
|
|
||||||
pixels = tbl[code]; // Output pixels
|
|
||||||
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
|
||||||
outPos += pixels.length;
|
|
||||||
continue; // Back to the loop with a valid previous code
|
|
||||||
} else if (code == endCode) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
final int[] prevVals = tbl[prevCode];
|
|
||||||
final int[] prevValsAndK = new int[prevVals.length + 1];
|
|
||||||
System.arraycopy(prevVals, 0, prevValsAndK, 0, prevVals.length);
|
|
||||||
if (code < ctx.codeTable.nextCode) { // Code table contains code
|
|
||||||
pixels = tbl[code]; // Output pixels
|
|
||||||
System.arraycopy(pixels, 0, out, outPos, pixels.length);
|
|
||||||
outPos += pixels.length;
|
|
||||||
prevValsAndK[prevVals.length] = tbl[code][0]; // K
|
|
||||||
} else {
|
|
||||||
prevValsAndK[prevVals.length] = prevVals[0]; // K
|
|
||||||
System.arraycopy(prevValsAndK, 0, out, outPos, prevValsAndK.length);
|
|
||||||
outPos += prevValsAndK.length;
|
|
||||||
}
|
|
||||||
ctx.codeTable.add(prevValsAndK); // Previous indices + K
|
|
||||||
}
|
|
||||||
} catch (final ArrayIndexOutOfBoundsException ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a properly sized array
|
|
||||||
return Arrays.copyOf(out, outPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] deinterlace(final int[] src, final GifFrame fr) {
|
|
||||||
final int w = fr.w, h = fr.h, wh = fr.wh;
|
|
||||||
final DecodingContext ctx = DECODING_CONTEXT.get();
|
|
||||||
final int[] dest = ctx.getTempBuffer(src.length);
|
|
||||||
|
|
||||||
// Interlaced images are organized in 4 sets of pixel lines
|
|
||||||
final int set2Y = (h + 7) >>> 3; // Line no. = ceil(h/8.0)
|
|
||||||
final int set3Y = set2Y + ((h + 3) >>> 3); // ceil(h-4/8.0)
|
|
||||||
final int set4Y = set3Y + ((h + 1) >>> 2); // ceil(h-2/4.0)
|
|
||||||
|
|
||||||
// Sets' start indices in source array
|
|
||||||
final int set2 = w * set2Y, set3 = w * set3Y, set4 = w * set4Y;
|
|
||||||
|
|
||||||
// Line skips in destination array - use bit shifts for multiplication
|
|
||||||
final int w2 = w << 1, w4 = w2 << 1, w8 = w4 << 1;
|
|
||||||
|
|
||||||
// Group 1 contains every 8th line starting from 0
|
|
||||||
int from = 0, to = 0;
|
|
||||||
for (; from < set2; from += w, to += w8) {
|
|
||||||
System.arraycopy(src, from, dest, to, w);
|
|
||||||
}
|
|
||||||
// Group 2 contains every 8th line starting from 4
|
|
||||||
for (to = w4; from < set3; from += w, to += w8) {
|
|
||||||
System.arraycopy(src, from, dest, to, w);
|
|
||||||
}
|
|
||||||
// Group 3 contains every 4th line starting from 2
|
|
||||||
for (to = w2; from < set4; from += w, to += w4) {
|
|
||||||
System.arraycopy(src, from, dest, to, w);
|
|
||||||
}
|
|
||||||
// Group 4 contains every 2nd line starting from 1 (biggest group)
|
|
||||||
for (to = w; from < wh; from += w, to += w2) {
|
|
||||||
System.arraycopy(src, from, dest, to, w);
|
|
||||||
}
|
|
||||||
return Arrays.copyOf(dest, src.length); // Return a copy, not the shared buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawFrame(final GifFrame fr) {
|
|
||||||
renderLock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
// Determine the color table that will be active for this frame
|
|
||||||
final int[] activeColTbl = fr.hasLocColTbl ? fr.localColTbl : globalColTbl;
|
|
||||||
|
|
||||||
// Get pixels from data stream
|
|
||||||
int[] pixels = decode(fr, activeColTbl);
|
|
||||||
if (fr.interlaceFlag) {
|
|
||||||
pixels = deinterlace(pixels, fr); // Rearrange pixel lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create image of type TYPE_INT_ARGB for frame area
|
|
||||||
final BufferedImage frame = new BufferedImage(fr.w, fr.h, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
final int[] frameData = ((DataBufferInt) frame.getRaster().getDataBuffer()).getData();
|
|
||||||
System.arraycopy(pixels, 0, frameData, 0, Math.min(pixels.length, fr.wh));
|
|
||||||
|
|
||||||
// Draw frame area on top of working image
|
|
||||||
g.drawImage(frame, fr.x, fr.y, null);
|
|
||||||
|
|
||||||
// Keep a copy of the previous frame's pixels
|
|
||||||
final DecodingContext ctx = DECODING_CONTEXT.get();
|
|
||||||
final int[] prevPixelBuffer = ctx.getPrevPixelBuffer(wh);
|
|
||||||
final int[] imgData = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
|
|
||||||
System.arraycopy(imgData, 0, prevPixelBuffer, 0, wh);
|
|
||||||
|
|
||||||
// Create another copy for the end user to not expose internal state
|
|
||||||
synchronized (fr.imageLock) {
|
|
||||||
if (fr.img == null) { // Double-checked locking
|
|
||||||
fr.img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
final int[] frameImgData = ((DataBufferInt) fr.img.getRaster().getDataBuffer()).getData();
|
|
||||||
System.arraycopy(prevPixelBuffer, 0, frameImgData, 0, wh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle disposal of current frame
|
|
||||||
if (fr.disposalMethod == 2) {
|
|
||||||
// Restore to background color (clear frame area only)
|
|
||||||
g.clearRect(fr.x, fr.y, fr.w, fr.h);
|
|
||||||
} else if (fr.disposalMethod == 3) {
|
|
||||||
// Restore previous frame
|
|
||||||
System.arraycopy(prevPixelBuffer, 0, imgData, 0, wh);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
renderLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the background color of the first frame in this GIF image. If
|
|
||||||
* the frame has a local color table, the returned color will be from
|
|
||||||
* that table. If not, the color will be from the global color table.
|
|
||||||
* Returns 0 if there is neither a local nor a global color table.
|
|
||||||
*
|
|
||||||
* @return 32 bit ARGB color in the form 0xAARRGGBB
|
|
||||||
*/
|
|
||||||
public final int getBackgroundColor() {
|
|
||||||
renderLock.readLock().lock();
|
|
||||||
try {
|
|
||||||
final GifFrame frame = frames.get(0);
|
|
||||||
if (frame.hasLocColTbl) {
|
|
||||||
return frame.localColTbl[bgColIndex];
|
|
||||||
} else if (hasGlobColTbl) {
|
|
||||||
return globalColTbl[bgColIndex];
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
} finally {
|
|
||||||
renderLock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If not 0, the delay specifies how many hundredths (1/100) of a second
|
|
||||||
* to wait before displaying the frame <i>after</i> the current frame.
|
|
||||||
*
|
|
||||||
* @param index Index of the current frame, 0 to N-1
|
|
||||||
* @return Delay as number of hundredths (1/100) of a second
|
|
||||||
*/
|
|
||||||
public final int getDelay(final int index) {
|
|
||||||
renderLock.readLock().lock();
|
|
||||||
try {
|
|
||||||
return frames.get(index).delay;
|
|
||||||
} finally {
|
|
||||||
renderLock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param index Index of the frame to return as image, starting from 0.
|
|
||||||
* For incremental calls such as [0, 1, 2, ...] the method's
|
|
||||||
* run time is O(1) as only one frame is drawn per call. For
|
|
||||||
* random access calls such as [7, 12, ...] the run time is
|
|
||||||
* O(N+1) with N being the number of previous frames that
|
|
||||||
* need to be drawn before N+1 can be drawn on top. Once a
|
|
||||||
* frame has been drawn it is being cached and the run time
|
|
||||||
* is more or less O(0) to retrieve it from the list.
|
|
||||||
* @return A BufferedImage for the specified frame.
|
|
||||||
*/
|
|
||||||
public BufferedImage getFrame(final int index) {
|
|
||||||
// Initialize rendering context if needed
|
|
||||||
if (img == null) {
|
|
||||||
synchronized (initLock) {
|
|
||||||
if (img == null) { // Double-checked locking
|
|
||||||
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
g = img.createGraphics();
|
|
||||||
g.setBackground(new Color(0, true)); // Transparent color
|
|
||||||
// Set rendering hints for better performance
|
|
||||||
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
|
|
||||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GifFrame fr = frames.get(index);
|
|
||||||
|
|
||||||
// Check if frame is already rendered using double-checked locking
|
|
||||||
if (fr.img == null) {
|
|
||||||
synchronized (fr.imageLock) {
|
|
||||||
if (fr.img == null) {
|
|
||||||
// Draw all frames until and including the requested frame
|
|
||||||
for (int i = 0; i <= index; i++) {
|
|
||||||
fr = frames.get(i);
|
|
||||||
if (fr.img == null) {
|
|
||||||
drawFrame(fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fr.img;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The number of frames contained in this GIF image
|
|
||||||
*/
|
|
||||||
public final int getFrameCount() {
|
|
||||||
return frames.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The height of the GIF image
|
|
||||||
*/
|
|
||||||
public final int getHeight() {
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The width of the GIF image
|
|
||||||
*/
|
|
||||||
public final int getWidth() {
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final boolean DEBUG_MODE = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param in Raw image data as a byte[] array
|
|
||||||
* @return A GifImage object exposing the properties of the GIF image.
|
|
||||||
* @throws IOException If the image violates the GIF specification or is
|
|
||||||
* truncated.
|
|
||||||
*/
|
|
||||||
public static GifImage read(final byte[] in) throws IOException {
|
|
||||||
final GifDecoder decoder = new GifDecoder();
|
|
||||||
final GifImage img = decoder.new GifImage();
|
|
||||||
GifFrame frame = null; // Currently open frame
|
|
||||||
int pos = readHeader(in, img); // Read header, get next byte position
|
|
||||||
pos = readLogicalScreenDescriptor(img, in, pos);
|
|
||||||
if (img.hasGlobColTbl) {
|
|
||||||
img.globalColTbl = new int[img.sizeOfGlobColTbl];
|
|
||||||
pos = readColTbl(in, img.globalColTbl, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main parsing loop with bounds checking optimization
|
|
||||||
while (pos < in.length) {
|
|
||||||
final int block = in[pos] & 0xFF;
|
|
||||||
switch (block) {
|
|
||||||
case 0x21: // Extension introducer
|
|
||||||
if (pos + 1 >= in.length) {
|
|
||||||
throw new IOException("Unexpected end of file.");
|
|
||||||
}
|
|
||||||
switch (in[pos + 1] & 0xFF) {
|
|
||||||
case 0xFE: // Comment extension
|
|
||||||
pos = readTextExtension(in, pos);
|
|
||||||
break;
|
|
||||||
case 0xFF: // Application extension
|
|
||||||
pos = readAppExt(img, in, pos);
|
|
||||||
break;
|
|
||||||
case 0x01: // Plain text extension
|
|
||||||
frame = null; // End of current frame
|
|
||||||
pos = readTextExtension(in, pos);
|
|
||||||
break;
|
|
||||||
case 0xF9: // Graphic control extension
|
|
||||||
if (frame == null) {
|
|
||||||
frame = decoder.new GifFrame();
|
|
||||||
img.frames.add(frame);
|
|
||||||
}
|
|
||||||
pos = readGraphicControlExt(frame, in, pos);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IOException("Unknown extension at " + pos);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x2C: // Image descriptor
|
|
||||||
if (frame == null) {
|
|
||||||
frame = decoder.new GifFrame();
|
|
||||||
img.frames.add(frame);
|
|
||||||
}
|
|
||||||
pos = readImgDescr(frame, in, pos);
|
|
||||||
if (frame.hasLocColTbl) {
|
|
||||||
frame.localColTbl = new int[frame.sizeOfLocColTbl];
|
|
||||||
pos = readColTbl(in, frame.localColTbl, pos);
|
|
||||||
}
|
|
||||||
pos = readImgData(frame, in, pos);
|
|
||||||
frame = null; // End of current frame
|
|
||||||
break;
|
|
||||||
case 0x3B: // GIF Trailer
|
|
||||||
return img; // Found trailer, finished reading.
|
|
||||||
default:
|
|
||||||
// Unknown block. The image is corrupted. Strategies: a) Skip
|
|
||||||
// and wait for a valid block. Experience: It'll get worse. b)
|
|
||||||
// Throw exception. c) Return gracefully if we are almost done
|
|
||||||
// processing. The frames we have so far should be error-free.
|
|
||||||
final double progress = (double) pos / in.length;
|
|
||||||
if (progress < 0.9) {
|
|
||||||
throw new IOException("Unknown block at: " + pos);
|
|
||||||
}
|
|
||||||
pos = in.length; // Exit loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param is Image data as input stream. This method will read from the
|
|
||||||
* input stream's current position. It will not reset the
|
|
||||||
* position before reading and won't reset or close the stream
|
|
||||||
* afterwards. Call these methods before and after calling this
|
|
||||||
* method as needed.
|
|
||||||
* @return A GifImage object exposing the properties of the GIF image.
|
|
||||||
* @throws IOException If an I/O error occurs, the image violates the GIF
|
|
||||||
* specification or the GIF is truncated.
|
|
||||||
*/
|
|
||||||
public static GifImage read(final InputStream is) throws IOException {
|
|
||||||
final byte[] data = new byte[is.available()];
|
|
||||||
is.read(data, 0, data.length);
|
|
||||||
return read(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param img GIF image
|
|
||||||
* @param in Raw data
|
|
||||||
* @param i Index of the first byte of the application extension
|
|
||||||
* @return Index of the first byte after this extension
|
|
||||||
*/
|
|
||||||
static int readAppExt(final GifImage img, final byte[] in, int i) {
|
|
||||||
// Use StandardCharsets for better performance if available, otherwise fallback
|
|
||||||
img.appId = new String(in, i + 3, 8); // should be "NETSCAPE"
|
|
||||||
img.appAuthCode = new String(in, i + 11, 3); // should be "2.0"
|
|
||||||
i += 14; // Go to sub-block size, it's value should be 3
|
|
||||||
final int subBlockSize = in[i] & 0xFF;
|
|
||||||
// The only app extension widely used is NETSCAPE, it's got 3 data bytes
|
|
||||||
if (subBlockSize == 3) {
|
|
||||||
// in[i+1] should have value 01, in[i+5] should be block terminator
|
|
||||||
img.repetitions = (in[i + 2] & 0xFF) | ((in[i + 3] & 0xFF) << 8); // Short
|
|
||||||
return i + 5;
|
|
||||||
} // Skip unknown application extensions
|
|
||||||
while ((in[i] & 0xFF) != 0) { // While sub-block size != 0
|
|
||||||
i += (in[i] & 0xFF) + 1; // Skip to next sub-block
|
|
||||||
}
|
|
||||||
return i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param in Raw data
|
|
||||||
* @param colors Pre-initialized target array to store ARGB colors
|
|
||||||
* @param i Index of the color table's first byte
|
|
||||||
* @return Index of the first byte after the color table
|
|
||||||
*/
|
|
||||||
static int readColTbl(final byte[] in, final int[] colors, int i) {
|
|
||||||
final int numColors = colors.length;
|
|
||||||
for (int c = 0; c < numColors; c++) {
|
|
||||||
final int r = in[i++] & 0xFF; // 1st byte is red
|
|
||||||
final int g = in[i++] & 0xFF; // 2nd byte is green
|
|
||||||
final int b = in[i++] & 0xFF; // 3rd byte is blue
|
|
||||||
// Optimized color packing - alpha is always 0xFF for opaque
|
|
||||||
colors[c] = 0xFF000000 | (r << 16) | (g << 8) | b;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fr GIF frame
|
|
||||||
* @param in Raw data
|
|
||||||
* @param i Index of the extension introducer
|
|
||||||
* @return Index of the first byte after this block
|
|
||||||
*/
|
|
||||||
static int readGraphicControlExt(final GifFrame fr, final byte[] in, final int i) {
|
|
||||||
final int packed = in[i + 3] & 0xFF;
|
|
||||||
fr.disposalMethod = (packed & 0b00011100) >>> 2; // Bits 4-2
|
|
||||||
fr.transpColFlag = (packed & 1) == 1; // Bit 0
|
|
||||||
fr.delay = (in[i + 4] & 0xFF) | ((in[i + 5] & 0xFF) << 8); // 16 bit LSB
|
|
||||||
fr.transpColIndex = in[i + 6] & 0xFF; // Byte 6
|
|
||||||
return i + 8; // Skipped byte 7 (blockTerminator), as it's always 0x00
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param in Raw data
|
|
||||||
* @param img The GifImage object that is currently read
|
|
||||||
* @return Index of the first byte after this block
|
|
||||||
* @throws IOException If the GIF header/trailer is missing, incomplete or
|
|
||||||
* unknown
|
|
||||||
*/
|
|
||||||
static int readHeader(final byte[] in, final GifImage img) throws IOException {
|
|
||||||
if (in.length < 6) { // Check first 6 bytes
|
|
||||||
throw new IOException("Image is truncated.");
|
|
||||||
}
|
|
||||||
// Use ASCII encoding for header - faster than default charset
|
|
||||||
img.header = new String(in, 0, 6);
|
|
||||||
if (!img.header.equals("GIF87a") && !img.header.equals("GIF89a")) {
|
|
||||||
throw new IOException("Invalid GIF header.");
|
|
||||||
}
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fr The GIF frame to whom this image descriptor belongs
|
|
||||||
* @param in Raw data
|
|
||||||
* @param i Index of the first byte of this block, i.e. the minCodeSize
|
|
||||||
* @return Byte index
|
|
||||||
*/
|
|
||||||
static int readImgData(final GifFrame fr, final byte[] in, int i) {
|
|
||||||
final int fileSize = in.length;
|
|
||||||
final int minCodeSize = in[i++] & 0xFF; // Read code size, go to block
|
|
||||||
final int clearCode = 1 << minCodeSize; // CLEAR = 2^minCodeSize
|
|
||||||
fr.firstCodeSize = minCodeSize + 1; // Add 1 bit for CLEAR and EOI
|
|
||||||
fr.clearCode = clearCode;
|
|
||||||
fr.endOfInfoCode = clearCode + 1;
|
|
||||||
|
|
||||||
// Pre-calculate image data size for better memory allocation
|
|
||||||
final int imgDataSize = readImgDataSize(in, i);
|
|
||||||
final byte[] imgData = new byte[imgDataSize + 2];
|
|
||||||
int imgDataPos = 0;
|
|
||||||
int subBlockSize = in[i] & 0xFF;
|
|
||||||
|
|
||||||
while (subBlockSize > 0) { // While block has data
|
|
||||||
try { // Next line may throw exception if sub-block size is fake
|
|
||||||
final int nextSubBlockSizePos = i + subBlockSize + 1;
|
|
||||||
final int nextSubBlockSize = in[nextSubBlockSizePos] & 0xFF;
|
|
||||||
System.arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
|
||||||
imgDataPos += subBlockSize; // Move output data position
|
|
||||||
i = nextSubBlockSizePos; // Move to next sub-block size
|
|
||||||
subBlockSize = nextSubBlockSize;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
// Sub-block exceeds file end, only use remaining bytes
|
|
||||||
subBlockSize = fileSize - i - 1; // Remaining bytes
|
|
||||||
if (subBlockSize > 0) {
|
|
||||||
System.arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
|
|
||||||
imgDataPos += subBlockSize; // Move output data position
|
|
||||||
}
|
|
||||||
i += subBlockSize + 1; // Move to next sub-block size
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fr.data = imgData; // Holds LZW encoded data
|
|
||||||
i++; // Skip last sub-block size, should be 0
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int readImgDataSize(final byte[] in, int i) {
|
|
||||||
final int fileSize = in.length;
|
|
||||||
int imgDataPos = 0;
|
|
||||||
int subBlockSize = in[i] & 0xFF;
|
|
||||||
while (subBlockSize > 0) { // While block has data
|
|
||||||
try { // Next line may throw exception if sub-block size is fake
|
|
||||||
final int nextSubBlockSizePos = i + subBlockSize + 1;
|
|
||||||
final int nextSubBlockSize = in[nextSubBlockSizePos] & 0xFF;
|
|
||||||
imgDataPos += subBlockSize; // Move output data position
|
|
||||||
i = nextSubBlockSizePos; // Move to next sub-block size
|
|
||||||
subBlockSize = nextSubBlockSize;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
// Sub-block exceeds file end, only use remaining bytes
|
|
||||||
subBlockSize = fileSize - i - 1; // Remaining bytes
|
|
||||||
if (subBlockSize > 0) {
|
|
||||||
imgDataPos += subBlockSize; // Move output data position
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imgDataPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fr The GIF frame to whom this image descriptor belongs
|
|
||||||
* @param in Raw data
|
|
||||||
* @param i Index of the image separator, i.e. the first block byte
|
|
||||||
* @return Index of the first byte after this block
|
|
||||||
*/
|
|
||||||
static int readImgDescr(final GifFrame fr, final byte[] in, int i) {
|
|
||||||
fr.x = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 1-2: left
|
|
||||||
fr.y = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 3-4: top
|
|
||||||
fr.w = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 5-6: width
|
|
||||||
fr.h = (in[++i] & 0xFF) | ((in[++i] & 0xFF) << 8); // Byte 7-8: height
|
|
||||||
fr.wh = fr.w * fr.h;
|
|
||||||
final int packed = in[++i] & 0xFF; // Byte 9 is a packed byte
|
|
||||||
fr.hasLocColTbl = (packed & 0b10000000) != 0; // Bit 7
|
|
||||||
fr.interlaceFlag = (packed & 0b01000000) != 0; // Bit 6
|
|
||||||
fr.sortFlag = (packed & 0b00100000) != 0; // Bit 5
|
|
||||||
final int colTblSizePower = (packed & 7) + 1; // Bits 2-0
|
|
||||||
fr.sizeOfLocColTbl = 1 << colTblSizePower; // 2^(N+1), As per the spec
|
|
||||||
return ++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param img GIF image
|
|
||||||
* @param i Start index of this block.
|
|
||||||
* @return Index of the first byte after this block.
|
|
||||||
*/
|
|
||||||
static int readLogicalScreenDescriptor(final GifImage img, final byte[] in, final int i) {
|
|
||||||
img.w = (in[i] & 0xFF) | ((in[i + 1] & 0xFF) << 8); // 16 bit, LSB 1st
|
|
||||||
img.h = (in[i + 2] & 0xFF) | ((in[i + 3] & 0xFF) << 8); // 16 bit
|
|
||||||
img.wh = img.w * img.h;
|
|
||||||
final int packed = in[i + 4] & 0xFF; // Byte 4 is a packed byte
|
|
||||||
img.hasGlobColTbl = (packed & 0b10000000) != 0; // Bit 7
|
|
||||||
final int colResPower = ((packed & 0b01110000) >>> 4) + 1; // Bits 6-4
|
|
||||||
img.colorResolution = 1 << colResPower; // 2^(N+1), As per the spec
|
|
||||||
img.sortFlag = (packed & 0b00001000) != 0; // Bit 3
|
|
||||||
final int globColTblSizePower = (packed & 7) + 1; // Bits 0-2
|
|
||||||
img.sizeOfGlobColTbl = 1 << globColTblSizePower; // 2^(N+1), see spec
|
|
||||||
img.bgColIndex = in[i + 5] & 0xFF; // 1 Byte
|
|
||||||
img.pxAspectRatio = in[i + 6] & 0xFF; // 1 Byte
|
|
||||||
return i + 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param in Raw data
|
|
||||||
* @param pos Index of the extension introducer
|
|
||||||
* @return Index of the first byte after this block
|
|
||||||
*/
|
|
||||||
static int readTextExtension(final byte[] in, final int pos) {
|
|
||||||
int i = pos + 2; // Skip extension introducer and label
|
|
||||||
int subBlockSize = in[i++] & 0xFF;
|
|
||||||
while (subBlockSize != 0 && i < in.length) {
|
|
||||||
i += subBlockSize;
|
|
||||||
if (i >= in.length) break; // Safety check
|
|
||||||
subBlockSize = in[i++] & 0xFF;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.cloak.utils;
|
|
||||||
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a single frame of an animated cloak.
|
|
||||||
*/
|
|
||||||
public class AnimatedCloakData {
|
|
||||||
private final Identifier textureId;
|
|
||||||
private final int delayMs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new animated cloak frame.
|
|
||||||
*
|
|
||||||
* @param textureId The texture identifier for this frame
|
|
||||||
* @param delayMs The delay in milliseconds before the next frame
|
|
||||||
*/
|
|
||||||
public AnimatedCloakData(Identifier textureId, int delayMs) {
|
|
||||||
this.textureId = textureId;
|
|
||||||
this.delayMs = delayMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The texture identifier for this frame
|
|
||||||
*/
|
|
||||||
public Identifier getTextureId() {
|
|
||||||
return textureId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The delay in milliseconds before the next frame
|
|
||||||
*/
|
|
||||||
public int getDelayMs() {
|
|
||||||
return delayMs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.cloak.utils;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import net.minecraft.client.texture.NativeImage;
|
|
||||||
import net.minecraft.client.texture.NativeImageBackedTexture;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.DataBufferInt;
|
|
||||||
|
|
||||||
public class IdentifierUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ORIGINAL METHOD - EXTREMELY SLOW - causes 10-20 second freezes
|
|
||||||
* This converts BufferedImage -> PNG bytes -> ByteBuffer -> NativeImage ->
|
|
||||||
* Texture
|
|
||||||
* This is completely unnecessary and wastes massive amounts of CPU time!
|
|
||||||
*/
|
|
||||||
public static void registerBufferedImageTexture(Identifier i, BufferedImage bi) {
|
|
||||||
try {
|
|
||||||
// This is the performance killer:
|
|
||||||
// 1. Converts BufferedImage to PNG (expensive compression)
|
|
||||||
// 2. Creates ByteBuffer copy (memory allocation)
|
|
||||||
// 3. NativeImage.read() parses PNG back to raw pixels (expensive decompression)
|
|
||||||
// This is doing compression -> decompression for no reason!
|
|
||||||
|
|
||||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
||||||
javax.imageio.ImageIO.write(bi, "png", baos);
|
|
||||||
byte[] bytes = baos.toByteArray();
|
|
||||||
java.nio.ByteBuffer bb = org.lwjgl.BufferUtils.createByteBuffer(bytes.length).put(bytes);
|
|
||||||
bb.flip();
|
|
||||||
NativeImageBackedTexture nibt = new NativeImageBackedTexture(NativeImage.read(bb));
|
|
||||||
SaturnClient.client.getTextureManager().registerTexture(i, nibt);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OPTIMIZED METHOD - Direct pixel conversion without unnecessary PNG
|
|
||||||
* encoding/decoding
|
|
||||||
* This should reduce loading time from 10-20 seconds to under 1 second
|
|
||||||
*/
|
|
||||||
public static void registerBufferedImageTextureFast(Identifier identifier, BufferedImage bufferedImage) {
|
|
||||||
try {
|
|
||||||
int width = bufferedImage.getWidth();
|
|
||||||
int height = bufferedImage.getHeight();
|
|
||||||
|
|
||||||
// Create NativeImage directly with the correct format
|
|
||||||
NativeImage nativeImage = new NativeImage(NativeImage.Format.RGBA, width, height, false);
|
|
||||||
|
|
||||||
// Direct pixel copy - much faster than PNG conversion
|
|
||||||
if (bufferedImage.getType() == BufferedImage.TYPE_INT_ARGB) {
|
|
||||||
// Fast path for ARGB images - direct memory access
|
|
||||||
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int pixel = pixels[y * width + x];
|
|
||||||
nativeImage.setColorArgb(x, y, pixel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback for other image types - still faster than PNG conversion
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int rgb = bufferedImage.getRGB(x, y);
|
|
||||||
nativeImage.setColorArgb(x, y, rgb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the texture
|
|
||||||
NativeImageBackedTexture texture = new NativeImageBackedTexture(nativeImage);
|
|
||||||
SaturnClient.client.execute(
|
|
||||||
() -> SaturnClient.client.getTextureManager().registerTexture(identifier, texture));
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to register texture: " + identifier, e);
|
|
||||||
// Fallback to slow method if fast method fails
|
|
||||||
registerBufferedImageTexture(identifier, bufferedImage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EVEN FASTER VERSION - Bulk texture registration
|
|
||||||
* Use this when registering many textures at once (like animated cloak frames)
|
|
||||||
*/
|
|
||||||
public static void registerBufferedImageTexturesBulk(java.util.Map<Identifier, BufferedImage> textures) {
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
for (java.util.Map.Entry<Identifier, BufferedImage> entry : textures.entrySet()) {
|
|
||||||
registerBufferedImageTextureFast(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
long endTime = System.currentTimeMillis();
|
|
||||||
SaturnClient.LOGGER.info("Registered {} textures in {}ms", textures.size(), (endTime - startTime));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.cloak.utils;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for generating random strings and numbers.
|
|
||||||
*/
|
|
||||||
public final class RandomUtils {
|
|
||||||
private static final String ALPHANUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
||||||
private static final String ALPHANUMERIC_LOWERCASE = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
||||||
private static final Random RANDOM = new Random();
|
|
||||||
|
|
||||||
private RandomUtils() {
|
|
||||||
// Prevent instantiation
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random string of specified length using given characters.
|
|
||||||
*/
|
|
||||||
private static String random(final int length, final char[] chars) {
|
|
||||||
StringBuilder sb = new StringBuilder(length);
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sb.append(chars[RANDOM.nextInt(chars.length)]);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random string of specified length using alphanumeric characters.
|
|
||||||
*/
|
|
||||||
public static String randomString(final int length) {
|
|
||||||
return random(length, ALPHANUMERIC.toCharArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random lowercase string of specified length.
|
|
||||||
*/
|
|
||||||
public static String randomStringLowercase(final int length) {
|
|
||||||
return random(length, ALPHANUMERIC_LOWERCASE.toCharArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random integer between min and max (inclusive).
|
|
||||||
*/
|
|
||||||
public static int randBetween(int min, int max) {
|
|
||||||
return RANDOM.nextInt(max - min + 1) + min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.hat;
|
|
||||||
|
|
||||||
import org.saturnclient.cosmetics.obj.MtlLoader;
|
|
||||||
import org.saturnclient.cosmetics.obj.ObjModel;
|
|
||||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRenderer;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
|
||||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
|
|
||||||
public class HatFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
|
|
||||||
public HatFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
|
|
||||||
PlayerEntityRenderState state, float limbAngle, float limbDistance) {
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(state.name);
|
|
||||||
|
|
||||||
if (state.invisible || player == null || player.hat.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
matrices.push();
|
|
||||||
|
|
||||||
this.getContextModel().head.rotate(matrices);
|
|
||||||
|
|
||||||
ObjModel.cosmetic("hat", player.hat).render(MtlLoader.cosmetic(player.hat),
|
|
||||||
matrices, vertexConsumers, light,
|
|
||||||
OverlayTexture.DEFAULT_UV);
|
|
||||||
|
|
||||||
matrices.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.hat;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
|
||||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
|
||||||
|
|
||||||
public class Hats {
|
|
||||||
public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
|
|
||||||
public static List<String> availableHats = new ArrayList<>();
|
|
||||||
|
|
||||||
public static void initialize() {
|
|
||||||
availableHats.add(0, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setHat(String hatName) {
|
|
||||||
if (availableHats.contains(hatName)) {
|
|
||||||
setHat(ServiceClient.uuid, hatName);
|
|
||||||
ServiceClient.setHat(hatName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setHat(UUID uuid, String hatName) {
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(uuid);
|
|
||||||
if (player != null) {
|
|
||||||
player.hat = hatName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.obj;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import de.javagl.obj.*;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class MtlLoader {
|
|
||||||
static Map<Identifier, Map<String, Mtl>> loadedMtls = new HashMap<>();
|
|
||||||
|
|
||||||
static Map<String, Mtl> loadMtl(Identifier mtlId) throws IOException {
|
|
||||||
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
|
|
||||||
.getResource(mtlId).get().getInputStream()) {
|
|
||||||
List<Mtl> mtlList = MtlReader.read(is);
|
|
||||||
Map<String, Mtl> mtlMap = new java.util.LinkedHashMap<>();
|
|
||||||
for (Mtl mtl : mtlList) {
|
|
||||||
mtlMap.put(mtl.getName(), mtl);
|
|
||||||
}
|
|
||||||
return mtlMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Mtl> of(Identifier mtlId) {
|
|
||||||
System.out.println(mtlId);
|
|
||||||
if (loadedMtls.containsKey(mtlId))
|
|
||||||
return loadedMtls.get(mtlId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Map<String, Mtl> mtl = loadMtl(mtlId);
|
|
||||||
loadedMtls.put(mtlId, mtl);
|
|
||||||
|
|
||||||
return mtl;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Mtl> cosmetic(String cosmeticId) {
|
|
||||||
int index = cosmeticId.indexOf('_');
|
|
||||||
|
|
||||||
String mtlId = (index == -1) ? "" : cosmeticId.substring(index + 1);
|
|
||||||
|
|
||||||
if (mtlId.isEmpty())
|
|
||||||
mtlId = "white";
|
|
||||||
|
|
||||||
return of(Identifier.of("saturnclient", "models/cosmetic/materials/" + mtlId + ".mtl"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.obj;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import de.javagl.obj.Mtl;
|
|
||||||
import de.javagl.obj.Obj;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.resource.Resource;
|
|
||||||
import net.minecraft.resource.ResourceManager;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class ObjModel {
|
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
||||||
public static final Map<Identifier, ObjModel> loadedObjModels = new HashMap<>();
|
|
||||||
|
|
||||||
public record Config(
|
|
||||||
@JsonProperty("translate") Vec3d transform,
|
|
||||||
@JsonProperty("scale") Vec3d scale,
|
|
||||||
@JsonProperty("rotation") Vec3d rotation) {
|
|
||||||
|
|
||||||
public Config {
|
|
||||||
transform = transform == null ? new Vec3d(0, 0, 0) : transform;
|
|
||||||
scale = scale == null ? new Vec3d(1, 1, 1) : scale;
|
|
||||||
rotation = rotation == null ? new Vec3d(0, 0, 0) : rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
|
|
||||||
public record Vec3d(float x, float y, float z) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Obj obj;
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
public static ObjModel cosmetic(String cosmeticKind, String id) {
|
|
||||||
return of(Identifier.of("saturnclient", "models/cosmetic/" + cosmeticKind + "/" + id.split("_")[0] + "/model"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjModel of(Identifier obj) {
|
|
||||||
if (loadedObjModels.containsKey(obj))
|
|
||||||
return loadedObjModels.get(obj);
|
|
||||||
|
|
||||||
ObjModel o = new ObjModel(obj);
|
|
||||||
loadedObjModels.put(obj, o);
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjModel(Identifier identifier) {
|
|
||||||
Identifier objId = Identifier.of(identifier.getNamespace(), identifier.getPath() + ".obj");
|
|
||||||
Identifier jsonId = Identifier.of(identifier.getNamespace(), identifier.getPath() + ".json");
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.obj = ObjRenderer.loadObj(objId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try loading JSON config if it exists
|
|
||||||
ResourceManager resourceManager = MinecraftClient.getInstance().getResourceManager();
|
|
||||||
Optional<Resource> resource = resourceManager.getResource(jsonId);
|
|
||||||
|
|
||||||
if (resource.isPresent()) {
|
|
||||||
try (InputStream stream = resource.get().getInputStream()) {
|
|
||||||
this.config = MAPPER.readValue(stream, Config.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void render(Map<String, Mtl> mtlMap, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
|
|
||||||
int overlay) {
|
|
||||||
matrices.push();
|
|
||||||
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f));
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f));
|
|
||||||
|
|
||||||
if (this.config != null) {
|
|
||||||
matrices.translate(config.transform.x, config.transform.y, config.transform.z);
|
|
||||||
|
|
||||||
matrices.scale(config.scale.x, config.scale.y, config.scale.z);
|
|
||||||
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(config.rotation.x));
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(config.rotation.y));
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Z.rotationDegrees(config.rotation.z));
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjRenderer.renderObj(this.obj, mtlMap, matrices, vertexConsumers, light, overlay);
|
|
||||||
|
|
||||||
matrices.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
package org.saturnclient.cosmetics.obj;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import de.javagl.obj.*;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class ObjRenderer {
|
|
||||||
public static Obj loadObj(Identifier objId) throws IOException {
|
|
||||||
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
|
|
||||||
.getResource(objId).get().getInputStream()) {
|
|
||||||
return ObjReader.read(is);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void renderObj(Obj obj, Map<String, Mtl> mtlMap, MatrixStack matrices,
|
|
||||||
VertexConsumerProvider vertexConsumers, int light, int overlay) {
|
|
||||||
MatrixStack.Entry entry = matrices.peek();
|
|
||||||
|
|
||||||
Map<String, Obj> materialGroups = ObjSplitting.splitByMaterialGroups(obj);
|
|
||||||
|
|
||||||
for (Map.Entry<String, Obj> group : materialGroups.entrySet()) {
|
|
||||||
String materialName = group.getKey();
|
|
||||||
Obj groupObj = group.getValue();
|
|
||||||
|
|
||||||
Mtl mtl = mtlMap != null ? mtlMap.get(materialName) : null;
|
|
||||||
|
|
||||||
// Resolve texture identifier using same logic as ObjUnbakedModelModel
|
|
||||||
Identifier texture = Identifier.of("minecraft:textures/misc/white.png");
|
|
||||||
|
|
||||||
if (mtl != null) {
|
|
||||||
String mapKd = mtl.getMapKd();
|
|
||||||
if (mapKd != null) {
|
|
||||||
// "#key" references are not resolvable at runtime without a texture map,
|
|
||||||
// so we only support direct resource location strings here
|
|
||||||
if (!mapKd.startsWith("#")) {
|
|
||||||
texture = Identifier.of(mapKd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diffuse color (Kd)
|
|
||||||
float r = 1f, g = 1f, b = 1f;
|
|
||||||
if (mtl != null) {
|
|
||||||
FloatTuple kd = mtl.getKd();
|
|
||||||
if (kd != null) {
|
|
||||||
r = kd.getX();
|
|
||||||
g = kd.getY();
|
|
||||||
b = kd.getZ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VertexConsumer consumer = vertexConsumers.getBuffer(RenderLayer.getEntityAlpha(texture));
|
|
||||||
|
|
||||||
int faces = groupObj.getNumFaces();
|
|
||||||
final float fr = r, fg = g, fb = b;
|
|
||||||
|
|
||||||
for (int f = 0; f < faces; f++) {
|
|
||||||
ObjFace face = groupObj.getFace(f);
|
|
||||||
int verts = face.getNumVertices();
|
|
||||||
|
|
||||||
// Your original winding order logic, untouched
|
|
||||||
for (int i = verts - 1; i >= 0; i--) {
|
|
||||||
writeVertex(groupObj, face, i, consumer, entry, light, overlay, fr, fg, fb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writeVertex(Obj obj, ObjFace face, int index, VertexConsumer consumer,
|
|
||||||
MatrixStack.Entry matrix, int light, int overlay,
|
|
||||||
float r, float g, float b) {
|
|
||||||
|
|
||||||
FloatTuple pos = obj.getVertex(face.getVertexIndex(index));
|
|
||||||
float x = pos.getX();
|
|
||||||
float y = pos.getY();
|
|
||||||
float z = pos.getZ();
|
|
||||||
|
|
||||||
float nx = 0f, ny = 1f, nz = 0f;
|
|
||||||
int normalIndex = face.getNormalIndex(index);
|
|
||||||
if (normalIndex >= 0) {
|
|
||||||
FloatTuple n = obj.getNormal(normalIndex);
|
|
||||||
nx = n.getX();
|
|
||||||
ny = n.getY();
|
|
||||||
nz = n.getZ();
|
|
||||||
}
|
|
||||||
|
|
||||||
float u = 0f, v = 0f;
|
|
||||||
int texIndex = face.getTexCoordIndex(index);
|
|
||||||
if (texIndex >= 0) {
|
|
||||||
FloatTuple tc = obj.getTexCoord(texIndex);
|
|
||||||
u = tc.getX();
|
|
||||||
v = 1f - tc.getY(); // Flip V: OBJ bottom-left origin -> Minecraft top-left
|
|
||||||
}
|
|
||||||
|
|
||||||
consumer.vertex(matrix, x, y, z)
|
|
||||||
.color(r, g, b, 1f)
|
|
||||||
.texture(u, v)
|
|
||||||
.overlay(overlay)
|
|
||||||
.light(light)
|
|
||||||
.normal(matrix, nx, ny, nz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.saturnclient.modules;
|
|
||||||
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
public interface HudMod {
|
|
||||||
public ModDimensions getDimensions();
|
|
||||||
|
|
||||||
public void renderDummy(RenderScope scope);
|
|
||||||
|
|
||||||
public void renderHud(RenderScope scope);
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package org.saturnclient.modules;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property.NamedProperty;
|
|
||||||
|
|
||||||
public class ModDimensions {
|
|
||||||
public Property<Integer> x = Property.integer(0);
|
|
||||||
public Property<Integer> y = Property.integer(0);
|
|
||||||
public Property<Float> scale = Property.floatProp(1.0f);
|
|
||||||
public Property<Integer> bgColor = Property.color(0x00000000);
|
|
||||||
public Property<Integer> fgColor = Property.color(0xFFffffff);
|
|
||||||
public Property<Integer> radius = Property.integer(0);
|
|
||||||
public Property<Integer> font = Property.font(0);
|
|
||||||
public boolean renderBackground = true;
|
|
||||||
|
|
||||||
public int width = 0;
|
|
||||||
public int height = 0;
|
|
||||||
|
|
||||||
public NamedProperty<Map<String, Property<?>>> prop() {
|
|
||||||
return Property.namespace(Map.of(
|
|
||||||
"X", x,
|
|
||||||
"Y", y,
|
|
||||||
"Scale", scale,
|
|
||||||
"Background Color", bgColor,
|
|
||||||
"Foreground Color", fgColor,
|
|
||||||
"Corner Radius", radius,
|
|
||||||
"Font", font)).named("In-Game Display");
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModDimensions() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModDimensions(int width, int height) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModDimensions(int x, int y, int width, int height) {
|
|
||||||
this.x.value = x;
|
|
||||||
this.y.value = y;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModDimensions(int x, int y, int width, int height, float scale) {
|
|
||||||
this.x.value = x;
|
|
||||||
this.y.value = y;
|
|
||||||
this.scale.value = scale;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package org.saturnclient.modules;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.mods.*;
|
|
||||||
|
|
||||||
public class ModManager {
|
|
||||||
public static final List<Module> ENABLED_MODS = new ArrayList<>();
|
|
||||||
public static final Module[] ALL_MODS = {
|
|
||||||
new Crosshair(),
|
|
||||||
new AutoSprint(),
|
|
||||||
new ArmorDisplay(),
|
|
||||||
new Fps(),
|
|
||||||
new StatusEffects(),
|
|
||||||
new Coordinates(),
|
|
||||||
new Freelook(),
|
|
||||||
new Keystrokes(),
|
|
||||||
new Fullbright(),
|
|
||||||
new NoFog(),
|
|
||||||
new DayCounter(),
|
|
||||||
new Speedometer(),
|
|
||||||
new Zoom(),
|
|
||||||
new HealthDisplay(),
|
|
||||||
new Nametags(),
|
|
||||||
new Ping(),
|
|
||||||
new Tps(),
|
|
||||||
new Clock()
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
// Pre-filter enabled modules to avoid checking every tick
|
|
||||||
updateEnabledModules();
|
|
||||||
|
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
|
||||||
// Only iterate over enabled modules
|
|
||||||
for (Module m : ENABLED_MODS) {
|
|
||||||
m.tick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized void updateEnabledModules() {
|
|
||||||
ENABLED_MODS.clear();
|
|
||||||
for (Module m : ALL_MODS) {
|
|
||||||
if (m.isEnabled()) {
|
|
||||||
ENABLED_MODS.add(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Module[] getAllMods() {
|
|
||||||
return ALL_MODS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void refreshEnabledModules() {
|
|
||||||
updateEnabledModules();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package org.saturnclient.modules;
|
|
||||||
|
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property.NamedProperty;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public abstract class Module {
|
|
||||||
private final ModuleDetails details;
|
|
||||||
private final ConfigManager configManager;
|
|
||||||
|
|
||||||
public Module(ModuleDetails details, NamedProperty<?>... props) {
|
|
||||||
this.details = details;
|
|
||||||
configManager = new ConfigManager(details.name);
|
|
||||||
for (NamedProperty<?> prop : props) {
|
|
||||||
configManager.property(prop.name, prop.prop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void render(RenderScope scope) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tick() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean isEnabled();
|
|
||||||
|
|
||||||
public final void setEnabled(boolean e) {
|
|
||||||
onEnabled(e);
|
|
||||||
ModManager.updateEnabledModules();
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void onEnabled(boolean e);
|
|
||||||
|
|
||||||
public final String getName() {
|
|
||||||
return details.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Identifier getIconTexture() {
|
|
||||||
return Textures.getModIcon(details.namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigManager getConfig() {
|
|
||||||
return configManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return details.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getTags() {
|
|
||||||
return details.tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersion() {
|
|
||||||
return details.version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.saturnclient.modules;
|
|
||||||
|
|
||||||
public class ModuleDetails {
|
|
||||||
public String name;
|
|
||||||
public String namespace;
|
|
||||||
public String description;
|
|
||||||
public String version;
|
|
||||||
public String[] tags = {};
|
|
||||||
|
|
||||||
public ModuleDetails(String name, String namespace) {
|
|
||||||
this.name = name;
|
|
||||||
this.namespace = namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleDetails name(String name) {
|
|
||||||
this.name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleDetails namespace(String namespace) {
|
|
||||||
this.namespace = namespace;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleDetails description(String description) {
|
|
||||||
this.description = description;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleDetails version(String version) {
|
|
||||||
this.version = version;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleDetails tags(String... tags) {
|
|
||||||
this.tags = tags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Credits
|
|
||||||
### Freelook
|
|
||||||
- https://github.com/Celibistrial/freelook
|
|
||||||
- https://modrinth.com/mod/freelook
|
|
||||||
|
|
||||||
**The other mods are coded by us, if you have a concern or we missed something feel free to submit a issue with the credits tag**
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
import net.minecraft.entity.EquipmentSlot;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.item.Items;
|
|
||||||
|
|
||||||
public class ArmorDisplay extends Module implements HudMod {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static ModDimensions dimensions = new ModDimensions(40, 75);
|
|
||||||
private static Property<Boolean> useMainHand = Property.bool(false);
|
|
||||||
|
|
||||||
public ArmorDisplay() {
|
|
||||||
super(new ModuleDetails("Armor Display", "armor")
|
|
||||||
.description("Displays armor durability")
|
|
||||||
.version("v0.1.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
useMainHand.named("Use Main Hand"),
|
|
||||||
dimensions.prop());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderArmor(RenderScope scope, ItemStack mainHand, ItemStack helmet, ItemStack chestplate,
|
|
||||||
ItemStack leggings, ItemStack boots) {
|
|
||||||
int row = 0;
|
|
||||||
|
|
||||||
if (useMainHand.value && !mainHand.isEmpty()) {
|
|
||||||
scope.drawItem(mainHand, 0, 15 * row);
|
|
||||||
renderHealth(scope, row, mainHand.getMaxDamage(), mainHand.getDamage());
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!helmet.isEmpty()) {
|
|
||||||
scope.drawItem(helmet, 0, 15 * row);
|
|
||||||
renderHealth(scope, row, helmet.getMaxDamage(), helmet.getDamage());
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chestplate.isEmpty()) {
|
|
||||||
scope.drawItem(chestplate, 0, 15 * row);
|
|
||||||
renderHealth(scope, row, chestplate.getMaxDamage(), chestplate.getDamage());
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
if (!leggings.isEmpty()) {
|
|
||||||
scope.drawItem(leggings, 0, 15 * row);
|
|
||||||
renderHealth(scope, row, leggings.getMaxDamage(), leggings.getDamage());
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!boots.isEmpty()) {
|
|
||||||
scope.drawItem(boots, 0, 15 * row);
|
|
||||||
renderHealth(scope, row, boots.getMaxDamage(), boots.getDamage());
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderHealth(RenderScope scope, int i, int max_damage, int damage) {
|
|
||||||
if (max_damage > 0) {
|
|
||||||
scope.drawText(0.5f, "" + (max_damage - damage), 17, (15 * i) + 3, dimensions.font.value,
|
|
||||||
dimensions.fgColor.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
renderArmor(scope,
|
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.MAINHAND),
|
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.HEAD),
|
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.CHEST),
|
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.LEGS),
|
|
||||||
SaturnClient.client.player.getEquippedStack(EquipmentSlot.FEET));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderDummy(RenderScope scope) {
|
|
||||||
renderArmor(scope, new ItemStack(Items.DIAMOND_SWORD), new ItemStack(Items.DIAMOND_HELMET),
|
|
||||||
new ItemStack(Items.DIAMOND_CHESTPLATE),
|
|
||||||
new ItemStack(Items.DIAMOND_LEGGINGS), new ItemStack(Items.DIAMOND_BOOTS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ModDimensions getDimensions() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
|
|
||||||
public class AutoSprint extends Module {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
|
|
||||||
public AutoSprint() {
|
|
||||||
super(
|
|
||||||
new ModuleDetails("Auto Sprint", "sprint")
|
|
||||||
.description("Makes the player always sprint")
|
|
||||||
.tags("Movement")
|
|
||||||
.version("v0.1.0"),
|
|
||||||
enabled.named("Enabled"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick() {
|
|
||||||
if (SaturnClient.client.player == null || SaturnClient.client.getNetworkHandler() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SaturnClient.client.options.forwardKey.isPressed()
|
|
||||||
&& !SaturnClient.client.options.backKey.isPressed()
|
|
||||||
&& !SaturnClient.client.player.isSneaking()
|
|
||||||
&& !SaturnClient.client.player.horizontalCollision
|
|
||||||
&& !SaturnClient.client.player.isUsingItem()) {
|
|
||||||
|
|
||||||
Objects.requireNonNull(SaturnClient.client.player).setSprinting(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
|
|
||||||
public class Coordinates extends Module implements HudMod {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static Property<Integer> displayMethod = Property.select(0, "Flat", "Flat annotated", "Newline", "Newline annotated");
|
|
||||||
private static ModDimensions dimensions = new ModDimensions(40, 18);
|
|
||||||
|
|
||||||
public Coordinates() {
|
|
||||||
super(new ModuleDetails("Coordinates", "coords")
|
|
||||||
.description("Displays your current coordinates")
|
|
||||||
.version("v0.1.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
displayMethod.named("Display Method"),
|
|
||||||
dimensions.prop()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderDummy(RenderScope scope) {
|
|
||||||
renderCoords(532, 69, 253, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
Vec3d pos = SaturnClient.client.player.getPos();
|
|
||||||
renderCoords((int) pos.x, (int) pos.y, (int) pos.z, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderCoords(int x, int y, int z, RenderScope scope) {
|
|
||||||
String f = "%d %d %d"; // Flat
|
|
||||||
switch (displayMethod.value) {
|
|
||||||
case 1: // Flat annotated
|
|
||||||
f = "X: %d Y: %d Z: %d";
|
|
||||||
break;
|
|
||||||
case 2: // Newline
|
|
||||||
f = "%d\n%d\n%d";
|
|
||||||
break;
|
|
||||||
case 3: // Newline annotated
|
|
||||||
f = "X: %d\nY: %d\nZ: %d";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String text = String.format(f, x, y, z);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
|
|
||||||
public class Crosshair extends Module {
|
|
||||||
public static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
|
|
||||||
public Crosshair() {
|
|
||||||
super(
|
|
||||||
new ModuleDetails("Crosshair", "crosshair")
|
|
||||||
.description("Changes the crosshair if you're aiming at a entity")
|
|
||||||
.tags("Visuals", "Utility")
|
|
||||||
.version("v0.1.0"),
|
|
||||||
enabled.named("Enabled"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(RenderScope scope) {
|
|
||||||
if (enabled.value
|
|
||||||
&& SaturnClient.client.targetedEntity != null
|
|
||||||
&& SaturnClient.client.targetedEntity.isAlive()) {
|
|
||||||
int scaledWidth = 15;
|
|
||||||
int scaledHeight = 15;
|
|
||||||
|
|
||||||
scope.setRenderLayer(RenderLayer::getCrosshair);
|
|
||||||
scope.drawTexture(Textures.CROSSHAIR_RANGE,
|
|
||||||
(scope.getScaledWindowWidth() - scaledWidth) / 2,
|
|
||||||
(scope.getScaledWindowHeight() - scaledHeight) / 2, 0.0F, 0.0F, scaledWidth, scaledHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
|
|
||||||
public class DayCounter extends Module implements HudMod {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static ModDimensions dimensions = new ModDimensions(40, 18);
|
|
||||||
long day = 0;
|
|
||||||
|
|
||||||
public DayCounter() {
|
|
||||||
super(new ModuleDetails("DayCounter", "day")
|
|
||||||
.description("Displays the number of days that have passed")
|
|
||||||
.version("v0.1.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
dimensions.prop()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderDummy(RenderScope scope) {
|
|
||||||
renderDay(271, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
day = (SaturnClient.client.world.getTimeOfDay() / 24000L);
|
|
||||||
renderDay(day, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderDay(long day, RenderScope scope) {
|
|
||||||
String text = "Day: " + day;
|
|
||||||
scope.drawText(text,
|
|
||||||
0, 0, dimensions.font.value, dimensions.fgColor.value);
|
|
||||||
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
|
||||||
dimensions.height = 18 * text.split("\n").length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ModDimensions getDimensions() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
|
|
||||||
public class Fps extends Module implements HudMod {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static ModDimensions dimensions = new ModDimensions(60, Fonts.getHeight());
|
|
||||||
|
|
||||||
public Fps() {
|
|
||||||
super(new ModuleDetails("FPS Display", "fps")
|
|
||||||
.description("Displays current FPS")
|
|
||||||
.version("v0.1.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
dimensions.prop());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
String text = String.valueOf(SaturnClient.client.getCurrentFps()) + " FPS";
|
|
||||||
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 = "369 FPS";
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
|
|
||||||
import net.minecraft.client.option.Perspective;
|
|
||||||
|
|
||||||
public class Freelook extends Module {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static Property<Boolean> toggle = Property.bool(true);
|
|
||||||
public static Property<Integer> freelookKey = Property.keybinding(GLFW.GLFW_KEY_H);
|
|
||||||
public static boolean isFreeLooking = false;
|
|
||||||
private static boolean was1stPerson = false;
|
|
||||||
|
|
||||||
public Freelook() {
|
|
||||||
super(
|
|
||||||
new ModuleDetails("Freelook", "freelook")
|
|
||||||
.description("Allows you to look around freely without moving your character")
|
|
||||||
.tags("Movement", "Camera")
|
|
||||||
.version("0.1.0"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
toggle.named("Toggle freelook"),
|
|
||||||
freelookKey.named("Freelook Keybinding")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick() {
|
|
||||||
if (toggle.value) {
|
|
||||||
if (freelookKey.wasKeyPressed()) {
|
|
||||||
if (isFreeLooking) {
|
|
||||||
stopFreelook();
|
|
||||||
} else {
|
|
||||||
startFreelook();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (freelookKey.isKeyPressed()) {
|
|
||||||
if (!isFreeLooking) {
|
|
||||||
startFreelook();
|
|
||||||
}
|
|
||||||
} else if (isFreeLooking) {
|
|
||||||
stopFreelook();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startFreelook() {
|
|
||||||
isFreeLooking = true;
|
|
||||||
was1stPerson = SaturnClient.client.options.getPerspective() == Perspective.FIRST_PERSON;
|
|
||||||
SaturnClient.client.options.setPerspective(Perspective.THIRD_PERSON_BACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopFreelook() {
|
|
||||||
if (was1stPerson) {
|
|
||||||
SaturnClient.client.options.setPerspective(Perspective.FIRST_PERSON);
|
|
||||||
}
|
|
||||||
isFreeLooking = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
|
|
||||||
public class Fullbright extends Module {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static Property<Integer> brightness = Property.integer(100);
|
|
||||||
|
|
||||||
public Fullbright() {
|
|
||||||
super(
|
|
||||||
new ModuleDetails("Fullbright", "fullbright")
|
|
||||||
.description("Allows you to see in the dark")
|
|
||||||
.tags("Camera")
|
|
||||||
.version("v0.1.0"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
brightness.named("Brightness %"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean shouldOverrideBrightness() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float getBrightnessValue() {
|
|
||||||
return brightness.value / 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
|
|
||||||
public class HealthDisplay extends Module implements HudMod {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static Property<Integer> displayMode = Property.select(0, "Health", "Hearts");
|
|
||||||
private static Property<Integer> decimals = Property.select(0, "0", "1", "2");
|
|
||||||
private static ModDimensions dimensions = new ModDimensions(40, 18);
|
|
||||||
double speed = 0.;
|
|
||||||
Vec3d velocity;
|
|
||||||
double y = 0.;
|
|
||||||
|
|
||||||
public HealthDisplay() {
|
|
||||||
super(new ModuleDetails("Health display", "health")
|
|
||||||
.description("Displays your current health")
|
|
||||||
.version("v0.1.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
displayMode.named("Display mode"),
|
|
||||||
decimals.named("Decimals"),
|
|
||||||
dimensions.prop()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderDummy(RenderScope scope) {
|
|
||||||
renderHealth(10, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
float health = SaturnClient.client.player.getHealth();
|
|
||||||
switch (displayMode.value) {
|
|
||||||
case 1:
|
|
||||||
health = health / 2;
|
|
||||||
}
|
|
||||||
renderHealth(health, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderHealth(float health, RenderScope scope) {
|
|
||||||
String text = "";
|
|
||||||
|
|
||||||
switch (decimals.value) {
|
|
||||||
case 0:
|
|
||||||
text = String.format("%.0f ", health);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
text = String.format("%.1f ", health);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
text = String.format("%.2f ", health);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.drawText(text,
|
|
||||||
0, 0, dimensions.font.value, dimensions.fgColor.value);
|
|
||||||
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
|
||||||
dimensions.height = 18 * text.split("\n").length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ModDimensions getDimensions() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
|
|
||||||
public class Keystrokes extends Module implements HudMod {
|
|
||||||
|
|
||||||
private static final Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static final Property<Boolean> showMouse = Property.bool(true);
|
|
||||||
private static final Property<Boolean> showSpace = Property.bool(false);
|
|
||||||
|
|
||||||
private static final Property<Integer> clickBg = Property.color(0xFFCCCCCC);
|
|
||||||
private static final Property<Integer> clickFg = Property.color(0xFFFFFFFF);
|
|
||||||
|
|
||||||
private static final ModDimensions dimensions = new ModDimensions(78, 54);
|
|
||||||
|
|
||||||
// Key states
|
|
||||||
private boolean w, a, s, d, lmb, rmb, space;
|
|
||||||
|
|
||||||
// Layout constants
|
|
||||||
private static final int KEY_SIZE = 24;
|
|
||||||
private static final int KEY_SPACING = 3;
|
|
||||||
private static final int MOUSE_WIDTH = 38;
|
|
||||||
private static final int MOUSE_HEIGHT = 24;
|
|
||||||
private static final int SPACE_HEIGHT = 19;
|
|
||||||
|
|
||||||
public Keystrokes() {
|
|
||||||
super(new ModuleDetails("Keystrokes", "keystrokes")
|
|
||||||
.description("Displays movement keystrokes")
|
|
||||||
.version("v0.2.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
showMouse.named("Show mouse clicks"),
|
|
||||||
showSpace.named("Show space"),
|
|
||||||
dimensions.prop(),
|
|
||||||
clickFg.named("Clicked fg"),
|
|
||||||
clickBg.named("Clicked bg")
|
|
||||||
);
|
|
||||||
|
|
||||||
dimensions.renderBackground = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick() {
|
|
||||||
w = SaturnClient.client.options.forwardKey.isPressed();
|
|
||||||
a = SaturnClient.client.options.leftKey.isPressed();
|
|
||||||
s = SaturnClient.client.options.backKey.isPressed();
|
|
||||||
d = SaturnClient.client.options.rightKey.isPressed();
|
|
||||||
lmb = SaturnClient.client.options.attackKey.isPressed();
|
|
||||||
rmb = SaturnClient.client.options.useKey.isPressed();
|
|
||||||
space = SaturnClient.client.options.jumpKey.isPressed();
|
|
||||||
|
|
||||||
updateHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateHeight() {
|
|
||||||
int height = KEY_SIZE * 2 + KEY_SPACING;
|
|
||||||
|
|
||||||
if (showSpace.value)
|
|
||||||
height += SPACE_HEIGHT + KEY_SPACING;
|
|
||||||
|
|
||||||
if (showMouse.value)
|
|
||||||
height += MOUSE_HEIGHT + KEY_SPACING;
|
|
||||||
|
|
||||||
dimensions.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderDummy(RenderScope scope) {
|
|
||||||
render(scope, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
render(scope, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg);
|
|
||||||
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) {
|
|
||||||
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
|
|
||||||
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
|
|
||||||
|
|
||||||
scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_HEIGHT, dimensions.radius.value, bg);
|
|
||||||
scope.drawText(0.6f, text, x + 9, y + 7, dimensions.font.value, fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) {
|
|
||||||
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
|
|
||||||
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
|
|
||||||
|
|
||||||
scope.drawRoundedRectangle(x, y, dimensions.width, SPACE_HEIGHT, dimensions.radius.value, bg);
|
|
||||||
|
|
||||||
int lineY = y + SPACE_HEIGHT / 2;
|
|
||||||
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ModDimensions getDimensions() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
|
|
||||||
public class NoFog extends Module {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static Property<Boolean> liquids = Property.bool(false);
|
|
||||||
|
|
||||||
public NoFog() {
|
|
||||||
super(
|
|
||||||
new ModuleDetails("No Fog", "nofog")
|
|
||||||
.description("Stops fog from rendering")
|
|
||||||
.tags("Camera")
|
|
||||||
.version("v0.1.0"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
liquids.named("See through liquids"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static for the mixin
|
|
||||||
public static boolean isActive() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean liquids() {
|
|
||||||
return liquids.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
|
|
||||||
public class Speedometer extends Module implements HudMod {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static Property<Integer> axis = Property.select(1, "Absolute", "X axis", "Y axis");
|
|
||||||
private static Property<Integer> unitText = Property.select(0, "None", "Blocks/s", "blocks/s", "b/s");
|
|
||||||
private static Property<Boolean> speedText = Property.bool(true);
|
|
||||||
private static ModDimensions dimensions = new ModDimensions(40, 18);
|
|
||||||
double speed = 0.;
|
|
||||||
Vec3d velocity;
|
|
||||||
double y = 0.;
|
|
||||||
|
|
||||||
public Speedometer() {
|
|
||||||
super(new ModuleDetails("Speedometer", "speed")
|
|
||||||
.description("Displays your current speed")
|
|
||||||
.version("v0.1.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
axis.named("Speed type"),
|
|
||||||
unitText.named("Unit text"),
|
|
||||||
speedText.named("Show speed text"),
|
|
||||||
dimensions.prop()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderDummy(RenderScope scope) {
|
|
||||||
renderSpeed(2.71, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
Entity vehicle = SaturnClient.client.player.getVehicle();
|
|
||||||
Entity player = SaturnClient.client.player;
|
|
||||||
|
|
||||||
if (vehicle == null) {
|
|
||||||
velocity = player.getVelocity();
|
|
||||||
y = player.isOnGround() ? 0. : velocity.y;
|
|
||||||
} else {
|
|
||||||
velocity = vehicle.getVelocity();
|
|
||||||
y = vehicle.isOnGround() ? 0. : velocity.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (axis.value) {
|
|
||||||
case 0: // Absolute
|
|
||||||
speed = (Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(y, 2) + Math.pow(velocity.z, 2)));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1: // x axis
|
|
||||||
speed = (Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(velocity.z, 2)));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2: // y axis
|
|
||||||
speed = (Math.abs(y));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
renderSpeed(speed * 20, scope); // blocks/tick to blocks/s
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderSpeed(double speed, RenderScope scope) {
|
|
||||||
String f = String.format("%.2f ", speed);
|
|
||||||
|
|
||||||
switch (unitText.value) {
|
|
||||||
case 1:
|
|
||||||
f += "Blocks/s";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
f += "blocks/s";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
f += "b/s";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
String text = f;
|
|
||||||
|
|
||||||
if (speedText.value) {
|
|
||||||
text = "Speed: " + text;
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.drawText(text,
|
|
||||||
0, 0, dimensions.font.value, dimensions.fgColor.value);
|
|
||||||
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
|
|
||||||
dimensions.height = 18 * text.split("\n").length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ModDimensions getDimensions() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.client.texture.StatusEffectSpriteManager;
|
|
||||||
import net.minecraft.entity.effect.StatusEffect;
|
|
||||||
import net.minecraft.entity.effect.StatusEffectInstance;
|
|
||||||
import net.minecraft.registry.Registries;
|
|
||||||
import net.minecraft.registry.entry.RegistryEntry;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
|
|
||||||
public class StatusEffects extends Module implements HudMod {
|
|
||||||
public static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static ModDimensions dimensions = new ModDimensions(60, 0);
|
|
||||||
|
|
||||||
private final StatusEffectInstance[] dummyEffects = {
|
|
||||||
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("speed")).get(), 12000, 2),
|
|
||||||
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("strength")).get(), 12000, 2),
|
|
||||||
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("fire_resistance")).get(), 12000, 2),
|
|
||||||
};
|
|
||||||
|
|
||||||
public StatusEffects() {
|
|
||||||
super(new ModuleDetails("Status effects", "effect")
|
|
||||||
.description("Displays status effects")
|
|
||||||
.version("v0.1.0")
|
|
||||||
.tags("Utility"),
|
|
||||||
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
dimensions.prop());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderEffects(RenderScope scope, Iterable<StatusEffectInstance> effects) {
|
|
||||||
int row = 0;
|
|
||||||
|
|
||||||
StatusEffectSpriteManager statusEffectSpriteManager = SaturnClient.client.getStatusEffectSpriteManager();
|
|
||||||
|
|
||||||
for (StatusEffectInstance effect : effects) {
|
|
||||||
if (effect.shouldShowIcon()) {
|
|
||||||
RegistryEntry<StatusEffect> registryEntry = effect.getEffectType();
|
|
||||||
Sprite sprite = statusEffectSpriteManager.getSprite(registryEntry);
|
|
||||||
scope.drawSpriteStretched(sprite, 0, 18 * row, 16, 16);
|
|
||||||
scope.drawText(0.5f, getDurationAsString(effect), 18, 18 * row + 3, dimensions.font.value, -1);
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dimensions.height = 18 * row;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderHud(RenderScope scope) {
|
|
||||||
renderEffects(scope, SaturnClient.client.player.getStatusEffects());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderDummy(RenderScope scope) {
|
|
||||||
renderEffects(scope, Arrays.asList(dummyEffects));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code was from: https://github.com/magicus/statuseffecttimer/blob/master/src/main/java/se/icus/mag/statuseffecttimer/mixin/StatusEffectTimerMixin.java
|
|
||||||
private String getDurationAsString(StatusEffectInstance statusEffectInstance) {
|
|
||||||
if (statusEffectInstance.isInfinite()) {
|
|
||||||
return I18n.translate("effect.duration.infinite");
|
|
||||||
}
|
|
||||||
|
|
||||||
int ticks = MathHelper.floor((float) statusEffectInstance.getDuration());
|
|
||||||
int totalSeconds = ticks / 20;
|
|
||||||
|
|
||||||
if (totalSeconds >= 3600) {
|
|
||||||
return totalSeconds / 3600 + "h";
|
|
||||||
} else if (totalSeconds >= 60) {
|
|
||||||
int minutes = totalSeconds / 60;
|
|
||||||
int seconds = totalSeconds % 60;
|
|
||||||
return String.format("%d:%02d", minutes, seconds);
|
|
||||||
} else {
|
|
||||||
return String.valueOf(totalSeconds) + "s";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ModDimensions getDimensions() {
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.modules.ModuleDetails;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
|
|
||||||
public class Zoom extends Module {
|
|
||||||
private static Property<Boolean> enabled = Property.bool(false);
|
|
||||||
private static Property<Boolean> toggle = Property.bool(false);
|
|
||||||
public static Property<Integer> zoomKey = Property.keybinding(GLFW.GLFW_KEY_C);
|
|
||||||
public static Property<Float> zoomLevel = Property.floatProp(3.0f);
|
|
||||||
|
|
||||||
public static boolean isZooming = false;
|
|
||||||
|
|
||||||
public Zoom() {
|
|
||||||
super(
|
|
||||||
new ModuleDetails("Zoom", "zoom")
|
|
||||||
.description("Allows you to zoom in for a closer view")
|
|
||||||
.tags("Camera", "Utility")
|
|
||||||
.version("0.1.0"),
|
|
||||||
enabled.named("Enabled"),
|
|
||||||
toggle.named("Toggle zoom"),
|
|
||||||
zoomKey.named("Zoom Keybinding"),
|
|
||||||
zoomLevel.named("Zoom Level")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick() {
|
|
||||||
if (toggle.value) {
|
|
||||||
if (zoomKey.wasKeyPressed()) {
|
|
||||||
isZooming = !isZooming;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isZooming = zoomKey.isKeyPressed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float getZoomLevel() {
|
|
||||||
return zoomLevel.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean shouldZoom() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(boolean e) {
|
|
||||||
enabled.value = e;
|
|
||||||
if (!e) {
|
|
||||||
isZooming = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods.mixins;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.BackgroundRenderer;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.mods.NoFog;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
|
||||||
|
|
||||||
@Mixin(BackgroundRenderer.class)
|
|
||||||
public class BackgroundRendererMixin {
|
|
||||||
|
|
||||||
@ModifyVariable(method = "applyFog", at = @At("HEAD"), ordinal = 0, argsOnly = true)
|
|
||||||
private static float modifyFogStart(float viewDistance) {
|
|
||||||
if (NoFog.isActive()) {
|
|
||||||
return Float.MAX_VALUE; // this could be customised
|
|
||||||
}
|
|
||||||
return viewDistance;
|
|
||||||
}
|
|
||||||
// liquids
|
|
||||||
@Redirect(
|
|
||||||
method = "applyFog",
|
|
||||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isSpectator()Z")
|
|
||||||
)
|
|
||||||
private static boolean redirectIsSpectator(Entity entity) {
|
|
||||||
if (NoFog.isActive() && NoFog.liquids()) {
|
|
||||||
return true; // pretend to be spectator
|
|
||||||
}
|
|
||||||
return entity.isSpectator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods.mixins;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
|
||||||
import net.minecraft.client.render.Camera;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import net.minecraft.world.BlockView;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.mods.Freelook;
|
|
||||||
import org.saturnclient.modules.mods.utils.CameraOverriddenEntity;
|
|
||||||
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(Camera.class)
|
|
||||||
public abstract class CameraMixin {
|
|
||||||
@Unique
|
|
||||||
boolean firstTime = true;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
protected abstract void setRotation(float yaw, float pitch);
|
|
||||||
|
|
||||||
@Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
|
|
||||||
public void lockRotation(BlockView focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing, float tickDelta, CallbackInfo ci) {
|
|
||||||
if (Freelook.isFreeLooking && cameraEntity instanceof ClientPlayerEntity) {
|
|
||||||
CameraOverriddenEntity cameraOverriddenEntity = (CameraOverriddenEntity) cameraEntity;
|
|
||||||
|
|
||||||
if (firstTime && MinecraftClient.getInstance().player != null) {
|
|
||||||
cameraOverriddenEntity.freelook$setCameraPitch(MinecraftClient.getInstance().player.getPitch());
|
|
||||||
cameraOverriddenEntity.freelook$setCameraYaw(MinecraftClient.getInstance().player.getYaw());
|
|
||||||
firstTime = false;
|
|
||||||
}
|
|
||||||
this.setRotation(cameraOverriddenEntity.freelook$getCameraYaw(), cameraOverriddenEntity.freelook$getCameraPitch());
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!Freelook.isFreeLooking && cameraEntity instanceof ClientPlayerEntity) {
|
|
||||||
firstTime = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods.mixins;
|
|
||||||
|
|
||||||
|
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.mods.Freelook;
|
|
||||||
import org.saturnclient.modules.mods.utils.CameraOverriddenEntity;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
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(Entity.class)
|
|
||||||
public class EntityMixin implements CameraOverriddenEntity {
|
|
||||||
@Unique
|
|
||||||
private float cameraPitch;
|
|
||||||
|
|
||||||
@Unique
|
|
||||||
private float cameraYaw;
|
|
||||||
|
|
||||||
@Inject(method = "changeLookDirection", at = @At("HEAD"), cancellable = true)
|
|
||||||
public void changeCameraLookDirection(double xDelta, double yDelta, CallbackInfo ci) {
|
|
||||||
//noinspection ConstantValue// IntelliJ is incorrect here, this code block is reachable
|
|
||||||
if (Freelook.isFreeLooking && (Object) this instanceof ClientPlayerEntity) {
|
|
||||||
double pitchDelta = (yDelta * 0.15);
|
|
||||||
double yawDelta = (xDelta * 0.15);
|
|
||||||
|
|
||||||
this.cameraPitch = MathHelper.clamp(this.cameraPitch + (float) pitchDelta, -90.0f, 90.0f);
|
|
||||||
this.cameraYaw += (float) yawDelta;
|
|
||||||
|
|
||||||
ci.cancel();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public float freelook$getCameraPitch() {
|
|
||||||
return this.cameraPitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public float freelook$getCameraYaw() {
|
|
||||||
return this.cameraYaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public void freelook$setCameraPitch(float pitch) {
|
|
||||||
this.cameraPitch = pitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public void freelook$setCameraYaw(float yaw) {
|
|
||||||
this.cameraYaw = yaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods.mixins;
|
|
||||||
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|
||||||
import org.saturnclient.modules.mods.Zoom;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.GameRenderer;
|
|
||||||
|
|
||||||
@Mixin(GameRenderer.class)
|
|
||||||
public class GameRendererMixin {
|
|
||||||
|
|
||||||
@Inject(method = "getFov", at = @At("RETURN"), cancellable = true)
|
|
||||||
private void onGetFov(CallbackInfoReturnable<Float> cir) {
|
|
||||||
if (Zoom.isZooming && Zoom.shouldZoom()) {
|
|
||||||
float originalFov = cir.getReturnValue();
|
|
||||||
float zoomedFov = originalFov / Zoom.getZoomLevel();
|
|
||||||
cir.setReturnValue(zoomedFov);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// code modified from Gamma utils: https://github.com/Sjouwer/gamma-utils
|
|
||||||
package org.saturnclient.modules.mods.mixins;
|
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
|
||||||
import net.minecraft.client.render.LightmapTextureManager;
|
|
||||||
import org.saturnclient.modules.mods.Fullbright;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
|
|
||||||
@Mixin(LightmapTextureManager.class)
|
|
||||||
public class LightmapTextureManagerMixin {
|
|
||||||
|
|
||||||
@ModifyExpressionValue(
|
|
||||||
method = "update",
|
|
||||||
at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0)
|
|
||||||
)
|
|
||||||
private float modifyGamma(float original) {
|
|
||||||
if (Fullbright.shouldOverrideBrightness()) {
|
|
||||||
return Fullbright.getBrightnessValue();
|
|
||||||
}
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods.mixins;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.ModManager;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.menus.HudEditor;
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import net.minecraft.client.font.TextRenderer;
|
|
||||||
import net.minecraft.client.gui.DrawContext;
|
|
||||||
import net.minecraft.client.gui.hud.InGameHud;
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.client.render.RenderTickCounter;
|
|
||||||
|
|
||||||
@Mixin(InGameHud.class)
|
|
||||||
public class RenderMixin {
|
|
||||||
@Inject(method = "renderChat", at = @At("TAIL"))
|
|
||||||
public void renderMods(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
|
|
||||||
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
|
||||||
|
|
||||||
if (textRenderer != null &&
|
|
||||||
!(SaturnClient.client.currentScreen instanceof HudEditor)) {
|
|
||||||
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
|
||||||
((DrawContextAccessor) context).getVertexConsumers());
|
|
||||||
|
|
||||||
for (org.saturnclient.modules.Module m : ModManager.ENABLED_MODS) {
|
|
||||||
if (m instanceof HudMod && m.isEnabled()) {
|
|
||||||
ModDimensions dim = ((HudMod) m).getDimensions();
|
|
||||||
|
|
||||||
renderScope.matrices.push();
|
|
||||||
|
|
||||||
renderScope.matrices.translate(dim.x.value, (float) dim.y.value, 0);
|
|
||||||
|
|
||||||
renderScope.matrices.scale(dim.scale.value, dim.scale.value, 1.0f);
|
|
||||||
|
|
||||||
if (dim.renderBackground) {
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, dim.width, dim.height, dim.radius.value,
|
|
||||||
dim.bgColor.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
|
||||||
|
|
||||||
((HudMod) m).renderHud(renderScope);
|
|
||||||
|
|
||||||
renderScope.matrices.pop();
|
|
||||||
} else {
|
|
||||||
m.render(renderScope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.saturnclient.modules.mods.utils;
|
|
||||||
|
|
||||||
public interface CameraOverriddenEntity {
|
|
||||||
float freelook$getCameraPitch();
|
|
||||||
float freelook$getCameraYaw();
|
|
||||||
|
|
||||||
void freelook$setCameraPitch(float pitch);
|
|
||||||
void freelook$setCameraYaw(float yaw);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient;
|
|
||||||
|
|
||||||
import net.fabricmc.api.ModInitializer;
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
|
||||||
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
|
|
||||||
import org.saturnclient.cosmetics.Emotes;
|
|
||||||
import org.saturnclient.cosmetics.cloak.Cloaks;
|
|
||||||
import org.saturnclient.cosmetics.hat.Hats;
|
|
||||||
import org.saturnclient.modules.ModManager;
|
|
||||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
|
||||||
import org.saturnclient.saturnclient.config.Config;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
|
||||||
import org.saturnclient.saturnclient.event.KeyInputHandler;
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.SaturnScreen;
|
|
||||||
import org.saturnclient.ui2.elements.ImageTexture;
|
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
|
||||||
import org.saturnclient.ui2.screens.TitleMenu;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main class for the Saturn Client mod.
|
|
||||||
*/
|
|
||||||
public class SaturnClient implements ModInitializer {
|
|
||||||
|
|
||||||
public static final String MOD_ID = "saturnclient";
|
|
||||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
|
||||||
public static MinecraftClient client;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInitialize() {
|
|
||||||
LOGGER.info("Initializing " + MOD_ID);
|
|
||||||
client = MinecraftClient.getInstance();
|
|
||||||
Config.init();
|
|
||||||
ModManager.init();
|
|
||||||
|
|
||||||
client.execute(() -> {
|
|
||||||
SaturnScreen.preload(client);
|
|
||||||
});
|
|
||||||
|
|
||||||
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
|
|
||||||
|
|
||||||
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
|
|
||||||
if (screen != null && !(screen instanceof TitleMenu)) {
|
|
||||||
if (screen instanceof SaturnScreen) {
|
|
||||||
((SaturnScreen) screen).draw(new ImageTexture(Textures.LOGO_TEXT_BIG).dimensions(180, 18)
|
|
||||||
.position(screen.width - 180 - 20, screen.height - 18 - 20));
|
|
||||||
} else {
|
|
||||||
ScreenEvents.afterRender(screen).register((screen1, context, mouseX, mouseY, tickDelta) -> {
|
|
||||||
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
|
||||||
((DrawContextAccessor) context).getVertexConsumers());
|
|
||||||
|
|
||||||
int width = context.getScaledWindowWidth();
|
|
||||||
int height = context.getScaledWindowHeight();
|
|
||||||
|
|
||||||
float scaleX = (float) screen1.width / width;
|
|
||||||
float scaleY = (float) screen1.height / height;
|
|
||||||
|
|
||||||
MatrixStack matrixStack = context.getMatrices();
|
|
||||||
matrixStack.push();
|
|
||||||
matrixStack.translate(width - 95, height - 14, 0);
|
|
||||||
matrixStack.scale(0.5f / scaleX, 0.5f / scaleY, 1.0f);
|
|
||||||
renderScope.drawTexture(Textures.LOGO_TEXT_BIG, 0, 0, 0, 0, 180, 18);
|
|
||||||
matrixStack.pop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
KeyInputHandler.register();
|
|
||||||
Emotes.initialize();
|
|
||||||
if (ServiceClient.authenticate()) {
|
|
||||||
Cloaks.initialize();
|
|
||||||
Hats.initialize();
|
|
||||||
LOGGER.info(MOD_ID + " initialization complete");
|
|
||||||
} else {
|
|
||||||
LOGGER.error("Failed to authenticate with the server");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.client;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.saturnclient.cosmetics.cloak.Cloaks;
|
|
||||||
import org.saturnclient.cosmetics.hat.Hats;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
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,52 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.client.player;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
|
||||||
|
|
||||||
import net.minecraft.client.network.PlayerListEntry;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import com.mojang.authlib.GameProfile;
|
|
||||||
|
|
||||||
public class PlayerTracker {
|
|
||||||
private static Set<UUID> trackedPlayers = new HashSet<>();
|
|
||||||
|
|
||||||
public static void initialize() {
|
|
||||||
// When client joins a server, reset tracked players
|
|
||||||
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
|
|
||||||
trackedPlayers.clear();
|
|
||||||
client.execute(() -> {
|
|
||||||
checkForNewPlayers(SaturnClient.client);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for new players every tick
|
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> checkForNewPlayers(client));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkForNewPlayers(MinecraftClient client) {
|
|
||||||
if (client.getNetworkHandler() == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (PlayerListEntry player : client.getNetworkHandler().getPlayerList()) {
|
|
||||||
UUID playerUUID = player.getProfile().getId();
|
|
||||||
|
|
||||||
if (!trackedPlayers.contains(playerUUID)) {
|
|
||||||
// A new player has joined
|
|
||||||
onPlayerJoin(client, player);
|
|
||||||
trackedPlayers.add(playerUUID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
|
|
||||||
GameProfile profile = player.getProfile();
|
|
||||||
ServiceClient.player(profile.getId(), profile.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
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 ? "" : "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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, 700, 20);
|
|
||||||
|
|
||||||
// Shift menu
|
|
||||||
public static final AnimationConfig shiftMenu = new AnimationConfig(true, 700, 100);
|
|
||||||
|
|
||||||
// Cosmetics menu
|
|
||||||
public static final AnimationConfig cosmeticsMenu = new AnimationConfig(true, 700, 40);
|
|
||||||
|
|
||||||
// Emote wheel
|
|
||||||
public static final AnimationConfig emoteWheel = new AnimationConfig(true, 700, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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,91 +0,0 @@
|
|||||||
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,181 +0,0 @@
|
|||||||
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,298 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.config.manager;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
|
|
||||||
public class Property<T> {
|
|
||||||
public static class NamedProperty<T> {
|
|
||||||
public String name;
|
|
||||||
public Property<T> prop;
|
|
||||||
|
|
||||||
public NamedProperty(String name, Property<T> value) {
|
|
||||||
this.name = name;
|
|
||||||
this.prop = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PropertyType {
|
|
||||||
BOOLEAN,
|
|
||||||
INTEGER,
|
|
||||||
FLOAT,
|
|
||||||
STRING,
|
|
||||||
HEX,
|
|
||||||
NAMESPACE,
|
|
||||||
SELECT,
|
|
||||||
KEYBINDING,
|
|
||||||
}
|
|
||||||
|
|
||||||
public T value;
|
|
||||||
public boolean isReset = false;
|
|
||||||
private T defaultValue;
|
|
||||||
private String[] availableValues;
|
|
||||||
private final PropertyType type;
|
|
||||||
private boolean wasPressedLastTick = false;
|
|
||||||
|
|
||||||
private Property(T value, PropertyType type) {
|
|
||||||
this.value = value;
|
|
||||||
this.defaultValue = value;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- Factory Methods ----------
|
|
||||||
public static <T> Property<T> from(T value) {
|
|
||||||
if (value instanceof Boolean)
|
|
||||||
return new Property<>(value, PropertyType.BOOLEAN);
|
|
||||||
if (value instanceof Integer)
|
|
||||||
return new Property<>(value, PropertyType.INTEGER);
|
|
||||||
if (value instanceof Float)
|
|
||||||
return new Property<>(value, PropertyType.FLOAT);
|
|
||||||
if (value instanceof String)
|
|
||||||
return new Property<>(value, PropertyType.STRING);
|
|
||||||
if (valueIsNamespace(value))
|
|
||||||
return new Property<>(value, PropertyType.NAMESPACE);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> font(int value) {
|
|
||||||
return select(value, "Default", "Inter", "Inter bold");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> select(Integer value, String... availableValues) {
|
|
||||||
Property<Integer> property = new Property<>(value, PropertyType.SELECT);
|
|
||||||
property.availableValues = availableValues;
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Map<String, Property<?>>> namespace(Map<String, Property<?>> value) {
|
|
||||||
return new Property<>(value, PropertyType.NAMESPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Boolean> bool(boolean value) {
|
|
||||||
return new Property<>(value, PropertyType.BOOLEAN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> integer(int value) {
|
|
||||||
return new Property<>(value, PropertyType.INTEGER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Float> floatProp(float value) {
|
|
||||||
return new Property<>(value, PropertyType.FLOAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<String> string(String value) {
|
|
||||||
return new Property<>(value, PropertyType.STRING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> color(int value) {
|
|
||||||
return new Property<>(value, PropertyType.HEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property<Integer> keybinding(int value) {
|
|
||||||
return new Property<>(value, PropertyType.KEYBINDING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- Select Helpers ----------
|
|
||||||
public void next() {
|
|
||||||
if (type == PropertyType.SELECT) {
|
|
||||||
int i = (Integer) value;
|
|
||||||
setValue((i < availableValues.length - 1) ? i + 1 : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void prev() {
|
|
||||||
if (type == PropertyType.SELECT) {
|
|
||||||
int i = (Integer) value;
|
|
||||||
setValue((i > 0) ? i - 1 : availableValues.length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelection(int selection) {
|
|
||||||
if (type == PropertyType.SELECT && selection >= 0 && selection < availableValues.length) {
|
|
||||||
setValue(selection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSelection() {
|
|
||||||
return (type == PropertyType.SELECT) ? availableValues[(Integer) value] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- Lifecycle ----------
|
|
||||||
public void reset() {
|
|
||||||
value = defaultValue;
|
|
||||||
isReset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PropertyType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Map<String, Property<?>> getNamespaceValue() {
|
|
||||||
if (type == PropertyType.NAMESPACE && value instanceof Map) {
|
|
||||||
return (Map<String, Property<?>>) value;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Property is not a namespace");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- JSON Serialization ----------
|
|
||||||
/*
|
|
||||||
* BOOLEAN,
|
|
||||||
* INTEGER,
|
|
||||||
* FLOAT,
|
|
||||||
* STRING,
|
|
||||||
* HEX,
|
|
||||||
* NAMESPACE,
|
|
||||||
* SELECT,
|
|
||||||
* KEYBINDING,
|
|
||||||
*/
|
|
||||||
public JsonElement toJson() {
|
|
||||||
switch (type) {
|
|
||||||
case BOOLEAN:
|
|
||||||
return new JsonPrimitive((Boolean) value);
|
|
||||||
case INTEGER:
|
|
||||||
return new JsonPrimitive((Integer) value);
|
|
||||||
case FLOAT:
|
|
||||||
return new JsonPrimitive((Float) value);
|
|
||||||
case NAMESPACE:
|
|
||||||
JsonObject nested = new JsonObject();
|
|
||||||
getNamespaceValue().forEach((k, v) -> nested.add(k, v.toJson()));
|
|
||||||
return nested;
|
|
||||||
case HEX:
|
|
||||||
return new JsonPrimitive(String.format("#%08X", (Integer) value));
|
|
||||||
case KEYBINDING:
|
|
||||||
return new JsonPrimitive((Integer) value);
|
|
||||||
case SELECT:
|
|
||||||
return new JsonPrimitive((Integer) value);
|
|
||||||
default:
|
|
||||||
return new JsonPrimitive(String.valueOf(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadFromJson(JsonElement element) {
|
|
||||||
if (element == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case BOOLEAN:
|
|
||||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean())
|
|
||||||
setValue(element.getAsBoolean());
|
|
||||||
break;
|
|
||||||
case INTEGER:
|
|
||||||
if (element.isJsonPrimitive()) {
|
|
||||||
JsonPrimitive p = element.getAsJsonPrimitive();
|
|
||||||
if (p.isNumber())
|
|
||||||
setValue(p.getAsInt());
|
|
||||||
else if (p.isString())
|
|
||||||
setValue(parseHexToInt(p.getAsString()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FLOAT:
|
|
||||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber())
|
|
||||||
setValue(element.getAsFloat());
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString())
|
|
||||||
setValue(element.getAsString());
|
|
||||||
break;
|
|
||||||
case HEX:
|
|
||||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString())
|
|
||||||
setValue(parseHexToInt(element.getAsString()));
|
|
||||||
break;
|
|
||||||
case NAMESPACE:
|
|
||||||
if (element.isJsonObject()) {
|
|
||||||
JsonObject obj = element.getAsJsonObject();
|
|
||||||
getNamespaceValue().forEach((k, v) -> v.loadFromJson(obj.get(k)));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SELECT:
|
|
||||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber())
|
|
||||||
setSelection(element.getAsInt());
|
|
||||||
break;
|
|
||||||
case KEYBINDING:
|
|
||||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber())
|
|
||||||
setValue(element.getAsInt());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- Utility ----------
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.valueOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean valueIsNamespace(Object obj) {
|
|
||||||
return obj instanceof Map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void setValue(Object value) {
|
|
||||||
this.value = (T) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void setValue(Object value, Object defaultValue) {
|
|
||||||
this.value = (T) value;
|
|
||||||
this.defaultValue = (T) defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NamedProperty<T> named(String name) {
|
|
||||||
return new NamedProperty<>(name, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- Keybindings ----------
|
|
||||||
public boolean isKeyPressed() {
|
|
||||||
return (Integer) value != -1 &&
|
|
||||||
GLFW.glfwGetKey(SaturnClient.client.getWindow().getHandle(), (Integer) value) == GLFW.GLFW_PRESS &&
|
|
||||||
SaturnClient.client.currentScreen == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasKeyPressed() {
|
|
||||||
boolean pressed = isKeyPressed();
|
|
||||||
boolean result = pressed && !wasPressedLastTick;
|
|
||||||
wasPressedLastTick = pressed;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- HEX ----------
|
|
||||||
public static int parseHexToInt(String hex) {
|
|
||||||
hex = hex.replace("#", "");
|
|
||||||
if (hex.length() == 6)
|
|
||||||
hex = "FF" + hex;
|
|
||||||
if (hex.length() != 8)
|
|
||||||
throw new IllegalArgumentException("Hex must be 6 or 8 chars long, got \'" + hex + "\'");
|
|
||||||
return (int) Long.parseLong(hex, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
public Property<T> copy() {
|
|
||||||
return new Property<>(this.value, this.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load(String name, JsonElement element) {
|
|
||||||
if (element != null && element.isJsonObject()) {
|
|
||||||
load(name, element.getAsJsonObject());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load(String name, JsonObject element) {
|
|
||||||
JsonElement value = element.get(name);
|
|
||||||
if (value != null)
|
|
||||||
this.loadFromJson(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNamespace() {
|
|
||||||
return type == PropertyType.NAMESPACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Map<String, Property<?>> getNamespaceMap() {
|
|
||||||
return isNamespace() ? (Map<String, Property<?>>) (Object) value : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.event;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
|
||||||
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
|
|
||||||
import net.minecraft.client.option.KeyBinding;
|
|
||||||
import net.minecraft.client.util.InputUtil;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.client.ServiceClient;
|
|
||||||
import org.saturnclient.saturnclient.config.Config;
|
|
||||||
import org.saturnclient.ui2.screens.EmoteWheel;
|
|
||||||
import org.saturnclient.ui2.screens.ShiftMenu;
|
|
||||||
|
|
||||||
import dev.kosmx.playerAnim.api.layered.AnimationStack;
|
|
||||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles keyboard input events for the Saturn Client.
|
|
||||||
* Manages keybindings and their associated actions.
|
|
||||||
*/
|
|
||||||
public class KeyInputHandler {
|
|
||||||
private static final String KEY_CATEGORY = "key.category.saturnclient";
|
|
||||||
private static final String MAIN_MENU_KEY_ID = "key.saturnclient.main_menu";
|
|
||||||
private static final int DEFAULT_MENU_KEY = GLFW.GLFW_KEY_RIGHT_SHIFT;
|
|
||||||
private static KeyBinding mainMenuKeyBinding;
|
|
||||||
|
|
||||||
public static void register() {
|
|
||||||
mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
|
|
||||||
MAIN_MENU_KEY_ID,
|
|
||||||
InputUtil.Type.KEYSYM,
|
|
||||||
DEFAULT_MENU_KEY,
|
|
||||||
KEY_CATEGORY));
|
|
||||||
|
|
||||||
// Register the event handler for the main menu key
|
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
|
||||||
if (mainMenuKeyBinding.wasPressed()) {
|
|
||||||
client.setScreen(new ShiftMenu());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.openEmoteWheel.wasKeyPressed() && client.currentScreen == null && !InputUtil.isKeyPressed(client.getWindow().getHandle(), GLFW.GLFW_KEY_F3)) {
|
|
||||||
client.setScreen(new EmoteWheel());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SaturnClient.client.player != null && SaturnClient.client.player.isSneaking()) {
|
|
||||||
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player);
|
|
||||||
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
|
|
||||||
animationStack.removeLayer(1000);
|
|
||||||
ServiceClient.emote("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.menus;
|
|
||||||
|
|
||||||
import net.minecraft.client.gui.DrawContext;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.HudMod;
|
|
||||||
import org.saturnclient.modules.ModDimensions;
|
|
||||||
import org.saturnclient.modules.ModManager;
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.SaturnScreen;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class HudEditor extends Screen {
|
|
||||||
private final List<HudMod> hudMods = new ArrayList<>();
|
|
||||||
private HudMod draggingMod = null;
|
|
||||||
private boolean resizing = false;
|
|
||||||
private int offsetX, offsetY;
|
|
||||||
private static final int RESIZE_MARGIN = 3;
|
|
||||||
|
|
||||||
public HudEditor() {
|
|
||||||
super(Text.literal("HUD Editor"));
|
|
||||||
for (Module m : ModManager.ENABLED_MODS) {
|
|
||||||
if (m.isEnabled() && m instanceof HudMod) {
|
|
||||||
hudMods.add((HudMod) m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
if (button == 0) {
|
|
||||||
for (int m = hudMods.size() - 1; m >= 0; m--) {
|
|
||||||
HudMod mod = hudMods.get(m);
|
|
||||||
ModDimensions dim = mod.getDimensions();
|
|
||||||
|
|
||||||
// Check if clicking the corner for resizing
|
|
||||||
if (isCorner(mouseX, mouseY, dim)) {
|
|
||||||
draggingMod = mod;
|
|
||||||
resizing = true;
|
|
||||||
offsetX = (int) (mouseX - (dim.x.value + dim.width * dim.scale.value));
|
|
||||||
offsetY = (int) (mouseY - (dim.y.value + dim.height * dim.scale.value));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if clicking inside the mod
|
|
||||||
if (isInside(mouseX, mouseY, dim)) {
|
|
||||||
draggingMod = mod;
|
|
||||||
resizing = false;
|
|
||||||
offsetX = (int) (mouseX - dim.x.value);
|
|
||||||
offsetY = (int) (mouseY - dim.y.value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.mouseClicked(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
|
||||||
if (button == 0) {
|
|
||||||
draggingMod = null;
|
|
||||||
resizing = false;
|
|
||||||
}
|
|
||||||
return super.mouseReleased(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
|
||||||
if (draggingMod != null) {
|
|
||||||
ModDimensions dim = draggingMod.getDimensions();
|
|
||||||
|
|
||||||
if (resizing) {
|
|
||||||
// Scale based on cursor movement
|
|
||||||
float newScale = Math.max(0.5f, ((float) (mouseX - dim.x.value) / dim.width));
|
|
||||||
dim.scale.value = newScale;
|
|
||||||
} else {
|
|
||||||
// Move the mod
|
|
||||||
dim.x.value = (int) mouseX - offsetX;
|
|
||||||
dim.y.value = (int) mouseY - offsetY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
|
||||||
if (client.world == null && client.getCurrentServerEntry() == null) {
|
|
||||||
SaturnScreen.ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderScope scope = new RenderScope(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
|
|
||||||
|
|
||||||
for (HudMod mod : hudMods) {
|
|
||||||
ModDimensions dim = mod.getDimensions();
|
|
||||||
MatrixStack matrices = context.getMatrices();
|
|
||||||
matrices.push();
|
|
||||||
|
|
||||||
matrices.translate(dim.x.value, dim.y.value, 0);
|
|
||||||
matrices.scale(dim.scale.value, dim.scale.value, 1.0f);
|
|
||||||
|
|
||||||
// Render the mod
|
|
||||||
mod.renderDummy(scope);
|
|
||||||
|
|
||||||
matrices.pop();
|
|
||||||
|
|
||||||
// Draw border around mod
|
|
||||||
int borderX = dim.x.value;
|
|
||||||
int borderY = dim.y.value;
|
|
||||||
int borderW = (int) (dim.width * dim.scale.value);
|
|
||||||
int borderH = (int) (dim.height * dim.scale.value);
|
|
||||||
|
|
||||||
context.drawBorder(borderX, borderY, borderW, borderH, 0xFFFFFFFF); // White border
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInside(double mouseX, double mouseY, ModDimensions dim) {
|
|
||||||
return mouseX >= dim.x.value && mouseX <= dim.x.value + (dim.width * dim.scale.value) &&
|
|
||||||
mouseY >= dim.y.value && mouseY <= dim.y.value + (dim.height * dim.scale.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCorner(double mouseX, double mouseY, ModDimensions dim) {
|
|
||||||
float rightX = dim.x.value + dim.width * dim.scale.value;
|
|
||||||
float bottomY = dim.y.value + dim.height * dim.scale.value;
|
|
||||||
|
|
||||||
return Math.abs(mouseX - rightX) < RESIZE_MARGIN && Math.abs(mouseY - bottomY) < RESIZE_MARGIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
ConfigManager.save();
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
|
||||||
|
|
||||||
import net.minecraft.client.gui.DrawContext;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
|
||||||
|
|
||||||
@Mixin(DrawContext.class)
|
|
||||||
public interface DrawContextAccessor {
|
|
||||||
@Accessor("vertexConsumers")
|
|
||||||
VertexConsumerProvider.Immediate getVertexConsumers();
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import net.minecraft.client.font.TextRenderer;
|
|
||||||
import net.minecraft.client.font.TextRenderer.TextLayerType;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.render.entity.EntityRenderDispatcher;
|
|
||||||
import net.minecraft.client.render.entity.EntityRenderer;
|
|
||||||
import net.minecraft.client.render.entity.state.EntityRenderState;
|
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.text.MutableText;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.Formatting;
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.client.player.Roles;
|
|
||||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.joml.Matrix4f;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
|
|
||||||
@Mixin(value = EntityRenderer.class, priority = 1800)
|
|
||||||
public abstract class NameTagMixin<S extends EntityRenderState> {
|
|
||||||
@Shadow
|
|
||||||
public EntityRenderDispatcher dispatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author HexLeo
|
|
||||||
* @reason Adds the Saturn Client icon to the player's name if they are online
|
|
||||||
* with Saturn Client
|
|
||||||
*/
|
|
||||||
@Inject(method = "renderLabelIfPresent(Lnet/minecraft/client/render/entity/state/EntityRenderState;Lnet/minecraft/text/Text;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At("HEAD"), cancellable = true)
|
|
||||||
private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices,
|
|
||||||
VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
|
|
||||||
|
|
||||||
UUID uuid = isSaturn(state);
|
|
||||||
|
|
||||||
if (uuid != null) {
|
|
||||||
MutableText iconText = Text.literal(Roles.getSaturnIndicator())
|
|
||||||
.styled(style -> style.withColor(Roles.getIconColor(uuid)));
|
|
||||||
|
|
||||||
Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE));
|
|
||||||
|
|
||||||
renderLabel(state, iconText.append(nameText), matrices,
|
|
||||||
vertexConsumers, light);
|
|
||||||
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UUID isSaturn(S state) {
|
|
||||||
if (state instanceof PlayerEntityRenderState) {
|
|
||||||
String name = ((PlayerEntityRenderState) state).name;
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(name);
|
|
||||||
if (player != null) {
|
|
||||||
return player.uuid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderLabel(S state, Text text, MatrixStack matrices,
|
|
||||||
VertexConsumerProvider vertexConsumers, int light) {
|
|
||||||
Vec3d vec3d = state.nameLabelPos;
|
|
||||||
if (vec3d != null) {
|
|
||||||
boolean bl = !state.sneaking;
|
|
||||||
int i = "deadmau5".equals(text.getString()) ? -10 : 0;
|
|
||||||
matrices.push();
|
|
||||||
matrices.translate(vec3d.x, vec3d.y + 0.5, vec3d.z);
|
|
||||||
matrices.multiply(this.dispatcher.getRotation());
|
|
||||||
matrices.scale(0.025F, -0.025F, 0.025F);
|
|
||||||
Matrix4f matrix4f = matrices.peek().getPositionMatrix();
|
|
||||||
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
|
||||||
float f = (float) (-textRenderer.getWidth(text)) / 2.0F;
|
|
||||||
int j = (int) (SaturnClient.client.options.getTextBackgroundOpacity(0.25F) * 255.0F) << 24;
|
|
||||||
textRenderer.draw(text, f, (float) i, -2130706433, false, matrix4f, vertexConsumers,
|
|
||||||
bl ? TextLayerType.SEE_THROUGH : TextLayerType.NORMAL, j, light);
|
|
||||||
if (bl) {
|
|
||||||
textRenderer.draw(text, f, (float) i, -1, false, matrix4f, vertexConsumers, TextLayerType.NORMAL, 0,
|
|
||||||
light);
|
|
||||||
}
|
|
||||||
matrices.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.entity.EntityRendererFactory;
|
|
||||||
import net.minecraft.client.render.entity.LivingEntityRenderer;
|
|
||||||
import net.minecraft.client.render.entity.PlayerEntityRenderer;
|
|
||||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
|
||||||
|
|
||||||
import org.saturnclient.cosmetics.cloak.CloakFeatureRenderer;
|
|
||||||
import org.saturnclient.cosmetics.hat.HatFeatureRenderer;
|
|
||||||
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(PlayerEntityRenderer.class)
|
|
||||||
public abstract class PlayerEntityRendererMixin
|
|
||||||
extends LivingEntityRenderer<PlayerEntity, PlayerEntityRenderState, PlayerEntityModel> {
|
|
||||||
|
|
||||||
protected PlayerEntityRendererMixin() {
|
|
||||||
super(null, null, 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("TAIL"))
|
|
||||||
private void addCustomFeatureRenderer(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) {
|
|
||||||
this.addFeature(new HatFeatureRenderer(this));
|
|
||||||
this.addFeature(new CloakFeatureRenderer(this, ctx.getEquipmentModelLoader()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
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,43 +0,0 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
|
||||||
|
|
||||||
import net.minecraft.client.network.PlayerListEntry;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.Formatting;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.saturnclient.saturnclient.client.player.Roles;
|
|
||||||
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Overwrite;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
|
|
||||||
import com.mojang.authlib.GameProfile;
|
|
||||||
|
|
||||||
@Mixin(PlayerListEntry.class)
|
|
||||||
public abstract class TabListMixin {
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
private GameProfile profile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author HexLeo
|
|
||||||
* @reason Adds the Saturn Client icon to the player's name if they are online
|
|
||||||
* with Saturn Client
|
|
||||||
*/
|
|
||||||
@Overwrite
|
|
||||||
@Nullable
|
|
||||||
public Text getDisplayName() {
|
|
||||||
GameProfile profile = ((PlayerListEntry) (Object) this).getProfile();
|
|
||||||
String name = profile.getName();
|
|
||||||
UUID uuid = profile.getId();
|
|
||||||
|
|
||||||
if (SaturnPlayer.get(uuid) != null) {
|
|
||||||
return Text.literal(Roles.getSaturnIndicator())
|
|
||||||
.styled(style -> style.withColor(Roles.getIconColor(uuid)))
|
|
||||||
.append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)));
|
|
||||||
}
|
|
||||||
return Text.literal(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Original code from FokshaWasTaken (GitHub),
|
|
||||||
* Repository: https://github.com/FokshaWasTaken/who-am-i/
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.saturnclient.saturnclient.mixin;
|
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
|
||||||
import net.minecraft.client.render.entity.LivingEntityRenderer;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
|
|
||||||
@Mixin(LivingEntityRenderer.class)
|
|
||||||
public class WhoAmI {
|
|
||||||
@ModifyExpressionValue(method = "hasLabel(Lnet/minecraft/entity/LivingEntity;D)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;getCameraEntity()Lnet/minecraft/entity/Entity;"))
|
|
||||||
public Entity obfuscateCameraEntity(Entity original) {
|
|
||||||
// hasLabel checks if the rendered entity is equal to the camera entity (aka our
|
|
||||||
// player),
|
|
||||||
// so by setting the camera entity to null in this instance makes the check
|
|
||||||
// always succeed
|
|
||||||
// this also allows to keep the other checks in mojang's code, such as checking
|
|
||||||
// if the hud is enabled etc.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package org.saturnclient.ui2;
|
|
||||||
|
|
||||||
import org.saturnclient.ui2.anim.Animation;
|
|
||||||
|
|
||||||
public class Element {
|
|
||||||
public int x;
|
|
||||||
public int y;
|
|
||||||
public int width;
|
|
||||||
public int height;
|
|
||||||
public float opacity = 1.0f;
|
|
||||||
public boolean focused;
|
|
||||||
public Animation animation;
|
|
||||||
public float scale = 1.0f;
|
|
||||||
public Integer duration = null; // Element duration, will fade out if not null
|
|
||||||
|
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void click(int mouseX, int mouseY) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void keyPressed(int keyCode, int scanCode, int modifiers) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void charTyped(char typedChar) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void playAnimationFrame(long elapsed) {
|
|
||||||
if (this.animation != null && elapsed >= this.animation.delay) {
|
|
||||||
this.animation.tick(this.animation.curve
|
|
||||||
.apply(Math.min(1.0, (double) (elapsed - this.animation.delay) / this.animation.duration)), this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element dimensions(int width, int height) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element position(int x, int y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element centerHorizontal(int w, int h, int offsetX, int offsetY) {
|
|
||||||
this.x = (w - (int) (width * scale)) / 2 + offsetX;
|
|
||||||
this.y = offsetY;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element center(int w, int h) {
|
|
||||||
this.centerOffset(w, h, 0, 0);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element centerOffset(int w, int h, int offsetX, int offsetY) {
|
|
||||||
this.x = (w - (int) (width * scale)) / 2 + offsetX;
|
|
||||||
this.y = (h - (int) (height * scale)) / 2 + offsetY;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element animation(Animation animation) {
|
|
||||||
this.animation = animation;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element scale(float scale) {
|
|
||||||
this.scale = scale;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.saturnclient.ui2;
|
|
||||||
|
|
||||||
public class ElementContext {
|
|
||||||
public int mouseX, mouseY;
|
|
||||||
public int elementWidth, elementHeight;
|
|
||||||
public float elementScale;
|
|
||||||
public long elapsed;
|
|
||||||
|
|
||||||
public ElementContext(long elapsed, int mouseX, int mouseY, Element e) {
|
|
||||||
this.mouseX = mouseX - e.x;
|
|
||||||
this.mouseY = mouseY - e.y;
|
|
||||||
this.elementWidth = e.width;
|
|
||||||
this.elementHeight = e.height;
|
|
||||||
this.elementScale = e.scale;
|
|
||||||
this.elapsed = elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHovering() {
|
|
||||||
return Utils.isHovering(mouseX, mouseY, elementWidth, elementHeight, elementScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHovering(int x, int y, int width, int height) {
|
|
||||||
return Utils.isHovering(mouseX - x, mouseY - y, width, height, elementScale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
package org.saturnclient.ui2;
|
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.joml.Matrix4f;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
import org.saturnclient.ui2.resources.SvgTexture;
|
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.font.TextRenderer;
|
|
||||||
import net.minecraft.client.font.TextRenderer.TextLayerType;
|
|
||||||
import net.minecraft.client.gui.DrawContext;
|
|
||||||
import net.minecraft.client.gui.ScreenRect;
|
|
||||||
import net.minecraft.client.render.DiffuseLighting;
|
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.render.item.ItemRenderState;
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.client.util.Window;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.entity.LivingEntity;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.item.ModelTransformationMode;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import net.minecraft.util.crash.CrashException;
|
|
||||||
import net.minecraft.util.crash.CrashReport;
|
|
||||||
import net.minecraft.util.crash.CrashReportSection;
|
|
||||||
import net.minecraft.util.math.RotationAxis;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
public class RenderScope {
|
|
||||||
public MatrixStack matrices;
|
|
||||||
public VertexConsumerProvider.Immediate vertexConsumers;
|
|
||||||
// private Function<Identifier, RenderLayer> renderLayers;
|
|
||||||
private ScissorStack scissorStack = new ScissorStack();
|
|
||||||
private int opacity = 255 << 24;
|
|
||||||
private final ItemRenderState itemRenderState;
|
|
||||||
|
|
||||||
public RenderScope(DrawContext context) {
|
|
||||||
this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
|
|
||||||
}
|
|
||||||
|
|
||||||
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
|
|
||||||
this.matrices = matrices;
|
|
||||||
this.vertexConsumers = vertexConsumers;
|
|
||||||
this.scissorStack = new ScissorStack();
|
|
||||||
this.itemRenderState = new ItemRenderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOpacity(float alpha) {
|
|
||||||
this.opacity = (int) (alpha * 255) << 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getColor(int color) {
|
|
||||||
int originalAlpha = (color >>> 24) & 0xFF;
|
|
||||||
int newAlpha = (opacity >>> 24) & 0xFF;
|
|
||||||
int mixedAlpha = (originalAlpha * newAlpha) / 255;
|
|
||||||
return (mixedAlpha << 24) | (color & 0x00FFFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRenderLayer(Function<Identifier, RenderLayer> renderLayers) {
|
|
||||||
// this.renderLayers = renderLayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawRect(int x, int y, int width, int height, int color) {
|
|
||||||
if (color == 0)
|
|
||||||
return;
|
|
||||||
color = getColor(color);
|
|
||||||
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
|
||||||
|
|
||||||
int x1 = x;
|
|
||||||
int x2 = x + width;
|
|
||||||
int y1 = y;
|
|
||||||
int y2 = y + height;
|
|
||||||
|
|
||||||
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(RenderLayer.getGui());
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, 0).color(color);
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, 0).color(color);
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, 0).color(color);
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, 0).color(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawText(String text, int x, int y, int font, int color) {
|
|
||||||
drawText(1.0f, text, x, y, font, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawText(float scale, String text, int x, int y, int font, int color) {
|
|
||||||
if (color == 0)
|
|
||||||
return;
|
|
||||||
if (font == 0) {
|
|
||||||
scale *= 2;
|
|
||||||
}
|
|
||||||
int i = 0;
|
|
||||||
for (String line : text.split("\n")) {
|
|
||||||
color = getColor(color);
|
|
||||||
matrices.push();
|
|
||||||
matrices.translate(x, y + (i * Fonts.getHeight()), 0);
|
|
||||||
matrices.scale(scale, scale, 1.0f);
|
|
||||||
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
|
||||||
textRenderer.draw(Fonts.setFont(line, Fonts.getFont(font)), 0, font == 0 ? 1 : 7, color, false,
|
|
||||||
this.matrices.peek().getPositionMatrix(),
|
|
||||||
this.vertexConsumers, TextLayerType.NORMAL, 0, 15728880);
|
|
||||||
matrices.pop();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawRoundedCorner(int width, int height, int radius, int color) {
|
|
||||||
int w = width * 20;
|
|
||||||
int h = height * 20;
|
|
||||||
|
|
||||||
for (int y = 0; y < h; y++) {
|
|
||||||
int startX = 0;
|
|
||||||
|
|
||||||
if (y < radius) {
|
|
||||||
double dy = radius - y - 0.5;
|
|
||||||
double dx = Math.sqrt(Math.max(0, radius * radius - dy * dy));
|
|
||||||
startX = radius - (int) dx;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drawRect(startX, y, w - startX, 1, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawRoundedSide(int cornerWidth, int cornerHeight, int radius, int color) {
|
|
||||||
// Top
|
|
||||||
this.matrices.push();
|
|
||||||
|
|
||||||
this.matrices.scale(0.05f, 0.05f, 1.0f);
|
|
||||||
|
|
||||||
this.drawRoundedCorner(cornerWidth, cornerHeight, radius * 10, color);
|
|
||||||
|
|
||||||
this.matrices.pop();
|
|
||||||
|
|
||||||
// Bottom
|
|
||||||
this.matrices.push();
|
|
||||||
|
|
||||||
this.matrices.translate(cornerWidth, cornerHeight * 2, 0);
|
|
||||||
|
|
||||||
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(90));
|
|
||||||
|
|
||||||
this.matrices.translate(0, -cornerWidth, 0);
|
|
||||||
|
|
||||||
this.matrices.scale(0.05f, 0.05f, 1.0f);
|
|
||||||
|
|
||||||
this.drawRoundedCorner(cornerHeight, cornerWidth, radius * 10, color);
|
|
||||||
|
|
||||||
this.matrices.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawRoundedRectangle(int x, int y, int width, int height, int radius, int color) {
|
|
||||||
if (color == 0)
|
|
||||||
return;
|
|
||||||
radius = Math.min(radius, Math.min(width, height));
|
|
||||||
int cornerWidth = width / 2;
|
|
||||||
int cornerHeight = height / 2;
|
|
||||||
|
|
||||||
this.matrices.push();
|
|
||||||
|
|
||||||
this.matrices.translate(x, y, 0);
|
|
||||||
|
|
||||||
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
|
|
||||||
|
|
||||||
this.matrices.translate(cornerWidth * 2, cornerHeight * 2, 0);
|
|
||||||
|
|
||||||
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(180));
|
|
||||||
|
|
||||||
this.matrices.translate(0, 0, 0);
|
|
||||||
|
|
||||||
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
|
|
||||||
|
|
||||||
this.matrices.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getScaledWindowWidth() {
|
|
||||||
return SaturnClient.client.getWindow().getScaledWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getScaledWindowHeight() {
|
|
||||||
return SaturnClient.client.getWindow().getScaledHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int color) {
|
|
||||||
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height) {
|
|
||||||
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWith,
|
|
||||||
int regionHeight, int textureWidth, int textureHeight) {
|
|
||||||
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, regionHeight, textureWidth, textureHeight, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWidth,
|
|
||||||
int regionHeight, int textureWidth, int textureHeight, int color) {
|
|
||||||
this.drawTexturedQuad(sprite, x, x + width, y, y + height, (u + 0.0F) / (float) textureWidth,
|
|
||||||
(u + (float) regionWidth) / (float) textureWidth, (v + 0.0F) / (float) textureHeight,
|
|
||||||
(v + (float) regionHeight) / (float) textureHeight, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1,
|
|
||||||
float v2, int color) {
|
|
||||||
|
|
||||||
if (sprite.toString().endsWith(".svg")) {
|
|
||||||
sprite = SvgTexture.getSvg(MinecraftClient.getInstance(), sprite, (x2 - x1) * 2, (y2 - y1) * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color == 0)
|
|
||||||
return;
|
|
||||||
x1 *= 4;
|
|
||||||
x2 *= 4;
|
|
||||||
y1 *= 4;
|
|
||||||
y2 *= 4;
|
|
||||||
color = getColor(color);
|
|
||||||
|
|
||||||
matrices.push();
|
|
||||||
matrices.scale(0.25f, 0.25f, 1.0f);
|
|
||||||
RenderSystem.setShaderTexture(0, sprite);
|
|
||||||
RenderLayer renderLayer = (RenderLayer) RenderLayer.getGuiTextured(sprite);
|
|
||||||
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
|
|
||||||
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(renderLayer);
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, 0.0F).texture(u1, v1).color(color);
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, 0.0F).texture(u1, v2).color(color);
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, 0.0F).texture(u2, v2).color(color);
|
|
||||||
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, 0.0F).texture(u2, v1).color(color);
|
|
||||||
matrices.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableScissor(int x1, int y1, int x2, int y2) {
|
|
||||||
ScreenRect screenRect = (new ScreenRect(x1, y1, x2 - x1, y2 - y1))
|
|
||||||
.transform(this.matrices.peek().getPositionMatrix());
|
|
||||||
this.setScissor(this.scissorStack.push(screenRect));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disableScissor() {
|
|
||||||
this.setScissor(this.scissorStack.pop());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean scissorContains(int x, int y) {
|
|
||||||
return this.scissorStack.containsPoint(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setScissor(@Nullable ScreenRect rect) {
|
|
||||||
this.draw();
|
|
||||||
if (rect != null) {
|
|
||||||
Window window = SaturnClient.client.getWindow();
|
|
||||||
int i = window.getFramebufferHeight();
|
|
||||||
double d = window.getScaleFactor();
|
|
||||||
double e = (double) rect.getLeft() * d;
|
|
||||||
double f = (double) i - (double) rect.getBottom() * d;
|
|
||||||
double g = (double) rect.width() * d;
|
|
||||||
double h = (double) rect.height() * d;
|
|
||||||
RenderSystem.enableScissor((int) e, (int) f, Math.max(0, (int) g), Math.max(0, (int) h));
|
|
||||||
} else {
|
|
||||||
RenderSystem.disableScissor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void draw() {
|
|
||||||
this.vertexConsumers.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void draw(Consumer<VertexConsumerProvider> drawer) {
|
|
||||||
drawer.accept(this.vertexConsumers);
|
|
||||||
this.vertexConsumers.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ScissorStack {
|
|
||||||
private final Deque<ScreenRect> stack = new ArrayDeque<>();
|
|
||||||
|
|
||||||
public ScreenRect push(ScreenRect p_281812_) {
|
|
||||||
ScreenRect screenrectangle = this.stack.peekLast();
|
|
||||||
if (screenrectangle != null) {
|
|
||||||
ScreenRect screenrectangle1 = Objects.requireNonNullElse(p_281812_.intersection(screenrectangle),
|
|
||||||
ScreenRect.empty());
|
|
||||||
this.stack.addLast(screenrectangle1);
|
|
||||||
return screenrectangle1;
|
|
||||||
} else {
|
|
||||||
this.stack.addLast(p_281812_);
|
|
||||||
return p_281812_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public ScreenRect pop() {
|
|
||||||
if (this.stack.isEmpty()) {
|
|
||||||
throw new IllegalStateException("Scissor stack underflow");
|
|
||||||
} else {
|
|
||||||
this.stack.removeLast();
|
|
||||||
return this.stack.peekLast();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsPoint(int p_329411_, int p_333404_) {
|
|
||||||
return this.stack.isEmpty() ? true : this.stack.peek().contains(p_329411_, p_333404_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawItem(ItemStack item, int x, int y) {
|
|
||||||
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, item, x, y, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawItem(ItemStack stack, int x, int y, int seed) {
|
|
||||||
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, stack, x, y, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawItem(ItemStack stack, int x, int y, int seed, int z) {
|
|
||||||
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, stack, x, y, seed, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawItemWithoutEntity(ItemStack stack, int x, int y) {
|
|
||||||
this.drawItemWithoutEntity(stack, x, y, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawItemWithoutEntity(ItemStack stack, int x, int y, int seed) {
|
|
||||||
this.drawItem((LivingEntity) null, SaturnClient.client.world, stack, x, y, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawItem(LivingEntity entity, ItemStack stack, int x, int y, int seed) {
|
|
||||||
this.drawItem(entity, entity.getWorld(), stack, x, y, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y,
|
|
||||||
int seed) {
|
|
||||||
this.drawItem(entity, world, stack, x, y, seed, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y, int seed,
|
|
||||||
int z) {
|
|
||||||
if (!stack.isEmpty()) {
|
|
||||||
SaturnClient.client.getItemModelManager().update(this.itemRenderState, stack, ModelTransformationMode.GUI,
|
|
||||||
false, world, entity, seed);
|
|
||||||
this.matrices.push();
|
|
||||||
this.matrices.translate((float) (x + 8), (float) (y + 8),
|
|
||||||
(float) (150 + (this.itemRenderState.hasDepth() ? z : 0)));
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.matrices.scale(16.0F, -16.0F, 16.0F);
|
|
||||||
boolean bl = !this.itemRenderState.isSideLit();
|
|
||||||
if (bl) {
|
|
||||||
this.draw();
|
|
||||||
DiffuseLighting.disableGuiDepthLighting();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.itemRenderState.render(this.matrices, this.vertexConsumers, 15728880, OverlayTexture.DEFAULT_UV);
|
|
||||||
this.draw();
|
|
||||||
if (bl) {
|
|
||||||
DiffuseLighting.enableGuiDepthLighting();
|
|
||||||
}
|
|
||||||
} catch (Throwable var11) {
|
|
||||||
CrashReport crashReport = CrashReport.create(var11, "Rendering item");
|
|
||||||
CrashReportSection crashReportSection = crashReport.addElement("Item being rendered");
|
|
||||||
crashReportSection.add("Item Type", () -> {
|
|
||||||
return String.valueOf(stack.getItem());
|
|
||||||
});
|
|
||||||
crashReportSection.add("Item Components", () -> {
|
|
||||||
return String.valueOf(stack.getComponents());
|
|
||||||
});
|
|
||||||
crashReportSection.add("Item Foil", () -> {
|
|
||||||
return String.valueOf(stack.hasGlint());
|
|
||||||
});
|
|
||||||
throw new CrashException(crashReport);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.matrices.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawSpriteStretched(Sprite sprite, int x, int y, int width, int height) {
|
|
||||||
this.drawSpriteStretched(sprite, x, y, width, height, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawSpriteStretched(Sprite sprite, int x, int y, int width, int height, int color) {
|
|
||||||
if (color == 0)
|
|
||||||
return;
|
|
||||||
if (width != 0 && height != 0) {
|
|
||||||
this.drawTexturedQuad(sprite.getAtlasId(), x, x + width, y, y + height, sprite.getMinU(), sprite.getMaxU(),
|
|
||||||
sprite.getMinV(), sprite.getMaxV(), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,169 +0,0 @@
|
|||||||
package org.saturnclient.ui2;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
|
||||||
import org.saturnclient.ui2.components.ElementRenderer;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gl.PostEffectProcessor;
|
|
||||||
import net.minecraft.client.gui.CubeMapRenderer;
|
|
||||||
import net.minecraft.client.gui.DrawContext;
|
|
||||||
import net.minecraft.client.gui.RotatingCubeMapRenderer;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.render.DefaultFramebufferSet;
|
|
||||||
import net.minecraft.client.texture.ResourceTexture;
|
|
||||||
import net.minecraft.client.texture.TextureManager;
|
|
||||||
import net.minecraft.client.util.Pool;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public abstract class SaturnScreen extends Screen {
|
|
||||||
private final Pool pool = new Pool(3);
|
|
||||||
protected List<Element> elements = new ArrayList<>();
|
|
||||||
protected float backgroundOpacity = 1.0f;
|
|
||||||
protected Instant start = null;
|
|
||||||
public int backgroundBlur = 10;
|
|
||||||
|
|
||||||
protected static final Identifier PANORAMA = Identifier.of("saturnclient",
|
|
||||||
"textures/gui/title/background/panorama");
|
|
||||||
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(PANORAMA);
|
|
||||||
|
|
||||||
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
|
|
||||||
|
|
||||||
static {
|
|
||||||
ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void preload(MinecraftClient client) {
|
|
||||||
TextureManager textureManager = client.getTextureManager();
|
|
||||||
|
|
||||||
// Force upload each texture to GPU
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
String var10001 = PANORAMA.getPath();
|
|
||||||
Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png");
|
|
||||||
ResourceTexture resourceTexture = new ResourceTexture(tex);
|
|
||||||
textureManager.registerTexture(tex, resourceTexture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SaturnScreen(String title) {
|
|
||||||
super(Text.literal(title));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void draw(Element element) {
|
|
||||||
ElementRenderer.draw(elements, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void init() {
|
|
||||||
elements.clear();
|
|
||||||
width *= 2;
|
|
||||||
height *= 2;
|
|
||||||
ui(); // abstraction to render the saturn ui and also render extra stuff here
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void ui();
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
|
||||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
|
||||||
if (start == null) {
|
|
||||||
start = Instant.now();
|
|
||||||
}
|
|
||||||
long elapsed = Duration.between(start, Instant.now()).toMillis();
|
|
||||||
|
|
||||||
mouseX *= 2;
|
|
||||||
mouseY *= 2;
|
|
||||||
|
|
||||||
if (client.world == null && client.getCurrentServerEntry() == null) {
|
|
||||||
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, backgroundOpacity, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
|
|
||||||
Identifier.ofVanilla("blur"),
|
|
||||||
DefaultFramebufferSet.MAIN_ONLY);
|
|
||||||
|
|
||||||
if (postEffectProcessor != null) {
|
|
||||||
postEffectProcessor.setUniforms("Radius", backgroundBlur * Math.min((float) elapsed / 700, 1.0f));
|
|
||||||
postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are using a Abstracted RenderScope because older minecraft versions don't
|
|
||||||
// use DrawContext
|
|
||||||
RenderScope renderScope = new RenderScope(context.getMatrices(),
|
|
||||||
((DrawContextAccessor) context).getVertexConsumers());
|
|
||||||
|
|
||||||
renderScope.matrices.push();
|
|
||||||
|
|
||||||
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
|
|
||||||
|
|
||||||
ElementRenderer.render(new ArrayList<>(elements), elapsed, renderScope, mouseX, mouseY);
|
|
||||||
|
|
||||||
renderScope.matrices.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
mouseX *= 2;
|
|
||||||
mouseY *= 2;
|
|
||||||
|
|
||||||
ElementRenderer.mouseClicked(elements, mouseX, mouseY, button);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
|
||||||
mouseX *= 2;
|
|
||||||
mouseY *= 2;
|
|
||||||
|
|
||||||
ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY);
|
|
||||||
|
|
||||||
return super.mouseReleased(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
|
||||||
mouseX *= 2;
|
|
||||||
mouseY *= 2;
|
|
||||||
|
|
||||||
ElementRenderer.mouseReleased(elements, mouseX, mouseY, button);
|
|
||||||
|
|
||||||
return super.mouseReleased(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseScrolled(
|
|
||||||
double mouseX,
|
|
||||||
double mouseY,
|
|
||||||
double horizontalAmount,
|
|
||||||
double verticalAmount) {
|
|
||||||
mouseX *= 2;
|
|
||||||
mouseY *= 2;
|
|
||||||
|
|
||||||
ElementRenderer.mouseScrolled(elements, mouseX, mouseY, horizontalAmount, verticalAmount);
|
|
||||||
|
|
||||||
return super.mouseScrolled(
|
|
||||||
mouseX,
|
|
||||||
mouseY,
|
|
||||||
horizontalAmount,
|
|
||||||
verticalAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resize(MinecraftClient client, int width, int height) {
|
|
||||||
elements.clear();
|
|
||||||
super.resize(client, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
|
||||||
ElementRenderer.keyPressed(elements, keyCode, scanCode, modifiers);
|
|
||||||
|
|
||||||
return super.keyPressed(keyCode, scanCode, modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package org.saturnclient.ui2;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.ui2.elements.Notification;
|
|
||||||
import org.saturnclient.ui2.elements.Notification.NotificationKind;
|
|
||||||
|
|
||||||
public class Utils {
|
|
||||||
public static boolean isHovering(int mouseX, int mouseY, int elementWidth, int elementHeight, float elementScale) {
|
|
||||||
return mouseX >= 0 &&
|
|
||||||
mouseX <= (elementWidth * elementScale) &&
|
|
||||||
mouseY >= 0 &&
|
|
||||||
mouseY <= (elementHeight * elementScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static char getCharFromKey(int keyCode, int modifiers) {
|
|
||||||
boolean shift = (modifiers & GLFW.GLFW_MOD_SHIFT) != 0;
|
|
||||||
|
|
||||||
// A-Z
|
|
||||||
if (keyCode >= GLFW.GLFW_KEY_A && keyCode <= GLFW.GLFW_KEY_Z) {
|
|
||||||
return (char) (shift
|
|
||||||
? 'A' + (keyCode - GLFW.GLFW_KEY_A)
|
|
||||||
: 'a' + (keyCode - GLFW.GLFW_KEY_A));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0-9
|
|
||||||
if (keyCode >= GLFW.GLFW_KEY_0 && keyCode <= GLFW.GLFW_KEY_9) {
|
|
||||||
return shift
|
|
||||||
? ")!@#$%^&*(".charAt(keyCode - GLFW.GLFW_KEY_0)
|
|
||||||
: (char) ('0' + (keyCode - GLFW.GLFW_KEY_0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special characters
|
|
||||||
return switch (keyCode) {
|
|
||||||
case GLFW.GLFW_KEY_SPACE -> ' ';
|
|
||||||
case GLFW.GLFW_KEY_PERIOD -> shift ? '>' : '.';
|
|
||||||
case GLFW.GLFW_KEY_COMMA -> shift ? '<' : ',';
|
|
||||||
case GLFW.GLFW_KEY_MINUS -> shift ? '_' : '-';
|
|
||||||
case GLFW.GLFW_KEY_EQUAL -> shift ? '+' : '=';
|
|
||||||
case GLFW.GLFW_KEY_SEMICOLON -> shift ? ':' : ';';
|
|
||||||
case GLFW.GLFW_KEY_APOSTROPHE -> shift ? '"' : '\'';
|
|
||||||
case GLFW.GLFW_KEY_SLASH -> shift ? '?' : '/';
|
|
||||||
case GLFW.GLFW_KEY_BACKSLASH -> shift ? '|' : '\\';
|
|
||||||
case GLFW.GLFW_KEY_LEFT_BRACKET -> shift ? '{' : '[';
|
|
||||||
case GLFW.GLFW_KEY_RIGHT_BRACKET -> shift ? '}' : ']';
|
|
||||||
case GLFW.GLFW_KEY_GRAVE_ACCENT -> shift ? '~' : '`';
|
|
||||||
default -> '\0';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T getOrNull(List<T> list, int index) {
|
|
||||||
return (index >= 0 && index < list.size()) ? list.get(index) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void notify(NotificationKind kind, String title, String toast) {
|
|
||||||
if (SaturnClient.client.currentScreen instanceof SaturnScreen) {
|
|
||||||
((SaturnScreen) SaturnClient.client.currentScreen)
|
|
||||||
.draw(new Notification(SaturnClient.client.currentScreen.width,
|
|
||||||
SaturnClient.client.currentScreen.height, kind,
|
|
||||||
title, toast));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
|
|
||||||
public abstract class Animation {
|
|
||||||
public int duration;
|
|
||||||
public int delay = 0;
|
|
||||||
public Function<Double, Double> curve = Curve::easeInOutCubic;
|
|
||||||
|
|
||||||
Animation(AnimationConfig config) {
|
|
||||||
this.duration = config.duration.value;
|
|
||||||
switch (config.curve.value) {
|
|
||||||
case 0:
|
|
||||||
this.curve = Curve::easeInOutCubic;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
this.curve = Curve::easeOutCubic;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
this.curve = Curve::easeInOutBack;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Animation(int duration) {
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void tick(double progress, Element element);
|
|
||||||
|
|
||||||
public abstract void init(Element element);
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
|
||||||
|
|
||||||
public class Curve {
|
|
||||||
public static double easeOutCubic(double x) {
|
|
||||||
return 1 - Math.pow(1 - x, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double easeInOutCubic(double x) {
|
|
||||||
return (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double easeInOutBack(double x) {
|
|
||||||
double c1 = 2.5;
|
|
||||||
double c2 = c1 * 1.525;
|
|
||||||
|
|
||||||
if (x < 0.5) {
|
|
||||||
return (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2;
|
|
||||||
} else {
|
|
||||||
return (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (2 * x - 2) + c2) + 2) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
|
|
||||||
public class Fade extends Animation {
|
|
||||||
float targetOpacity;
|
|
||||||
|
|
||||||
public Fade(int duration) {
|
|
||||||
super(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Fade(AnimationConfig config) {
|
|
||||||
super(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(double progress, Element element) {
|
|
||||||
element.opacity = Math.max(0.1f, targetOpacity * (float) progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Element element) {
|
|
||||||
targetOpacity = element.opacity;
|
|
||||||
element.opacity = 0.1f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
|
|
||||||
public class FadeOut extends Animation {
|
|
||||||
float targetOpacity;
|
|
||||||
|
|
||||||
public FadeOut(int duration) {
|
|
||||||
super(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FadeOut(AnimationConfig config) {
|
|
||||||
super(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(double progress, Element element) {
|
|
||||||
element.opacity = targetOpacity - (float) progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Element element) {
|
|
||||||
targetOpacity = element.opacity;
|
|
||||||
// element.opacity = 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
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,39 +0,0 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
|
|
||||||
public class SlideX extends Animation {
|
|
||||||
int offset = 20;
|
|
||||||
double startX;
|
|
||||||
|
|
||||||
public SlideX(AnimationConfig config) {
|
|
||||||
super(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideX(AnimationConfig config, int offset) {
|
|
||||||
super(config);
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideX(int duration) {
|
|
||||||
super(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideX(int duration, int offset) {
|
|
||||||
super(duration);
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Element element) {
|
|
||||||
startX = element.x - offset;
|
|
||||||
element.x = (int) startX;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(double progress, Element element) {
|
|
||||||
double currentX = startX + offset * progress;
|
|
||||||
element.x = (int) currentX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.saturnclient.ui2.anim;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.AnimationConfig;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
|
|
||||||
public class SlideY extends Animation {
|
|
||||||
int offset = 20;
|
|
||||||
double startY;
|
|
||||||
|
|
||||||
public SlideY(AnimationConfig config) {
|
|
||||||
super(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideY(AnimationConfig config, int offset) {
|
|
||||||
super(config);
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideY(int duration) {
|
|
||||||
super(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlideY(int duration, int offset) {
|
|
||||||
super(duration);
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Element element) {
|
|
||||||
startY = element.y - offset;
|
|
||||||
element.y = (int) startY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(double progress, Element element) {
|
|
||||||
double currentY = startY + offset * progress;
|
|
||||||
element.y = (int) currentY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.Theme;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
import org.saturnclient.ui2.ElementContext;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class CosmeticPreview extends Element {
|
|
||||||
static int padding = 14;
|
|
||||||
|
|
||||||
Identifier sprite;
|
|
||||||
private Runnable onClick;
|
|
||||||
private boolean isSelected;
|
|
||||||
|
|
||||||
public CosmeticPreview(boolean isSelected, Identifier sprite, Runnable onClick) {
|
|
||||||
this.sprite = sprite;
|
|
||||||
this.onClick = onClick;
|
|
||||||
this.isSelected = isSelected;
|
|
||||||
|
|
||||||
this.width = 50;
|
|
||||||
this.height = 111;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
|
||||||
isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
|
||||||
|
|
||||||
int texWidth = width - padding;
|
|
||||||
int texHeight = height - padding;
|
|
||||||
|
|
||||||
renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
|
|
||||||
Theme.FOREGROUND.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void click(int mouseX, int mouseY) {
|
|
||||||
if (onClick != null) {
|
|
||||||
onClick.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
import org.saturnclient.ui2.ElementContext;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.Utils;
|
|
||||||
|
|
||||||
public class ElementRenderer {
|
|
||||||
public static void draw(List<Element> elements, Element element) {
|
|
||||||
synchronized (elements) {
|
|
||||||
elements.add(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.animation != null) {
|
|
||||||
element.animation.init(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void render(List<Element> elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) {
|
|
||||||
for (Element element : elements) {
|
|
||||||
element.playAnimationFrame(elapsed);
|
|
||||||
|
|
||||||
renderScope.matrices.push();
|
|
||||||
renderScope.setOpacity(element.opacity);
|
|
||||||
renderScope.matrices.translate(element.x, element.y, 0);
|
|
||||||
renderScope.matrices.scale(element.scale, element.scale, 1.0f);
|
|
||||||
element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, element));
|
|
||||||
renderScope.matrices.pop();
|
|
||||||
renderScope.setRenderLayer(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void mouseClicked(List<Element> elements, double mouseX, double mouseY, int button) {
|
|
||||||
if (button == 0) {
|
|
||||||
for (Element element : new ArrayList<>(elements)) {
|
|
||||||
element.focused = false;
|
|
||||||
|
|
||||||
int adjustedMouseX = (int) mouseX - element.x;
|
|
||||||
int adjustedMouseY = (int) mouseY - element.y;
|
|
||||||
|
|
||||||
element.mouseClicked(adjustedMouseX * element.scale, adjustedMouseY * element.scale, button);
|
|
||||||
|
|
||||||
if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width,
|
|
||||||
element.height, element.scale)) {
|
|
||||||
element.focused = true;
|
|
||||||
element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void mouseScrolled(
|
|
||||||
List<Element> elements,
|
|
||||||
double mouseX,
|
|
||||||
double mouseY,
|
|
||||||
double horizontalAmount,
|
|
||||||
double verticalAmount) {
|
|
||||||
for (Element element : new ArrayList<>(elements)) {
|
|
||||||
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height,
|
|
||||||
element.scale)) {
|
|
||||||
element.scroll(
|
|
||||||
(int) mouseX - element.x,
|
|
||||||
(int) mouseY - element.y,
|
|
||||||
horizontalAmount,
|
|
||||||
verticalAmount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void keyPressed(List<Element> elements, int keyCode, int scanCode, int modifiers) {
|
|
||||||
for (Element element : new ArrayList<>(elements)) {
|
|
||||||
if (element.focused && element.opacity > 0) {
|
|
||||||
element.keyPressed(keyCode, scanCode, modifiers);
|
|
||||||
|
|
||||||
char typedChar = Utils.getCharFromKey(keyCode, modifiers);
|
|
||||||
if (typedChar != '\0') {
|
|
||||||
element.charTyped(typedChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components;
|
|
||||||
|
|
||||||
import org.saturnclient.modules.Module;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.Theme;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
import org.saturnclient.ui2.ElementContext;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.Utils;
|
|
||||||
import org.saturnclient.ui2.screens.ConfigEditor;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
|
|
||||||
public class SaturnModule extends Element {
|
|
||||||
|
|
||||||
private final Module mod;
|
|
||||||
|
|
||||||
static int p = 10; // padding
|
|
||||||
static int s = 14; // settings icon size
|
|
||||||
static int expand = 5; // settings icon click area
|
|
||||||
|
|
||||||
public SaturnModule(Module mod) {
|
|
||||||
this.mod = mod;
|
|
||||||
this.width = 160;
|
|
||||||
this.height = 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
|
||||||
boolean hovered = ctx.isHovering();
|
|
||||||
boolean enabled = mod.isEnabled();
|
|
||||||
|
|
||||||
int iconX = width - s - p;
|
|
||||||
int iconY = height - s - p;
|
|
||||||
|
|
||||||
boolean settingsHover = ctx.isHovering(
|
|
||||||
getSettingsX(),
|
|
||||||
getSettingsY(),
|
|
||||||
getSettingsHitBox(),
|
|
||||||
getSettingsHitBox());
|
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
|
||||||
|
|
||||||
// Resolve theme colors
|
|
||||||
int bgColor = Theme.PRIMARY.value;
|
|
||||||
int fgColor = hovered && !settingsHover ? Theme.ACCENT.value : Theme.PRIMARY_FG.value;
|
|
||||||
|
|
||||||
int iconBg = enabled ? Theme.ACCENT.value : Theme.PRIMARY.value;
|
|
||||||
int iconFg = enabled ? Theme.ACCENT_FG.value : fgColor;
|
|
||||||
|
|
||||||
int settingsColor = settingsHover
|
|
||||||
? Theme.ACCENT.value
|
|
||||||
: Theme.withAlpha(160, fgColor);
|
|
||||||
|
|
||||||
int h = height - (p * 2);
|
|
||||||
|
|
||||||
// Main background
|
|
||||||
renderScope.drawRoundedRectangle(
|
|
||||||
0, 0,
|
|
||||||
width, height,
|
|
||||||
Theme.WIDGET_RADIUS.value,
|
|
||||||
bgColor);
|
|
||||||
|
|
||||||
// Icon background
|
|
||||||
renderScope.drawRoundedRectangle(
|
|
||||||
p, p,
|
|
||||||
h, h,
|
|
||||||
Theme.WIDGET_RADIUS.value,
|
|
||||||
iconBg);
|
|
||||||
|
|
||||||
// Icon
|
|
||||||
renderScope.drawTexture(
|
|
||||||
mod.getIconTexture(),
|
|
||||||
p + (p / 2),
|
|
||||||
p + (p / 2),
|
|
||||||
0, 0,
|
|
||||||
h - p,
|
|
||||||
h - p,
|
|
||||||
iconFg);
|
|
||||||
|
|
||||||
// Module name
|
|
||||||
renderScope.drawText(
|
|
||||||
0.6f,
|
|
||||||
mod.getName(),
|
|
||||||
p + h + 4,
|
|
||||||
p + 1,
|
|
||||||
Theme.FONT.value,
|
|
||||||
fgColor);
|
|
||||||
|
|
||||||
// Version (right aligned)
|
|
||||||
String version = mod.getVersion();
|
|
||||||
int versionWidth = (int) (Fonts.getWidth(version, Theme.FONT.value) * 0.35f);
|
|
||||||
|
|
||||||
renderScope.drawText(
|
|
||||||
0.35f,
|
|
||||||
version,
|
|
||||||
width - p - versionWidth,
|
|
||||||
p + 1,
|
|
||||||
Theme.FONT.value,
|
|
||||||
fgColor);
|
|
||||||
|
|
||||||
// Tags
|
|
||||||
int xt = p + h + 4;
|
|
||||||
for (String tag : mod.getTags()) {
|
|
||||||
int tagWidth = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f);
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(
|
|
||||||
xt,
|
|
||||||
p + (h / 2),
|
|
||||||
tagWidth + 6,
|
|
||||||
12,
|
|
||||||
5,
|
|
||||||
Theme.ACCENT.value);
|
|
||||||
|
|
||||||
renderScope.drawText(
|
|
||||||
0.4f,
|
|
||||||
tag,
|
|
||||||
xt + 2,
|
|
||||||
p + (h / 2) + 3,
|
|
||||||
Theme.FONT.value,
|
|
||||||
Theme.ACCENT_FG.value);
|
|
||||||
|
|
||||||
xt += tagWidth + 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings icon
|
|
||||||
renderScope.drawTexture(
|
|
||||||
Textures.SETTINGS,
|
|
||||||
iconX,
|
|
||||||
iconY,
|
|
||||||
0, 0,
|
|
||||||
s, s,
|
|
||||||
settingsColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void click(int mouseX, int mouseY) {
|
|
||||||
boolean settingsHover = Utils.isHovering(
|
|
||||||
mouseX - getSettingsX(),
|
|
||||||
mouseY - getSettingsY(),
|
|
||||||
getSettingsHitBox(),
|
|
||||||
getSettingsHitBox(),
|
|
||||||
1.0f);
|
|
||||||
|
|
||||||
if (settingsHover) {
|
|
||||||
SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig()));
|
|
||||||
} else {
|
|
||||||
mod.setEnabled(!mod.isEnabled());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSettingsX() {
|
|
||||||
return width - s - p - expand;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSettingsY() {
|
|
||||||
return height - s - p - expand;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSettingsHitBox() {
|
|
||||||
return s + expand * 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.saturnclient.config.Config;
|
|
||||||
import org.saturnclient.saturnclient.config.Theme;
|
|
||||||
import org.saturnclient.ui2.resources.Textures;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
import org.saturnclient.ui2.ElementContext;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
import org.saturnclient.ui2.Utils;
|
|
||||||
import org.saturnclient.ui2.screens.ConfigEditor;
|
|
||||||
import org.saturnclient.ui2.screens.ModMenu;
|
|
||||||
import org.saturnclient.ui2.screens.cosmetics.CloakMenu;
|
|
||||||
import org.saturnclient.ui2.screens.store.CloakStore;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class Sidebar extends Element {
|
|
||||||
public static class SidebarComponent {
|
|
||||||
Runnable onClick;
|
|
||||||
boolean end;
|
|
||||||
Identifier sprite;
|
|
||||||
|
|
||||||
public SidebarComponent(Identifier sprite, Runnable onClick, boolean end) {
|
|
||||||
this.onClick = onClick;
|
|
||||||
this.end = end;
|
|
||||||
this.sprite = sprite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Runnable onClose;
|
|
||||||
SidebarComponent[] components = {
|
|
||||||
new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> {
|
|
||||||
SaturnClient.client.setScreen(new ModMenu());
|
|
||||||
}, false),
|
|
||||||
new Sidebar.SidebarComponent(Textures.SETTINGS, () -> {
|
|
||||||
SaturnClient.client.setScreen(new ConfigEditor(Config.config));
|
|
||||||
}, false),
|
|
||||||
|
|
||||||
new Sidebar.SidebarComponent(Textures.SHIRT, () -> {
|
|
||||||
SaturnClient.client.setScreen(new CloakMenu());
|
|
||||||
}, false),
|
|
||||||
|
|
||||||
new Sidebar.SidebarComponent(Textures.CLOSE, () -> {
|
|
||||||
onClose.run();
|
|
||||||
}, true),
|
|
||||||
|
|
||||||
new Sidebar.SidebarComponent(Textures.STORE, () -> {
|
|
||||||
SaturnClient.client.setScreen(new CloakStore());
|
|
||||||
}, true),
|
|
||||||
};
|
|
||||||
int active = 0;
|
|
||||||
|
|
||||||
public Sidebar(int active, Runnable onClose) {
|
|
||||||
this.width = 30;
|
|
||||||
this.height = 350;
|
|
||||||
this.active = active;
|
|
||||||
this.onClose = onClose;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
|
||||||
int topRow = 0;
|
|
||||||
int bottomRow = 0;
|
|
||||||
|
|
||||||
int gap = 5; // 5px gap between icons
|
|
||||||
int padding = 10;
|
|
||||||
int sp = 30 - padding + gap;
|
|
||||||
int s = 30 - (padding * 2);
|
|
||||||
int s2 = s + padding;
|
|
||||||
|
|
||||||
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (SidebarComponent component : components) {
|
|
||||||
int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
|
|
||||||
boolean selected = ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i;
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding,
|
|
||||||
s + padding, Theme.WIDGET_RADIUS.value, Theme.getBg(selected));
|
|
||||||
|
|
||||||
renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.getFg(selected));
|
|
||||||
|
|
||||||
if (component.end) {
|
|
||||||
bottomRow += 1;
|
|
||||||
} else {
|
|
||||||
topRow += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void click(int mouseX, int mouseY) {
|
|
||||||
int topRow = 0;
|
|
||||||
int bottomRow = 0;
|
|
||||||
|
|
||||||
int gap = 5;
|
|
||||||
int padding = 10;
|
|
||||||
int sp = 30 - padding + gap;
|
|
||||||
int s = 30 - (padding * 2);
|
|
||||||
int s2 = s + padding;
|
|
||||||
|
|
||||||
for (SidebarComponent component : components) {
|
|
||||||
int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
|
|
||||||
boolean hovering = Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale);
|
|
||||||
|
|
||||||
if (hovering) {
|
|
||||||
component.onClick.run();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.end) {
|
|
||||||
bottomRow += 1;
|
|
||||||
} else {
|
|
||||||
topRow += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.joml.Quaternionf;
|
|
||||||
import org.joml.Vector3f;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
import org.saturnclient.ui2.ElementContext;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.DiffuseLighting;
|
|
||||||
import net.minecraft.client.render.entity.EntityRenderDispatcher;
|
|
||||||
import net.minecraft.entity.LivingEntity;
|
|
||||||
|
|
||||||
public class SkinPreview extends Element {
|
|
||||||
private boolean negativeAngle = false;
|
|
||||||
private float angle = 0.0f;
|
|
||||||
|
|
||||||
public SkinPreview(float angle, boolean negativeAngle) {
|
|
||||||
this.angle = angle;
|
|
||||||
this.negativeAngle = negativeAngle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float f, float mouseX,
|
|
||||||
float mouseY, LivingEntity entity, float angle, boolean negativeAngle) {
|
|
||||||
float g = (float) (x1 + x2) / 2.0F;
|
|
||||||
float h = (float) (y1 + y2) / 2.0F;
|
|
||||||
renderScope.enableScissor(0, 6, x2, y2);
|
|
||||||
float i = (float) Math.atan((double) ((g - mouseX) / 40.0F));
|
|
||||||
float j = (float) Math.atan((double) ((h - mouseY) / 40.0F));
|
|
||||||
Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F);
|
|
||||||
float k = entity.bodyYaw;
|
|
||||||
float l = entity.getYaw();
|
|
||||||
float m = entity.getPitch();
|
|
||||||
float n = entity.prevHeadYaw;
|
|
||||||
float o = entity.headYaw;
|
|
||||||
entity.setYaw(angle);
|
|
||||||
entity.bodyYaw = angle;
|
|
||||||
entity.setPitch(-(j * 20.0F));
|
|
||||||
entity.headYaw = negativeAngle ? (i * 40.0F) + 180f : -(i * 40.0F);
|
|
||||||
entity.prevHeadYaw = entity.getHeadYaw();
|
|
||||||
entity.setCustomNameVisible(false);
|
|
||||||
|
|
||||||
float p = entity.getScale();
|
|
||||||
Vector3f vector3f = new Vector3f(0.0F, entity.getHeight() / 2.0F + f * p, 0.0F);
|
|
||||||
float q = (float) size / p;
|
|
||||||
drawEntity(renderScope, g, h, q, vector3f, quaternionf, null, entity);
|
|
||||||
|
|
||||||
entity.setCustomNameVisible(true);
|
|
||||||
entity.bodyYaw = k;
|
|
||||||
entity.setYaw(l);
|
|
||||||
entity.setPitch(m);
|
|
||||||
entity.prevHeadYaw = n;
|
|
||||||
entity.headYaw = o;
|
|
||||||
renderScope.disableScissor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void drawEntity(RenderScope renderScope, float x, float y, float size, Vector3f vector3f,
|
|
||||||
Quaternionf quaternionf, @Nullable Quaternionf quaternionf2, LivingEntity entity) {
|
|
||||||
renderScope.matrices.push();
|
|
||||||
renderScope.matrices.translate((double) x, (double) y, 50.0);
|
|
||||||
renderScope.matrices.scale(size, size, -size);
|
|
||||||
renderScope.matrices.translate(vector3f.x, vector3f.y, vector3f.z);
|
|
||||||
renderScope.matrices.multiply(quaternionf);
|
|
||||||
renderScope.draw();
|
|
||||||
DiffuseLighting.method_34742();
|
|
||||||
EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher();
|
|
||||||
|
|
||||||
entityRenderDispatcher.setRenderShadows(false);
|
|
||||||
renderScope.draw((vertexConsumers) -> {
|
|
||||||
entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 1.0F, renderScope.matrices, vertexConsumers,
|
|
||||||
15728880);
|
|
||||||
});
|
|
||||||
renderScope.draw();
|
|
||||||
entityRenderDispatcher.setRenderShadows(true);
|
|
||||||
renderScope.matrices.pop();
|
|
||||||
DiffuseLighting.enableGuiDepthLighting();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
|
||||||
LivingEntity entity = SaturnClient.client.player;
|
|
||||||
if (entity != null) {
|
|
||||||
drawEntity(renderScope, 0, 0, 75, 78, 30, 0.0625F, ctx.mouseX / scale, ctx.mouseY / scale, entity, angle, negativeAngle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.manager.ConfigManager;
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.resources.Fonts;
|
|
||||||
|
|
||||||
public class FloatInput extends Input {
|
|
||||||
public Property<Float> prop;
|
|
||||||
|
|
||||||
public FloatInput(Property<Float> prop) {
|
|
||||||
this.width = 120;
|
|
||||||
this.height = Fonts.getHeight();
|
|
||||||
this.prop = prop;
|
|
||||||
this.text = String.valueOf(prop.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void charTyped(char chr) {
|
|
||||||
if ((Character.isDigit(chr) || chr == '.' || (chr == '-' && cursorPosition == 0)) && text.length() < 10) {
|
|
||||||
if (text.equals("0") && cursorPosition == 1) {
|
|
||||||
text = "" + chr;
|
|
||||||
} else {
|
|
||||||
text = text.substring(0, cursorPosition) +
|
|
||||||
chr +
|
|
||||||
text.substring(cursorPosition);
|
|
||||||
|
|
||||||
try {
|
|
||||||
prop.value = text.isEmpty() ? 0 : Float.parseFloat(text);
|
|
||||||
cursorPosition++;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void backspace() {
|
|
||||||
try {
|
|
||||||
if (text.length() == 0) {
|
|
||||||
text = "0";
|
|
||||||
cursorPosition = 1;
|
|
||||||
}
|
|
||||||
prop.value = Float.parseFloat(text);
|
|
||||||
ConfigManager.save();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkReset() {
|
|
||||||
if (prop.isReset) {
|
|
||||||
this.text = String.valueOf(prop.value);
|
|
||||||
this.cursorPosition = 0;
|
|
||||||
prop.isReset = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
package org.saturnclient.ui2.components.inputs;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
|
|
||||||
import org.saturnclient.saturnclient.config.manager.Property;
|
|
||||||
import org.saturnclient.ui2.Element;
|
|
||||||
import org.saturnclient.ui2.ElementContext;
|
|
||||||
import org.saturnclient.ui2.RenderScope;
|
|
||||||
|
|
||||||
public class HexInput extends Element {
|
|
||||||
public Property<Integer> prop;
|
|
||||||
|
|
||||||
// Picker state
|
|
||||||
private boolean open = false;
|
|
||||||
private float hue = 0f;
|
|
||||||
private float opacity = 1f;
|
|
||||||
private int selectedX = 0;
|
|
||||||
private int selectedY = 0;
|
|
||||||
|
|
||||||
private boolean draggingOpacity = false;
|
|
||||||
|
|
||||||
// Dimensions
|
|
||||||
private static final int PICKER_WIDTH = 100;
|
|
||||||
private static final int PICKER_HEIGHT = 100;
|
|
||||||
private static final int SLIDER_WIDTH = 100;
|
|
||||||
private static final int SLIDER_HEIGHT = 10;
|
|
||||||
private static final int HUE_SLIDER_HEIGHT = 10;
|
|
||||||
|
|
||||||
public HexInput(Property<Integer> prop) {
|
|
||||||
this.prop = prop;
|
|
||||||
this.width = 58;
|
|
||||||
this.height = 20;
|
|
||||||
syncFromColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void click(int mouseX, int mouseY) {
|
|
||||||
open = !open;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
if (open) {
|
|
||||||
handlePickerClick(mouseX - width - 8, mouseY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
|
||||||
if (open) {
|
|
||||||
handlePickerDrag(mouseX - width - 8, mouseY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
|
||||||
draggingOpacity = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user