From 08c001afbbfd9cd53dbf462b5d5290cda376b150 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 19 Feb 2026 23:21:03 +0100 Subject: [PATCH 01/82] Session build --- build.gradle | 17 +++++++---------- .../saturnclient/saturnclient/auth/Network.java | 3 ++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index d99ed5d..63a7a8d 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,8 @@ dependencies { modApi "dev.felnull:special-model-loader:1.4.0+pre.1" include "dev.felnull:special-model-loader:1.4.0+pre.1" + implementation 'com.github.selimaj-dev:session-java:v0.1.0' + include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") //You might want bendy-lib. playerAnimator will wrap it. // include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}") @@ -27,16 +29,11 @@ dependencies { repositories { - maven { url "https://maven.parchmentmc.org" } - maven { - name = "FelNull Maven" - url = "https://maven.felnull.dev" - } - - maven { - name "KosmX's maven" - url 'https://maven.kosmx.dev/' - } + maven { url 'https://maven.fabricmc.net/' } // Fabric's official repo + maven { url 'https://jitpack.io' } + maven { url "https://maven.parchmentmc.org" } + maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" } + maven { name = "KosmX maven"; url = 'https://maven.kosmx.dev/' } } processResources { diff --git a/src/main/java/org/saturnclient/saturnclient/auth/Network.java b/src/main/java/org/saturnclient/saturnclient/auth/Network.java index 507f857..d3f2346 100644 --- a/src/main/java/org/saturnclient/saturnclient/auth/Network.java +++ b/src/main/java/org/saturnclient/saturnclient/auth/Network.java @@ -10,6 +10,7 @@ import java.security.interfaces.*; import java.security.spec.X509EncodedKeySpec; public class Network { + // static Session session; static Socket socket; static PrintWriter out; static BufferedReader in; @@ -42,7 +43,7 @@ public class Network { socket = new Socket(); socket.connect(new InetSocketAddress("77.247.92.168", 8080), CONNECTION_TIMEOUT); - + socket.setSoTimeout(READ_TIMEOUT); // out = new DataOutputStream(socket.getOutputStream()); From 418c47f48362f2e5c1b41cdf3c1acf5b7ef2ff1d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 00:31:19 +0100 Subject: [PATCH 02/82] New structure --- .../saturnclient/saturnclient/auth/Auth.java | 283 ------------------ .../saturnclient/auth/Network.java | 106 ------- .../saturnclient/auth/SaturnParser.java | 71 ----- .../saturnclient/client/ServiceClient.java | 68 +++++ .../saturnclient/client/ServiceMethods.java | 5 + .../player}/PlayerTracker.java | 8 +- .../{auth => client/player}/SaturnPlayer.java | 2 +- .../cosmetics/HatFeatureRenderer.java | 3 +- .../saturnclient/cosmetics/Hats.java | 3 +- .../saturnclient/cosmetics/cloaks/Cloaks.java | 2 +- .../ui2/screens/cosmetics/CloakMenu.java | 2 +- .../ui2/screens/cosmetics/HatMenu.java | 2 +- .../ui2/screens/store/CloakStore.java | 2 +- .../ui2/screens/store/HatStore.java | 2 +- 14 files changed, 86 insertions(+), 473 deletions(-) delete mode 100644 src/main/java/org/saturnclient/saturnclient/auth/Auth.java delete mode 100644 src/main/java/org/saturnclient/saturnclient/auth/Network.java delete mode 100644 src/main/java/org/saturnclient/saturnclient/auth/SaturnParser.java create mode 100644 src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java create mode 100644 src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java rename src/main/java/org/saturnclient/saturnclient/{auth => client/player}/PlayerTracker.java (89%) rename src/main/java/org/saturnclient/saturnclient/{auth => client/player}/SaturnPlayer.java (79%) diff --git a/src/main/java/org/saturnclient/saturnclient/auth/Auth.java b/src/main/java/org/saturnclient/saturnclient/auth/Auth.java deleted file mode 100644 index 3bcd7af..0000000 --- a/src/main/java/org/saturnclient/saturnclient/auth/Auth.java +++ /dev/null @@ -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 players = new HashMap<>(); - public static Map 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); - } - } -} diff --git a/src/main/java/org/saturnclient/saturnclient/auth/Network.java b/src/main/java/org/saturnclient/saturnclient/auth/Network.java deleted file mode 100644 index d3f2346..0000000 --- a/src/main/java/org/saturnclient/saturnclient/auth/Network.java +++ /dev/null @@ -1,106 +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 Session session; - 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) { - } - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/auth/SaturnParser.java b/src/main/java/org/saturnclient/saturnclient/auth/SaturnParser.java deleted file mode 100644 index 60d1c31..0000000 --- a/src/main/java/org/saturnclient/saturnclient/auth/SaturnParser.java +++ /dev/null @@ -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 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); - } - } -} diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java new file mode 100644 index 0000000..0809ad3 --- /dev/null +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -0,0 +1,68 @@ +package org.saturnclient.saturnclient.client; + +import java.net.http.WebSocket; +import java.util.UUID; + +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.saturnclient.cosmetics.Hats; +import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; + +import dev.selimaj.session.Session; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; + +public class ServiceClient { + private static Session session; + public static UUID uuid; + + 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(); + + SaturnClient.LOGGER.info("Authenticating with UUID: " + accessToken); + + // 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; + } + } +} diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java new file mode 100644 index 0000000..c9065c8 --- /dev/null +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -0,0 +1,5 @@ +package org.saturnclient.saturnclient.client; + +public class ServiceMethods { + +} diff --git a/src/main/java/org/saturnclient/saturnclient/auth/PlayerTracker.java b/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java similarity index 89% rename from src/main/java/org/saturnclient/saturnclient/auth/PlayerTracker.java rename to src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java index a6aff97..308fb3a 100644 --- a/src/main/java/org/saturnclient/saturnclient/auth/PlayerTracker.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java @@ -1,4 +1,4 @@ -package org.saturnclient.saturnclient.auth; +package org.saturnclient.saturnclient.client.player; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; @@ -45,8 +45,8 @@ public class PlayerTracker { private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) { GameProfile profile = player.getProfile(); - String uuid = profile.getId().toString(); - if (uuid != Auth.uuid) - Auth.player(profile.getName(), uuid); + UUID uuid = profile.getId(); + // if (uuid != ServiceClient.uuid) + // ServiceClient.player(profile.getName(), uuid); } } diff --git a/src/main/java/org/saturnclient/saturnclient/auth/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java similarity index 79% rename from src/main/java/org/saturnclient/saturnclient/auth/SaturnPlayer.java rename to src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index 8ffe19e..5bfd6a1 100644 --- a/src/main/java/org/saturnclient/saturnclient/auth/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -1,4 +1,4 @@ -package org.saturnclient.saturnclient.auth; +package org.saturnclient.saturnclient.client.player; public class SaturnPlayer { public String cloak = ""; diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java index 422e2cc..de91606 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java @@ -1,8 +1,7 @@ package org.saturnclient.saturnclient.cosmetics; -import org.saturnclient.saturnclient.auth.SaturnPlayer; import org.saturnclient.saturnclient.auth.Auth; - +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.SaturnClient; import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.VertexConsumerProvider; diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java index ea30e6f..24de9d2 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java @@ -2,8 +2,9 @@ package org.saturnclient.saturnclient.cosmetics; import java.util.ArrayList; import java.util.List; -import org.saturnclient.saturnclient.auth.SaturnPlayer; + import org.saturnclient.saturnclient.auth.Auth; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; public class Hats { public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" }; diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java index 556a7c4..4a31396 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java @@ -2,9 +2,9 @@ package org.saturnclient.saturnclient.cosmetics.cloaks; import net.minecraft.util.Identifier; import org.saturnclient.saturnclient.auth.Auth; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils; -import org.saturnclient.saturnclient.auth.SaturnPlayer; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; diff --git a/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java b/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java index aae0b06..5261827 100644 --- a/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java @@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.cosmetics; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.auth.Auth; -import org.saturnclient.saturnclient.auth.SaturnPlayer; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.SaturnScreen; diff --git a/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java b/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java index d81a9cb..978ec90 100644 --- a/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java @@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.cosmetics; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.auth.Auth; -import org.saturnclient.saturnclient.auth.SaturnPlayer; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.SaturnScreen; diff --git a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java index e400320..848e598 100644 --- a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java +++ b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java @@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.store; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.auth.Auth; -import org.saturnclient.saturnclient.auth.SaturnPlayer; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.Utils; diff --git a/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java b/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java index 579ffa5..bb06249 100644 --- a/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java +++ b/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java @@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.store; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.auth.Auth; -import org.saturnclient.saturnclient.auth.SaturnPlayer; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.Utils; From 5d8068c964104938592af6b07f4ebe81a56b9ef5 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 01:44:03 +0100 Subject: [PATCH 03/82] Before switching session versions --- .../saturnclient/client/ServiceClient.java | 43 ++++++++++++++----- .../saturnclient/client/ServiceMethods.java | 35 ++++++++++++++- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 0809ad3..d5ff6af 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -1,11 +1,14 @@ package org.saturnclient.saturnclient.client; -import java.net.http.WebSocket; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.cosmetics.Hats; -import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import dev.selimaj.session.Session; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; @@ -14,6 +17,29 @@ public class ServiceClient { private static Session session; public static UUID uuid; + public static boolean connectTimeout() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + + Callable task = () -> Session.connect("ws://localhost:3000"); + + Future future = executor.submit(task); + + try { + session = future.get(10, TimeUnit.SECONDS); + System.out.println("Connected!"); + return true; + } catch (TimeoutException e) { + System.out.println("Connection timed out!"); + future.cancel(true); + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + executor.shutdown(); + } + } + public static boolean authenticate() { ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> { if (session != null) @@ -37,6 +63,10 @@ public class ServiceClient { SaturnClient.LOGGER.info("Authenticating with UUID: " + accessToken); + if (!connectTimeout()) { + return false; + } + // String cloak = parser.getString("cloak"); // String hat = parser.getString("hat"); @@ -52,13 +82,6 @@ public class ServiceClient { // Cloaks.loadCloak(uuid); - // playerNames.put(SaturnClient.client.getSession().getUsername(), uuid); - - // afterAuth(); - - // startListenerThread(); - // startPingThread(); - return true; } catch (Exception e) { SaturnClient.LOGGER.error("Authentication failed", e); diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index c9065c8..9c5b6ac 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -1,5 +1,36 @@ package org.saturnclient.saturnclient.client; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.fasterxml.jackson.databind.node.TextNode; + +import dev.selimaj.session.types.Method; + public class ServiceMethods { - -} + public static class AuthResponse extends JsonNode { + @JsonProperty(required = true) + private String token; + + @JsonProperty(required = true) + private String userId; + + // Constructor enforcing required fields + public AuthResponse(String token, String userId) { + this.token = token; + this.userId = userId; + } + + // Default constructor needed for Jackson deserialization + public AuthResponse() { + } + + @Override + public JsonNodeType getNodeType() { + return JsonNodeType.OBJECT; + } + } + + public static final Method Authenticate = new Method<>("auth", + TextNode.class, TextNode.class, TextNode.class); +} \ No newline at end of file From 57c1d21407206e7230f2136b23d84bad3325e774 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 02:15:15 +0100 Subject: [PATCH 04/82] Updated session version --- build.gradle | 2 +- .../saturnclient/client/ServiceMethods.java | 31 +++---------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/build.gradle b/build.gradle index 63a7a8d..83ef670 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ dependencies { modApi "dev.felnull:special-model-loader:1.4.0+pre.1" include "dev.felnull:special-model-loader:1.4.0+pre.1" - implementation 'com.github.selimaj-dev:session-java:v0.1.0' + implementation 'com.github.selimaj-dev:session-java:0.1.3' include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") //You might want bendy-lib. playerAnimator will wrap it. diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index 9c5b6ac..bd61c1b 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -1,36 +1,15 @@ package org.saturnclient.saturnclient.client; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.TextNode; import dev.selimaj.session.types.Method; public class ServiceMethods { - public static class AuthResponse extends JsonNode { - @JsonProperty(required = true) - private String token; - - @JsonProperty(required = true) - private String userId; - - // Constructor enforcing required fields - public AuthResponse(String token, String userId) { - this.token = token; - this.userId = userId; + public record AuthResponse( + String username, + String password) { } - // Default constructor needed for Jackson deserialization - public AuthResponse() { - } - - @Override - public JsonNodeType getNodeType() { - return JsonNodeType.OBJECT; - } - } - - public static final Method Authenticate = new Method<>("auth", - TextNode.class, TextNode.class, TextNode.class); + public static final Method Authenticate = new Method<>("auth", + TextNode.class, AuthResponse.class, TextNode.class); } \ No newline at end of file From 96f201cdb44a93e6970c15efab3692aecb76e853 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 02:45:42 +0100 Subject: [PATCH 05/82] Auth --- build.gradle | 2 +- .../saturnclient/client/ServiceClient.java | 39 +++++++------------ .../saturnclient/client/ServiceMethods.java | 12 ++---- .../client/player/SaturnPlayer.java | 24 ++++++++++++ .../cosmetics/CloakFeatureRenderer.java | 1 - .../saturnclient/cosmetics/cloaks/Cloaks.java | 11 +++--- 6 files changed, 49 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index 83ef670..3fcc938 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ dependencies { modApi "dev.felnull:special-model-loader:1.4.0+pre.1" include "dev.felnull:special-model-loader:1.4.0+pre.1" - implementation 'com.github.selimaj-dev:session-java:0.1.3' + implementation 'com.github.selimaj-dev:session-java:0.1.4' include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") //You might want bendy-lib. playerAnimator will wrap it. diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index d5ff6af..6b8595c 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -9,6 +9,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; 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.selimaj.session.Session; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; @@ -18,25 +21,11 @@ public class ServiceClient { public static UUID uuid; public static boolean connectTimeout() { - ExecutorService executor = Executors.newSingleThreadExecutor(); - - Callable task = () -> Session.connect("ws://localhost:3000"); - - Future future = executor.submit(task); - try { - session = future.get(10, TimeUnit.SECONDS); - System.out.println("Connected!"); + session = Session.connect("ws://localhost:3000", 0, null); return true; - } catch (TimeoutException e) { - System.out.println("Connection timed out!"); - future.cancel(true); - return false; } catch (Exception e) { - e.printStackTrace(); return false; - } finally { - executor.shutdown(); } } @@ -64,23 +53,23 @@ public class ServiceClient { SaturnClient.LOGGER.info("Authenticating with UUID: " + accessToken); if (!connectTimeout()) { + SaturnClient.LOGGER.error("Unable to authenticate: Session Server Timeout"); return false; } - // String cloak = parser.getString("cloak"); - // String hat = parser.getString("hat"); + ServiceMethods.AuthResponse response = session.request(ServiceMethods.Authenticate, accessToken).get(); - // for (String availableCloak : parser.getArray("cloaks")) { - // Cloaks.availableCloaks.add(availableCloak); - // } + for (String availableCloak : response.cloaks()) { + Cloaks.availableCloaks.add(availableCloak); + } - // for (String availableHat : parser.getArray("hats")) { - // Hats.availableHats.add(availableHat); - // } + for (String availableHat : response.hats()) { + Hats.availableHats.add(availableHat); + } - // players.put(uuid, new SaturnPlayer(cloak, hat)); + SaturnPlayer.set(uuid, new SaturnPlayer(response.cloak(), response.hat())); - // Cloaks.loadCloak(uuid); + Cloaks.loadCloak(uuid); return true; } catch (Exception e) { diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index bd61c1b..6f3a1c4 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -1,15 +1,11 @@ package org.saturnclient.saturnclient.client; -import com.fasterxml.jackson.databind.node.TextNode; - import dev.selimaj.session.types.Method; public class ServiceMethods { - public record AuthResponse( - String username, - String password) { - } + public record AuthResponse(String cloak, String hat, String[] cloaks, String[] hats) { + } - public static final Method Authenticate = new Method<>("auth", - TextNode.class, AuthResponse.class, TextNode.class); + public static final Method Authenticate = new Method<>("auth", + String.class, AuthResponse.class, String.class); } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index 5bfd6a1..551f245 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -1,6 +1,12 @@ package org.saturnclient.saturnclient.client.player; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + public class SaturnPlayer { + private static Map PLAYERS = new HashMap<>(); + public String cloak = ""; public String hat = ""; @@ -8,4 +14,22 @@ public class SaturnPlayer { this.cloak = cloak; this.hat = hat; } + + public static SaturnPlayer get(UUID uuid) { + return PLAYERS.get(uuid); + } + + public static void set(UUID uuid, SaturnPlayer player) { + PLAYERS.put(uuid, player); + } + + public static void player(UUID uuid, SaturnPlayer player) { + if (PLAYERS.containsKey(uuid)) { + return; + } + + // If offline then leave + + PLAYERS.put(uuid, player); + } } diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java index 6ce1401..8137649 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java @@ -1,7 +1,6 @@ package org.saturnclient.saturnclient.cosmetics; import org.saturnclient.saturnclient.SaturnClientConfig; -import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import net.minecraft.client.render.OverlayTexture; diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java index 4a31396..db0d24b 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java @@ -32,7 +32,7 @@ public class Cloaks { private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/"; public static final List availableCloaks = new ArrayList<>(); public static Identifier cloakCacheIdentifier = null; - public static final Map> animatedCloaks = new ConcurrentHashMap<>(); + public static final Map> animatedCloaks = new ConcurrentHashMap<>(); private static final Map lastFrameTime = new ConcurrentHashMap<>(); private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool( @@ -99,8 +99,9 @@ public class Cloaks { * * @param cloakName Name of the cloak file to load */ - public static void loadCloak(String uuid) { - SaturnPlayer player = Auth.players.get(uuid); + public static void loadCloak(UUID uuid) { + SaturnPlayer player = SaturnPlayer.get(uuid); + if (player != null && player.cloak != null) { if (!player.cloak.isEmpty()) { if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) { @@ -146,7 +147,7 @@ public class Cloaks { } } - public static CompletableFuture loadAnimatedCloakAsync(String uuid, String cloakName) { + public static CompletableFuture loadAnimatedCloakAsync(UUID uuid, String cloakName) { return CompletableFuture.runAsync(() -> { String fileName = cloakName + ".gif"; @@ -196,7 +197,7 @@ public class Cloaks { } else { // Queue for main thread execution // SaturnClient.client.execute( - // () -> IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame)); + // () -> IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frame)); } animatedFrames.add(new AnimatedCloakData(frameIdentifier, delay)); From 2d021e886fa12ba9d78c2b5407187dfaf775c59f Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:07:31 +0100 Subject: [PATCH 06/82] Cloaks --- .../saturnclient/client/ServiceClient.java | 25 ++++++++++----- .../saturnclient/client/ServiceMethods.java | 3 ++ .../client/player/PlayerTracker.java | 4 +-- .../client/player/SaturnPlayer.java | 21 ++++++++++--- .../cosmetics/CloakFeatureRenderer.java | 9 ++---- .../saturnclient/cosmetics/cloaks/Cloaks.java | 31 ++++++++++--------- .../ui2/screens/store/CloakStore.java | 1 - 7 files changed, 59 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 6b8595c..0476370 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -1,12 +1,6 @@ package org.saturnclient.saturnclient.client; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.client.player.SaturnPlayer; @@ -49,6 +43,7 @@ public class ServiceClient { String accessToken = mcSession.getAccessToken(); uuid = mcSession.getUuidOrNull(); + String username = mcSession.getUsername(); SaturnClient.LOGGER.info("Authenticating with UUID: " + accessToken); @@ -67,7 +62,7 @@ public class ServiceClient { Hats.availableHats.add(availableHat); } - SaturnPlayer.set(uuid, new SaturnPlayer(response.cloak(), response.hat())); + SaturnPlayer.set(uuid, username, new SaturnPlayer(uuid, username, response.cloak(), response.hat())); Cloaks.loadCloak(uuid); @@ -77,4 +72,20 @@ public class ServiceClient { return false; } } + + public static void setCloak(String cloak) { + try { + session.request(ServiceMethods.SetCloak, cloak).get(); + } catch (Exception e) { + SaturnClient.LOGGER.error("Failed to set cloak (service): ", e); + } + } + + public static void setHat(String hat) { + try { + session.request(ServiceMethods.SetCloak, hat).get(); + } catch (Exception e) { + SaturnClient.LOGGER.error("Failed to set hat (service): ", e); + } + } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index 6f3a1c4..4be3b40 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -8,4 +8,7 @@ public class ServiceMethods { public static final Method Authenticate = new Method<>("auth", String.class, AuthResponse.class, String.class); + + public static final Method SetCloak = new Method<>("set_cloak", + String.class, String.class, String.class); } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java b/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java index 308fb3a..2a27c6b 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java @@ -3,6 +3,7 @@ package org.saturnclient.saturnclient.client.player; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import org.saturnclient.saturnclient.SaturnClient; + import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.MinecraftClient; @@ -46,7 +47,6 @@ public class PlayerTracker { private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) { GameProfile profile = player.getProfile(); UUID uuid = profile.getId(); - // if (uuid != ServiceClient.uuid) - // ServiceClient.player(profile.getName(), uuid); + SaturnPlayer.player(uuid); } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index 551f245..9173f31 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -6,30 +6,43 @@ import java.util.UUID; public class SaturnPlayer { private static Map PLAYERS = new HashMap<>(); + private static Map PLAYER_NAMES = new HashMap<>(); public String cloak = ""; public String hat = ""; + public final UUID uuid; + public final String name; - public SaturnPlayer(String cloak, String hat) { + public SaturnPlayer(UUID uuid, String name, String cloak, String hat) { this.cloak = cloak; this.hat = hat; + this.uuid = uuid; + this.name = name; } public static SaturnPlayer get(UUID uuid) { return PLAYERS.get(uuid); } - public static void set(UUID uuid, SaturnPlayer player) { + 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(UUID uuid, String name, SaturnPlayer player) { + PLAYER_NAMES.put(name, uuid); PLAYERS.put(uuid, player); } - public static void player(UUID uuid, SaturnPlayer player) { + public static void player(UUID uuid) { if (PLAYERS.containsKey(uuid)) { return; } // If offline then leave - PLAYERS.put(uuid, player); + PLAYERS.put(uuid, null); } } diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java index 8137649..7f869a3 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java @@ -1,6 +1,7 @@ package org.saturnclient.saturnclient.cosmetics; import org.saturnclient.saturnclient.SaturnClientConfig; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import net.minecraft.client.render.OverlayTexture; @@ -289,13 +290,9 @@ public class CloakFeatureRenderer extends FeatureRenderer availableCloaks = new ArrayList<>(); public static Identifier cloakCacheIdentifier = null; public static final Map> animatedCloaks = new ConcurrentHashMap<>(); - private static final Map lastFrameTime = new ConcurrentHashMap<>(); + private static final Map lastFrameTime = new ConcurrentHashMap<>(); private static final ExecutorService CLOAK_LOADER_EXECUTOR = Executors.newFixedThreadPool( Math.min(4, Runtime.getRuntime().availableProcessors()), @@ -59,8 +60,7 @@ public class Cloaks { * * @param cloakName Name of the cloak file to load */ - public static void setCloak(String uuid, String cloakName) { - Auth.setCloak(cloakName); + public static void setCloak(UUID uuid, String cloakName) { setCloakSilent(uuid, cloakName); SaturnClient.LOGGER.info("Cloak set to " + cloakName); } @@ -71,8 +71,9 @@ public class Cloaks { * @param cloakName Name of the cloak file to load */ public static void setCloak(String cloakName) { + ServiceClient.setCloak(cloakName); if (availableCloaks.contains(cloakName)) { - setCloak(Auth.uuid, cloakName); + setCloak(ServiceClient.uuid, cloakName); } } @@ -82,10 +83,12 @@ public class Cloaks { * * @param cloakName Name of the cloak file to load */ - public static void setCloakSilent(String uuid, String cloakName) { - SaturnPlayer player = Auth.players.get(uuid); + public static void setCloakSilent(UUID uuid, String cloakName) { + SaturnPlayer player = SaturnPlayer.get(uuid); + if (player == null) { - Auth.players.put(uuid, new SaturnPlayer(cloakName, null)); + // Auth.players.put(uuid, new SaturnPlayer(cloakName, null)); + // TODO: Address this functionality } else { player.cloak = cloakName; } @@ -227,16 +230,14 @@ public class Cloaks { }, CLOAK_LOADER_EXECUTOR); } - public static Identifier getCurrentCloakTexture(String uuid) { - if (!Auth.players.containsKey(uuid)) { + public static Identifier getCurrentCloakTexture(UUID uuid) { + SaturnPlayer player = SaturnPlayer.get(uuid); + + if (player == null || player.cloak.isEmpty()) { return null; } - String cloakName = Auth.players.get(uuid).cloak; - - if (cloakName.isEmpty()) { - return null; - } + String cloakName = player.cloak; if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) { List frames = animatedCloaks.get(uuid); diff --git a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java index 848e598..ba662d2 100644 --- a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java +++ b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java @@ -1,7 +1,6 @@ package org.saturnclient.ui2.screens.store; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.ui2.SaturnScreen; From 3f06efe8f4c637e4e96dbcd47f7f4b2c4ce5a359 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:09:50 +0100 Subject: [PATCH 07/82] Hat feature renderer --- .../cosmetics/HatFeatureRenderer.java | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java index de91606..2f7e061 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java @@ -1,6 +1,5 @@ package org.saturnclient.saturnclient.cosmetics; -import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.SaturnClient; import net.minecraft.client.render.OverlayTexture; @@ -28,38 +27,35 @@ public class HatFeatureRenderer extends FeatureRenderer Date: Fri, 20 Feb 2026 03:11:08 +0100 Subject: [PATCH 08/82] Hat service --- .../saturnclient/saturnclient/cosmetics/Hats.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java index 24de9d2..849c00c 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java @@ -2,8 +2,9 @@ package org.saturnclient.saturnclient.cosmetics; import java.util.ArrayList; import java.util.List; +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 { @@ -14,15 +15,16 @@ public class Hats { availableHats.add(0, ""); } - public static void setHat(String uuid, String hatName) { + public static void setHat(UUID uuid, String hatName) { setHatSilent(uuid, hatName); - Auth.setHat(hatName); + ServiceClient.setHat(hatName); } - public static void setHatSilent(String uuid, String hatName) { - SaturnPlayer player = Auth.players.get(uuid); + public static void setHatSilent(UUID uuid, String hatName) { + SaturnPlayer player = SaturnPlayer.get(uuid); if (player == null) { - Auth.players.put(uuid, new SaturnPlayer("", hatName)); + // Auth.players.put(uuid, new SaturnPlayer("", hatName)); + // TODO: address this behaviour } else { player.hat = hatName; } From f5776450f06098141b6815470ff35397ce0a2d75 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:24:09 +0100 Subject: [PATCH 09/82] Name tag service --- .../saturnclient/SaturnClientConfig.java | 38 ++++++++++++++++--- .../saturnclient/mixin/NameTagMixin.java | 19 +++++----- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java b/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java index db45255..201ef98 100644 --- a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java +++ b/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java @@ -1,6 +1,10 @@ package org.saturnclient.saturnclient; import org.saturnclient.saturnclient.config.Property; + +import java.util.Map; +import java.util.UUID; + import org.lwjgl.glfw.GLFW; import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.ui2.resources.Textures; @@ -16,6 +20,17 @@ public class SaturnClientConfig { public static Property bendyCloaks = Property.bool(true); public static Property openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B); + static enum Role { + OWNER, + ADMIN, + PARTNER, + CONTRIBUTOR + } + + private static final Map ROLES = Map.of( + + ); + public static Identifier getLogo() { return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO; } @@ -33,12 +48,25 @@ public class SaturnClientConfig { * - Contributor: Aqua * - Other/player: White */ - public static Formatting getIconColor(String uuid) { - if (uuid.equals("d362a04228bd49e1a807ae74dbe8aba9")) { - return Formatting.DARK_RED; // Owner - } + public static Formatting getIconColor(UUID uuid) { + Role role = ROLES.get(uuid); - 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 void init() { diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java index defa7f5..b0e9d34 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java @@ -15,7 +15,10 @@ import net.minecraft.util.Formatting; import net.minecraft.util.math.Vec3d; import org.saturnclient.saturnclient.SaturnClientConfig; -import org.saturnclient.saturnclient.auth.Auth; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; + +import java.util.UUID; + import org.joml.Matrix4f; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.Inject; @@ -37,12 +40,12 @@ public abstract class NameTagMixin { private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { - String uuid = isSaturn(state); + UUID uuid = isSaturn(state); if (uuid != null) { MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator()) - .styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); - + .styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); + Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE)); renderLabel(state, iconText.append(nameText), matrices, @@ -52,13 +55,11 @@ public abstract class NameTagMixin { } } - private String isSaturn(S state) { + private UUID isSaturn(S state) { if (state instanceof PlayerEntityRenderState) { String name = ((PlayerEntityRenderState) state).name; - String uuid = Auth.playerNames.get(name); - if (Auth.players.containsKey(uuid)) { - return uuid; - } + SaturnPlayer player = SaturnPlayer.get(name); + return player.uuid; } return null; From 50da953dc44b7b7390764f61de0d9374c45f716f Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:25:33 +0100 Subject: [PATCH 10/82] Added myself as the owner again --- .../org/saturnclient/saturnclient/SaturnClientConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java b/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java index 201ef98..de13281 100644 --- a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java +++ b/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java @@ -28,8 +28,8 @@ public class SaturnClientConfig { } private static final Map ROLES = Map.of( - - ); + UUID.fromString("d362a042-28bd-49e1-a807-ae74dbe8aba9"), + Role.OWNER); public static Identifier getLogo() { return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO; From 68519ac00c16989b6d1a69ef67f8898772a74e70 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:38:05 +0100 Subject: [PATCH 11/82] UI service auth refactored --- .../client/player/SaturnPlayer.java | 6 +++++ .../saturnclient/cosmetics/Hats.java | 10 ++++---- .../saturnclient/cosmetics/cloaks/Cloaks.java | 14 ++--------- .../saturnclient/event/KeyInputHandler.java | 1 - .../ui2/screens/cosmetics/CloakMenu.java | 21 ++++++++--------- .../ui2/screens/cosmetics/HatMenu.java | 23 +++++++++---------- .../ui2/screens/store/CloakStore.java | 5 ++-- 7 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index 9173f31..5f18c51 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -4,6 +4,8 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import org.saturnclient.saturnclient.client.ServiceClient; + public class SaturnPlayer { private static Map PLAYERS = new HashMap<>(); private static Map PLAYER_NAMES = new HashMap<>(); @@ -20,6 +22,10 @@ public class SaturnPlayer { this.name = name; } + public static SaturnPlayer get() { + return PLAYERS.get(ServiceClient.uuid); + } + public static SaturnPlayer get(UUID uuid) { return PLAYERS.get(uuid); } diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java index 849c00c..0d2b7cf 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java @@ -15,12 +15,14 @@ public class Hats { availableHats.add(0, ""); } - public static void setHat(UUID uuid, String hatName) { - setHatSilent(uuid, hatName); - ServiceClient.setHat(hatName); + public static void setHat(String hatName) { + if (availableHats.contains(hatName)) { + setHat(ServiceClient.uuid, hatName); + ServiceClient.setHat(hatName); + } } - public static void setHatSilent(UUID uuid, String hatName) { + public static void setHat(UUID uuid, String hatName) { SaturnPlayer player = SaturnPlayer.get(uuid); if (player == null) { // Auth.players.put(uuid, new SaturnPlayer("", hatName)); diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java index 0fbabc1..32e3283 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java @@ -55,25 +55,15 @@ public class Cloaks { availableCloaks.add(0, ""); } - /** - * Handles loading and caching of a new cloak texture. - * - * @param cloakName Name of the cloak file to load - */ - public static void setCloak(UUID uuid, String cloakName) { - setCloakSilent(uuid, cloakName); - SaturnClient.LOGGER.info("Cloak set to " + cloakName); - } - /** * Sets cloak of the current player * * @param cloakName Name of the cloak file to load */ public static void setCloak(String cloakName) { - ServiceClient.setCloak(cloakName); if (availableCloaks.contains(cloakName)) { setCloak(ServiceClient.uuid, cloakName); + ServiceClient.setCloak(cloakName); } } @@ -83,7 +73,7 @@ public class Cloaks { * * @param cloakName Name of the cloak file to load */ - public static void setCloakSilent(UUID uuid, String cloakName) { + public static void setCloak(UUID uuid, String cloakName) { SaturnPlayer player = SaturnPlayer.get(uuid); if (player == null) { diff --git a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java index 7f6ad17..0054aee 100644 --- a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java +++ b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java @@ -7,7 +7,6 @@ import net.minecraft.client.util.InputUtil; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClientConfig; -import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.ui2.screens.EmoteWheel; import org.saturnclient.ui2.screens.ShiftMenu; diff --git a/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java b/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java index 5261827..2abb28d 100644 --- a/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/cosmetics/CloakMenu.java @@ -1,7 +1,6 @@ package org.saturnclient.ui2.screens.cosmetics; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.ui2.resources.Textures; @@ -27,15 +26,15 @@ public class CloakMenu extends SaturnScreen { int row = 0; int col = 0; - - SaturnPlayer player = Auth.players.get(Auth.uuid); + + SaturnPlayer player = SaturnPlayer.get(); if (player != null) { for (String cloak : Cloaks.availableCloaks) { scroll.draw(new CosmeticPreview(cloak == player.cloak, Textures.getCloakPreview(cloak), () -> { Cloaks.setCloak(cloak); }).position((50 + g) * col, (111 + g) * row)); - + if (col == 5) { col = 0; row++; @@ -46,18 +45,18 @@ public class CloakMenu extends SaturnScreen { } int scrollWidth = 480 + 10 + (g * 2) + (p * 2); - + 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 TabMenu(0, - new TabMenuComponent(Textures.CLOAK, () -> {}), - new TabMenuComponent(Textures.HAT, () -> { - SaturnClient.client.setScreen(new HatMenu()); - }) - ).centerOffset(width, height, 0, -195)); + new TabMenuComponent(Textures.CLOAK, () -> { + }), + new TabMenuComponent(Textures.HAT, () -> { + SaturnClient.client.setScreen(new HatMenu()); + })).centerOffset(width, height, 0, -195)); } } diff --git a/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java b/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java index 978ec90..06deb3c 100644 --- a/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/cosmetics/HatMenu.java @@ -1,7 +1,6 @@ package org.saturnclient.ui2.screens.cosmetics; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.auth.Auth; import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.ui2.resources.Textures; @@ -27,15 +26,15 @@ public class HatMenu extends SaturnScreen { int row = 0; int col = 0; - - SaturnPlayer player = Auth.players.get(Auth.uuid); + + SaturnPlayer player = SaturnPlayer.get(); if (player != null) { for (String hat : Hats.availableHats) { 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)); - + if (col == 5) { col = 0; row++; @@ -46,18 +45,18 @@ public class HatMenu extends SaturnScreen { } int scrollWidth = 480 + 10 + (g * 2) + (p * 2); - + 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 TabMenu(1, - new TabMenuComponent(Textures.CLOAK, () -> { - SaturnClient.client.setScreen(new CloakMenu()); - }), - new TabMenuComponent(Textures.HAT, () -> {}) - ).centerOffset(width, height, 0, -195)); + new TabMenuComponent(Textures.CLOAK, () -> { + SaturnClient.client.setScreen(new CloakMenu()); + }), + new TabMenuComponent(Textures.HAT, () -> { + })).centerOffset(width, height, 0, -195)); } } diff --git a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java index ba662d2..1f0a056 100644 --- a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java +++ b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java @@ -1,6 +1,7 @@ package org.saturnclient.ui2.screens.store; import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.ui2.SaturnScreen; @@ -41,7 +42,7 @@ public class CloakStore extends SaturnScreen { int row = 0; int col = 0; - SaturnPlayer player = Auth.players.get(Auth.uuid); + SaturnPlayer player = SaturnPlayer.get(); if (player != null) { for (String cloak : Cloaks.ALL_CLOAKS) { @@ -53,7 +54,7 @@ public class CloakStore extends SaturnScreen { long now = System.currentTimeMillis(); if (now - lastPurchaseTime >= 3000) { Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait"); - Auth.buyCloak(cloak); + ServiceClient.buyCloak(cloak); lastPurchaseTime = now; new Thread(() -> { try { From 4e6bdce019c6ac88b761d9604ab79995c6cf9e9b Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:41:17 +0100 Subject: [PATCH 12/82] Buy cosmetics service --- .../saturnclient/client/ServiceClient.java | 18 +++++++++++++++++- .../saturnclient/client/ServiceMethods.java | 9 +++++++++ .../ui2/screens/store/HatStore.java | 6 +++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 0476370..74b2cf3 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -83,9 +83,25 @@ public class ServiceClient { public static void setHat(String hat) { try { - session.request(ServiceMethods.SetCloak, hat).get(); + session.request(ServiceMethods.SetHat, hat).get(); } catch (Exception e) { SaturnClient.LOGGER.error("Failed to set hat (service): ", e); } } + + public static void buyCloak(String cloak) { + try { + session.request(ServiceMethods.BuyCloak, cloak).get(); + } catch (Exception e) { + SaturnClient.LOGGER.error("Failed to buy cloak (service): ", e); + } + } + + public static void buyHat(String hat) { + try { + session.request(ServiceMethods.BuyHat, hat).get(); + } catch (Exception e) { + SaturnClient.LOGGER.error("Failed to buy hat (service): ", e); + } + } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index 4be3b40..fb10ae9 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -9,6 +9,15 @@ public class ServiceMethods { public static final Method Authenticate = new Method<>("auth", String.class, AuthResponse.class, String.class); + // Equip public static final Method SetCloak = new Method<>("set_cloak", String.class, String.class, String.class); + public static final Method SetHat = new Method<>("set_hat", + String.class, String.class, String.class); + + // Buy + public static final Method BuyCloak = new Method<>("buy_cloak", + String.class, String.class, String.class); + public static final Method BuyHat = new Method<>("buy_hat", + String.class, String.class, String.class); } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java b/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java index bb06249..15f87b8 100644 --- a/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java +++ b/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java @@ -1,7 +1,7 @@ package org.saturnclient.ui2.screens.store; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.auth.Auth; +import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.ui2.SaturnScreen; @@ -42,7 +42,7 @@ public class HatStore extends SaturnScreen { int row = 0; int col = 0; - SaturnPlayer player = Auth.players.get(Auth.uuid); + SaturnPlayer player = SaturnPlayer.get(); if (player != null) { for (String hat : Hats.ALL_HATS) { @@ -54,7 +54,7 @@ public class HatStore extends SaturnScreen { long now = System.currentTimeMillis(); if (now - lastPurchaseTime >= 3000) { Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait"); - Auth.buyHat(hat); + ServiceClient.buyHat(hat); lastPurchaseTime = now; new Thread(() -> { try { From 8015a233422adcac717190c62a8ca15ce3b0f508 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:42:56 +0100 Subject: [PATCH 13/82] Emote service --- .../saturnclient/saturnclient/client/ServiceClient.java | 8 ++++++++ .../saturnclient/saturnclient/client/ServiceMethods.java | 4 ++++ .../java/org/saturnclient/ui2/screens/EmoteWheel.java | 5 ++--- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 74b2cf3..82ba54b 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -104,4 +104,12 @@ public class ServiceClient { SaturnClient.LOGGER.error("Failed to buy hat (service): ", e); } } + + public static void emote(String emote) { + try { + session.request(ServiceMethods.Emote, emote).get(); + } catch (Exception e) { + SaturnClient.LOGGER.error("Failed to emote (service): ", e); + } + } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index fb10ae9..7eb9dea 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -20,4 +20,8 @@ public class ServiceMethods { String.class, String.class, String.class); public static final Method BuyHat = new Method<>("buy_hat", String.class, String.class, String.class); + + // Emote + public static final Method Emote = new Method<>("emote", + String.class, String.class, String.class); } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java index 73ba8d4..6e4ad52 100644 --- a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java +++ b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java @@ -2,8 +2,7 @@ package org.saturnclient.ui2.screens; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClientConfig; -import org.saturnclient.saturnclient.auth.Auth; -// import org.saturnclient.saturnclient.auth.Auth; +import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.Utils; @@ -71,7 +70,7 @@ public class EmoteWheel extends SaturnScreen { animationStack.addAnimLayer(1000, PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", emote)).playAnimation()); - Auth.emote(emote); + ServiceClient.emote(emote); close(); }).dimensions(70, 70).centerOffset(width, height, -80 + (col * 80), -35 + (row * 80)) .animation(new Fade(700))); From 46d3408be91cc97341e07a63aeb7ffc9a4d39621 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:46:51 +0100 Subject: [PATCH 14/82] TabList --- .../saturnclient/event/KeyInputHandler.java | 3 ++- .../saturnclient/mixin/TabListMixin.java | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java index 0054aee..1504913 100644 --- a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java +++ b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java @@ -7,6 +7,7 @@ import net.minecraft.client.util.InputUtil; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClientConfig; +import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.ui2.screens.EmoteWheel; import org.saturnclient.ui2.screens.ShiftMenu; @@ -46,7 +47,7 @@ public class KeyInputHandler { AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); if (animationStack.isActive() && animationStack.getPriority() == 1000) { animationStack.removeLayer(1000); - Auth.emote(""); + ServiceClient.emote(""); } } }); diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java index 058ec46..a1f4f37 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java @@ -1,13 +1,14 @@ package org.saturnclient.saturnclient.mixin; import net.minecraft.client.network.PlayerListEntry; -import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import java.util.UUID; + import org.jetbrains.annotations.Nullable; import org.saturnclient.saturnclient.SaturnClientConfig; -import org.saturnclient.saturnclient.auth.Auth; +import org.saturnclient.saturnclient.client.player.SaturnPlayer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; @@ -30,13 +31,13 @@ public abstract class TabListMixin { public Text getDisplayName() { GameProfile profile = ((PlayerListEntry) (Object) this).getProfile(); String name = profile.getName(); - String uuid = profile.getId().toString().replaceAll("-", ""); + UUID uuid = profile.getId(); - MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator()) - .styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); - - Text nameText = Text.literal(name).styled(style -> style.withColor(Formatting.WHITE)); - - return Auth.players.containsKey(uuid) ? iconText.append(nameText) : Text.literal(name); + if (SaturnPlayer.get(uuid) != null) { + return Text.literal(SaturnClientConfig.getSaturnIndicator()) + .styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))) + .append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE))); + } + return Text.literal(name); } } From 6750abcc110f55f6d54dc4bbc1aad20bd5bf06a4 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 03:47:32 +0100 Subject: [PATCH 15/82] Service client refactor finalized --- src/main/java/org/saturnclient/saturnclient/SaturnClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/SaturnClient.java b/src/main/java/org/saturnclient/saturnclient/SaturnClient.java index 0b9d836..e922ee9 100644 --- a/src/main/java/org/saturnclient/saturnclient/SaturnClient.java +++ b/src/main/java/org/saturnclient/saturnclient/SaturnClient.java @@ -7,7 +7,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.util.math.MatrixStack; import org.saturnclient.modules.ModManager; -import org.saturnclient.saturnclient.auth.Auth; +import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.saturnclient.cosmetics.Hats; @@ -75,7 +75,7 @@ public class SaturnClient implements ModInitializer { KeyInputHandler.register(); Emotes.initialize(); - if (Auth.authenticate()) { + if (ServiceClient.authenticate()) { Cloaks.initialize(); Hats.initialize(); LOGGER.info(MOD_ID + " initialization complete"); From 365063e8d5142a1029e15ea10b2798a60874b34c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 16:41:09 +0100 Subject: [PATCH 16/82] Custom splash overlay --- .../mixin/SplashOverlayMixin.java | 122 ++++++++++++++++++ .../org/saturnclient/ui2/RenderScope.java | 7 + src/main/resources/saturnclient.mixins.json | 3 +- 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java new file mode 100644 index 0000000..f06b992 --- /dev/null +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -0,0 +1,122 @@ +package org.saturnclient.saturnclient.mixin; + +import java.util.Optional; +import java.util.function.Consumer; + +import org.saturnclient.ui2.RenderScope; +import org.saturnclient.ui2.resources.Textures; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +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.client.texture.TextureManager; +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> exceptionHandler; + + private static int withAlpha(int color, int alpha) { + return color & 0xFFFFFF | alpha << 24; + } + + @Inject(method = "init(Lnet/minecraft/client/texture/TextureManager;)V", at = @At("HEAD")) + private static void onInit(TextureManager textureManager, CallbackInfo ci) { + textureManager.registerTexture(Textures.LOGO_TEXT); + } + + /** + * @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 width = context.getScaledWindowWidth(); + int height = context.getScaledWindowHeight(); + long currentTime = Util.getMeasuringTimeMs(); + + // Handle reload start timing (vanilla uses 500ms fade-in) + if (this.reloadStartTime == -1L) { + this.reloadStartTime = currentTime; + } + + // Compute fade-out timing + float f = this.reloadCompleteTime > -1L ? (float) (currentTime - this.reloadCompleteTime) / 1000f : -1f; + + int alpha = f >= 0f + ? MathHelper.ceil((1.0f - MathHelper.clamp(f - 1f, 0f, 1f)) * 255f) + : 255; + + long l = Util.getMeasuringTimeMs(); + float g = this.reloadStartTime > -1L ? (float) (l - this.reloadStartTime) / 500.0F : -1.0F; + + int k; + + if (f >= 1.0F) { + if (this.client.currentScreen != null) { + this.client.currentScreen.render(context, 0, 0, delta); + } + + k = MathHelper.ceil((1.0F - MathHelper.clamp(f - 1.0F, 0.0F, 1.0F)) * + 255.0F); + context.fill(RenderLayer.getGuiOverlay(), 0, 0, width, height, + withAlpha(0x28282B, k)); + } else if (this.reloading) { + if (this.client.currentScreen != null && g < 1.0F) { + this.client.currentScreen.render(context, mouseX, mouseY, delta); + } + + k = MathHelper.ceil(MathHelper.clamp((double) g, 0.15, 1.0) * 255.0); + context.fill(RenderLayer.getGuiOverlay(), 0, 0, width, height, + withAlpha(0x28282B, k)); + } + + RenderScope scope = new RenderScope(context); + scope.setRenderLayer(RenderLayer::getGuiTextured); + scope.drawTexture(Textures.LOGO, width / 2 - 49, height / 2 - 49, 0, 0, 98, 98, withAlpha(0xFFFFFF, alpha)); + + if (f >= 2.0F) { + this.client.setOverlay(null); + } + + if (this.reloadCompleteTime == -1L && this.reload.isComplete() && (!this.reloading || g >= 2.0F)) { + try { + this.reload.throwException(); + this.exceptionHandler.accept(Optional.empty()); + } catch (Throwable var24) { + this.exceptionHandler.accept(Optional.of(var24)); + } + + this.reloadCompleteTime = Util.getMeasuringTimeMs(); + if (this.client.currentScreen != null) { + this.client.currentScreen.init(this.client, context.getScaledWindowWidth(), + context.getScaledWindowHeight()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui2/RenderScope.java b/src/main/java/org/saturnclient/ui2/RenderScope.java index 8954e05..231428c 100644 --- a/src/main/java/org/saturnclient/ui2/RenderScope.java +++ b/src/main/java/org/saturnclient/ui2/RenderScope.java @@ -12,8 +12,11 @@ import org.saturnclient.ui2.resources.Fonts; import com.mojang.blaze3d.systems.RenderSystem; import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.saturnclient.mixin.DrawContextAccessor; + import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer.TextLayerType; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.ScreenRect; import net.minecraft.client.render.DiffuseLighting; import net.minecraft.client.render.OverlayTexture; @@ -42,6 +45,10 @@ public class RenderScope { private int opacity = 255 << 24; private final ItemRenderState itemRenderState; + public RenderScope(DrawContext context) { + this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers()); + } + public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) { this.matrices = matrices; this.vertexConsumers = vertexConsumers; diff --git a/src/main/resources/saturnclient.mixins.json b/src/main/resources/saturnclient.mixins.json index 8327a69..fc7de68 100644 --- a/src/main/resources/saturnclient.mixins.json +++ b/src/main/resources/saturnclient.mixins.json @@ -9,7 +9,8 @@ "PlayerEntityRendererMixin", "WhoAmI", "DrawContextAccessor", - "TitleScreenMixin" + "TitleScreenMixin", + "SplashOverlayMixin" ], "injectors": { "defaultRequire": 1 From 039d2cd03280b991ddbd7044507119ab793249fb Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 17:35:59 +0100 Subject: [PATCH 17/82] SetScreen mixin --- .../saturnclient/mixin/SetScreenMixin.java | 35 ++++++ .../mixin/SplashOverlayMixin.java | 111 +++++++++++------- .../saturnclient/mixin/TitleScreenMixin.java | 22 ---- .../org/saturnclient/ui2/SaturnScreen.java | 17 +-- .../saturnclient/ui2/screens/TitleMenu.java | 7 +- src/main/resources/saturnclient.mixins.json | 4 +- 6 files changed, 119 insertions(+), 77 deletions(-) create mode 100644 src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java delete mode 100644 src/main/java/org/saturnclient/saturnclient/mixin/TitleScreenMixin.java diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java new file mode 100644 index 0000000..913d004 --- /dev/null +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java @@ -0,0 +1,35 @@ +package org.saturnclient.saturnclient.mixin; + +import org.saturnclient.saturnclient.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.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +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 { + + private boolean replacingScreen = false; + + @Inject(method = "setScreen", at = @At("HEAD"), cancellable = true) + private void onSetScreen(Screen screen, CallbackInfo ci) { + if (replacingScreen) + return; + + @SuppressWarnings("resource") // False positive bc MinecraftClient has close() + MinecraftClient client = ((MinecraftClient) (Object) this); + + if (SaturnClientConfig.saturnTitleScreen.value + && ((screen == null && client.world == null) || screen instanceof TitleScreen)) { + replacingScreen = true; + client.setScreen(new TitleMenu()); + ci.cancel(); + replacingScreen = false; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index f06b992..f9889d8 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -51,71 +51,96 @@ public abstract class SplashOverlayMixin { } /** - * @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 width = context.getScaledWindowWidth(); - int height = context.getScaledWindowHeight(); + int screenWidth = context.getScaledWindowWidth(); + int screenHeight = context.getScaledWindowHeight(); long currentTime = Util.getMeasuringTimeMs(); - // Handle reload start timing (vanilla uses 500ms fade-in) - if (this.reloadStartTime == -1L) { - this.reloadStartTime = currentTime; + // Initialize reload start time if not set + if (reloadStartTime == -1L) { + reloadStartTime = currentTime; } - // Compute fade-out timing - float f = this.reloadCompleteTime > -1L ? (float) (currentTime - this.reloadCompleteTime) / 1000f : -1f; + // Compute fade-out progress after reload completes + float fadeOutProgress = reloadCompleteTime > -1L + ? (float) (currentTime - reloadCompleteTime) / 1000f + : -1f; - int alpha = f >= 0f - ? MathHelper.ceil((1.0f - MathHelper.clamp(f - 1f, 0f, 1f)) * 255f) + int logoAlpha = fadeOutProgress >= 0f + ? MathHelper.ceil((1.0f - MathHelper.clamp(fadeOutProgress - 1f, 0f, 1f)) * 255f) : 255; - long l = Util.getMeasuringTimeMs(); - float g = this.reloadStartTime > -1L ? (float) (l - this.reloadStartTime) / 500.0F : -1.0F; + // Compute fade-in progress while reloading + float reloadProgress = reloadStartTime > -1L + ? (float) (currentTime - reloadStartTime) / 500f + : -1f; - int k; - - if (f >= 1.0F) { - if (this.client.currentScreen != null) { - this.client.currentScreen.render(context, 0, 0, delta); + // Draw background overlay + if (fadeOutProgress >= 1.0F) { + renderCurrentScreen(context, mouseX, mouseY, delta); + int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f); + context.fill(RenderLayer.getGuiOverlay(), 0, 0, screenWidth, screenHeight, + withAlpha(0x28282B, overlayAlpha)); + } else if (reloading) { + if (reloadProgress < 1.0F) { + renderCurrentScreen(context, mouseX, mouseY, delta); } - - k = MathHelper.ceil((1.0F - MathHelper.clamp(f - 1.0F, 0.0F, 1.0F)) * - 255.0F); - context.fill(RenderLayer.getGuiOverlay(), 0, 0, width, height, - withAlpha(0x28282B, k)); - } else if (this.reloading) { - if (this.client.currentScreen != null && g < 1.0F) { - this.client.currentScreen.render(context, mouseX, mouseY, delta); - } - - k = MathHelper.ceil(MathHelper.clamp((double) g, 0.15, 1.0) * 255.0); - context.fill(RenderLayer.getGuiOverlay(), 0, 0, width, height, - withAlpha(0x28282B, k)); + int overlayAlpha = MathHelper.ceil(MathHelper.clamp(reloadProgress, 0.15, 1.0) * 255.0); + context.fill(RenderLayer.getGuiOverlay(), 0, 0, screenWidth, screenHeight, + withAlpha(0x28282B, overlayAlpha)); } + // Draw central logo + drawLogo(context, screenWidth, screenHeight, logoAlpha); + + // 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(DrawContext context, int screenWidth, int screenHeight, int alpha) { RenderScope scope = new RenderScope(context); scope.setRenderLayer(RenderLayer::getGuiTextured); - scope.drawTexture(Textures.LOGO, width / 2 - 49, height / 2 - 49, 0, 0, 98, 98, withAlpha(0xFFFFFF, alpha)); + scope.drawTexture(Textures.LOGO, screenWidth / 2 - 49, screenHeight / 2 - 49, 0, 0, 98, 98, + withAlpha(0xFFFFFF, alpha)); + } - if (f >= 2.0F) { - this.client.setOverlay(null); - } + /** 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); + } - if (this.reloadCompleteTime == -1L && this.reload.isComplete() && (!this.reloading || g >= 2.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 { - this.reload.throwException(); - this.exceptionHandler.accept(Optional.empty()); - } catch (Throwable var24) { - this.exceptionHandler.accept(Optional.of(var24)); + reload.throwException(); + exceptionHandler.accept(Optional.empty()); + } catch (Throwable ex) { + exceptionHandler.accept(Optional.of(ex)); } - this.reloadCompleteTime = Util.getMeasuringTimeMs(); - if (this.client.currentScreen != null) { - this.client.currentScreen.init(this.client, context.getScaledWindowWidth(), - context.getScaledWindowHeight()); + reloadCompleteTime = Util.getMeasuringTimeMs(); + + if (client.currentScreen != null) { + client.currentScreen.init(client, context.getScaledWindowWidth(), context.getScaledWindowHeight()); } } } diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/TitleScreenMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/TitleScreenMixin.java deleted file mode 100644 index 44151c1..0000000 --- a/src/main/java/org/saturnclient/saturnclient/mixin/TitleScreenMixin.java +++ /dev/null @@ -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(); - } - } -} diff --git a/src/main/java/org/saturnclient/ui2/SaturnScreen.java b/src/main/java/org/saturnclient/ui2/SaturnScreen.java index d69b579..05bde40 100644 --- a/src/main/java/org/saturnclient/ui2/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui2/SaturnScreen.java @@ -22,6 +22,7 @@ import net.minecraft.util.Identifier; public abstract class SaturnScreen extends Screen { private final Pool pool = new Pool(3); protected List elements = new ArrayList<>(); + protected float backgroundOpacity = 1.0f; public int blurDuration = 700; public float blurProgress = 0.0f; public int backgroundBlur = 10; @@ -43,7 +44,7 @@ public abstract class SaturnScreen extends Screen { synchronized (elements) { elements.add(element); } - + if (element.animation != null) { element.animation.init(element); @@ -93,16 +94,16 @@ public abstract class SaturnScreen extends Screen { mouseY *= 2; if (client.world == null && client.getCurrentServerEntry() == null) { - ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta); + ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, backgroundOpacity, delta); } PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect( - Identifier.ofVanilla("blur"), - DefaultFramebufferSet.MAIN_ONLY); - if (postEffectProcessor != null) { - postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress); - postEffectProcessor.render(this.client.getFramebuffer(), this.pool); - } + Identifier.ofVanilla("blur"), + DefaultFramebufferSet.MAIN_ONLY); + if (postEffectProcessor != null) { + postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress); + postEffectProcessor.render(this.client.getFramebuffer(), this.pool); + } // We are using a Abstracted RenderScope because older minecraft versions don't // use DrawContext diff --git a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java index 8bcde8e..bfe76b0 100644 --- a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java @@ -20,14 +20,17 @@ import net.minecraft.client.gui.screen.world.SelectWorldScreen; public class TitleMenu extends SaturnScreen { public TitleMenu() { super("Tile Menu"); + this.backgroundOpacity = 0.0f; } @Override public void ui() { backgroundBlur = 0; - draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -36).animation(new Fade(700))); - draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -82).animation(new SlideY(700, -20))); + draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -36) + .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", () -> { client.setScreen(new SelectWorldScreen(new TitleMenu())); diff --git a/src/main/resources/saturnclient.mixins.json b/src/main/resources/saturnclient.mixins.json index fc7de68..3bc9ccc 100644 --- a/src/main/resources/saturnclient.mixins.json +++ b/src/main/resources/saturnclient.mixins.json @@ -9,8 +9,8 @@ "PlayerEntityRendererMixin", "WhoAmI", "DrawContextAccessor", - "TitleScreenMixin", - "SplashOverlayMixin" + "SplashOverlayMixin", + "SetScreenMixin" ], "injectors": { "defaultRequire": 1 From eb409033720ee9541cc17a4a264be7efefdfd166 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 18:11:58 +0100 Subject: [PATCH 18/82] SetScreen mixin --- .../saturnclient/mixin/SetScreenMixin.java | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java index 913d004..7e4e0af 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java @@ -4,8 +4,7 @@ import org.saturnclient.saturnclient.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.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.ModifyVariable; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; @@ -13,23 +12,15 @@ import net.minecraft.client.gui.screen.TitleScreen; @Mixin(MinecraftClient.class) public class SetScreenMixin { - - private boolean replacingScreen = false; - - @Inject(method = "setScreen", at = @At("HEAD"), cancellable = true) - private void onSetScreen(Screen screen, CallbackInfo ci) { - if (replacingScreen) - return; - - @SuppressWarnings("resource") // False positive bc MinecraftClient has close() - MinecraftClient client = ((MinecraftClient) (Object) this); - - if (SaturnClientConfig.saturnTitleScreen.value - && ((screen == null && client.world == null) || screen instanceof TitleScreen)) { - replacingScreen = true; - client.setScreen(new TitleMenu()); - ci.cancel(); - replacingScreen = false; + @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; } } \ No newline at end of file From 86be2eb9567992b49badbefd5f00df5287f79e01 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 19:44:43 +0100 Subject: [PATCH 19/82] Tested auth and fixed title menu --- .../org/saturnclient/saturnclient/client/ServiceClient.java | 3 ++- src/main/java/org/saturnclient/ui2/screens/TitleMenu.java | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 82ba54b..8876664 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -1,6 +1,7 @@ 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; @@ -16,7 +17,7 @@ public class ServiceClient { public static boolean connectTimeout() { try { - session = Session.connect("ws://localhost:3000", 0, null); + session = Session.connect("ws://127.0.0.1:8080", 10, TimeUnit.SECONDS); return true; } catch (Exception e) { return false; diff --git a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java index bfe76b0..a97a6c4 100644 --- a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java @@ -20,7 +20,6 @@ import net.minecraft.client.gui.screen.world.SelectWorldScreen; public class TitleMenu extends SaturnScreen { public TitleMenu() { super("Tile Menu"); - this.backgroundOpacity = 0.0f; } @Override From bb93d72f66234f822d282c1b46be3a50c366fb40 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 20:12:17 +0100 Subject: [PATCH 20/82] Progress bar --- .../mixin/SplashOverlayMixin.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index f9889d8..b962c02 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -41,6 +41,9 @@ public abstract class SplashOverlayMixin { @Shadow(remap = false) private Consumer> exceptionHandler; + @Shadow(remap = false) + private float progress; + private static int withAlpha(int color, int alpha) { return color & 0xFFFFFF | alpha << 24; } @@ -93,8 +96,15 @@ public abstract class SplashOverlayMixin { withAlpha(0x28282B, overlayAlpha)); } + float u = this.reload.getProgress(); + this.progress = MathHelper.clamp(this.progress * 0.95F + u * 0.050000012F, 0.0F, 1.0F); + + // Get render scope for better UI + RenderScope scope = new RenderScope(context); + // Draw central logo - drawLogo(context, screenWidth, screenHeight, logoAlpha); + drawLogo(scope, screenWidth, screenHeight, logoAlpha); + renderProgressBar(scope, context, screenWidth, screenHeight, logoAlpha); // Remove overlay after fade-out completes if (fadeOutProgress >= 2.0F) { @@ -112,13 +122,23 @@ public abstract class SplashOverlayMixin { } /** Draw the main logo centered */ - private void drawLogo(DrawContext context, int screenWidth, int screenHeight, int alpha) { - RenderScope scope = new RenderScope(context); + private void drawLogo(RenderScope scope, int screenWidth, int screenHeight, int alpha) { scope.setRenderLayer(RenderLayer::getGuiTextured); - scope.drawTexture(Textures.LOGO, screenWidth / 2 - 49, screenHeight / 2 - 49, 0, 0, 98, 98, + scope.drawTexture(Textures.LOGO, (screenWidth - 60) / 2, ((screenHeight - 60) / 2) - 20, 0, 0, 60, 60, withAlpha(0xFFFFFF, alpha)); } + 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, withAlpha(0xFFFFFF, alpha)); + + int progressWidth = Math.min((int) (progress * 120), 120); + int progressX = (screenWidth - 120) / 2; + + context.fill(progressX, progressY, progressX + progressWidth, progressY + 5, withAlpha(0xFFFFFF, alpha)); + } + /** 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); From f9039fdf670d204cebc39f153b0954e3825fa5ec Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 20:53:27 +0100 Subject: [PATCH 21/82] Splash Background --- .../mixin/SplashOverlayMixin.java | 37 ++++++++---------- .../saturnclient/ui2/resources/Textures.java | 1 + .../saturnclient/textures/gui/splash.png | Bin 0 -> 121086 bytes 3 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 src/main/resources/assets/saturnclient/textures/gui/splash.png diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index b962c02..846b1cc 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -8,15 +8,11 @@ import org.saturnclient.ui2.resources.Textures; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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.client.texture.TextureManager; import net.minecraft.resource.ResourceReload; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; @@ -48,11 +44,6 @@ public abstract class SplashOverlayMixin { return color & 0xFFFFFF | alpha << 24; } - @Inject(method = "init(Lnet/minecraft/client/texture/TextureManager;)V", at = @At("HEAD")) - private static void onInit(TextureManager textureManager, CallbackInfo ci) { - textureManager.registerTexture(Textures.LOGO_TEXT); - } - /** * @reason Replace vanilla splash rendering completely but keep exit logic */ @@ -72,7 +63,7 @@ public abstract class SplashOverlayMixin { ? (float) (currentTime - reloadCompleteTime) / 1000f : -1f; - int logoAlpha = fadeOutProgress >= 0f + int alpha = fadeOutProgress >= 0f ? MathHelper.ceil((1.0f - MathHelper.clamp(fadeOutProgress - 1f, 0f, 1f)) * 255f) : 255; @@ -81,30 +72,35 @@ public abstract class SplashOverlayMixin { ? (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); - context.fill(RenderLayer.getGuiOverlay(), 0, 0, screenWidth, screenHeight, - withAlpha(0x28282B, overlayAlpha)); + // context.fill(0, 0, screenWidth, screenHeight, + // withAlpha(0x53389E, overlayAlpha)); + scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, + withAlpha(0xFFFFFF, overlayAlpha)); } else if (reloading) { if (reloadProgress < 1.0F) { renderCurrentScreen(context, mouseX, mouseY, delta); } - int overlayAlpha = MathHelper.ceil(MathHelper.clamp(reloadProgress, 0.15, 1.0) * 255.0); - context.fill(RenderLayer.getGuiOverlay(), 0, 0, screenWidth, screenHeight, - withAlpha(0x28282B, overlayAlpha)); + scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, + withAlpha(0xFFFFFF, alpha)); + } else { + scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, + withAlpha(0xFFFFFF, alpha)); } float u = this.reload.getProgress(); this.progress = MathHelper.clamp(this.progress * 0.95F + u * 0.050000012F, 0.0F, 1.0F); - // Get render scope for better UI - RenderScope scope = new RenderScope(context); - // Draw central logo - drawLogo(scope, screenWidth, screenHeight, logoAlpha); - renderProgressBar(scope, context, screenWidth, screenHeight, logoAlpha); + drawLogo(scope, screenWidth, screenHeight, alpha); + renderProgressBar(scope, context, screenWidth, screenHeight, alpha); // Remove overlay after fade-out completes if (fadeOutProgress >= 2.0F) { @@ -123,7 +119,6 @@ public abstract class SplashOverlayMixin { /** Draw the main logo centered */ private void drawLogo(RenderScope scope, int screenWidth, int screenHeight, int alpha) { - scope.setRenderLayer(RenderLayer::getGuiTextured); scope.drawTexture(Textures.LOGO, (screenWidth - 60) / 2, ((screenHeight - 60) / 2) - 20, 0, 0, 60, 60, withAlpha(0xFFFFFF, alpha)); } diff --git a/src/main/java/org/saturnclient/ui2/resources/Textures.java b/src/main/java/org/saturnclient/ui2/resources/Textures.java index 1e29bdd..cdaac79 100644 --- a/src/main/java/org/saturnclient/ui2/resources/Textures.java +++ b/src/main/java/org/saturnclient/ui2/resources/Textures.java @@ -28,6 +28,7 @@ public class Textures { 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_BIG = Identifier.of("saturnclient", "textures/logo/text_big.png"); + public static final Identifier SPLASH = Identifier.of("saturnclient", "textures/gui/splash.png"); // Widgets public static final Identifier BUTTON_BORDER = Identifier.of("saturnclient", "button_border"); diff --git a/src/main/resources/assets/saturnclient/textures/gui/splash.png b/src/main/resources/assets/saturnclient/textures/gui/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..84f2988c7fc0a94a55e4469f807b91f3960c4c70 GIT binary patch literal 121086 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

n0@+svyi|B25d0Q0@)XU}{A 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 z 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 dNkl@TW7Nm|84*P002ovPDHLkV1fgIEkXbQ literal 0 HcmV?d00001 From 619c4bce21a7ba24aad5af46226b042ede883144 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 20 Feb 2026 21:02:00 +0100 Subject: [PATCH 22/82] Fixed role --- .../org/saturnclient/saturnclient/SaturnClientConfig.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java b/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java index de13281..4f603b9 100644 --- a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java +++ b/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java @@ -51,6 +51,10 @@ public class SaturnClientConfig { 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; From 8baf28fcfe93ba9366d98caadda405d88a8403a6 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:05:26 +0100 Subject: [PATCH 23/82] Implemented theme --- .../saturnclient/SaturnClient.java | 1 + .../{ => config}/SaturnClientConfig.java | 8 +- .../saturnclient/config/Theme.java | 20 ++++ .../saturnclient/config/ThemeManager.java | 93 ------------------- .../cosmetics/CloakFeatureRenderer.java | 2 +- .../saturnclient/event/KeyInputHandler.java | 2 +- .../saturnclient/mixin/NameTagMixin.java | 2 +- .../saturnclient/mixin/SetScreenMixin.java | 2 +- .../saturnclient/mixin/TabListMixin.java | 2 +- .../saturnclient/ui2/components/Sidebar.java | 2 +- .../saturnclient/ui2/screens/EmoteWheel.java | 2 +- .../saturnclient/ui2/screens/ShiftMenu.java | 2 +- .../saturnclient/ui2/screens/TitleMenu.java | 2 +- 13 files changed, 34 insertions(+), 106 deletions(-) rename src/main/java/org/saturnclient/saturnclient/{ => config}/SaturnClientConfig.java (93%) create mode 100644 src/main/java/org/saturnclient/saturnclient/config/Theme.java delete mode 100644 src/main/java/org/saturnclient/saturnclient/config/ThemeManager.java diff --git a/src/main/java/org/saturnclient/saturnclient/SaturnClient.java b/src/main/java/org/saturnclient/saturnclient/SaturnClient.java index e922ee9..7a6c803 100644 --- a/src/main/java/org/saturnclient/saturnclient/SaturnClient.java +++ b/src/main/java/org/saturnclient/saturnclient/SaturnClient.java @@ -9,6 +9,7 @@ import net.minecraft.client.util.math.MatrixStack; import org.saturnclient.modules.ModManager; import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.saturnclient.config.ConfigManager; +import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; diff --git a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java b/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java similarity index 93% rename from src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java rename to src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java index 4f603b9..cd09f65 100644 --- a/src/main/java/org/saturnclient/saturnclient/SaturnClientConfig.java +++ b/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java @@ -1,12 +1,9 @@ -package org.saturnclient.saturnclient; - -import org.saturnclient.saturnclient.config.Property; +package org.saturnclient.saturnclient.config; import java.util.Map; import java.util.UUID; import org.lwjgl.glfw.GLFW; -import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.ui2.resources.Textures; import net.minecraft.util.Formatting; @@ -79,5 +76,8 @@ public class SaturnClientConfig { config.property("Saturn client title screen", saturnTitleScreen); config.property("Open Emote Wheel", openEmoteWheel); config.property("Bendy Cloaks", bendyCloaks); + + // Initialize a sub namespace for theme + Theme.init(config); } } diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java new file mode 100644 index 0000000..6eb9241 --- /dev/null +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -0,0 +1,20 @@ +package org.saturnclient.saturnclient.config; + +public class Theme { + public static Property BACKGROUND = Property.color(0x00000000); + public static Property FOREGROUND = Property.color(0x00000000); + public static Property PRIMARY = Property.color(0x00000000); + public static Property PRIMARY_FG = Property.color(0x00000000); + public static Property ACCENT = Property.color(0x00000000); + public static Property ACCENT_FG = Property.color(0x00000000); + + 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); + } +} diff --git a/src/main/java/org/saturnclient/saturnclient/config/ThemeManager.java b/src/main/java/org/saturnclient/saturnclient/config/ThemeManager.java deleted file mode 100644 index 0bcc14b..0000000 --- a/src/main/java/org/saturnclient/saturnclient/config/ThemeManager.java +++ /dev/null @@ -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 theme = new ArrayList<>(); - private static JsonObject cachedThemeJson = null; - - private Map> states; - private Map> 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 tProp : states.get(state).entrySet()) { - currentStyling.get(tProp.getKey()).setValue(tProp.getValue()); - } - } - - public void applyState(String state) { - for (Map.Entry tProp : states.get(state).entrySet()) { - if (!states.get(null).get(tProp.getKey()).equals(tProp.getValue())) - currentStyling.get(tProp.getKey()).setValue(tProp.getValue()); - } - } - - public Property property(String name, Property prop) { - for (Map.Entry> 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 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); - } - } -} diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java index 7f869a3..9f4344f 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java @@ -1,7 +1,7 @@ package org.saturnclient.saturnclient.cosmetics; -import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import net.minecraft.client.render.OverlayTexture; diff --git a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java index 1504913..dae59f3 100644 --- a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java +++ b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java @@ -6,8 +6,8 @@ import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.ServiceClient; +import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.saturnclient.ui2.screens.EmoteWheel; import org.saturnclient.ui2.screens.ShiftMenu; diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java index b0e9d34..112295a 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java @@ -14,8 +14,8 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.math.Vec3d; -import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.saturnclient.config.SaturnClientConfig; import java.util.UUID; diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java index 7e4e0af..4a398a5 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java @@ -1,6 +1,6 @@ package org.saturnclient.saturnclient.mixin; -import org.saturnclient.saturnclient.SaturnClientConfig; +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; diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java index a1f4f37..93513cc 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java @@ -7,8 +7,8 @@ import net.minecraft.util.Formatting; import java.util.UUID; import org.jetbrains.annotations.Nullable; -import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; diff --git a/src/main/java/org/saturnclient/ui2/components/Sidebar.java b/src/main/java/org/saturnclient/ui2/components/Sidebar.java index 0a93dff..36b9492 100644 --- a/src/main/java/org/saturnclient/ui2/components/Sidebar.java +++ b/src/main/java/org/saturnclient/ui2/components/Sidebar.java @@ -1,8 +1,8 @@ package org.saturnclient.ui2.components; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.config.Property; +import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.saturnclient.saturnclient.config.ThemeManager; import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.Element; diff --git a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java index 6e4ad52..55d6fe1 100644 --- a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java +++ b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java @@ -1,8 +1,8 @@ package org.saturnclient.ui2.screens; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.SaturnClientConfig; import org.saturnclient.saturnclient.client.ServiceClient; +import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.saturnclient.saturnclient.cosmetics.Emotes; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.Utils; diff --git a/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java b/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java index 004308e..92866ef 100644 --- a/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java @@ -1,7 +1,7 @@ package org.saturnclient.ui2.screens; 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.ui2.resources.Textures; import org.saturnclient.ui2.screens.cosmetics.CloakMenu; diff --git a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java index a97a6c4..7f0636a 100644 --- a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java @@ -1,7 +1,7 @@ package org.saturnclient.ui2.screens; 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.ui2.resources.Textures; import org.saturnclient.ui2.screens.cosmetics.CloakMenu; From 39cea1418348b349a1d11db8f935a14038ffb055 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:10:57 +0100 Subject: [PATCH 24/82] Theme padding and radius --- .../java/org/saturnclient/saturnclient/config/Theme.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index 6eb9241..33dd5d6 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -7,6 +7,9 @@ public class Theme { public static Property PRIMARY_FG = Property.color(0x00000000); public static Property ACCENT = Property.color(0x00000000); public static Property ACCENT_FG = Property.color(0x00000000); + public static Property BG_RADIUS = Property.integer(10); + public static Property WIDGET_RADIUS = Property.integer(10); + public static Property PADDING = Property.integer(15); public static void init(ConfigManager parent) { ConfigManager config = new ConfigManager(parent, "UI Style"); @@ -16,5 +19,7 @@ public class Theme { config.property("Primary Foreground", PRIMARY_FG); config.property("Accent", ACCENT); config.property("Accent Foreground", ACCENT_FG); + config.property("Background Radius", BG_RADIUS); + config.property("Widget Radius", WIDGET_RADIUS); } } From 953182e56a41a1a1916d90241245aac616a86edd Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:13:36 +0100 Subject: [PATCH 25/82] Theme font, and implemented input --- .../saturnclient/config/Theme.java | 2 ++ .../ui2/components/inputs/Input.java | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index 33dd5d6..5e3ffb0 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -10,6 +10,7 @@ public class Theme { public static Property BG_RADIUS = Property.integer(10); public static Property WIDGET_RADIUS = Property.integer(10); public static Property PADDING = Property.integer(15); + public static Property FONT = Property.font(1); public static void init(ConfigManager parent) { ConfigManager config = new ConfigManager(parent, "UI Style"); @@ -21,5 +22,6 @@ public class Theme { config.property("Accent Foreground", ACCENT_FG); config.property("Background Radius", BG_RADIUS); config.property("Widget Radius", WIDGET_RADIUS); + config.property("Font", WIDGET_RADIUS); } } diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/Input.java b/src/main/java/org/saturnclient/ui2/components/inputs/Input.java index 99997e4..fbd48d9 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/Input.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/Input.java @@ -2,21 +2,20 @@ package org.saturnclient.ui2.components.inputs; import org.lwjgl.glfw.GLFW; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.resources.Fonts; public abstract class Input extends Element { - private static ThemeManager theme = new ThemeManager("Input"); - private static Property font = theme.property("font", Property.font(1)); - String text = ""; public int cursorPosition = 0; + public abstract void checkReset(); + public abstract void backspace(); + public boolean validateBackspace() { return true; } @@ -71,11 +70,12 @@ public abstract class Input extends Element { int scrollOffset = getScrollOffset(); String visibleText = getVisibleText(scrollOffset); int textColor = focused ? 0xFFFFFF : 0xAAAAAA; - renderScope.drawText(0.6f, visibleText, 4, 4, font.value, textColor); + renderScope.drawText(0.6f, visibleText, 4, 4, Theme.FONT.value, textColor); if (focused) { int cursorX = 4 + - (int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), font.value) * 0.6f); + (int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), Theme.FONT.value) + * 0.6f); renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF); } @@ -90,7 +90,7 @@ public abstract class Input extends Element { int maxWidth = width - 4; // Available width for text while (offset < cursorPosition) { // Ensure cursor remains visible String visibleText = text.substring(offset, cursorPosition); - if (Fonts.getWidth(visibleText, font.value) > maxWidth) { + if (Fonts.getWidth(visibleText, Theme.FONT.value) > maxWidth) { offset++; } else { break; @@ -104,7 +104,7 @@ public abstract class Input extends Element { // Ensure text does not overflow past the available width for (int i = 1; i <= visibleText.length(); i++) { - if (Fonts.getWidth(visibleText.substring(0, i), font.value) > width - 4) { + if (Fonts.getWidth(visibleText.substring(0, i), Theme.FONT.value) > width - 4) { return visibleText.substring(0, i - 1); // Trim overflow } } From 3bc21a5b30f08ff6818fda8c1b497be9ab6078b1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:19:44 +0100 Subject: [PATCH 26/82] Keybinding selector and select --- .../components/inputs/KeybindingSelector.java | 6 ++---- .../ui2/components/inputs/Select.java | 16 +++++----------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java b/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java index c4f07ad..d65ebfd 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java @@ -2,15 +2,13 @@ package org.saturnclient.ui2.components.inputs; import org.lwjgl.glfw.GLFW; import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.resources.Fonts; public class KeybindingSelector extends Element { - private static ThemeManager theme = new ThemeManager("Input"); - private static Property font = theme.property("font", Property.font(1)); Property prop; public KeybindingSelector(Property prop) { @@ -25,7 +23,7 @@ public class KeybindingSelector extends Element { renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000); renderScope.drawText(0.6f, prop.value == -1 ? "" : GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)).toUpperCase(), - 4, 4, font.value, textColor); + 4, 4, Theme.FONT.value, textColor); } @Override diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/Select.java b/src/main/java/org/saturnclient/ui2/components/inputs/Select.java index c4d207c..31aeedf 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/Select.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/Select.java @@ -1,7 +1,7 @@ package org.saturnclient.ui2.components.inputs; import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; @@ -10,11 +10,6 @@ import org.saturnclient.ui2.resources.Fonts; import net.minecraft.client.render.RenderLayer; public class Select extends Element { - private static ThemeManager theme = new ThemeManager("Toggle"); - private static Property fgColor = theme.property("fg-color", Property.color(0xAAAAAA)); - private static Property arrowColor = theme.property("arrow-color", Property.color(0xff8B5CF6)); - private static Property font = theme.property("font", Property.font(1)); - private Property prop; public Select(Property prop) { @@ -27,15 +22,14 @@ public class Select extends Element { public void render(RenderScope renderScope, ElementContext ctx) { renderScope.setRenderLayer(RenderLayer::getGuiTextured); - renderScope.drawText(0.8f, "<", 0, 0, font.value, arrowColor.value); + renderScope.drawText(0.8f, "<", 0, 0, Theme.FONT.value, Theme.FOREGROUND.value); String text = prop.getSelection(); - renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, font.value) * 0.6f)) / 2, 1, font.value, fgColor.value); + renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, Theme.FONT.value) * 0.6f)) / 2, 1, + Theme.FONT.value, Theme.FOREGROUND.value); - renderScope.drawText(0.8f, ">", width - 10, 0, font.value, arrowColor.value); - - theme.setState(null); + renderScope.drawText(0.8f, ">", width - 10, 0, Theme.FONT.value, Theme.FOREGROUND.value); } @Override From b0dbdeafa3e63e6815f99c7354cbdbfab3c8f76c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:25:17 +0100 Subject: [PATCH 27/82] Toggle --- .../ui2/components/inputs/Toggle.java | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java b/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java index 5436d42..c0cfcf6 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java @@ -1,22 +1,12 @@ package org.saturnclient.ui2.components.inputs; import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; public class Toggle extends Element { - private static ThemeManager theme = new ThemeManager("Toggle", "hovering", "enabled"); - private static Property bgColor = theme.property("bg-color", Property.color(0xFF808080)); - private static Property fgColor = theme.property("fg-color", Property.color(0xFF909090)); - private static Property cornerRadius = theme.property("corner-radius", Property.integer(30)); - - static { - theme.propertyStateDefault("enabled", "bg-color", 0xaf8B5CF6); - theme.propertyStateDefault("enabled", "fg-color", 0xff8B5CF6); - } - private Property prop; public Toggle(Property prop) { @@ -27,19 +17,10 @@ public class Toggle extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - theme.setState(null); - - if (prop.value) { - theme.setState("enabled"); - } - - if (ctx.isHovering()) { - theme.applyState("hovering"); - } - - renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); - - renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, fgColor.value); + renderScope.drawRoundedRectangle(0, 0, width, height, height, + prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value); + renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, + prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value); } @Override From 79ceaf67a12c121ab5c53a8576778eb46d26617c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:28:30 +0100 Subject: [PATCH 28/82] Cosmetic Preview --- .../ui2/components/CosmeticPreview.java | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java b/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java index 55d9e99..6056550 100644 --- a/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java +++ b/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java @@ -1,7 +1,6 @@ package org.saturnclient.ui2.components; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; @@ -10,13 +9,8 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; public class CosmeticPreview extends Element { - private static ThemeManager theme = new ThemeManager("CosmeticPreview", "selected", "hovering"); - private static Property bgColor = theme.property("bg-color", Property.color(0x89000000)); - private static Property fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF)); - private static Property padding = theme.property("padding", Property.integer(14)); - private static Property cornerRadius = theme.property("corner-radius", Property.integer(10)); - - static {} + static { + } Identifier sprite; private Runnable onClick; @@ -30,25 +24,19 @@ public class CosmeticPreview extends Element { this.width = 50; this.height = 111; } - + @Override public void render(RenderScope renderScope, ElementContext ctx) { - if (ctx.isHovering()) { - theme.setState("hovering"); - } else if (isSelected) { - theme.setState("selected"); - } + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, + isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value); - renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); - renderScope.setRenderLayer(RenderLayer::getGuiTextured); - int texWidth = width - padding.value; - int texHeight = height - padding.value; - - renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value); + int texWidth = width - Theme.PADDING.value; + int texHeight = height - Theme.PADDING.value; - theme.setState(null); + renderScope.drawTexture(sprite, Theme.PADDING.value / 2, Theme.PADDING.value / 2, 0, 0, texWidth, texHeight, + Theme.FOREGROUND.value); } @Override From 5b37131bfd539fa1625b48660223d18363e528e5 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:34:12 +0100 Subject: [PATCH 29/82] Saturn module --- .../ui2/components/SaturnModule.java | 59 +++++++------------ 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/components/SaturnModule.java b/src/main/java/org/saturnclient/ui2/components/SaturnModule.java index a9c0059..60d27e5 100644 --- a/src/main/java/org/saturnclient/ui2/components/SaturnModule.java +++ b/src/main/java/org/saturnclient/ui2/components/SaturnModule.java @@ -2,8 +2,7 @@ package org.saturnclient.ui2.components; import org.saturnclient.modules.Module; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.Element; @@ -15,21 +14,12 @@ import org.saturnclient.ui2.screens.ConfigEditor; import net.minecraft.client.render.RenderLayer; public class SaturnModule extends Element { - private static ThemeManager theme = new ThemeManager("Module", "enabled", "hovering"); - private static Property bgColor = theme.property("bg", Property.color(0xFF1d202d)); - private static Property fgColor = theme.property("fg", Property.color(0xFFA1A2B8)); - private static Property iconFg = theme.property("icon-fg", Property.color(0xFFA1A2B8)); - private static Property iconBg = theme.property("icon-bg", Property.color(0xFF2e3248)); - private static Property tagBg = theme.property("tag-bg", Property.color(0xFF2e3248)); - private static Property iconRadius = theme.property("icon-radius", Property.color(10)); - private static Property font = theme.property("font", Property.font(1)); - private static Property radius = theme.property("radius", Property.color(10)); - - static { - theme.propertyStateDefault("enabled", "bg", 0xFF1d202d); - theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee); - theme.propertyStateDefault("hovering", "fg", 0xFF845eee); - } + // TODO make these states + // static { + // theme.propertyStateDefault("enabled", "bg", 0xFF1d202d); + // theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee); + // theme.propertyStateDefault("hovering", "fg", 0xFF845eee); + // } private Module mod; @@ -47,43 +37,38 @@ public class SaturnModule extends Element { renderScope.setRenderLayer(RenderLayer::getGuiTextured); + // TODO use this if (mod.isEnabled()) { - theme.setState("enabled"); } - if (ctx.isHovering(width - s - p - 5, height - s - p - 5, s + 10, s + 10)) { - settingsHover = true; - } else if (ctx.isHovering()) { - theme.applyState("hovering"); - } + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value); - renderScope.drawRoundedRectangle(0, 0, width, height, radius.value, bgColor.value); + int h = height - (p * 2); - int h = height-(p * 2); + renderScope.drawRoundedRectangle(p, p, h, h, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value); - renderScope.drawRoundedRectangle(p, p, h, h, iconRadius.value, iconBg.value); + renderScope.drawTexture(mod.getIconTexture(), p + (p / 2), p + (p / 2), 0, 0, h - p, h - p, + Theme.PRIMARY_FG.value); - renderScope.drawTexture(mod.getIconTexture(), p + (p / 2), p + (p / 2), 0, 0, h - p, h - p, iconFg.value); + renderScope.drawText(0.6f, mod.getName(), p + h + 4, p + 1, Theme.FONT.value, Theme.PRIMARY_FG.value); - renderScope.drawText(0.6f, mod.getName(), p+h+4, p + 1, font.value, fgColor.value); - - renderScope.drawText(0.35f, mod.getVersion(), width - p - (int) (Fonts.getWidth(mod.getVersion(), font.value) * 0.35f), p + 1, font.value, fgColor.value); + renderScope.drawText(0.35f, mod.getVersion(), + width - p - (int) (Fonts.getWidth(mod.getVersion(), Theme.FONT.value) * 0.35f), p + 1, Theme.FONT.value, + Theme.PRIMARY_FG.value); int xt = p + h + 4; for (String tag : mod.getTags()) { - int xtb = (int) (Fonts.getWidth(tag, font.value) * 0.4f); - renderScope.drawRoundedRectangle(xt, p + (h/2), xtb + 6, 12, 5, tagBg.value); - renderScope.drawText(0.4f, tag, xt+2, p + (h/2) + 3, font.value, fgColor.value); + int xtb = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f); + renderScope.drawRoundedRectangle(xt, p + (h / 2), xtb + 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 += xtb + 9; } + // TODO if (settingsHover) { - theme.setState("hovering"); } - renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, fgColor.value); - - theme.setState(null); + renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, Theme.PRIMARY_FG.value); } @Override From aab4f02dc0abdb5a6b88ef87bb3a33ec28c3fc66 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:40:14 +0100 Subject: [PATCH 30/82] Sidebar --- .../saturnclient/ui2/components/Sidebar.java | 62 +++++++------------ 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/components/Sidebar.java b/src/main/java/org/saturnclient/ui2/components/Sidebar.java index 36b9492..aa47d99 100644 --- a/src/main/java/org/saturnclient/ui2/components/Sidebar.java +++ b/src/main/java/org/saturnclient/ui2/components/Sidebar.java @@ -1,9 +1,8 @@ package org.saturnclient.ui2.components; import org.saturnclient.saturnclient.SaturnClient; -import org.saturnclient.saturnclient.config.Property; import org.saturnclient.saturnclient.config.SaturnClientConfig; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; @@ -18,19 +17,6 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; public class Sidebar extends Element { - private static ThemeManager theme = new ThemeManager("SideBar", "hovering"); - private static Property bgColor = theme.property("bg-color", Property.color(0x90000000)); - private static Property iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF)); - private static Property iconBg = theme.property("icon-bg", Property.color(0x00000000)); - - private static Property cornerRadius = theme.property("corner-radius", Property.integer(10)); - private static Property iconRadius = theme.property("icon-radius", Property.integer(10)); - private static Property padding = theme.property("padding", Property.integer(10)); - - static { - theme.propertyStateDefault("hovering", "icon-bg", -7643914); - } - public static class SidebarComponent { Runnable onClick; boolean end; @@ -45,7 +31,7 @@ public class Sidebar extends Element { Runnable onClose; SidebarComponent[] components = { - new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> { + new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> { SaturnClient.client.setScreen(new ModMenu()); }, false), new Sidebar.SidebarComponent(Textures.SETTINGS, () -> { @@ -79,36 +65,33 @@ public class Sidebar extends Element { int bottomRow = 0; int gap = 5; // 5px gap between icons - int sp = 30 - padding.value + gap; - int s = 30 - (padding.value * 2); - int s2 = s + padding.value; + int padding = 10; + int sp = 30 - padding + gap; + int s = 30 - (padding * 2); + int s2 = s + padding; renderScope.setRenderLayer(RenderLayer::getGuiTextured); - renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value); int i = 0; for (SidebarComponent component : components) { 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); + if (ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i) { } - 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); + renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding, + s + padding, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value); + renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.PRIMARY_FG.value); bottomRow += 1; } else { - int y = padding.value + (sp * topRow); - if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) { - theme.setState("hovering"); - } else { - theme.setState(null); + int y = padding + (sp * topRow); + if (ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i) { } - 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); + renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding, + s + padding, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value); + renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.PRIMARY_FG.value); topRow += 1; } @@ -122,21 +105,22 @@ public class Sidebar extends Element { int bottomRow = 0; int gap = 5; // same 5px gap here - int sp = 30 - padding.value + gap; - int s = 30 - (padding.value * 2); - int s2 = s + padding.value; + int padding = 10; + int sp = 30 - padding + gap; + int s = 30 - (padding * 2); + int s2 = s + padding; for (SidebarComponent component : components) { 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)) { + if (Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale)) { component.onClick.run(); break; } bottomRow += 1; } else { - int y = padding.value + (sp * topRow); - if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) { + int y = padding + (sp * topRow); + if (Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale)) { component.onClick.run(); break; } From 289a2cd78a9ebee51eb8661937b1756236450966 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:44:24 +0100 Subject: [PATCH 31/82] Button --- .../org/saturnclient/ui2/elements/Button.java | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/Button.java b/src/main/java/org/saturnclient/ui2/elements/Button.java index 0b74643..73a1a1c 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Button.java +++ b/src/main/java/org/saturnclient/ui2/elements/Button.java @@ -1,43 +1,28 @@ package org.saturnclient.ui2.elements; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.resources.Fonts; public class Button extends Element { - private static ThemeManager theme = new ThemeManager("Button", "hovering"); - private static Property bgColor = theme.property("bg-color", Property.color(0xFF000000)); - private static Property fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF)); - private static Property cornerRadius = theme.property("corner-radius", Property.integer(10)); - private static Property font = theme.property("font", Property.font(1)); - - static { - theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee); - } - private String text; private Runnable onClick; public Button(String text, Runnable onClick) { this.text = text; this.onClick = onClick; - this.dimensions(Fonts.getWidth(text, font.value) + 50, 38); } @Override public void render(RenderScope renderScope, ElementContext ctx) { - if (ctx.isHovering()) { - theme.setState("hovering"); - } else { - theme.setState(null); - } - renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, + ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value); - // renderScope.drawText(text, (width - Fonts.getWidth(text, font)) / 2, (height - Fonts.getHeight()) / 2, bold.value, fgColor.value); - renderScope.drawText(text, Fonts.centerX(width, text, font.value), Fonts.centerY(height), font.value, fgColor.value); + renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height), + Theme.FONT.value, + ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value); } @Override From e031672d72a750c9c8a0f8aa02e75411af9526f5 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:46:03 +0100 Subject: [PATCH 32/82] Clicakble texture --- .../ui2/elements/ClickableTexture.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/ClickableTexture.java b/src/main/java/org/saturnclient/ui2/elements/ClickableTexture.java index 0a8aff1..877eeea 100644 --- a/src/main/java/org/saturnclient/ui2/elements/ClickableTexture.java +++ b/src/main/java/org/saturnclient/ui2/elements/ClickableTexture.java @@ -1,7 +1,6 @@ package org.saturnclient.ui2.elements; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; @@ -10,9 +9,6 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; public class ClickableTexture extends Element { - private static ThemeManager theme = new ThemeManager("ClickableTexture", "hovering"); - private static Property fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF)); - Identifier sprite; private Runnable onClick; @@ -20,17 +16,12 @@ public class ClickableTexture extends Element { this.sprite = sprite; this.onClick = onClick; } - + @Override public void render(RenderScope renderScope, ElementContext ctx) { - if (ctx.isHovering()) { - theme.setState("hovering"); - } else { - theme.setState(null); - } - renderScope.setRenderLayer(RenderLayer::getGuiTextured); - renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height, fgColor.value); + renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height, + ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value); } @Override From 9fd79aadd549c104d47d15eb99e47949098d2ad7 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:47:19 +0100 Subject: [PATCH 33/82] Notification --- .../org/saturnclient/ui2/elements/Notification.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/Notification.java b/src/main/java/org/saturnclient/ui2/elements/Notification.java index 61916d0..0da60c7 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Notification.java +++ b/src/main/java/org/saturnclient/ui2/elements/Notification.java @@ -1,7 +1,7 @@ package org.saturnclient.ui2.elements; import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; @@ -10,11 +10,9 @@ import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Textures; public class Notification extends Element { - private static ThemeManager theme = new ThemeManager("Notification"); - private static Property bgColor = theme.property("bg-color", Property.color(0xFF000000)); - private static Property error = theme.property("error", Property.color(0xFFbf212f)); - private static Property info = theme.property("info", Property.color(0xFF264b96)); - private static Property success = theme.property("success", Property.color(0xFF27b376)); + private static Property error = Property.color(0xFFbf212f); + private static Property info = Property.color(0xFF264b96); + private static Property success = Property.color(0xFF27b376); public static enum NotificationKind { Error, @@ -40,7 +38,7 @@ public class Notification extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.drawRoundedRectangle(0, 0, width, height, 10, bgColor.value); + renderScope.drawRoundedRectangle(0, 0, width, height, 10, Theme.PRIMARY.value); switch (kind) { case Error: renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value); From 569aecde4a91da0587a6b2f1632c8a1bb0d997ea Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 18:51:58 +0100 Subject: [PATCH 34/82] Scroll --- .../org/saturnclient/ui2/elements/Scroll.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index 0b0e761..7b78025 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -4,22 +4,19 @@ import java.util.ArrayList; import java.util.List; import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.components.ElementRenderer; public class Scroll extends Element { - private static ThemeManager theme = new ThemeManager("Scroll"); - private static Property bgColor = theme.property("bg-color", Property.color(0x90000000)); - private static Property scrollBarColor = theme.property("scrollbar-color", Property.color(-7643914)); + private static Property scrollBarColor = Property.color(-7643914); + + private static Property scrollBarRadius = Property.integer(10); + private static Property scrollBarWidth = Property.integer(5); + private static Property scrollBarPadding = Property.integer(5); - private static Property scrollBarRadius = theme.property("scrollbar-radius", Property.integer(10)); - private static Property scrollBarWidth = theme.property("scrollbar-width", Property.integer(5)); - private static Property scrollBarPadding = theme.property("scrollbar-padding", Property.integer(5)); - private static Property cornerRadius = theme.property("corner-radius", Property.integer(10)); - int padding = 0; protected List children = new ArrayList<>(); @@ -32,7 +29,7 @@ public class Scroll extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value); renderScope.enableScissor(padding, padding, width - padding, height - padding); renderScope.matrices.push(); @@ -41,7 +38,8 @@ public class Scroll extends Element { renderScope.matrices.pop(); renderScope.disableScissor(); - renderScope.drawRoundedRectangle(width - scrollBarWidth.value - scrollBarPadding.value, calculateScrollBarY(), scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, scrollBarColor.value); + renderScope.drawRoundedRectangle(width - scrollBarWidth.value - scrollBarPadding.value, calculateScrollBarY(), + scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, scrollBarColor.value); } @Override @@ -72,13 +70,16 @@ public class Scroll extends Element { } int calculateScrollBarHeight() { - if (maxScroll <= 0) return height - (scrollBarPadding.value * 2); // No scrolling needed - return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); // At least 20px tall + if (maxScroll <= 0) + return height - (scrollBarPadding.value * 2); // No scrolling needed + return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); // At least 20px + // tall } int calculateScrollBarY() { int scrollBarHeight = calculateScrollBarHeight(); - if (maxScroll <= 0) return scrollBarPadding.value; + if (maxScroll <= 0) + return scrollBarPadding.value; return (scroll * (height - scrollBarHeight)) / maxScroll + scrollBarPadding.value; } From 60d83530aea6289f3ce5e349f066025bf61df2d9 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 19:18:42 +0100 Subject: [PATCH 35/82] Tabmenu text and texture button --- .../saturnclient/config/Theme.java | 2 +- .../saturnclient/ui2/elements/TabMenu.java | 29 ++++++---------- .../org/saturnclient/ui2/elements/Text.java | 11 ++---- .../ui2/elements/TextureButton.java | 34 +++++-------------- 4 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index 5e3ffb0..b053d95 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -5,7 +5,7 @@ public class Theme { public static Property FOREGROUND = Property.color(0x00000000); public static Property PRIMARY = Property.color(0x00000000); public static Property PRIMARY_FG = Property.color(0x00000000); - public static Property ACCENT = Property.color(0x00000000); + public static Property ACCENT = Property.color(0xFF845eee); public static Property ACCENT_FG = Property.color(0x00000000); public static Property BG_RADIUS = Property.integer(10); public static Property WIDGET_RADIUS = Property.integer(10); diff --git a/src/main/java/org/saturnclient/ui2/elements/TabMenu.java b/src/main/java/org/saturnclient/ui2/elements/TabMenu.java index e640106..b41757b 100644 --- a/src/main/java/org/saturnclient/ui2/elements/TabMenu.java +++ b/src/main/java/org/saturnclient/ui2/elements/TabMenu.java @@ -1,7 +1,6 @@ package org.saturnclient.ui2.elements; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; @@ -10,16 +9,6 @@ import org.saturnclient.ui2.Utils; import net.minecraft.util.Identifier; public class TabMenu extends Element { - private static ThemeManager theme = new ThemeManager("TabMenu", "hovering", "selected"); - private static Property bgColor = theme.property("bg-color", Property.color(0x90000000)); - private static Property iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF)); - private static Property iconBg = theme.property("icon-bg", Property.color(0x00000000)); - private static Property cornerRadius = theme.property("corner-radius", Property.integer(10)); - - static { - theme.propertyStateDefault("hovering", "icon-bg", -7643914); - } - public static class TabMenuComponent { Runnable onClick; Identifier sprite; @@ -29,7 +18,7 @@ public class TabMenu extends Element { this.sprite = sprite; } } - + TabMenuComponent[] components; public int selectedScreen = 0; @@ -42,19 +31,21 @@ public class TabMenu extends Element { @Override 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; for (TabMenuComponent component : components) { 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, iconFg.value); + renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10, + hovering ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value); - theme.setState(null); col++; } } diff --git a/src/main/java/org/saturnclient/ui2/elements/Text.java b/src/main/java/org/saturnclient/ui2/elements/Text.java index 8465021..285433d 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Text.java +++ b/src/main/java/org/saturnclient/ui2/elements/Text.java @@ -1,27 +1,22 @@ package org.saturnclient.ui2.elements; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.resources.Fonts; public class Text extends Element { - private static ThemeManager theme = new ThemeManager("Text"); - private static Property fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF)); - public static Property font = theme.property("font", Property.font(1)); - private String text; public Text(String text) { this.text = text; - width = Fonts.getWidth(text, font.value); + width = Fonts.getWidth(text, Theme.FONT.value); height = Fonts.getHeight(); } @Override 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); } } diff --git a/src/main/java/org/saturnclient/ui2/elements/TextureButton.java b/src/main/java/org/saturnclient/ui2/elements/TextureButton.java index d345bf3..56d8a75 100644 --- a/src/main/java/org/saturnclient/ui2/elements/TextureButton.java +++ b/src/main/java/org/saturnclient/ui2/elements/TextureButton.java @@ -1,7 +1,6 @@ package org.saturnclient.ui2.elements; -import org.saturnclient.saturnclient.config.Property; -import org.saturnclient.saturnclient.config.ThemeManager; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; @@ -10,17 +9,6 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; public class TextureButton extends Element { - private static ThemeManager theme = new ThemeManager("TextureButton", "hovering"); - private static Property bgColor = theme.property("bg-color", Property.color(0xFF000000)); - private static Property fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF)); - private static Property padding = theme.property("padding", Property.integer(14)); - private static Property cornerRadius = theme.property("corner-radius", Property.integer(10)); - - - static { - theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee); - } - Identifier sprite; private Runnable onClick; @@ -28,23 +16,19 @@ public class TextureButton extends Element { this.sprite = sprite; this.onClick = onClick; } - + @Override public void render(RenderScope renderScope, ElementContext ctx) { - if (ctx.isHovering()) { - theme.setState("hovering"); - } else { - theme.setState(null); - } + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, + ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value); - renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value); - renderScope.setRenderLayer(RenderLayer::getGuiTextured); - int texWidth = width - padding.value; - int texHeight = height - padding.value; - - renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value); + int texWidth = width - Theme.PADDING.value; + int texHeight = height - Theme.PADDING.value; + + renderScope.drawTexture(sprite, Theme.PADDING.value / 2, Theme.PADDING.value / 2, 0, 0, texWidth, texHeight, + ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value); } @Override From a7c5f0d3df9dcb0dcf109e463929a4cddaef536e Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 19:19:54 +0100 Subject: [PATCH 36/82] Fixed font stuff in screens --- .../org/saturnclient/ui2/screens/ConfigEditor.java | 14 +++++++------- .../saturnclient/ui2/screens/store/CloakStore.java | 5 +++-- .../saturnclient/ui2/screens/store/HatStore.java | 5 +++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java b/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java index e46fcb6..7b00966 100644 --- a/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java +++ b/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java @@ -4,6 +4,7 @@ import java.util.Map; import org.saturnclient.saturnclient.config.ConfigManager; import org.saturnclient.saturnclient.config.Property; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.components.Sidebar; import org.saturnclient.ui2.components.inputs.FloatInput; @@ -59,14 +60,13 @@ public class ConfigEditor extends SaturnScreen { if (prop.getType() == Property.PropertyType.NAMESPACE) { configScroll.draw( - new Text( - propName - ).position(Fonts.centerX(520, propName, Text.font.value), modY + 1).scale(0.7f)); + new Text( + propName).position(Fonts.centerX(520, propName, Theme.FONT.value), modY + 1) + .scale(0.7f)); } else { configScroll.draw( - new Text( - propName - ).position(modX, modY + 1).scale(0.7f)); + new Text( + propName).position(modX, modY + 1).scale(0.7f)); } switch (prop.getType()) { @@ -76,7 +76,7 @@ public class ConfigEditor extends SaturnScreen { .position(modX + (w / 2) - 40, modY) .scale(0.5f)); break; - + case HEX: configScroll.draw(new HexInput((Property) prop, this).position(w / 2, modY)); break; diff --git a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java index 1f0a056..d13b099 100644 --- a/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java +++ b/src/main/java/org/saturnclient/ui2/screens/store/CloakStore.java @@ -3,6 +3,7 @@ package org.saturnclient.ui2.screens.store; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.Utils; @@ -75,10 +76,10 @@ public class CloakStore extends SaturnScreen { 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) - .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) { col = 0; diff --git a/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java b/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java index 15f87b8..7a3de0a 100644 --- a/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java +++ b/src/main/java/org/saturnclient/ui2/screens/store/HatStore.java @@ -3,6 +3,7 @@ package org.saturnclient.ui2.screens.store; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.client.ServiceClient; import org.saturnclient.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.saturnclient.config.Theme; import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.Utils; @@ -75,10 +76,10 @@ public class HatStore extends SaturnScreen { 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) - .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) { col = 0; From 8d8cbc8b28f8625acdb39820b038222b8efdba9c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 19:29:36 +0100 Subject: [PATCH 37/82] Implemenented theme --- .../saturnclient/config/Theme.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index b053d95..8036644 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -1,15 +1,30 @@ package org.saturnclient.saturnclient.config; public class Theme { - public static Property BACKGROUND = Property.color(0x00000000); - public static Property FOREGROUND = Property.color(0x00000000); - public static Property PRIMARY = Property.color(0x00000000); - public static Property PRIMARY_FG = Property.color(0x00000000); - public static Property ACCENT = Property.color(0xFF845eee); - public static Property ACCENT_FG = Property.color(0x00000000); - public static Property BG_RADIUS = Property.integer(10); + // Main UI background (slightly warm dark to match Saturn tone) + public static Property BACKGROUND = Property.color(0xFF12100D); + + // Default text color + public static Property FOREGROUND = Property.color(0xFFF2E6D2); + + // Widget default background (normal state) + public static Property PRIMARY = Property.color(0xFF1C1915); + + // Widget text color + public static Property PRIMARY_FG = Property.color(0xFFEAD9B8); + + // Saturn brand color (hovered / enabled / selected) + public static Property ACCENT = Property.color(0xFFE2BF7D); + + // Text/icons shown on accent background + public static Property ACCENT_FG = Property.color(0xFF1A140A); + + // UI metrics + public static Property BG_RADIUS = Property.integer(12); public static Property WIDGET_RADIUS = Property.integer(10); - public static Property PADDING = Property.integer(15); + public static Property PADDING = Property.integer(14); + + // Font (keeping your current system) public static Property FONT = Property.font(1); public static void init(ConfigManager parent) { From 2f93822996cac9d8aa71be6f2cf9f337b2998a97 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 19:38:19 +0100 Subject: [PATCH 38/82] Scrollbar --- .../java/org/saturnclient/saturnclient/config/Theme.java | 6 ++++++ src/main/java/org/saturnclient/ui2/elements/Scroll.java | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index 8036644..7471507 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -19,6 +19,9 @@ public class Theme { // Text/icons shown on accent background public static Property ACCENT_FG = Property.color(0xFF1A140A); + // Misc colors + public static Property SCROLL = Property.color(0xFFE2BF7D); + // UI metrics public static Property BG_RADIUS = Property.integer(12); public static Property WIDGET_RADIUS = Property.integer(10); @@ -35,8 +38,11 @@ public class Theme { 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", WIDGET_RADIUS); } } diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index 7b78025..1be2461 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -11,8 +11,6 @@ import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.components.ElementRenderer; public class Scroll extends Element { - private static Property scrollBarColor = Property.color(-7643914); - private static Property scrollBarRadius = Property.integer(10); private static Property scrollBarWidth = Property.integer(5); private static Property scrollBarPadding = Property.integer(5); @@ -39,7 +37,7 @@ public class Scroll extends Element { renderScope.disableScissor(); renderScope.drawRoundedRectangle(width - scrollBarWidth.value - scrollBarPadding.value, calculateScrollBarY(), - scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, scrollBarColor.value); + scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, Theme.SCROLL.value); } @Override From 52f80281bd5faf9c769f55983ac3fdce1be7adbb Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 19:46:40 +0100 Subject: [PATCH 39/82] Fixed a few issues --- .../java/org/saturnclient/saturnclient/config/Theme.java | 1 - .../org/saturnclient/ui2/components/CosmeticPreview.java | 9 ++++----- src/main/java/org/saturnclient/ui2/elements/Button.java | 1 + .../org/saturnclient/ui2/elements/TextureButton.java | 7 ++++--- .../java/org/saturnclient/ui2/screens/ShiftMenu.java | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index 7471507..c10a139 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -25,7 +25,6 @@ public class Theme { // UI metrics public static Property BG_RADIUS = Property.integer(12); public static Property WIDGET_RADIUS = Property.integer(10); - public static Property PADDING = Property.integer(14); // Font (keeping your current system) public static Property FONT = Property.font(1); diff --git a/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java b/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java index 6056550..49ace16 100644 --- a/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java +++ b/src/main/java/org/saturnclient/ui2/components/CosmeticPreview.java @@ -9,8 +9,7 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; public class CosmeticPreview extends Element { - static { - } + static int padding = 14; Identifier sprite; private Runnable onClick; @@ -32,10 +31,10 @@ public class CosmeticPreview extends Element { renderScope.setRenderLayer(RenderLayer::getGuiTextured); - int texWidth = width - Theme.PADDING.value; - int texHeight = height - Theme.PADDING.value; + int texWidth = width - padding; + int texHeight = height - padding; - renderScope.drawTexture(sprite, Theme.PADDING.value / 2, Theme.PADDING.value / 2, 0, 0, texWidth, texHeight, + renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight, Theme.FOREGROUND.value); } diff --git a/src/main/java/org/saturnclient/ui2/elements/Button.java b/src/main/java/org/saturnclient/ui2/elements/Button.java index 73a1a1c..4774f11 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Button.java +++ b/src/main/java/org/saturnclient/ui2/elements/Button.java @@ -13,6 +13,7 @@ public class Button extends Element { public Button(String text, Runnable onClick) { this.text = text; this.onClick = onClick; + this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38); } @Override diff --git a/src/main/java/org/saturnclient/ui2/elements/TextureButton.java b/src/main/java/org/saturnclient/ui2/elements/TextureButton.java index 56d8a75..ccbf1ba 100644 --- a/src/main/java/org/saturnclient/ui2/elements/TextureButton.java +++ b/src/main/java/org/saturnclient/ui2/elements/TextureButton.java @@ -11,6 +11,7 @@ import net.minecraft.util.Identifier; public class TextureButton extends Element { Identifier sprite; private Runnable onClick; + static int padding = 14; public TextureButton(Identifier sprite, Runnable onClick) { this.sprite = sprite; @@ -24,10 +25,10 @@ public class TextureButton extends Element { renderScope.setRenderLayer(RenderLayer::getGuiTextured); - int texWidth = width - Theme.PADDING.value; - int texHeight = height - Theme.PADDING.value; + int texWidth = width - padding; + int texHeight = height - padding; - renderScope.drawTexture(sprite, Theme.PADDING.value / 2, Theme.PADDING.value / 2, 0, 0, texWidth, texHeight, + renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight, ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value); } diff --git a/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java b/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java index 92866ef..5d5448f 100644 --- a/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java @@ -25,7 +25,7 @@ public class ShiftMenu extends SaturnScreen { Element button = new Button("Settings", () -> { SaturnClient.client.setScreen(new ModMenu()); - }).center(width, height); + }).center(width, height); draw(button); From 88fe53b9071e7c84b62d5f745211d8e27c81421c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 19:57:40 +0100 Subject: [PATCH 40/82] Fixed sidebar with abstractions --- .../saturnclient/config/Theme.java | 8 ++++ .../saturnclient/ui2/components/Sidebar.java | 40 ++++++++----------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index c10a139..8c702df 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -44,4 +44,12 @@ public class Theme { config.property("Font", WIDGET_RADIUS); } + + public static int getBg(boolean hover) { + return hover ? ACCENT.value : PRIMARY.value; + } + + public static int getFg(boolean hover) { + return hover ? ACCENT_FG.value : PRIMARY_FG.value; + } } diff --git a/src/main/java/org/saturnclient/ui2/components/Sidebar.java b/src/main/java/org/saturnclient/ui2/components/Sidebar.java index aa47d99..7d6e002 100644 --- a/src/main/java/org/saturnclient/ui2/components/Sidebar.java +++ b/src/main/java/org/saturnclient/ui2/components/Sidebar.java @@ -77,21 +77,17 @@ public class Sidebar extends Element { int i = 0; for (SidebarComponent component : components) { + int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow)); + boolean selected = ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i; + + renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding, + s + padding, Theme.WIDGET_RADIUS.value, Theme.getBg(selected)); + + renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.getFg(selected)); + if (component.end) { - int y = (height - (sp * bottomRow)) - (s * 2); - if (ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i) { - } - renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding, - s + padding, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value); - renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.PRIMARY_FG.value); bottomRow += 1; } else { - int y = padding + (sp * topRow); - if (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.PRIMARY.value); - renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.PRIMARY_FG.value); topRow += 1; } @@ -104,26 +100,24 @@ public class Sidebar extends Element { int topRow = 0; int bottomRow = 0; - int gap = 5; // same 5px gap here + int gap = 5; int padding = 10; int sp = 30 - padding + gap; int s = 30 - (padding * 2); int s2 = s + padding; for (SidebarComponent component : components) { + int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow)); + boolean hovering = Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale); + + if (hovering) { + component.onClick.run(); + break; + } + if (component.end) { - int y = (height - (sp * bottomRow)) - (s * 2); - if (Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale)) { - component.onClick.run(); - break; - } bottomRow += 1; } else { - int y = padding + (sp * topRow); - if (Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale)) { - component.onClick.run(); - break; - } topRow += 1; } } From a49212ca646dad549c662ff5e371bd09401c71d1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 20:02:12 +0100 Subject: [PATCH 41/82] Attempting to fix scroll --- src/main/java/org/saturnclient/ui2/elements/Scroll.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index 1be2461..ee8b10e 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -69,9 +69,8 @@ public class Scroll extends Element { int calculateScrollBarHeight() { if (maxScroll <= 0) - return height - (scrollBarPadding.value * 2); // No scrolling needed - return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); // At least 20px - // tall + return height - (scrollBarPadding.value * 2); + return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); } int calculateScrollBarY() { From 3ba6202b43a11d8e79b012d9cd32d3bde4ec4635 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 20:09:19 +0100 Subject: [PATCH 42/82] Improved scroll --- .../org/saturnclient/ui2/elements/Scroll.java | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index ee8b10e..7a751cc 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -27,17 +27,31 @@ public class Scroll extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value); + calculateMaxScroll(); + + renderScope.drawRoundedRectangle(0, 0, width, height, + Theme.BG_RADIUS.value, Theme.BACKGROUND.value); renderScope.enableScissor(padding, padding, width - padding, height - padding); renderScope.matrices.push(); - renderScope.matrices.translate(padding, (-scroll) + padding, 0); - ElementRenderer.render(children, renderScope, ctx.mouseX - padding, ctx.mouseY - padding + scroll); + renderScope.matrices.translate(padding, -scroll + padding, 0); + + ElementRenderer.render(children, renderScope, + ctx.mouseX - padding, + ctx.mouseY - padding + scroll); + renderScope.matrices.pop(); renderScope.disableScissor(); - renderScope.drawRoundedRectangle(width - scrollBarWidth.value - scrollBarPadding.value, calculateScrollBarY(), - scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, Theme.SCROLL.value); + if (maxScroll > 0) { + renderScope.drawRoundedRectangle( + width - scrollBarWidth.value - scrollBarPadding.value, + calculateScrollBarY(), + scrollBarWidth.value, + calculateScrollBarHeight(), + scrollBarRadius.value, + Theme.SCROLL.value); + } } @Override @@ -61,9 +75,13 @@ public class Scroll extends Element { @Override public Element dimensions(int width, int height) { + maxScroll = 0; + for (Element element : children) { maxScroll = Math.max(maxScroll, element.y + element.height - height); } + + maxScroll = Math.max(0, maxScroll); // never negative return super.dimensions(width, height); } @@ -80,6 +98,21 @@ public class Scroll extends Element { return (scroll * (height - scrollBarHeight)) / maxScroll + scrollBarPadding.value; } + private void calculateMaxScroll() { + maxScroll = 0; + + for (Element element : children) { + int bottom = element.y + element.height; + maxScroll = Math.max(maxScroll, bottom - height + padding); + } + + if (maxScroll > 0) { + maxScroll = maxScroll + 10; + } else { + maxScroll = 0; + } + } + @Override public void keyPressed(int keyCode, int scanCode, int modifiers) { ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers); From 7683e7b91e12f8cadade33ccc3d2149e4474fbf3 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 20:18:52 +0100 Subject: [PATCH 43/82] Removed NamedProperty --- .../saturnclient/modules/ModDimensions.java | 20 +++++++++---------- .../java/org/saturnclient/modules/Module.java | 15 ++++++++------ .../saturnclient/config/NamedProperty.java | 11 ---------- .../saturnclient/config/Property.java | 9 +++++++++ 4 files changed, 28 insertions(+), 27 deletions(-) delete mode 100644 src/main/java/org/saturnclient/saturnclient/config/NamedProperty.java diff --git a/src/main/java/org/saturnclient/modules/ModDimensions.java b/src/main/java/org/saturnclient/modules/ModDimensions.java index b0bdb4f..c09b096 100644 --- a/src/main/java/org/saturnclient/modules/ModDimensions.java +++ b/src/main/java/org/saturnclient/modules/ModDimensions.java @@ -2,7 +2,7 @@ package org.saturnclient.modules; import java.util.Map; -import org.saturnclient.saturnclient.config.NamedProperty; +import org.saturnclient.saturnclient.config.Property.NamedProperty; import org.saturnclient.saturnclient.config.Property; public class ModDimensions { @@ -20,17 +20,17 @@ public class ModDimensions { public NamedProperty>> prop() { return Property.namespace(Map.of( - "X", x, - "Y", y, - "Scale", scale, - "Background Color", bgColor, - "Foreground Color", fgColor, - "Corner Radius", radius, - "Font", font - )).named("In-Game Display"); + "X", x, + "Y", y, + "Scale", scale, + "Background Color", bgColor, + "Foreground Color", fgColor, + "Corner Radius", radius, + "Font", font)).named("In-Game Display"); } - public ModDimensions() {} + public ModDimensions() { + } public ModDimensions(int width, int height) { this.width = width; diff --git a/src/main/java/org/saturnclient/modules/Module.java b/src/main/java/org/saturnclient/modules/Module.java index 87527e4..bedc306 100644 --- a/src/main/java/org/saturnclient/modules/Module.java +++ b/src/main/java/org/saturnclient/modules/Module.java @@ -1,9 +1,9 @@ package org.saturnclient.modules; import org.saturnclient.saturnclient.config.ConfigManager; -import org.saturnclient.saturnclient.config.NamedProperty; import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.RenderScope; +import org.saturnclient.saturnclient.config.Property.NamedProperty; import net.minecraft.util.Identifier; @@ -19,8 +19,11 @@ public abstract class Module { } } - public void render(RenderScope scope) {} - public void tick() {} + public void render(RenderScope scope) { + } + + public void tick() { + } public abstract boolean isEnabled(); @@ -34,7 +37,7 @@ public abstract class Module { public final String getName() { return details.name; } - + public Identifier getIconTexture() { return Textures.getModIcon(details.namespace); } @@ -46,11 +49,11 @@ public abstract class Module { public String getDescription() { return details.description; } - + public String[] getTags() { return details.tags; } - + public String getVersion() { return details.version; } diff --git a/src/main/java/org/saturnclient/saturnclient/config/NamedProperty.java b/src/main/java/org/saturnclient/saturnclient/config/NamedProperty.java deleted file mode 100644 index 3e63c31..0000000 --- a/src/main/java/org/saturnclient/saturnclient/config/NamedProperty.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.saturnclient.saturnclient.config; - -public class NamedProperty { - public String name; - public Property prop; - - public NamedProperty(String name, Property value) { - this.name = name; - this.prop = value; - } -} diff --git a/src/main/java/org/saturnclient/saturnclient/config/Property.java b/src/main/java/org/saturnclient/saturnclient/config/Property.java index 0a8aa01..6d81da7 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Property.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Property.java @@ -10,6 +10,15 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; public class Property { + public static class NamedProperty { + public String name; + public Property prop; + + public NamedProperty(String name, Property value) { + this.name = name; + this.prop = value; + } + } public enum PropertyType { BOOLEAN, From 3b0ab53d8664a3f382bceb2246f54fa3a1d45382 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 20:35:52 +0100 Subject: [PATCH 44/82] Fixed Theme issue --- .../saturnclient/config/Theme.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index 8c702df..a5125ae 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -2,29 +2,29 @@ package org.saturnclient.saturnclient.config; public class Theme { // Main UI background (slightly warm dark to match Saturn tone) - public static Property BACKGROUND = Property.color(0xFF12100D); + public static final Property BACKGROUND = Property.color(0xFF12100D); // Default text color - public static Property FOREGROUND = Property.color(0xFFF2E6D2); + public static final Property FOREGROUND = Property.color(0xFFF2E6D2); // Widget default background (normal state) - public static Property PRIMARY = Property.color(0xFF1C1915); + public static final Property PRIMARY = Property.color(0xFF1C1915); // Widget text color - public static Property PRIMARY_FG = Property.color(0xFFEAD9B8); + public static final Property PRIMARY_FG = Property.color(0xFFEAD9B8); // Saturn brand color (hovered / enabled / selected) - public static Property ACCENT = Property.color(0xFFE2BF7D); + public static final Property ACCENT = Property.color(0xFFE2BF7D); // Text/icons shown on accent background - public static Property ACCENT_FG = Property.color(0xFF1A140A); + public static final Property ACCENT_FG = Property.color(0xFF1A140A); // Misc colors - public static Property SCROLL = Property.color(0xFFE2BF7D); + public static final Property SCROLL = Property.color(0xFFE2BF7D); // UI metrics - public static Property BG_RADIUS = Property.integer(12); - public static Property WIDGET_RADIUS = Property.integer(10); + public static final Property BG_RADIUS = Property.integer(12); + public static final Property WIDGET_RADIUS = Property.integer(10); // Font (keeping your current system) public static Property FONT = Property.font(1); @@ -42,7 +42,7 @@ public class Theme { config.property("Background Radius", BG_RADIUS); config.property("Widget Radius", WIDGET_RADIUS); - config.property("Font", WIDGET_RADIUS); + config.property("Font", FONT); } public static int getBg(boolean hover) { From 6ee2fc3b0e373160ef27b0083c9bc3146b75a0da Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 20:42:04 +0100 Subject: [PATCH 45/82] Improved theme usability --- .../saturnclient/config/Theme.java | 22 +++++++++++++++++++ .../mixin/SplashOverlayMixin.java | 20 +++++++---------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index a5125ae..5023458 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -45,11 +45,33 @@ public class Theme { 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; } + + /** + * Sets alpha using 0–255 range. + */ + public static int withAlpha(int alpha, int color) { + alpha = Math.max(0, Math.min(255, alpha)); + return (color & 0x00FFFFFF) | (alpha << 24); + } + + /** + * Sets alpha using 0.0–1.0 range. + */ + public static int withAlpha(float alpha, int color) { + alpha = Math.max(0f, Math.min(1f, alpha)); + return withAlpha((int) (alpha * 255f), color); + } } diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index 846b1cc..4570254 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -3,6 +3,7 @@ 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; @@ -40,10 +41,6 @@ public abstract class SplashOverlayMixin { @Shadow(remap = false) private float progress; - private static int withAlpha(int color, int alpha) { - return color & 0xFFFFFF | alpha << 24; - } - /** * @reason Replace vanilla splash rendering completely but keep exit logic */ @@ -80,19 +77,17 @@ public abstract class SplashOverlayMixin { if (fadeOutProgress >= 1.0F) { renderCurrentScreen(context, mouseX, mouseY, delta); int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f); - // context.fill(0, 0, screenWidth, screenHeight, - // withAlpha(0x53389E, overlayAlpha)); scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, - withAlpha(0xFFFFFF, overlayAlpha)); + Theme.withAlpha(0xFFFFFFFF, overlayAlpha)); } else if (reloading) { if (reloadProgress < 1.0F) { renderCurrentScreen(context, mouseX, mouseY, delta); } scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, - withAlpha(0xFFFFFF, alpha)); + Theme.withAlpha(0xFFFFFFFF, alpha)); } else { scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, - withAlpha(0xFFFFFF, alpha)); + Theme.withAlpha(0xFFFFFFFF, alpha)); } float u = this.reload.getProgress(); @@ -120,18 +115,19 @@ public abstract class SplashOverlayMixin { /** 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, - withAlpha(0xFFFFFF, alpha)); + Theme.withAlpha(0xFFFFFFFF, alpha)); } 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, withAlpha(0xFFFFFF, alpha)); + context.drawBorder((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(0xFFFFFFFF, alpha)); int progressWidth = Math.min((int) (progress * 120), 120); int progressX = (screenWidth - 120) / 2; - context.fill(progressX, progressY, progressX + progressWidth, progressY + 5, withAlpha(0xFFFFFF, alpha)); + context.fill(progressX, progressY, progressX + progressWidth, progressY + 5, + Theme.withAlpha(0xFFFFFFFF, alpha)); } /** Compute overlay alpha from fade progress */ From 4403a31c936abffe091a4b1bb1b9948b2e44df16 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 20:59:07 +0100 Subject: [PATCH 46/82] Fix splashoverlay and saturnmodule --- .../saturnclient/config/Theme.java | 19 ++- .../mixin/SplashOverlayMixin.java | 12 +- .../ui2/components/SaturnModule.java | 132 +++++++++++++----- 3 files changed, 121 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/saturnclient/config/Theme.java index 5023458..29cb887 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Theme.java @@ -60,18 +60,29 @@ public class Theme { } /** - * Sets alpha using 0–255 range. + * Multiplies existing alpha with 0–255 alpha. */ public static int withAlpha(int alpha, int color) { alpha = Math.max(0, Math.min(255, alpha)); - return (color & 0x00FFFFFF) | (alpha << 24); + + int originalAlpha = (color >>> 24) & 0xFF; + + // Merge (multiply) alphas + int merged = (originalAlpha * alpha) / 255; + + return (color & 0x00FFFFFF) | (merged << 24); } /** - * Sets alpha using 0.0–1.0 range. + * 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)); - return withAlpha((int) (alpha * 255f), color); + + int originalAlpha = (color >>> 24) & 0xFF; + + int merged = (int) (originalAlpha * alpha); + + return (color & 0x00FFFFFF) | (merged << 24); } } diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index 4570254..48b0b19 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -78,16 +78,16 @@ public abstract class SplashOverlayMixin { renderCurrentScreen(context, mouseX, mouseY, delta); int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f); scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, - Theme.withAlpha(0xFFFFFFFF, overlayAlpha)); + 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(0xFFFFFFFF, alpha)); + Theme.withAlpha(alpha, 0xFFFFFFFF)); } else { scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, - Theme.withAlpha(0xFFFFFFFF, alpha)); + Theme.withAlpha(alpha, 0xFFFFFFFF)); } float u = this.reload.getProgress(); @@ -115,19 +115,19 @@ public abstract class SplashOverlayMixin { /** 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(0xFFFFFFFF, alpha)); + 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(0xFFFFFFFF, alpha)); + 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(0xFFFFFFFF, alpha)); + Theme.withAlpha(alpha, 0xFFFFFFFF)); } /** Compute overlay alpha from fade progress */ diff --git a/src/main/java/org/saturnclient/ui2/components/SaturnModule.java b/src/main/java/org/saturnclient/ui2/components/SaturnModule.java index 60d27e5..87da97d 100644 --- a/src/main/java/org/saturnclient/ui2/components/SaturnModule.java +++ b/src/main/java/org/saturnclient/ui2/components/SaturnModule.java @@ -14,14 +14,8 @@ import org.saturnclient.ui2.screens.ConfigEditor; import net.minecraft.client.render.RenderLayer; public class SaturnModule extends Element { - // TODO make these states - // static { - // theme.propertyStateDefault("enabled", "bg", 0xFF1d202d); - // theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee); - // theme.propertyStateDefault("hovering", "fg", 0xFF845eee); - // } - private Module mod; + private final Module mod; public SaturnModule(Module mod) { this.mod = mod; @@ -31,44 +25,111 @@ public class SaturnModule extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - int p = 10; - int s = 14; - boolean settingsHover = false; + int p = 10; // padding + int s = 14; // settings icon size + + boolean hovered = ctx.isHovering(); + boolean enabled = mod.isEnabled(); + + boolean settingsHover = Utils.isHovering( + ctx.mouseX - (width - s - p - 5), + ctx.mouseY - (height - s - p - 5), + s + 10, + s + 10, + 1.0f); renderScope.setRenderLayer(RenderLayer::getGuiTextured); - // TODO use this - if (mod.isEnabled()) { - } + // Resolve theme colors + int bgColor = Theme.PRIMARY.value; + int fgColor = hovered && !settingsHover ? Theme.ACCENT.value : Theme.PRIMARY_FG.value; - renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value); + int iconBg = enabled ? Theme.ACCENT.value : Theme.PRIMARY.value; + int iconFg = enabled ? Theme.ACCENT_FG.value : fgColor; + + int settingsColor = settingsHover + ? Theme.ACCENT.value + : Theme.withAlpha(160, fgColor); int h = height - (p * 2); - renderScope.drawRoundedRectangle(p, p, h, h, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.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, - Theme.PRIMARY_FG.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, Theme.FONT.value, Theme.PRIMARY_FG.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(), Theme.FONT.value) * 0.35f), p + 1, Theme.FONT.value, - Theme.PRIMARY_FG.value); + // Module name + renderScope.drawText( + 0.6f, + mod.getName(), + p + h + 4, + p + 1, + Theme.FONT.value, + fgColor); + // Version (right aligned) + String version = mod.getVersion(); + int versionWidth = (int) (Fonts.getWidth(version, Theme.FONT.value) * 0.35f); + + renderScope.drawText( + 0.35f, + version, + width - p - versionWidth, + p + 1, + Theme.FONT.value, + fgColor); + + // Tags int xt = p + h + 4; for (String tag : mod.getTags()) { - int xtb = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f); - renderScope.drawRoundedRectangle(xt, p + (h / 2), xtb + 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 += xtb + 9; + int tagWidth = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f); + + renderScope.drawRoundedRectangle( + xt, + p + (h / 2), + tagWidth + 6, + 12, + 5, + Theme.ACCENT.value); + + renderScope.drawText( + 0.4f, + tag, + xt + 2, + p + (h / 2) + 3, + Theme.FONT.value, + Theme.ACCENT_FG.value); + + xt += tagWidth + 9; } - // TODO - if (settingsHover) { - } - - renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, Theme.PRIMARY_FG.value); + // Settings icon + renderScope.drawTexture( + Textures.SETTINGS, + width - s - p, + height - s - p, + 0, 0, + s, s, + settingsColor); } @Override @@ -76,10 +137,17 @@ public class SaturnModule extends Element { int p = 10; int s = 14; - if (Utils.isHovering(mouseX - (width - s - p - 5), mouseY - (height - s - p - 5), s + 10, s + 10, 1.0f)) { + boolean settingsHover = Utils.isHovering( + mouseX - (width - s - p - 5), + mouseY - (height - s - p - 5), + s + 10, + s + 10, + 1.0f); + + if (settingsHover) { SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig())); } else { mod.setEnabled(!mod.isEnabled()); } } -} +} \ No newline at end of file From bba12146ea8dad0a3c522ff921cfea74a42949c3 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 22:17:59 +0100 Subject: [PATCH 47/82] HexInput --- .../java/org/saturnclient/ui2/Element.java | 9 + .../org/saturnclient/ui2/RenderScope.java | 41 +++ .../ui2/components/dialog/ColorDialog.java | 246 ------------------ .../ui2/components/inputs/HexInput.java | 172 +++++++++++- .../ui2/screens/ConfigEditor.java | 2 +- 5 files changed, 212 insertions(+), 258 deletions(-) delete mode 100644 src/main/java/org/saturnclient/ui2/components/dialog/ColorDialog.java diff --git a/src/main/java/org/saturnclient/ui2/Element.java b/src/main/java/org/saturnclient/ui2/Element.java index 4e4e00c..ec8b110 100644 --- a/src/main/java/org/saturnclient/ui2/Element.java +++ b/src/main/java/org/saturnclient/ui2/Element.java @@ -32,6 +32,15 @@ public class Element { public void charTyped(char typedChar) { } + public void mouseClicked(double mouseX, double mouseY, int button) { + } + + public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + } + + public void mouseReleased(double mouseX, double mouseY, int button) { + } + public Element dimensions(int width, int height) { this.width = width; this.height = height; diff --git a/src/main/java/org/saturnclient/ui2/RenderScope.java b/src/main/java/org/saturnclient/ui2/RenderScope.java index 231428c..5cd7f59 100644 --- a/src/main/java/org/saturnclient/ui2/RenderScope.java +++ b/src/main/java/org/saturnclient/ui2/RenderScope.java @@ -398,4 +398,45 @@ public class RenderScope { 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); + } } diff --git a/src/main/java/org/saturnclient/ui2/components/dialog/ColorDialog.java b/src/main/java/org/saturnclient/ui2/components/dialog/ColorDialog.java deleted file mode 100644 index 9567218..0000000 --- a/src/main/java/org/saturnclient/ui2/components/dialog/ColorDialog.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java b/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java index 025bfd9..8a1864e 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java @@ -1,32 +1,182 @@ package org.saturnclient.ui2.components.inputs; -import org.saturnclient.saturnclient.SaturnClient; +import java.awt.Color; + import org.saturnclient.saturnclient.config.Property; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; -import org.saturnclient.ui2.SaturnScreen; -import org.saturnclient.ui2.components.dialog.ColorDialog; public class HexInput extends Element { public Property prop; - public SaturnScreen screen; - public HexInput(Property 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 = 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; + + public HexInput(Property prop) { + this.prop = prop; this.width = 58; this.height = 20; - this.prop = prop; - this.scale = 0.8f; - this.screen = screen; + syncFromColor(); } @Override public void click(int mouseX, int mouseY) { - SaturnClient.client.setScreen(new ColorDialog(prop, screen)); + open = !open; + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (open && mouseX >= width + 8 && mouseX <= width && mouseY >= 0 && mouseY <= height) { + handlePickerClick(mouseX - width, mouseY); + } + } + + @Override + public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (open) + handlePickerDrag(mouseX - width, mouseY); + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) { + draggingOpacity = false; } @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.drawRoundedRectangle(0, 0, width, height, 17, prop.value); + // Draw the main color rectangle + renderScope.drawRoundedRectangle(0, 0, width, height, 6, prop.value); + + if (!open) + return; + + // Draw the picker relative to (width + 8, 0) + int pickerX = width + 8; + int pickerY = 0; + + renderColorSquare(renderScope, pickerX, pickerY); + renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10); + renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10); } -} + + private void renderColorSquare(RenderScope renderScope, int xStart, int yStart) { + for (int x = 0; x < PICKER_WIDTH; x++) { + for (int y = 0; y < PICKER_HEIGHT; y++) { + float saturation = x / (float) PICKER_WIDTH; + float value = 1f - y / (float) PICKER_HEIGHT; + int color = Color.HSBtoRGB(hue, saturation, value); + int alpha = (int) (opacity * 255) & 0xFF; + color = (alpha << 24) | (color & 0xFFFFFF); + renderScope.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, color); + } + } + + // Draw selection square + int selX = xStart + selectedX - 4; + int selY = yStart + selectedY - 4; + renderScope.fill(selX, selY, selX + 8, selY + 8, 0xFF000000 | prop.value); + renderScope.drawBorder(selX, selY, 8, 8, 0xFFFFFFFF); + } + + private void renderOpacitySlider(RenderScope renderScope, int xStart, int yStart) { + for (int x = 0; x < SLIDER_WIDTH; x++) { + float localOpacity = x / (float) SLIDER_WIDTH; + int alpha = (int) (localOpacity * 255) & 0xFF; + int color = (alpha << 24) | (prop.value & 0xFFFFFF); + renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color); + } + + int knobX = xStart + (int) (opacity * SLIDER_WIDTH); + renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000); + } + + private void renderHueSlider(RenderScope renderScope, int xStart, int yStart) { + for (int x = 0; x < SLIDER_WIDTH; x++) { + float localHue = x / (float) SLIDER_WIDTH; + int color = Color.HSBtoRGB(localHue, 1f, 1f); + renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color); + } + + int knobX = xStart + (int) (hue * SLIDER_WIDTH); + renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000); + } + + private void handlePickerClick(double mouseX, double mouseY) { + // Color square + if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) { + selectedX = (int) mouseX; + selectedY = (int) mouseY; + updateColorFromSelection(); + } + + // Opacity slider + int opY = -SLIDER_HEIGHT - 10; + if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= opY && mouseY <= opY + SLIDER_HEIGHT) { + updateOpacity(mouseX); + draggingOpacity = true; + } + + // Hue slider + int hueY = PICKER_HEIGHT + 10; + if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= hueY && mouseY <= hueY + HUE_SLIDER_HEIGHT) { + updateHue(mouseX); + } + } + + private void handlePickerDrag(double mouseX, double mouseY) { + // Opacity + if (draggingOpacity) + updateOpacity(mouseX); + + // Color square + if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) { + selectedX = (int) mouseX; + selectedY = (int) mouseY; + updateColorFromSelection(); + } + } + + private void updateColorFromSelection() { + float saturation = selectedX / (float) PICKER_WIDTH; + float value = 1f - selectedY / (float) PICKER_HEIGHT; + int color = Color.HSBtoRGB(hue, saturation, value); + int alpha = (int) (opacity * 255) & 0xFF; + prop.value = (alpha << 24) | (color & 0xFFFFFF); + } + + private void updateHue(double mouseX) { + hue = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH)); + updateColorFromSelection(); + } + + private void updateOpacity(double mouseX) { + opacity = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH)); + updateColorFromSelection(); + } + + private void syncFromColor() { + int rgb = prop.value & 0xFFFFFF; + float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null); + hue = hsb[0]; + selectedX = (int) (hsb[1] * PICKER_WIDTH); + selectedY = (int) ((1f - hsb[2]) * PICKER_HEIGHT); + int alpha = (prop.value >> 24) & 0xFF; + if (alpha == 0) + alpha = 255; + opacity = alpha / 255f; + } +} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java b/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java index 7b00966..c56ed4a 100644 --- a/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java +++ b/src/main/java/org/saturnclient/ui2/screens/ConfigEditor.java @@ -78,7 +78,7 @@ public class ConfigEditor extends SaturnScreen { break; case HEX: - configScroll.draw(new HexInput((Property) prop, this).position(w / 2, modY)); + configScroll.draw(new HexInput((Property) prop).position(w / 2, modY)); break; case INTEGER: From 3db44bab85f4f9e0903befbd9ce444933682e623 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 22:37:56 +0100 Subject: [PATCH 48/82] Fixed HexInput and stuff --- .../org/saturnclient/ui2/SaturnScreen.java | 20 ++++++++++ .../ui2/components/ElementRenderer.java | 39 +++++++++++++------ .../ui2/components/inputs/HexInput.java | 9 +++-- .../org/saturnclient/ui2/elements/Scroll.java | 10 +++++ 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/SaturnScreen.java b/src/main/java/org/saturnclient/ui2/SaturnScreen.java index 05bde40..c6af321 100644 --- a/src/main/java/org/saturnclient/ui2/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui2/SaturnScreen.java @@ -129,6 +129,26 @@ public abstract class SaturnScreen extends Screen { return false; } + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + mouseX *= 2; + mouseY *= 2; + + ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY); + + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + mouseX *= 2; + mouseY *= 2; + + ElementRenderer.mouseReleased(elements, mouseX, mouseY, button); + + return super.mouseReleased(mouseX, mouseY, button); + } + @Override public boolean mouseScrolled( double mouseX, diff --git a/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java b/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java index d7b9a02..ee165da 100644 --- a/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java +++ b/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java @@ -14,7 +14,7 @@ public class ElementRenderer { synchronized (elements) { elements.add(element); } - + if (element.animation != null) { element.animation.init(element); @@ -44,7 +44,10 @@ public class ElementRenderer { int adjustedMouseX = (int) mouseX - element.x; int adjustedMouseY = (int) mouseY - element.y; - if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, element.height, element.scale)) { + element.mouseClicked(adjustedMouseX * element.scale, adjustedMouseY * element.scale, button); + + if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, + element.height, element.scale)) { element.focused = true; element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale)); } @@ -58,16 +61,17 @@ public class ElementRenderer { double mouseY, double horizontalAmount, double verticalAmount) { - for (Element element : new ArrayList<>(elements)) { - if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, element.scale)) { - element.scroll( - (int) mouseX - element.x, - (int) mouseY - element.y, - horizontalAmount, - verticalAmount); - } - } + for (Element element : new ArrayList<>(elements)) { + if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, + element.scale)) { + element.scroll( + (int) mouseX - element.x, + (int) mouseY - element.y, + horizontalAmount, + verticalAmount); } + } + } public static void keyPressed(List elements, int keyCode, int scanCode, int modifiers) { for (Element element : new ArrayList<>(elements)) { @@ -81,4 +85,17 @@ public class ElementRenderer { } } } + + public static void mouseDragged(List 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 elements, double mouseX, double mouseY, int button) { + for (Element element : new ArrayList<>(elements)) { + element.mouseReleased(mouseX - element.x, mouseY - element.y, button); + } + } } diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java b/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java index 8a1864e..f46e28a 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java @@ -40,15 +40,16 @@ public class HexInput extends Element { @Override public void mouseClicked(double mouseX, double mouseY, int button) { - if (open && mouseX >= width + 8 && mouseX <= width && mouseY >= 0 && mouseY <= height) { - handlePickerClick(mouseX - width, mouseY); + 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, mouseY); + if (open) { + handlePickerDrag(mouseX - width - 8, mouseY); + } } @Override diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index 7a751cc..4ebd219 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -117,4 +117,14 @@ public class Scroll extends Element { 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 - 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); + } } From 2096925a0280713933c593afa3ea0d1d625e43dd Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 22:42:21 +0100 Subject: [PATCH 49/82] Smaller HexInput --- .../org/saturnclient/ui2/components/inputs/HexInput.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java b/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java index f46e28a..aa49018 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/HexInput.java @@ -20,9 +20,9 @@ public class HexInput extends Element { private boolean draggingOpacity = false; // Dimensions - 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 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; From d3258edbc7d0e37326b08ee72d56450429c59a6d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 22:55:47 +0100 Subject: [PATCH 50/82] Cleaner Config --- .../saturnclient/config/ConfigManager.java | 178 +++++++++++------- .../saturnclient/config/Property.java | 9 + 2 files changed, 118 insertions(+), 69 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java b/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java index 4123bbf..87aab0e 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java +++ b/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java @@ -1,107 +1,86 @@ package org.saturnclient.saturnclient.config; import com.google.gson.*; +import org.saturnclient.saturnclient.SaturnClient; + import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.util.*; -import org.saturnclient.saturnclient.SaturnClient; +import java.util.LinkedHashMap; +import java.util.Map; public class ConfigManager { - private static final File configFile = new File(SaturnClient.client.runDirectory, "saturn.json"); - private static final Map>> properties = new HashMap<>(); - private static JsonObject cachedThemeJson = null; + + private static final File configFile = + new File(SaturnClient.client.runDirectory, "saturn.json"); + + // SINGLE ROOT TREE + private static final Map> ROOT = new LinkedHashMap<>(); + + private static JsonObject cachedJson = null; private final Map> currentMap; - private final String namespace; + /** + * Creates a root namespace + */ public ConfigManager(String namespace) { - this.namespace = namespace; this.currentMap = new LinkedHashMap<>(); - properties.put(namespace, currentMap); - 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) { - this.namespace = namespace; this.currentMap = new LinkedHashMap<>(); - Property>> nsProp = Property.namespace(currentMap); - parent.property(namespace, nsProp); - System.out.println("Created sub-namespace: " + namespace); - } - - public void sub(String name) { - property(name, Property.namespace(new LinkedHashMap<>())); + parent.property(namespace, Property.namespace(currentMap)); } + /** + * Add a property + */ public Property property(String name, Property value) { currentMap.put(name, value); - loadProp(name, value); + loadSingleProperty(name, value); return value; } - private void loadProp(String name, Property prop) { - JsonObject source = cachedThemeJson != null ? cachedThemeJson : loadAndCache(); - if (source == null) - return; - - JsonElement namespaceJson = source.get(namespace); - if (namespaceJson == null || !namespaceJson.isJsonObject()) - return; - - JsonElement value = namespaceJson.getAsJsonObject().get(name); - if (value != null) - prop.loadFromJson(value); - } - - private static JsonObject loadAndCache() { - try { - if (!configFile.exists()) { - Files.writeString(configFile.toPath(), "{}"); - return new JsonObject(); - } - String json = Files.readString(configFile.toPath()); - cachedThemeJson = JsonParser.parseString(json).getAsJsonObject(); - return cachedThemeJson; - } catch (IOException e) { - SaturnClient.LOGGER.error("Error reading config file", e); - return null; - } + /** + * Add empty namespace + */ + public ConfigManager sub(String name) { + return new ConfigManager(this, name); } + /** + * Load entire config + */ public static void load() { SaturnClient.LOGGER.info("Loading config..."); + JsonObject json = loadAndCache(); if (json == null) return; - properties.forEach((namespace, map) -> { - JsonElement nsElement = json.get(namespace); - if (nsElement != null && nsElement.isJsonObject()) { - loadProperties(nsElement.getAsJsonObject(), map); - } - }); - } - - private static void loadProperties(JsonObject json, Map> props) { - props.forEach((name, prop) -> { - JsonElement el = json.get(name); - if (el != null) - prop.loadFromJson(el); - }); + loadRecursive(json, ROOT); } + /** + * Save entire config + */ public static void save() { SaturnClient.LOGGER.info("Saving config..."); - JsonObject root = new JsonObject(); - properties.forEach((namespace, map) -> { - JsonObject nsJson = new JsonObject(); - saveProperties(nsJson, map); - root.add(namespace, nsJson); - }); + + JsonObject rootJson = new JsonObject(); + saveRecursive(rootJson, ROOT); try { - String formatted = new GsonBuilder().setPrettyPrinting().create().toJson(root); + String formatted = new GsonBuilder() + .setPrettyPrinting() + .create() + .toJson(rootJson); + Files.writeString(configFile.toPath(), formatted); SaturnClient.LOGGER.info("Config saved successfully."); } catch (IOException e) { @@ -109,11 +88,72 @@ public class ConfigManager { } } - private static void saveProperties(JsonObject json, Map> props) { - props.forEach((name, prop) -> json.add(name, prop.toJson())); + /** + * Recursive save + */ + private static void saveRecursive(JsonObject json, Map> 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> 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 + */ + private void loadSingleProperty(String name, Property prop) { + JsonObject source = cachedJson != null ? cachedJson : loadAndCache(); + if (source == null) + return; + + JsonElement element = source.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> getProperties() { return currentMap; } -} +} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/config/Property.java b/src/main/java/org/saturnclient/saturnclient/config/Property.java index 6d81da7..2d7dd2e 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/Property.java +++ b/src/main/java/org/saturnclient/saturnclient/config/Property.java @@ -286,4 +286,13 @@ public class Property { if (value != null) this.loadFromJson(value); } + + public boolean isNamespace() { + return type == PropertyType.NAMESPACE; + } + + @SuppressWarnings("unchecked") + public Map> getNamespaceMap() { + return isNamespace() ? (Map>) (Object) value : null; + } } From e62da29a4960c4692c9f2b06580349342ac981fa Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 21 Feb 2026 23:11:22 +0100 Subject: [PATCH 51/82] Sub namespace loading --- .../saturnclient/config/ConfigManager.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java b/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java index 87aab0e..bc628b1 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java +++ b/src/main/java/org/saturnclient/saturnclient/config/ConfigManager.java @@ -11,8 +11,7 @@ import java.util.Map; 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"); // SINGLE ROOT TREE private static final Map> ROOT = new LinkedHashMap<>(); @@ -21,11 +20,14 @@ public class ConfigManager { private final Map> currentMap; + private final String[] path; + /** * Creates a root namespace */ public ConfigManager(String namespace) { this.currentMap = new LinkedHashMap<>(); + this.path = new String[] { namespace }; // Path is just ["namespace"] ROOT.put(namespace, Property.namespace(currentMap)); } @@ -34,6 +36,12 @@ public class ConfigManager { */ public ConfigManager(ConfigManager parent, String namespace) { this.currentMap = new LinkedHashMap<>(); + + // Build new path: parent path + current namespace + this.path = new String[parent.path.length + 1]; + System.arraycopy(parent.path, 0, this.path, 0, parent.path.length); + this.path[this.path.length - 1] = namespace; + parent.property(namespace, Property.namespace(currentMap)); } @@ -121,16 +129,30 @@ public class ConfigManager { } /** - * Load only a single property immediately after creation + * 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; - JsonElement element = source.get(name); - if (element != null) + // 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); + } } /** From f952fa70958980436921f7f7ca7cd63a6d7d23d7 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 00:12:13 +0100 Subject: [PATCH 52/82] Better animation system --- .../java/org/saturnclient/ui2/Element.java | 6 ++ .../org/saturnclient/ui2/ElementContext.java | 4 +- .../org/saturnclient/ui2/SaturnScreen.java | 47 +++----------- .../org/saturnclient/ui2/anim/Animation.java | 64 +------------------ .../java/org/saturnclient/ui2/anim/Fade.java | 4 +- .../org/saturnclient/ui2/anim/FadeOut.java | 4 +- .../org/saturnclient/ui2/anim/SlideX.java | 2 +- .../saturnclient/ui2/anim/SlideXAbsolute.java | 2 +- .../org/saturnclient/ui2/anim/SlideY.java | 2 +- .../ui2/components/ElementRenderer.java | 11 ++-- .../org/saturnclient/ui2/elements/Scroll.java | 2 +- 11 files changed, 33 insertions(+), 115 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/Element.java b/src/main/java/org/saturnclient/ui2/Element.java index ec8b110..296ec7f 100644 --- a/src/main/java/org/saturnclient/ui2/Element.java +++ b/src/main/java/org/saturnclient/ui2/Element.java @@ -41,6 +41,12 @@ public class Element { public void mouseReleased(double mouseX, double mouseY, int button) { } + public void playAnimationFrame(long elapsed) { + if (this.animation != null) { + this.animation.tick(this.curve.apply(Math.min(1.0, (double) elapsed / this.animation.duration)), this); + } + } + public Element dimensions(int width, int height) { this.width = width; this.height = height; diff --git a/src/main/java/org/saturnclient/ui2/ElementContext.java b/src/main/java/org/saturnclient/ui2/ElementContext.java index 8f3f898..def84dd 100644 --- a/src/main/java/org/saturnclient/ui2/ElementContext.java +++ b/src/main/java/org/saturnclient/ui2/ElementContext.java @@ -4,13 +4,15 @@ public class ElementContext { public int mouseX, mouseY; public int elementWidth, elementHeight; public float elementScale; + public long elapsed; - public ElementContext(int mouseX, int mouseY, Element e) { + public ElementContext(long elapsed, int mouseX, int mouseY, Element e) { this.mouseX = mouseX - e.x; this.mouseY = mouseY - e.y; this.elementWidth = e.width; this.elementHeight = e.height; this.elementScale = e.scale; + this.elapsed = elapsed; } public boolean isHovering() { diff --git a/src/main/java/org/saturnclient/ui2/SaturnScreen.java b/src/main/java/org/saturnclient/ui2/SaturnScreen.java index c6af321..f88cbe6 100644 --- a/src/main/java/org/saturnclient/ui2/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui2/SaturnScreen.java @@ -1,11 +1,11 @@ package org.saturnclient.ui2; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import org.saturnclient.saturnclient.mixin.DrawContextAccessor; -import org.saturnclient.ui2.anim.Animation; -import org.saturnclient.ui2.anim.SlideXAbsolute; import org.saturnclient.ui2.components.ElementRenderer; import net.minecraft.client.MinecraftClient; @@ -23,6 +23,7 @@ public abstract class SaturnScreen extends Screen { private final Pool pool = new Pool(3); protected List elements = new ArrayList<>(); protected float backgroundOpacity = 1.0f; + protected Instant start = null; public int blurDuration = 700; public float blurProgress = 0.0f; public int backgroundBlur = 10; @@ -41,36 +42,7 @@ public abstract class SaturnScreen extends Screen { } public void draw(Element element) { - synchronized (elements) { - elements.add(element); - } - - if (element.animation != null) { - element.animation.init(element); - - Animation.execute((Float progress) -> { - element.animation.tick(progress, element); - }, element.animation.duration); - } - - if (element.duration != null) { - new Thread(() -> { - try { - Animation fadeOut = new SlideXAbsolute(700, -(width - element.x)); - Thread.sleep(element.duration); - fadeOut.init(element); - Animation.executeSync((Float progress) -> { - fadeOut.tick(progress, element); - }, fadeOut.duration); - synchronized (elements) { - elements.remove(element); - } - - } catch (InterruptedException e) { - e.printStackTrace(); - } - }).start(); - } + ElementRenderer.draw(elements, element); } @Override @@ -79,10 +51,6 @@ public abstract class SaturnScreen extends Screen { width *= 2; height *= 2; ui(); // abstraction to render the saturn ui and also render extra stuff here - - Animation.execute((Float progress) -> { - blurProgress = progress; - }, blurDuration); } public abstract void ui(); @@ -90,6 +58,11 @@ public abstract class SaturnScreen extends Screen { @SuppressWarnings("deprecation") @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { + if (start == null) { + start = Instant.now(); + } + long elapsed = Duration.between(start, Instant.now()).toMillis(); + mouseX *= 2; mouseY *= 2; @@ -114,7 +87,7 @@ public abstract class SaturnScreen extends Screen { renderScope.matrices.scale(0.5f, 0.5f, 0.5f); - ElementRenderer.render(new ArrayList<>(elements), renderScope, mouseX, mouseY); + ElementRenderer.render(new ArrayList<>(elements), elapsed, renderScope, mouseX, mouseY); renderScope.matrices.pop(); } diff --git a/src/main/java/org/saturnclient/ui2/anim/Animation.java b/src/main/java/org/saturnclient/ui2/anim/Animation.java index d1ed85d..269cbcf 100644 --- a/src/main/java/org/saturnclient/ui2/anim/Animation.java +++ b/src/main/java/org/saturnclient/ui2/anim/Animation.java @@ -1,8 +1,5 @@ package org.saturnclient.ui2.anim; -import java.util.function.Consumer; -import java.util.function.Function; - import org.saturnclient.ui2.Element; public abstract class Animation { @@ -12,64 +9,7 @@ public abstract class Animation { this.duration = duration; } - public abstract void tick(float progress, Element element); + public abstract void tick(double progress, Element element); + public abstract void init(Element element); - - public static void execute(Consumer tick, int duration) { - new Thread(() -> { - Function 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 tick, int duration) { - Function 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(); - } - } - } - } } diff --git a/src/main/java/org/saturnclient/ui2/anim/Fade.java b/src/main/java/org/saturnclient/ui2/anim/Fade.java index 47dfd65..b20bade 100644 --- a/src/main/java/org/saturnclient/ui2/anim/Fade.java +++ b/src/main/java/org/saturnclient/ui2/anim/Fade.java @@ -10,8 +10,8 @@ public class Fade extends Animation { } @Override - public void tick(float progress, Element element) { - element.opacity = targetOpacity * progress; + public void tick(double progress, Element element) { + element.opacity = targetOpacity * (float) progress; } @Override diff --git a/src/main/java/org/saturnclient/ui2/anim/FadeOut.java b/src/main/java/org/saturnclient/ui2/anim/FadeOut.java index a1fb42a..77d93f6 100644 --- a/src/main/java/org/saturnclient/ui2/anim/FadeOut.java +++ b/src/main/java/org/saturnclient/ui2/anim/FadeOut.java @@ -10,8 +10,8 @@ public class FadeOut extends Animation { } @Override - public void tick(float progress, Element element) { - element.opacity = targetOpacity - progress; + public void tick(double progress, Element element) { + element.opacity = targetOpacity - (float) progress; } @Override diff --git a/src/main/java/org/saturnclient/ui2/anim/SlideX.java b/src/main/java/org/saturnclient/ui2/anim/SlideX.java index 595c412..58d5e8c 100644 --- a/src/main/java/org/saturnclient/ui2/anim/SlideX.java +++ b/src/main/java/org/saturnclient/ui2/anim/SlideX.java @@ -24,7 +24,7 @@ public class SlideX extends Animation { } @Override - public void tick(float progress, Element element) { + public void tick(double progress, Element element) { element.x = elementX + (int) (offset * progress); } diff --git a/src/main/java/org/saturnclient/ui2/anim/SlideXAbsolute.java b/src/main/java/org/saturnclient/ui2/anim/SlideXAbsolute.java index ace746f..75a3121 100644 --- a/src/main/java/org/saturnclient/ui2/anim/SlideXAbsolute.java +++ b/src/main/java/org/saturnclient/ui2/anim/SlideXAbsolute.java @@ -24,7 +24,7 @@ public class SlideXAbsolute extends Animation { } @Override - public void tick(float progress, Element element) { + public void tick(double progress, Element element) { element.x = elementX + (int) (offset * progress); } diff --git a/src/main/java/org/saturnclient/ui2/anim/SlideY.java b/src/main/java/org/saturnclient/ui2/anim/SlideY.java index 48ca61b..9d91f3b 100644 --- a/src/main/java/org/saturnclient/ui2/anim/SlideY.java +++ b/src/main/java/org/saturnclient/ui2/anim/SlideY.java @@ -26,7 +26,7 @@ public class SlideY extends Animation { } @Override - public void tick(float progress, Element element) { + public void tick(double progress, Element element) { element.y = elementY + (int) (offset * progress); } diff --git a/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java b/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java index ee165da..30842a1 100644 --- a/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java +++ b/src/main/java/org/saturnclient/ui2/components/ElementRenderer.java @@ -7,7 +7,6 @@ import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.Utils; -import org.saturnclient.ui2.anim.Animation; public class ElementRenderer { public static void draw(List elements, Element element) { @@ -17,20 +16,18 @@ public class ElementRenderer { if (element.animation != null) { element.animation.init(element); - - Animation.execute((Float progress) -> { - element.animation.tick(progress, element); - }, element.animation.duration); } } - public static void render(List elements, RenderScope renderScope, int mouseX, int mouseY) { + public static void render(List elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) { for (Element element : elements) { + element.playAnimationFrame(elapsed); + renderScope.matrices.push(); renderScope.setOpacity(element.opacity); renderScope.matrices.translate(element.x, element.y, 0); renderScope.matrices.scale(element.scale, element.scale, 1.0f); - element.render(renderScope, new ElementContext(mouseX, mouseY, element)); + element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, element)); renderScope.matrices.pop(); renderScope.setRenderLayer(null); } diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index 4ebd219..e02e379 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -36,7 +36,7 @@ public class Scroll extends Element { renderScope.matrices.push(); renderScope.matrices.translate(padding, -scroll + padding, 0); - ElementRenderer.render(children, renderScope, + ElementRenderer.render(children, ctx.elapsed, renderScope, ctx.mouseX - padding, ctx.mouseY - padding + scroll); From 7de3d5c9bc0d516d2f86ef72e5c5080d93a793d1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 00:20:04 +0100 Subject: [PATCH 53/82] Fixed keybinding crashing when invalid --- .../saturnclient/mixin/SplashOverlayMixin.java | 1 + .../ui2/components/inputs/KeybindingSelector.java | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index 48b0b19..b5c0c3b 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -42,6 +42,7 @@ public abstract class SplashOverlayMixin { private float progress; /** + * @author SaturnClient * @reason Replace vanilla splash rendering completely but keep exit logic */ @Overwrite diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java b/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java index d65ebfd..f85d76e 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/KeybindingSelector.java @@ -21,9 +21,12 @@ public class KeybindingSelector extends Element { public void render(RenderScope renderScope, ElementContext ctx) { int textColor = focused ? 0xFFFFFF : 0xAAAAAA; renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000); - renderScope.drawText(0.6f, prop.value == -1 ? "" : - GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)).toUpperCase(), - 4, 4, Theme.FONT.value, textColor); + String name = GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)); + + renderScope.drawText(0.6f, + prop.value == -1 ? "" + : name != null ? name.toUpperCase() : "", + 4, 4, Theme.FONT.value, textColor); } @Override From 2747ad366eed5686206a77b0041070df75e39fdd Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 01:06:33 +0100 Subject: [PATCH 54/82] Fixed title menu freezing --- .../saturnclient/SaturnClient.java | 4 ++++ .../org/saturnclient/ui2/SaturnScreen.java | 20 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/SaturnClient.java b/src/main/java/org/saturnclient/saturnclient/SaturnClient.java index 7a6c803..1894a58 100644 --- a/src/main/java/org/saturnclient/saturnclient/SaturnClient.java +++ b/src/main/java/org/saturnclient/saturnclient/SaturnClient.java @@ -41,6 +41,10 @@ public class SaturnClient implements ModInitializer { SaturnClientConfig.init(); ModManager.init(); + client.execute(() -> { + SaturnScreen.preload(client); + }); + ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save()); SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> { diff --git a/src/main/java/org/saturnclient/ui2/SaturnScreen.java b/src/main/java/org/saturnclient/ui2/SaturnScreen.java index f88cbe6..ac39d7f 100644 --- a/src/main/java/org/saturnclient/ui2/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui2/SaturnScreen.java @@ -15,6 +15,8 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.RotatingCubeMapRenderer; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.render.DefaultFramebufferSet; +import net.minecraft.client.texture.ResourceTexture; +import net.minecraft.client.texture.TextureManager; import net.minecraft.client.util.Pool; import net.minecraft.text.Text; import net.minecraft.util.Identifier; @@ -28,8 +30,9 @@ public abstract class SaturnScreen extends Screen { public float blurProgress = 0.0f; public int backgroundBlur = 10; - protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer( - Identifier.of("saturnclient", "textures/gui/title/background/panorama")); + protected static final Identifier PANORAMA = Identifier.of("saturnclient", + "textures/gui/title/background/panorama"); + protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(PANORAMA); public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER; @@ -37,6 +40,18 @@ public abstract class SaturnScreen extends Screen { ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER); } + public static void preload(MinecraftClient client) { + TextureManager textureManager = client.getTextureManager(); + + // Force upload each texture to GPU + for (int i = 0; i < 6; i++) { + String var10001 = PANORAMA.getPath(); + Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png"); + ResourceTexture resourceTexture = new ResourceTexture(tex); + textureManager.registerTexture(tex, resourceTexture); + } + } + public SaturnScreen(String title) { super(Text.literal(title)); } @@ -73,6 +88,7 @@ public abstract class SaturnScreen extends Screen { PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect( Identifier.ofVanilla("blur"), DefaultFramebufferSet.MAIN_ONLY); + if (postEffectProcessor != null) { postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress); postEffectProcessor.render(this.client.getFramebuffer(), this.pool); From 37ec729d1df2acde740bf46c3554444ef0e63825 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 01:11:12 +0100 Subject: [PATCH 55/82] Blurring --- src/main/java/org/saturnclient/ui2/SaturnScreen.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/SaturnScreen.java b/src/main/java/org/saturnclient/ui2/SaturnScreen.java index ac39d7f..29651de 100644 --- a/src/main/java/org/saturnclient/ui2/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui2/SaturnScreen.java @@ -26,8 +26,6 @@ public abstract class SaturnScreen extends Screen { protected List elements = new ArrayList<>(); protected float backgroundOpacity = 1.0f; protected Instant start = null; - public int blurDuration = 700; - public float blurProgress = 0.0f; public int backgroundBlur = 10; protected static final Identifier PANORAMA = Identifier.of("saturnclient", @@ -90,7 +88,7 @@ public abstract class SaturnScreen extends Screen { DefaultFramebufferSet.MAIN_ONLY); if (postEffectProcessor != null) { - postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress); + postEffectProcessor.setUniforms("Radius", backgroundBlur * Math.min((float) elapsed / 700, 1.0f)); postEffectProcessor.render(this.client.getFramebuffer(), this.pool); } From 049c92785d4ba28e299fb2ebfa9fcbfc95524cec Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 01:25:22 +0100 Subject: [PATCH 56/82] Fixed bugs and restructured roles --- .../saturnclient/client/player/Roles.java | 59 +++++++++++++++++++ .../client/player/SaturnPlayer.java | 4 ++ .../config/SaturnClientConfig.java | 53 ----------------- .../cosmetics/CloakFeatureRenderer.java | 8 ++- .../saturnclient/mixin/NameTagMixin.java | 10 ++-- .../saturnclient/mixin/TabListMixin.java | 6 +- 6 files changed, 77 insertions(+), 63 deletions(-) create mode 100644 src/main/java/org/saturnclient/saturnclient/client/player/Roles.java diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/Roles.java b/src/main/java/org/saturnclient/saturnclient/client/player/Roles.java new file mode 100644 index 0000000..8771960 --- /dev/null +++ b/src/main/java/org/saturnclient/saturnclient/client/player/Roles.java @@ -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 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 ? "" : ""; + } +} diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index 5f18c51..a6caf95 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -4,6 +4,7 @@ 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 { @@ -22,14 +23,17 @@ public class SaturnPlayer { 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) diff --git a/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java b/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java index cd09f65..5f35a38 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java +++ b/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java @@ -1,12 +1,8 @@ package org.saturnclient.saturnclient.config; -import java.util.Map; -import java.util.UUID; - import org.lwjgl.glfw.GLFW; import org.saturnclient.ui2.resources.Textures; -import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; public class SaturnClientConfig { @@ -17,59 +13,10 @@ public class SaturnClientConfig { public static Property bendyCloaks = Property.bool(true); public static Property openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B); - static enum Role { - OWNER, - ADMIN, - PARTNER, - CONTRIBUTOR - } - - private static final Map ROLES = Map.of( - UUID.fromString("d362a042-28bd-49e1-a807-ae74dbe8aba9"), - Role.OWNER); - public static Identifier getLogo() { return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO; } - public static String getSaturnIndicator() { - return realisticLogo.value ? "" : ""; - } - - /* - * Gets the icon color of a specific individual, here are the different colors - * - * - Owner: Dark Red - * - Admin: Red - * - Partners: Gold - * - Contributor: Aqua - * - Other/player: White - */ - public static Formatting getIconColor(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 void init() { config = new ConfigManager("Saturn Client"); config.property("Realistic logo", realisticLogo); diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java index 9f4344f..bafc998 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/CloakFeatureRenderer.java @@ -283,14 +283,16 @@ public class CloakFeatureRenderer extends FeatureRenderer { UUID uuid = isSaturn(state); if (uuid != null) { - MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator()) - .styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))); + MutableText iconText = Text.literal(Roles.getSaturnIndicator()) + .styled(style -> style.withColor(Roles.getIconColor(uuid))); Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE)); @@ -59,7 +59,9 @@ public abstract class NameTagMixin { if (state instanceof PlayerEntityRenderState) { String name = ((PlayerEntityRenderState) state).name; SaturnPlayer player = SaturnPlayer.get(name); - return player.uuid; + if (player != null) { + return player.uuid; + } } return null; diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java index 93513cc..e830a3e 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java @@ -7,8 +7,8 @@ import net.minecraft.util.Formatting; import java.util.UUID; import org.jetbrains.annotations.Nullable; +import org.saturnclient.saturnclient.client.player.Roles; import org.saturnclient.saturnclient.client.player.SaturnPlayer; -import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; @@ -34,8 +34,8 @@ public abstract class TabListMixin { UUID uuid = profile.getId(); if (SaturnPlayer.get(uuid) != null) { - return Text.literal(SaturnClientConfig.getSaturnIndicator()) - .styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid))) + return Text.literal(Roles.getSaturnIndicator()) + .styled(style -> style.withColor(Roles.getIconColor(uuid))) .append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE))); } return Text.literal(name); From f46f9c474edec53bb665b7a65107c25b4a16d550 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 01:34:54 +0100 Subject: [PATCH 57/82] Animation Stagger element --- .../ui2/elements/AnimationStagger.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java diff --git a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java new file mode 100644 index 0000000..ce2e1cd --- /dev/null +++ b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java @@ -0,0 +1,52 @@ +package org.saturnclient.ui2.elements; + +import java.util.ArrayList; +import java.util.List; + +import org.saturnclient.ui2.Element; +import org.saturnclient.ui2.ElementContext; +import org.saturnclient.ui2.RenderScope; +import org.saturnclient.ui2.components.ElementRenderer; + +public class AnimationStagger extends Element { + protected List children = new ArrayList<>(); + public int duration; + + public AnimationStagger(int duration) { + this.duration = duration; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + ElementRenderer.render(children, ctx.elapsed, renderScope, + ctx.mouseX, + ctx.mouseY); + } + + @Override + public void click(int mouseX, int mouseY) { + ElementRenderer.mouseClicked(children, mouseX, mouseY, 0); + } + + public void draw(Element element) { + if (element.animation != null) { + element.animation.duration = duration * (children.size() + 1); + } + children.add(element); + } + + @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); + } +} From 265b44187b2aca48e878e23ef42b14a54109c992 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 01:38:30 +0100 Subject: [PATCH 58/82] Animation stagger delay --- src/main/java/org/saturnclient/ui2/Element.java | 2 +- src/main/java/org/saturnclient/ui2/anim/Animation.java | 1 + .../org/saturnclient/ui2/elements/AnimationStagger.java | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/Element.java b/src/main/java/org/saturnclient/ui2/Element.java index 296ec7f..1d41d3d 100644 --- a/src/main/java/org/saturnclient/ui2/Element.java +++ b/src/main/java/org/saturnclient/ui2/Element.java @@ -42,7 +42,7 @@ public class Element { } public void playAnimationFrame(long elapsed) { - if (this.animation != null) { + if (this.animation != null && elapsed >= this.animation.delay) { this.animation.tick(this.curve.apply(Math.min(1.0, (double) elapsed / this.animation.duration)), this); } } diff --git a/src/main/java/org/saturnclient/ui2/anim/Animation.java b/src/main/java/org/saturnclient/ui2/anim/Animation.java index 269cbcf..5fa328b 100644 --- a/src/main/java/org/saturnclient/ui2/anim/Animation.java +++ b/src/main/java/org/saturnclient/ui2/anim/Animation.java @@ -4,6 +4,7 @@ import org.saturnclient.ui2.Element; public abstract class Animation { public int duration; + public int delay = 0; Animation(int duration) { this.duration = duration; diff --git a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java index ce2e1cd..5cd6e93 100644 --- a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java +++ b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java @@ -10,10 +10,10 @@ import org.saturnclient.ui2.components.ElementRenderer; public class AnimationStagger extends Element { protected List children = new ArrayList<>(); - public int duration; + public int delay; - public AnimationStagger(int duration) { - this.duration = duration; + public AnimationStagger(int delay) { + this.delay = delay; } @Override @@ -30,7 +30,7 @@ public class AnimationStagger extends Element { public void draw(Element element) { if (element.animation != null) { - element.animation.duration = duration * (children.size() + 1); + element.animation.delay = delay * (children.size() + 1); } children.add(element); } From 53732c5df35a37b589cf7f03e5bc09678f126e4f Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 02:19:04 +0100 Subject: [PATCH 59/82] Animation stagger and a few slide fixes --- .../java/org/saturnclient/ui2/Element.java | 3 +- .../java/org/saturnclient/ui2/anim/Fade.java | 4 +-- .../org/saturnclient/ui2/anim/SlideFade.java | 32 +++++++++++++++++++ .../org/saturnclient/ui2/anim/SlideY.java | 17 +++++----- .../ui2/elements/AnimationStagger.java | 4 +-- .../saturnclient/ui2/screens/TitleMenu.java | 24 ++++++++------ 6 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/saturnclient/ui2/anim/SlideFade.java diff --git a/src/main/java/org/saturnclient/ui2/Element.java b/src/main/java/org/saturnclient/ui2/Element.java index 1d41d3d..d00b80f 100644 --- a/src/main/java/org/saturnclient/ui2/Element.java +++ b/src/main/java/org/saturnclient/ui2/Element.java @@ -43,7 +43,8 @@ public class Element { 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.duration)), this); + this.animation.tick(this.curve + .apply(Math.min(1.0, (double) (elapsed - this.animation.delay) / this.animation.duration)), this); } } diff --git a/src/main/java/org/saturnclient/ui2/anim/Fade.java b/src/main/java/org/saturnclient/ui2/anim/Fade.java index b20bade..db75ae6 100644 --- a/src/main/java/org/saturnclient/ui2/anim/Fade.java +++ b/src/main/java/org/saturnclient/ui2/anim/Fade.java @@ -11,12 +11,12 @@ public class Fade extends Animation { @Override public void tick(double progress, Element element) { - element.opacity = targetOpacity * (float) progress; + element.opacity = Math.max(0.1f, targetOpacity * (float) progress); } @Override public void init(Element element) { targetOpacity = element.opacity; - element.opacity = 0; + element.opacity = 0.1f; } } diff --git a/src/main/java/org/saturnclient/ui2/anim/SlideFade.java b/src/main/java/org/saturnclient/ui2/anim/SlideFade.java new file mode 100644 index 0000000..c407cc0 --- /dev/null +++ b/src/main/java/org/saturnclient/ui2/anim/SlideFade.java @@ -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); + } +} diff --git a/src/main/java/org/saturnclient/ui2/anim/SlideY.java b/src/main/java/org/saturnclient/ui2/anim/SlideY.java index 9d91f3b..5ca5a97 100644 --- a/src/main/java/org/saturnclient/ui2/anim/SlideY.java +++ b/src/main/java/org/saturnclient/ui2/anim/SlideY.java @@ -5,7 +5,7 @@ import org.saturnclient.ui2.Element; public class SlideY extends Animation { int offset = 20; int maxOffset = 20; - int elementY; + double startY; public SlideY(int duration) { super(duration); @@ -24,15 +24,16 @@ public class SlideY extends Animation { this.offset = offset; this.maxOffset = offset; } - - @Override - public void tick(double progress, Element element) { - element.y = elementY + (int) (offset * progress); - } @Override public void init(Element element) { - element.y -= offset; - elementY = element.y; + startY = element.y - offset; + element.y = (int) startY; + } + + @Override + public void tick(double progress, Element element) { + double currentY = startY + offset * progress; + element.y = (int) currentY; } } diff --git a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java index 5cd6e93..b716b9c 100644 --- a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java +++ b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java @@ -30,9 +30,9 @@ public class AnimationStagger extends Element { public void draw(Element element) { if (element.animation != null) { - element.animation.delay = delay * (children.size() + 1); + element.animation.delay = delay * children.size(); } - children.add(element); + ElementRenderer.draw(children, element); } @Override diff --git a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java index 7f0636a..aa17223 100644 --- a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java @@ -7,7 +7,9 @@ import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.screens.cosmetics.CloakMenu; import org.saturnclient.ui2.SaturnScreen; import org.saturnclient.ui2.anim.Fade; +import org.saturnclient.ui2.anim.SlideFade; import org.saturnclient.ui2.anim.SlideY; +import org.saturnclient.ui2.elements.AnimationStagger; import org.saturnclient.ui2.elements.Button; import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.TextureButton; @@ -19,7 +21,7 @@ import net.minecraft.client.gui.screen.world.SelectWorldScreen; public class TitleMenu extends SaturnScreen { public TitleMenu() { - super("Tile Menu"); + super("Title Menu"); } @Override @@ -31,21 +33,25 @@ public class TitleMenu extends SaturnScreen { draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -82) .animation(new SlideY(700, -20))); - draw(new Button("SINGLEPLAYER", () -> { + AnimationStagger stagger = new AnimationStagger(120); + + stagger.draw(new Button("SINGLEPLAYER", () -> { 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", () -> { + stagger.draw(new Button("MULTIPLAYER", () -> { 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", () -> { + stagger.draw(new Button("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", () -> { + stagger.draw(new Button("QUIT", () -> { 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(stagger.dimensions(289, 143).centerOffset(width, height, 0, 60)); int s = Fonts.getHeight() + 20; From 44d7aa3641a3d7ebe7a01b6808cb9497427ec9d5 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 02:31:56 +0100 Subject: [PATCH 60/82] Hoo lee sheet --- .../saturnclient/ui2/screens/TitleMenu.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java index aa17223..ebffa12 100644 --- a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java @@ -33,38 +33,42 @@ public class TitleMenu extends SaturnScreen { draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -82) .animation(new SlideY(700, -20))); - AnimationStagger stagger = new AnimationStagger(120); + AnimationStagger mainButtonStagger = new AnimationStagger(120); - stagger.draw(new Button("SINGLEPLAYER", () -> { + mainButtonStagger.draw(new Button("SINGLEPLAYER", () -> { client.setScreen(new SelectWorldScreen(new TitleMenu())); }).scale(0.69f).dimensions(420, 52).position(0, 0).animation(new SlideFade(1000, -10))); - stagger.draw(new Button("MULTIPLAYER", () -> { + mainButtonStagger.draw(new Button("MULTIPLAYER", () -> { client.setScreen(new MultiplayerScreen(new TitleMenu())); }).scale(0.69f).dimensions(420, 52).position(0, 40).animation(new SlideFade(1000, -10))); - stagger.draw(new Button("OPTIONS", () -> { + mainButtonStagger.draw(new Button("OPTIONS", () -> { client.setScreen(new OptionsScreen(new TitleMenu(), SaturnClient.client.options)); }).scale(0.69f).dimensions(206, 52).position(0, 80).animation(new SlideFade(1000, -10))); - stagger.draw(new Button("QUIT", () -> { + mainButtonStagger.draw(new Button("QUIT", () -> { client.scheduleStop(); }).scale(0.69f).dimensions(206, 52).position(146, 80).animation(new SlideFade(1000, -10))); - draw(stagger.dimensions(289, 143).centerOffset(width, height, 0, 60)); + draw(mainButtonStagger.dimensions(289, 143).centerOffset(width, height, 0, 60)); 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()); - }).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()); - }).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()); - }).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)); } } From 85d4a1de389479e521a81aad6b0f8e64c3bdd2d2 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 02:44:58 +0100 Subject: [PATCH 61/82] Animated mods --- .../ui2/elements/AnimationStagger.java | 18 +++++++++--------- .../org/saturnclient/ui2/elements/Scroll.java | 18 +++++++++--------- .../org/saturnclient/ui2/screens/ModMenu.java | 9 ++++++++- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java index b716b9c..d72d62a 100644 --- a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java +++ b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java @@ -16,6 +16,13 @@ public class AnimationStagger extends Element { this.delay = delay; } + public void draw(Element element) { + 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, @@ -24,15 +31,8 @@ public class AnimationStagger extends Element { } @Override - public void click(int mouseX, int mouseY) { - ElementRenderer.mouseClicked(children, mouseX, mouseY, 0); - } - - public void draw(Element element) { - if (element.animation != null) { - element.animation.delay = delay * children.size(); - } - ElementRenderer.draw(children, element); + public void mouseClicked(double mouseX, double mouseY, int button) { + ElementRenderer.mouseClicked(children, mouseX, mouseY, button); } @Override diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index e02e379..b39616a 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -25,6 +25,10 @@ public class Scroll extends Element { this.padding = padding; } + public void draw(Element element) { + ElementRenderer.draw(children, element); + } + @Override public void render(RenderScope renderScope, ElementContext ctx) { calculateMaxScroll(); @@ -64,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 public Element dimensions(int width, int height) { maxScroll = 0; @@ -118,6 +113,11 @@ public class Scroll extends Element { ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers); } + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + ElementRenderer.mouseClicked(children, mouseX, mouseY, 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); diff --git a/src/main/java/org/saturnclient/ui2/screens/ModMenu.java b/src/main/java/org/saturnclient/ui2/screens/ModMenu.java index 8b414a6..83041b1 100644 --- a/src/main/java/org/saturnclient/ui2/screens/ModMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/ModMenu.java @@ -3,8 +3,10 @@ package org.saturnclient.ui2.screens; import org.saturnclient.modules.ModManager; import org.saturnclient.modules.Module; import org.saturnclient.ui2.SaturnScreen; +import org.saturnclient.ui2.anim.SlideFade; import org.saturnclient.ui2.components.SaturnModule; import org.saturnclient.ui2.components.Sidebar; +import org.saturnclient.ui2.elements.AnimationStagger; import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.Scroll; import org.saturnclient.ui2.resources.Textures; @@ -24,8 +26,11 @@ public class ModMenu extends SaturnScreen { int row = 0; int col = 0; + AnimationStagger moduleStagger = new AnimationStagger(80); + 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) { col = 0; @@ -35,6 +40,8 @@ public class ModMenu extends SaturnScreen { } } + scroll.draw(moduleStagger); + int scrollWidth = 480 + 10 + (g * 2) + (p * 2); draw(scroll.dimensions(scrollWidth, 350).center(width, height)); From 377031c0a9696d6072afd8bf6fa5036802108dc5 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 03:20:16 +0100 Subject: [PATCH 62/82] Fixed click bugs --- .../ui2/components/SaturnModule.java | 48 ++++++++++++------- .../ui2/components/inputs/Toggle.java | 2 +- .../org/saturnclient/ui2/elements/Scroll.java | 2 +- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/components/SaturnModule.java b/src/main/java/org/saturnclient/ui2/components/SaturnModule.java index 87da97d..21c8b1c 100644 --- a/src/main/java/org/saturnclient/ui2/components/SaturnModule.java +++ b/src/main/java/org/saturnclient/ui2/components/SaturnModule.java @@ -17,6 +17,10 @@ public class SaturnModule extends Element { private final Module mod; + static int p = 10; // padding + static int s = 14; // settings icon size + static int expand = 5; // settings icon click area + public SaturnModule(Module mod) { this.mod = mod; this.width = 160; @@ -25,18 +29,17 @@ public class SaturnModule extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - int p = 10; // padding - int s = 14; // settings icon size - boolean hovered = ctx.isHovering(); boolean enabled = mod.isEnabled(); - boolean settingsHover = Utils.isHovering( - ctx.mouseX - (width - s - p - 5), - ctx.mouseY - (height - s - p - 5), - s + 10, - s + 10, - 1.0f); + int iconX = width - s - p; + int iconY = height - s - p; + + boolean settingsHover = ctx.isHovering( + getSettingsX(), + getSettingsY(), + getSettingsHitBox(), + getSettingsHitBox()); renderScope.setRenderLayer(RenderLayer::getGuiTextured); @@ -125,8 +128,8 @@ public class SaturnModule extends Element { // Settings icon renderScope.drawTexture( Textures.SETTINGS, - width - s - p, - height - s - p, + iconX, + iconY, 0, 0, s, s, settingsColor); @@ -134,14 +137,11 @@ public class SaturnModule extends Element { @Override public void click(int mouseX, int mouseY) { - int p = 10; - int s = 14; - boolean settingsHover = Utils.isHovering( - mouseX - (width - s - p - 5), - mouseY - (height - s - p - 5), - s + 10, - s + 10, + mouseX - getSettingsX(), + mouseY - getSettingsY(), + getSettingsHitBox(), + getSettingsHitBox(), 1.0f); if (settingsHover) { @@ -150,4 +150,16 @@ public class SaturnModule extends Element { 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; + } } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java b/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java index c0cfcf6..e692ad9 100644 --- a/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java +++ b/src/main/java/org/saturnclient/ui2/components/inputs/Toggle.java @@ -18,7 +18,7 @@ public class Toggle extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { renderScope.drawRoundedRectangle(0, 0, width, height, height, - prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value); + Theme.withAlpha(0.5f, prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value)); renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value); } diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index b39616a..68bde0b 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -115,7 +115,7 @@ public class Scroll extends Element { @Override public void mouseClicked(double mouseX, double mouseY, int button) { - ElementRenderer.mouseClicked(children, mouseX, mouseY, button); + ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, button); } @Override From cd5cac7f6923697d1b5a1ba347ba0dac4db145af Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 03:36:39 +0100 Subject: [PATCH 63/82] Shift menu animations --- .../saturnclient/ui2/screens/ShiftMenu.java | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java b/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java index 5d5448f..350a6a1 100644 --- a/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/ShiftMenu.java @@ -5,8 +5,8 @@ import org.saturnclient.saturnclient.config.SaturnClientConfig; import org.saturnclient.saturnclient.menus.HudEditor; import org.saturnclient.ui2.resources.Textures; import org.saturnclient.ui2.screens.cosmetics.CloakMenu; -import org.saturnclient.ui2.Element; import org.saturnclient.ui2.SaturnScreen; +import org.saturnclient.ui2.elements.AnimationStagger; import org.saturnclient.ui2.elements.Button; import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.TextureButton; @@ -20,21 +20,42 @@ public class ShiftMenu extends SaturnScreen { @Override public void ui() { - draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -36).animation(new Fade(700))); - draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -80).animation(new SlideY(700, -20))); + // Logo animations (unchanged) + draw(new ImageTexture(Textures.LOGO_TEXT) + .dimensions(98, 10) + .centerOffset(width, height, 0, -36) + .animation(new Fade(700))); - Element button = new Button("Settings", () -> { - SaturnClient.client.setScreen(new ModMenu()); - }).center(width, height); + draw(new ImageTexture(SaturnClientConfig.getLogo()) + .dimensions(98, 98) + .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, () -> { - client.setScreen(new CloakMenu()); - }).dimensions(button.height, button.height).centerOffset(width, height, (button.width / 2) + (button.height / 2) + 4, 0)); + // Create stagger + AnimationStagger stagger = new AnimationStagger(120); - draw(new TextureButton(Textures.HUD_ICON, () -> { + stagger.draw(new TextureButton(Textures.HUD_ICON, () -> { 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)); } } From cd84866ee809c220f703772505b1aba44d50e3c6 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 03:44:22 +0100 Subject: [PATCH 64/82] Optional staggering --- .../saturnclient/config/SaturnClientConfig.java | 2 ++ .../org/saturnclient/ui2/elements/AnimationStagger.java | 6 ++++++ src/main/java/org/saturnclient/ui2/elements/Scroll.java | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java b/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java index 5f35a38..0b9a628 100644 --- a/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java +++ b/src/main/java/org/saturnclient/saturnclient/config/SaturnClientConfig.java @@ -12,6 +12,7 @@ public class SaturnClientConfig { public static Property saturnTitleScreen = Property.bool(true); public static Property bendyCloaks = Property.bool(true); public static Property openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B); + public static Property stagger = Property.bool(true); public static Identifier getLogo() { return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO; @@ -23,6 +24,7 @@ public class SaturnClientConfig { config.property("Saturn client title screen", saturnTitleScreen); config.property("Open Emote Wheel", openEmoteWheel); config.property("Bendy Cloaks", bendyCloaks); + config.property("Stagger Animations", stagger); // Initialize a sub namespace for theme Theme.init(config); diff --git a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java index d72d62a..3865481 100644 --- a/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java +++ b/src/main/java/org/saturnclient/ui2/elements/AnimationStagger.java @@ -3,6 +3,7 @@ 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; @@ -17,9 +18,14 @@ public class AnimationStagger extends Element { } 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); } diff --git a/src/main/java/org/saturnclient/ui2/elements/Scroll.java b/src/main/java/org/saturnclient/ui2/elements/Scroll.java index 68bde0b..3114af6 100644 --- a/src/main/java/org/saturnclient/ui2/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui2/elements/Scroll.java @@ -83,7 +83,7 @@ public class Scroll extends Element { int calculateScrollBarHeight() { if (maxScroll <= 0) return height - (scrollBarPadding.value * 2); - return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); + return Math.max(20, (height * height) / (height + maxScroll + 10)) - (scrollBarPadding.value * 2); } int calculateScrollBarY() { From d32f1249743ec451688dae815bb1d2c5ffac4846 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 04:27:30 +0100 Subject: [PATCH 65/82] Removed todos --- .../java/org/saturnclient/saturnclient/cosmetics/Hats.java | 5 +---- .../saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java index 0d2b7cf..ced3e77 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/Hats.java @@ -24,10 +24,7 @@ public class Hats { public static void setHat(UUID uuid, String hatName) { SaturnPlayer player = SaturnPlayer.get(uuid); - if (player == null) { - // Auth.players.put(uuid, new SaturnPlayer("", hatName)); - // TODO: address this behaviour - } else { + if (player != null) { player.hat = hatName; } } diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java index 32e3283..d624942 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java @@ -76,10 +76,7 @@ public class Cloaks { public static void setCloak(UUID uuid, String cloakName) { SaturnPlayer player = SaturnPlayer.get(uuid); - if (player == null) { - // Auth.players.put(uuid, new SaturnPlayer(cloakName, null)); - // TODO: Address this functionality - } else { + if (player != null) { player.cloak = cloakName; } From 7fc05df240a00a214bb303d095b967e7cd1a5988 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 14:35:57 +0100 Subject: [PATCH 66/82] Svg image rendering --- build.gradle | 3 + .../ui2/resources/SvgTexture.java | 65 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/main/java/org/saturnclient/ui2/resources/SvgTexture.java diff --git a/build.gradle b/build.gradle index 3fcc938..208e588 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,9 @@ dependencies { implementation 'com.github.selimaj-dev:session-java:0.1.4' + modImplementation "org.apache.xmlgraphics:batik-transcoder:1.19" + modImplementation "org.apache.xmlgraphics:batik-codec:1.19" + include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") //You might want bendy-lib. playerAnimator will wrap it. // include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}") diff --git a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java new file mode 100644 index 0000000..f1baacc --- /dev/null +++ b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java @@ -0,0 +1,65 @@ +package org.saturnclient.ui2.resources; + +import java.awt.image.BufferedImage; +import java.io.InputStream; + +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.jetbrains.annotations.Nullable; +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.Identifier; + +public class SvgTexture { + private static BufferedImage renderSvg(InputStream svgStream, float width, float height) throws Exception { + TranscoderInput input = new TranscoderInput(svgStream); + + final BufferedImage[] imagePointer = new BufferedImage[1]; + + ImageTranscoder transcoder = new ImageTranscoder() { + + @Override + public BufferedImage createImage(int w, int h) { + return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + } + + @Override + public void writeImage(BufferedImage img, org.apache.batik.transcoder.TranscoderOutput out) { + imagePointer[0] = img; + } + }; + + transcoder.addTranscodingHint(ImageTranscoder.KEY_WIDTH, width); + transcoder.addTranscodingHint(ImageTranscoder.KEY_HEIGHT, height); + + transcoder.transcode(input, null); + + return imagePointer[0]; + } + + @Nullable + @SuppressWarnings("resource") + public static Identifier getSvg(MinecraftClient client, Identifier svgImage, int width, int height, float scale) { + try (InputStream svgStream = client + .getResourceManager() + .getResource(svgImage).get() + .getInputStream()) { + + BufferedImage image = renderSvg(svgStream, width, height); + + System.out.println(svgImage.toString()); + + Identifier id = Identifier.of(SaturnClient.MOD_ID, "img_svg_render_" + (int) (Math.random() * 30)); + IdentifierUtils.registerBufferedImageTexture(id, image); + + return id; + + } catch (Exception e) { + e.printStackTrace(); + + return null; + } + } +} From 66b96102f8e0e7f1749c896e675020093a18fbee Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 16:41:26 +0100 Subject: [PATCH 67/82] Working svg renderer --- build.gradle | 8 ++-- .../ui2/resources/SvgTexture.java | 44 +++++++++---------- .../saturnclient/ui2/screens/TitleMenu.java | 6 +++ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index 208e588..1266b7f 100644 --- a/build.gradle +++ b/build.gradle @@ -22,8 +22,8 @@ dependencies { implementation 'com.github.selimaj-dev:session-java:0.1.4' - modImplementation "org.apache.xmlgraphics:batik-transcoder:1.19" - modImplementation "org.apache.xmlgraphics:batik-codec:1.19" + // implementation("com.kitfox.svg:svg-salamander:1.1.3") + implementation 'com.formdev:svgSalamander:1.1.4' include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") //You might want bendy-lib. playerAnimator will wrap it. @@ -32,11 +32,13 @@ dependencies { repositories { - maven { url 'https://maven.fabricmc.net/' } // Fabric's official repo + maven { url 'https://maven.fabricmc.net/' } maven { url 'https://jitpack.io' } maven { url "https://maven.parchmentmc.org" } maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" } maven { name = "KosmX maven"; url = 'https://maven.kosmx.dev/' } + + mavenCentral() } processResources { diff --git a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java index f1baacc..c7a6482 100644 --- a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java +++ b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java @@ -1,10 +1,13 @@ package org.saturnclient.ui2.resources; +import java.awt.Graphics2D; +import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.InputStream; -import org.apache.batik.transcoder.TranscoderInput; -import org.apache.batik.transcoder.image.ImageTranscoder; +import com.kitfox.svg.SVGDiagram; +import com.kitfox.svg.SVGUniverse; + import org.jetbrains.annotations.Nullable; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils; @@ -13,30 +16,26 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.util.Identifier; public class SvgTexture { - private static BufferedImage renderSvg(InputStream svgStream, float width, float height) throws Exception { - TranscoderInput input = new TranscoderInput(svgStream); - final BufferedImage[] imagePointer = new BufferedImage[1]; + private static BufferedImage renderSvg(InputStream svgStream, int width, int height) throws Exception { + // Create SVGUniverse and load the SVG + SVGUniverse universe = new SVGUniverse(); + java.net.URI uri = universe.loadSVG(svgStream, "tempSvg"); + SVGDiagram diagram = universe.getDiagram(uri); - ImageTranscoder transcoder = new ImageTranscoder() { + // Create a BufferedImage to render the SVG + BufferedImage image = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); - @Override - public BufferedImage createImage(int w, int h) { - return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); - } + // Set the diagram's viewport to match the desired size + diagram.setIgnoringClipHeuristic(true); + diagram.setDeviceViewport(new Rectangle(0, 0, width, height)); - @Override - public void writeImage(BufferedImage img, org.apache.batik.transcoder.TranscoderOutput out) { - imagePointer[0] = img; - } - }; + // Render the SVG onto the BufferedImage + diagram.render(g); - transcoder.addTranscodingHint(ImageTranscoder.KEY_WIDTH, width); - transcoder.addTranscodingHint(ImageTranscoder.KEY_HEIGHT, height); - - transcoder.transcode(input, null); - - return imagePointer[0]; + g.dispose(); + return image; } @Nullable @@ -58,8 +57,7 @@ public class SvgTexture { } catch (Exception e) { e.printStackTrace(); - return null; } } -} +} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java index ebffa12..a132199 100644 --- a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java @@ -14,10 +14,12 @@ import org.saturnclient.ui2.elements.Button; import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.TextureButton; import org.saturnclient.ui2.resources.Fonts; +import org.saturnclient.ui2.resources.SvgTexture; import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; import net.minecraft.client.gui.screen.option.OptionsScreen; import net.minecraft.client.gui.screen.world.SelectWorldScreen; +import net.minecraft.util.Identifier; public class TitleMenu extends SaturnScreen { public TitleMenu() { @@ -70,5 +72,9 @@ public class TitleMenu extends SaturnScreen { }).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)); + + draw(new ImageTexture( + SvgTexture.getSvg(client, Identifier.of("saturnclient", "textures/gui/test.svg"), 98, 98, 1.0f)) + .dimensions(98, 98)); } } From 3c27d3b377bc1ce5e0e5f4e86bc3ec261c1ff13d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 16:41:37 +0100 Subject: [PATCH 68/82] Working svg renderer --- src/main/resources/assets/saturnclient/textures/gui/test.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/resources/assets/saturnclient/textures/gui/test.svg diff --git a/src/main/resources/assets/saturnclient/textures/gui/test.svg b/src/main/resources/assets/saturnclient/textures/gui/test.svg new file mode 100644 index 0000000..15e0aff --- /dev/null +++ b/src/main/resources/assets/saturnclient/textures/gui/test.svg @@ -0,0 +1 @@ + \ No newline at end of file From 9caf47d81b112fc3ac22278559ef1c696d42cb09 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 18:11:58 +0100 Subject: [PATCH 69/82] Fixed svg issues --- .../ui2/elements/ImageTexture.java | 14 +++++++-- .../ui2/resources/SvgTexture.java | 29 +++++++++++++------ .../saturnclient/ui2/screens/TitleMenu.java | 6 ---- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/saturnclient/ui2/elements/ImageTexture.java b/src/main/java/org/saturnclient/ui2/elements/ImageTexture.java index b5d9426..037ef31 100644 --- a/src/main/java/org/saturnclient/ui2/elements/ImageTexture.java +++ b/src/main/java/org/saturnclient/ui2/elements/ImageTexture.java @@ -3,8 +3,9 @@ package org.saturnclient.ui2.elements; import org.saturnclient.ui2.Element; import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.RenderScope; +import org.saturnclient.ui2.resources.SvgTexture; -import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.MinecraftClient; import net.minecraft.util.Identifier; public class ImageTexture extends Element { @@ -16,7 +17,16 @@ public class ImageTexture extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.setRenderLayer(RenderLayer::getGuiTextured); + if (sprite.toString().endsWith(".svg")) { + MinecraftClient client = MinecraftClient.getInstance(); + + // Get the actual window pixel dimensions for the image + int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWidth()); + int renderHeight = (int) (height * client.getWindow().getFramebufferHeight() / client.getWindow().getHeight()); + + sprite = SvgTexture.getSvg(client, sprite, renderWidth * 2, renderHeight * 2); + } + renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height); } } diff --git a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java index c7a6482..438a190 100644 --- a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java +++ b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java @@ -1,7 +1,7 @@ package org.saturnclient.ui2.resources; import java.awt.Graphics2D; -import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.InputStream; @@ -18,20 +18,31 @@ import net.minecraft.util.Identifier; public class SvgTexture { private static BufferedImage renderSvg(InputStream svgStream, int width, int height) throws Exception { - // Create SVGUniverse and load the SVG + // Load SVG SVGUniverse universe = new SVGUniverse(); java.net.URI uri = universe.loadSVG(svgStream, "tempSvg"); SVGDiagram diagram = universe.getDiagram(uri); - // Create a BufferedImage to render the SVG - BufferedImage image = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB); + // 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(); - // Set the diagram's viewport to match the desired size - diagram.setIgnoringClipHeuristic(true); - diagram.setDeviceViewport(new Rectangle(0, 0, width, height)); + // Enable anti-aliasing for smooth scaling + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - // Render the SVG onto the BufferedImage + // Apply scaling transform + g.scale(scaleX, scaleY); + + // Render SVG diagram.render(g); g.dispose(); @@ -40,7 +51,7 @@ public class SvgTexture { @Nullable @SuppressWarnings("resource") - public static Identifier getSvg(MinecraftClient client, Identifier svgImage, int width, int height, float scale) { + public static Identifier getSvg(MinecraftClient client, Identifier svgImage, int width, int height) { try (InputStream svgStream = client .getResourceManager() .getResource(svgImage).get() diff --git a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java index a132199..ebffa12 100644 --- a/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui2/screens/TitleMenu.java @@ -14,12 +14,10 @@ import org.saturnclient.ui2.elements.Button; import org.saturnclient.ui2.elements.ImageTexture; import org.saturnclient.ui2.elements.TextureButton; import org.saturnclient.ui2.resources.Fonts; -import org.saturnclient.ui2.resources.SvgTexture; import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; import net.minecraft.client.gui.screen.option.OptionsScreen; import net.minecraft.client.gui.screen.world.SelectWorldScreen; -import net.minecraft.util.Identifier; public class TitleMenu extends SaturnScreen { public TitleMenu() { @@ -72,9 +70,5 @@ public class TitleMenu extends SaturnScreen { }).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)); - - draw(new ImageTexture( - SvgTexture.getSvg(client, Identifier.of("saturnclient", "textures/gui/test.svg"), 98, 98, 1.0f)) - .dimensions(98, 98)); } } From 0cfa7b81ef022391528844ff7ce7069949a8440c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 23:40:39 +0100 Subject: [PATCH 70/82] SVG mod icons --- .../org/saturnclient/ui2/RenderScope.java | 8 ++++ .../ui2/resources/SvgTexture.java | 15 ++++++-- .../saturnclient/ui2/resources/Textures.java | 2 +- .../saturnclient/textures/gui/mod/armor.png | Bin 675 -> 0 bytes .../saturnclient/textures/gui/mod/armor.svg | 1 + .../saturnclient/textures/gui/mod/coords.png | Bin 526 -> 0 bytes .../saturnclient/textures/gui/mod/coords.svg | 1 + .../textures/gui/mod/crosshair.png | Bin 1027 -> 0 bytes .../textures/gui/mod/crosshair.svg | 1 + .../saturnclient/textures/gui/mod/day.png | Bin 294 -> 0 bytes .../saturnclient/textures/gui/mod/day.svg | 1 + .../saturnclient/textures/gui/mod/effect.png | Bin 2580 -> 0 bytes .../saturnclient/textures/gui/mod/effect.svg | 1 + .../saturnclient/textures/gui/mod/fps.png | Bin 478 -> 0 bytes .../saturnclient/textures/gui/mod/fps.svg | 1 + .../textures/gui/mod/freelook.png | Bin 959 -> 0 bytes .../textures/gui/mod/freelook.svg | 1 + .../textures/gui/mod/fullbright.png | Bin 697 -> 0 bytes .../textures/gui/mod/fullbright.svg | 1 + .../saturnclient/textures/gui/mod/health.png | Bin 962 -> 0 bytes .../saturnclient/textures/gui/mod/health.svg | 1 + .../textures/gui/mod/keystrokes.png | Bin 3226 -> 0 bytes .../textures/gui/mod/keystrokes.svg | 1 + .../saturnclient/textures/gui/mod/nofog.png | Bin 282 -> 0 bytes .../saturnclient/textures/gui/mod/nofog.svg | 1 + .../textures/gui/mod/particles.png | Bin 1087 -> 0 bytes .../textures/gui/mod/particles.svg | 1 + .../saturnclient/textures/gui/mod/speed.png | Bin 1032 -> 0 bytes .../saturnclient/textures/gui/mod/speed.svg | 1 + .../saturnclient/textures/gui/mod/sprint.png | Bin 961 -> 0 bytes .../saturnclient/textures/gui/mod/sprint.svg | 35 ++++++++++++++++++ .../saturnclient/textures/gui/mod/zoom.png | Bin 1736 -> 0 bytes .../saturnclient/textures/gui/mod/zoom.svg | 1 + .../assets/saturnclient/textures/gui/test.svg | 1 - 34 files changed, 69 insertions(+), 6 deletions(-) delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/armor.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/armor.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/coords.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/coords.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/crosshair.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/crosshair.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/day.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/day.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/effect.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/effect.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/fps.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/fps.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/freelook.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/freelook.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/fullbright.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/fullbright.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/health.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/health.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/keystrokes.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/keystrokes.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/nofog.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/nofog.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/particles.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/particles.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/speed.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/speed.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/sprint.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/sprint.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/zoom.png create mode 100644 src/main/resources/assets/saturnclient/textures/gui/mod/zoom.svg delete mode 100644 src/main/resources/assets/saturnclient/textures/gui/test.svg diff --git a/src/main/java/org/saturnclient/ui2/RenderScope.java b/src/main/java/org/saturnclient/ui2/RenderScope.java index 5cd7f59..1f846e6 100644 --- a/src/main/java/org/saturnclient/ui2/RenderScope.java +++ b/src/main/java/org/saturnclient/ui2/RenderScope.java @@ -9,11 +9,14 @@ import java.util.function.Function; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; import org.saturnclient.ui2.resources.Fonts; +import org.saturnclient.ui2.resources.SvgTexture; + import com.mojang.blaze3d.systems.RenderSystem; import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.mixin.DrawContextAccessor; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer.TextLayerType; import net.minecraft.client.gui.DrawContext; @@ -224,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, float v2, int color) { + + if (sprite.toString().endsWith(".svg")) { + sprite = SvgTexture.getSvg(MinecraftClient.getInstance(), sprite, (x2 - x1) * 2, (y2 - y1) * 2); + } + if (color == 0) return; x1 *= 4; diff --git a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java index 438a190..937a79e 100644 --- a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java +++ b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java @@ -4,12 +4,13 @@ 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.SaturnClient; import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils; import net.minecraft.client.MinecraftClient; @@ -17,6 +18,8 @@ import net.minecraft.util.Identifier; public class SvgTexture { + private static List svgCache = new ArrayList<>(); + private static BufferedImage renderSvg(InputStream svgStream, int width, int height) throws Exception { // Load SVG SVGUniverse universe = new SVGUniverse(); @@ -52,6 +55,13 @@ public class SvgTexture { @Nullable @SuppressWarnings("resource") 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() @@ -59,9 +69,6 @@ public class SvgTexture { BufferedImage image = renderSvg(svgStream, width, height); - System.out.println(svgImage.toString()); - - Identifier id = Identifier.of(SaturnClient.MOD_ID, "img_svg_render_" + (int) (Math.random() * 30)); IdentifierUtils.registerBufferedImageTexture(id, image); return id; diff --git a/src/main/java/org/saturnclient/ui2/resources/Textures.java b/src/main/java/org/saturnclient/ui2/resources/Textures.java index cdaac79..35a238c 100644 --- a/src/main/java/org/saturnclient/ui2/resources/Textures.java +++ b/src/main/java/org/saturnclient/ui2/resources/Textures.java @@ -53,7 +53,7 @@ public class Textures { } public static Identifier getModIcon(String modId) { - return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".png"); + return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".svg"); } } diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/armor.png b/src/main/resources/assets/saturnclient/textures/gui/mod/armor.png deleted file mode 100644 index 3d1c75e62fe47fff5b6b3f0bbe587370da0762b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 675 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz07Sip>6gA}f5OZv>fz{Kn6;usRa z`F8qw?c6|#w(kcYh#4)r!>P=sVR+=slWTJ&6OvLCf|FF3)w=c{Ff|E!<<@yoSM3jH zmx_w2eOur2%Tc?pX06?O?bdTK=J>sD^Xp&N&fj`#?q+NC({?+xQopYcTfI2tcu3vu zpp|c?r>Z``Q6qQLbUu$4?}lXgU)k?-?wpQ_*D`;>Zq>7`r?Ba`q}3y^1WY- z_cC0+=#j@|^2k-%W7DAvUnM%yW|kH0<(s9EGG}M+ zxUO^dT;Mw9IV-R7-Meo!gFQ(4=!*c$v*!%gG0Qwz9ch;LB{h0-;-z=CR`ED zDz-ixylHLM!KckP1^gZ!*rUK1Y8|N9-ub7mtsu4};*T=Zakq-g?@QKCxNGuBJum#= zlmhWxrV8#~?y^qoSDk8YQdDll{dCT27WNBE87G%XZoR>n=9|Gev-IfM1#f+Kv0v!> z=A-pjd(!>RzbnMoD*ik$OLc!+y;^Qq3Y%H;lft%Fa))ySSAUUuAfLfES9!Od#EI?8 z4O5$={>}N9wD`V9T-xqGmb!mSeJwxKf8WPkqP=6b \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/coords.png b/src/main/resources/assets/saturnclient/textures/gui/mod/coords.png deleted file mode 100644 index 5af14bf85154a3ec9adef2485e203130dd13b8d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 526 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz07Sip>6gA}f5OZp5{R^jR57!twx zcABjpvm;OIW)G2&4(=t(9Wpa_Fos&`HJ&hCQN*U9RLHEd&Vko8sE}d7!Ud6biwlz? zB~4~`hKppM+?cNRt@`oJpOU?7CqowG-j4oaV=Qx<_XD@qr(VCLi!o&k;!QbEc=yW- zF;o^j74$#USg*Nhqtl}^qB-`yLg!^KEY7^WO6Pm?j*~?j3hVcZ?w|f*`8RXl;@0~0 zZw*fOEPoNfuz^2{k#)=CTjFWk1v{8NdkeHqK5H!?p|v5H^@i%^Exz+N#fP6g5P#dC zv0jBKm)FnC%>6(kcW0!T=h*{hOnV!-3;h$s3zCk#Fv{E>J!Qey*am(1IqND5Z+1@H z?Yi`Kb(4no0^=Qe_Lc9ZFm9XEqO4%qmA>vlR_E6Dokl_|w%l10PB@qUy>(93Mct~I zq1xlz%4fM(Hj1@f-;#f!JS5K3w8-ZJYt@uVdp@1ATI(4hbJ|61#hTrx7Fq20*01o& u@`C-23G7$rG5 \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/crosshair.png b/src/main/resources/assets/saturnclient/textures/gui/mod/crosshair.png deleted file mode 100644 index cd208a312e25e9bef61db26c5f02d583457009a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1027 zcmV+e1pNDnP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFZAnByRA>e5nmdliL_Y9`;%uM@k$HdH==7RSNLQz}4=7CVcRT-VfC zg5PaT(p$Ner-(n zqmFcrPsDbLAun|ph&#R@n^Rl1@8OTsZBeC(!54pZ2&s@h{1a*O=5wQP1%mY zO|A6Vx$8H5x6TC6wd%B;r^0Lx=+Mbx+1}Kg_F6KnHUOQNS!9ca&8#WywPadt0DiGh zx6Q;>f`{gZv)4M9#u~Yq%X)x4hYni1Ziv*r8%2-T&`q>DsoXMT8vjAl>`zMd3BAzV zkNPBlW*@60LFi+;nFm`6Kp=qT9xeV2VGu-f4|kJZX!h~C2|_*LPpZip96Htd1IPdW z6I1#=B)y^8$)8-SC&mTYp6pR34zx~avGa$(WL1W2$!78KxqYIfqlb`dr)joL-ZtEX xYp|9dx+@2O;0y$#v&vfjYLJPzP1N)R{s2chGlfVd^7sG%002ovPDHLkV1lRc%Pjx^ diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/crosshair.svg b/src/main/resources/assets/saturnclient/textures/gui/mod/crosshair.svg new file mode 100644 index 0000000..bb0cb50 --- /dev/null +++ b/src/main/resources/assets/saturnclient/textures/gui/mod/crosshair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/day.png b/src/main/resources/assets/saturnclient/textures/gui/mod/day.png deleted file mode 100644 index 5fb33522fdf647e710b98dd9e5166550181eb2e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?3oVGw3ym^DWND0suu z#WAE}&f8m#d`$)dZVxTxwBPMtSJ0O(B_iy!P)S$z=YCng8NZwsNuM!dl>P8P`N;XA z_xr2Z!3eX!KUQO3paJ@>;J6#8+D@MlVkgh^55-u-J{=J zoWEuMl|O!p3zyE1uj6qYl&H~$DwO`zUc&UaLv?M65^4&Oh7%rBYB7O pi3WS;SE79WhKFBi%OvGiFx7jU->Vw5-xKIZ22WQ%mvv4FO#munb;1Au diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/day.svg b/src/main/resources/assets/saturnclient/textures/gui/mod/day.svg new file mode 100644 index 0000000..9432885 --- /dev/null +++ b/src/main/resources/assets/saturnclient/textures/gui/mod/day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/effect.png b/src/main/resources/assets/saturnclient/textures/gui/mod/effect.png deleted file mode 100644 index ad39774d89b2e83a7f92ad1717855ae89aafdbc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2580 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 \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/fps.png b/src/main/resources/assets/saturnclient/textures/gui/mod/fps.png deleted file mode 100644 index 0daed1674648db12c476379da0550c463a13df75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 478 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz07Sip>6gA}f5OZp5{=Hcn$7!twx zHq?-d$xz@-=Z%QSAI#j`@(;OlT9~;Heye*RT+_IAYg6yujKtix@;Wt*^ESJSiJd$; z;WXIUAN zN5Ko;eiqu#d`a%Ztoa+|S<0HGT1`+n>AazYwXAI_i^b*$q5shgX(?W5nGLHeAM#&EG4W=#^ zzT4j@68(^JtpC0J?!D#VjI#M{`ue-Legqw}|6|PL_p8VKaOBP7mUD_2cVsE&#Lsvq za!h+`dVBXN?TXG#m-O#ET=>U)Pv \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/freelook.png b/src/main/resources/assets/saturnclient/textures/gui/mod/freelook.png deleted file mode 100644 index 9418d12a8d19800cf074e01ac0c7c25cf48be101..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 959 zcmV;w13>(VP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFDM>^@RA>e5SiNo(F%Z559)JSq zQbc(JLQNgfN3?qn5%C&P`k>T2K$JQ)k@607i4c@YksRMgJiFtuy}!N-MmN%&XU6mE z_1}51*g_#Auv)E_z#E6lLeg&2bcv&Bpl*8$ppZ64-$FS>AahQk7!*}R9jgGcZ4p^l z5k~X~_#Jo;90R8|5g5zvHO7zDWe&{c$odX=DD63AG?U=LI)#_m;-BOkMl57c$EY+z z1@QY?Hj;jPV#hh#vcP;z~36#9CqL3>igj6 z12udF^TyX&EFjzHPQ)C5%tZrdiVx_u3-;NQmkf~`i_mZ{eNC%-NjDvifaqBdvcCK{ zH9UwE5Zg^^tG6!ROAd_0c_qYzc8T5K%-X^C0c4+mAAvir{|DMj;5lqAoGfpXs!*b! zUk)UGiQqa7*)H%Hcn3@Y`>3-keuuW!VPMy*5LmPVCItLueRS~D9aV(&)kb$zuR>ta z3Si4ojlnMni7_~CJ@ix7L159V5Li43V0Qqt)8-tqLmy&-e5zw|G8`b70U{t`|$~g+)Atmq{M03>@p=#Mj9RY1CvQcf00xnI&C8lU} z5kt=&fY#Qy8o{O(NuLYYmA#F!Kon8#LFNi5`zoP|;FqwcvYwsE%N&%rc6d=tT^6;U z<{V(`zu@(P5&75_rd$&|oq53hbV!=A=+yyo`!|&j0Ukid1Fw^e*9#_4Sd##uZ1PN{ zi~M7@1Z#%wVRw~2-3Wh zR(b^Cc<$4pc1QXaf8yV1A!6|Tbj}qf{U>ew?CF0mA~?v_Jf73tC%!~*m6#r31D25@$|`_kgd#ex h(;FDaZHP%D@CUy;9|+M{F`NJZ002ovPDHLkV1ie~ncDyW diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/freelook.svg b/src/main/resources/assets/saturnclient/textures/gui/mod/freelook.svg new file mode 100644 index 0000000..199e319 --- /dev/null +++ b/src/main/resources/assets/saturnclient/textures/gui/mod/freelook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/fullbright.png b/src/main/resources/assets/saturnclient/textures/gui/mod/fullbright.png deleted file mode 100644 index dfa9f7a583088a07a0740fe01da8b58388ebc482..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 697 zcmV;q0!ICbP)3+h!>OxSe3+( zXu{Fcdc@tpOXv$29s@X`h=>oM!Q^Ku)FegQ?)Hq`vkaLp>C0?a*Z1|`s@g1T(4avQ z0As)tZLO?s;GB4lm8@<6HjRMItZtwY5E;NVU@o-);EwnzO>_YH&q}5*H%x%dh7qt4 zcx&~wzxW!DDL`h$VrQ5LU>4X1_JCUfzg0rs5z!U$K*xNa2DX9U{2ON~Y|7X@*Ri_E$qx{9 z7dRmtKY)kXWdJY_{Bj6>lGOp0v~~2Gh1lF5HTh|s%^Y6_%`C709GU*#fyruFUnI2c zke>;#nQ=t)s$uKDZR~-aFJQaq9PD;lwtf<8cF&iv-!=A6Q)E)FoVG6Ef$1lAdyY;C zKQD~^dnW{0_xKOX?3_%>#h&RW4^)n&5(1Wu{Ri{?V1WOXItbYRUjZ%Y2)uRLYa3N2 z-a>CY{vSsz4rpu>>kzL-BUXSwTd%+z3k7T+8M`xJA}!mn9N{SNA-k!;TaK-2*!sy# z0jG|L#UV3q$)5l>s%8B*sRy25ZdVWKvbLAbo)`QC?q!z&8cy59WEf71gdS0$FSVqB z$C8Yg?~{79+Jo&ve%rF!hVzv=t|XxeOerTMwvoGC9w==>dewK^P*qBqFsbi4W2O;N z>#_>v3tI=;W=C4s;$msBS3zmIC&Ftc4 fQVkk3$SnQ<+wIF!@{WD(00000NkvXXu0mjfk#Rfb diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/fullbright.svg b/src/main/resources/assets/saturnclient/textures/gui/mod/fullbright.svg new file mode 100644 index 0000000..3e8360d --- /dev/null +++ b/src/main/resources/assets/saturnclient/textures/gui/mod/fullbright.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/health.png b/src/main/resources/assets/saturnclient/textures/gui/mod/health.png deleted file mode 100644 index dcca15ca6145bcc8d171e707e4aabb03a82be5af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 962 zcmV;z13mnSP)OT9K@8^n097M5h92Lu`sqy#H`K1Cx^e{F6wnvb=T|MPTrr?;=Ob3fBsW< zKIL2qC6rJ?2_XZh0M-DPfcwBF;2Tf}Tm;qugER0A0oJQ+owz=U+x}Y(49YWsQNU%= z;Wuy&sA_<3gnZ054K4xIO-Z0Fu)_=Z2G|cwRqu2J+5vrlDZp;vt$cq1n}9NpuMF5M zTg0~um@E$C>L~7MzybM0FK9c^CTkMifGZ;T4AcPSDcW@gHfop{jwk_pj_E(MZ7|;y zeC101i`P3{v-Cui#4{TTDf9O^s(`2BIOZks1Q^~3zG~pPmrQccXgEbl^vW94Uyv+f5DAT;KqvOY4O=<3kw-xWK{x zY49M-by zwBC+09+h#i3mm{QX+0EYTrT5b7dZI;(w7#xu^HZlJmG=%zz1oqDCBfg4zpaK0Om;J z^@f~Q$jK6F0E-(NyrL8t`_{b zxS<9IT_6AsOVhQ~*8*UeS3p=)bd||^mPpH2U})CH3)J{KFtKPVlXXmz2E+Wul0=0F zs{oG8U1f6o@v0vr7aUStQ;&PeG+WOA6M&D3eWJEyntL4WsvA{PGMu&2YL5I?radq2Un;8>?{uMp^?L<;1dPdn za~$y4JG4&DM{RQ80l-}a{;A=Rc^qr=wQnvz#C6|J;l)wiZ~d73#G&p%=Q40rUh;dvD#2IXk`I)^j~vO93!X$C?PXQ?GO_5kMtyPfw+5 k$>873E|{BU39gy&2VctvN`ERT%K!iX07*qoM6N<$f(a9{jsO4v diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/health.svg b/src/main/resources/assets/saturnclient/textures/gui/mod/health.svg new file mode 100644 index 0000000..491bbd7 --- /dev/null +++ b/src/main/resources/assets/saturnclient/textures/gui/mod/health.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/keystrokes.png b/src/main/resources/assets/saturnclient/textures/gui/mod/keystrokes.png deleted file mode 100644 index da20fcdf594e6824899c2a52b07b0e3614437159..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3226 zcmb_ec{o)49v{n)wf7>H89Xb2YX`F`g|G%!U^6va4 zyZkscoAlV`_Qk@U`m$TJNk)@f=MB8EjEqck(qYwlfr!3{?+I~Bg=0xm)xj~CfiFz7 ziNhPlVopnNGUMiwNArZ}_`6>$yu}tG-{DvK(Mb+$#Rj~Iq?C(d>f^guUI)(m0^)Si zB9uyWzMWl^Wek7%-Ijs9VN;thlGOANHU6yqDYcO;`>aB-en`y1Yns=JGqE=%ey7Q9 zkEAW>{&;GkbI*Kt$=Iop<{rO&Gh#bcYDhvDa&%1jBpwfF_0uKVgiQ=4ZKCyF=68xA zpT{m?EMC$-w1tf%Io3^!*lu+i_J_YFMe8%#4I@g!N2=kvo)jtKHO95loQtqe_TDB< zQnGgHaDhu!Nj(3CXT!8`X6<0cXiqc0QU9i9IUncx>Z{IHl=YdY0Hh z80G$isq_7@t4g}Mi6)=oGLz4wk{6K2JakCcLC~|0muB-p0FDbxAEG8n_-Vm$YUy~c z^*1=~%7blA7mhphV4IVM!66Vmr~ zTZiNL0@c>U_t22G<(E}7KW8hC9Bp`k7@eTQwD$fA5vplt_$xl8`QygYHGI+I;;& z?;Vwa8{B^IIa_c*r5}2l)e`eDxuxXtK2==f91xzFw)JMmrJz9NCC`$eH(6O}>IQ;; z`eOeD)?8>M|8nSI*SY17?mlx&UZAOY!I!+i>J|6#i^U5@U&%52yR|;rIk{m;L*av# zj6DSx*&U9ioi_>!7Z-h2$jkSJ($sta-v(f6ujUJ9kVQYU=i8e?rE% z){*&G?*h5(x)rGUH^^kNuARDtcL6oB3phCv;az7t^dcd%UeN2;S((y;61K>)6y8v#LvUcDdzvcnC(T*}s$0i;5BaBf4_&;U@ zWb$5r7h+SZ*$|a9717LD^!JOEa_LDu7)w&$LUyrjXVLhJU%HQ*3%*p7wA-h0a+*n9U_S1PnoA}Tj(+@^kQ zQXB*Y(a9Vuwv;vn&3SI#b4H1MU8+<3Wb~N2o|&%eAm~bi+&5()_v(&Xxv%~$*kbA<>D=DqW+VkCmpOV`w&$SA&cg#x{U|P%f4iuNDP?3xGdmM49EB4txFR zFG>rlvH>idSTzTl0RDrhRQ-cLMJQ^bfOw~sIoJWfXAB06C)qNY z^Ope4Zrpkyp-xaXr)ccfTkOdaL-YjI3sFJ12n>G4dY`OjOt<%j&5sT{r>BaI()L$( zvcL|x5rU6dD|6fm*m)MG2o4{R9EY68t3j*GXTG zNJkqyFk6G`K5oD1#rrB~DA!R($tfb~tr`Y+u#f`_+B}}Akg`^y(hwjLXd2U@8j&7D z9QvUL94@m=cXag@!C%kMFSPseqd7rZ$;l&0RZ})fL&?bzGKyE^&)1glI(QqE zgsCUEimxu6%G)WNvkVL zH;X>~GDttt!l!D2YO5suB`p$7aN^4>D0Eqg3&Vi#w*nn~Et@Ba<=ZJzTtcSDp%mf> zCzn4`C~p+B_2&KB*kCBieS>8Bu1Vy21!0a)t;1A0Q}4P_>liq^MLE)-MhaN6={esz zh7Aks+bB3S?+}<6OGN14N{&ONY#fHJu~H-H?rEE;w_jPq$c|~O=XT6F#3ja#n@Hjw zY{!IRYtE}tU+->Xa$_{*M}yb1PR%uz@@Nq4(&`%~xaTzxk5O+}vQ_ireV$kvQN(3MRkMQW8q~_(>bNG-esMVlPOtHA8)X$ z?=Y6$)7sNw|chCw!EiYWYzjWVr zr1<5b9&tA}K8tPJ?dq0qsK7!)BFJrS1H@O3%AW?-AJA!?_pTNj@G)g*S^+UtD)rFt zqAhA8-4=mp)b7^7`VTGez2?5f}( z^HHvY3|od)hR=Q)pM%HKFEKY6Xh=(y@W$iwU2k}{CbwEdGZ*xx((V;=l%Z%#rmfNV z41%`XY=}+e^c@;+2Q`>=oPsZOm<4Z9_k($L zW=}bdHc^Iw1I?2kPy$aub4KgQqm$zJh~_xishTfk9E#UK5YZI)iz)fPQm \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/nofog.png b/src/main/resources/assets/saturnclient/textures/gui/mod/nofog.png deleted file mode 100644 index d7dc168035cdec9a615b34d443177d2f2bf4cdc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/particles.png b/src/main/resources/assets/saturnclient/textures/gui/mod/particles.png deleted file mode 100644 index 3709190894dd0017a277f52e44154ae6c21b2c38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1087 zcmV-F1i<@=P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFsYygZRA>e5n!QpKK@i99EXdGE zLQH%GLoGKTgHWG<#1|maOfyrljC>IY){+cDAR!k`Hi_v%8lQEWm$Wur0~F*%QOCv|&#sV-!$D5#mWn7HckJ#(J-Tf5N&a zM#G9YOQhh1dg|(P_x-3bD@RpOaZp%t$vDb_r%aGP8Owy zF2E=Eg#>Gtq%SgvKS7*^Nnw^C;&d;M}{>}04Pp8x0ju%bUHL%w!FJ;{azoa%a@6ZS3m6`Tjz4Ts_BJY(dF@eWkXsGaux$kkj=CI2%4 z%iYAb5|Ytl^fkCzk5OAhpebVY4ziGZYNVhIa0k@!uWLXGZnP6h`zb|u@*!Wl;P6+8 zR1ua(P;JE3xy;$*1KkwC5JCHNf~z7uwDRDm&uGtq$ew6>pq5uy{0_r`@)Ki!iK~o5 zON)Kl*cg?5WVQg@y#n<~PbhjG)J~x?b4!a|>(3})etF0UiYtMpHlc;HkRVKd&)EsG z!AF8A(Hyj2#C%{3^mOvD1w-gzu%}I_g?x_1IX$fl*0ECx7cJYO63B&6?X*%C?S802{pqTDW8)w2=I`YyAvD6S4DSGz7$E z9W1?qO12jC@8n2=M_>*PhfX@R2NsP{7aWvi1+%-7WwC002ovPDHLk FV1kjz=gt5C diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/particles.svg b/src/main/resources/assets/saturnclient/textures/gui/mod/particles.svg new file mode 100644 index 0000000..f4addd6 --- /dev/null +++ b/src/main/resources/assets/saturnclient/textures/gui/mod/particles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/speed.png b/src/main/resources/assets/saturnclient/textures/gui/mod/speed.png deleted file mode 100644 index 8ad03eb9be96cda1310cf705ba57b9eec6e02d61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1032 zcmV+j1o!)iP)-+xSTI(bdF=E7s5#wJ5&>ffsYyuj9Tfl4J6Yv>m0d4~)G-fu?HBkxxIs*%Uf% zj!&Aq5GZet06GDyfR~O_Ux5q27GMrA45)VG_B=$g%^Lg7kv|1i0~N&zV!Dh5xCtx= zs?v=Ag^+YyE*$~x0}~4wKo?*S(CQd9C(1I+OB;wYW&rTr(Q7vL+REoUh@QZ8C$EDq zcqEIt*#vS}!wv##MGl5b=~g;Q5Ph}WhKImNpbzjt@1|v#-w0d|k&n>|d^Ej!XZ6)A z{f~q9xvs<$DYO;o;mGXE8Sdqh0>EkMsY`ipN&qCuC1A?ttZEWi-VLTNF`v%04p<1>RG@FJ--Mod|1+~yGg099izI|>> zAeVCtWW4_TU$6jyv`E-9im?!8aj`W|x~rwHC9K$o=dswgYh_+p5c#@H;CHd#_6g5Q z7>P-W31FUlZSV=V`OeOz2w<0d-Qp9@mGEk!#6j{7!Jj?OW~?;|MHcy-(kd$V_dI8t zvOeYg2;KD-^E}c~HvhOY05-_Sli3`s-sp%)+?*#c8E=7)T!mQUuJ zVNs^Ltl+4740xw!+xzd#aBUMMkgyxba@h=iu6Qq>g41d^l>@aY#w^_iCE5wVASdMm z)MhiWT$jhzS7szi1i%d7oO~<(k3unG#E22&zs66y5_GVucokv*0000 \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/sprint.png b/src/main/resources/assets/saturnclient/textures/gui/mod/sprint.png deleted file mode 100644 index c1c507c090466ffec1a84e9d1fd1b5264fb47a75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 961 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy!Wi-X*q7;m1`sRwd63p^r=85p>Q zL70(Y)*K0-U}Jz!h%1o(KcFD?zEUvI5!xj|e!&cuGj9H#{(q-I;>x$rW0IyE{gHm` zkK;Gv4gXa9{#NW-BD82r@|m=mlN~l)QJU6Q$H2g}$kW9!q+-t78`b%j4Fnhptlu75 zawvp%8xLEm4eR~?hZ`#T4hhKeEB1Ot{^H>5lz)EXWo(wW^r^p-t+GF_K7ZZY<{T6I z`$oAR{ETrdr@J{=-gNNmTz71ce%1Z?G=qc56}F#;tPhkQC}@mi7h+Irh+z{lkZ5?A z^;BBIEn!DooV7wlSCf9x2cyv5MLaQ^cOc!wwi;$9EUqLrN7hJ8993vrOtbM(DPx-*#j#w&PGP<+422w1-I7?4wsb{#WC^k zKFm?dVKJ(Fc5scA+)}O3@}Dd1A6$$q^$A+1d#rX}mMO=9%bBG%7S<0Ec1H6uDbz`p z?=y1FF#gb>v8Zt8G2X_Fe`~hMDs*Lh>bu!$xSg}YLFD2`_h$m%_UE#mQ2G@kzw;*F z@#kwKXV04ZYl4aZduwdzmT@P2ZGP^EmH|gk(xn~r)#EV~S zV&<8H)rAd$Zh{M$d1tsjkri+i`OnF6qWa5|L$k8$8It+$w=sMuBf|#;HxJ=| zA8mJ;&YS%Mo+%>bUT-u*9%fsRIG5^^Pl4sq$SuSiVZ54jOW$^vk z?HLZ`2Ns%~DztXc_$04zR(=Zmf}kHMVz=j2)-axX(J1$V@wD7I^9wf)|71I3JE#1> lox?xbCtPH + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/main/resources/assets/saturnclient/textures/gui/mod/zoom.png b/src/main/resources/assets/saturnclient/textures/gui/mod/zoom.png deleted file mode 100644 index c6969ae522664a35d9cfeb01a30ce4867f44de7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1736 zcmV;(1~>VMP)zqfJ)velIo1HesEVyF3WDiE%4 zZ~HOe@EVNY1Ncbe>;}e#0H~EWQ=feX99HQ0?G*}QHaWvkCofk3G%VfUghs34Hf@E5 z*KG1`m3=Dt#Qou^iBbzhJl)ApbACM2`)&a^d&=hxFQ!)87R8wEh2pDDnQNuUCg3Q^ z4gpq5J_TqPfwwF0TgIvzA(^>dS)qBe-u@~P-sgGL=QSy*pkLYCI6SN1@MbFbAE4I ztBXJFVi7k3{uD_rw+Bwv+c)2A8HG4omwTkg3@5V#lVL13lMdo~Jb)w&$5 z{KH~F3&}2$E(>CjjupWKC$qgCpQ+UrfOo`wrJkG1DBGf(7=c+L9IWSq6J$KOa(t?xar%xa8zvX0aNOo}y!ZjMaqn@qYwMRBso6t$q zu*}JxlkA!pgq!^GQX#`Iso-S!>C;C1UprZ-OI#O&utJ09k{t^^dY)Wxvi$V1nxf%z zCwoe=K`{tF)8J?7xm(%?YZJh^>Ff`9MY4V|2;bG<1N7Wl&ua=!mY+U1ivLw73#ZgG zVi3Nl!AHA;QH3(WV+&4Ly*@8UpJ7fG(%~aw5MHmrXF1uck_|67VfBUBbt#v$wA}Dz zEYcH2U`>lgsoKDzD5O>EVr@2b49?X6fSV;7ACq{M4>CH1{U}+FXvF{3;{$Pyao2}d;RFhtqFD4}Se>!RTj>P0+hv4yOkOLchD0N;#j1t`}A{#Bjf zqxU-Co=Hw`x7DFu;Xf5;;4If8u-+jmlor(Ws)dRoAs)O99GW_I-PHmQy~KR5TC1jo z8>dWp$PPF8hVBwpQH^k%x8k=r|TL)J@{iIxGxm@TA;6p|Eky*YBobIB1SoV7a zJsDxHGeVczn0tDHh0VmGk>yA$+C z`cl*ZVT1sss&{D8Ve6_2yA&HepOjWD~u z8=-y42;^?GhHlZ^j69*KjDw6-_kWa8LnBmFV+8vRf?-NhAHkkRn3rJ$yM>AhEzx@$ zMS*GxnMTlE \ No newline at end of file diff --git a/src/main/resources/assets/saturnclient/textures/gui/test.svg b/src/main/resources/assets/saturnclient/textures/gui/test.svg deleted file mode 100644 index 15e0aff..0000000 --- a/src/main/resources/assets/saturnclient/textures/gui/test.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 44c0b69c4d0a6ef2117a369360f62123fea599f2 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 22 Feb 2026 23:59:25 +0100 Subject: [PATCH 71/82] Fixed keystrokes --- .../java/org/saturnclient/modules/mods/Keystrokes.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/saturnclient/modules/mods/Keystrokes.java b/src/main/java/org/saturnclient/modules/mods/Keystrokes.java index 5cdef8f..f3358da 100644 --- a/src/main/java/org/saturnclient/modules/mods/Keystrokes.java +++ b/src/main/java/org/saturnclient/modules/mods/Keystrokes.java @@ -110,13 +110,13 @@ public class Keystrokes extends Module implements HudMod { renderKey(scope, s, 'S', 27, 27); // Bottom center renderKey(scope, d, 'D', 54, 27); // Bottom right - if (showMouse.value) { - renderKeyM(scope, lmb, "LMB", 0, 54); // Below A/S/D - renderKeyM(scope, rmb, "RMB", 40, 54); // With 3px between + if (showSpace.value) { + renderKeySpace(scope, space, 0, 37); } - if (showSpace.value) { - renderKeySpace(scope, space, 0, cachedHeight - (showSpace.value ? 22 : 0)); + if (showMouse.value) { + renderKeyM(scope, lmb, "LMB", 0, showSpace.value ? 73 : 54); // Below A/S/D + renderKeyM(scope, rmb, "RMB", 40, showSpace.value ? 73 : 54); // With 3px between } } From f9c0d3fe58f277aea884f64020e8ae23622d28e2 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 00:24:14 +0100 Subject: [PATCH 72/82] Fixed keystrokes --- .../saturnclient/modules/mods/Keystrokes.java | 159 ++++++++---------- 1 file changed, 69 insertions(+), 90 deletions(-) diff --git a/src/main/java/org/saturnclient/modules/mods/Keystrokes.java b/src/main/java/org/saturnclient/modules/mods/Keystrokes.java index f3358da..7b356ea 100644 --- a/src/main/java/org/saturnclient/modules/mods/Keystrokes.java +++ b/src/main/java/org/saturnclient/modules/mods/Keystrokes.java @@ -9,46 +9,45 @@ import org.saturnclient.ui2.RenderScope; import org.saturnclient.modules.ModuleDetails; public class Keystrokes extends Module implements HudMod { - public static Property enabled = Property.bool(false); - public static Property showMouse = Property.bool(true); - public static Property showSpace = Property.bool(false); - public static ModDimensions dimensions = new ModDimensions(78, 46); - // Cache key states to avoid repeated calls - private boolean w = false; - private boolean a = false; - private boolean s = false; - private boolean d = false; - private boolean lmb = false; - private boolean rmb = false; - private boolean space = false; + private static final Property enabled = Property.bool(false); + private static final Property showMouse = Property.bool(true); + private static final Property showSpace = Property.bool(false); - // Cache dimensions to avoid recalculation - private int cachedHeight = 54; - private boolean dimensionsDirty = true; + private static final Property clickBg = Property.color(0xFFCCCCCC); + private static final Property clickFg = Property.color(0xFFFFFFFF); - private static Property clickBg = Property.color(0xFFCCCCCC); - private static Property clickFg = Property.color(0xFFFFFFFF); + private static final ModDimensions dimensions = new ModDimensions(78, 54); + + // Key states + private boolean w, a, s, d, lmb, rmb, space; + + // Layout constants + private static final int KEY_SIZE = 24; + private static final int KEY_SPACING = 3; + private static final int MOUSE_WIDTH = 38; + private static final int MOUSE_HEIGHT = 24; + private static final int SPACE_HEIGHT = 19; public Keystrokes() { super(new ModuleDetails("Keystrokes", "keystrokes") - .description("Displays the keystrokes for movement") - .version("v0.1.0") + .description("Displays movement keystrokes") + .version("v0.2.0") .tags("Utility"), enabled.named("Enabled"), showMouse.named("Show mouse clicks"), - showSpace.named("Show space clicks"), + showSpace.named("Show space"), dimensions.prop(), clickFg.named("Clicked fg"), - clickBg.named("Clicked bg")); + clickBg.named("Clicked bg") + ); dimensions.renderBackground = false; } @Override public void tick() { - // Cache key states to reduce method calls w = SaturnClient.client.options.forwardKey.isPressed(); a = SaturnClient.client.options.leftKey.isPressed(); s = SaturnClient.client.options.backKey.isPressed(); @@ -57,103 +56,84 @@ public class Keystrokes extends Module implements HudMod { rmb = SaturnClient.client.options.useKey.isPressed(); space = SaturnClient.client.options.jumpKey.isPressed(); - // Mark dimensions as dirty if mouse or space visibility changed - if (dimensionsDirty) { - updateDimensions(); - dimensionsDirty = false; - } + updateHeight(); } - private void updateDimensions() { - cachedHeight = 54; // base height with padding considered + private void updateHeight() { + int height = KEY_SIZE * 2 + KEY_SPACING; - if (showMouse.value) { - cachedHeight += 27; - } + if (showSpace.value) + height += SPACE_HEIGHT + KEY_SPACING; - if (showSpace.value) { - cachedHeight += 22; - } + if (showMouse.value) + height += MOUSE_HEIGHT + KEY_SPACING; - dimensions.height = cachedHeight; + dimensions.height = height; } @Override public void renderDummy(RenderScope scope) { - // Use cached dimensions - 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)); - } + render(scope, false); } @Override public void renderHud(RenderScope scope) { - // Use cached dimensions - dimensions.height = cachedHeight; + render(scope, true); + } - // WASD keys (3px padding between each key) - renderKey(scope, w, 'W', 27, 0); // Top center - renderKey(scope, a, 'A', 0, 27); // Bottom left - renderKey(scope, s, 'S', 27, 27); // Bottom center - renderKey(scope, d, 'D', 54, 27); // Bottom right + private void render(RenderScope scope, boolean live) { + int yOffset = 0; + + // --- W --- + renderKey(scope, live && w, 'W', KEY_SIZE + KEY_SPACING, yOffset); + + // --- A S D --- + yOffset += KEY_SIZE + KEY_SPACING; + + renderKey(scope, live && a, 'A', 0, yOffset); + renderKey(scope, live && s, 'S', KEY_SIZE + KEY_SPACING, yOffset); + renderKey(scope, live && d, 'D', (KEY_SIZE + KEY_SPACING) * 2, yOffset); + + yOffset += KEY_SIZE + KEY_SPACING; + + // --- SPACE --- if (showSpace.value) { - renderKeySpace(scope, space, 0, 37); + renderKeySpace(scope, live && space, 0, yOffset); + yOffset += SPACE_HEIGHT + KEY_SPACING; } + // --- MOUSE --- if (showMouse.value) { - renderKeyM(scope, lmb, "LMB", 0, showSpace.value ? 73 : 54); // Below A/S/D - renderKeyM(scope, rmb, "RMB", 40, showSpace.value ? 73 : 54); // With 3px between + renderKeyMouse(scope, live && lmb, "LMB", 0, yOffset); + renderKeyMouse(scope, live && rmb, "RMB", MOUSE_WIDTH + KEY_SPACING, yOffset); } } - // Consistent 3px padding applied below: + private void renderKey(RenderScope scope, boolean pressed, char c, int x, int y) { + int bg = pressed ? clickBg.value : dimensions.bgColor.value; + int fg = pressed ? clickFg.value : dimensions.fgColor.value; - private void renderKeySpace(RenderScope scope, boolean isPressed, int x, int y) { - int height = 19; - 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); + scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg); + scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg); } - private void renderKeyM(RenderScope scope, boolean isPressed, String c, int x, int y) { - int width = 38, height = 24; - int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; - int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value; + private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) { + int bg = pressed ? clickBg.value : dimensions.bgColor.value; + int fg = pressed ? clickFg.value : dimensions.fgColor.value; - scope.drawRoundedRectangle(x, y, width, height, dimensions.radius.value, bgColor); - - // 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); + scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_HEIGHT, dimensions.radius.value, bg); + scope.drawText(0.6f, text, x + 9, y + 7, dimensions.font.value, fg); } - private void renderKey(RenderScope scope, boolean isPressed, char c, int x, int y) { - int size = 24; - int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; - int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value; + private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) { + int bg = pressed ? clickBg.value : dimensions.bgColor.value; + int fg = pressed ? clickFg.value : dimensions.fgColor.value; - 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 - scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fgColor); + int lineY = y + SPACE_HEIGHT / 2; + scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg); } @Override @@ -169,6 +149,5 @@ public class Keystrokes extends Module implements HudMod { @Override public void onEnabled(boolean e) { enabled.value = e; - dimensionsDirty = true; } } \ No newline at end of file From c5c38247ccce101a6319d708c3c3e5c43c2a84b3 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 03:45:26 +0100 Subject: [PATCH 73/82] Improved build.gradle --- build.gradle | 186 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 144 insertions(+), 42 deletions(-) diff --git a/build.gradle b/build.gradle index 1266b7f..2384564 100644 --- a/build.gradle +++ b/build.gradle @@ -1,46 +1,126 @@ +/* + * ================================================================ + * Saturn Client - Fabric Mod Build Configuration + * ================================================================ + * + * This build file configures: + * - Fabric Loom (Minecraft mod development) + * - Dependency management + * - Resource processing + * - Java toolchain (Java 21) + * - Publishing setup + * + * All version values are defined in gradle.properties. + * ================================================================ + */ + plugins { + // Fabric mod development plugin id 'fabric-loom' version '1.9-SNAPSHOT' + + // Maven publishing support id 'maven-publish' } +/* + * ================================================================ + * Project Metadata + * ================================================================ + */ + version = project.mod_version -group = project.maven_group - -repositories {} - -dependencies { - minecraft "com.mojang:minecraft:${project.minecraft_version}" - - // Switch back to Yarn - mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" - - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - - modApi "dev.felnull:special-model-loader:1.4.0+pre.1" - include "dev.felnull:special-model-loader:1.4.0+pre.1" - - implementation 'com.github.selimaj-dev:session-java:0.1.4' - - // implementation("com.kitfox.svg:svg-salamander:1.1.3") - implementation 'com.formdev:svgSalamander:1.1.4' - - include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}") - //You might want bendy-lib. playerAnimator will wrap it. - // include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}") -} - +group = project.maven_group +/* + * ================================================================ + * Repositories + * ================================================================ + * All dependency sources. + */ repositories { + // Fabric maven { url 'https://maven.fabricmc.net/' } - maven { url 'https://jitpack.io' } - maven { url "https://maven.parchmentmc.org" } - maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" } - maven { name = "KosmX maven"; url = 'https://maven.kosmx.dev/' } + // JitPack (GitHub releases) + maven { url 'https://jitpack.io' } + + // Parchment mappings (if needed later) + maven { url 'https://maven.parchmentmc.org' } + + // Special Model Loader + maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" } + + // Player Animation Library + maven { name = "KosmX Maven"; url = 'https://maven.kosmx.dev/' } + + // Central repository mavenCentral() } +/* + * ================================================================ + * Dependencies + * ================================================================ + */ +dependencies { + + // ───────────────────────────────────────────── + // Minecraft Core + // ───────────────────────────────────────────── + minecraft "com.mojang:minecraft:${project.minecraft_version}" + + // Yarn mappings (named mappings for dev environment) + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + + // Fabric Loader + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + + // ───────────────────────────────────────────── + // Rendering / Model Libraries + // ───────────────────────────────────────────── + + // Special Model Loader (included inside your mod jar) + modApi "dev.felnull:special-model-loader:1.4.0+pre.1" + include "dev.felnull:special-model-loader:1.4.0+pre.1" + + // SVG Rendering Library + implementation "com.formdev:svgSalamander:1.1.4" + + + // ───────────────────────────────────────────── + // Animation + // ───────────────────────────────────────────── + + // Player Animation Library (bundled into your mod) + include modImplementation( + "dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}" + ) + + // Optional: BendyLib (only if you directly use it) + // include modRuntimeOnly( + // "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}" + // ) + + + // ───────────────────────────────────────────── + // External Libraries + // ───────────────────────────────────────────── + + // Session Java (auth/session handling) + implementation "com.github.selimaj-dev:session-java:0.1.4" +} + + +/* + * ================================================================ + * Resource Processing + * ================================================================ + * Injects version values into fabric.mod.json. + */ processResources { inputs.property "version", project.version inputs.property "minecraft_version", project.minecraft_version @@ -48,38 +128,58 @@ processResources { filteringCharset "UTF-8" filesMatching("fabric.mod.json") { - expand "version": project.version, + expand( + "version": project.version, "minecraft_version": project.minecraft_version, "loader_version": project.loader_version + ) } } + +/* + * ================================================================ + * Java Configuration + * ================================================================ + * Targeting Java 21. + */ def targetJavaVersion = 21 + tasks.withType(JavaCompile).configureEach { - it.options.encoding = "UTF-8" - if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { - it.options.release.set(targetJavaVersion) - } + options.encoding = "UTF-8" + options.release.set(targetJavaVersion) } java { - def javaVersion = JavaVersion.toVersion(targetJavaVersion) - if (JavaVersion.current() < javaVersion) { - toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) - } + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + archivesBaseName = project.archives_base_name withSourcesJar() } + +/* + * ================================================================ + * Jar Packaging + * ================================================================ + * Includes LICENSE and NOTICE inside the final jar. + */ jar { from("LICENSE") { - rename { "${it}_${project.archivesBaseName}"} + rename { "${it}_${project.archivesBaseName}" } } from("NOTICE") { - rename { "${it}_${project.archivesBaseName}"} + rename { "${it}_${project.archivesBaseName}" } } } + +/* + * ================================================================ + * Publishing + * ================================================================ + * Configured but no remote repository defined yet. + */ publishing { publications { mavenJava(MavenPublication) { @@ -87,5 +187,7 @@ publishing { } } - repositories {} + repositories { + // Add publishing repository here when needed + } } \ No newline at end of file From 8614e1bdd1176b8c52fd8eb8ae33a0809c44dc2d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 06:47:08 +0100 Subject: [PATCH 74/82] Working service system --- build.gradle | 2 +- .../saturnclient/client/ServiceClient.java | 36 +++++++++++++++---- .../ui2/resources/SvgTexture.java | 1 - 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 2384564..8d5fc02 100644 --- a/build.gradle +++ b/build.gradle @@ -111,7 +111,7 @@ dependencies { // ───────────────────────────────────────────── // Session Java (auth/session handling) - implementation "com.github.selimaj-dev:session-java:0.1.4" + implementation "com.github.selimaj-dev:session-java:0.1.5" } diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 8876664..4a9f364 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -46,7 +46,7 @@ public class ServiceClient { uuid = mcSession.getUuidOrNull(); String username = mcSession.getUsername(); - SaturnClient.LOGGER.info("Authenticating with UUID: " + accessToken); + SaturnClient.LOGGER.info("Authenticating with UUID: " + uuid); if (!connectTimeout()) { SaturnClient.LOGGER.error("Unable to authenticate: Session Server Timeout"); @@ -76,7 +76,11 @@ public class ServiceClient { public static void setCloak(String cloak) { try { - session.request(ServiceMethods.SetCloak, cloak).get(); + session.request(ServiceMethods.SetCloak, cloak).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } + }); } catch (Exception e) { SaturnClient.LOGGER.error("Failed to set cloak (service): ", e); } @@ -84,7 +88,11 @@ public class ServiceClient { public static void setHat(String hat) { try { - session.request(ServiceMethods.SetHat, hat).get(); + session.request(ServiceMethods.SetHat, hat).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } + }); } catch (Exception e) { SaturnClient.LOGGER.error("Failed to set hat (service): ", e); } @@ -92,7 +100,13 @@ public class ServiceClient { public static void buyCloak(String cloak) { try { - session.request(ServiceMethods.BuyCloak, cloak).get(); + session.request(ServiceMethods.BuyCloak, cloak).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } else { + Cloaks.availableCloaks.add(cloak); + } + }); } catch (Exception e) { SaturnClient.LOGGER.error("Failed to buy cloak (service): ", e); } @@ -100,7 +114,13 @@ public class ServiceClient { public static void buyHat(String hat) { try { - session.request(ServiceMethods.BuyHat, hat).get(); + session.request(ServiceMethods.BuyHat, hat).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } else { + Hats.availableHats.add(hat); + } + }); } catch (Exception e) { SaturnClient.LOGGER.error("Failed to buy hat (service): ", e); } @@ -108,7 +128,11 @@ public class ServiceClient { public static void emote(String emote) { try { - session.request(ServiceMethods.Emote, emote).get(); + session.request(ServiceMethods.Emote, emote).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } + }); } catch (Exception e) { SaturnClient.LOGGER.error("Failed to emote (service): ", e); } diff --git a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java index 937a79e..0bcd0f8 100644 --- a/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java +++ b/src/main/java/org/saturnclient/ui2/resources/SvgTexture.java @@ -53,7 +53,6 @@ public class SvgTexture { } @Nullable - @SuppressWarnings("resource") public static Identifier getSvg(MinecraftClient client, Identifier svgImage, int width, int height) { Identifier id = Identifier .of(svgImage.toString().replaceAll("\\.svg$", (width + "_" + height).toString() + ".png")); From df35497af29fefa0b77d66984d9e430a0dafa3e8 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 07:18:54 +0100 Subject: [PATCH 75/82] Trying to find the hat rendering issue --- .../saturnclient/client/ServiceClient.java | 20 +++++++++---------- .../cosmetics/HatFeatureRenderer.java | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 4a9f364..a6bccaf 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -74,9 +74,9 @@ public class ServiceClient { } } - public static void setCloak(String cloak) { + public static void setCloak(String itemId) { try { - session.request(ServiceMethods.SetCloak, cloak).whenComplete((msg, throwable) -> { + session.request(ServiceMethods.SetCloak, itemId).whenComplete((msg, throwable) -> { if (throwable != null) { throwable.printStackTrace(); } @@ -86,9 +86,9 @@ public class ServiceClient { } } - public static void setHat(String hat) { + public static void setHat(String itemId) { try { - session.request(ServiceMethods.SetHat, hat).whenComplete((msg, throwable) -> { + session.request(ServiceMethods.SetHat, itemId).whenComplete((msg, throwable) -> { if (throwable != null) { throwable.printStackTrace(); } @@ -98,13 +98,13 @@ public class ServiceClient { } } - public static void buyCloak(String cloak) { + public static void buyCloak(String itemId) { try { - session.request(ServiceMethods.BuyCloak, cloak).whenComplete((msg, throwable) -> { + session.request(ServiceMethods.BuyCloak, itemId).whenComplete((msg, throwable) -> { if (throwable != null) { throwable.printStackTrace(); } else { - Cloaks.availableCloaks.add(cloak); + Cloaks.availableCloaks.add(itemId); } }); } catch (Exception e) { @@ -112,13 +112,13 @@ public class ServiceClient { } } - public static void buyHat(String hat) { + public static void buyHat(String itemId) { try { - session.request(ServiceMethods.BuyHat, hat).whenComplete((msg, throwable) -> { + session.request(ServiceMethods.BuyHat, itemId).whenComplete((msg, throwable) -> { if (throwable != null) { throwable.printStackTrace(); } else { - Hats.availableHats.add(hat); + Hats.availableHats.add(itemId); } }); } catch (Exception e) { diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java index 2f7e061..4d741d1 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java @@ -28,6 +28,7 @@ public class HatFeatureRenderer extends FeatureRenderer Date: Mon, 23 Feb 2026 07:22:44 +0100 Subject: [PATCH 76/82] Fixed the hat rendering issue --- .../saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java index 4d741d1..158c29a 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java @@ -29,7 +29,7 @@ public class HatFeatureRenderer extends FeatureRenderer Date: Mon, 23 Feb 2026 09:20:10 +0100 Subject: [PATCH 77/82] Fixing emote system --- .../saturnclient/client/ServiceClient.java | 51 ++++++++++++++++--- .../saturnclient/client/ServiceMethods.java | 21 ++++++-- .../cosmetics/HatFeatureRenderer.java | 2 +- src/main/java/org/saturnclient/ui2/Utils.java | 1 - .../saturnclient/ui2/screens/EmoteWheel.java | 14 ++--- 5 files changed, 69 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index a6bccaf..ea19364 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -1,6 +1,7 @@ package org.saturnclient.saturnclient.client; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.saturnclient.saturnclient.SaturnClient; @@ -8,8 +9,14 @@ 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 dev.selimaj.session.types.SessionResult; 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; @@ -53,7 +60,8 @@ public class ServiceClient { return false; } - ServiceMethods.AuthResponse response = session.request(ServiceMethods.Authenticate, accessToken).get(); + ServiceMethods.Types.AuthResponse response = session.request(ServiceMethods.Authenticate, accessToken) + .get(); for (String availableCloak : response.cloaks()) { Cloaks.availableCloaks.add(availableCloak); @@ -63,6 +71,8 @@ public class ServiceClient { Hats.availableHats.add(availableHat); } + eventHandlers(); + SaturnPlayer.set(uuid, username, new SaturnPlayer(uuid, username, response.cloak(), response.hat())); Cloaks.loadCloak(uuid); @@ -128,13 +138,42 @@ public class ServiceClient { public static void emote(String emote) { try { - session.request(ServiceMethods.Emote, emote).whenComplete((msg, throwable) -> { - if (throwable != null) { - throwable.printStackTrace(); - } - }); + String[] targets = { uuid.toString().replaceAll("-", "") }; + + session.request(ServiceMethods.Emote, + new ServiceMethods.Types.EmoteRequest(emote, targets)) + .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, (l, data) -> { + return CompletableFuture.supplyAsync(() -> { + for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) { + if (player.getUuidAsString().replace("-", "").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); + } + } + } + + return SessionResult.ok(""); + }); + }); + } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index 7eb9dea..afb0ccf 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -3,11 +3,19 @@ package org.saturnclient.saturnclient.client; import dev.selimaj.session.types.Method; public class ServiceMethods { - public record AuthResponse(String cloak, String hat, String[] cloaks, String[] hats) { + public class Types { + public record AuthResponse(String cloak, String hat, String[] cloaks, String[] hats) { + } + + public record EmoteRequest(String emote, String[] targets) { + } + + public record EmoteEvent(String emote, String from) { + } } - public static final Method Authenticate = new Method<>("auth", - String.class, AuthResponse.class, String.class); + public static final Method Authenticate = new Method<>("auth", + String.class, Types.AuthResponse.class, String.class); // Equip public static final Method SetCloak = new Method<>("set_cloak", @@ -22,6 +30,9 @@ public class ServiceMethods { String.class, String.class, String.class); // Emote - public static final Method Emote = new Method<>("emote", - String.class, String.class, String.class); + public static final Method Emote = new Method<>("emote", + Types.EmoteRequest.class, String.class, String.class); + + public static final Method EmoteEvent = new Method<>("emote_event", + Types.EmoteEvent.class, String.class, String.class); } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java index 158c29a..cabc440 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/HatFeatureRenderer.java @@ -28,7 +28,7 @@ public class HatFeatureRenderer extends FeatureRenderer T getOrNull(List 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; } diff --git a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java index 55d6fe1..ab726ea 100644 --- a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java +++ b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java @@ -63,13 +63,13 @@ public class EmoteWheel extends SaturnScreen { if (emote == null) { return; } - AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); - if (animationStack.isActive() && animationStack.getPriority() == 1000) { - animationStack.removeLayer(1000); - } - animationStack.addAnimLayer(1000, - PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", - emote)).playAnimation()); + // AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); + // if (animationStack.isActive() && animationStack.getPriority() == 1000) { + // animationStack.removeLayer(1000); + // } + // animationStack.addAnimLayer(1000, + // PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", + // emote)).playAnimation()); ServiceClient.emote(emote); close(); }).dimensions(70, 70).centerOffset(width, height, -80 + (col * 80), -35 + (row * 80)) From 2a425335f06a1bea139349915909b0190c7bd4b4 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 09:34:38 +0100 Subject: [PATCH 78/82] Working emote streaming --- build.gradle | 2 +- .../saturnclient/client/ServiceClient.java | 36 ++++++++----------- .../client/player/SaturnPlayer.java | 6 ++++ .../saturnclient/ui2/screens/EmoteWheel.java | 14 ++++---- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/build.gradle b/build.gradle index 8d5fc02..0d0efed 100644 --- a/build.gradle +++ b/build.gradle @@ -111,7 +111,7 @@ dependencies { // ───────────────────────────────────────────── // Session Java (auth/session handling) - implementation "com.github.selimaj-dev:session-java:0.1.5" + implementation "com.github.selimaj-dev:session-java:0.1.6" } diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index ea19364..615b3b2 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -1,7 +1,6 @@ package org.saturnclient.saturnclient.client; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.saturnclient.saturnclient.SaturnClient; @@ -13,7 +12,6 @@ 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 dev.selimaj.session.types.SessionResult; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.util.Identifier; @@ -138,10 +136,8 @@ public class ServiceClient { public static void emote(String emote) { try { - String[] targets = { uuid.toString().replaceAll("-", "") }; - session.request(ServiceMethods.Emote, - new ServiceMethods.Types.EmoteRequest(emote, targets)) + new ServiceMethods.Types.EmoteRequest(emote, SaturnPlayer.getExternalUUIDAsString())) .whenComplete((msg, throwable) -> { if (throwable != null) { throwable.printStackTrace(); @@ -153,27 +149,23 @@ public class ServiceClient { } public static void eventHandlers() { - session.onNotification(ServiceMethods.EmoteEvent, (l, data) -> { - return CompletableFuture.supplyAsync(() -> { - for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) { - if (player.getUuidAsString().replace("-", "").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 { + session.onNotification(ServiceMethods.EmoteEvent, (data) -> { + for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) { + if (player.getUuidAsString().replace("-", "").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); } } - - return SessionResult.ok(""); - }); + } }); } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index a6caf95..edcd633 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -55,4 +55,10 @@ public class SaturnPlayer { PLAYERS.put(uuid, null); } + + public static String[] getExternalUUIDAsString() { + return PLAYERS.keySet().stream().filter(id -> !id.equals(ServiceClient.uuid)).map(UUID::toString) + .toArray(String[]::new); + } + } diff --git a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java index ab726ea..55d6fe1 100644 --- a/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java +++ b/src/main/java/org/saturnclient/ui2/screens/EmoteWheel.java @@ -63,13 +63,13 @@ public class EmoteWheel extends SaturnScreen { if (emote == null) { return; } - // AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); - // if (animationStack.isActive() && animationStack.getPriority() == 1000) { - // animationStack.removeLayer(1000); - // } - // animationStack.addAnimLayer(1000, - // PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", - // emote)).playAnimation()); + AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); + if (animationStack.isActive() && animationStack.getPriority() == 1000) { + animationStack.removeLayer(1000); + } + animationStack.addAnimLayer(1000, + PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient", + emote)).playAnimation()); ServiceClient.emote(emote); close(); }).dimensions(70, 70).centerOffset(width, height, -80 + (col * 80), -35 + (row * 80)) From 68b4bfc439781b42785d9d915a0429f6c6fab1a8 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 16:29:48 +0100 Subject: [PATCH 79/82] Working peer player --- .../saturnclient/client/ServiceClient.java | 19 ++++++++++++++++-- .../saturnclient/client/ServiceMethods.java | 20 ++++++++++++++----- .../client/player/PlayerTracker.java | 4 ++-- .../client/player/SaturnPlayer.java | 16 +++------------ 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 615b3b2..777f5de 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -58,7 +58,7 @@ public class ServiceClient { return false; } - ServiceMethods.Types.AuthResponse response = session.request(ServiceMethods.Authenticate, accessToken) + ServiceMethods.Types.Player response = session.request(ServiceMethods.Authenticate, accessToken) .get(); for (String availableCloak : response.cloaks()) { @@ -71,7 +71,7 @@ public class ServiceClient { eventHandlers(); - SaturnPlayer.set(uuid, username, new SaturnPlayer(uuid, username, response.cloak(), response.hat())); + player(uuid, username); Cloaks.loadCloak(uuid); @@ -168,4 +168,19 @@ public class ServiceClient { } }); } + + public static void player(UUID uuid, String name) { + try { + session.request(ServiceMethods.Player, uuid.toString().replaceAll("-", "")) + .whenComplete((user, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } else { + SaturnPlayer.set(user.toSaturnPlayer(uuid, name)); + } + }); + } catch (Exception e) { + SaturnClient.LOGGER.error("Failed to get player", e); + } + } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index afb0ccf..1f5d986 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -1,21 +1,28 @@ package org.saturnclient.saturnclient.client; +import java.util.UUID; + +import org.saturnclient.saturnclient.client.player.SaturnPlayer; + import dev.selimaj.session.types.Method; public class ServiceMethods { public class Types { - public record AuthResponse(String cloak, String hat, String[] cloaks, String[] hats) { - } - 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 static final Method Authenticate = new Method<>("auth", - String.class, Types.AuthResponse.class, String.class); + public static final Method Authenticate = new Method<>("auth", + String.class, Types.Player.class, String.class); // Equip public static final Method SetCloak = new Method<>("set_cloak", @@ -35,4 +42,7 @@ public class ServiceMethods { public static final Method EmoteEvent = new Method<>("emote_event", Types.EmoteEvent.class, String.class, String.class); + + public static final Method Player = new Method<>("player", + String.class, Types.Player.class, String.class); } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java b/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java index 2a27c6b..f43347f 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/PlayerTracker.java @@ -3,6 +3,7 @@ package org.saturnclient.saturnclient.client.player; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.saturnclient.client.ServiceClient; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.MinecraftClient; @@ -46,7 +47,6 @@ public class PlayerTracker { private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) { GameProfile profile = player.getProfile(); - UUID uuid = profile.getId(); - SaturnPlayer.player(uuid); + ServiceClient.player(profile.getId(), profile.getName()); } } diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index edcd633..f4c935e 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -41,19 +41,9 @@ public class SaturnPlayer { return PLAYERS.get(uuid); } - public static void set(UUID uuid, String name, SaturnPlayer player) { - PLAYER_NAMES.put(name, uuid); - PLAYERS.put(uuid, player); - } - - public static void player(UUID uuid) { - if (PLAYERS.containsKey(uuid)) { - return; - } - - // If offline then leave - - PLAYERS.put(uuid, null); + public static void set(SaturnPlayer player) { + PLAYER_NAMES.put(player.name, player.uuid); + PLAYERS.put(player.uuid, player); } public static String[] getExternalUUIDAsString() { From bbf4b1749454ca63f5b73e7b7a31a73683cc8cce Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 17:46:28 +0100 Subject: [PATCH 80/82] Attempting to fix sendplayer --- .../saturnclient/client/ServiceClient.java | 23 ++++- .../saturnclient/client/ServiceMethods.java | 97 ++++++++++++++++--- .../client/player/SaturnPlayer.java | 2 +- .../saturnclient/cosmetics/cloaks/Cloaks.java | 2 +- 4 files changed, 104 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 777f5de..27611bf 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -87,6 +87,13 @@ public class ServiceClient { 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) { @@ -99,6 +106,13 @@ public class ServiceClient { 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) { @@ -167,15 +181,20 @@ public class ServiceClient { } } }); + + 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.Player, uuid.toString().replaceAll("-", "")) + session.request(ServiceMethods.GetPlayer, uuid.toString().replaceAll("-", "")) .whenComplete((user, throwable) -> { if (throwable != null) { throwable.printStackTrace(); - } else { + } else if (user != null) { SaturnPlayer.set(user.toSaturnPlayer(uuid, name)); } }); diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java index 1f5d986..0a09c53 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceMethods.java @@ -2,47 +2,112 @@ 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 { - public class Types { + + // ========================= + // 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 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 Authenticate = new Method<>("auth", - String.class, Types.Player.class, String.class); + String.class, + Types.Player.class, + String.class); - // Equip + // ========================= + // EQUIP + // ========================= public static final Method SetCloak = new Method<>("set_cloak", - String.class, String.class, String.class); + String.class, + String.class, + String.class); + public static final Method SetHat = new Method<>("set_hat", - String.class, String.class, String.class); + String.class, + String.class, + String.class); - // Buy + // ========================= + // BUY + // ========================= public static final Method BuyCloak = new Method<>("buy_cloak", - String.class, String.class, String.class); + String.class, + String.class, + String.class); + public static final Method BuyHat = new Method<>("buy_hat", - String.class, String.class, String.class); + String.class, + String.class, + String.class); - // Emote - public static final Method Emote = new Method<>("emote", - Types.EmoteRequest.class, String.class, String.class); + // ========================= + // EMOTE + // ========================= + public static final Method Emote = new Method<>("emote", + Types.EmoteRequest.class, + Void.class, + String.class); - public static final Method EmoteEvent = new Method<>("emote_event", - Types.EmoteEvent.class, String.class, String.class); + public static final Method EmoteEvent = new Method<>("emote_event", + Types.EmoteEvent.class, + Void.class, + Void.class); - public static final Method Player = new Method<>("player", - String.class, Types.Player.class, String.class); + // ========================= + // GET PLAYER + // ========================= + public static final Method GetPlayer = new Method<>("get_player", + String.class, + Types.Player.class, + String.class); + + // ========================= + // PLAYER (Server Notification) + // ========================= + public static final Method Player = new Method<>("player", + Types.PlayerStream.class, + Void.class, + Void.class); + + // ========================= + // SEND PLAYER + // ========================= + public static final Method SendPlayer = new Method<>("send_player", + SendPlayerRequest.class, + Void.class, + String.class); } \ No newline at end of file diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index f4c935e..bda57c7 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -47,7 +47,7 @@ public class SaturnPlayer { } public static String[] getExternalUUIDAsString() { - return PLAYERS.keySet().stream().filter(id -> !id.equals(ServiceClient.uuid)).map(UUID::toString) + return PLAYERS.keySet().stream().filter(id -> id.equals(ServiceClient.uuid)).map(UUID::toString) .toArray(String[]::new); } diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java index d624942..f8140df 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java @@ -62,7 +62,7 @@ public class Cloaks { */ public static void setCloak(String cloakName) { if (availableCloaks.contains(cloakName)) { - setCloak(ServiceClient.uuid, cloakName); + // setCloak(ServiceClient.uuid, cloakName); ServiceClient.setCloak(cloakName); } } From 6717d7e9015f4eb9f6795fbd0efd507b5d958d04 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 18:51:28 +0100 Subject: [PATCH 81/82] Fixed UUID and cloak --- .../org/saturnclient/saturnclient/client/ServiceClient.java | 6 +++--- .../saturnclient/client/player/SaturnPlayer.java | 2 +- .../saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index 27611bf..fe00d24 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -71,7 +71,7 @@ public class ServiceClient { eventHandlers(); - player(uuid, username); + SaturnPlayer.set(new SaturnPlayer(uuid, username, response.cloak(), response.hat())); Cloaks.loadCloak(uuid); @@ -165,7 +165,7 @@ public class ServiceClient { public static void eventHandlers() { session.onNotification(ServiceMethods.EmoteEvent, (data) -> { for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) { - if (player.getUuidAsString().replace("-", "").equals(data.from())) { + if (player.getUuidAsString().equals(data.from())) { AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player); if (data.emote() != null && !data.emote().isEmpty()) { if (animationStack.isActive() && animationStack.getPriority() == 1000) { @@ -190,7 +190,7 @@ public class ServiceClient { public static void player(UUID uuid, String name) { try { - session.request(ServiceMethods.GetPlayer, uuid.toString().replaceAll("-", "")) + session.request(ServiceMethods.GetPlayer, uuid.toString()) .whenComplete((user, throwable) -> { if (throwable != null) { throwable.printStackTrace(); diff --git a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java index bda57c7..f4c935e 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java +++ b/src/main/java/org/saturnclient/saturnclient/client/player/SaturnPlayer.java @@ -47,7 +47,7 @@ public class SaturnPlayer { } public static String[] getExternalUUIDAsString() { - return PLAYERS.keySet().stream().filter(id -> id.equals(ServiceClient.uuid)).map(UUID::toString) + return PLAYERS.keySet().stream().filter(id -> !id.equals(ServiceClient.uuid)).map(UUID::toString) .toArray(String[]::new); } diff --git a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java index f8140df..d624942 100644 --- a/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java +++ b/src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java @@ -62,7 +62,7 @@ public class Cloaks { */ public static void setCloak(String cloakName) { if (availableCloaks.contains(cloakName)) { - // setCloak(ServiceClient.uuid, cloakName); + setCloak(ServiceClient.uuid, cloakName); ServiceClient.setCloak(cloakName); } } From bff18a05d1efd7ae51467392aecde1e1df73d6bb Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 23 Feb 2026 21:30:50 +0100 Subject: [PATCH 82/82] Changed ip to the actual server --- .../org/saturnclient/saturnclient/client/ServiceClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java index fe00d24..442f7d9 100644 --- a/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java +++ b/src/main/java/org/saturnclient/saturnclient/client/ServiceClient.java @@ -22,7 +22,7 @@ public class ServiceClient { public static boolean connectTimeout() { try { - session = Session.connect("ws://127.0.0.1:8080", 10, TimeUnit.SECONDS); + session = Session.connect("wss://saturn-server.selimaj.dev", 10, TimeUnit.SECONDS); return true; } catch (Exception e) { return false;