Merge branch 'master' into master

This commit is contained in:
Rudi
2026-02-24 10:05:15 +01:00
committed by GitHub
104 changed files with 2169 additions and 1720 deletions
+135 -31
View File
@@ -1,44 +1,126 @@
/*
* ================================================================
* Saturn Client - Fabric Mod Build Configuration
* ================================================================
*
* This build file configures:
* - Fabric Loom (Minecraft mod development)
* - Dependency management
* - Resource processing
* - Java toolchain (Java 21)
* - Publishing setup
*
* All version values are defined in gradle.properties.
* ================================================================
*/
plugins { plugins {
// Fabric mod development plugin
id 'fabric-loom' version '1.9-SNAPSHOT' id 'fabric-loom' version '1.9-SNAPSHOT'
// Maven publishing support
id 'maven-publish' id 'maven-publish'
} }
/*
* ================================================================
* Project Metadata
* ================================================================
*/
version = project.mod_version version = project.mod_version
group = project.maven_group group = project.maven_group
repositories {} /*
* ================================================================
* Repositories
* ================================================================
* All dependency sources.
*/
repositories {
// Fabric
maven { url 'https://maven.fabricmc.net/' }
// JitPack (GitHub releases)
maven { url 'https://jitpack.io' }
// Parchment mappings (if needed later)
maven { url 'https://maven.parchmentmc.org' }
// Special Model Loader
maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" }
// Player Animation Library
maven { name = "KosmX Maven"; url = 'https://maven.kosmx.dev/' }
// Central repository
mavenCentral()
}
/*
* ================================================================
* Dependencies
* ================================================================
*/
dependencies { dependencies {
// ─────────────────────────────────────────────
// Minecraft Core
// ─────────────────────────────────────────────
minecraft "com.mojang:minecraft:${project.minecraft_version}" minecraft "com.mojang:minecraft:${project.minecraft_version}"
// Switch back to Yarn // Yarn mappings (named mappings for dev environment)
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
// Fabric Loader
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
// ─────────────────────────────────────────────
// Rendering / Model Libraries
// ─────────────────────────────────────────────
// Special Model Loader (included inside your mod jar)
modApi "dev.felnull:special-model-loader:1.4.0+pre.1" modApi "dev.felnull:special-model-loader:1.4.0+pre.1"
include "dev.felnull:special-model-loader:1.4.0+pre.1" include "dev.felnull:special-model-loader:1.4.0+pre.1"
include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") // SVG Rendering Library
//You might want bendy-lib. playerAnimator will wrap it. implementation "com.formdev:svgSalamander:1.1.4"
// include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}")
// ─────────────────────────────────────────────
// Animation
// ─────────────────────────────────────────────
// Player Animation Library (bundled into your mod)
include modImplementation(
"dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}"
)
// Optional: BendyLib (only if you directly use it)
// include modRuntimeOnly(
// "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}"
// )
// ─────────────────────────────────────────────
// External Libraries
// ─────────────────────────────────────────────
// Session Java (auth/session handling)
implementation "com.github.selimaj-dev:session-java:0.1.6"
} }
repositories { /*
maven { url "https://maven.parchmentmc.org" } * ================================================================
maven { * Resource Processing
name = "FelNull Maven" * ================================================================
url = "https://maven.felnull.dev" * Injects version values into fabric.mod.json.
} */
maven {
name "KosmX's maven"
url 'https://maven.kosmx.dev/'
}
}
processResources { processResources {
inputs.property "version", project.version inputs.property "version", project.version
inputs.property "minecraft_version", project.minecraft_version inputs.property "minecraft_version", project.minecraft_version
@@ -46,38 +128,58 @@ processResources {
filteringCharset "UTF-8" filteringCharset "UTF-8"
filesMatching("fabric.mod.json") { filesMatching("fabric.mod.json") {
expand "version": project.version, expand(
"version": project.version,
"minecraft_version": project.minecraft_version, "minecraft_version": project.minecraft_version,
"loader_version": project.loader_version "loader_version": project.loader_version
)
} }
} }
/*
* ================================================================
* Java Configuration
* ================================================================
* Targeting Java 21.
*/
def targetJavaVersion = 21 def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8" options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { options.release.set(targetJavaVersion)
it.options.release.set(targetJavaVersion)
}
} }
java { java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion) toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
archivesBaseName = project.archives_base_name archivesBaseName = project.archives_base_name
withSourcesJar() withSourcesJar()
} }
/*
* ================================================================
* Jar Packaging
* ================================================================
* Includes LICENSE and NOTICE inside the final jar.
*/
jar { jar {
from("LICENSE") { from("LICENSE") {
rename { "${it}_${project.archivesBaseName}"} rename { "${it}_${project.archivesBaseName}" }
} }
from("NOTICE") { from("NOTICE") {
rename { "${it}_${project.archivesBaseName}"} rename { "${it}_${project.archivesBaseName}" }
} }
} }
/*
* ================================================================
* Publishing
* ================================================================
* Configured but no remote repository defined yet.
*/
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { mavenJava(MavenPublication) {
@@ -85,5 +187,7 @@ publishing {
} }
} }
repositories {} repositories {
// Add publishing repository here when needed
}
} }
@@ -2,7 +2,7 @@ package org.saturnclient.modules;
import java.util.Map; import java.util.Map;
import org.saturnclient.saturnclient.config.NamedProperty; import org.saturnclient.saturnclient.config.Property.NamedProperty;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
public class ModDimensions { public class ModDimensions {
@@ -20,17 +20,17 @@ public class ModDimensions {
public NamedProperty<Map<String, Property<?>>> prop() { public NamedProperty<Map<String, Property<?>>> prop() {
return Property.namespace(Map.of( return Property.namespace(Map.of(
"X", x, "X", x,
"Y", y, "Y", y,
"Scale", scale, "Scale", scale,
"Background Color", bgColor, "Background Color", bgColor,
"Foreground Color", fgColor, "Foreground Color", fgColor,
"Corner Radius", radius, "Corner Radius", radius,
"Font", font "Font", font)).named("In-Game Display");
)).named("In-Game Display");
} }
public ModDimensions() {} public ModDimensions() {
}
public ModDimensions(int width, int height) { public ModDimensions(int width, int height) {
this.width = width; this.width = width;
@@ -1,9 +1,9 @@
package org.saturnclient.modules; package org.saturnclient.modules;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.NamedProperty;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.saturnclient.config.Property.NamedProperty;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@@ -19,8 +19,11 @@ public abstract class Module {
} }
} }
public void render(RenderScope scope) {} public void render(RenderScope scope) {
public void tick() {} }
public void tick() {
}
public abstract boolean isEnabled(); public abstract boolean isEnabled();
@@ -9,46 +9,45 @@ import org.saturnclient.ui2.RenderScope;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
public class Keystrokes extends Module implements HudMod { public class Keystrokes extends Module implements HudMod {
public static Property<Boolean> enabled = Property.bool(false);
public static Property<Boolean> showMouse = Property.bool(true);
public static Property<Boolean> showSpace = Property.bool(false);
public static ModDimensions dimensions = new ModDimensions(78, 46);
// Cache key states to avoid repeated calls private static final Property<Boolean> enabled = Property.bool(false);
private boolean w = false; private static final Property<Boolean> showMouse = Property.bool(true);
private boolean a = false; private static final Property<Boolean> showSpace = Property.bool(false);
private boolean s = false;
private boolean d = false;
private boolean lmb = false;
private boolean rmb = false;
private boolean space = false;
// Cache dimensions to avoid recalculation private static final Property<Integer> clickBg = Property.color(0xFFCCCCCC);
private int cachedHeight = 54; private static final Property<Integer> clickFg = Property.color(0xFFFFFFFF);
private boolean dimensionsDirty = true;
private static Property<Integer> clickBg = Property.color(0xFFCCCCCC); private static final ModDimensions dimensions = new ModDimensions(78, 54);
private static Property<Integer> clickFg = Property.color(0xFFFFFFFF);
// Key states
private boolean w, a, s, d, lmb, rmb, space;
// Layout constants
private static final int KEY_SIZE = 24;
private static final int KEY_SPACING = 3;
private static final int MOUSE_WIDTH = 38;
private static final int MOUSE_HEIGHT = 24;
private static final int SPACE_HEIGHT = 19;
public Keystrokes() { public Keystrokes() {
super(new ModuleDetails("Keystrokes", "keystrokes") super(new ModuleDetails("Keystrokes", "keystrokes")
.description("Displays the keystrokes for movement") .description("Displays movement keystrokes")
.version("v0.1.0") .version("v0.2.0")
.tags("Utility"), .tags("Utility"),
enabled.named("Enabled"), enabled.named("Enabled"),
showMouse.named("Show mouse clicks"), showMouse.named("Show mouse clicks"),
showSpace.named("Show space clicks"), showSpace.named("Show space"),
dimensions.prop(), dimensions.prop(),
clickFg.named("Clicked fg"), clickFg.named("Clicked fg"),
clickBg.named("Clicked bg")); clickBg.named("Clicked bg")
);
dimensions.renderBackground = false; dimensions.renderBackground = false;
} }
@Override @Override
public void tick() { public void tick() {
// Cache key states to reduce method calls
w = SaturnClient.client.options.forwardKey.isPressed(); w = SaturnClient.client.options.forwardKey.isPressed();
a = SaturnClient.client.options.leftKey.isPressed(); a = SaturnClient.client.options.leftKey.isPressed();
s = SaturnClient.client.options.backKey.isPressed(); s = SaturnClient.client.options.backKey.isPressed();
@@ -57,103 +56,84 @@ public class Keystrokes extends Module implements HudMod {
rmb = SaturnClient.client.options.useKey.isPressed(); rmb = SaturnClient.client.options.useKey.isPressed();
space = SaturnClient.client.options.jumpKey.isPressed(); space = SaturnClient.client.options.jumpKey.isPressed();
// Mark dimensions as dirty if mouse or space visibility changed updateHeight();
if (dimensionsDirty) {
updateDimensions();
dimensionsDirty = false;
}
} }
private void updateDimensions() { private void updateHeight() {
cachedHeight = 54; // base height with padding considered int height = KEY_SIZE * 2 + KEY_SPACING;
if (showMouse.value) { if (showSpace.value)
cachedHeight += 27; height += SPACE_HEIGHT + KEY_SPACING;
}
if (showSpace.value) { if (showMouse.value)
cachedHeight += 22; height += MOUSE_HEIGHT + KEY_SPACING;
}
dimensions.height = cachedHeight; dimensions.height = height;
} }
@Override @Override
public void renderDummy(RenderScope scope) { public void renderDummy(RenderScope scope) {
// Use cached dimensions render(scope, false);
dimensions.height = cachedHeight;
// WASD keys (3px padding between each key)
renderKey(scope, false, 'W', 27, 0); // Top center
renderKey(scope, false, 'A', 0, 27); // Bottom left
renderKey(scope, false, 'S', 27, 27); // Bottom center
renderKey(scope, false, 'D', 54, 27); // Bottom right
if (showMouse.value) {
renderKeyM(scope, false, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, false, "RMB", 40, 54); // With 3px between
}
if (showSpace.value) {
renderKeySpace(scope, false, 0, cachedHeight - (showSpace.value ? 22 : 0));
}
} }
@Override @Override
public void renderHud(RenderScope scope) { public void renderHud(RenderScope scope) {
// Use cached dimensions render(scope, true);
dimensions.height = cachedHeight; }
// WASD keys (3px padding between each key) private void render(RenderScope scope, boolean live) {
renderKey(scope, w, 'W', 27, 0); // Top center
renderKey(scope, a, 'A', 0, 27); // Bottom left
renderKey(scope, s, 'S', 27, 27); // Bottom center
renderKey(scope, d, 'D', 54, 27); // Bottom right
if (showMouse.value) { int yOffset = 0;
renderKeyM(scope, lmb, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, rmb, "RMB", 40, 54); // With 3px between
}
// --- W ---
renderKey(scope, live && w, 'W', KEY_SIZE + KEY_SPACING, yOffset);
// --- A S D ---
yOffset += KEY_SIZE + KEY_SPACING;
renderKey(scope, live && a, 'A', 0, yOffset);
renderKey(scope, live && s, 'S', KEY_SIZE + KEY_SPACING, yOffset);
renderKey(scope, live && d, 'D', (KEY_SIZE + KEY_SPACING) * 2, yOffset);
yOffset += KEY_SIZE + KEY_SPACING;
// --- SPACE ---
if (showSpace.value) { if (showSpace.value) {
renderKeySpace(scope, space, 0, cachedHeight - (showSpace.value ? 22 : 0)); renderKeySpace(scope, live && space, 0, yOffset);
yOffset += SPACE_HEIGHT + KEY_SPACING;
}
// --- MOUSE ---
if (showMouse.value) {
renderKeyMouse(scope, live && lmb, "LMB", 0, yOffset);
renderKeyMouse(scope, live && rmb, "RMB", MOUSE_WIDTH + KEY_SPACING, yOffset);
} }
} }
// Consistent 3px padding applied below: private void renderKey(RenderScope scope, boolean pressed, char c, int x, int y) {
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
private void renderKeySpace(RenderScope scope, boolean isPressed, int x, int y) { scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg);
int height = 19; scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg);
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, dimensions.width, height, dimensions.radius.value, bgColor);
// A 30-pixel line centered vertically with 3px top padding
int lineY = y + (height - 1) / 2;
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fgColor);
} }
private void renderKeyM(RenderScope scope, boolean isPressed, String c, int x, int y) { private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) {
int width = 38, height = 24; int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; int fg = pressed ? clickFg.value : dimensions.fgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, width, height, dimensions.radius.value, bgColor); scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_HEIGHT, dimensions.radius.value, bg);
scope.drawText(0.6f, text, x + 9, y + 7, dimensions.font.value, fg);
// Apply padding: 3px top and left -> add slight offset to x/y
scope.drawText(0.6f, c, x + 9, y + 7, dimensions.font.value, fgColor);
} }
private void renderKey(RenderScope scope, boolean isPressed, char c, int x, int y) { private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) {
int size = 24; int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; int fg = pressed ? clickFg.value : dimensions.fgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, size, size, dimensions.radius.value, bgColor); scope.drawRoundedRectangle(x, y, dimensions.width, SPACE_HEIGHT, dimensions.radius.value, bg);
// Center the character with 3px top padding int lineY = y + SPACE_HEIGHT / 2;
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fgColor); scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg);
} }
@Override @Override
@@ -169,6 +149,5 @@ public class Keystrokes extends Module implements HudMod {
@Override @Override
public void onEnabled(boolean e) { public void onEnabled(boolean e) {
enabled.value = e; enabled.value = e;
dimensionsDirty = true;
} }
} }
@@ -7,8 +7,9 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import org.saturnclient.modules.ModManager; import org.saturnclient.modules.ModManager;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.SaturnClientConfig;
import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.saturnclient.cosmetics.Emotes;
import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
@@ -40,6 +41,10 @@ public class SaturnClient implements ModInitializer {
SaturnClientConfig.init(); SaturnClientConfig.init();
ModManager.init(); ModManager.init();
client.execute(() -> {
SaturnScreen.preload(client);
});
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save()); ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> { SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> {
@@ -75,7 +80,7 @@ public class SaturnClient implements ModInitializer {
KeyInputHandler.register(); KeyInputHandler.register();
Emotes.initialize(); Emotes.initialize();
if (Auth.authenticate()) { if (ServiceClient.authenticate()) {
Cloaks.initialize(); Cloaks.initialize();
Hats.initialize(); Hats.initialize();
LOGGER.info(MOD_ID + " initialization complete"); LOGGER.info(MOD_ID + " initialization complete");
@@ -1,283 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.elements.Notification.NotificationKind;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import org.saturnclient.saturnclient.SaturnClient;
public class Auth {
public static Map<String, SaturnPlayer> players = new HashMap<>();
public static Map<String, String> playerNames = new HashMap<>();
public static String uuid;
private static Thread listenerThread;
private static Thread pingThread;
private static volatile boolean running = false;
public static boolean authenticate() {
// Register shutdown hook ONCE
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> close());
try {
if (SaturnClient.client.getSession() == null) {
SaturnClient.LOGGER.error("No active Minecraft session found");
return false;
}
String accessToken = SaturnClient.client.getSession().getAccessToken();
SaturnClient.LOGGER.info("Authenticating");
Network.init();
Network.write(accessToken);
String response = Network.read();
SaturnParser parser = new SaturnParser(response);
uuid = parser.getString("uuid");
String cloak = parser.getString("cloak");
String hat = parser.getString("hat");
for (String availableCloak : parser.getArray("cloaks")) {
Cloaks.availableCloaks.add(availableCloak);
}
for (String availableHat : parser.getArray("hats")) {
Hats.availableHats.add(availableHat);
}
players.put(uuid, new SaturnPlayer(cloak, hat));
Cloaks.loadCloak(uuid);
playerNames.put(SaturnClient.client.getSession().getUsername(), uuid);
afterAuth();
startListenerThread();
startPingThread();
return true;
} catch (Exception e) {
SaturnClient.LOGGER.error("Authentication failed", e);
return false;
}
}
private static void startListenerThread() {
running = true;
listenerThread = new Thread(() -> {
try {
while (running) {
String message = Network.read();
if (message == null) {
SaturnClient.LOGGER.error("Connection closed");
running = false;
break;
}
SaturnClient.LOGGER.info("Received: " + message);
SaturnParser parser = new SaturnParser(message);
switch (parser.method) {
case "player":
String uuid = parser.getString("uuid");
if (parser.getBool("saturn")) {
String cloak = parser.getString("cloak");
String hat = parser.getString("hat");
players.put(uuid, new SaturnPlayer(cloak, hat));
Cloaks.loadCloak(uuid);
} else {
playerNames.entrySet().removeIf(entry -> entry.getValue().equals(uuid));
}
break;
case "successful_transaction":
String cloak = parser.getStringOrNull("cloak");
String hat = parser.getStringOrNull("hat");
if (cloak != null) {
Cloaks.availableCloaks.add(cloak);
}
if (hat != null) {
Hats.availableHats.add(hat);
}
case "update_cloak":
String targetUuid = parser.getStringOrNull("uuid");
String targetCloak = parser.getStringOrNull("cloak");
if (targetUuid != null && targetCloak != null) {
SaturnPlayer player = players.get(targetUuid);
if (player != null) {
player.cloak = targetCloak;
}
}
break;
case "update_hat":
targetUuid = parser.getStringOrNull("uuid");
String targetHat = parser.getStringOrNull("hat");
if (targetUuid != null && targetHat != null) {
SaturnPlayer player = players.get(targetUuid);
if (player != null) {
player.hat = targetHat;
}
}
break;
case "emote":
targetUuid = parser.getStringOrNull("uuid");
String emote = parser.getStringOrNull("name");
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
if (player.getUuidAsString().replace("-", "").equals(targetUuid)) {
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
if (emote != null && !emote.isEmpty()) {
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000);
}
animationStack.addAnimLayer(1000,
PlayerAnimationRegistry
.getAnimation(Identifier.of("saturnclient", emote))
.playAnimation());
} else {
animationStack.removeLayer(1000);
}
}
}
break;
default:
if (parser.error != null) {
String[] a = parser.error.replace("!", "").split(": ");
Utils.notify(NotificationKind.Error, a[0], a[1]);
}
break;
}
}
} catch (Exception e) {
SaturnClient.LOGGER.error("Error reading from server", e);
} finally {
close();
}
});
listenerThread.setDaemon(true);
listenerThread.start();
}
private static void startPingThread() {
pingThread = new Thread(() -> {
try {
while (running && !Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(25000); // 25 seconds
if (running) {
Network.write("ping");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupt status
break; // Exit the loop on interruption
}
}
} catch (Exception e) {
if (!(e instanceof InterruptedException)) {
SaturnClient.LOGGER.error("Error in ping thread", e);
}
}
});
pingThread.setDaemon(true);
pingThread.start();
}
public static void close() {
running = false;
if (pingThread != null) {
pingThread.interrupt();
}
if (listenerThread != null) {
listenerThread.interrupt();
}
try {
close();
SaturnClient.LOGGER.info("SaturnApi connection closed.");
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to close resources", e);
}
}
public static void afterAuth() {
PlayerTracker.initialize();
}
public static void setCloak(String cloakName) {
try {
Network.write("set_cloak@cloak=" + cloakName + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void setHat(String hatName) {
try {
Network.write("set_hat@hat=" + hatName + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void emote(String name) {
try {
Network.write("emote@name=" + name + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static String stringifyPlayers() {
return players.keySet().stream()
.filter(key -> key != uuid)
.collect(Collectors.joining("$"));
}
public static void player(String name, String uuid) {
try {
playerNames.put(name, uuid.replaceAll("-", ""));
Network.write("player@uuid=" + uuid.replaceAll("-", ""));
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void buyCloak(String name) {
try {
Network.write("buy_cloak@cloak=" + name);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void buyHat(String name) {
try {
Network.write("buy_hat@hat=" + name);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
}
@@ -1,105 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.io.*;
import java.net.*;
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.interfaces.*;
import java.security.spec.X509EncodedKeySpec;
public class Network {
static Socket socket;
static PrintWriter out;
static BufferedReader in;
static byte[] key = new byte[32];
private static final int CONNECTION_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
public static class AES {
public static byte[] encryptAES256(String message, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encryptedMessage = cipher.doFinal(message.getBytes());
return encryptedMessage;
}
public static String decryptAES256(byte[] encryptedMessage, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
return new String(decryptedMessage);
}
}
public static void init() throws Exception {
// Generate AES key
new SecureRandom().nextBytes(key);
socket = new Socket();
socket.connect(new InetSocketAddress("77.247.92.168", 8080), CONNECTION_TIMEOUT);
socket.setSoTimeout(READ_TIMEOUT);
// out = new DataOutputStream(socket.getOutputStream());
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder publicKeyBuilder = new StringBuilder();
String line;
while (!(line = in.readLine()).contains("-----END PUBLIC KEY-----")) {
publicKeyBuilder.append(line).append("\n");
}
publicKeyBuilder.append(line);
String publicKeyPem = publicKeyBuilder.toString();
String publicKeyBase64 = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "").replace("\n", "").replace("\r", "");
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedKey = rsaCipher.doFinal(key);
out.println(Base64.getEncoder().encodeToString(encryptedKey));
}
public static void write(String message) {
try {
out.println(Base64.getEncoder().encodeToString(AES.encryptAES256(message, key)));
} catch (Exception e) {
throw new RuntimeException("Failed to write to server: " + e.getMessage());
}
}
public static String read() {
try {
String msg = in.readLine();
if (msg != null) {
return AES.decryptAES256(Base64.getDecoder().decode(msg), key);
} else {
return "";
}
} catch (Exception e) {
return "";
}
}
public static void close() {
try {
socket.close();
out.close();
in.close();
} catch (Exception e) {
}
}
}
@@ -1,71 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class SaturnParser {
public String method = "";
public String error;
private Map<String, String> params = new HashMap<>();
public SaturnParser(String input) throws IOException {
String[] parts = input.split("@");
if (parts.length == 0) {
throw new IOException("Invalid input string");
}
if (parts[0].startsWith("!")) {
error = input;
}
method = parts[0];
for (int i = 1; i < parts.length; i++) {
String[] keyValue = parts[i].split("=");
if (keyValue.length == 2) {
params.put(keyValue[0].trim(), keyValue[1].trim());
} else if (keyValue.length == 1) {
params.put(keyValue[0].trim(), "");
} else {
throw new IOException("Invalid parameter format: " + parts[i]);
}
}
}
public String getString(String i) throws IOException {
String val = params.get(i);
if (val != null) {
return val;
} else {
throw new IOException("Parameter not found " + i);
}
}
public String getStringOrNull(String i) throws IOException {
String val = params.get(i);
return val;
}
public boolean getBool(String i) throws IOException {
String val = params.get(i);
if (val != null) {
return val.equals("true");
} else {
throw new IOException("Parameter not found " + i);
}
}
public String[] getArray(String i) throws IOException {
String val = params.get(i);
if (val != null) {
if (val.isEmpty()) {
return new String[] {};
}
return val.split("\\$");
} else {
throw new IOException("Parameter not found " + i);
}
}
}
@@ -1,11 +0,0 @@
package org.saturnclient.saturnclient.auth;
public class SaturnPlayer {
public String cloak = "";
public String hat = "";
public SaturnPlayer(String cloak, String hat) {
this.cloak = cloak;
this.hat = hat;
}
}
@@ -0,0 +1,205 @@
package org.saturnclient.saturnclient.client;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import dev.selimaj.session.Session;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.util.Identifier;
public class ServiceClient {
private static Session session;
public static UUID uuid;
public static boolean connectTimeout() {
try {
session = Session.connect("wss://saturn-server.selimaj.dev", 10, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
return false;
}
}
public static boolean authenticate() {
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> {
if (session != null)
try {
session.close();
} catch (Exception e) {
SaturnClient.LOGGER.error("Unable to close Saturn session");
}
});
try {
var mcSession = SaturnClient.client.getSession();
if (mcSession == null) {
SaturnClient.LOGGER.error("No active Minecraft session found");
return false;
}
String accessToken = mcSession.getAccessToken();
uuid = mcSession.getUuidOrNull();
String username = mcSession.getUsername();
SaturnClient.LOGGER.info("Authenticating with UUID: " + uuid);
if (!connectTimeout()) {
SaturnClient.LOGGER.error("Unable to authenticate: Session Server Timeout");
return false;
}
ServiceMethods.Types.Player response = session.request(ServiceMethods.Authenticate, accessToken)
.get();
for (String availableCloak : response.cloaks()) {
Cloaks.availableCloaks.add(availableCloak);
}
for (String availableHat : response.hats()) {
Hats.availableHats.add(availableHat);
}
eventHandlers();
SaturnPlayer.set(new SaturnPlayer(uuid, username, response.cloak(), response.hat()));
Cloaks.loadCloak(uuid);
return true;
} catch (Exception e) {
SaturnClient.LOGGER.error("Authentication failed", e);
return false;
}
}
public static void setCloak(String itemId) {
try {
session.request(ServiceMethods.SetCloak, itemId).whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else {
try {
session.request(ServiceMethods.SendPlayer,
new ServiceMethods.Types.SendPlayerRequest(SaturnPlayer.getExternalUUIDAsString()));
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to set cloak (service): ", e);
}
}
public static void setHat(String itemId) {
try {
session.request(ServiceMethods.SetHat, itemId).whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else {
try {
session.request(ServiceMethods.SendPlayer,
new ServiceMethods.Types.SendPlayerRequest(SaturnPlayer.getExternalUUIDAsString()));
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to set hat (service): ", e);
}
}
public static void buyCloak(String itemId) {
try {
session.request(ServiceMethods.BuyCloak, itemId).whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else {
Cloaks.availableCloaks.add(itemId);
}
});
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to buy cloak (service): ", e);
}
}
public static void buyHat(String itemId) {
try {
session.request(ServiceMethods.BuyHat, itemId).whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else {
Hats.availableHats.add(itemId);
}
});
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to buy hat (service): ", e);
}
}
public static void emote(String emote) {
try {
session.request(ServiceMethods.Emote,
new ServiceMethods.Types.EmoteRequest(emote, SaturnPlayer.getExternalUUIDAsString()))
.whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
}
});
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to emote (service): ", e);
}
}
public static void eventHandlers() {
session.onNotification(ServiceMethods.EmoteEvent, (data) -> {
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
if (player.getUuidAsString().equals(data.from())) {
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
if (data.emote() != null && !data.emote().isEmpty()) {
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000);
}
animationStack.addAnimLayer(1000,
PlayerAnimationRegistry
.getAnimation(Identifier.of("saturnclient", data.emote()))
.playAnimation());
} else {
animationStack.removeLayer(1000);
}
}
}
});
session.onNotification(ServiceMethods.Player, (player) -> {
System.out.println(player);
SaturnPlayer.set(player.toSaturnPlayer());
});
}
public static void player(UUID uuid, String name) {
try {
session.request(ServiceMethods.GetPlayer, uuid.toString())
.whenComplete((user, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else if (user != null) {
SaturnPlayer.set(user.toSaturnPlayer(uuid, name));
}
});
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to get player", e);
}
}
}
@@ -0,0 +1,113 @@
package org.saturnclient.saturnclient.client;
import java.util.UUID;
import org.saturnclient.saturnclient.client.ServiceMethods.Types.SendPlayerRequest;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import dev.selimaj.session.types.Method;
public class ServiceMethods {
// =========================
// Types (Rust structs)
// =========================
public static class Types {
public record EmoteRequest(String emote, String[] targets) {
}
public record EmoteEvent(String emote, String from) {
}
public record Player(
String cloak,
String hat,
String[] cloaks,
String[] hats) {
public SaturnPlayer toSaturnPlayer(UUID uuid, String name) {
return new SaturnPlayer(uuid, name, cloak, hat);
}
}
public record PlayerStream(Player player, String name, String uuid) {
public SaturnPlayer toSaturnPlayer() {
return new SaturnPlayer(UUID.fromString(uuid), name, this.player.cloak, this.player.hat);
}
}
public record SendPlayerRequest(String[] targets) {
}
}
// =========================
// AUTH
// =========================
public static final Method<String, Types.Player, String> Authenticate = new Method<>("auth",
String.class,
Types.Player.class,
String.class);
// =========================
// EQUIP
// =========================
public static final Method<String, String, String> SetCloak = new Method<>("set_cloak",
String.class,
String.class,
String.class);
public static final Method<String, String, String> SetHat = new Method<>("set_hat",
String.class,
String.class,
String.class);
// =========================
// BUY
// =========================
public static final Method<String, String, String> BuyCloak = new Method<>("buy_cloak",
String.class,
String.class,
String.class);
public static final Method<String, String, String> BuyHat = new Method<>("buy_hat",
String.class,
String.class,
String.class);
// =========================
// EMOTE
// =========================
public static final Method<Types.EmoteRequest, Void, String> Emote = new Method<>("emote",
Types.EmoteRequest.class,
Void.class,
String.class);
public static final Method<Types.EmoteEvent, Void, Void> EmoteEvent = new Method<>("emote_event",
Types.EmoteEvent.class,
Void.class,
Void.class);
// =========================
// GET PLAYER
// =========================
public static final Method<String, Types.Player, String> GetPlayer = new Method<>("get_player",
String.class,
Types.Player.class,
String.class);
// =========================
// PLAYER (Server Notification)
// =========================
public static final Method<Types.PlayerStream, Void, Void> Player = new Method<>("player",
Types.PlayerStream.class,
Void.class,
Void.class);
// =========================
// SEND PLAYER
// =========================
public static final Method<SendPlayerRequest, Void, String> SendPlayer = new Method<>("send_player",
SendPlayerRequest.class,
Void.class,
String.class);
}
@@ -1,8 +1,10 @@
package org.saturnclient.saturnclient.auth; package org.saturnclient.saturnclient.client.player;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.client.ServiceClient;
import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@@ -45,8 +47,6 @@ public class PlayerTracker {
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) { private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
GameProfile profile = player.getProfile(); GameProfile profile = player.getProfile();
String uuid = profile.getId().toString(); ServiceClient.player(profile.getId(), profile.getName());
if (uuid != Auth.uuid)
Auth.player(profile.getName(), uuid);
} }
} }
@@ -0,0 +1,59 @@
package org.saturnclient.saturnclient.client.player;
import java.util.Map;
import java.util.UUID;
import org.saturnclient.saturnclient.config.SaturnClientConfig;
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);
/*
* 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 SaturnClientConfig.realisticLogo.value ? "" : "";
}
}
@@ -0,0 +1,54 @@
package org.saturnclient.saturnclient.client.player;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.client.ServiceClient;
public class SaturnPlayer {
private static Map<UUID, SaturnPlayer> PLAYERS = new HashMap<>();
private static Map<String, UUID> PLAYER_NAMES = new HashMap<>();
public String cloak = "";
public String hat = "";
public final UUID uuid;
public final String name;
public SaturnPlayer(UUID uuid, String name, String cloak, String hat) {
this.cloak = cloak;
this.hat = hat;
this.uuid = uuid;
this.name = name;
}
@Nullable
public static SaturnPlayer get() {
return PLAYERS.get(ServiceClient.uuid);
}
@Nullable
public static SaturnPlayer get(UUID uuid) {
return PLAYERS.get(uuid);
}
@Nullable
public static SaturnPlayer get(String name) {
UUID uuid = PLAYER_NAMES.get(name);
if (uuid == null)
return null;
return PLAYERS.get(uuid);
}
public static void set(SaturnPlayer player) {
PLAYER_NAMES.put(player.name, player.uuid);
PLAYERS.put(player.uuid, player);
}
public static String[] getExternalUUIDAsString() {
return PLAYERS.keySet().stream().filter(id -> !id.equals(ServiceClient.uuid)).map(UUID::toString)
.toArray(String[]::new);
}
}
@@ -1,107 +1,94 @@
package org.saturnclient.saturnclient.config; package org.saturnclient.saturnclient.config;
import com.google.gson.*; import com.google.gson.*;
import org.saturnclient.saturnclient.SaturnClient;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.LinkedHashMap;
import org.saturnclient.saturnclient.SaturnClient; import java.util.Map;
public class ConfigManager { public class ConfigManager {
private static final File configFile = new File(SaturnClient.client.runDirectory, "saturn.json"); private static final File configFile = new File(SaturnClient.client.runDirectory, "saturn.json");
private static final Map<String, Map<String, Property<?>>> properties = new HashMap<>();
private static JsonObject cachedThemeJson = null; // 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 Map<String, Property<?>> currentMap;
private final String namespace;
private final String[] path;
/**
* Creates a root namespace
*/
public ConfigManager(String namespace) { public ConfigManager(String namespace) {
this.namespace = namespace;
this.currentMap = new LinkedHashMap<>(); this.currentMap = new LinkedHashMap<>();
properties.put(namespace, currentMap); this.path = new String[] { namespace }; // Path is just ["namespace"]
System.out.println("Created namespace: " + namespace); ROOT.put(namespace, Property.namespace(currentMap));
} }
/**
* Creates a sub-namespace inside a parent
*/
public ConfigManager(ConfigManager parent, String namespace) { public ConfigManager(ConfigManager parent, String namespace) {
this.namespace = namespace;
this.currentMap = new LinkedHashMap<>(); this.currentMap = new LinkedHashMap<>();
Property<Map<String, Property<?>>> nsProp = Property.namespace(currentMap);
parent.property(namespace, nsProp);
System.out.println("Created sub-namespace: " + namespace);
}
public void sub(String name) { // Build new path: parent path + current namespace
property(name, Property.namespace(new LinkedHashMap<>())); 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) { public <T> Property<T> property(String name, Property<T> value) {
currentMap.put(name, value); currentMap.put(name, value);
loadProp(name, value); loadSingleProperty(name, value);
return value; return value;
} }
private <T> void loadProp(String name, Property<T> prop) { /**
JsonObject source = cachedThemeJson != null ? cachedThemeJson : loadAndCache(); * Add empty namespace
if (source == null) */
return; public ConfigManager sub(String name) {
return new ConfigManager(this, name);
JsonElement namespaceJson = source.get(namespace);
if (namespaceJson == null || !namespaceJson.isJsonObject())
return;
JsonElement value = namespaceJson.getAsJsonObject().get(name);
if (value != null)
prop.loadFromJson(value);
}
private static JsonObject loadAndCache() {
try {
if (!configFile.exists()) {
Files.writeString(configFile.toPath(), "{}");
return new JsonObject();
}
String json = Files.readString(configFile.toPath());
cachedThemeJson = JsonParser.parseString(json).getAsJsonObject();
return cachedThemeJson;
} catch (IOException e) {
SaturnClient.LOGGER.error("Error reading config file", e);
return null;
}
} }
/**
* Load entire config
*/
public static void load() { public static void load() {
SaturnClient.LOGGER.info("Loading config..."); SaturnClient.LOGGER.info("Loading config...");
JsonObject json = loadAndCache(); JsonObject json = loadAndCache();
if (json == null) if (json == null)
return; return;
properties.forEach((namespace, map) -> { loadRecursive(json, ROOT);
JsonElement nsElement = json.get(namespace);
if (nsElement != null && nsElement.isJsonObject()) {
loadProperties(nsElement.getAsJsonObject(), map);
}
});
}
private static void loadProperties(JsonObject json, Map<String, Property<?>> props) {
props.forEach((name, prop) -> {
JsonElement el = json.get(name);
if (el != null)
prop.loadFromJson(el);
});
} }
/**
* Save entire config
*/
public static void save() { public static void save() {
SaturnClient.LOGGER.info("Saving config..."); SaturnClient.LOGGER.info("Saving config...");
JsonObject root = new JsonObject();
properties.forEach((namespace, map) -> { JsonObject rootJson = new JsonObject();
JsonObject nsJson = new JsonObject(); saveRecursive(rootJson, ROOT);
saveProperties(nsJson, map);
root.add(namespace, nsJson);
});
try { try {
String formatted = new GsonBuilder().setPrettyPrinting().create().toJson(root); String formatted = new GsonBuilder()
.setPrettyPrinting()
.create()
.toJson(rootJson);
Files.writeString(configFile.toPath(), formatted); Files.writeString(configFile.toPath(), formatted);
SaturnClient.LOGGER.info("Config saved successfully."); SaturnClient.LOGGER.info("Config saved successfully.");
} catch (IOException e) { } catch (IOException e) {
@@ -109,8 +96,83 @@ public class ConfigManager {
} }
} }
private static void saveProperties(JsonObject json, Map<String, Property<?>> props) { /**
props.forEach((name, prop) -> json.add(name, prop.toJson())); * 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() { public Map<String, Property<?>> getProperties() {
@@ -1,11 +0,0 @@
package org.saturnclient.saturnclient.config;
public class NamedProperty<T> {
public String name;
public Property<T> prop;
public NamedProperty(String name, Property<T> value) {
this.name = name;
this.prop = value;
}
}
@@ -10,6 +10,15 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
public class Property<T> { public class Property<T> {
public static class NamedProperty<T> {
public String name;
public Property<T> prop;
public NamedProperty(String name, Property<T> value) {
this.name = name;
this.prop = value;
}
}
public enum PropertyType { public enum PropertyType {
BOOLEAN, BOOLEAN,
@@ -277,4 +286,13 @@ public class Property<T> {
if (value != null) if (value != null)
this.loadFromJson(value); this.loadFromJson(value);
} }
public boolean isNamespace() {
return type == PropertyType.NAMESPACE;
}
@SuppressWarnings("unchecked")
public Map<String, Property<?>> getNamespaceMap() {
return isNamespace() ? (Map<String, Property<?>>) (Object) value : null;
}
} }
@@ -1,11 +1,8 @@
package org.saturnclient.saturnclient; package org.saturnclient.saturnclient.config;
import org.saturnclient.saturnclient.config.Property;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class SaturnClientConfig { public class SaturnClientConfig {
@@ -15,37 +12,21 @@ public class SaturnClientConfig {
public static Property<Boolean> saturnTitleScreen = Property.bool(true); public static Property<Boolean> saturnTitleScreen = Property.bool(true);
public static Property<Boolean> bendyCloaks = Property.bool(true); public static Property<Boolean> bendyCloaks = Property.bool(true);
public static Property<Integer> openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B); public static Property<Integer> openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B);
public static Property<Boolean> stagger = Property.bool(true);
public static Identifier getLogo() { public static Identifier getLogo() {
return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO; return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO;
} }
public static String getSaturnIndicator() {
return realisticLogo.value ? "" : "";
}
/*
* Gets the icon color of a specific individual, here are the different colors
*
* - Owner: Dark Red
* - Admin: Red
* - Partners: Gold
* - Contributor: Aqua
* - Other/player: White
*/
public static Formatting getIconColor(String uuid) {
if (uuid.equals("d362a04228bd49e1a807ae74dbe8aba9")) {
return Formatting.DARK_RED; // Owner
}
return Formatting.WHITE;
}
public static void init() { public static void init() {
config = new ConfigManager("Saturn Client"); config = new ConfigManager("Saturn Client");
config.property("Realistic logo", realisticLogo); config.property("Realistic logo", realisticLogo);
config.property("Saturn client title screen", saturnTitleScreen); config.property("Saturn client title screen", saturnTitleScreen);
config.property("Open Emote Wheel", openEmoteWheel); config.property("Open Emote Wheel", openEmoteWheel);
config.property("Bendy Cloaks", bendyCloaks); config.property("Bendy Cloaks", bendyCloaks);
config.property("Stagger Animations", stagger);
// Initialize a sub namespace for theme
Theme.init(config);
} }
} }
@@ -0,0 +1,88 @@
package org.saturnclient.saturnclient.config;
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 (keeping your current system)
public static Property<Integer> FONT = Property.font(1);
public static void init(ConfigManager parent) {
ConfigManager config = new ConfigManager(parent, "UI Style");
config.property("Background", BACKGROUND);
config.property("Foreground", FOREGROUND);
config.property("Primary", PRIMARY);
config.property("Primary Foreground", PRIMARY_FG);
config.property("Accent", ACCENT);
config.property("Accent Foreground", ACCENT_FG);
config.property("Scrollbar Color", SCROLL);
config.property("Background Radius", BG_RADIUS);
config.property("Widget Radius", WIDGET_RADIUS);
config.property("Font", FONT);
}
/**
* primary by default, accent if true
*/
public static int getBg(boolean hover) {
return hover ? ACCENT.value : PRIMARY.value;
}
/**
* (Foreground) primary by default, accent if true
*/
public static int getFg(boolean hover) {
return hover ? ACCENT_FG.value : PRIMARY_FG.value;
}
/**
* Multiplies existing alpha with 0–255 alpha.
*/
public static int withAlpha(int alpha, int color) {
alpha = Math.max(0, Math.min(255, alpha));
int originalAlpha = (color >>> 24) & 0xFF;
// Merge (multiply) alphas
int merged = (originalAlpha * alpha) / 255;
return (color & 0x00FFFFFF) | (merged << 24);
}
/**
* Multiplies existing alpha with 0.0–1.0 alpha.
*/
public static int withAlpha(float alpha, int color) {
alpha = Math.max(0f, Math.min(1f, alpha));
int originalAlpha = (color >>> 24) & 0xFF;
int merged = (int) (originalAlpha * alpha);
return (color & 0x00FFFFFF) | (merged << 24);
}
}
@@ -1,93 +0,0 @@
package org.saturnclient.saturnclient.config;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.saturnclient.saturnclient.SaturnClient;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class ThemeManager {
private static File themeFile = new File(
SaturnClient.client.runDirectory,
"saturn.theme.json");
private static List<ThemeManager> theme = new ArrayList<>();
private static JsonObject cachedThemeJson = null;
private Map<String, Map<String, Object>> states;
private Map<String, Property<?>> currentStyling;
private String namespace;
public ThemeManager(String namespace, String... states) {
this.namespace = namespace;
this.states = new LinkedHashMap<>();
this.currentStyling = new LinkedHashMap<>();
this.states.put(null, new LinkedHashMap<>());
for (String s : states) {
this.states.put(s, new LinkedHashMap<>());
}
theme.add(this);
}
public void setState(String state) {
for (Map.Entry<String, Object> tProp : states.get(state).entrySet()) {
currentStyling.get(tProp.getKey()).setValue(tProp.getValue());
}
}
public void applyState(String state) {
for (Map.Entry<String, Object> tProp : states.get(state).entrySet()) {
if (!states.get(null).get(tProp.getKey()).equals(tProp.getValue()))
currentStyling.get(tProp.getKey()).setValue(tProp.getValue());
}
}
public <T> Property<T> property(String name, Property<T> prop) {
for (Map.Entry<String, Map<String, Object>> state : this.states.entrySet()) {
Property<?> property = prop.copy();
if (cachedThemeJson == null) {
load();
}
String stateName = state.getKey();
property.load(name, cachedThemeJson.get(stateName != null ? namespace + "@" + stateName : namespace));
state.getValue().put(name, property.value);
}
currentStyling.put(name, prop);
return prop;
}
public <T> void propertyStateDefault(String state, String name, T value) {
Property<?> prop = currentStyling.get(name).copy();
prop.setValue(value, value);
if (cachedThemeJson == null) {
load();
}
prop.load(name, cachedThemeJson.get(state != null ? namespace + "@" + state : namespace));
states.get(state).put(name, prop.value);
}
public static void load() {
try {
SaturnClient.LOGGER.info("Starting to load theme...");
if (!themeFile.exists()) {
themeFile.createNewFile();
Files.write(themeFile.toPath(), "{}".getBytes());
}
cachedThemeJson = JsonParser.parseString(
new String(Files.readAllBytes(themeFile.toPath()))).getAsJsonObject();
SaturnClient.LOGGER.info("Theme loaded");
} catch (IOException e) {
SaturnClient.LOGGER.error("Error reading the theme file", e);
}
}
}
@@ -1,7 +1,7 @@
package org.saturnclient.saturnclient.cosmetics; package org.saturnclient.saturnclient.cosmetics;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.config.SaturnClientConfig;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
@@ -283,20 +283,18 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light,
PlayerEntityRenderState playerEntityRenderState, float f, float g) { PlayerEntityRenderState playerEntityRenderState, float f, float g) {
if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible) { if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible
|| playerEntityRenderState.skinTextures.capeTexture() != null) {
return; return;
} }
if (playerEntityRenderState.skinTextures.capeTexture() != null) { SaturnPlayer player = SaturnPlayer.get(playerEntityRenderState.name);
return;
} if (player == null) {
String name = playerEntityRenderState.name;
String uuid = Auth.playerNames.get(name);
if (uuid == null || !Auth.players.containsKey(uuid)) {
return; return;
} }
Identifier customCape = Cloaks.getCurrentCloakTexture(uuid); Identifier customCape = Cloaks.getCurrentCloakTexture(player.uuid);
if (customCape == null || this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, if (customCape == null || this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack,
LayerType.WINGS)) { LayerType.WINGS)) {
return; return;
@@ -1,8 +1,6 @@
package org.saturnclient.saturnclient.cosmetics; package org.saturnclient.saturnclient.cosmetics;
import org.saturnclient.saturnclient.auth.SaturnPlayer; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
@@ -29,38 +27,36 @@ public class HatFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState,
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
PlayerEntityRenderState state, float limbAngle, float limbDistance) { PlayerEntityRenderState state, float limbAngle, float limbDistance) {
ItemStack headItem = state.equippedHeadStack; ItemStack headItem = state.equippedHeadStack;
String uuid = Auth.playerNames.get(state.name); SaturnPlayer player = SaturnPlayer.get(state.name);
if (headItem.isEmpty() && uuid != null && !state.invisible) {
SaturnPlayer player = Auth.players.get(uuid);
if (player == null || player.hat == null || player.hat.isEmpty()) {
return;
}
matrices.push(); if (!headItem.isEmpty() || state.invisible || player == null || player.hat.isEmpty()) {
return;
this.getContextModel().head.rotate(matrices);
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f));
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f));
ItemStack customHat = new ItemStack(Items.STICK);
customHat.set(DataComponentTypes.ITEM_MODEL,
Identifier.of("saturnclient:hat_" + player.hat));
customHat.set(DataComponentTypes.EQUIPPABLE,
EquippableComponent.builder(EquipmentSlot.HEAD).build());
ItemRenderer itemRenderer = SaturnClient.client.getItemRenderer();
itemRenderer.renderItem(
customHat,
ModelTransformationMode.THIRD_PERSON_RIGHT_HAND,
light,
OverlayTexture.DEFAULT_UV,
matrices,
vertexConsumers,
null,
0);
matrices.pop();
} }
matrices.push();
this.getContextModel().head.rotate(matrices);
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f));
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f));
ItemStack customHat = new ItemStack(Items.STICK);
customHat.set(DataComponentTypes.ITEM_MODEL,
Identifier.of("saturnclient:hat_" + player.hat));
customHat.set(DataComponentTypes.EQUIPPABLE,
EquippableComponent.builder(EquipmentSlot.HEAD).build());
ItemRenderer itemRenderer = SaturnClient.client.getItemRenderer();
itemRenderer.renderItem(
customHat,
ModelTransformationMode.THIRD_PERSON_RIGHT_HAND,
light,
OverlayTexture.DEFAULT_UV,
matrices,
vertexConsumers,
null,
0);
matrices.pop();
} }
} }
@@ -2,8 +2,10 @@ package org.saturnclient.saturnclient.cosmetics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.saturnclient.saturnclient.auth.SaturnPlayer; import java.util.UUID;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
public class Hats { public class Hats {
public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" }; public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
@@ -13,16 +15,16 @@ public class Hats {
availableHats.add(0, ""); availableHats.add(0, "");
} }
public static void setHat(String uuid, String hatName) { public static void setHat(String hatName) {
setHatSilent(uuid, hatName); if (availableHats.contains(hatName)) {
Auth.setHat(hatName); setHat(ServiceClient.uuid, hatName);
ServiceClient.setHat(hatName);
}
} }
public static void setHatSilent(String uuid, String hatName) { public static void setHat(UUID uuid, String hatName) {
SaturnPlayer player = Auth.players.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null) { if (player != null) {
Auth.players.put(uuid, new SaturnPlayer("", hatName));
} else {
player.hat = hatName; player.hat = hatName;
} }
} }
@@ -1,10 +1,11 @@
package org.saturnclient.saturnclient.cosmetics.cloaks; package org.saturnclient.saturnclient.cosmetics.cloaks;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@@ -32,8 +33,8 @@ public class Cloaks {
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/"; private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
public static final List<String> availableCloaks = new ArrayList<>(); public static final List<String> availableCloaks = new ArrayList<>();
public static Identifier cloakCacheIdentifier = null; public static Identifier cloakCacheIdentifier = null;
public static final Map<String, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>(); public static final Map<UUID, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
private static final Map<String, Long> lastFrameTime = new ConcurrentHashMap<>(); private static final Map<UUID, Long> lastFrameTime = new ConcurrentHashMap<>();
private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool( private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool(
Math.min(4, Runtime.getRuntime().availableProcessors()), Math.min(4, Runtime.getRuntime().availableProcessors()),
@@ -54,17 +55,6 @@ public class Cloaks {
availableCloaks.add(0, ""); availableCloaks.add(0, "");
} }
/**
* Handles loading and caching of a new cloak texture.
*
* @param cloakName Name of the cloak file to load
*/
public static void setCloak(String uuid, String cloakName) {
Auth.setCloak(cloakName);
setCloakSilent(uuid, cloakName);
SaturnClient.LOGGER.info("Cloak set to " + cloakName);
}
/** /**
* Sets cloak of the current player * Sets cloak of the current player
* *
@@ -72,7 +62,8 @@ public class Cloaks {
*/ */
public static void setCloak(String cloakName) { public static void setCloak(String cloakName) {
if (availableCloaks.contains(cloakName)) { if (availableCloaks.contains(cloakName)) {
setCloak(Auth.uuid, cloakName); setCloak(ServiceClient.uuid, cloakName);
ServiceClient.setCloak(cloakName);
} }
} }
@@ -82,11 +73,10 @@ public class Cloaks {
* *
* @param cloakName Name of the cloak file to load * @param cloakName Name of the cloak file to load
*/ */
public static void setCloakSilent(String uuid, String cloakName) { public static void setCloak(UUID uuid, String cloakName) {
SaturnPlayer player = Auth.players.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null) {
Auth.players.put(uuid, new SaturnPlayer(cloakName, null)); if (player != null) {
} else {
player.cloak = cloakName; player.cloak = cloakName;
} }
@@ -99,8 +89,9 @@ public class Cloaks {
* *
* @param cloakName Name of the cloak file to load * @param cloakName Name of the cloak file to load
*/ */
public static void loadCloak(String uuid) { public static void loadCloak(UUID uuid) {
SaturnPlayer player = Auth.players.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player != null && player.cloak != null) { if (player != null && player.cloak != null) {
if (!player.cloak.isEmpty()) { if (!player.cloak.isEmpty()) {
if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) { if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) {
@@ -146,7 +137,7 @@ public class Cloaks {
} }
} }
public static CompletableFuture<Void> loadAnimatedCloakAsync(String uuid, String cloakName) { public static CompletableFuture<Void> loadAnimatedCloakAsync(UUID uuid, String cloakName) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
String fileName = cloakName + ".gif"; String fileName = cloakName + ".gif";
@@ -196,7 +187,7 @@ public class Cloaks {
} else { } else {
// Queue for main thread execution // Queue for main thread execution
// SaturnClient.client.execute( // SaturnClient.client.execute(
// () -> IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame)); // () -> IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame));
} }
animatedFrames.add(new AnimatedCloakData(frameIdentifier, delay)); animatedFrames.add(new AnimatedCloakData(frameIdentifier, delay));
@@ -226,16 +217,14 @@ public class Cloaks {
}, CLOAK_LOADER_EXECUTOR); }, CLOAK_LOADER_EXECUTOR);
} }
public static Identifier getCurrentCloakTexture(String uuid) { public static Identifier getCurrentCloakTexture(UUID uuid) {
if (!Auth.players.containsKey(uuid)) { SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null || player.cloak.isEmpty()) {
return null; return null;
} }
String cloakName = Auth.players.get(uuid).cloak; String cloakName = player.cloak;
if (cloakName.isEmpty()) {
return null;
}
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) { if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
List<AnimatedCloakData> frames = animatedCloaks.get(uuid); List<AnimatedCloakData> frames = animatedCloaks.get(uuid);
@@ -6,8 +6,8 @@ import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.InputUtil;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.config.SaturnClientConfig;
import org.saturnclient.ui2.screens.EmoteWheel; import org.saturnclient.ui2.screens.EmoteWheel;
import org.saturnclient.ui2.screens.ShiftMenu; import org.saturnclient.ui2.screens.ShiftMenu;
@@ -47,7 +47,7 @@ public class KeyInputHandler {
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player);
if (animationStack.isActive() && animationStack.getPriority() == 1000) { if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000); animationStack.removeLayer(1000);
Auth.emote(""); ServiceClient.emote("");
} }
} }
}); });
@@ -14,8 +14,11 @@ import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.Roles;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import java.util.UUID;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
@@ -37,11 +40,11 @@ public abstract class NameTagMixin<S extends EntityRenderState> {
private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices, private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
String uuid = isSaturn(state); UUID uuid = isSaturn(state);
if (uuid != null) { if (uuid != null) {
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator()) MutableText iconText = Text.literal(Roles.getSaturnIndicator())
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); .styled(style -> style.withColor(Roles.getIconColor(uuid)));
Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE)); Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE));
@@ -52,12 +55,12 @@ public abstract class NameTagMixin<S extends EntityRenderState> {
} }
} }
private String isSaturn(S state) { private UUID isSaturn(S state) {
if (state instanceof PlayerEntityRenderState) { if (state instanceof PlayerEntityRenderState) {
String name = ((PlayerEntityRenderState) state).name; String name = ((PlayerEntityRenderState) state).name;
String uuid = Auth.playerNames.get(name); SaturnPlayer player = SaturnPlayer.get(name);
if (Auth.players.containsKey(uuid)) { if (player != null) {
return uuid; return player.uuid;
} }
} }
@@ -0,0 +1,26 @@
package org.saturnclient.saturnclient.mixin;
import org.saturnclient.saturnclient.config.SaturnClientConfig;
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 (SaturnClientConfig.saturnTitleScreen.value) {
if (screen instanceof TitleScreen)
return new TitleMenu();
if (screen == null && ((MinecraftClient) (Object) this).world == null)
return new TitleMenu();
}
return screen;
}
}
@@ -0,0 +1,159 @@
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 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(remap = false)
private long reloadCompleteTime;
@Shadow(remap = false)
private long reloadStartTime;
@Shadow(remap = false)
private boolean reloading;
@Shadow(remap = false)
private Consumer<Optional<Throwable>> exceptionHandler;
@Shadow(remap = false)
private float progress;
/**
* @author SaturnClient
* @reason Replace vanilla splash rendering completely but keep exit logic
*/
@Overwrite
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
int screenWidth = context.getScaledWindowWidth();
int screenHeight = context.getScaledWindowHeight();
long currentTime = Util.getMeasuringTimeMs();
// Initialize reload start time if not set
if (reloadStartTime == -1L) {
reloadStartTime = currentTime;
}
// Compute fade-out progress after reload completes
float fadeOutProgress = reloadCompleteTime > -1L
? (float) (currentTime - reloadCompleteTime) / 1000f
: -1f;
int alpha = fadeOutProgress >= 0f
? MathHelper.ceil((1.0f - MathHelper.clamp(fadeOutProgress - 1f, 0f, 1f)) * 255f)
: 255;
// Compute fade-in progress while reloading
float reloadProgress = reloadStartTime > -1L
? (float) (currentTime - reloadStartTime) / 500f
: -1f;
// Get render scope for better UI
RenderScope scope = new RenderScope(context);
scope.setRenderLayer(RenderLayer::getGuiTextured);
// Draw background overlay
if (fadeOutProgress >= 1.0F) {
renderCurrentScreen(context, mouseX, mouseY, delta);
int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f);
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
Theme.withAlpha(overlayAlpha, 0xFFFFFFFF));
} else if (reloading) {
if (reloadProgress < 1.0F) {
renderCurrentScreen(context, mouseX, mouseY, delta);
}
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
Theme.withAlpha(alpha, 0xFFFFFFFF));
} else {
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
Theme.withAlpha(alpha, 0xFFFFFFFF));
}
float u = this.reload.getProgress();
this.progress = MathHelper.clamp(this.progress * 0.95F + u * 0.050000012F, 0.0F, 1.0F);
// Draw central logo
drawLogo(scope, screenWidth, screenHeight, alpha);
renderProgressBar(scope, context, screenWidth, screenHeight, alpha);
// Remove overlay after fade-out completes
if (fadeOutProgress >= 2.0F) {
client.setOverlay(null);
}
// Handle reload completion and exceptions
handleReloadCompletion(context);
}
private void renderCurrentScreen(DrawContext context, int mouseX, int mouseY, float delta) {
if (client.currentScreen != null) {
client.currentScreen.render(context, mouseX, mouseY, delta);
}
}
/** Draw the main logo centered */
private void drawLogo(RenderScope scope, int screenWidth, int screenHeight, int alpha) {
scope.drawTexture(Textures.LOGO, (screenWidth - 60) / 2, ((screenHeight - 60) / 2) - 20, 0, 0, 60, 60,
Theme.withAlpha(alpha, 0xFFFFFFFF));
}
private void renderProgressBar(RenderScope scope, DrawContext context, int screenWidth, int screenHeight,
int alpha) {
int progressY = ((screenHeight - 5) / 2) + 25;
context.drawBorder((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
int progressWidth = Math.min((int) (progress * 120), 120);
int progressX = (screenWidth - 120) / 2;
context.fill(progressX, progressY, progressX + progressWidth, progressY + 5,
Theme.withAlpha(alpha, 0xFFFFFFFF));
}
/** Compute overlay alpha from fade progress */
private int computeOverlayAlpha(float progress) {
return MathHelper.ceil((1.0F - MathHelper.clamp(progress, 0.0F, 1.0F)) * 255.0F);
}
/** Handle reload completion logic */
private void handleReloadCompletion(DrawContext context) {
float reloadProgress = reloadStartTime > -1L ? (float) (Util.getMeasuringTimeMs() - reloadStartTime) / 500f
: -1f;
if (reloadCompleteTime == -1L && reload.isComplete() && (!reloading || reloadProgress >= 2.0F)) {
try {
reload.throwException();
exceptionHandler.accept(Optional.empty());
} catch (Throwable ex) {
exceptionHandler.accept(Optional.of(ex));
}
reloadCompleteTime = Util.getMeasuringTimeMs();
if (client.currentScreen != null) {
client.currentScreen.init(client, context.getScaledWindowWidth(), context.getScaledWindowHeight());
}
}
}
}
@@ -1,13 +1,14 @@
package org.saturnclient.saturnclient.mixin; package org.saturnclient.saturnclient.mixin;
import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import java.util.UUID;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.Roles;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@@ -30,13 +31,13 @@ public abstract class TabListMixin {
public Text getDisplayName() { public Text getDisplayName() {
GameProfile profile = ((PlayerListEntry) (Object) this).getProfile(); GameProfile profile = ((PlayerListEntry) (Object) this).getProfile();
String name = profile.getName(); String name = profile.getName();
String uuid = profile.getId().toString().replaceAll("-", ""); UUID uuid = profile.getId();
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator()) if (SaturnPlayer.get(uuid) != null) {
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); return Text.literal(Roles.getSaturnIndicator())
.styled(style -> style.withColor(Roles.getIconColor(uuid)))
Text nameText = Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)); .append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)));
}
return Auth.players.containsKey(uuid) ? iconText.append(nameText) : Text.literal(name); return Text.literal(name);
} }
} }
@@ -1,22 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui2.screens.TitleMenu;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.gui.screen.TitleScreen;
@Mixin(TitleScreen.class)
public class TitleScreenMixin {
@Inject(method = "init", at = @At("HEAD"), cancellable = true)
private void onInit(CallbackInfo ci) {
if (SaturnClientConfig.saturnTitleScreen.value) {
SaturnClient.client.setScreen(new TitleMenu());
ci.cancel();
}
}
}
@@ -32,6 +32,22 @@ public class Element {
public void charTyped(char typedChar) { public void charTyped(char typedChar) {
} }
public void mouseClicked(double mouseX, double mouseY, int button) {
}
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
}
public void mouseReleased(double mouseX, double mouseY, int button) {
}
public void playAnimationFrame(long elapsed) {
if (this.animation != null && elapsed >= this.animation.delay) {
this.animation.tick(this.curve
.apply(Math.min(1.0, (double) (elapsed - this.animation.delay) / this.animation.duration)), this);
}
}
public Element dimensions(int width, int height) { public Element dimensions(int width, int height) {
this.width = width; this.width = width;
this.height = height; this.height = height;
@@ -4,13 +4,15 @@ public class ElementContext {
public int mouseX, mouseY; public int mouseX, mouseY;
public int elementWidth, elementHeight; public int elementWidth, elementHeight;
public float elementScale; public float elementScale;
public long elapsed;
public ElementContext(int mouseX, int mouseY, Element e) { public ElementContext(long elapsed, int mouseX, int mouseY, Element e) {
this.mouseX = mouseX - e.x; this.mouseX = mouseX - e.x;
this.mouseY = mouseY - e.y; this.mouseY = mouseY - e.y;
this.elementWidth = e.width; this.elementWidth = e.width;
this.elementHeight = e.height; this.elementHeight = e.height;
this.elementScale = e.scale; this.elementScale = e.scale;
this.elapsed = elapsed;
} }
public boolean isHovering() { public boolean isHovering() {
@@ -9,11 +9,17 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.SvgTexture;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.font.TextRenderer.TextLayerType; import net.minecraft.client.font.TextRenderer.TextLayerType;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.ScreenRect; import net.minecraft.client.gui.ScreenRect;
import net.minecraft.client.render.DiffuseLighting; import net.minecraft.client.render.DiffuseLighting;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
@@ -42,6 +48,10 @@ public class RenderScope {
private int opacity = 255 << 24; private int opacity = 255 << 24;
private final ItemRenderState itemRenderState; private final ItemRenderState itemRenderState;
public RenderScope(DrawContext context) {
this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
}
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) { public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
this.matrices = matrices; this.matrices = matrices;
this.vertexConsumers = vertexConsumers; this.vertexConsumers = vertexConsumers;
@@ -217,6 +227,11 @@ public class RenderScope {
private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1, private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1,
float v2, int color) { float v2, int color) {
if (sprite.toString().endsWith(".svg")) {
sprite = SvgTexture.getSvg(MinecraftClient.getInstance(), sprite, (x2 - x1) * 2, (y2 - y1) * 2);
}
if (color == 0) if (color == 0)
return; return;
x1 *= 4; x1 *= 4;
@@ -391,4 +406,45 @@ public class RenderScope {
sprite.getMinV(), sprite.getMaxV(), color); sprite.getMinV(), sprite.getMaxV(), color);
} }
} }
public void fill(int x1, int y1, int x2, int y2, int color) {
this.fill(x1, y1, x2, y2, 0, color);
}
public void fill(int x1, int y1, int x2, int y2, int z, int color) {
this.fill(RenderLayer.getGui(), x1, y1, x2, y2, z, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int color) {
this.fill(layer, x1, y1, x2, y2, 0, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int z, int color) {
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
int i;
if (x1 < x2) {
i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
i = y1;
y1 = y2;
y2 = i;
}
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(layer);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, (float) z).color(color);
}
public void drawBorder(int x, int y, int width, int height, int color) {
this.fill(x, y, x + width, y + 1, color);
this.fill(x, y + height - 1, x + width, y + height, color);
this.fill(x, y + 1, x + 1, y + height - 1, color);
this.fill(x + width - 1, y + 1, x + width, y + height - 1, color);
}
} }
@@ -1,11 +1,11 @@
package org.saturnclient.ui2; package org.saturnclient.ui2;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor; import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.anim.Animation;
import org.saturnclient.ui2.anim.SlideXAbsolute;
import org.saturnclient.ui2.components.ElementRenderer; import org.saturnclient.ui2.components.ElementRenderer;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@@ -15,6 +15,8 @@ import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.RotatingCubeMapRenderer; import net.minecraft.client.gui.RotatingCubeMapRenderer;
import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.render.DefaultFramebufferSet; import net.minecraft.client.render.DefaultFramebufferSet;
import net.minecraft.client.texture.ResourceTexture;
import net.minecraft.client.texture.TextureManager;
import net.minecraft.client.util.Pool; import net.minecraft.client.util.Pool;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@@ -22,12 +24,13 @@ import net.minecraft.util.Identifier;
public abstract class SaturnScreen extends Screen { public abstract class SaturnScreen extends Screen {
private final Pool pool = new Pool(3); private final Pool pool = new Pool(3);
protected List<Element> elements = new ArrayList<>(); protected List<Element> elements = new ArrayList<>();
public int blurDuration = 700; protected float backgroundOpacity = 1.0f;
public float blurProgress = 0.0f; protected Instant start = null;
public int backgroundBlur = 10; public int backgroundBlur = 10;
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer( protected static final Identifier PANORAMA = Identifier.of("saturnclient",
Identifier.of("saturnclient", "textures/gui/title/background/panorama")); "textures/gui/title/background/panorama");
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(PANORAMA);
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER; public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
@@ -35,41 +38,24 @@ public abstract class SaturnScreen extends Screen {
ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER); ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER);
} }
public static void preload(MinecraftClient client) {
TextureManager textureManager = client.getTextureManager();
// Force upload each texture to GPU
for (int i = 0; i < 6; i++) {
String var10001 = PANORAMA.getPath();
Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png");
ResourceTexture resourceTexture = new ResourceTexture(tex);
textureManager.registerTexture(tex, resourceTexture);
}
}
public SaturnScreen(String title) { public SaturnScreen(String title) {
super(Text.literal(title)); super(Text.literal(title));
} }
public void draw(Element element) { public void draw(Element element) {
synchronized (elements) { ElementRenderer.draw(elements, element);
elements.add(element);
}
if (element.animation != null) {
element.animation.init(element);
Animation.execute((Float progress) -> {
element.animation.tick(progress, element);
}, element.animation.duration);
}
if (element.duration != null) {
new Thread(() -> {
try {
Animation fadeOut = new SlideXAbsolute(700, -(width - element.x));
Thread.sleep(element.duration);
fadeOut.init(element);
Animation.executeSync((Float progress) -> {
fadeOut.tick(progress, element);
}, fadeOut.duration);
synchronized (elements) {
elements.remove(element);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
} }
@Override @Override
@@ -78,10 +64,6 @@ public abstract class SaturnScreen extends Screen {
width *= 2; width *= 2;
height *= 2; height *= 2;
ui(); // abstraction to render the saturn ui and also render extra stuff here ui(); // abstraction to render the saturn ui and also render extra stuff here
Animation.execute((Float progress) -> {
blurProgress = progress;
}, blurDuration);
} }
public abstract void ui(); public abstract void ui();
@@ -89,20 +71,26 @@ public abstract class SaturnScreen extends Screen {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) { public void render(DrawContext context, int mouseX, int mouseY, float delta) {
if (start == null) {
start = Instant.now();
}
long elapsed = Duration.between(start, Instant.now()).toMillis();
mouseX *= 2; mouseX *= 2;
mouseY *= 2; mouseY *= 2;
if (client.world == null && client.getCurrentServerEntry() == null) { if (client.world == null && client.getCurrentServerEntry() == null) {
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta); ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, backgroundOpacity, delta);
} }
PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect( PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
Identifier.ofVanilla("blur"), Identifier.ofVanilla("blur"),
DefaultFramebufferSet.MAIN_ONLY); DefaultFramebufferSet.MAIN_ONLY);
if (postEffectProcessor != null) {
postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress); if (postEffectProcessor != null) {
postEffectProcessor.render(this.client.getFramebuffer(), this.pool); postEffectProcessor.setUniforms("Radius", backgroundBlur * Math.min((float) elapsed / 700, 1.0f));
} postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
}
// We are using a Abstracted RenderScope because older minecraft versions don't // We are using a Abstracted RenderScope because older minecraft versions don't
// use DrawContext // use DrawContext
@@ -113,7 +101,7 @@ public abstract class SaturnScreen extends Screen {
renderScope.matrices.scale(0.5f, 0.5f, 0.5f); renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
ElementRenderer.render(new ArrayList<>(elements), renderScope, mouseX, mouseY); ElementRenderer.render(new ArrayList<>(elements), elapsed, renderScope, mouseX, mouseY);
renderScope.matrices.pop(); renderScope.matrices.pop();
} }
@@ -128,6 +116,26 @@ public abstract class SaturnScreen extends Screen {
return false; return false;
} }
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
mouseX *= 2;
mouseY *= 2;
ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY);
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
mouseX *= 2;
mouseY *= 2;
ElementRenderer.mouseReleased(elements, mouseX, mouseY, button);
return super.mouseReleased(mouseX, mouseY, button);
}
@Override @Override
public boolean mouseScrolled( public boolean mouseScrolled(
double mouseX, double mouseX,
@@ -51,7 +51,6 @@ public class Utils {
} }
public static <T> T getOrNull(List<T> list, int index) { public static <T> T getOrNull(List<T> list, int index) {
SaturnClient.LOGGER.info("Accessing index {} in list of size {}", index, list.size());
return (index >= 0 && index < list.size()) ? list.get(index) : null; return (index >= 0 && index < list.size()) ? list.get(index) : null;
} }
@@ -1,75 +1,16 @@
package org.saturnclient.ui2.anim; package org.saturnclient.ui2.anim;
import java.util.function.Consumer;
import java.util.function.Function;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
public abstract class Animation { public abstract class Animation {
public int duration; public int duration;
public int delay = 0;
Animation(int duration) { Animation(int duration) {
this.duration = duration; this.duration = duration;
} }
public abstract void tick(float progress, Element element); public abstract void tick(double progress, Element element);
public abstract void init(Element element); public abstract void init(Element element);
public static void execute(Consumer<Float> tick, int duration) {
new Thread(() -> {
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
float progress = 0.0f;
double incremental = (1000 - duration) / 10000.0;
for (int i = 0; i <= 100; i++) {
float newProgress = (float) (double) curveFunction.apply(i / 100.0);
while (Math.abs(newProgress - progress) > 1e-6) {
if (progress < newProgress) {
progress += incremental;
if (progress > newProgress) progress = newProgress;
} else {
progress -= incremental;
if (progress < newProgress) progress = newProgress;
}
tick.accept(newProgress);
try {
Thread.sleep(duration / 100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
public static void executeSync(Consumer<Float> tick, int duration) {
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
float progress = 0.0f;
double incremental = (1000 - duration) / 10000.0;
for (int i = 0; i <= 100; i++) {
float newProgress = (float) (double) curveFunction.apply(i / 100.0);
while (Math.abs(newProgress - progress) > 1e-6) {
if (progress < newProgress) {
progress += incremental;
if (progress > newProgress) progress = newProgress;
} else {
progress -= incremental;
if (progress < newProgress) progress = newProgress;
}
tick.accept(newProgress);
try {
Thread.sleep(duration / 100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
} }
@@ -10,13 +10,13 @@ public class Fade extends Animation {
} }
@Override @Override
public void tick(float progress, Element element) { public void tick(double progress, Element element) {
element.opacity = targetOpacity * progress; element.opacity = Math.max(0.1f, targetOpacity * (float) progress);
} }
@Override @Override
public void init(Element element) { public void init(Element element) {
targetOpacity = element.opacity; targetOpacity = element.opacity;
element.opacity = 0; element.opacity = 0.1f;
} }
} }
@@ -10,8 +10,8 @@ public class FadeOut extends Animation {
} }
@Override @Override
public void tick(float progress, Element element) { public void tick(double progress, Element element) {
element.opacity = targetOpacity - progress; element.opacity = targetOpacity - (float) progress;
} }
@Override @Override
@@ -0,0 +1,32 @@
package org.saturnclient.ui2.anim;
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);
}
@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);
}
}
@@ -24,7 +24,7 @@ public class SlideX extends Animation {
} }
@Override @Override
public void tick(float progress, Element element) { public void tick(double progress, Element element) {
element.x = elementX + (int) (offset * progress); element.x = elementX + (int) (offset * progress);
} }
@@ -24,7 +24,7 @@ public class SlideXAbsolute extends Animation {
} }
@Override @Override
public void tick(float progress, Element element) { public void tick(double progress, Element element) {
element.x = elementX + (int) (offset * progress); element.x = elementX + (int) (offset * progress);
} }
@@ -5,7 +5,7 @@ import org.saturnclient.ui2.Element;
public class SlideY extends Animation { public class SlideY extends Animation {
int offset = 20; int offset = 20;
int maxOffset = 20; int maxOffset = 20;
int elementY; double startY;
public SlideY(int duration) { public SlideY(int duration) {
super(duration); super(duration);
@@ -26,13 +26,14 @@ public class SlideY extends Animation {
} }
@Override @Override
public void tick(float progress, Element element) { public void init(Element element) {
element.y = elementY + (int) (offset * progress); startY = element.y - offset;
element.y = (int) startY;
} }
@Override @Override
public void init(Element element) { public void tick(double progress, Element element) {
element.y -= offset; double currentY = startY + offset * progress;
elementY = element.y; element.y = (int) currentY;
} }
} }
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.components; package org.saturnclient.ui2.components;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,13 +9,7 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class CosmeticPreview extends Element { public class CosmeticPreview extends Element {
private static ThemeManager theme = new ThemeManager("CosmeticPreview", "selected", "hovering"); static int padding = 14;
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x89000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> padding = theme.property("padding", Property.integer(14));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {}
Identifier sprite; Identifier sprite;
private Runnable onClick; private Runnable onClick;
@@ -33,22 +26,16 @@ public class CosmeticPreview extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
theme.setState("hovering"); isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value);
} else if (isSelected) {
theme.setState("selected");
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
int texWidth = width - padding.value; int texWidth = width - padding;
int texHeight = height - padding.value; int texHeight = height - padding;
renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value); renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
Theme.FOREGROUND.value);
theme.setState(null);
} }
@Override @Override
@@ -7,7 +7,6 @@ import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.Utils; import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.anim.Animation;
public class ElementRenderer { public class ElementRenderer {
public static void draw(List<Element> elements, Element element) { public static void draw(List<Element> elements, Element element) {
@@ -17,20 +16,18 @@ public class ElementRenderer {
if (element.animation != null) { if (element.animation != null) {
element.animation.init(element); element.animation.init(element);
Animation.execute((Float progress) -> {
element.animation.tick(progress, element);
}, element.animation.duration);
} }
} }
public static void render(List<Element> elements, RenderScope renderScope, int mouseX, int mouseY) { public static void render(List<Element> elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) {
for (Element element : elements) { for (Element element : elements) {
element.playAnimationFrame(elapsed);
renderScope.matrices.push(); renderScope.matrices.push();
renderScope.setOpacity(element.opacity); renderScope.setOpacity(element.opacity);
renderScope.matrices.translate(element.x, element.y, 0); renderScope.matrices.translate(element.x, element.y, 0);
renderScope.matrices.scale(element.scale, element.scale, 1.0f); renderScope.matrices.scale(element.scale, element.scale, 1.0f);
element.render(renderScope, new ElementContext(mouseX, mouseY, element)); element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, element));
renderScope.matrices.pop(); renderScope.matrices.pop();
renderScope.setRenderLayer(null); renderScope.setRenderLayer(null);
} }
@@ -44,7 +41,10 @@ public class ElementRenderer {
int adjustedMouseX = (int) mouseX - element.x; int adjustedMouseX = (int) mouseX - element.x;
int adjustedMouseY = (int) mouseY - element.y; int adjustedMouseY = (int) mouseY - element.y;
if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, element.height, element.scale)) { element.mouseClicked(adjustedMouseX * element.scale, adjustedMouseY * element.scale, button);
if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width,
element.height, element.scale)) {
element.focused = true; element.focused = true;
element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale)); element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale));
} }
@@ -58,16 +58,17 @@ public class ElementRenderer {
double mouseY, double mouseY,
double horizontalAmount, double horizontalAmount,
double verticalAmount) { double verticalAmount) {
for (Element element : new ArrayList<>(elements)) { for (Element element : new ArrayList<>(elements)) {
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, element.scale)) { if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height,
element.scroll( element.scale)) {
(int) mouseX - element.x, element.scroll(
(int) mouseY - element.y, (int) mouseX - element.x,
horizontalAmount, (int) mouseY - element.y,
verticalAmount); horizontalAmount,
} verticalAmount);
}
} }
}
}
public static void keyPressed(List<Element> elements, int keyCode, int scanCode, int modifiers) { public static void keyPressed(List<Element> elements, int keyCode, int scanCode, int modifiers) {
for (Element element : new ArrayList<>(elements)) { for (Element element : new ArrayList<>(elements)) {
@@ -81,4 +82,17 @@ public class ElementRenderer {
} }
} }
} }
public static void mouseDragged(List<Element> elements, double mouseX, double mouseY, int button, double deltaX,
double deltaY) {
for (Element element : new ArrayList<>(elements)) {
element.mouseDragged(mouseX - element.x, mouseY - element.y, button, deltaX, deltaY);
}
}
public static void mouseReleased(List<Element> elements, double mouseX, double mouseY, int button) {
for (Element element : new ArrayList<>(elements)) {
element.mouseReleased(mouseX - element.x, mouseY - element.y, button);
}
}
} }
@@ -2,8 +2,7 @@ package org.saturnclient.ui2.components;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
@@ -15,23 +14,12 @@ import org.saturnclient.ui2.screens.ConfigEditor;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayer;
public class SaturnModule extends Element { public class SaturnModule extends Element {
private static ThemeManager theme = new ThemeManager("Module", "enabled", "hovering");
private static Property<Integer> bgColor = theme.property("bg", Property.color(0xFF1d202d));
private static Property<Integer> fgColor = theme.property("fg", Property.color(0xFFA1A2B8));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFA1A2B8));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0xFF2e3248));
private static Property<Integer> tagBg = theme.property("tag-bg", Property.color(0xFF2e3248));
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.color(10));
private static Property<Integer> font = theme.property("font", Property.font(1));
private static Property<Integer> radius = theme.property("radius", Property.color(10));
static { private final Module mod;
theme.propertyStateDefault("enabled", "bg", 0xFF1d202d);
theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee);
theme.propertyStateDefault("hovering", "fg", 0xFF845eee);
}
private Module mod; static int p = 10; // padding
static int s = 14; // settings icon size
static int expand = 5; // settings icon click area
public SaturnModule(Module mod) { public SaturnModule(Module mod) {
this.mod = mod; this.mod = mod;
@@ -41,60 +29,137 @@ public class SaturnModule extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
int p = 10; boolean hovered = ctx.isHovering();
int s = 14; boolean enabled = mod.isEnabled();
boolean settingsHover = false;
int iconX = width - s - p;
int iconY = height - s - p;
boolean settingsHover = ctx.isHovering(
getSettingsX(),
getSettingsY(),
getSettingsHitBox(),
getSettingsHitBox());
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
if (mod.isEnabled()) { // Resolve theme colors
theme.setState("enabled"); int bgColor = Theme.PRIMARY.value;
} int fgColor = hovered && !settingsHover ? Theme.ACCENT.value : Theme.PRIMARY_FG.value;
if (ctx.isHovering(width - s - p - 5, height - s - p - 5, s + 10, s + 10)) { int iconBg = enabled ? Theme.ACCENT.value : Theme.PRIMARY.value;
settingsHover = true; int iconFg = enabled ? Theme.ACCENT_FG.value : fgColor;
} else if (ctx.isHovering()) {
theme.applyState("hovering");
}
renderScope.drawRoundedRectangle(0, 0, width, height, radius.value, bgColor.value); int settingsColor = settingsHover
? Theme.ACCENT.value
: Theme.withAlpha(160, fgColor);
int h = height-(p * 2); int h = height - (p * 2);
renderScope.drawRoundedRectangle(p, p, h, h, iconRadius.value, iconBg.value); // Main background
renderScope.drawRoundedRectangle(
0, 0,
width, height,
Theme.WIDGET_RADIUS.value,
bgColor);
renderScope.drawTexture(mod.getIconTexture(), p + (p / 2), p + (p / 2), 0, 0, h - p, h - p, iconFg.value); // Icon background
renderScope.drawRoundedRectangle(
p, p,
h, h,
Theme.WIDGET_RADIUS.value,
iconBg);
renderScope.drawText(0.6f, mod.getName(), p+h+4, p + 1, font.value, fgColor.value); // Icon
renderScope.drawTexture(
mod.getIconTexture(),
p + (p / 2),
p + (p / 2),
0, 0,
h - p,
h - p,
iconFg);
renderScope.drawText(0.35f, mod.getVersion(), width - p - (int) (Fonts.getWidth(mod.getVersion(), font.value) * 0.35f), p + 1, font.value, fgColor.value); // Module name
renderScope.drawText(
0.6f,
mod.getName(),
p + h + 4,
p + 1,
Theme.FONT.value,
fgColor);
// Version (right aligned)
String version = mod.getVersion();
int versionWidth = (int) (Fonts.getWidth(version, Theme.FONT.value) * 0.35f);
renderScope.drawText(
0.35f,
version,
width - p - versionWidth,
p + 1,
Theme.FONT.value,
fgColor);
// Tags
int xt = p + h + 4; int xt = p + h + 4;
for (String tag : mod.getTags()) { for (String tag : mod.getTags()) {
int xtb = (int) (Fonts.getWidth(tag, font.value) * 0.4f); int tagWidth = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f);
renderScope.drawRoundedRectangle(xt, p + (h/2), xtb + 6, 12, 5, tagBg.value);
renderScope.drawText(0.4f, tag, xt+2, p + (h/2) + 3, font.value, fgColor.value); renderScope.drawRoundedRectangle(
xt += xtb + 9; xt,
p + (h / 2),
tagWidth + 6,
12,
5,
Theme.ACCENT.value);
renderScope.drawText(
0.4f,
tag,
xt + 2,
p + (h / 2) + 3,
Theme.FONT.value,
Theme.ACCENT_FG.value);
xt += tagWidth + 9;
} }
if (settingsHover) { // Settings icon
theme.setState("hovering"); renderScope.drawTexture(
} Textures.SETTINGS,
iconX,
renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, fgColor.value); iconY,
0, 0,
theme.setState(null); s, s,
settingsColor);
} }
@Override @Override
public void click(int mouseX, int mouseY) { public void click(int mouseX, int mouseY) {
int p = 10; boolean settingsHover = Utils.isHovering(
int s = 14; mouseX - getSettingsX(),
mouseY - getSettingsY(),
getSettingsHitBox(),
getSettingsHitBox(),
1.0f);
if (Utils.isHovering(mouseX - (width - s - p - 5), mouseY - (height - s - p - 5), s + 10, s + 10, 1.0f)) { if (settingsHover) {
SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig())); SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig()));
} else { } else {
mod.setEnabled(!mod.isEnabled()); mod.setEnabled(!mod.isEnabled());
} }
} }
private int getSettingsX() {
return width - s - p - expand;
}
private int getSettingsY() {
return height - s - p - expand;
}
private int getSettingsHitBox() {
return s + expand * 2;
}
} }
@@ -1,9 +1,8 @@
package org.saturnclient.ui2.components; package org.saturnclient.ui2.components;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.config.SaturnClientConfig;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
@@ -18,19 +17,6 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class Sidebar extends Element { public class Sidebar extends Element {
private static ThemeManager theme = new ThemeManager("SideBar", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0x00000000));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.integer(10));
private static Property<Integer> padding = theme.property("padding", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "icon-bg", -7643914);
}
public static class SidebarComponent { public static class SidebarComponent {
Runnable onClick; Runnable onClick;
boolean end; boolean end;
@@ -45,7 +31,7 @@ public class Sidebar extends Element {
Runnable onClose; Runnable onClose;
SidebarComponent[] components = { SidebarComponent[] components = {
new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> { new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> {
SaturnClient.client.setScreen(new ModMenu()); SaturnClient.client.setScreen(new ModMenu());
}, false), }, false),
new Sidebar.SidebarComponent(Textures.SETTINGS, () -> { new Sidebar.SidebarComponent(Textures.SETTINGS, () -> {
@@ -79,36 +65,29 @@ public class Sidebar extends Element {
int bottomRow = 0; int bottomRow = 0;
int gap = 5; // 5px gap between icons int gap = 5; // 5px gap between icons
int sp = 30 - padding.value + gap; int padding = 10;
int s = 30 - (padding.value * 2); int sp = 30 - padding + gap;
int s2 = s + padding.value; int s = 30 - (padding * 2);
int s2 = s + padding;
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value);
int i = 0; int i = 0;
for (SidebarComponent component : components) { for (SidebarComponent component : components) {
int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
boolean selected = ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i;
renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding,
s + padding, Theme.WIDGET_RADIUS.value, Theme.getBg(selected));
renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.getFg(selected));
if (component.end) { if (component.end) {
int y = (height - (sp * bottomRow)) - (s * 2);
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
bottomRow += 1; bottomRow += 1;
} else { } else {
int y = padding.value + (sp * topRow);
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
topRow += 1; topRow += 1;
} }
@@ -121,25 +100,24 @@ public class Sidebar extends Element {
int topRow = 0; int topRow = 0;
int bottomRow = 0; int bottomRow = 0;
int gap = 5; // same 5px gap here int gap = 5;
int sp = 30 - padding.value + gap; int padding = 10;
int s = 30 - (padding.value * 2); int sp = 30 - padding + gap;
int s2 = s + padding.value; int s = 30 - (padding * 2);
int s2 = s + padding;
for (SidebarComponent component : components) { for (SidebarComponent component : components) {
int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow));
boolean hovering = Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale);
if (hovering) {
component.onClick.run();
break;
}
if (component.end) { if (component.end) {
int y = (height - (sp * bottomRow)) - (s * 2);
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
component.onClick.run();
break;
}
bottomRow += 1; bottomRow += 1;
} else { } else {
int y = padding.value + (sp * topRow);
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
component.onClick.run();
break;
}
topRow += 1; topRow += 1;
} }
} }
@@ -1,246 +0,0 @@
package org.saturnclient.ui2.components.dialog;
import java.awt.Color;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
import net.minecraft.client.gui.DrawContext;
public class ColorDialog extends SaturnScreen {
private Property<Integer> prop;
private SaturnScreen parent;
private static final int PICKER_WIDTH = 200;
private static final int PICKER_HEIGHT = 200;
private static final int SLIDER_WIDTH = 200;
private static final int SLIDER_HEIGHT = 10;
private static final int HUE_SLIDER_HEIGHT = 10;
private float hue = 0.0f;
private int selectedX = 0;
private int selectedY = 0;
private float opacity = 1.0f;
private boolean dragging = false;
public ColorDialog(Property<Integer> prop , SaturnScreen parent) {
super("Color Dialog");
this.prop = prop;
this.parent = parent;
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0) alpha = 255; // fallback if no alpha specified
this.opacity = alpha / 255.0f;
syncFromColor();
}
@Override
public void ui() {}
@Override
public void close() {
SaturnClient.client.setScreen(parent);
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
RenderScope renderScope = new RenderScope(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
renderScope.matrices.push();
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
// Draw the HSV saturation/value square (with current hue)
for (int x = 0; x < PICKER_WIDTH; x++) {
for (int y = 0; y < PICKER_HEIGHT; y++) {
float saturation = x / (float) PICKER_WIDTH;
float value = 1.0f - y / (float) PICKER_HEIGHT;
int color = Color.HSBtoRGB(hue, saturation, value);
context.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, 0xFF000000 | color);
}
}
int x = (xStart + selectedX) - 4;
int y = (yStart + selectedY) - 4;
context.fill(x, y, x+8, y+8, 0xFF000000 | prop.value);
context.drawBorder(x, y, 8, 8, 0xFFffffff);
renderSlider(context, mouseX, mouseY, delta);
renderHueSlider(context);
renderScope.matrices.pop();
}
public void renderSlider(DrawContext context, int mouseX, int mouseY, float delta) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
// Draw slider background (left = transparent, right = white)
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localOpacity = x / (float) SLIDER_WIDTH;
int alpha = (int)(localOpacity * 255) & 0xFF;
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
context.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
}
// Draw current value as a marker (thin black line)
int knobX = xStart + (int)(opacity * SLIDER_WIDTH);
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
// Draw opacity text
// context.drawText(textRenderer, String.format("Opacity: %.2f", opacity), xStart, yStart - 15, 0xFFFFFF, false);
}
private void renderHueSlider(DrawContext context) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localHue = x / (float) SLIDER_WIDTH;
int color = Color.HSBtoRGB(localHue, 1.0f, 1.0f);
context.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
}
int knobX = xStart + (int)(hue * SLIDER_WIDTH);
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
mouseX *= 2;
mouseY *= 2;
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
int relX = (int) mouseX - xStart;
int relY = (int) mouseY - yStart;
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
float saturation = relX / (float) PICKER_WIDTH;
float value = 1.0f - relY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
selectedX = relX;
selectedY = relY;
return true;
} else if (isInSlider(mouseX, mouseY)) {
updateOpacity(mouseX);
dragging = true;
return true;
} else if (isInHueSlider(mouseX, mouseY)) {
updateHue(mouseX);
return true;
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
dragging = false;
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
mouseX *= 2;
mouseY *= 2;
if (isInHueSlider(mouseX, mouseY)) {
updateHue(mouseX);
return true;
} else if (dragging) {
updateOpacity(mouseX);
return true;
} else {
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
int relX = (int) mouseX - xStart;
int relY = (int) mouseY - yStart;
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
float saturation = relX / (float) PICKER_WIDTH;
float value = 1.0f - relY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
selectedX = relX;
selectedY = relY;
return true;
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
private boolean isInSlider(double mouseX, double mouseY) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
mouseY >= yStart && mouseY <= yStart + SLIDER_HEIGHT;
}
private boolean isInHueSlider(double mouseX, double mouseY) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
mouseY >= yStart && mouseY <= yStart + HUE_SLIDER_HEIGHT;
}
private void updateOpacity(double mouseX) {
int xStart = (width - SLIDER_WIDTH) / 2;
float newOpacity = (float)((mouseX - xStart) / SLIDER_WIDTH);
opacity = Math.max(0.0f, Math.min(1.0f, newOpacity));
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
}
private void updateHue(double mouseX) {
int xStart = (width - SLIDER_WIDTH) / 2;
float newHue = (float)((mouseX - xStart) / SLIDER_WIDTH);
hue = Math.max(0.0f, Math.min(1.0f, newHue));
float saturation = selectedX / (float) PICKER_WIDTH;
float value = 1.0f - selectedY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
}
private void syncFromColor() {
int rgb = prop.value & 0xFFFFFF;
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
this.hue = hsb[0];
float saturation = hsb[1];
float value = hsb[2];
this.selectedX = (int)(saturation * PICKER_WIDTH);
this.selectedY = (int)((1.0f - value) * PICKER_HEIGHT);
// Extract alpha and convert to opacity float
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0) alpha = 255;
this.opacity = alpha / 255.0f;
}
}
@@ -1,32 +1,183 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.SaturnClient; import java.awt.Color;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.dialog.ColorDialog;
public class HexInput extends Element { public class HexInput extends Element {
public Property<Integer> prop; public Property<Integer> prop;
public SaturnScreen screen;
public HexInput(Property<Integer> prop, SaturnScreen screen) { // Picker state
private boolean open = false;
private float hue = 0f;
private float opacity = 1f;
private int selectedX = 0;
private int selectedY = 0;
private boolean draggingOpacity = false;
// Dimensions
private static final int PICKER_WIDTH = 100;
private static final int PICKER_HEIGHT = 100;
private static final int SLIDER_WIDTH = 100;
private static final int SLIDER_HEIGHT = 10;
private static final int HUE_SLIDER_HEIGHT = 10;
public HexInput(Property<Integer> prop) {
this.prop = prop;
this.width = 58; this.width = 58;
this.height = 20; this.height = 20;
this.prop = prop; syncFromColor();
this.scale = 0.8f;
this.screen = screen;
} }
@Override @Override
public void click(int mouseX, int mouseY) { public void click(int mouseX, int mouseY) {
SaturnClient.client.setScreen(new ColorDialog(prop, screen)); open = !open;
}
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
if (open) {
handlePickerClick(mouseX - width - 8, mouseY);
}
}
@Override
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
if (open) {
handlePickerDrag(mouseX - width - 8, mouseY);
}
}
@Override
public void mouseReleased(double mouseX, double mouseY, int button) {
draggingOpacity = false;
} }
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, 17, prop.value); // Draw the main color rectangle
renderScope.drawRoundedRectangle(0, 0, width, height, 6, prop.value);
if (!open)
return;
// Draw the picker relative to (width + 8, 0)
int pickerX = width + 8;
int pickerY = 0;
renderColorSquare(renderScope, pickerX, pickerY);
renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10);
renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10);
}
private void renderColorSquare(RenderScope renderScope, int xStart, int yStart) {
for (int x = 0; x < PICKER_WIDTH; x++) {
for (int y = 0; y < PICKER_HEIGHT; y++) {
float saturation = x / (float) PICKER_WIDTH;
float value = 1f - y / (float) PICKER_HEIGHT;
int color = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int) (opacity * 255) & 0xFF;
color = (alpha << 24) | (color & 0xFFFFFF);
renderScope.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, color);
}
}
// Draw selection square
int selX = xStart + selectedX - 4;
int selY = yStart + selectedY - 4;
renderScope.fill(selX, selY, selX + 8, selY + 8, 0xFF000000 | prop.value);
renderScope.drawBorder(selX, selY, 8, 8, 0xFFFFFFFF);
}
private void renderOpacitySlider(RenderScope renderScope, int xStart, int yStart) {
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localOpacity = x / (float) SLIDER_WIDTH;
int alpha = (int) (localOpacity * 255) & 0xFF;
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
}
int knobX = xStart + (int) (opacity * SLIDER_WIDTH);
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
}
private void renderHueSlider(RenderScope renderScope, int xStart, int yStart) {
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localHue = x / (float) SLIDER_WIDTH;
int color = Color.HSBtoRGB(localHue, 1f, 1f);
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
}
int knobX = xStart + (int) (hue * SLIDER_WIDTH);
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
}
private void handlePickerClick(double mouseX, double mouseY) {
// Color square
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
selectedX = (int) mouseX;
selectedY = (int) mouseY;
updateColorFromSelection();
}
// Opacity slider
int opY = -SLIDER_HEIGHT - 10;
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= opY && mouseY <= opY + SLIDER_HEIGHT) {
updateOpacity(mouseX);
draggingOpacity = true;
}
// Hue slider
int hueY = PICKER_HEIGHT + 10;
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= hueY && mouseY <= hueY + HUE_SLIDER_HEIGHT) {
updateHue(mouseX);
}
}
private void handlePickerDrag(double mouseX, double mouseY) {
// Opacity
if (draggingOpacity)
updateOpacity(mouseX);
// Color square
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
selectedX = (int) mouseX;
selectedY = (int) mouseY;
updateColorFromSelection();
}
}
private void updateColorFromSelection() {
float saturation = selectedX / (float) PICKER_WIDTH;
float value = 1f - selectedY / (float) PICKER_HEIGHT;
int color = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int) (opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (color & 0xFFFFFF);
}
private void updateHue(double mouseX) {
hue = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
updateColorFromSelection();
}
private void updateOpacity(double mouseX) {
opacity = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
updateColorFromSelection();
}
private void syncFromColor() {
int rgb = prop.value & 0xFFFFFF;
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
hue = hsb[0];
selectedX = (int) (hsb[1] * PICKER_WIDTH);
selectedY = (int) ((1f - hsb[2]) * PICKER_HEIGHT);
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0)
alpha = 255;
opacity = alpha / 255f;
} }
} }
@@ -2,21 +2,20 @@ package org.saturnclient.ui2.components.inputs;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public abstract class Input extends Element { public abstract class Input extends Element {
private static ThemeManager theme = new ThemeManager("Input");
private static Property<Integer> font = theme.property("font", Property.font(1));
String text = ""; String text = "";
public int cursorPosition = 0; public int cursorPosition = 0;
public abstract void checkReset(); public abstract void checkReset();
public abstract void backspace(); public abstract void backspace();
public boolean validateBackspace() { public boolean validateBackspace() {
return true; return true;
} }
@@ -71,11 +70,12 @@ public abstract class Input extends Element {
int scrollOffset = getScrollOffset(); int scrollOffset = getScrollOffset();
String visibleText = getVisibleText(scrollOffset); String visibleText = getVisibleText(scrollOffset);
int textColor = focused ? 0xFFFFFF : 0xAAAAAA; int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
renderScope.drawText(0.6f, visibleText, 4, 4, font.value, textColor); renderScope.drawText(0.6f, visibleText, 4, 4, Theme.FONT.value, textColor);
if (focused) { if (focused) {
int cursorX = 4 + int cursorX = 4 +
(int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), font.value) * 0.6f); (int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), Theme.FONT.value)
* 0.6f);
renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF); renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF);
} }
@@ -90,7 +90,7 @@ public abstract class Input extends Element {
int maxWidth = width - 4; // Available width for text int maxWidth = width - 4; // Available width for text
while (offset < cursorPosition) { // Ensure cursor remains visible while (offset < cursorPosition) { // Ensure cursor remains visible
String visibleText = text.substring(offset, cursorPosition); String visibleText = text.substring(offset, cursorPosition);
if (Fonts.getWidth(visibleText, font.value) > maxWidth) { if (Fonts.getWidth(visibleText, Theme.FONT.value) > maxWidth) {
offset++; offset++;
} else { } else {
break; break;
@@ -104,7 +104,7 @@ public abstract class Input extends Element {
// Ensure text does not overflow past the available width // Ensure text does not overflow past the available width
for (int i = 1; i <= visibleText.length(); i++) { for (int i = 1; i <= visibleText.length(); i++) {
if (Fonts.getWidth(visibleText.substring(0, i), font.value) > width - 4) { if (Fonts.getWidth(visibleText.substring(0, i), Theme.FONT.value) > width - 4) {
return visibleText.substring(0, i - 1); // Trim overflow return visibleText.substring(0, i - 1); // Trim overflow
} }
} }
@@ -2,15 +2,13 @@ package org.saturnclient.ui2.components.inputs;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class KeybindingSelector extends Element { public class KeybindingSelector extends Element {
private static ThemeManager theme = new ThemeManager("Input");
private static Property<Integer> font = theme.property("font", Property.font(1));
Property<Integer> prop; Property<Integer> prop;
public KeybindingSelector(Property<Integer> prop) { public KeybindingSelector(Property<Integer> prop) {
@@ -23,9 +21,12 @@ public class KeybindingSelector extends Element {
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
int textColor = focused ? 0xFFFFFF : 0xAAAAAA; int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000); renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
renderScope.drawText(0.6f, prop.value == -1 ? "<NONE>" : String name = GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value));
GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)).toUpperCase(),
4, 4, font.value, textColor); renderScope.drawText(0.6f,
prop.value == -1 ? "<NONE>"
: name != null ? name.toUpperCase() : "<Invalid-Key>",
4, 4, Theme.FONT.value, textColor);
} }
@Override @Override
@@ -1,7 +1,7 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,11 +10,6 @@ import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayer;
public class Select extends Element { public class Select extends Element {
private static ThemeManager theme = new ThemeManager("Toggle");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xAAAAAA));
private static Property<Integer> arrowColor = theme.property("arrow-color", Property.color(0xff8B5CF6));
private static Property<Integer> font = theme.property("font", Property.font(1));
private Property<Integer> prop; private Property<Integer> prop;
public Select(Property<Integer> prop) { public Select(Property<Integer> prop) {
@@ -27,15 +22,14 @@ public class Select extends Element {
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawText(0.8f, "<", 0, 0, font.value, arrowColor.value); renderScope.drawText(0.8f, "<", 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
String text = prop.getSelection(); String text = prop.getSelection();
renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, font.value) * 0.6f)) / 2, 1, font.value, fgColor.value); renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, Theme.FONT.value) * 0.6f)) / 2, 1,
Theme.FONT.value, Theme.FOREGROUND.value);
renderScope.drawText(0.8f, ">", width - 10, 0, font.value, arrowColor.value); renderScope.drawText(0.8f, ">", width - 10, 0, Theme.FONT.value, Theme.FOREGROUND.value);
theme.setState(null);
} }
@Override @Override
@@ -1,22 +1,12 @@
package org.saturnclient.ui2.components.inputs; package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
public class Toggle extends Element { public class Toggle extends Element {
private static ThemeManager theme = new ThemeManager("Toggle", "hovering", "enabled");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF808080));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFF909090));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(30));
static {
theme.propertyStateDefault("enabled", "bg-color", 0xaf8B5CF6);
theme.propertyStateDefault("enabled", "fg-color", 0xff8B5CF6);
}
private Property<Boolean> prop; private Property<Boolean> prop;
public Toggle(Property<Boolean> prop) { public Toggle(Property<Boolean> prop) {
@@ -27,19 +17,10 @@ public class Toggle extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
theme.setState(null); renderScope.drawRoundedRectangle(0, 0, width, height, height,
Theme.withAlpha(0.5f, prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value));
if (prop.value) { renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30,
theme.setState("enabled"); prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value);
}
if (ctx.isHovering()) {
theme.applyState("hovering");
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, fgColor.value);
} }
@Override @Override
@@ -0,0 +1,58 @@
package org.saturnclient.ui2.elements;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.saturnclient.config.SaturnClientConfig;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.components.ElementRenderer;
public class AnimationStagger extends Element {
protected List<Element> children = new ArrayList<>();
public int delay;
public AnimationStagger(int delay) {
this.delay = delay;
}
public void draw(Element element) {
if (!SaturnClientConfig.stagger.value) {
element.animation = null;
}
if (element.animation != null) {
element.animation.delay = delay * children.size();
}
ElementRenderer.draw(children, element);
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
ElementRenderer.render(children, ctx.elapsed, renderScope,
ctx.mouseX,
ctx.mouseY);
}
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
ElementRenderer.mouseClicked(children, mouseX, mouseY, button);
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers);
}
@Override
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
ElementRenderer.mouseDragged(children, mouseX, mouseY, button, deltaX, deltaY);
}
@Override
public void mouseReleased(double mouseX, double mouseY, int button) {
ElementRenderer.mouseReleased(children, mouseX, mouseY, button);
}
}
@@ -1,43 +1,29 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class Button extends Element { public class Button extends Element {
private static ThemeManager theme = new ThemeManager("Button", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
private static Property<Integer> font = theme.property("font", Property.font(1));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee);
}
private String text; private String text;
private Runnable onClick; private Runnable onClick;
public Button(String text, Runnable onClick) { public Button(String text, Runnable onClick) {
this.text = text; this.text = text;
this.onClick = onClick; this.onClick = onClick;
this.dimensions(Fonts.getWidth(text, font.value) + 50, 38); this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38);
} }
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
theme.setState("hovering"); ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
// renderScope.drawText(text, (width - Fonts.getWidth(text, font)) / 2, (height - Fonts.getHeight()) / 2, bold.value, fgColor.value); renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height),
renderScope.drawText(text, Fonts.centerX(width, text, font.value), Fonts.centerY(height), font.value, fgColor.value); Theme.FONT.value,
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
} }
@Override @Override
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,9 +9,6 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class ClickableTexture extends Element { public class ClickableTexture extends Element {
private static ThemeManager theme = new ThemeManager("ClickableTexture", "hovering");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
Identifier sprite; Identifier sprite;
private Runnable onClick; private Runnable onClick;
@@ -23,14 +19,9 @@ public class ClickableTexture extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height, fgColor.value); renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height,
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
} }
@Override @Override
@@ -3,8 +3,9 @@ package org.saturnclient.ui2.elements;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.SvgTexture;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class ImageTexture extends Element { public class ImageTexture extends Element {
@@ -16,7 +17,16 @@ public class ImageTexture extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured); if (sprite.toString().endsWith(".svg")) {
MinecraftClient client = MinecraftClient.getInstance();
// Get the actual window pixel dimensions for the image
int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWidth());
int renderHeight = (int) (height * client.getWindow().getFramebufferHeight() / client.getWindow().getHeight());
sprite = SvgTexture.getSvg(client, sprite, renderWidth * 2, renderHeight * 2);
}
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height); renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height);
} }
} }
@@ -1,7 +1,7 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,11 +10,9 @@ import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
public class Notification extends Element { public class Notification extends Element {
private static ThemeManager theme = new ThemeManager("Notification"); private static Property<Integer> error = Property.color(0xFFbf212f);
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000)); private static Property<Integer> info = Property.color(0xFF264b96);
private static Property<Integer> error = theme.property("error", Property.color(0xFFbf212f)); private static Property<Integer> success = Property.color(0xFF27b376);
private static Property<Integer> info = theme.property("info", Property.color(0xFF264b96));
private static Property<Integer> success = theme.property("success", Property.color(0xFF27b376));
public static enum NotificationKind { public static enum NotificationKind {
Error, Error,
@@ -40,7 +38,7 @@ public class Notification extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, 10, bgColor.value); renderScope.drawRoundedRectangle(0, 0, width, height, 10, Theme.PRIMARY.value);
switch (kind) { switch (kind) {
case Error: case Error:
renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value); renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value);
@@ -4,21 +4,16 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.components.ElementRenderer; import org.saturnclient.ui2.components.ElementRenderer;
public class Scroll extends Element { public class Scroll extends Element {
private static ThemeManager theme = new ThemeManager("Scroll"); private static Property<Integer> scrollBarRadius = Property.integer(10);
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000)); private static Property<Integer> scrollBarWidth = Property.integer(5);
private static Property<Integer> scrollBarColor = theme.property("scrollbar-color", Property.color(-7643914)); private static Property<Integer> scrollBarPadding = Property.integer(5);
private static Property<Integer> scrollBarRadius = theme.property("scrollbar-radius", Property.integer(10));
private static Property<Integer> scrollBarWidth = theme.property("scrollbar-width", Property.integer(5));
private static Property<Integer> scrollBarPadding = theme.property("scrollbar-padding", Property.integer(5));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
int padding = 0; int padding = 0;
@@ -30,18 +25,37 @@ public class Scroll extends Element {
this.padding = padding; this.padding = padding;
} }
public void draw(Element element) {
ElementRenderer.draw(children, element);
}
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); calculateMaxScroll();
renderScope.drawRoundedRectangle(0, 0, width, height,
Theme.BG_RADIUS.value, Theme.BACKGROUND.value);
renderScope.enableScissor(padding, padding, width - padding, height - padding); renderScope.enableScissor(padding, padding, width - padding, height - padding);
renderScope.matrices.push(); renderScope.matrices.push();
renderScope.matrices.translate(padding, (-scroll) + padding, 0); renderScope.matrices.translate(padding, -scroll + padding, 0);
ElementRenderer.render(children, renderScope, ctx.mouseX - padding, ctx.mouseY - padding + scroll);
ElementRenderer.render(children, ctx.elapsed, renderScope,
ctx.mouseX - padding,
ctx.mouseY - padding + scroll);
renderScope.matrices.pop(); renderScope.matrices.pop();
renderScope.disableScissor(); renderScope.disableScissor();
renderScope.drawRoundedRectangle(width - scrollBarWidth.value - scrollBarPadding.value, calculateScrollBarY(), scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, scrollBarColor.value); if (maxScroll > 0) {
renderScope.drawRoundedRectangle(
width - scrollBarWidth.value - scrollBarPadding.value,
calculateScrollBarY(),
scrollBarWidth.value,
calculateScrollBarHeight(),
scrollBarRadius.value,
Theme.SCROLL.value);
}
} }
@Override @Override
@@ -54,15 +68,6 @@ public class Scroll extends Element {
} }
} }
@Override
public void click(int mouseX, int mouseY) {
ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, 0);
}
public void draw(Element element) {
children.add(element);
}
@Override @Override
public Element dimensions(int width, int height) { public Element dimensions(int width, int height) {
super.dimensions(width, height); super.dimensions(width, height);
@@ -76,8 +81,9 @@ public class Scroll extends Element {
} }
int calculateScrollBarHeight() { int calculateScrollBarHeight() {
if (maxScroll <= 0) return height - (scrollBarPadding.value * 2); // No scrolling needed if (maxScroll <= 0)
return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); // At least 20px tall return height - (scrollBarPadding.value * 2);
return Math.max(20, (height * height) / (height + maxScroll + 10)) - (scrollBarPadding.value * 2);
} }
int calculateScrollBarY() { int calculateScrollBarY() {
@@ -87,8 +93,38 @@ public class Scroll extends Element {
return Math.min(y, height - scrollBarHeight - scrollBarPadding.value); return Math.min(y, height - scrollBarHeight - scrollBarPadding.value);
} }
private void calculateMaxScroll() {
maxScroll = 0;
for (Element element : children) {
int bottom = element.y + element.height;
maxScroll = Math.max(maxScroll, bottom - height + padding);
}
if (maxScroll > 0) {
maxScroll = maxScroll + 10;
} else {
maxScroll = 0;
}
}
@Override @Override
public void keyPressed(int keyCode, int scanCode, int modifiers) { public void keyPressed(int keyCode, int scanCode, int modifiers) {
ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers); ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers);
} }
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, button);
}
@Override
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
ElementRenderer.mouseDragged(children, mouseX - padding, mouseY - padding + scroll, button, deltaX, deltaY);
}
@Override
public void mouseReleased(double mouseX, double mouseY, int button) {
ElementRenderer.mouseReleased(children, mouseX - padding, mouseY - padding + scroll, button);
}
} }
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,16 +9,6 @@ import org.saturnclient.ui2.Utils;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class TabMenu extends Element { public class TabMenu extends Element {
private static ThemeManager theme = new ThemeManager("TabMenu", "hovering", "selected");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0x00000000));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "icon-bg", -7643914);
}
public static class TabMenuComponent { public static class TabMenuComponent {
Runnable onClick; Runnable onClick;
Identifier sprite; Identifier sprite;
@@ -42,19 +31,21 @@ public class TabMenu extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value);
int col = 0; int col = 0;
for (TabMenuComponent component : components) { for (TabMenuComponent component : components) {
int x = 25 * col + 5; int x = 25 * col + 5;
if (ctx.isHovering(x, 5, 20, 20) || selectedScreen == col) {
theme.setState("hovering"); boolean hovering = ctx.isHovering(x, 5, 20, 20) || selectedScreen == col;
if (hovering) {
renderScope.drawRoundedRectangle(x, 5, 20, 20, Theme.WIDGET_RADIUS.value, Theme.ACCENT.value);
} }
renderScope.drawRoundedRectangle(x, 5, 20, 20, cornerRadius.value, iconBg.value); renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10,
renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10, iconFg.value); hovering ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
theme.setState(null);
col++; col++;
} }
} }
@@ -1,27 +1,22 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
public class Text extends Element { public class Text extends Element {
private static ThemeManager theme = new ThemeManager("Text");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
public static Property<Integer> font = theme.property("font", Property.font(1));
private String text; private String text;
public Text(String text) { public Text(String text) {
this.text = text; this.text = text;
width = Fonts.getWidth(text, font.value); width = Fonts.getWidth(text, Theme.FONT.value);
height = Fonts.getHeight(); height = Fonts.getHeight();
} }
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawText(this.text, 0, 0, font.value, fgColor.value); renderScope.drawText(this.text, 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
} }
} }
@@ -1,7 +1,6 @@
package org.saturnclient.ui2.elements; package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
@@ -10,19 +9,9 @@ import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class TextureButton extends Element { public class TextureButton extends Element {
private static ThemeManager theme = new ThemeManager("TextureButton", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> padding = theme.property("padding", Property.integer(14));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee);
}
Identifier sprite; Identifier sprite;
private Runnable onClick; private Runnable onClick;
static int padding = 14;
public TextureButton(Identifier sprite, Runnable onClick) { public TextureButton(Identifier sprite, Runnable onClick) {
this.sprite = sprite; this.sprite = sprite;
@@ -31,20 +20,16 @@ public class TextureButton extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
theme.setState("hovering"); ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.setRenderLayer(RenderLayer::getGuiTextured); renderScope.setRenderLayer(RenderLayer::getGuiTextured);
int texWidth = width - padding.value; int texWidth = width - padding;
int texHeight = height - padding.value; int texHeight = height - padding;
renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value); renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
} }
@Override @Override
@@ -0,0 +1,80 @@
package org.saturnclient.ui2.resources;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGUniverse;
import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
public class SvgTexture {
private static List<Identifier> svgCache = new ArrayList<>();
private static BufferedImage renderSvg(InputStream svgStream, int width, int height) throws Exception {
// Load SVG
SVGUniverse universe = new SVGUniverse();
java.net.URI uri = universe.loadSVG(svgStream, "tempSvg");
SVGDiagram diagram = universe.getDiagram(uri);
// Get intrinsic size of SVG
float svgWidth = (float) diagram.getWidth();
float svgHeight = (float) diagram.getHeight();
// Compute scale factors
float scaleX = width / svgWidth;
float scaleY = height / svgHeight;
// Create output image
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// Enable anti-aliasing for smooth scaling
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// Apply scaling transform
g.scale(scaleX, scaleY);
// Render SVG
diagram.render(g);
g.dispose();
return image;
}
@Nullable
public static Identifier getSvg(MinecraftClient client, Identifier svgImage, int width, int height) {
Identifier id = Identifier
.of(svgImage.toString().replaceAll("\\.svg$", (width + "_" + height).toString() + ".png"));
if (svgCache.contains(id)) {
return id;
}
try (InputStream svgStream = client
.getResourceManager()
.getResource(svgImage).get()
.getInputStream()) {
BufferedImage image = renderSvg(svgStream, width, height);
IdentifierUtils.registerBufferedImageTexture(id, image);
return id;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
@@ -28,6 +28,7 @@ public class Textures {
public static final Identifier REALISTIC_LOGO = Identifier.of("saturnclient", "textures/logo/realistic.png"); public static final Identifier REALISTIC_LOGO = Identifier.of("saturnclient", "textures/logo/realistic.png");
public static final Identifier LOGO_TEXT = Identifier.of("saturnclient", "textures/logo/text.png"); public static final Identifier LOGO_TEXT = Identifier.of("saturnclient", "textures/logo/text.png");
public static final Identifier LOGO_TEXT_BIG = Identifier.of("saturnclient", "textures/logo/text_big.png"); public static final Identifier LOGO_TEXT_BIG = Identifier.of("saturnclient", "textures/logo/text_big.png");
public static final Identifier SPLASH = Identifier.of("saturnclient", "textures/gui/splash.png");
// Widgets // Widgets
public static final Identifier BUTTON_BORDER = Identifier.of("saturnclient", "button_border"); public static final Identifier BUTTON_BORDER = Identifier.of("saturnclient", "button_border");
@@ -52,7 +53,7 @@ public class Textures {
} }
public static Identifier getModIcon(String modId) { public static Identifier getModIcon(String modId) {
return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".png"); return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".svg");
} }
} }
@@ -4,6 +4,7 @@ import java.util.Map;
import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.Sidebar; import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.components.inputs.FloatInput; import org.saturnclient.ui2.components.inputs.FloatInput;
@@ -59,14 +60,13 @@ public class ConfigEditor extends SaturnScreen {
if (prop.getType() == Property.PropertyType.NAMESPACE) { if (prop.getType() == Property.PropertyType.NAMESPACE) {
configScroll.draw( configScroll.draw(
new Text( new Text(
propName propName).position(Fonts.centerX(520, propName, Theme.FONT.value), modY + 1)
).position(Fonts.centerX(520, propName, Text.font.value), modY + 1).scale(0.7f)); .scale(0.7f));
} else { } else {
configScroll.draw( configScroll.draw(
new Text( new Text(
propName propName).position(modX, modY + 1).scale(0.7f));
).position(modX, modY + 1).scale(0.7f));
} }
switch (prop.getType()) { switch (prop.getType()) {
@@ -78,7 +78,7 @@ public class ConfigEditor extends SaturnScreen {
break; break;
case HEX: case HEX:
configScroll.draw(new HexInput((Property<Integer>) prop, this).position(w / 2, modY)); configScroll.draw(new HexInput((Property<Integer>) prop).position(w / 2, modY));
break; break;
case INTEGER: case INTEGER:
@@ -1,9 +1,8 @@
package org.saturnclient.ui2.screens; package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.config.SaturnClientConfig;
// import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.saturnclient.cosmetics.Emotes;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.Utils; import org.saturnclient.ui2.Utils;
@@ -71,7 +70,7 @@ public class EmoteWheel extends SaturnScreen {
animationStack.addAnimLayer(1000, animationStack.addAnimLayer(1000,
PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient",
emote)).playAnimation()); emote)).playAnimation());
Auth.emote(emote); ServiceClient.emote(emote);
close(); close();
}).dimensions(70, 70).centerOffset(width, height, -80 + (col * 80), -35 + (row * 80)) }).dimensions(70, 70).centerOffset(width, height, -80 + (col * 80), -35 + (row * 80))
.animation(new Fade(700))); .animation(new Fade(700)));
@@ -3,8 +3,10 @@ package org.saturnclient.ui2.screens;
import org.saturnclient.modules.ModManager; import org.saturnclient.modules.ModManager;
import org.saturnclient.modules.Module; import org.saturnclient.modules.Module;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.anim.SlideFade;
import org.saturnclient.ui2.components.SaturnModule; import org.saturnclient.ui2.components.SaturnModule;
import org.saturnclient.ui2.components.Sidebar; import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.elements.AnimationStagger;
import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.Scroll; import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
@@ -24,8 +26,11 @@ public class ModMenu extends SaturnScreen {
int row = 0; int row = 0;
int col = 0; int col = 0;
AnimationStagger moduleStagger = new AnimationStagger(80);
for (Module mod : ModManager.ALL_MODS) { for (Module mod : ModManager.ALL_MODS) {
scroll.draw(new SaturnModule(mod).position((160 + g) * col, (50 + g) * row)); moduleStagger.draw(
new SaturnModule(mod).position((160 + g) * col, (50 + g) * row).animation(new SlideFade(600, -12)));
if (col == 2) { if (col == 2) {
col = 0; col = 0;
@@ -35,6 +40,8 @@ public class ModMenu extends SaturnScreen {
} }
} }
scroll.draw(moduleStagger);
int scrollWidth = 480 + 10 + (g * 2) + (p * 2); int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).center(width, height)); draw(scroll.dimensions(scrollWidth, 350).center(width, height));
@@ -1,12 +1,12 @@
package org.saturnclient.ui2.screens; package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.config.SaturnClientConfig;
import org.saturnclient.saturnclient.menus.HudEditor; import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.screens.cosmetics.CloakMenu; import org.saturnclient.ui2.screens.cosmetics.CloakMenu;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.elements.AnimationStagger;
import org.saturnclient.ui2.elements.Button; import org.saturnclient.ui2.elements.Button;
import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.TextureButton; import org.saturnclient.ui2.elements.TextureButton;
@@ -20,21 +20,42 @@ public class ShiftMenu extends SaturnScreen {
@Override @Override
public void ui() { public void ui() {
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -36).animation(new Fade(700))); // Logo animations (unchanged)
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -80).animation(new SlideY(700, -20))); draw(new ImageTexture(Textures.LOGO_TEXT)
.dimensions(98, 10)
.centerOffset(width, height, 0, -36)
.animation(new Fade(700)));
Element button = new Button("Settings", () -> { draw(new ImageTexture(SaturnClientConfig.getLogo())
SaturnClient.client.setScreen(new ModMenu()); .dimensions(98, 98)
}).center(width, height); .centerOffset(width, height, 0, -80)
.animation(new SlideY(700, -20)));
draw(button); // Spacing and sizes
int btnWidth = 120;
int btnHeight = 40;
int spacing = 4;
draw(new TextureButton(Textures.COSMETICS, () -> { // Create stagger
client.setScreen(new CloakMenu()); AnimationStagger stagger = new AnimationStagger(120);
}).dimensions(button.height, button.height).centerOffset(width, height, (button.width / 2) + (button.height / 2) + 4, 0));
draw(new TextureButton(Textures.HUD_ICON, () -> { stagger.draw(new TextureButton(Textures.HUD_ICON, () -> {
client.setScreen(new HudEditor()); client.setScreen(new HudEditor());
}).dimensions(button.height, button.height).centerOffset(width, height, -((button.width / 2) + (button.height / 2) + 4), 0)); }).dimensions(btnHeight, btnHeight).position(0, 0)
.animation(new Fade(300)));
stagger.draw(new Button("Settings", () -> {
SaturnClient.client.setScreen(new ModMenu());
}).dimensions(btnWidth, btnHeight).position(btnHeight + spacing, 0)
.animation(new Fade(300)));
stagger.draw(new TextureButton(Textures.COSMETICS, () -> {
client.setScreen(new CloakMenu());
}).dimensions(btnHeight, btnHeight).position(btnWidth + btnHeight + (spacing * 2), 0)
.animation(new Fade(300)));
// Draw the stagger at the absolute center of the screen
draw(stagger.dimensions(btnWidth + (btnHeight * 2) + (spacing * 2), btnHeight).centerOffset(width, height, 0,
0));
} }
} }
@@ -1,13 +1,15 @@
package org.saturnclient.ui2.screens; package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.config.SaturnClientConfig;
import org.saturnclient.saturnclient.menus.HudEditor; import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.screens.cosmetics.CloakMenu; import org.saturnclient.ui2.screens.cosmetics.CloakMenu;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.anim.Fade; import org.saturnclient.ui2.anim.Fade;
import org.saturnclient.ui2.anim.SlideFade;
import org.saturnclient.ui2.anim.SlideY; import org.saturnclient.ui2.anim.SlideY;
import org.saturnclient.ui2.elements.AnimationStagger;
import org.saturnclient.ui2.elements.Button; import org.saturnclient.ui2.elements.Button;
import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.TextureButton; import org.saturnclient.ui2.elements.TextureButton;
@@ -19,44 +21,54 @@ import net.minecraft.client.gui.screen.world.SelectWorldScreen;
public class TitleMenu extends SaturnScreen { public class TitleMenu extends SaturnScreen {
public TitleMenu() { public TitleMenu() {
super("Tile Menu"); super("Title Menu");
} }
@Override @Override
public void ui() { public void ui() {
backgroundBlur = 0; backgroundBlur = 0;
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -36).animation(new Fade(700))); draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -36)
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -82).animation(new SlideY(700, -20))); .animation(new Fade(700)));
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -82)
.animation(new SlideY(700, -20)));
draw(new Button("SINGLEPLAYER", () -> { AnimationStagger mainButtonStagger = new AnimationStagger(120);
mainButtonStagger.draw(new Button("SINGLEPLAYER", () -> {
client.setScreen(new SelectWorldScreen(new TitleMenu())); client.setScreen(new SelectWorldScreen(new TitleMenu()));
}).scale(0.69f).dimensions(420, 52).centerOffset(width, height, 0, 0)); }).scale(0.69f).dimensions(420, 52).position(0, 0).animation(new SlideFade(1000, -10)));
draw(new Button("MULTIPLAYER", () -> { mainButtonStagger.draw(new Button("MULTIPLAYER", () -> {
client.setScreen(new MultiplayerScreen(new TitleMenu())); client.setScreen(new MultiplayerScreen(new TitleMenu()));
}).scale(0.69f).dimensions(420, 52).centerOffset(width, height, 0, 40)); }).scale(0.69f).dimensions(420, 52).position(0, 40).animation(new SlideFade(1000, -10)));
draw(new Button("OPTIONS", () -> { mainButtonStagger.draw(new Button("OPTIONS", () -> {
client.setScreen(new OptionsScreen(new TitleMenu(), SaturnClient.client.options)); client.setScreen(new OptionsScreen(new TitleMenu(), SaturnClient.client.options));
}).scale(0.69f).dimensions(206, 52).centerOffset(width, height, -73, 80)); }).scale(0.69f).dimensions(206, 52).position(0, 80).animation(new SlideFade(1000, -10)));
draw(new Button("QUIT", () -> { mainButtonStagger.draw(new Button("QUIT", () -> {
client.scheduleStop(); client.scheduleStop();
}).scale(0.69f).dimensions(206, 52).centerOffset(width, height, 73, 80)); }).scale(0.69f).dimensions(206, 52).position(146, 80).animation(new SlideFade(1000, -10)));
draw(mainButtonStagger.dimensions(289, 143).centerOffset(width, height, 0, 60));
int s = Fonts.getHeight() + 20; int s = Fonts.getHeight() + 20;
draw(new TextureButton(Textures.HUD_ICON, () -> { AnimationStagger saturnStagger = new AnimationStagger(180);
saturnStagger.draw(new TextureButton(Textures.HUD_ICON, () -> {
client.setScreen(new HudEditor()); client.setScreen(new HudEditor());
}).dimensions(s, s).centerHorizontal(width, height, -(s + 2), 8).animation(new SlideY(700, s + 8))); }).dimensions(s, s).position(0, 0).animation(new SlideFade(1000, s + 8)));
draw(new TextureButton(Textures.SETTINGS, () -> { saturnStagger.draw(new TextureButton(Textures.SETTINGS, () -> {
SaturnClient.client.setScreen(new ModMenu()); SaturnClient.client.setScreen(new ModMenu());
}).dimensions(s, s).centerHorizontal(width, height, 0, 8).animation(new SlideY(700, s + 8))); }).dimensions(s, s).position(s + 2, 0).animation(new SlideFade(1000, s + 8)));
draw(new TextureButton(Textures.CLOAK, () -> { saturnStagger.draw(new TextureButton(Textures.CLOAK, () -> {
client.setScreen(new CloakMenu()); client.setScreen(new CloakMenu());
}).dimensions(s, s).centerHorizontal(width, height, s + 2, 8).animation(new SlideY(700, s + 8))); }).dimensions(s, s).position((s + 2) * 2, 0).animation(new SlideFade(1000, s + 8)));
draw(saturnStagger.dimensions(((s + 2) * 2) + s, s).centerHorizontal(width, height, 0, 8));
} }
} }
@@ -1,8 +1,7 @@
package org.saturnclient.ui2.screens.cosmetics; package org.saturnclient.ui2.screens.cosmetics;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
@@ -28,7 +27,7 @@ public class CloakMenu extends SaturnScreen {
int row = 0; int row = 0;
int col = 0; int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid); SaturnPlayer player = SaturnPlayer.get();
if (player != null) { if (player != null) {
for (String cloak : Cloaks.availableCloaks) { for (String cloak : Cloaks.availableCloaks) {
@@ -49,15 +48,15 @@ public class CloakMenu extends SaturnScreen {
draw(scroll.dimensions(scrollWidth, 350).center(width, height)); draw(scroll.dimensions(scrollWidth, 350).center(width, height));
draw(new SkinPreview(-30.0f, false).scale(3.5f).position(scroll.x + (scrollWidth - 220), scroll.y+40)); draw(new SkinPreview(-30.0f, false).scale(3.5f).position(scroll.x + (scrollWidth - 220), scroll.y + 40));
draw(new Sidebar(2, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0)); draw(new Sidebar(2, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0));
draw(new TabMenu(0, draw(new TabMenu(0,
new TabMenuComponent(Textures.CLOAK, () -> {}), new TabMenuComponent(Textures.CLOAK, () -> {
new TabMenuComponent(Textures.HAT, () -> { }),
SaturnClient.client.setScreen(new HatMenu()); new TabMenuComponent(Textures.HAT, () -> {
}) SaturnClient.client.setScreen(new HatMenu());
).centerOffset(width, height, 0, -195)); })).centerOffset(width, height, 0, -195));
} }
} }
@@ -1,8 +1,7 @@
package org.saturnclient.ui2.screens.cosmetics; package org.saturnclient.ui2.screens.cosmetics;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
@@ -28,12 +27,12 @@ public class HatMenu extends SaturnScreen {
int row = 0; int row = 0;
int col = 0; int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid); SaturnPlayer player = SaturnPlayer.get();
if (player != null) { if (player != null) {
for (String hat : Hats.availableHats) { for (String hat : Hats.availableHats) {
scroll.draw(new CosmeticPreview(hat == player.hat, Textures.getHatPreview(hat), () -> { scroll.draw(new CosmeticPreview(hat == player.hat, Textures.getHatPreview(hat), () -> {
Hats.setHat(Auth.uuid, hat); Hats.setHat(hat);
}).dimensions(50, 50).position((50 + g) * col, (50 + g) * row)); }).dimensions(50, 50).position((50 + g) * col, (50 + g) * row));
if (col == 5) { if (col == 5) {
@@ -49,15 +48,15 @@ public class HatMenu extends SaturnScreen {
draw(scroll.dimensions(scrollWidth, 350).center(width, height)); draw(scroll.dimensions(scrollWidth, 350).center(width, height));
draw(new SkinPreview(170f, true).scale(3.5f).position(scroll.x + (scrollWidth - 220), scroll.y+40)); draw(new SkinPreview(170f, true).scale(3.5f).position(scroll.x + (scrollWidth - 220), scroll.y + 40));
draw(new Sidebar(2, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0)); draw(new Sidebar(2, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0));
draw(new TabMenu(1, draw(new TabMenu(1,
new TabMenuComponent(Textures.CLOAK, () -> { new TabMenuComponent(Textures.CLOAK, () -> {
SaturnClient.client.setScreen(new CloakMenu()); SaturnClient.client.setScreen(new CloakMenu());
}), }),
new TabMenuComponent(Textures.HAT, () -> {}) new TabMenuComponent(Textures.HAT, () -> {
).centerOffset(width, height, 0, -195)); })).centerOffset(width, height, 0, -195));
} }
} }
@@ -1,8 +1,9 @@
package org.saturnclient.ui2.screens.store; package org.saturnclient.ui2.screens.store;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.auth.SaturnPlayer; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.Utils; import org.saturnclient.ui2.Utils;
@@ -42,7 +43,7 @@ public class CloakStore extends SaturnScreen {
int row = 0; int row = 0;
int col = 0; int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid); SaturnPlayer player = SaturnPlayer.get();
if (player != null) { if (player != null) {
for (String cloak : Cloaks.ALL_CLOAKS) { for (String cloak : Cloaks.ALL_CLOAKS) {
@@ -54,7 +55,7 @@ public class CloakStore extends SaturnScreen {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (now - lastPurchaseTime >= 3000) { if (now - lastPurchaseTime >= 3000) {
Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait"); Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait");
Auth.buyCloak(cloak); ServiceClient.buyCloak(cloak);
lastPurchaseTime = now; lastPurchaseTime = now;
new Thread(() -> { new Thread(() -> {
try { try {
@@ -75,10 +76,10 @@ public class CloakStore extends SaturnScreen {
String t = "100"; String t = "100";
scroll.draw(new Text(t).position(x + Fonts.centerX(50, t, Text.font.value), y + 113).scale(0.5f)); scroll.draw(new Text(t).position(x + Fonts.centerX(50, t, Theme.FONT.value), y + 113).scale(0.5f));
scroll.draw(new ImageTexture(Textures.COINS).dimensions(16, 16) scroll.draw(new ImageTexture(Textures.COINS).dimensions(16, 16)
.position(x + Fonts.getWidth(t, Text.font.value) - 1, y + 112).scale(0.5f)); .position(x + Fonts.getWidth(t, Theme.FONT.value) - 1, y + 112).scale(0.5f));
if (col == 8) { if (col == 8) {
col = 0; col = 0;
@@ -1,8 +1,9 @@
package org.saturnclient.ui2.screens.store; package org.saturnclient.ui2.screens.store;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.ServiceClient;
import org.saturnclient.saturnclient.auth.SaturnPlayer; import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.config.Theme;
import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.Utils; import org.saturnclient.ui2.Utils;
@@ -42,7 +43,7 @@ public class HatStore extends SaturnScreen {
int row = 0; int row = 0;
int col = 0; int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid); SaturnPlayer player = SaturnPlayer.get();
if (player != null) { if (player != null) {
for (String hat : Hats.ALL_HATS) { for (String hat : Hats.ALL_HATS) {
@@ -54,7 +55,7 @@ public class HatStore extends SaturnScreen {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (now - lastPurchaseTime >= 3000) { if (now - lastPurchaseTime >= 3000) {
Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait"); Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait");
Auth.buyHat(hat); ServiceClient.buyHat(hat);
lastPurchaseTime = now; lastPurchaseTime = now;
new Thread(() -> { new Thread(() -> {
try { try {
@@ -75,10 +76,10 @@ public class HatStore extends SaturnScreen {
String t = "50"; String t = "50";
scroll.draw(new Text(t).position(x + Fonts.centerX(50, t, Text.font.value), y + 53).scale(0.5f)); scroll.draw(new Text(t).position(x + Fonts.centerX(50, t, Theme.FONT.value), y + 53).scale(0.5f));
scroll.draw(new ImageTexture(Textures.COINS).dimensions(16, 16) scroll.draw(new ImageTexture(Textures.COINS).dimensions(16, 16)
.position(x + Fonts.getWidth(t, Text.font.value) + 4, y + 52).scale(0.5f)); .position(x + Fonts.getWidth(t, Theme.FONT.value) + 4, y + 52).scale(0.5f));
if (col == 8) { if (col == 8) {
col = 0; col = 0;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-icon lucide-shield"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-move3d-icon lucide-move-3d"><path d="M5 3v16h16"/><path d="m5 19 6-6"/><path d="m2 6 3-3 3 3"/><path d="m18 16 3 3-3 3"/></svg>

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus-icon lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days-icon lucide-calendar-days"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles-icon lucide-sparkles"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg>

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chart-area-icon lucide-chart-area"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z"/></svg>

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-switch-camera-icon lucide-switch-camera"><path d="M11 19H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"/><path d="M13 5h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-5"/><circle cx="12" cy="12" r="3"/><path d="m18 22-3-3 3-3"/><path d="m6 2 3 3-3 3"/></svg>

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun-icon lucide-sun"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heart-icon lucide-heart"><path d="M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"/></svg>

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-keyboard-icon lucide-keyboard"><path d="M10 8h.01"/><path d="M12 12h.01"/><path d="M14 8h.01"/><path d="M16 12h.01"/><path d="M18 8h.01"/><path d="M6 8h.01"/><path d="M7 16h10"/><path d="M8 12h.01"/><rect width="20" height="16" x="2" y="4" rx="2"/></svg>

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-fog-icon lucide-cloud-fog"><path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"/><path d="M16 17H7"/><path d="M17 21H9"/></svg>

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wand-sparkles-icon lucide-wand-sparkles"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72"/><path d="m14 7 3 3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10 2v2"/><path d="M7 8H3"/><path d="M21 16h-4"/><path d="M11 3H9"/></svg>

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gauge-icon lucide-gauge"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

@@ -0,0 +1,35 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1167.000000pt" height="1280.000000pt" viewBox="0 0 1167.000000 1280.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.15, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,1280.000000) scale(0.100000,-0.100000)"
fill="white" stroke="none">
<path d="M9270 12789 c-229 -29 -473 -124 -657 -256 -272 -194 -468 -494 -539
-825 -25 -114 -25 -369 -1 -491 94 -472 445 -861 918 -1021 167 -56 260 -70
459 -70 199 0 292 14 459 70 400 135 727 449 864 829 50 141 70 257 71 425 1
176 -11 259 -59 406 -165 505 -614 864 -1166 934 -96 12 -254 11 -349 -1z"/>
<path d="M5145 10889 c-80 -12 -193 -69 -252 -127 -42 -41 -1667 -2180 -1780
-2342 -24 -35 -54 -97 -69 -143 -23 -69 -27 -100 -27 -197 -1 -103 2 -124 29
-200 70 -200 212 -329 394 -362 128 -22 284 28 378 123 27 27 397 512 823
1078 l774 1030 443 0 443 1 -19 -27 c-11 -16 -769 -1026 -1684 -2246 l-1664
-2219 -1215 -1 -1214 -2 -68 -27 c-93 -38 -164 -87 -239 -166 -323 -343 -232
-974 171 -1186 134 -70 29 -66 1631 -66 1549 0 1541 0 1705 51 124 38 264 118
331 190 18 19 321 405 674 859 354 454 648 831 655 838 10 11 162 -154 768
-832 417 -465 757 -852 757 -860 0 -9 -140 -728 -311 -1598 -223 -1137 -312
-1611 -316 -1681 -21 -363 196 -688 508 -762 302 -71 612 129 718 465 11 36
180 886 376 1890 195 1004 376 1926 401 2049 40 196 45 239 46 355 0 197 -40
357 -124 486 -27 42 -731 859 -1325 1537 l-55 62 728 968 c400 532 728 969
729 971 1 1 211 -240 467 -535 256 -296 489 -560 517 -586 63 -59 178 -115
260 -127 144 -20 301 37 430 156 98 91 1610 1844 1649 1912 57 100 77 181 76
317 0 87 -5 137 -18 180 -49 163 -154 293 -292 361 -77 39 -79 39 -193 39
-104 -1 -122 -3 -174 -27 -32 -15 -77 -41 -100 -58 -24 -17 -328 -358 -677
-760 -349 -401 -638 -728 -641 -727 -4 1 -354 402 -780 891 -440 506 -796 907
-827 931 -30 24 -80 55 -111 71 -127 62 -38 58 -1401 60 -685 1 -1272 -2
-1305 -7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

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