feat: file skin type to use files for npc skin

This commit is contained in:
D3v1s0m 2024-12-16 22:42:27 +04:00
parent 439f152ef5
commit 255a938dda
No known key found for this signature in database
GPG key ID: FA1F770C7B1D40C1
4 changed files with 110 additions and 3 deletions

View file

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

View file

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

View file

@ -14,6 +14,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;
@ -26,9 +27,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() {
@ -142,6 +146,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;
@ -213,4 +269,8 @@ public class MojangSkinCache {
throw new RuntimeException(exception);
}
}
public File getSkinsFolder() {
return skinsFolder;
}
}

View file

@ -7,6 +7,7 @@ import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import org.bukkit.entity.Player;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
@ -25,6 +26,16 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
return CompletableFuture.supplyAsync(() -> 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);