Merge pull request #24 from saturnclientmc/services-and-ui-tweaks

Services and UI tweaks
This commit is contained in:
2026-02-23 12:32:03 -08:00
committed by GitHub
44 changed files with 571 additions and 173 deletions
+130 -23
View File
@@ -1,41 +1,126 @@
/*
* ================================================================
* Saturn Client - Fabric Mod Build Configuration
* ================================================================
*
* This build file configures:
* - Fabric Loom (Minecraft mod development)
* - Dependency management
* - Resource processing
* - Java toolchain (Java 21)
* - Publishing setup
*
* All version values are defined in gradle.properties.
* ================================================================
*/
plugins { plugins {
// Fabric mod development plugin
id 'fabric-loom' version '1.9-SNAPSHOT' id 'fabric-loom' version '1.9-SNAPSHOT'
// Maven publishing support
id 'maven-publish' id 'maven-publish'
} }
/*
* ================================================================
* Project Metadata
* ================================================================
*/
version = project.mod_version version = project.mod_version
group = project.maven_group group = project.maven_group
repositories {} /*
* ================================================================
* Repositories
* ================================================================
* All dependency sources.
*/
repositories {
// Fabric
maven { url 'https://maven.fabricmc.net/' }
// JitPack (GitHub releases)
maven { url 'https://jitpack.io' }
// Parchment mappings (if needed later)
maven { url 'https://maven.parchmentmc.org' }
// Special Model Loader
maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" }
// Player Animation Library
maven { name = "KosmX Maven"; url = 'https://maven.kosmx.dev/' }
// Central repository
mavenCentral()
}
/*
* ================================================================
* Dependencies
* ================================================================
*/
dependencies { dependencies {
// ─────────────────────────────────────────────
// Minecraft Core
// ─────────────────────────────────────────────
minecraft "com.mojang:minecraft:${project.minecraft_version}" minecraft "com.mojang:minecraft:${project.minecraft_version}"
// Switch back to Yarn // Yarn mappings (named mappings for dev environment)
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
// Fabric Loader
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
// ─────────────────────────────────────────────
// Rendering / Model Libraries
// ─────────────────────────────────────────────
// Special Model Loader (included inside your mod jar)
modApi "dev.felnull:special-model-loader:1.4.0+pre.1" modApi "dev.felnull:special-model-loader:1.4.0+pre.1"
include "dev.felnull:special-model-loader:1.4.0+pre.1" include "dev.felnull:special-model-loader:1.4.0+pre.1"
implementation 'com.github.selimaj-dev:session-java:0.1.4' // SVG Rendering Library
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}") // Animation
// ─────────────────────────────────────────────
// Player Animation Library (bundled into your mod)
include modImplementation(
"dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}"
)
// Optional: BendyLib (only if you directly use it)
// include modRuntimeOnly(
// "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}"
// )
// ─────────────────────────────────────────────
// External Libraries
// ─────────────────────────────────────────────
// Session Java (auth/session handling)
implementation "com.github.selimaj-dev:session-java:0.1.6"
} }
repositories { /*
maven { url 'https://maven.fabricmc.net/' } // Fabric's official repo * ================================================================
maven { url 'https://jitpack.io' } * Resource Processing
maven { url "https://maven.parchmentmc.org" } * ================================================================
maven { name = "FelNull Maven"; url = "https://maven.felnull.dev" } * Injects version values into fabric.mod.json.
maven { name = "KosmX maven"; url = 'https://maven.kosmx.dev/' } */
}
processResources { processResources {
inputs.property "version", project.version inputs.property "version", project.version
inputs.property "minecraft_version", project.minecraft_version inputs.property "minecraft_version", project.minecraft_version
@@ -43,29 +128,42 @@ processResources {
filteringCharset "UTF-8" filteringCharset "UTF-8"
filesMatching("fabric.mod.json") { filesMatching("fabric.mod.json") {
expand "version": project.version, expand(
"version": project.version,
"minecraft_version": project.minecraft_version, "minecraft_version": project.minecraft_version,
"loader_version": project.loader_version "loader_version": project.loader_version
)
} }
} }
/*
* ================================================================
* Java Configuration
* ================================================================
* Targeting Java 21.
*/
def targetJavaVersion = 21 def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8" options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { options.release.set(targetJavaVersion)
it.options.release.set(targetJavaVersion)
}
} }
java { java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
archivesBaseName = project.archives_base_name archivesBaseName = project.archives_base_name
withSourcesJar() withSourcesJar()
} }
/*
* ================================================================
* Jar Packaging
* ================================================================
* Includes LICENSE and NOTICE inside the final jar.
*/
jar { jar {
from("LICENSE") { from("LICENSE") {
rename { "${it}_${project.archivesBaseName}" } rename { "${it}_${project.archivesBaseName}" }
@@ -75,6 +173,13 @@ jar {
} }
} }
/*
* ================================================================
* Publishing
* ================================================================
* Configured but no remote repository defined yet.
*/
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { mavenJava(MavenPublication) {
@@ -82,5 +187,7 @@ publishing {
} }
} }
repositories {} repositories {
// Add publishing repository here when needed
}
} }
@@ -9,46 +9,45 @@ import org.saturnclient.ui2.RenderScope;
import org.saturnclient.modules.ModuleDetails; import org.saturnclient.modules.ModuleDetails;
public class Keystrokes extends Module implements HudMod { public class Keystrokes extends Module implements HudMod {
public static Property<Boolean> enabled = Property.bool(false);
public static Property<Boolean> showMouse = Property.bool(true);
public static Property<Boolean> showSpace = Property.bool(false);
public static ModDimensions dimensions = new ModDimensions(78, 46);
// Cache key states to avoid repeated calls private static final Property<Boolean> enabled = Property.bool(false);
private boolean w = false; private static final Property<Boolean> showMouse = Property.bool(true);
private boolean a = false; private static final Property<Boolean> showSpace = Property.bool(false);
private boolean s = false;
private boolean d = false;
private boolean lmb = false;
private boolean rmb = false;
private boolean space = false;
// Cache dimensions to avoid recalculation private static final Property<Integer> clickBg = Property.color(0xFFCCCCCC);
private int cachedHeight = 54; private static final Property<Integer> clickFg = Property.color(0xFFFFFFFF);
private boolean dimensionsDirty = true;
private static Property<Integer> clickBg = Property.color(0xFFCCCCCC); private static final ModDimensions dimensions = new ModDimensions(78, 54);
private static Property<Integer> clickFg = Property.color(0xFFFFFFFF);
// Key states
private boolean w, a, s, d, lmb, rmb, space;
// Layout constants
private static final int KEY_SIZE = 24;
private static final int KEY_SPACING = 3;
private static final int MOUSE_WIDTH = 38;
private static final int MOUSE_HEIGHT = 24;
private static final int SPACE_HEIGHT = 19;
public Keystrokes() { public Keystrokes() {
super(new ModuleDetails("Keystrokes", "keystrokes") super(new ModuleDetails("Keystrokes", "keystrokes")
.description("Displays the keystrokes for movement") .description("Displays movement keystrokes")
.version("v0.1.0") .version("v0.2.0")
.tags("Utility"), .tags("Utility"),
enabled.named("Enabled"), enabled.named("Enabled"),
showMouse.named("Show mouse clicks"), showMouse.named("Show mouse clicks"),
showSpace.named("Show space clicks"), showSpace.named("Show space"),
dimensions.prop(), dimensions.prop(),
clickFg.named("Clicked fg"), clickFg.named("Clicked fg"),
clickBg.named("Clicked bg")); clickBg.named("Clicked bg")
);
dimensions.renderBackground = false; dimensions.renderBackground = false;
} }
@Override @Override
public void tick() { public void tick() {
// Cache key states to reduce method calls
w = SaturnClient.client.options.forwardKey.isPressed(); w = SaturnClient.client.options.forwardKey.isPressed();
a = SaturnClient.client.options.leftKey.isPressed(); a = SaturnClient.client.options.leftKey.isPressed();
s = SaturnClient.client.options.backKey.isPressed(); s = SaturnClient.client.options.backKey.isPressed();
@@ -57,103 +56,84 @@ public class Keystrokes extends Module implements HudMod {
rmb = SaturnClient.client.options.useKey.isPressed(); rmb = SaturnClient.client.options.useKey.isPressed();
space = SaturnClient.client.options.jumpKey.isPressed(); space = SaturnClient.client.options.jumpKey.isPressed();
// Mark dimensions as dirty if mouse or space visibility changed updateHeight();
if (dimensionsDirty) {
updateDimensions();
dimensionsDirty = false;
}
} }
private void updateDimensions() { private void updateHeight() {
cachedHeight = 54; // base height with padding considered int height = KEY_SIZE * 2 + KEY_SPACING;
if (showMouse.value) { if (showSpace.value)
cachedHeight += 27; height += SPACE_HEIGHT + KEY_SPACING;
}
if (showSpace.value) { if (showMouse.value)
cachedHeight += 22; height += MOUSE_HEIGHT + KEY_SPACING;
}
dimensions.height = cachedHeight; dimensions.height = height;
} }
@Override @Override
public void renderDummy(RenderScope scope) { public void renderDummy(RenderScope scope) {
// Use cached dimensions render(scope, false);
dimensions.height = cachedHeight;
// WASD keys (3px padding between each key)
renderKey(scope, false, 'W', 27, 0); // Top center
renderKey(scope, false, 'A', 0, 27); // Bottom left
renderKey(scope, false, 'S', 27, 27); // Bottom center
renderKey(scope, false, 'D', 54, 27); // Bottom right
if (showMouse.value) {
renderKeyM(scope, false, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, false, "RMB", 40, 54); // With 3px between
}
if (showSpace.value) {
renderKeySpace(scope, false, 0, cachedHeight - (showSpace.value ? 22 : 0));
}
} }
@Override @Override
public void renderHud(RenderScope scope) { public void renderHud(RenderScope scope) {
// Use cached dimensions render(scope, true);
dimensions.height = cachedHeight;
// WASD keys (3px padding between each key)
renderKey(scope, w, 'W', 27, 0); // Top center
renderKey(scope, a, 'A', 0, 27); // Bottom left
renderKey(scope, s, 'S', 27, 27); // Bottom center
renderKey(scope, d, 'D', 54, 27); // Bottom right
if (showMouse.value) {
renderKeyM(scope, lmb, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, rmb, "RMB", 40, 54); // With 3px between
} }
private void render(RenderScope scope, boolean live) {
int yOffset = 0;
// --- W ---
renderKey(scope, live && w, 'W', KEY_SIZE + KEY_SPACING, yOffset);
// --- A S D ---
yOffset += KEY_SIZE + KEY_SPACING;
renderKey(scope, live && a, 'A', 0, yOffset);
renderKey(scope, live && s, 'S', KEY_SIZE + KEY_SPACING, yOffset);
renderKey(scope, live && d, 'D', (KEY_SIZE + KEY_SPACING) * 2, yOffset);
yOffset += KEY_SIZE + KEY_SPACING;
// --- SPACE ---
if (showSpace.value) { if (showSpace.value) {
renderKeySpace(scope, space, 0, cachedHeight - (showSpace.value ? 22 : 0)); renderKeySpace(scope, live && space, 0, yOffset);
yOffset += SPACE_HEIGHT + KEY_SPACING;
}
// --- MOUSE ---
if (showMouse.value) {
renderKeyMouse(scope, live && lmb, "LMB", 0, yOffset);
renderKeyMouse(scope, live && rmb, "RMB", MOUSE_WIDTH + KEY_SPACING, yOffset);
} }
} }
// Consistent 3px padding applied below: private void renderKey(RenderScope scope, boolean pressed, char c, int x, int y) {
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
private void renderKeySpace(RenderScope scope, boolean isPressed, int x, int y) { scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg);
int height = 19; scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg);
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, dimensions.width, height, dimensions.radius.value, bgColor);
// A 30-pixel line centered vertically with 3px top padding
int lineY = y + (height - 1) / 2;
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fgColor);
} }
private void renderKeyM(RenderScope scope, boolean isPressed, String c, int x, int y) { private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) {
int width = 38, height = 24; int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; int fg = pressed ? clickFg.value : dimensions.fgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, width, height, dimensions.radius.value, bgColor); scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_HEIGHT, dimensions.radius.value, bg);
scope.drawText(0.6f, text, x + 9, y + 7, dimensions.font.value, fg);
// Apply padding: 3px top and left -> add slight offset to x/y
scope.drawText(0.6f, c, x + 9, y + 7, dimensions.font.value, fgColor);
} }
private void renderKey(RenderScope scope, boolean isPressed, char c, int x, int y) { private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) {
int size = 24; int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int bgColor = isPressed ? clickBg.value : dimensions.bgColor.value; int fg = pressed ? clickFg.value : dimensions.fgColor.value;
int fgColor = isPressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, size, size, dimensions.radius.value, bgColor); scope.drawRoundedRectangle(x, y, dimensions.width, SPACE_HEIGHT, dimensions.radius.value, bg);
// Center the character with 3px top padding int lineY = y + SPACE_HEIGHT / 2;
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fgColor); scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg);
} }
@Override @Override
@@ -169,6 +149,5 @@ public class Keystrokes extends Module implements HudMod {
@Override @Override
public void onEnabled(boolean e) { public void onEnabled(boolean e) {
enabled.value = e; enabled.value = e;
dimensionsDirty = true;
} }
} }
@@ -8,8 +8,13 @@ import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.Hats; import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks; import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
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.Session;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.util.Identifier;
public class ServiceClient { public class ServiceClient {
private static Session session; private static Session session;
@@ -17,7 +22,7 @@ public class ServiceClient {
public static boolean connectTimeout() { public static boolean connectTimeout() {
try { 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; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;
@@ -46,14 +51,15 @@ public class ServiceClient {
uuid = mcSession.getUuidOrNull(); uuid = mcSession.getUuidOrNull();
String username = mcSession.getUsername(); String username = mcSession.getUsername();
SaturnClient.LOGGER.info("Authenticating with UUID: " + accessToken); SaturnClient.LOGGER.info("Authenticating with UUID: " + uuid);
if (!connectTimeout()) { if (!connectTimeout()) {
SaturnClient.LOGGER.error("Unable to authenticate: Session Server Timeout"); SaturnClient.LOGGER.error("Unable to authenticate: Session Server Timeout");
return false; return false;
} }
ServiceMethods.AuthResponse response = session.request(ServiceMethods.Authenticate, accessToken).get(); ServiceMethods.Types.Player response = session.request(ServiceMethods.Authenticate, accessToken)
.get();
for (String availableCloak : response.cloaks()) { for (String availableCloak : response.cloaks()) {
Cloaks.availableCloaks.add(availableCloak); Cloaks.availableCloaks.add(availableCloak);
@@ -63,7 +69,9 @@ public class ServiceClient {
Hats.availableHats.add(availableHat); Hats.availableHats.add(availableHat);
} }
SaturnPlayer.set(uuid, username, new SaturnPlayer(uuid, username, response.cloak(), response.hat())); eventHandlers();
SaturnPlayer.set(new SaturnPlayer(uuid, username, response.cloak(), response.hat()));
Cloaks.loadCloak(uuid); Cloaks.loadCloak(uuid);
@@ -74,33 +82,67 @@ public class ServiceClient {
} }
} }
public static void setCloak(String cloak) { public static void setCloak(String itemId) {
try { try {
session.request(ServiceMethods.SetCloak, cloak).get(); 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) { } catch (Exception e) {
SaturnClient.LOGGER.error("Failed to set cloak (service): ", e); SaturnClient.LOGGER.error("Failed to set cloak (service): ", e);
} }
} }
public static void setHat(String hat) { public static void setHat(String itemId) {
try { try {
session.request(ServiceMethods.SetHat, hat).get(); 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) { } catch (Exception e) {
SaturnClient.LOGGER.error("Failed to set hat (service): ", e); SaturnClient.LOGGER.error("Failed to set hat (service): ", e);
} }
} }
public static void buyCloak(String cloak) { public static void buyCloak(String itemId) {
try { try {
session.request(ServiceMethods.BuyCloak, cloak).get(); session.request(ServiceMethods.BuyCloak, itemId).whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else {
Cloaks.availableCloaks.add(itemId);
}
});
} catch (Exception e) { } catch (Exception e) {
SaturnClient.LOGGER.error("Failed to buy cloak (service): ", e); SaturnClient.LOGGER.error("Failed to buy cloak (service): ", e);
} }
} }
public static void buyHat(String hat) { public static void buyHat(String itemId) {
try { try {
session.request(ServiceMethods.BuyHat, hat).get(); session.request(ServiceMethods.BuyHat, itemId).whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else {
Hats.availableHats.add(itemId);
}
});
} catch (Exception e) { } catch (Exception e) {
SaturnClient.LOGGER.error("Failed to buy hat (service): ", e); SaturnClient.LOGGER.error("Failed to buy hat (service): ", e);
} }
@@ -108,9 +150,56 @@ public class ServiceClient {
public static void emote(String emote) { public static void emote(String emote) {
try { try {
session.request(ServiceMethods.Emote, emote).get(); session.request(ServiceMethods.Emote,
new ServiceMethods.Types.EmoteRequest(emote, SaturnPlayer.getExternalUUIDAsString()))
.whenComplete((msg, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
}
});
} catch (Exception e) { } catch (Exception e) {
SaturnClient.LOGGER.error("Failed to emote (service): ", e); SaturnClient.LOGGER.error("Failed to emote (service): ", e);
} }
} }
public static void eventHandlers() {
session.onNotification(ServiceMethods.EmoteEvent, (data) -> {
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
if (player.getUuidAsString().equals(data.from())) {
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
if (data.emote() != null && !data.emote().isEmpty()) {
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000);
}
animationStack.addAnimLayer(1000,
PlayerAnimationRegistry
.getAnimation(Identifier.of("saturnclient", data.emote()))
.playAnimation());
} else {
animationStack.removeLayer(1000);
}
}
}
});
session.onNotification(ServiceMethods.Player, (player) -> {
System.out.println(player);
SaturnPlayer.set(player.toSaturnPlayer());
});
}
public static void player(UUID uuid, String name) {
try {
session.request(ServiceMethods.GetPlayer, uuid.toString())
.whenComplete((user, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
} else if (user != null) {
SaturnPlayer.set(user.toSaturnPlayer(uuid, name));
}
});
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to get player", e);
}
}
} }
@@ -1,27 +1,113 @@
package org.saturnclient.saturnclient.client; 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; import dev.selimaj.session.types.Method;
public class ServiceMethods { public class ServiceMethods {
public record AuthResponse(String cloak, String hat, String[] cloaks, String[] hats) {
// =========================
// Types (Rust structs)
// =========================
public static class Types {
public record EmoteRequest(String emote, String[] targets) {
} }
public static final Method<String, AuthResponse, String> Authenticate = new Method<>("auth", public record EmoteEvent(String emote, String from) {
String.class, AuthResponse.class, String.class); }
// Equip public record Player(
String cloak,
String hat,
String[] cloaks,
String[] hats) {
public SaturnPlayer toSaturnPlayer(UUID uuid, String name) {
return new SaturnPlayer(uuid, name, cloak, hat);
}
}
public record PlayerStream(Player player, String name, String uuid) {
public SaturnPlayer toSaturnPlayer() {
return new SaturnPlayer(UUID.fromString(uuid), name, this.player.cloak, this.player.hat);
}
}
public record SendPlayerRequest(String[] targets) {
}
}
// =========================
// AUTH
// =========================
public static final Method<String, Types.Player, String> Authenticate = new Method<>("auth",
String.class,
Types.Player.class,
String.class);
// =========================
// EQUIP
// =========================
public static final Method<String, String, String> SetCloak = new Method<>("set_cloak", public static final Method<String, String, String> SetCloak = new Method<>("set_cloak",
String.class, String.class, String.class); String.class,
String.class,
String.class);
public static final Method<String, String, String> SetHat = new Method<>("set_hat", public static final Method<String, String, String> SetHat = new Method<>("set_hat",
String.class, String.class, String.class); String.class,
String.class,
String.class);
// Buy // =========================
// BUY
// =========================
public static final Method<String, String, String> BuyCloak = new Method<>("buy_cloak", public static final Method<String, String, String> BuyCloak = new Method<>("buy_cloak",
String.class, String.class, String.class); String.class,
public static final Method<String, String, String> BuyHat = new Method<>("buy_hat", String.class,
String.class, String.class, String.class); String.class);
// Emote public static final Method<String, String, String> BuyHat = new Method<>("buy_hat",
public static final Method<String, String, String> Emote = new Method<>("emote", String.class,
String.class, String.class, String.class); String.class,
String.class);
// =========================
// EMOTE
// =========================
public static final Method<Types.EmoteRequest, Void, String> Emote = new Method<>("emote",
Types.EmoteRequest.class,
Void.class,
String.class);
public static final Method<Types.EmoteEvent, Void, Void> EmoteEvent = new Method<>("emote_event",
Types.EmoteEvent.class,
Void.class,
Void.class);
// =========================
// GET PLAYER
// =========================
public static final Method<String, Types.Player, String> GetPlayer = new Method<>("get_player",
String.class,
Types.Player.class,
String.class);
// =========================
// PLAYER (Server Notification)
// =========================
public static final Method<Types.PlayerStream, Void, Void> Player = new Method<>("player",
Types.PlayerStream.class,
Void.class,
Void.class);
// =========================
// SEND PLAYER
// =========================
public static final Method<SendPlayerRequest, Void, String> SendPlayer = new Method<>("send_player",
SendPlayerRequest.class,
Void.class,
String.class);
} }
@@ -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.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.client.ServiceClient;
import net.minecraft.client.network.PlayerListEntry; import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@@ -46,7 +47,6 @@ public class PlayerTracker {
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) { private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
GameProfile profile = player.getProfile(); GameProfile profile = player.getProfile();
UUID uuid = profile.getId(); ServiceClient.player(profile.getId(), profile.getName());
SaturnPlayer.player(uuid);
} }
} }
@@ -41,18 +41,14 @@ public class SaturnPlayer {
return PLAYERS.get(uuid); return PLAYERS.get(uuid);
} }
public static void set(UUID uuid, String name, SaturnPlayer player) { public static void set(SaturnPlayer player) {
PLAYER_NAMES.put(name, uuid); PLAYER_NAMES.put(player.name, player.uuid);
PLAYERS.put(uuid, player); PLAYERS.put(player.uuid, player);
} }
public static void player(UUID uuid) { public static String[] getExternalUUIDAsString() {
if (PLAYERS.containsKey(uuid)) { return PLAYERS.keySet().stream().filter(id -> !id.equals(ServiceClient.uuid)).map(UUID::toString)
return; .toArray(String[]::new);
} }
// If offline then leave
PLAYERS.put(uuid, null);
}
} }
@@ -28,7 +28,8 @@ public class HatFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState,
PlayerEntityRenderState state, float limbAngle, float limbDistance) { PlayerEntityRenderState state, float limbAngle, float limbDistance) {
ItemStack headItem = state.equippedHeadStack; ItemStack headItem = state.equippedHeadStack;
SaturnPlayer player = SaturnPlayer.get(state.name); SaturnPlayer player = SaturnPlayer.get(state.name);
if (headItem.isEmpty() || state.invisible || player == null || player.hat.isEmpty()) {
if (!headItem.isEmpty() || state.invisible || player == null || player.hat.isEmpty()) {
return; return;
} }
@@ -24,10 +24,7 @@ public class Hats {
public static void setHat(UUID uuid, String hatName) { public static void setHat(UUID uuid, String hatName) {
SaturnPlayer player = SaturnPlayer.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null) { if (player != null) {
// Auth.players.put(uuid, new SaturnPlayer("", hatName));
// TODO: address this behaviour
} else {
player.hat = hatName; player.hat = hatName;
} }
} }
@@ -76,10 +76,7 @@ public class Cloaks {
public static void setCloak(UUID uuid, String cloakName) { public static void setCloak(UUID uuid, String cloakName) {
SaturnPlayer player = SaturnPlayer.get(uuid); SaturnPlayer player = SaturnPlayer.get(uuid);
if (player == null) { if (player != null) {
// Auth.players.put(uuid, new SaturnPlayer(cloakName, null));
// TODO: Address this functionality
} else {
player.cloak = cloakName; player.cloak = cloakName;
} }
@@ -9,11 +9,14 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.saturnclient.ui2.resources.Fonts; import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.SvgTexture;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor; import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.font.TextRenderer.TextLayerType; import net.minecraft.client.font.TextRenderer.TextLayerType;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.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, private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, float u1, float u2, float v1,
float v2, int color) { float v2, int color) {
if (sprite.toString().endsWith(".svg")) {
sprite = SvgTexture.getSvg(MinecraftClient.getInstance(), sprite, (x2 - x1) * 2, (y2 - y1) * 2);
}
if (color == 0) if (color == 0)
return; return;
x1 *= 4; x1 *= 4;
@@ -51,7 +51,6 @@ public class Utils {
} }
public static <T> T getOrNull(List<T> list, int index) { public static <T> T getOrNull(List<T> list, int index) {
SaturnClient.LOGGER.info("Accessing index {} in list of size {}", index, list.size());
return (index >= 0 && index < list.size()) ? list.get(index) : null; return (index >= 0 && index < list.size()) ? list.get(index) : null;
} }
@@ -3,8 +3,9 @@ package org.saturnclient.ui2.elements;
import org.saturnclient.ui2.Element; import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext; import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope; import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.SvgTexture;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class ImageTexture extends Element { public class ImageTexture extends Element {
@@ -16,7 +17,16 @@ public class ImageTexture extends Element {
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured); if (sprite.toString().endsWith(".svg")) {
MinecraftClient client = MinecraftClient.getInstance();
// Get the actual window pixel dimensions for the image
int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWidth());
int renderHeight = (int) (height * client.getWindow().getFramebufferHeight() / client.getWindow().getHeight());
sprite = SvgTexture.getSvg(client, sprite, renderWidth * 2, renderHeight * 2);
}
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height); renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height);
} }
} }
@@ -0,0 +1,80 @@
package org.saturnclient.ui2.resources;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGUniverse;
import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
public class SvgTexture {
private static List<Identifier> svgCache = new ArrayList<>();
private static BufferedImage renderSvg(InputStream svgStream, int width, int height) throws Exception {
// Load SVG
SVGUniverse universe = new SVGUniverse();
java.net.URI uri = universe.loadSVG(svgStream, "tempSvg");
SVGDiagram diagram = universe.getDiagram(uri);
// Get intrinsic size of SVG
float svgWidth = (float) diagram.getWidth();
float svgHeight = (float) diagram.getHeight();
// Compute scale factors
float scaleX = width / svgWidth;
float scaleY = height / svgHeight;
// Create output image
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// Enable anti-aliasing for smooth scaling
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// Apply scaling transform
g.scale(scaleX, scaleY);
// Render SVG
diagram.render(g);
g.dispose();
return image;
}
@Nullable
public static Identifier getSvg(MinecraftClient client, Identifier svgImage, int width, int height) {
Identifier id = Identifier
.of(svgImage.toString().replaceAll("\\.svg$", (width + "_" + height).toString() + ".png"));
if (svgCache.contains(id)) {
return id;
}
try (InputStream svgStream = client
.getResourceManager()
.getResource(svgImage).get()
.getInputStream()) {
BufferedImage image = renderSvg(svgStream, width, height);
IdentifierUtils.registerBufferedImageTexture(id, image);
return id;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
@@ -53,7 +53,7 @@ public class Textures {
} }
public static Identifier getModIcon(String modId) { public static Identifier getModIcon(String modId) {
return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".png"); return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".svg");
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

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

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

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

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

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

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

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

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

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

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 B

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

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 B

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

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 B

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

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

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

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

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

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

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

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg>

After

Width:  |  Height:  |  Size: 289 B