diff --git a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/SkinCMD.java b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/SkinCMD.java index 6b0e2298..e4cf5aaf 100644 --- a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/SkinCMD.java +++ b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/SkinCMD.java @@ -20,15 +20,29 @@ import org.incendo.cloud.context.CommandInput; import org.jetbrains.annotations.NotNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import de.oliver.fancynpcs.skins.SkinManagerImpl; +import de.oliver.fancynpcs.skins.mojang.MojangAPI; +import de.oliver.fancynpcs.skins.mineskin.RatelimitException; + import java.io.File; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Base64; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; public enum SkinCMD { INSTANCE; // SINGLETON private final Translator translator = FancyNpcs.getInstance().getTranslator(); + private final HttpClient httpClient = HttpClient.newHttpClient(); /* PARSERS AND SUGGESTIONS */ @@ -38,7 +52,8 @@ public void onSkin( final @NotNull CommandSender sender, final @NotNull Npc npc, final @NotNull @Argument(suggestions = "SkinCMD/skin") String skin, - final @Flag("slim") boolean slim + final @Flag("slim") boolean slim, + final @Flag("static") boolean isStatic ) { if (npc.getData().getType() != EntityType.PLAYER) { translator.translate("command_unsupported_npc_type").send(sender); @@ -69,27 +84,16 @@ public void onSkin( translator.translate("command_npc_modification_cancelled").send(sender); } } else try { - SkinData.SkinVariant variant = slim ? SkinData.SkinVariant.SLIM : SkinData.SkinVariant.AUTO; - SkinData skinData = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skin, variant); - skinData.setIdentifier(skin); + if (isStatic && SkinUtils.isUsername(skin)) { + downloadStaticSkin(skin, sender, npc, slim); + return; + } - if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, false, sender).callEvent() && new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, skinData, sender).callEvent()) { - translator.translate("npc_skin_set") - .replace("npc", npc.getData().getName()) - .replace("name", skinData.getIdentifier()) - .send(sender); - if (!skinData.hasTexture()) { - translator.translate("npc_skin_set_later").replace("npc", npc.getData().getName()).send(sender); - } - npc.getData().setMirrorSkin(false); - npc.getData().setSkinData(skinData); - npc.removeForAll(); - npc.create(); - npc.spawnForAll(); + final SkinData.SkinVariant variant = slim ? SkinData.SkinVariant.SLIM : SkinData.SkinVariant.AUTO; + final SkinData skinData = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skin, variant); + skinData.setIdentifier(skin); - } else { - translator.translate("command_npc_modification_cancelled").send(sender); - } + applySkin(npc, skinData, sender); } catch (final SkinLoadException e) { switch (e.getReason()) { case INVALID_URL -> translator.translate("npc_skin_failure_invalid_url").replace("npc", npc.getData().getName()).send(sender); @@ -102,6 +106,107 @@ public void onSkin( /* UTILITY METHODS */ + private void downloadStaticSkin(final String username, final CommandSender sender, final Npc npc, final boolean slim) { + final SkinData.SkinVariant variant = slim ? SkinData.SkinVariant.SLIM : SkinData.SkinVariant.AUTO; + + try { + final SkinData skinData = FancyNpcs.getInstance().getSkinManagerImpl().getByUsername(username, variant); + if (skinData.hasTexture()) { + processStaticSkin(skinData.getTextureValue(), username, sender, npc, slim); + return; + } + } catch (final SkinLoadException e) { + translator.translate("npc_skin_failure_invalid_username").replace("npc", npc.getData().getName()).send(sender); + return; + } + + CompletableFuture.supplyAsync(() -> { + try { + final UUID uuid = FancyNpcs.getInstance().getSkinManagerImpl().getUuidCache().getUUID(username); + if (uuid == null) { + return null; + } + return new MojangAPI(SkinManagerImpl.EXECUTOR).fetchSkin(uuid.toString(), variant); + } catch (final RatelimitException e) { + return null; + } + }, SkinManagerImpl.EXECUTOR).thenAccept(fetchedSkinData -> { + FancyNpcs.getInstance().getScheduler().runTask(null, () -> { + if (fetchedSkinData == null || !fetchedSkinData.hasTexture()) { + translator.translate("npc_skin_failure_invalid_username").replace("npc", npc.getData().getName()).send(sender); + return; + } + processStaticSkin(fetchedSkinData.getTextureValue(), username, sender, npc, slim); + }); + }); + } + + private void processStaticSkin(final String textureValue, final String username, final CommandSender sender, final Npc npc, final boolean slim) { + try { + final String decodedJson = new String(Base64.getDecoder().decode(textureValue)); + final JsonObject jsonObject = JsonParser.parseString(decodedJson).getAsJsonObject(); + final String skinUrl = jsonObject.getAsJsonObject("textures").getAsJsonObject("SKIN").get("url").getAsString(); + + final String fileName = username + ".png"; + final File skinFile = new File(new File(FancyNpcs.getInstance().getDataFolder(), "skins"), fileName); + skinFile.getParentFile().mkdirs(); + + httpClient.sendAsync( + HttpRequest.newBuilder().uri(URI.create(skinUrl)).GET().build(), + HttpResponse.BodyHandlers.ofFile(skinFile.toPath()) + ).thenAccept(response -> { + if (response.statusCode() >= 200 && response.statusCode() < 300) { + FancyNpcs.getInstance().getScheduler().runTask(null, () -> { + try { + final SkinData.SkinVariant variant = slim ? SkinData.SkinVariant.SLIM : SkinData.SkinVariant.AUTO; + final SkinData skinData = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(fileName, variant); + skinData.setIdentifier(fileName); + + if (!skinData.hasTexture()) { + translator.translate("npc_skin_set_later").replace("npc", npc.getData().getName()).send(sender); + } + applySkin(npc, skinData, sender); + } catch (final SkinLoadException e) { + translator.translate("npc_skin_failure_invalid_file").replace("npc", npc.getData().getName()).send(sender); + } + }); + } else { + FancyNpcs.getInstance().getScheduler().runTask(null, () -> { + translator.translate("npc_skin_failure_invalid_username").replace("npc", npc.getData().getName()).send(sender); + }); + } + }).exceptionally(throwable -> { + FancyNpcs.getInstance().getScheduler().runTask(null, () -> { + translator.translate("npc_skin_failure_invalid_username").replace("npc", npc.getData().getName()).send(sender); + }); + return null; + }); + } catch (final Exception e) { + FancyNpcs.getInstance().getScheduler().runTask(null, () -> { + translator.translate("npc_skin_failure_invalid_username").replace("npc", npc.getData().getName()).send(sender); + }); + } + } + + private void applySkin(final Npc npc, final SkinData skinData, final CommandSender sender) { + if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, false, sender).callEvent() && new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, skinData, sender).callEvent()) { + translator.translate("npc_skin_set") + .replace("npc", npc.getData().getName()) + .replace("name", skinData.getIdentifier()) + .send(sender); + if (!skinData.hasTexture() && !SkinUtils.isFile(skinData.getIdentifier())) { + translator.translate("npc_skin_set_later").replace("npc", npc.getData().getName()).send(sender); + } + npc.getData().setMirrorSkin(false); + npc.getData().setSkinData(skinData); + npc.removeForAll(); + npc.create(); + npc.spawnForAll(); + } else { + translator.translate("command_npc_modification_cancelled").send(sender); + } + } + @Suggestions("SkinCMD/skin") public List suggestSkin(final CommandContext context, final CommandInput input) { return new ArrayList<>() {{