Merge pull request #161 from D3v1s0m/feat/file-skin-type
Feature: File skin type
This commit is contained in:
commit
d1227500ce
4 changed files with 110 additions and 3 deletions
|
@ -93,7 +93,7 @@ public class ZNpcsPlus {
|
|||
packetEvents.load();
|
||||
|
||||
configManager = new ConfigManager(getDataFolder());
|
||||
skinCache = new MojangSkinCache(configManager);
|
||||
skinCache = new MojangSkinCache(configManager, new File(getDataFolder(), "skins"));
|
||||
propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
|
||||
|
||||
NpcPropertyRegistryProvider.register(propertyRegistry);
|
||||
|
|
|
@ -17,10 +17,14 @@ import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
|
|||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SkinCommand implements CommandHandler {
|
||||
private final MojangSkinCache skinCache;
|
||||
|
@ -90,6 +94,30 @@ public class SkinCommand implements CommandHandler {
|
|||
context.send(Component.text("Invalid url!", NamedTextColor.RED));
|
||||
}
|
||||
return;
|
||||
} else if (type.equalsIgnoreCase("file")) {
|
||||
context.ensureArgsNotEmpty();
|
||||
String path = context.dumpAllArgs();
|
||||
context.send(Component.text("Fetching skin from file \"" + path + "\"...", NamedTextColor.GREEN));
|
||||
PrefetchedDescriptor.fromFile(skinCache, path).exceptionally(e -> {
|
||||
if (e instanceof FileNotFoundException || e.getCause() instanceof FileNotFoundException) {
|
||||
context.send(Component.text("A file at the specified path could not be found!", NamedTextColor.RED));
|
||||
} else {
|
||||
context.send(Component.text("An error occurred while fetching the skin from file! Check the console for more details.", NamedTextColor.RED));
|
||||
//noinspection CallToPrintStackTrace
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}).thenAccept(skin -> {
|
||||
if (skin == null) return;
|
||||
if (skin.getSkin() == null) {
|
||||
context.send(Component.text("Failed to fetch skin, are you sure the file path is valid?", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), skin);
|
||||
npc.respawn();
|
||||
context.send(Component.text("The NPC's skin has been set.", NamedTextColor.GREEN));
|
||||
});
|
||||
return;
|
||||
}
|
||||
context.send(Component.text("Unknown skin type! Please use one of the following: mirror, static, dynamic, url"));
|
||||
}
|
||||
|
@ -97,11 +125,19 @@ public class SkinCommand implements CommandHandler {
|
|||
@Override
|
||||
public List<String> suggest(CommandContext context) throws CommandExecutionException {
|
||||
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
|
||||
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url");
|
||||
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url", "file");
|
||||
if (context.matchSuggestion("*", "static")) return context.suggestPlayers();
|
||||
if (context.argSize() == 3 && context.matchSuggestion("*", "url")) {
|
||||
return context.suggestLiteral("slim", "classic");
|
||||
}
|
||||
if (context.argSize() == 3 && context.matchSuggestion("*", "file")) {
|
||||
if (skinCache.getSkinsFolder().exists()) {
|
||||
File[] files = skinCache.getSkinsFolder().listFiles();
|
||||
if (files != null) {
|
||||
return Arrays.stream(files).map(File::getName).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.net.HttpURLConnection;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
@ -27,9 +28,12 @@ public class MojangSkinCache {
|
|||
|
||||
private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
|
||||
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
|
||||
private final File skinsFolder;
|
||||
|
||||
public MojangSkinCache(ConfigManager configManager) {
|
||||
public MojangSkinCache(ConfigManager configManager, File skinsFolder) {
|
||||
this.configManager = configManager;
|
||||
this.skinsFolder = skinsFolder;
|
||||
if (!skinsFolder.exists()) skinsFolder.mkdirs();
|
||||
}
|
||||
|
||||
public void cleanCache() {
|
||||
|
@ -143,6 +147,58 @@ public class MojangSkinCache {
|
|||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<SkinImpl> fetchFromFile(String path) throws FileNotFoundException {
|
||||
File file = new File(skinsFolder, path);
|
||||
if (!file.exists()) throw new FileNotFoundException("File not found: " + path);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
URL apiUrl = parseUrl("https://api.mineskin.org/generate/upload");
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
String boundary = "*****";
|
||||
String CRLF = "\r\n";
|
||||
|
||||
connection = (HttpURLConnection) apiUrl.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setReadTimeout(10000);
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setUseCaches(false);
|
||||
connection.setRequestProperty("Cache-Control", "no-cache");
|
||||
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
OutputStream outputStream = connection.getOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(outputStream);
|
||||
out.writeBytes("--" + boundary + CRLF);
|
||||
out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"" + CRLF);
|
||||
out.writeBytes("Content-Type: image/png" + CRLF);
|
||||
out.writeBytes(CRLF);
|
||||
out.write(Files.readAllBytes(file.toPath()));
|
||||
out.writeBytes(CRLF);
|
||||
out.writeBytes("--" + boundary + "--" + CRLF);
|
||||
out.flush();
|
||||
out.close();
|
||||
outputStream.close();
|
||||
|
||||
try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
|
||||
JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
|
||||
if (obj.has("error")) return null;
|
||||
if (!obj.has("data")) return null;
|
||||
JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject();
|
||||
return new SkinImpl(texture.get("value").getAsString(), texture.get("signature").getAsString());
|
||||
}
|
||||
|
||||
} catch (IOException exception) {
|
||||
if (!configManager.getConfig().disableSkinFetcherWarnings()) {
|
||||
logger.warning("Failed to get skin from file:");
|
||||
exception.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
if (connection != null) connection.disconnect();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isNameFullyCached(String s) {
|
||||
String name = s.toLowerCase();
|
||||
if (!idCache.containsKey(name)) return false;
|
||||
|
@ -214,4 +270,8 @@ public class MojangSkinCache {
|
|||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public File getSkinsFolder() {
|
||||
return skinsFolder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
|
|||
import lol.pyr.znpcsplus.util.FutureUtil;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
@ -26,6 +27,16 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
|
|||
return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join()));
|
||||
}
|
||||
|
||||
public static CompletableFuture<PrefetchedDescriptor> fromFile(MojangSkinCache cache, String path) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return new PrefetchedDescriptor(cache.fetchFromFile(path).join());
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SkinImpl> fetch(Player player) {
|
||||
return CompletableFuture.completedFuture(skin);
|
||||
|
|
Loading…
Reference in a new issue