1
0
mirror of https://github.com/Suiranoil/SkinRestorer.git synced 2026-01-16 04:42:12 +00:00

use custom type to store skins

This commit is contained in:
2024-07-01 15:13:20 +03:00
parent a50c6e431c
commit a4b4297af2
9 changed files with 160 additions and 63 deletions

View File

@@ -1,11 +1,12 @@
package net.lionarius.skinrestorer;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import net.lionarius.skinrestorer.config.Config;
import net.lionarius.skinrestorer.skin.SkinIO;
import net.lionarius.skinrestorer.skin.SkinStorage;
import net.lionarius.skinrestorer.skin.SkinValue;
import net.lionarius.skinrestorer.skin.provider.SkinProvider;
import net.lionarius.skinrestorer.skin.provider.SkinProviderContext;
import net.lionarius.skinrestorer.skin.provider.SkinProviderRegistry;
import net.lionarius.skinrestorer.util.FileUtils;
import net.lionarius.skinrestorer.util.PlayerUtils;
@@ -23,7 +24,6 @@ import java.util.HashSet;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public final class SkinRestorer {
public static final String MOD_ID = "skinrestorer";
@@ -81,31 +81,48 @@ public final class SkinRestorer {
return String.format("/assets/%s/%s", SkinRestorer.MOD_ID, name);
}
public static Collection<ServerPlayer> applySkin(MinecraftServer server, Iterable<GameProfile> targets, SkinValue value) {
var acceptedPlayers = new HashSet<ServerPlayer>();
for (var profile : targets) {
SkinRestorer.getSkinStorage().setSkin(profile.getId(), value);
if (PlayerUtils.areSkinPropertiesEquals(value.value(), PlayerUtils.getPlayerSkin(profile)))
continue;
PlayerUtils.applyRestoredSkin(profile, value.value());
var player = server.getPlayerList().getPlayer(profile.getId());
if (player == null)
continue;
PlayerUtils.refreshPlayer(player);
acceptedPlayers.add(player);
}
return acceptedPlayers;
}
public static CompletableFuture<Result<Collection<ServerPlayer>, String>> setSkinAsync(
MinecraftServer server,
Collection<GameProfile> targets,
Supplier<Result<Optional<Property>, Exception>> skinSupplier
SkinProviderContext context
) {
return CompletableFuture.supplyAsync(skinSupplier)
.thenApplyAsync(skinResult -> {
if (skinResult.isError()) {
return CompletableFuture.supplyAsync(
() -> SkinRestorer.getProvider(context.name()).map(provider -> provider.getSkin(context.argument(), context.variant()))
)
.thenApplyAsync(result -> {
if (result.isEmpty())
throw new IllegalArgumentException("provider " + context.argument() + " is not registered");
var skinResult = result.get();
if (skinResult.isError())
return Result.<Collection<ServerPlayer>, String>error(skinResult.getErrorValue().getMessage());
}
var skin = skinResult.getSuccessValue().orElse(null);
HashSet<ServerPlayer> acceptedPlayers = new HashSet<>();
var skinValue = SkinValue.fromProviderContextWithValue(context, skinResult.getSuccessValue().orElse(null));
var acceptedPlayers = SkinRestorer.applySkin(server, targets, skinValue);
for (GameProfile profile : targets) {
SkinRestorer.getSkinStorage().setSkin(profile.getId(), skin);
ServerPlayer player = server.getPlayerList().getPlayer(profile.getId());
if (player == null || PlayerUtils.areSkinPropertiesEquals(skin, PlayerUtils.getPlayerSkin(player)))
continue;
PlayerUtils.applyRestoredSkin(player, skin);
PlayerUtils.refreshPlayer(player);
acceptedPlayers.add(player);
}
return Result.<Collection<ServerPlayer>, String>success(acceptedPlayers);
}, server)
.orTimeout(10, TimeUnit.SECONDS)

View File

@@ -1,7 +1,6 @@
package net.lionarius.skinrestorer.command;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
@@ -11,15 +10,14 @@ import com.mojang.brigadier.context.CommandContext;
import net.lionarius.skinrestorer.SkinRestorer;
import net.lionarius.skinrestorer.skin.SkinVariant;
import net.lionarius.skinrestorer.skin.provider.SkinProvider;
import net.lionarius.skinrestorer.skin.provider.SkinProviderContext;
import net.lionarius.skinrestorer.util.PlayerUtils;
import net.lionarius.skinrestorer.util.Result;
import net.lionarius.skinrestorer.util.Translation;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.GameProfileArgument;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -33,7 +31,7 @@ public final class SkinCommand {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> base =
literal("skin")
.then(buildAction("clear", SkinProvider.EMPTY::getSkin));
.then(buildAction("clear", () -> new SkinProviderContext("empty", null, null)));
LiteralArgumentBuilder<CommandSourceStack> set = literal("set");
@@ -55,15 +53,20 @@ public final class SkinCommand {
literal(variant.toString())
.then(buildArgument(
argument(provider.getArgumentName(), StringArgumentType.string()),
context -> provider.getSkin(StringArgumentType.getString(context, provider.getArgumentName()), variant)
context -> {
var argument = StringArgumentType.getString(context, provider.getArgumentName());
return new SkinProviderContext(name, argument, variant);
}
))
);
}
} else {
action.then(
buildArgument(
argument(provider.getArgumentName(), StringArgumentType.string()),
context -> provider.getSkin(StringArgumentType.getString(context, provider.getArgumentName()), SkinVariant.CLASSIC)
argument(provider.getArgumentName(), StringArgumentType.string()), context -> {
var argument = StringArgumentType.getString(context, provider.getArgumentName());
return new SkinProviderContext(name, argument, null);
}
)
);
}
@@ -73,45 +76,45 @@ public final class SkinCommand {
private static ArgumentBuilder<CommandSourceStack, LiteralArgumentBuilder<CommandSourceStack>> buildAction(
String name,
Supplier<Result<Optional<Property>, Exception>> supplier
Supplier<SkinProviderContext> supplier
) {
return buildArgument(literal(name), context -> supplier.get());
}
private static <T extends ArgumentBuilder<CommandSourceStack, T>> ArgumentBuilder<CommandSourceStack, T> buildArgument(
ArgumentBuilder<CommandSourceStack, T> argument,
Function<CommandContext<CommandSourceStack>, Result<Optional<Property>, Exception>> provider
Function<CommandContext<CommandSourceStack>, SkinProviderContext> provider
) {
return argument
.executes(context -> skinAction(
context.getSource(),
() -> provider.apply(context)
provider.apply(context)
))
.then(makeTargetsArgument(provider));
}
private static RequiredArgumentBuilder<CommandSourceStack, GameProfileArgument.Result> makeTargetsArgument(
Function<CommandContext<CommandSourceStack>, Result<Optional<Property>, Exception>> provider
Function<CommandContext<CommandSourceStack>, SkinProviderContext> provider
) {
return argument("targets", GameProfileArgument.gameProfile())
.requires(source -> source.hasPermission(2))
.executes(context -> skinAction(
provider.apply(context),
context.getSource(),
GameProfileArgument.getGameProfiles(context, "targets"),
true,
() -> provider.apply(context)
true
));
}
private static int skinAction(
SkinProviderContext context,
CommandSourceStack src,
Collection<GameProfile> targets,
boolean setByOperator,
Supplier<Result<Optional<Property>, Exception>> skinSupplier
boolean setByOperator
) {
src.sendSystemMessage(Translation.translatableWithFallback(Translation.COMMAND_SKIN_LOADING_KEY));
SkinRestorer.setSkinAsync(src.getServer(), targets, skinSupplier).thenAccept(result -> {
SkinRestorer.setSkinAsync(src.getServer(), targets, context).thenAccept(result -> {
if (result.isError()) {
src.sendFailure(Translation.translatableWithFallback(
Translation.COMMAND_SKIN_FAILED_KEY,
@@ -146,12 +149,14 @@ public final class SkinCommand {
return targets.size();
}
private static int skinAction(CommandSourceStack
src, Supplier<Result<Optional<Property>, Exception>> skinSupplier) {
private static int skinAction(
CommandSourceStack src,
SkinProviderContext context
) {
if (src.getPlayer() == null)
return 0;
return skinAction(src, Collections.singleton(src.getPlayer().getGameProfile()), false, skinSupplier);
return skinAction(context, src, Collections.singleton(src.getPlayer().getGameProfile()), false);
}

View File

@@ -1,7 +1,6 @@
package net.lionarius.skinrestorer.mixin;
import net.lionarius.skinrestorer.SkinRestorer;
import net.lionarius.skinrestorer.util.Result;
import net.minecraft.network.Connection;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
@@ -40,6 +39,6 @@ public abstract class PlayerListMixin {
@Inject(method = "placeNewPlayer", at = @At("HEAD"))
private void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie, CallbackInfo ci) {
SkinRestorer.setSkinAsync(server, Collections.singleton(player.getGameProfile()), () -> Result.ofNullable(SkinRestorer.getSkinStorage().getSkin(player.getUUID())));
SkinRestorer.applySkin(server, Collections.singleton(player.getGameProfile()), SkinRestorer.getSkinStorage().getSkin(player.getUUID()));
}
}

View File

@@ -2,7 +2,7 @@ package net.lionarius.skinrestorer.mixin;
import com.mojang.authlib.GameProfile;
import net.lionarius.skinrestorer.SkinRestorer;
import net.lionarius.skinrestorer.skin.SkinVariant;
import net.lionarius.skinrestorer.skin.SkinValue;
import net.lionarius.skinrestorer.util.Result;
import net.minecraft.server.network.ServerLoginPacketListenerImpl;
import org.jetbrains.annotations.Nullable;
@@ -35,11 +35,15 @@ public abstract class ServerLoginPacketListenerImplMixin {
if (SkinRestorer.getConfig().fetchSkinOnFirstJoin() && !SkinRestorer.getSkinStorage().hasSavedSkin(authenticatedProfile.getId())) { // when player joins for the first time fetch Mojang skin by his username
var result = SkinRestorer.getProvider("mojang").map(
provider -> provider.getSkin(authenticatedProfile.getName(), SkinVariant.CLASSIC)
provider -> provider.getSkin(authenticatedProfile.getName(), null)
).orElse(Result.ofNullable(null));
if (!result.isError())
SkinRestorer.getSkinStorage().setSkin(authenticatedProfile.getId(), result.getSuccessValue().orElse(null));
if (!result.isError()) {
var value = new SkinValue("mojang", authenticatedProfile.getName(), null, result.getSuccessValue().orElse(null));
SkinRestorer.getSkinStorage().setSkin(authenticatedProfile.getId(), value);
} else {
SkinRestorer.LOGGER.error("failed to fetch skin on first join", result.getErrorValue());
}
}
SkinRestorer.getSkinStorage().getSkin(authenticatedProfile.getId());

View File

@@ -4,7 +4,9 @@ import com.mojang.authlib.properties.Property;
import net.lionarius.skinrestorer.util.FileUtils;
import net.lionarius.skinrestorer.util.JsonUtils;
import java.io.File;
import java.nio.file.Path;
import java.util.Objects;
import java.util.UUID;
public class SkinIO {
@@ -21,11 +23,55 @@ public class SkinIO {
return savePath.resolve(uuid + FILE_EXTENSION).toFile().exists();
}
public Property loadSkin(UUID uuid) {
return JsonUtils.fromJson(FileUtils.readFile(savePath.resolve(uuid + FILE_EXTENSION).toFile()), Property.class);
public SkinValue loadSkin(UUID uuid) {
try {
var value = SkinIO.loadSkin(savePath.resolve(uuid + FILE_EXTENSION).toFile());
Objects.requireNonNull(value.provider());
return value;
} catch (Exception e) {
return SkinValue.EMPTY;
}
}
public void saveSkin(UUID uuid, Property skin) {
private static SkinValue loadSkin(File file) {
var json = FileUtils.readFile(file);
try {
return JsonUtils.fromJson(json, SkinValue.class);
} catch (Exception e) {
var property = JsonUtils.fromJson(json, Property.class);
return SkinIO.convertFromOldFormat(property);
}
}
public void saveSkin(UUID uuid, SkinValue skin) {
FileUtils.writeFile(savePath.toFile(), uuid + FILE_EXTENSION, JsonUtils.toJson(skin));
}
private static SkinValue convertFromOldFormat(Property property) {
try {
var propertyJson = Objects.requireNonNull(JsonUtils.skinPropertyToJson(property));
var textures = propertyJson.getAsJsonObject("textures");
var capeTexture = textures.getAsJsonObject("CAPE");
if (capeTexture != null) {
var profileName = propertyJson.get("profileName").getAsString();
return new SkinValue("mojang", profileName, null, property);
}
var skinTexture = textures.getAsJsonObject("SKIN");
var url = skinTexture.get("url").getAsString();
var variant = SkinVariant.CLASSIC;
var metadata = skinTexture.getAsJsonObject("metadata");
if (metadata != null) {
var model = metadata.get("model");
if (model != null && "slim".equals(model.getAsString()))
variant = SkinVariant.SLIM;
}
return new SkinValue("web", url, variant, property);
} catch (Exception e) {
return SkinValue.EMPTY;
}
}
}

View File

@@ -1,15 +1,12 @@
package net.lionarius.skinrestorer.skin;
import com.mojang.authlib.properties.Property;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class SkinStorage {
private final Map<UUID, Optional<Property>> skinMap = new ConcurrentHashMap<>();
private final Map<UUID, SkinValue> skinMap = new ConcurrentHashMap<>();
private final SkinIO skinIO;
public SkinStorage(SkinIO skinIO) {
@@ -20,21 +17,25 @@ public class SkinStorage {
return this.skinIO.skinExists(uuid);
}
public Property getSkin(UUID uuid) {
public SkinValue getSkin(UUID uuid) {
if (!skinMap.containsKey(uuid)) {
Property skin = skinIO.loadSkin(uuid);
var skin = skinIO.loadSkin(uuid);
setSkin(uuid, skin);
}
return skinMap.get(uuid).orElse(null);
return skinMap.get(uuid);
}
public void removeSkin(UUID uuid) {
if (skinMap.containsKey(uuid))
skinIO.saveSkin(uuid, skinMap.get(uuid).orElse(null));
var skin = skinMap.remove(uuid);
if (skin != null)
skinIO.saveSkin(uuid, skin);
}
public void setSkin(UUID uuid, Property skin) {
skinMap.put(uuid, Optional.ofNullable(skin));
public void setSkin(UUID uuid, SkinValue skin) {
if (skin == null)
skin = SkinValue.EMPTY;
skinMap.put(uuid, skin);
}
}

View File

@@ -0,0 +1,20 @@
package net.lionarius.skinrestorer.skin;
import com.mojang.authlib.properties.Property;
import net.lionarius.skinrestorer.skin.provider.SkinProviderContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record SkinValue(@NotNull String provider, @Nullable String argument, @Nullable SkinVariant variant,
@Nullable Property value) {
public static final SkinValue EMPTY = new SkinValue("empty", null, null, null);
public static SkinValue fromProviderContextWithValue(SkinProviderContext context, Property value) {
return new SkinValue(context.name(), context.argument(), context.variant(), value);
}
public SkinProviderContext toProviderContext() {
return new SkinProviderContext(this.provider, this.argument, this.variant);
}
}

View File

@@ -0,0 +1,6 @@
package net.lionarius.skinrestorer.skin.provider;
import net.lionarius.skinrestorer.skin.SkinVariant;
public record SkinProviderContext(String name, String argument, SkinVariant variant) {
}

View File

@@ -84,12 +84,11 @@ public final class PlayerUtils {
}
}
public static Property getPlayerSkin(ServerPlayer player) {
return player.getGameProfile().getProperties().get(TEXTURES_KEY).stream().findFirst().orElse(null);
public static Property getPlayerSkin(GameProfile profile) {
return profile.getProperties().get(TEXTURES_KEY).stream().findFirst().orElse(null);
}
public static void applyRestoredSkin(ServerPlayer player, Property skin) {
GameProfile profile = player.getGameProfile();
public static void applyRestoredSkin(GameProfile profile, Property skin) {
profile.getProperties().removeAll(TEXTURES_KEY);
if (skin != null)