diff --git a/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java b/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java index f54b058..96e8710 100644 --- a/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java +++ b/src/client/java/dev/tggamesyt/szar/client/ClientCosmetics.java @@ -1,16 +1,21 @@ package dev.tggamesyt.szar.client; -import dev.tggamesyt.szar.Szar; +import com.google.gson.*; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.PacketByteBuf; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.text.MutableText; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; public class ClientCosmetics { @@ -20,62 +25,41 @@ public class ClientCosmetics { } public static class CosmeticProfile { - public final NameType nameType; - public final Formatting staticColor; // for STATIC names - public final Identifier capeTexture; // optional cape - public final int gradientStart; // RGB int, for GRADIENT - public final int gradientEnd; // RGB int, for GRADIENT - - public CosmeticProfile(NameType nameType, Formatting staticColor, Identifier capeTexture, - int gradientStart, int gradientEnd) { - this.nameType = nameType; - this.staticColor = staticColor; - this.capeTexture = capeTexture; - this.gradientStart = gradientStart; - this.gradientEnd = gradientEnd; - } + public NameType nameType; + public Integer staticColor; + public Integer gradientStart; + public Integer gradientEnd; + public Identifier capeTexture; } - // Registry private static final Map PROFILES = new HashMap<>(); - static { - // ===== TGdoesCode ===== animated gradient - PROFILES.put( - UUID.fromString("20bbb23e-2f22-46ba-b201-c6bd435b445b"), - new CosmeticProfile( - NameType.GRADIENT, - null, - new Identifier(Szar.MOD_ID, "textures/etc/tg_cape.png"), - 0x8CD6FF, // light blue - 0x00FFFF // cyan - ) - ); + // Player UUID -> Mojang cape list + public static final Map> MOJANG_CAPES = new HashMap<>(); - // ===== Berci08ur_mom ===== - PROFILES.put( - UUID.fromString("dda61748-15a4-45ff-9eea-29efc99c1711"), - new CosmeticProfile( - NameType.STATIC, - Formatting.GREEN, - new Identifier(Szar.MOD_ID, "textures/etc/gold_cape.png"), - 0, 0 - ) - ); - - // ===== gabri ===== - PROFILES.put( - UUID.fromString("52af5540-dd18-4ad9-9acb-50eb11531180"), - new CosmeticProfile( - NameType.STATIC, - Formatting.RED, - new Identifier(Szar.MOD_ID, "textures/etc/gabri_cape.png"), - 0, 0 - ) - ); + public static class MojangCape { + public String id; + public String name; + public String url; } - public static CosmeticProfile getProfile(UUID uuid) { + public static void apply(UUID uuid, NameType type, + Integer staticColor, + Integer gradientStart, + Integer gradientEnd, + Identifier capeTexture) { + + CosmeticProfile profile = new CosmeticProfile(); + profile.nameType = type; + profile.staticColor = staticColor; + profile.gradientStart = gradientStart; + profile.gradientEnd = gradientEnd; + profile.capeTexture = capeTexture; + + PROFILES.put(uuid, profile); + } + + public static CosmeticProfile get(UUID uuid) { return PROFILES.get(uuid); } @@ -83,33 +67,89 @@ public class ClientCosmetics { CosmeticProfile profile = PROFILES.get(uuid); if (profile == null) return null; - if (profile.nameType == NameType.STATIC) { - return Text.literal(name).formatted(profile.staticColor, Formatting.BOLD); + if (profile.nameType == NameType.STATIC && profile.staticColor != null) { + return Text.literal(name) + .styled(s -> s.withColor(profile.staticColor).withBold(true)); } - // GRADIENT animation long time = Util.getMeasuringTimeMs(); MutableText animated = Text.empty(); for (int i = 0; i < name.length(); i++) { - // Animate wave - float progress = (time * 0.08f) + (i * 1.2f); + float progress = (time * 0.008f) + (i * 0.5f); float wave = (float) Math.sin(progress); - float t = (wave + 1f) / 2f; // 0..1 + float t = (wave + 1f) / 2f; - // Interpolate RGB - int r = (int) MathHelper.lerp(t, (profile.gradientStart >> 16) & 0xFF, (profile.gradientEnd >> 16) & 0xFF); - int g = (int) MathHelper.lerp(t, (profile.gradientStart >> 8) & 0xFF, (profile.gradientEnd >> 8) & 0xFF); - int b = (int) MathHelper.lerp(t, profile.gradientStart & 0xFF, profile.gradientEnd & 0xFF); + int r = (int) MathHelper.lerp(t, + (profile.gradientStart >> 16) & 0xFF, + (profile.gradientEnd >> 16) & 0xFF); + + int g = (int) MathHelper.lerp(t, + (profile.gradientStart >> 8) & 0xFF, + (profile.gradientEnd >> 8) & 0xFF); + + int b = (int) MathHelper.lerp(t, + profile.gradientStart & 0xFF, + profile.gradientEnd & 0xFF); int color = (r << 16) | (g << 8) | b; animated.append( Text.literal(String.valueOf(name.charAt(i))) - .styled(style -> style.withColor(color).withBold(true)) + .styled(s -> s.withColor(color).withBold(true)) ); } return animated; } + + /* ---------------- FETCH MOJANG CAPES ---------------- */ + + public static void fetchMojangCapes(UUID uuid) { + try { + String accessToken = getAccessTokenFromLaunchArgs(); + if (accessToken == null) return; + + URL url = new URL("https://api.minecraftservices.com/minecraft/profile"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestProperty("Authorization", "Bearer " + accessToken); + con.setRequestMethod("GET"); + + InputStream in = con.getInputStream(); + String json = new String(in.readAllBytes()); + in.close(); + + System.out.println("[ClientCosmetics] Mojang capes JSON: " + json); + + JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); + JsonArray capes = obj.getAsJsonArray("capes"); + if (capes == null) return; + + List list = new ArrayList<>(); + for (JsonElement e : capes) { + JsonObject c = e.getAsJsonObject(); + MojangCape cape = new MojangCape(); + cape.id = c.get("id").getAsString(); + cape.name = c.has("alias") ? c.get("alias").getAsString() : cape.id; + cape.url = c.get("url").getAsString(); + list.add(cape); + } + + MOJANG_CAPES.put(uuid, list); + + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private static String getAccessTokenFromLaunchArgs() { + for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) { + if (arg.startsWith("--accessToken")) { + String[] split = arg.split("=", 2); + if (split.length == 2) return split[1]; + else continue; + } + } + return null; + } } \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/TGcapeMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/TGcapeMixin.java index 170bc99..0cf7db9 100644 --- a/src/client/java/dev/tggamesyt/szar/client/mixin/TGcapeMixin.java +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/TGcapeMixin.java @@ -13,11 +13,15 @@ public abstract class TGcapeMixin { @Inject(method = "getCapeTexture", at = @At("HEAD"), cancellable = true) private void injectCapeTexture(CallbackInfoReturnable cir) { - AbstractClientPlayerEntity self = (AbstractClientPlayerEntity)(Object)this; + AbstractClientPlayerEntity player = (AbstractClientPlayerEntity)(Object) this; - var profile = ClientCosmetics.getProfile(self.getUuid()); + ClientCosmetics.CosmeticProfile profile = + ClientCosmetics.get(player.getUuid()); + + // Only override if we actually have a custom cape if (profile != null && profile.capeTexture != null) { cir.setReturnValue(profile.capeTexture); } + // Otherwise vanilla continues → Mojang cape works normally } } \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java b/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java index 6388cc0..90e2bb6 100644 --- a/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java +++ b/src/main/java/dev/tggamesyt/szar/ServerCosmetics.java @@ -1,4 +1,205 @@ package dev.tggamesyt.szar; +import com.google.gson.*; +import com.mojang.brigadier.arguments.StringArgumentType; +import dev.tggamesyt.szar.client.ClientCosmetics.NameType; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.network.PacketByteBuf; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; + +import java.io.InputStream; +import java.net.URL; +import java.util.*; + public class ServerCosmetics { -} + + public static final Identifier SYNC_PACKET = + new Identifier(Szar.MOD_ID, "cosmetic_sync"); + + private static final String CAPES_URL = + "https://raw.githubusercontent.com/tggamesyt/szar/main/capes.json"; + + private static final String USERS_URL = + "https://raw.githubusercontent.com/tggamesyt/szar/main/usercosmetics.json"; + + public static final Map CAPES = new HashMap<>(); + public static final Map USERS = new HashMap<>(); + public static final Map> PLAYER_MOJANG_CAPES = new HashMap<>(); + + public static class MojangCape { + public String id; + public String name; + public String url; + } + + public static class UserCosmetics { + public NameType nameType; + public Integer staticColor; + public Integer gradientStart; + public Integer gradientEnd; + public List ownedCapes = new ArrayList<>(); + public String selectedCape; + } + + /* ---------------- INITIALIZE ---------------- */ + + public static void init() { + loadJson(); + registerCommand(); + } + + /* ---------------- LOAD JSON ---------------- */ + + private static void loadJson() { + try { + Gson gson = new Gson(); + + // CAPES + String capesRaw = readUrl(CAPES_URL); + JsonObject capesJson = gson.fromJson(capesRaw, JsonObject.class); + for (JsonElement e : capesJson.getAsJsonArray("capes")) { + JsonObject obj = e.getAsJsonObject(); + CAPES.put( + obj.get("id").getAsString(), + obj.get("texture").getAsString() + ); + } + + // USERS + String usersRaw = readUrl(USERS_URL); + JsonObject usersJson = gson.fromJson(usersRaw, JsonObject.class); + for (JsonElement e : usersJson.getAsJsonArray("users")) { + JsonObject obj = e.getAsJsonObject(); + + UUID uuid = UUID.fromString(obj.get("uuid").getAsString()); + UserCosmetics user = new UserCosmetics(); + + user.nameType = NameType.valueOf(obj.get("nameType").getAsString()); + + if (obj.has("staticColor")) + user.staticColor = parseHex(obj.get("staticColor").getAsString()); + + if (obj.has("gradientStart")) { + user.gradientStart = parseHex(obj.get("gradientStart").getAsString()); + user.gradientEnd = parseHex(obj.get("gradientEnd").getAsString()); + } + + for (JsonElement cape : obj.getAsJsonArray("capes")) + user.ownedCapes.add(cape.getAsString()); + + USERS.put(uuid, user); + } + + Szar.LOGGER.info("Loaded server capes & user cosmetics from GitHub"); + + } catch (Exception e) { + Szar.LOGGER.error("Failed loading cosmetics", e); + } + } + + private static String readUrl(String url) throws Exception { + try (InputStream in = new URL(url).openStream()) { + return new String(in.readAllBytes()); + } + } + + private static int parseHex(String hex) { + return Integer.parseInt(hex.replace("#", ""), 16); + } + + /* ---------------- COMMAND ---------------- */ + + private static void registerCommand() { + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> + dispatcher.register(CommandManager.literal("cape") + .then(CommandManager.argument("id", StringArgumentType.word()) + .suggests((ctx, builder) -> { + + ServerPlayerEntity player = ctx.getSource().getPlayer(); + + // Custom capes + for (String id : CAPES.keySet()) + builder.suggest(id); + + // Mojang capes + List mojang = PLAYER_MOJANG_CAPES.get(player.getUuid()); + if (mojang != null) { + for (MojangCape c : mojang) builder.suggest(c.name); + } + + builder.suggest("none"); + return builder.buildFuture(); + }) + .executes(ctx -> { + + ServerPlayerEntity player = ctx.getSource().getPlayer(); + String id = StringArgumentType.getString(ctx, "id"); + + UserCosmetics user = USERS.get(player.getUuid()); + if (user == null) return 0; + + // Deselect + if (id.equalsIgnoreCase("none")) { + user.selectedCape = null; + sync(player, user); + player.sendMessage(Text.literal("Unequipped cape."), false); + return 1; + } + + // Mojang cape selection (only from fetched list) + List mojang = PLAYER_MOJANG_CAPES.get(player.getUuid()); + if (mojang != null) { + for (MojangCape c : mojang) { + if (c.name.equalsIgnoreCase(id)) { + user.selectedCape = null; // vanilla cape + sync(player, user); + player.sendMessage(Text.literal("Equipped Mojang cape: " + c.name), false); + return 1; + } + } + } + + // Custom + if (!user.ownedCapes.contains(id)) { + player.sendMessage(Text.literal("You don't own this cape."), false); + return 0; + } + + user.selectedCape = id; + sync(player, user); + player.sendMessage(Text.literal("Equipped cape: " + id), false); + return 1; + }) + ) + ) + ); + } + + /* ---------------- SYNC ---------------- */ + + public static void sync(ServerPlayerEntity player, UserCosmetics user) { + PacketByteBuf buf = PacketByteBufs.create(); + + buf.writeEnumConstant(user.nameType); + buf.writeBoolean(user.staticColor != null); + if (user.staticColor != null) buf.writeInt(user.staticColor); + + buf.writeBoolean(user.gradientStart != null); + if (user.gradientStart != null) { + buf.writeInt(user.gradientStart); + buf.writeInt(user.gradientEnd); + } + + String textureUrl = user.selectedCape != null + ? CAPES.get(user.selectedCape) + : ""; + + buf.writeString(textureUrl == null ? "" : textureUrl); + ServerPlayNetworking.send(player, SYNC_PACKET, buf); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/Szar.java b/src/main/java/dev/tggamesyt/szar/Szar.java index 970ff89..70ca0ab 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -73,6 +73,7 @@ import net.minecraft.world.gen.structure.StructureType; import net.minecraft.world.poi.PointOfInterestType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.jmx.Server; import java.util.HashMap; import java.util.List; @@ -285,6 +286,7 @@ public class Szar implements ModInitializer { ); @Override public void onInitialize() { + ServerCosmetics.init(); ServerLifecycleEvents.SERVER_STARTED.register(server -> { SERVER = server; });