expose skin texture methods in api

This commit is contained in:
Pyrbu 2024-01-07 18:26:17 +01:00
parent c560fe597e
commit 2a1f44b1bb
11 changed files with 72 additions and 43 deletions

@ -0,0 +1,6 @@
package lol.pyr.znpcsplus.api.skin;
public interface Skin {
String getTexture();
String getSignature();
}

@ -1,4 +1,11 @@
package lol.pyr.znpcsplus.api.skin; package lol.pyr.znpcsplus.api.skin;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public interface SkinDescriptor { public interface SkinDescriptor {
CompletableFuture<? extends Skin> fetch(Player player);
Skin fetchInstant(Player player);
boolean supportsInstant(Player player);
} }

@ -4,7 +4,7 @@ import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait; import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait;
import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.skin.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor; import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -21,7 +21,7 @@ public class SkinTrait extends SectionCitizensTrait {
public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) { public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) {
String texture = section.getString("textureRaw"); String texture = section.getString("textureRaw");
String signature = section.getString("signature"); String signature = section.getString("signature");
if (texture != null && signature != null) npc.setProperty(registry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new Skin(texture, signature))); if (texture != null && signature != null) npc.setProperty(registry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(texture, signature)));
return npc; return npc;
} }
} }

@ -25,7 +25,7 @@ import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor; import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor; import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
@ -36,7 +36,6 @@ import lol.pyr.znpcsplus.util.LookType;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.DyeColor; import org.bukkit.DyeColor;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -130,7 +129,7 @@ public class ZNpcImporter implements DataImporter {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName())); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName()));
} }
else if (model.getSkin() != null && model.getSignature() != null) { else if (model.getSkin() != null && model.getSignature() != null) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new Skin(model.getSkin(), model.getSignature()))); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature())));
} }
Map<String, Object> toggleValues = model.getNpcToggleValues(); Map<String, Object> toggleValues = model.getNpcToggleValues();

@ -13,8 +13,8 @@ import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface BaseSkinDescriptor extends SkinDescriptor { public interface BaseSkinDescriptor extends SkinDescriptor {
CompletableFuture<Skin> fetch(Player player); CompletableFuture<SkinImpl> fetch(Player player);
Skin fetchInstant(Player player); SkinImpl fetchInstant(Player player);
boolean supportsInstant(Player player); boolean supportsInstant(Player player);
String serialize(); String serialize();
@ -27,7 +27,7 @@ public interface BaseSkinDescriptor extends SkinDescriptor {
for (int i = 0; i < (arr.length - 1) / 3; i++) { for (int i = 0; i < (arr.length - 1) / 3; i++) {
properties.add(new TextureProperty(arr[i + 1], arr[i + 2], arr[i + 3])); properties.add(new TextureProperty(arr[i + 1], arr[i + 2], arr[i + 3]));
} }
return new PrefetchedDescriptor(new Skin(properties)); return new PrefetchedDescriptor(new SkinImpl(properties));
} }
throw new IllegalArgumentException("Unknown SkinDescriptor type!"); throw new IllegalArgumentException("Unknown SkinDescriptor type!");
} }

@ -36,7 +36,7 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
@Override @Override
public SkinDescriptor createStaticDescriptor(String texture, String signature) { public SkinDescriptor createStaticDescriptor(String texture, String signature) {
return new PrefetchedDescriptor(new Skin(texture, signature)); return new PrefetchedDescriptor(new SkinImpl(texture, signature));
} }
@Override @Override

