From 0e57e62f5a1041b9e8a7b625487ec79eebb805e4 Mon Sep 17 00:00:00 2001 From: Sollace Date: Wed, 31 Jul 2024 14:38:20 +0200 Subject: [PATCH] Implement server-side functionality for getting pony data from a texture --- .../minelittlepony/api/pony/PonyManager.java | 4 +- .../minelittlepony/api/pony/SkinsProxy.java | 25 +++++- .../minelittlepony/api/pony/meta/Mats.java | 40 +++++++++ .../server/MineLittlePonyServer.java | 18 +++- .../server/ServerPonyManager.java | 83 +++++++++++++++++++ 5 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/minelittlepony/api/pony/meta/Mats.java create mode 100644 src/main/java/com/minelittlepony/server/ServerPonyManager.java diff --git a/src/main/java/com/minelittlepony/api/pony/PonyManager.java b/src/main/java/com/minelittlepony/api/pony/PonyManager.java index 32e577ea..8e8868ff 100644 --- a/src/main/java/com/minelittlepony/api/pony/PonyManager.java +++ b/src/main/java/com/minelittlepony/api/pony/PonyManager.java @@ -7,6 +7,8 @@ import net.minecraft.util.Identifier; import org.jetbrains.annotations.Nullable; +import com.minelittlepony.server.ServerPonyManager; + import java.util.Optional; import java.util.UUID; @@ -71,6 +73,6 @@ public interface PonyManager { interface ForcedPony {} final class Instance { - public static PonyManager instance; + public static PonyManager instance = new ServerPonyManager(); } } diff --git a/src/main/java/com/minelittlepony/api/pony/SkinsProxy.java b/src/main/java/com/minelittlepony/api/pony/SkinsProxy.java index f87a2419..d3a25f93 100644 --- a/src/main/java/com/minelittlepony/api/pony/SkinsProxy.java +++ b/src/main/java/com/minelittlepony/api/pony/SkinsProxy.java @@ -1,15 +1,19 @@ package com.minelittlepony.api.pony; -import java.util.Optional; -import java.util.Set; +import java.util.*; +import java.util.concurrent.TimeUnit; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.Identifier; import org.jetbrains.annotations.Nullable; +import com.google.common.cache.*; +import com.minelittlepony.server.MineLittlePonyServer; import com.mojang.authlib.GameProfile; +import com.mojang.authlib.minecraft.MinecraftProfileTextures; /** * Proxy handler for getting player skin data from HDSkins @@ -18,6 +22,13 @@ public class SkinsProxy { public static SkinsProxy INSTANCE = new SkinsProxy(); private static final SkinsProxy DEFAULT = INSTANCE; + private final LoadingCache profileCache = CacheBuilder.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(CacheLoader.from(profile -> { + var result = MineLittlePonyServer.getServer().getSessionService().fetchProfile(profile.getId(), false); + return result == null ? profile : result.profile(); + })); + public static SkinsProxy getInstance() { return INSTANCE; } @@ -30,6 +41,16 @@ public class SkinsProxy { @Nullable public Identifier getSkinTexture(GameProfile profile) { + MinecraftServer server = MineLittlePonyServer.getServer(); + if (server != null) { + profile = profileCache.getUnchecked(profile); + + MinecraftProfileTextures textures = server.getSessionService().getTextures(profile); + + if (textures != MinecraftProfileTextures.EMPTY) { + return Identifier.of(textures.skin().getUrl()); + } + } return null; } diff --git a/src/main/java/com/minelittlepony/api/pony/meta/Mats.java b/src/main/java/com/minelittlepony/api/pony/meta/Mats.java new file mode 100644 index 00000000..a123c9c9 --- /dev/null +++ b/src/main/java/com/minelittlepony/api/pony/meta/Mats.java @@ -0,0 +1,40 @@ +package com.minelittlepony.api.pony.meta; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.common.util.Color; +import com.mojang.authlib.minecraft.MinecraftProfileTexture; + +import javax.imageio.ImageIO; + +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.io.IOException; +import java.net.URI; +import java.net.URL; + +public interface Mats { + static TriggerPixel.Mat createMat(MinecraftProfileTexture texture) throws IOException { + return createMat(URI.create(texture.getUrl()).toURL()); + } + + static TriggerPixel.Mat createMat(URL url) throws IOException { + try { + @Nullable + BufferedImage image = ImageIO.read(url); + if (image == null) { + throw new IOException("Unable to read image from url " + url); + } + Raster raster = image.getData(); + return (x, y) -> { + if (x < 0 || y < 0 || x > raster.getWidth() || y > raster.getHeight()) { + return 0; + } + int[] color = raster.getPixel(x, y, new int[] {0, 0, 0, 0}); + return Color.argbToHex(color[3], color[0], color[1], color[2]); + }; + } catch (IllegalArgumentException e) { + throw new IOException("Could not create mat from image", e); + } + } +} diff --git a/src/main/java/com/minelittlepony/server/MineLittlePonyServer.java b/src/main/java/com/minelittlepony/server/MineLittlePonyServer.java index 420396a2..203a1bba 100644 --- a/src/main/java/com/minelittlepony/server/MineLittlePonyServer.java +++ b/src/main/java/com/minelittlepony/server/MineLittlePonyServer.java @@ -1,19 +1,35 @@ package com.minelittlepony.server; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + import com.minelittlepony.api.events.CommonChannel; +import java.lang.ref.WeakReference; + public class MineLittlePonyServer implements ModInitializer { + private static WeakReference server = new WeakReference<>(null); + public static Identifier id(String name) { return Identifier.of("minelittlepony", name); } + @Nullable + public static MinecraftServer getServer() { + return server.get(); + } + @Override public void onInitialize() { CommonChannel.bootstrap(); - } + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + MineLittlePonyServer.server = new WeakReference<>(server); + }); + } } diff --git a/src/main/java/com/minelittlepony/server/ServerPonyManager.java b/src/main/java/com/minelittlepony/server/ServerPonyManager.java new file mode 100644 index 00000000..b934cd98 --- /dev/null +++ b/src/main/java/com/minelittlepony/server/ServerPonyManager.java @@ -0,0 +1,83 @@ +package com.minelittlepony.server; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Identifier; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.cache.*; +import com.minelittlepony.api.pony.*; +import com.minelittlepony.api.pony.meta.Mats; + +import java.io.IOException; +import java.net.URI; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class ServerPonyManager implements PonyManager { + static final Pony NULL_PONY = new Pony(Identifier.ofVanilla("null"), () -> Optional.of(PonyData.NULL)); + + private final LoadingCache poniesCache = CacheBuilder.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(CacheLoader.from(resource -> { + return new Pony(resource, load(consumer -> { + CompletableFuture.runAsync(() -> { + try { + consumer.accept(new PonyData(Mats.createMat(URI.create(resource.toString()).toURL()), false)); + } catch (IOException e) { + consumer.accept(PonyData.NULL); + } + }); + })); + })); + + private static Supplier> load(Consumer> factory) { + return new Supplier>() { + Optional value = Optional.empty(); + boolean loadRequested; + @Override + public Optional get() { + synchronized (this) { + if (!loadRequested) { + loadRequested = true; + factory.accept(value -> { + this.value = Optional.ofNullable(value); + }); + } + } + return value; + } + }; + } + + @Override + public Optional getPony(LivingEntity entity) { + if (entity instanceof PlayerEntity player) { + return Optional.ofNullable(getPony(player)); + } + return Optional.empty(); + } + + @Override + public Pony getBackgroundPony(UUID uuid) { + return NULL_PONY; + } + + @Override + public Pony getPony(PlayerEntity player) { + return getPony(SkinsProxy.getInstance().getSkinTexture(player.getGameProfile()), null); + } + + @Override + public Pony getPony(@Nullable Identifier resource, @Nullable UUID uuid) { + if (resource != null && (resource.getNamespace().equals("http") || resource.getNamespace().equals("https"))) { + return poniesCache.getUnchecked(resource); + } + return NULL_PONY; + } +}