diff --git a/src/main/java/org/saturnclient/client/ServiceClient.java b/src/main/java/org/saturnclient/client/ServiceClient.java new file mode 100644 index 0000000..c5a7cf4 --- /dev/null +++ b/src/main/java/org/saturnclient/client/ServiceClient.java @@ -0,0 +1,198 @@ +package org.saturnclient.client; + +import java.util.UUID; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.common.minecraft.bindings.SaturnClientBindings; + +import dev.selimaj.session.Session; + +public class ServiceClient { + private static Session session; + public static UUID uuid; + + public static final List availableCloaks = new ArrayList<>(); + public static final List availableHats = new ArrayList<>(); + + public static boolean connectTimeout() { + try { + session = Session.connect("wss://saturn-server.selimaj.dev", 10, TimeUnit.SECONDS); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean authenticate() { + SaturnClientBindings.platform().onClientStopping(() -> { + if (session != null) { + try { + session.close(); + } catch (Exception e) { + SaturnClientBindings.platform().logError("Unable to close Saturn session", e); + } + } + }); + + try { + String accessToken = SaturnClientBindings.platform().getAccessToken(); + uuid = SaturnClientBindings.platform().getUuid(); + String username = SaturnClientBindings.platform().getUsername(); + + if (accessToken == null || uuid == null || username == null) { + SaturnClientBindings.platform().logError("No active Minecraft session found"); + return false; + } + + SaturnClientBindings.platform().logInfo("Authenticating with UUID: " + uuid); + + if (!connectTimeout()) { + SaturnClientBindings.platform().logError("Unable to authenticate: Session Server Timeout"); + return false; + } + + ServiceMethods.Types.Player response = session.request(ServiceMethods.Authenticate, accessToken) + .get(); + + for (String availableCloak : response.cloaks()) { + availableCloaks.add(availableCloak); + } + + for (String availableHat : response.hats()) { + availableHats.add(availableHat); + } + + eventHandlers(); + + SaturnPlayer.set(new SaturnPlayer(uuid, username, response.cloak(), response.hat())); + + return true; + } catch (Exception e) { + SaturnClientBindings.platform().logError("Authentication failed", e); + return false; + } + } + + public static void setCloak(String itemId) { + try { + session.request(ServiceMethods.SetCloak, itemId).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } else { + try { + session.request(ServiceMethods.SendPlayer, + new ServiceMethods.Types.SendPlayerRequest(SaturnPlayer.getExternalUUIDAsString())); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } catch (Exception e) { + SaturnClientBindings.platform().logError("Failed to set cloak (service): ", e); + } + } + + public static void setHat(String itemId) { + try { + session.request(ServiceMethods.SetHat, itemId).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } else { + try { + session.request(ServiceMethods.SendPlayer, + new ServiceMethods.Types.SendPlayerRequest(SaturnPlayer.getExternalUUIDAsString())); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } catch (Exception e) { + SaturnClientBindings.platform().logError("Failed to set hat (service): ", e); + } + } + + public static void buyCloak(String itemId) { + try { + session.request(ServiceMethods.BuyCloak, itemId).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } else { + availableCloaks.add(itemId); + } + }); + } catch (Exception e) { + SaturnClientBindings.platform().logError("Failed to buy cloak (service): ", e); + } + } + + public static void buyHat(String itemId) { + try { + session.request(ServiceMethods.BuyHat, itemId).whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } else { + availableHats.add(itemId); + } + }); + } catch (Exception e) { + SaturnClientBindings.platform().logError("Failed to buy hat (service): ", e); + } + } + + public static void emote(String emote) { + try { + session.request(ServiceMethods.Emote, + new ServiceMethods.Types.EmoteRequest(emote, SaturnPlayer.getExternalUUIDAsString())) + .whenComplete((msg, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + } + }); + } catch (Exception e) { + SaturnClientBindings.platform().logError("Failed to emote (service): ", e); + } + } + + public static void eventHandlers() { + session.onNotification(ServiceMethods.EmoteEvent, (data) -> { + if (data == null) { + return; + } + UUID from; + try { + from = UUID.fromString(data.from()); + } catch (Exception e) { + return; + } + + if (data.emote() != null && !data.emote().isEmpty()) { + SaturnClientBindings.emotes().setEmote(from, data.emote()); + } else { + SaturnClientBindings.emotes().setEmote(from, null); + } + }); + + 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) { + SaturnClientBindings.platform().logError("Failed to get player", e); + } + } +} diff --git a/src/main/java/org/saturnclient/client/ServiceMethods.java b/src/main/java/org/saturnclient/client/ServiceMethods.java new file mode 100644 index 0000000..ac77b4e --- /dev/null +++ b/src/main/java/org/saturnclient/client/ServiceMethods.java @@ -0,0 +1,113 @@ +package org.saturnclient.client; + +import java.util.UUID; + +import org.saturnclient.client.ServiceMethods.Types.SendPlayerRequest; +import org.saturnclient.client.player.SaturnPlayer; + +import dev.selimaj.session.types.Method; + +public class ServiceMethods { + + // ========================= + // Types (Rust structs) + // ========================= + public static class Types { + + public record EmoteRequest(String emote, String[] targets) { + } + + public record EmoteEvent(String emote, String from) { + } + + public record Player( + String cloak, + String hat, + String[] cloaks, + String[] hats) { + public SaturnPlayer toSaturnPlayer(UUID uuid, String name) { + return new SaturnPlayer(uuid, name, cloak, hat); + } + } + + public record PlayerStream(Player player, String name, String uuid) { + public SaturnPlayer toSaturnPlayer() { + return new SaturnPlayer(UUID.fromString(uuid), name, this.player.cloak, this.player.hat); + } + } + + public record SendPlayerRequest(String[] targets) { + } + } + + // ========================= + // AUTH + // ========================= + public static final Method Authenticate = new Method<>("auth", + String.class, + Types.Player.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); + + // ========================= + // 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, + Void.class, + Void.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); +} diff --git a/src/main/java/org/saturnclient/client/player/Roles.java b/src/main/java/org/saturnclient/client/player/Roles.java new file mode 100644 index 0000000..38f4bc8 --- /dev/null +++ b/src/main/java/org/saturnclient/client/player/Roles.java @@ -0,0 +1,59 @@ +package org.saturnclient.client.player; + +import java.util.Map; +import java.util.UUID; + +import org.saturnclient.config.Config; + +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, + UUID.fromString("d8dc7023-179b-4460-bcf6-493fbf1289a6"), + Role.CONTRIBUTOR); + + /* + * Gets the icon color of a specific individual, here are the different colors + * + * - Owner: Dark Red + * - Admin: Red + * - Partners: Gold + * - Contributor: Aqua + * - Other/player: White + */ + public static int getIconColor(UUID uuid) { + Role role = ROLES.get(uuid); + + if (role == null) { + return 0xFFFFFF; + } + + switch (role) { + case OWNER: + return 0xAA0000; + + case ADMIN: + return 0xFF5555; + + case PARTNER: + return 0xFFAA00; + + case CONTRIBUTOR: + return 0x55FFFF; + + default: + return 0xFFFFFF; + } + } + + public static String getSaturnIndicator() { + return Config.realisticLogo.value ? "\uE003" : "\uE002"; + } +} diff --git a/src/main/java/org/saturnclient/client/player/SaturnPlayer.java b/src/main/java/org/saturnclient/client/player/SaturnPlayer.java new file mode 100644 index 0000000..dce3337 --- /dev/null +++ b/src/main/java/org/saturnclient/client/player/SaturnPlayer.java @@ -0,0 +1,55 @@ +package org.saturnclient.client.player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.saturnclient.common.minecraft.bindings.SaturnClientBindings; + +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(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 self = SaturnClientBindings.platform().getUuid(); + if (self == null) { + return null; + } + return PLAYERS.get(self); + } + + public static SaturnPlayer get(UUID uuid) { + return PLAYERS.get(uuid); + } + + public static SaturnPlayer get(String name) { + UUID uuid = PLAYER_NAMES.get(name); + if (uuid == null) + return null; + return PLAYERS.get(uuid); + } + + public static void set(SaturnPlayer player) { + PLAYER_NAMES.put(player.name, player.uuid); + PLAYERS.put(player.uuid, player); + } + + public static String[] getExternalUUIDAsString() { + UUID self = SaturnClientBindings.platform().getUuid(); + return PLAYERS.keySet().stream().filter(id -> self == null || !id.equals(self)).map(UUID::toString) + .toArray(String[]::new); + } + +} diff --git a/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnClientBindings.java b/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnClientBindings.java new file mode 100644 index 0000000..cbec7a5 --- /dev/null +++ b/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnClientBindings.java @@ -0,0 +1,31 @@ +package org.saturnclient.common.minecraft.bindings; + +public final class SaturnClientBindings { + private static SaturnPlatformBindings platform; + private static SaturnEmoteBindings emotes; + + private SaturnClientBindings() { + } + + public static void setPlatform(SaturnPlatformBindings bindings) { + platform = bindings; + } + + public static SaturnPlatformBindings platform() { + if (platform == null) { + throw new IllegalStateException("SaturnPlatformBindings not initialized"); + } + return platform; + } + + public static void setEmotes(SaturnEmoteBindings bindings) { + emotes = bindings; + } + + public static SaturnEmoteBindings emotes() { + if (emotes == null) { + throw new IllegalStateException("SaturnEmoteBindings not initialized"); + } + return emotes; + } +} diff --git a/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnEmoteBindings.java b/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnEmoteBindings.java new file mode 100644 index 0000000..e987f70 --- /dev/null +++ b/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnEmoteBindings.java @@ -0,0 +1,7 @@ +package org.saturnclient.common.minecraft.bindings; + +import java.util.UUID; + +public interface SaturnEmoteBindings { + void setEmote(UUID fromPlayerUuid, String emoteIdOrNull); +} diff --git a/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnPlatformBindings.java b/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnPlatformBindings.java new file mode 100644 index 0000000..46ba569 --- /dev/null +++ b/src/main/java/org/saturnclient/common/minecraft/bindings/SaturnPlatformBindings.java @@ -0,0 +1,19 @@ +package org.saturnclient.common.minecraft.bindings; + +import java.util.UUID; + +public interface SaturnPlatformBindings { + String getAccessToken(); + + UUID getUuid(); + + String getUsername(); + + void onClientStopping(Runnable handler); + + void logInfo(String message); + + void logError(String message); + + void logError(String message, Throwable throwable); +}