rewrite skins & add support for placeholders skins

This commit is contained in:
Pyrbu 2023-04-25 16:52:23 +01:00
parent 679552a40c
commit d71ba9b664
19 changed files with 422 additions and 55 deletions

@ -234,7 +234,7 @@ public class NPC {
if (npcIsPlayer) {
if (FunctionFactory.isTrue(this, "mirror")) updateProfile(user.getGameProfile().getProperties());
Utils.sendPackets(user, this.tabConstructor, this.updateTabConstructor);
ZNPCsPlus.SCHEDULER.runTask(() -> {
ZNPCsPlus.SCHEDULER.runNextTick(() -> {
PacketEvents.getAPI().getPlayerManager().sendPacket(player, new WrapperPlayServerSpawnPlayer(entityID,
this.gameProfile.getId(), SpigotConversionUtil.fromBukkitLocation(location.toBukkitLocation())));
PacketEvents.getAPI().getPlayerManager().sendPacket(player, new WrapperPlayServerEntityMetadata(entityID,
@ -257,7 +257,7 @@ public class NPC {
updateMetadata(Collections.singleton(user));
sendEquipPackets(user);
lookAt(user, getLocation(), true);
if (npcIsPlayer) ZNPCsPlus.SCHEDULER.scheduleSyncDelayedTask(() -> {
if (npcIsPlayer) ZNPCsPlus.SCHEDULER.runTaskLaterSync(() -> {
removeFromTab(player);
Utils.sendPackets(user, this.updateTabConstructor);
}, 60);

@ -30,7 +30,7 @@ public class InteractionPacketListener implements PacketListener {
ClickType clickType = ClickType.forName(packet.getAction().name());
user.updateLastInteract();
ZNPCsPlus.SCHEDULER.runTask(() -> {
ZNPCsPlus.SCHEDULER.runNextTick(() -> {
Bukkit.getServer().getPluginManager().callEvent(new NPCInteractEvent(player, clickType, npc));
List<NPCAction> actions = npc.getNpcPojo().getClickActions();
if (actions == null) return;

@ -31,7 +31,7 @@ public class EventService<T extends Event> {
}
public void runAll(T event) {
ZNPCsPlus.SCHEDULER.runTask(() -> this.eventConsumers.forEach(consumer -> consumer.accept(event)));
ZNPCsPlus.SCHEDULER.runNextTick(() -> this.eventConsumers.forEach(consumer -> consumer.accept(event)));
}
public static <T extends Event> EventService<T> addService(ZUser user, Class<T> eventClass) {

@ -24,11 +24,15 @@ public class SchedulerUtils {
return Bukkit.getScheduler().runTaskTimerAsynchronously(this.plugin, runnable, delay, continuousDelay);
}
public void scheduleSyncDelayedTask(Runnable runnable, int delay) {
public void runTaskLaterSync(Runnable runnable, int delay) {
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, runnable, delay);
}
public BukkitTask runTask(Runnable runnable) {
public BukkitTask runNextTick(Runnable runnable) {
return Bukkit.getScheduler().runTask(this.plugin, runnable);
}
public void runAsync(Runnable runnable) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable);
}
}

@ -12,10 +12,7 @@ import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants;
import io.github.znetworkw.znpcservers.listeners.InventoryListener;
import io.github.znetworkw.znpcservers.listeners.PlayerListener;
import io.github.znetworkw.znpcservers.npc.NPCPath;
import io.github.znetworkw.znpcservers.npc.NPCSkin;
import io.github.znetworkw.znpcservers.npc.interaction.InteractionPacketListener;
import io.github.znetworkw.znpcservers.npc.task.NPCPositionTask;
import io.github.znetworkw.znpcservers.npc.task.NPCSaveTask;
import io.github.znetworkw.znpcservers.user.ZUser;
import io.github.znetworkw.znpcservers.utility.BungeeUtils;
import io.github.znetworkw.znpcservers.utility.SchedulerUtils;
@ -26,6 +23,11 @@ import lol.pyr.znpcsplus.npc.NPC;
import lol.pyr.znpcsplus.npc.NPCProperty;
import lol.pyr.znpcsplus.npc.NPCRegistry;
import lol.pyr.znpcsplus.npc.NPCType;
import lol.pyr.znpcsplus.skin.cache.SkinCache;
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import lol.pyr.znpcsplus.tasks.NPCVisibilityTask;
import lol.pyr.znpcsplus.updater.UpdateChecker;
import lol.pyr.znpcsplus.updater.UpdateNotificationListener;
@ -57,6 +59,7 @@ public class ZNPCsPlus extends JavaPlugin {
public static SchedulerUtils SCHEDULER;
public static BungeeUtils BUNGEE_UTILS;
public static BukkitAudiences ADVENTURE;
public static boolean PLACEHOLDERS_SUPPORTED;
private boolean enabled = false;
@ -106,6 +109,9 @@ public class ZNPCsPlus extends JavaPlugin {
PacketEvents.getAPI().getEventManager().registerListener(new InteractionPacketListener(), PacketListenerPriority.MONITOR);
PacketEvents.getAPI().init();
PLACEHOLDERS_SUPPORTED = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
if (PLACEHOLDERS_SUPPORTED) log(ChatColor.WHITE + " * Enabling PlaceholderAPI Support...");
PLUGIN_FOLDER.mkdirs();
PATH_FOLDER.mkdirs();
@ -121,11 +127,10 @@ public class ZNPCsPlus extends JavaPlugin {
Bukkit.getOnlinePlayers().forEach(ZUser::find);
log(ChatColor.WHITE + " * Starting tasks...");
new NPCPositionTask(this);
new NPCVisibilityTask(this);
new NPCSaveTask(this, ConfigurationConstants.SAVE_DELAY);
new PlayerListener(this);
new InventoryListener(this);
new SkinCacheCleanTask(this);
if (ConfigurationConstants.CHECK_FOR_UPDATES) new UpdateNotificationListener(this, new UpdateChecker(this));
enabled = true;
@ -141,7 +146,7 @@ public class ZNPCsPlus extends JavaPlugin {
for (NPCType type : NPCType.values()) {
NPC npc = new NPC(world, type, new PacketLocation(x * 3, 200, z * 3, 0, 0));
if (type.getType() == EntityTypes.PLAYER) {
NPCSkin.forName("Notch", (skin, ex) -> npc.setProperty(NPCProperty.SKIN, skin));
SkinCache.fetchByName("Notch").thenAccept(skin -> npc.setProperty(NPCProperty.SKIN, new PrefetchedDescriptor(skin)));
}
npc.setProperty(NPCProperty.GLOW, NamedTextColor.RED);
npc.setProperty(NPCProperty.FIRE, true);
@ -151,6 +156,13 @@ public class ZNPCsPlus extends JavaPlugin {
z++;
}
}
NPC npc = new NPC(world, NPCType.byName("player"), new PacketLocation(x * 3, 200, z * 3, 0, 0));
npc.setProperty(NPCProperty.SKIN, new FetchingDescriptor("jeb_"));
NPCRegistry.register("debug_npc" + (z * wrap + x), npc);
x++;
npc = new NPC(world, NPCType.byName("player"), new PacketLocation(x * 3, 200, z * 3, 0, 0));
npc.setProperty(NPCProperty.SKIN, new MirrorDescriptor());
NPCRegistry.register("debug_npc" + (z * wrap + x), npc);
}
}

@ -65,6 +65,11 @@ public class NPC {
_showAll();
}
public void respawn(Player player) {
_hide(player);
_show(player);
}
public void show(Player player) {
if (viewers.contains(player)) return;
_show(player);

@ -1,6 +1,6 @@
package lol.pyr.znpcsplus.npc;
import io.github.znetworkw.znpcservers.npc.NPCSkin;
import lol.pyr.znpcsplus.skin.SkinDescriptor;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.HashMap;
@ -35,7 +35,7 @@ public class NPCProperty<T> {
}
public static NPCProperty<Boolean> SKIN_LAYERS = new NPCProperty<>("skin_layers", true);
public static NPCProperty<NPCSkin> SKIN = new NPCProperty<>("skin");
public static NPCProperty<SkinDescriptor> SKIN = new NPCProperty<>("skin");
public static NPCProperty<NamedTextColor> GLOW = new NPCProperty<>("glow");
public static NPCProperty<Boolean> FIRE = new NPCProperty<>("fire", false);
}

@ -5,22 +5,25 @@ import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
public class NPCType {
private final static Set<NPCType> npcTypes;
private final static Map<String, NPCType> BY_NAME = new HashMap<>();
public static Set<NPCType> values() {
return npcTypes;
public static Collection<NPCType> values() {
return BY_NAME.values();
}
public static NPCType byName(String name) {
return BY_NAME.get(name.toUpperCase());
}
private final EntityType type;
private final Set<NPCProperty<?>> allowedProperties;
private final String name;
public NPCType(EntityType type, NPCProperty<?>... allowedProperties) {
private NPCType(String name, EntityType type, NPCProperty<?>... allowedProperties) {
this.name = name.toUpperCase();
this.type = type;
ArrayList<NPCProperty<?>> list = new ArrayList<>(List.of(allowedProperties));
list.add(NPCProperty.FIRE);
@ -28,6 +31,10 @@ public class NPCType {
this.allowedProperties = Set.copyOf(list);
}
public String getName() {
return name;
}
public EntityType getType() {
return type;
}
@ -36,12 +43,15 @@ public class NPCType {
return allowedProperties;
}
private static void register(NPCType type) {
BY_NAME.put(type.getName(), type);
}
static {
Set<NPCType> set = new HashSet<>();
set.add(new NPCType(EntityTypes.PLAYER, NPCProperty.SKIN, NPCProperty.SKIN_LAYERS));
set.add(new NPCType(EntityTypes.CREEPER));
set.add(new NPCType(EntityTypes.ZOMBIE));
set.add(new NPCType(EntityTypes.SKELETON));
npcTypes = Set.copyOf(set);
register(new NPCType("player", EntityTypes.PLAYER, NPCProperty.SKIN, NPCProperty.SKIN_LAYERS));
register(new NPCType("creeper", EntityTypes.CREEPER));
register(new NPCType("zombie", EntityTypes.ZOMBIE));
register(new NPCType("skeleton", EntityTypes.SKELETON));
}
}

@ -9,13 +9,14 @@ import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public interface PacketFactory {
void spawnPlayer(Player player, PacketEntity entity);
void spawnEntity(Player player, PacketEntity entity);
void destroyEntity(Player player, PacketEntity entity);
void teleportEntity(Player player, PacketEntity entity);
void addTabPlayer(Player player, PacketEntity entity);
CompletableFuture<Void> addTabPlayer(Player player, PacketEntity entity);
void removeTabPlayer(Player player, PacketEntity entity);
void createTeam(Player player, PacketEntity entity);
void removeTeam(Player player, PacketEntity entity);

@ -10,16 +10,21 @@ import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import java.util.EnumSet;
import java.util.concurrent.CompletableFuture;
public class V1_19Factory extends V1_14Factory {
@Override
public void addTabPlayer(Player player, PacketEntity entity) {
if (entity.getType() != EntityTypes.PLAYER) return;
public CompletableFuture<Void> addTabPlayer(Player player, PacketEntity entity) {
if (entity.getType() != EntityTypes.PLAYER) return CompletableFuture.completedFuture(null);
CompletableFuture<Void> future = new CompletableFuture<>();
skinned(player, entity, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> {
WrapperPlayServerPlayerInfoUpdate.PlayerInfo info = new WrapperPlayServerPlayerInfoUpdate.PlayerInfo(
skinned(entity, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))), false,
1, GameMode.CREATIVE, Component.empty(), null);
profile, false, 1, GameMode.CREATIVE, Component.empty(), null);
sendPacket(player, new WrapperPlayServerPlayerInfoUpdate(EnumSet.of(WrapperPlayServerPlayerInfoUpdate.Action.ADD_PLAYER,
WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_LISTED), info, info));
future.complete(null);
});
return future;
}
@Override

@ -6,35 +6,36 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import com.github.retrooper.packetevents.protocol.player.UserProfile;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.*;
import io.github.znetworkw.znpcservers.npc.NPCSkin;
import lol.pyr.znpcsplus.ZNPCsPlus;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.entity.PacketLocation;
import lol.pyr.znpcsplus.metadata.MetadataFactory;
import lol.pyr.znpcsplus.npc.NPC;
import lol.pyr.znpcsplus.npc.NPCProperty;
import lol.pyr.znpcsplus.skin.SkinDescriptor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class V1_8Factory implements PacketFactory {
@Override
public void spawnPlayer(Player player, PacketEntity entity) {
addTabPlayer(player, entity);
addTabPlayer(player, entity).thenAccept(ignored -> {
createTeam(player, entity);
PacketLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerSpawnPlayer(entity.getEntityId(),
entity.getUuid(), location.toVector3d(), location.getYaw(), location.getPitch(), List.of()));
sendAllMetadata(player, entity);
ZNPCsPlus.SCHEDULER.scheduleSyncDelayedTask(() -> removeTabPlayer(player, entity), 60);
ZNPCsPlus.SCHEDULER.runTaskLaterSync(() -> removeTabPlayer(player, entity), 60);
});
}
@Override
@ -64,11 +65,16 @@ public class V1_8Factory implements PacketFactory {
}
@Override
public void addTabPlayer(Player player, PacketEntity entity) {
if (entity.getType() != EntityTypes.PLAYER) return;
public CompletableFuture<Void> addTabPlayer(Player player, PacketEntity entity) {
if (entity.getType() != EntityTypes.PLAYER) return CompletableFuture.completedFuture(null);
CompletableFuture<Void> future = new CompletableFuture<>();
skinned(player, entity, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> {
sendPacket(player, new WrapperPlayServerPlayerInfo(
WrapperPlayServerPlayerInfo.Action.ADD_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData(Component.text(""),
skinned(entity, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))), GameMode.CREATIVE, 1)));
profile, GameMode.CREATIVE, 1)));
future.complete(null);
});
return future;
}
@Override
@ -114,11 +120,19 @@ public class V1_8Factory implements PacketFactory {
PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet);
}
protected UserProfile skinned(PacketEntity entity, UserProfile profile) {
protected CompletableFuture<UserProfile> skinned(Player player, PacketEntity entity, UserProfile profile) {
NPC owner = entity.getOwner();
if (!owner.hasProperty(NPCProperty.SKIN)) return profile;
NPCSkin skin = owner.getProperty(NPCProperty.SKIN);
profile.setTextureProperties(List.of(new TextureProperty("textures", skin.getTexture(), skin.getSignature())));
return profile;
if (!owner.hasProperty(NPCProperty.SKIN)) return CompletableFuture.completedFuture(profile);
SkinDescriptor descriptor = owner.getProperty(NPCProperty.SKIN);
if (descriptor.supportsInstant(player)) {
descriptor.fetchInstant(player).apply(profile);
return CompletableFuture.completedFuture(profile);
}
CompletableFuture<UserProfile> future = new CompletableFuture<>();
descriptor.fetch(player).thenAccept(skin -> {
skin.apply(profile);
future.complete(profile);
});
return future;
}
}

@ -0,0 +1,45 @@
package lol.pyr.znpcsplus.skin;
import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import com.github.retrooper.packetevents.protocol.player.UserProfile;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.authlib.properties.PropertyMap;
import java.util.ArrayList;
import java.util.List;
public class Skin {
private final long timestamp = System.currentTimeMillis();
private final List<TextureProperty> properties = new ArrayList<>();
public Skin(String texture, String signature) {
properties.add(new TextureProperty("textures", texture, signature));
}
public Skin(TextureProperty... properties) {
this.properties.addAll(List.of(properties));
}
public Skin(PropertyMap properties) {
this.properties.addAll(properties.values().stream()
.map(property -> new TextureProperty(property.getName(), property.getValue(), property.getSignature()))
.toList());
}
public Skin(JsonObject obj) {
for (JsonElement e : obj.get("properties").getAsJsonArray()) {
JsonObject o = e.getAsJsonObject();
properties.add(new TextureProperty(o.get("name").getAsString(), o.get("value").getAsString(), o.has("signature") ? o.get("signature").getAsString() : null));
}
}
public UserProfile apply(UserProfile profile) {
profile.setTextureProperties(properties);
return profile;
}
public boolean isExpired() {
return System.currentTimeMillis() - timestamp > 60000L;
}
}

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

@ -0,0 +1,18 @@
package lol.pyr.znpcsplus.skin.cache;
public class CachedId {
private final long timestamp = System.currentTimeMillis();
private final String id;
public CachedId(String id) {
this.id = id;
}
public boolean isExpired() {
return System.currentTimeMillis() - timestamp > 60000L;
}
public String getId() {
return id;
}
}

@ -0,0 +1,131 @@
package lol.pyr.znpcsplus.skin.cache;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.authlib.GameProfile;
import io.github.znetworkw.znpcservers.reflection.Reflections;
import lol.pyr.znpcsplus.skin.Skin;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class SkinCache {
private final static Map<String, Skin> cache = new ConcurrentHashMap<>();
private final static Map<String, CachedId> idCache = new ConcurrentHashMap<>();
public static void cleanCache() {
for (Map.Entry<String, Skin> 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());
}
public static CompletableFuture<Skin> fetchByName(String name) {
Player player = Bukkit.getPlayerExact(name);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
if (cache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
CompletableFuture<Skin> future = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
URL url = parseUrl("https://api.mojang.com/users/profiles/minecraft/" + name);
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
if (obj.has("errorMessage")) future.complete(null);
String id = obj.get("id").getAsString();
idCache.put(name.toLowerCase(), new CachedId(id));
fetchByUUID(id).thenAccept(future::complete);
}
} catch (IOException exception) {
exception.printStackTrace();
} finally {
if (connection != null) connection.disconnect();
}
});
return future;
}
public static CompletableFuture<Skin> fetchByUUID(UUID uuid) {
return fetchByUUID(uuid.toString().replace("-", ""));
}
public static boolean isNameFullyCached(String s) {
String name = s.toLowerCase();
if (!idCache.containsKey(name)) return false;
CachedId id = idCache.get(name);
if (id.isExpired() || !cache.containsKey(id.getId())) return false;
Skin skin = cache.get(id.getId());
return !skin.isExpired();
}
public static Skin getFullyCachedByName(String s) {
String name = s.toLowerCase();
if (!idCache.containsKey(name)) return null;
CachedId id = idCache.get(name);
if (id.isExpired() || !cache.containsKey(id.getId())) return null;
Skin skin = cache.get(id.getId());
if (skin.isExpired()) return null;
return skin;
}
public static CompletableFuture<Skin> fetchByUUID(String uuid) {
Player player = Bukkit.getPlayer(uuid);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
if (cache.containsKey(uuid)) {
Skin skin = cache.get(uuid);
if (!skin.isExpired()) return CompletableFuture.completedFuture(skin);
}
return CompletableFuture.supplyAsync(() -> {
URL url = parseUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
Skin skin = new Skin(JsonParser.parseReader(reader).getAsJsonObject());
cache.put(uuid, skin);
return skin;
}
} catch (IOException exception) {
exception.printStackTrace();
} finally {
if (connection != null) connection.disconnect();
}
return null;
});
}
public static Skin getFromPlayer(Player player) {
try {
Object playerHandle = Reflections.GET_HANDLE_PLAYER_METHOD.get().invoke(player);
GameProfile gameProfile = (GameProfile) Reflections.GET_PROFILE_METHOD.get().invoke(playerHandle, new Object[0]);
return new Skin(gameProfile.getProperties());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private static URL parseUrl(String url) {
try {
return new URL(url);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
}
}

@ -0,0 +1,15 @@
package lol.pyr.znpcsplus.skin.cache;
import lol.pyr.znpcsplus.ZNPCsPlus;
import org.bukkit.scheduler.BukkitRunnable;
public class SkinCacheCleanTask extends BukkitRunnable {
public SkinCacheCleanTask(ZNPCsPlus plugin) {
runTaskTimerAsynchronously(plugin, 1200, 1200);
}
@Override
public void run() {
SkinCache.cleanCache();
}
}

@ -0,0 +1,38 @@
package lol.pyr.znpcsplus.skin.descriptor;
import lol.pyr.znpcsplus.ZNPCsPlus;
import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.cache.SkinCache;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public class FetchingDescriptor implements SkinDescriptor {
private final String name;
public FetchingDescriptor(String name) {
this.name = name;
}
@Override
public CompletableFuture<Skin> fetch(Player player) {
return SkinCache.fetchByName(papi(player));
}
@Override
public Skin fetchInstant(Player player) {
return SkinCache.getFullyCachedByName(papi(player));
}
@Override
public boolean supportsInstant(Player player) {
return SkinCache.isNameFullyCached(papi(player));
}
private String papi(Player player) {
if (ZNPCsPlus.PLACEHOLDERS_SUPPORTED) return PlaceholderAPI.setPlaceholders(player, name);
return name;
}
}

@ -0,0 +1,28 @@
package lol.pyr.znpcsplus.skin.descriptor;
import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.cache.SkinCache;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public class MirrorDescriptor implements SkinDescriptor {
public MirrorDescriptor() {}
@Override
public CompletableFuture<Skin> fetch(Player player) {
return CompletableFuture.completedFuture(SkinCache.getFromPlayer(player));
}
@Override
public Skin fetchInstant(Player player) {
return SkinCache.getFromPlayer(player);
}
@Override
public boolean supportsInstant(Player player) {
return true;
}
}

@ -0,0 +1,30 @@
package lol.pyr.znpcsplus.skin.descriptor;
import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.SkinDescriptor;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public class PrefetchedDescriptor implements SkinDescriptor {
private final Skin skin;
public PrefetchedDescriptor(Skin skin) {
this.skin = skin;
}
@Override
public CompletableFuture<Skin> fetch(Player player) {
return CompletableFuture.completedFuture(skin);
}
@Override
public Skin fetchInstant(Player player) {
return skin;
}
@Override
public boolean supportsInstant(Player player) {
return true;
}
}