costmetics update
This commit is contained in:
@@ -1,16 +1,21 @@
|
|||||||
package dev.tggamesyt.szar.client;
|
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.MutableText;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.Formatting;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.Util;
|
import net.minecraft.util.Util;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.io.InputStream;
|
||||||
import java.util.Map;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.util.UUID;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class ClientCosmetics {
|
public class ClientCosmetics {
|
||||||
|
|
||||||
@@ -20,62 +25,41 @@ public class ClientCosmetics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class CosmeticProfile {
|
public static class CosmeticProfile {
|
||||||
public final NameType nameType;
|
public NameType nameType;
|
||||||
public final Formatting staticColor; // for STATIC names
|
public Integer staticColor;
|
||||||
public final Identifier capeTexture; // optional cape
|
public Integer gradientStart;
|
||||||
public final int gradientStart; // RGB int, for GRADIENT
|
public Integer gradientEnd;
|
||||||
public final int gradientEnd; // RGB int, for GRADIENT
|
public Identifier capeTexture;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registry
|
|
||||||
private static final Map<UUID, CosmeticProfile> PROFILES = new HashMap<>();
|
private static final Map<UUID, CosmeticProfile> PROFILES = new HashMap<>();
|
||||||
|
|
||||||
static {
|
// Player UUID -> Mojang cape list
|
||||||
// ===== TGdoesCode ===== animated gradient
|
public static final Map<UUID, List<MojangCape>> MOJANG_CAPES = new HashMap<>();
|
||||||
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== Berci08ur_mom =====
|
public static class MojangCape {
|
||||||
PROFILES.put(
|
public String id;
|
||||||
UUID.fromString("dda61748-15a4-45ff-9eea-29efc99c1711"),
|
public String name;
|
||||||
new CosmeticProfile(
|
public String url;
|
||||||
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 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);
|
return PROFILES.get(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,33 +67,89 @@ public class ClientCosmetics {
|
|||||||
CosmeticProfile profile = PROFILES.get(uuid);
|
CosmeticProfile profile = PROFILES.get(uuid);
|
||||||
if (profile == null) return null;
|
if (profile == null) return null;
|
||||||
|
|
||||||
if (profile.nameType == NameType.STATIC) {
|
if (profile.nameType == NameType.STATIC && profile.staticColor != null) {
|
||||||
return Text.literal(name).formatted(profile.staticColor, Formatting.BOLD);
|
return Text.literal(name)
|
||||||
|
.styled(s -> s.withColor(profile.staticColor).withBold(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// GRADIENT animation
|
|
||||||
long time = Util.getMeasuringTimeMs();
|
long time = Util.getMeasuringTimeMs();
|
||||||
MutableText animated = Text.empty();
|
MutableText animated = Text.empty();
|
||||||
|
|
||||||
for (int i = 0; i < name.length(); i++) {
|
for (int i = 0; i < name.length(); i++) {
|
||||||
// Animate wave
|
float progress = (time * 0.008f) + (i * 0.5f);
|
||||||
float progress = (time * 0.08f) + (i * 1.2f);
|
|
||||||
float wave = (float) Math.sin(progress);
|
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,
|
||||||
int r = (int) MathHelper.lerp(t, (profile.gradientStart >> 16) & 0xFF, (profile.gradientEnd >> 16) & 0xFF);
|
(profile.gradientStart >> 16) & 0xFF,
|
||||||
int g = (int) MathHelper.lerp(t, (profile.gradientStart >> 8) & 0xFF, (profile.gradientEnd >> 8) & 0xFF);
|
(profile.gradientEnd >> 16) & 0xFF);
|
||||||
int b = (int) MathHelper.lerp(t, profile.gradientStart & 0xFF, profile.gradientEnd & 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;
|
int color = (r << 16) | (g << 8) | b;
|
||||||
|
|
||||||
animated.append(
|
animated.append(
|
||||||
Text.literal(String.valueOf(name.charAt(i)))
|
Text.literal(String.valueOf(name.charAt(i)))
|
||||||
.styled(style -> style.withColor(color).withBold(true))
|
.styled(s -> s.withColor(color).withBold(true))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return animated;
|
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<MojangCape> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,11 +13,15 @@ public abstract class TGcapeMixin {
|
|||||||
|
|
||||||
@Inject(method = "getCapeTexture", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "getCapeTexture", at = @At("HEAD"), cancellable = true)
|
||||||
private void injectCapeTexture(CallbackInfoReturnable<Identifier> cir) {
|
private void injectCapeTexture(CallbackInfoReturnable<Identifier> 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) {
|
if (profile != null && profile.capeTexture != null) {
|
||||||
cir.setReturnValue(profile.capeTexture);
|
cir.setReturnValue(profile.capeTexture);
|
||||||
}
|
}
|
||||||
|
// Otherwise vanilla continues → Mojang cape works normally
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,205 @@
|
|||||||
package dev.tggamesyt.szar;
|
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 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<String, String> CAPES = new HashMap<>();
|
||||||
|
public static final Map<UUID, UserCosmetics> USERS = new HashMap<>();
|
||||||
|
public static final Map<UUID, List<MojangCape>> 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<String> 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<MojangCape> 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<MojangCape> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,7 @@ import net.minecraft.world.gen.structure.StructureType;
|
|||||||
import net.minecraft.world.poi.PointOfInterestType;
|
import net.minecraft.world.poi.PointOfInterestType;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.core.jmx.Server;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -285,6 +286,7 @@ public class Szar implements ModInitializer {
|
|||||||
);
|
);
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
|
ServerCosmetics.init();
|
||||||
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||||
SERVER = server;
|
SERVER = server;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user