Implement server-side functionality for getting pony data from a texture

This commit is contained in:
Sollace 2024-07-31 14:38:20 +02:00
parent 95b32fca23
commit 0e57e62f5a
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
5 changed files with 166 additions and 4 deletions

View file

@ -7,6 +7,8 @@ import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.server.ServerPonyManager;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -71,6 +73,6 @@ public interface PonyManager {
interface ForcedPony {} interface ForcedPony {}
final class Instance { final class Instance {
public static PonyManager instance; public static PonyManager instance = new ServerPonyManager();
} }
} }

View file

@ -1,15 +1,19 @@
package com.minelittlepony.api.pony; package com.minelittlepony.api.pony;
import java.util.Optional; import java.util.*;
import java.util.Set; import java.util.concurrent.TimeUnit;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.google.common.cache.*;
import com.minelittlepony.server.MineLittlePonyServer;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTextures;
/** /**
* Proxy handler for getting player skin data from HDSkins * Proxy handler for getting player skin data from HDSkins
@ -18,6 +22,13 @@ public class SkinsProxy {
public static SkinsProxy INSTANCE = new SkinsProxy(); public static SkinsProxy INSTANCE = new SkinsProxy();
private static final SkinsProxy DEFAULT = INSTANCE; private static final SkinsProxy DEFAULT = INSTANCE;
private final LoadingCache<GameProfile, GameProfile> 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() { public static SkinsProxy getInstance() {
return INSTANCE; return INSTANCE;
} }
@ -30,6 +41,16 @@ public class SkinsProxy {
@Nullable @Nullable
public Identifier getSkinTexture(GameProfile profile) { 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; return null;
} }

View file

@ -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);
}
}
}

View file

@ -1,19 +1,35 @@
package com.minelittlepony.server; package com.minelittlepony.server;
import net.fabricmc.api.ModInitializer; 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 net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.api.events.CommonChannel; import com.minelittlepony.api.events.CommonChannel;
import java.lang.ref.WeakReference;
public class MineLittlePonyServer implements ModInitializer { public class MineLittlePonyServer implements ModInitializer {
private static WeakReference<MinecraftServer> server = new WeakReference<>(null);
public static Identifier id(String name) { public static Identifier id(String name) {
return Identifier.of("minelittlepony", name); return Identifier.of("minelittlepony", name);
} }
@Nullable
public static MinecraftServer getServer() {
return server.get();
}
@Override @Override
public void onInitialize() { public void onInitialize() {
CommonChannel.bootstrap(); CommonChannel.bootstrap();
}
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
MineLittlePonyServer.server = new WeakReference<>(server);
});
}
} }

View file

@ -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<Identifier, Pony> 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 <T> Supplier<Optional<T>> load(Consumer<Consumer<T>> factory) {
return new Supplier<Optional<T>>() {
Optional<T> value = Optional.empty();
boolean loadRequested;
@Override
public Optional<T> get() {
synchronized (this) {
if (!loadRequested) {
loadRequested = true;
factory.accept(value -> {
this.value = Optional.ofNullable(value);
});
}
}
return value;
}
};
}
@Override
public Optional<Pony> 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;
}
}