@ -6,6 +6,7 @@ import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import com.github.retrooper.packetevents.protocol.player.UserProfile; import com.github.retrooper.packetevents.protocol.player.UserProfile;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lol.pyr.znpcsplus.api.skin.Skin;
import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.reflection.Reflections;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -13,21 +14,21 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
public class Skin { public class SkinImpl implements Skin {
private final long timestamp = System.currentTimeMillis(); private final long timestamp = System.currentTimeMillis();
private final List<TextureProperty> properties; private final List<TextureProperty> properties;
private static final boolean V1_20_2 = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2); private static final boolean V1_20_2 = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2);
public Skin(String texture, String signature) { public SkinImpl(String texture, String signature) {
properties = new ArrayList<>(1); properties = new ArrayList<>(1);
properties.add(new TextureProperty("textures", texture, signature)); properties.add(new TextureProperty("textures", texture, signature));
} }
public Skin(Collection<TextureProperty> properties) { public SkinImpl(Collection<TextureProperty> properties) {
this.properties = new ArrayList<>(properties); this.properties = new ArrayList<>(properties);
} }
public Skin(Object propertyMap) { public SkinImpl(Object propertyMap) {
this.properties = new ArrayList<>(); this.properties = new ArrayList<>();
try { try {
Collection<?> properties = (Collection<?>) Reflections.PROPERTY_MAP_VALUES_METHOD.get().invoke(propertyMap); Collection<?> properties = (Collection<?>) Reflections.PROPERTY_MAP_VALUES_METHOD.get().invoke(propertyMap);
@ -51,7 +52,7 @@ public class Skin {
} }
} }
public Skin(JsonObject obj) { public SkinImpl(JsonObject obj) {
properties = new ArrayList<>(); properties = new ArrayList<>();
for (JsonElement e : obj.get("properties").getAsJsonArray()) { for (JsonElement e : obj.get("properties").getAsJsonArray()) {
JsonObject o = e.getAsJsonObject(); JsonObject o = e.getAsJsonObject();
@ -71,4 +72,20 @@ public class Skin {
public boolean isExpired() { public boolean isExpired() {
return System.currentTimeMillis() - timestamp > 60000L; return System.currentTimeMillis() - timestamp > 60000L;
} }
@Override
public String getTexture() {
for (TextureProperty property : properties)
if (property.getName().equalsIgnoreCase("textures"))
return property.getValue();
return null;
}
@Override
public String getSignature() {
for (TextureProperty property : properties)
if (property.getName().equalsIgnoreCase("textures"))
return property.getSignature();
return null;
}
} }

@ -4,7 +4,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.skin.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -24,7 +24,7 @@ public class MojangSkinCache {
private final ConfigManager configManager; private final ConfigManager configManager;
private final Map<String, Skin> cache = new ConcurrentHashMap<>(); private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>(); private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
public MojangSkinCache(ConfigManager configManager) { public MojangSkinCache(ConfigManager configManager) {
@ -32,11 +32,11 @@ public class MojangSkinCache {
} }
public void cleanCache() { public void cleanCache() {
for (Map.Entry<String, Skin> entry : cache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); for (Map.Entry<String, SkinImpl> entry : cache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey());
for (Map.Entry<String, CachedId> entry : idCache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); for (Map.Entry<String, CachedId> entry : idCache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey());
} }
public CompletableFuture<Skin> fetchByName(String name) { public CompletableFuture<SkinImpl> fetchByName(String name) {
Player player = Bukkit.getPlayerExact(name); Player player = Bukkit.getPlayerExact(name);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
@ -53,7 +53,7 @@ public class MojangSkinCache {
if (obj.has("errorMessage")) return fetchByNameFallback(name).join(); if (obj.has("errorMessage")) return fetchByNameFallback(name).join();
String id = obj.get("id").getAsString(); String id = obj.get("id").getAsString();
idCache.put(name.toLowerCase(), new CachedId(id)); idCache.put(name.toLowerCase(), new CachedId(id));
Skin skin = fetchByUUID(id).join(); SkinImpl skin = fetchByUUID(id).join();
if (skin == null) return fetchByNameFallback(name).join(); if (skin == null) return fetchByNameFallback(name).join();
return skin; return skin;
} }
@ -69,7 +69,7 @@ public class MojangSkinCache {
}); });
} }
public CompletableFuture<Skin> fetchByNameFallback(String name) { public CompletableFuture<SkinImpl> fetchByNameFallback(String name) {
Player player = Bukkit.getPlayerExact(name); Player player = Bukkit.getPlayerExact(name);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
@ -89,7 +89,7 @@ public class MojangSkinCache {
JsonObject textures = obj.get("textures").getAsJsonObject(); JsonObject textures = obj.get("textures").getAsJsonObject();
String value = textures.get("raw").getAsJsonObject().get("value").getAsString(); String value = textures.get("raw").getAsJsonObject().get("value").getAsString();
String signature = textures.get("raw").getAsJsonObject().get("signature").getAsString(); String signature = textures.get("raw").getAsJsonObject().get("signature").getAsString();
Skin skin = new Skin(value, signature); SkinImpl skin = new SkinImpl(value, signature);
cache.put(uuid, skin); cache.put(uuid, skin);
return skin; return skin;
} }
@ -105,7 +105,7 @@ public class MojangSkinCache {
}); });
} }
public CompletableFuture<Skin> fetchByUrl(URL url, String variant) { public CompletableFuture<SkinImpl> fetchByUrl(URL url, String variant) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
URL apiUrl = parseUrl("https://api.mineskin.org/generate/url"); URL apiUrl = parseUrl("https://api.mineskin.org/generate/url");
HttpURLConnection connection = null; HttpURLConnection connection = null;
@ -127,7 +127,7 @@ public class MojangSkinCache {
if (obj.has("error")) return null; if (obj.has("error")) return null;
if (!obj.has("data")) return null; if (!obj.has("data")) return null;
JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject(); JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject();
return new Skin(texture.get("value").getAsString(), texture.get("signature").getAsString()); return new SkinImpl(texture.get("value").getAsString(), texture.get("signature").getAsString());
} }
} catch (IOException exception) { } catch (IOException exception) {
@ -147,26 +147,26 @@ public class MojangSkinCache {
if (!idCache.containsKey(name)) return false; if (!idCache.containsKey(name)) return false;
CachedId id = idCache.get(name); CachedId id = idCache.get(name);
if (id.isExpired() || !cache.containsKey(id.getId())) return false; if (id.isExpired() || !cache.containsKey(id.getId())) return false;
Skin skin = cache.get(id.getId()); SkinImpl skin = cache.get(id.getId());
return !skin.isExpired(); return !skin.isExpired();
} }
public Skin getFullyCachedByName(String s) { public SkinImpl getFullyCachedByName(String s) {
String name = s.toLowerCase(); String name = s.toLowerCase();
if (!idCache.containsKey(name)) return null; if (!idCache.containsKey(name)) return null;
CachedId id = idCache.get(name); CachedId id = idCache.get(name);
if (id.isExpired() || !cache.containsKey(id.getId())) return null; if (id.isExpired() || !cache.containsKey(id.getId())) return null;
Skin skin = cache.get(id.getId()); SkinImpl skin = cache.get(id.getId());
if (skin.isExpired()) return null; if (skin.isExpired()) return null;
return skin; return skin;
} }
public CompletableFuture<Skin> fetchByUUID(String uuid) { public CompletableFuture<SkinImpl> fetchByUUID(String uuid) {
Player player = Bukkit.getPlayer(uuid); Player player = Bukkit.getPlayer(uuid);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
if (cache.containsKey(uuid)) { if (cache.containsKey(uuid)) {
Skin skin = cache.get(uuid); SkinImpl skin = cache.get(uuid);
if (!skin.isExpired()) return CompletableFuture.completedFuture(skin); if (!skin.isExpired()) return CompletableFuture.completedFuture(skin);
} }
@ -179,7 +179,7 @@ public class MojangSkinCache {
try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) { try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject(); JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
if (obj.has("errorMessage")) return null; if (obj.has("errorMessage")) return null;
Skin skin = new Skin(obj); SkinImpl skin = new SkinImpl(obj);
cache.put(uuid, skin); cache.put(uuid, skin);
return skin; return skin;
} }
@ -195,12 +195,12 @@ public class MojangSkinCache {
}); });
} }
public Skin getFromPlayer(Player player) { public SkinImpl getFromPlayer(Player player) {
try { try {
Object playerHandle = Reflections.GET_PLAYER_HANDLE_METHOD.get().invoke(player); Object playerHandle = Reflections.GET_PLAYER_HANDLE_METHOD.get().invoke(player);
Object gameProfile = Reflections.GET_PROFILE_METHOD.get().invoke(playerHandle); Object gameProfile = Reflections.GET_PROFILE_METHOD.get().invoke(playerHandle);
Object propertyMap = Reflections.GET_PROPERTY_MAP_METHOD.get().invoke(gameProfile); Object propertyMap = Reflections.GET_PROPERTY_MAP_METHOD.get().invoke(gameProfile);
return new Skin(propertyMap); return new SkinImpl(propertyMap);
} catch (IllegalAccessException | InvocationTargetException e) { } catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

@ -2,7 +2,7 @@ package lol.pyr.znpcsplus.skin.descriptor;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.util.PapiUtil; import lol.pyr.znpcsplus.util.PapiUtil;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -19,12 +19,12 @@ public class FetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
} }
@Override @Override
public CompletableFuture<Skin> fetch(Player player) { public CompletableFuture<SkinImpl> fetch(Player player) {
return skinCache.fetchByName(PapiUtil.set(player, name)); return skinCache.fetchByName(PapiUtil.set(player, name));
} }
@Override @Override
public Skin fetchInstant(Player player) { public SkinImpl fetchInstant(Player player) {
return skinCache.getFullyCachedByName(PapiUtil.set(player, name)); return skinCache.getFullyCachedByName(PapiUtil.set(player, name));
} }

@ -2,7 +2,7 @@ package lol.pyr.znpcsplus.skin.descriptor;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -16,12 +16,12 @@ public class MirrorDescriptor implements BaseSkinDescriptor, SkinDescriptor {
} }
@Override @Override
public CompletableFuture<Skin> fetch(Player player) { public CompletableFuture<SkinImpl> fetch(Player player) {
return CompletableFuture.completedFuture(skinCache.getFromPlayer(player)); return CompletableFuture.completedFuture(skinCache.getFromPlayer(player));
} }
@Override @Override
public Skin fetchInstant(Player player) { public SkinImpl fetchInstant(Player player) {
return skinCache.getFromPlayer(player); return skinCache.getFromPlayer(player);
} }

@ -3,7 +3,7 @@ package lol.pyr.znpcsplus.skin.descriptor;
import com.github.retrooper.packetevents.protocol.player.TextureProperty; import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -11,9 +11,9 @@ import java.net.URL;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor { public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor {
private final Skin skin; private final SkinImpl skin;
public PrefetchedDescriptor(Skin skin) { public PrefetchedDescriptor(SkinImpl skin) {
this.skin = skin; this.skin = skin;
} }
@ -26,12 +26,12 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
} }
@Override @Override
public CompletableFuture<Skin> fetch(Player player) { public CompletableFuture<SkinImpl> fetch(Player player) {
return CompletableFuture.completedFuture(skin); return CompletableFuture.completedFuture(skin);
} }
@Override @Override
public Skin fetchInstant(Player player) { public SkinImpl fetchInstant(Player player) {
return skin; return skin;
} }
@ -40,7 +40,7 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
return true; return true;
} }
public Skin getSkin() { public SkinImpl getSkin() {
return skin; return skin;
} }