rewrite skins & add support for placeholders skins
This commit is contained in:
parent
85d2b852f9
commit
8c0d6f07fa
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;
|
||||
WrapperPlayServerPlayerInfoUpdate.PlayerInfo info = new WrapperPlayServerPlayerInfoUpdate.PlayerInfo(
|
||||
skinned(entity, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))), false,
|
||||
1, GameMode.CREATIVE, Component.empty(), null);
|
||||
sendPacket(player, new WrapperPlayServerPlayerInfoUpdate(EnumSet.of(WrapperPlayServerPlayerInfoUpdate.Action.ADD_PLAYER,
|
||||
WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_LISTED), info, info));
|
||||
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(
|
||||
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);
|
||||
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);
|
||||
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.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;
|
||||
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)));
|
||||
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(""),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
45
src/main/java/lol/pyr/znpcsplus/skin/Skin.java
Normal file
45
src/main/java/lol/pyr/znpcsplus/skin/Skin.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
11
src/main/java/lol/pyr/znpcsplus/skin/SkinDescriptor.java
Normal file
11
src/main/java/lol/pyr/znpcsplus/skin/SkinDescriptor.java
Normal file
|
@ -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);
|
||||
}
|
18
src/main/java/lol/pyr/znpcsplus/skin/cache/CachedId.java
vendored
Normal file
18
src/main/java/lol/pyr/znpcsplus/skin/cache/CachedId.java
vendored
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
131
src/main/java/lol/pyr/znpcsplus/skin/cache/SkinCache.java
vendored
Normal file
131
src/main/java/lol/pyr/znpcsplus/skin/cache/SkinCache.java
vendored
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
15
src/main/java/lol/pyr/znpcsplus/skin/cache/SkinCacheCleanTask.java
vendored
Normal file
15
src/main/java/lol/pyr/znpcsplus/skin/cache/SkinCacheCleanTask.java
vendored
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue