fancynpcs: handle skin errors gracefully (#23)

This commit is contained in:
Michał
2025-04-17 16:59:50 +02:00
committed by GitHub
parent 0d52abc0a6
commit 2e8f2fb9ed
6 changed files with 92 additions and 27 deletions

View File

@@ -0,0 +1,30 @@
package de.oliver.fancynpcs.api.skins;
import org.jetbrains.annotations.NotNull;
/**
* Exception that is thrown when a skin cannot be fetched or loaded.
*/
public final class SkinLoadException extends RuntimeException {
private final @NotNull SkinLoadException.Reason reason;
public SkinLoadException(final @NotNull SkinLoadException.Reason reason, final @NotNull String message) {
super(message);
this.reason = reason;
}
/**
* Returns the reason why the skin could not be loaded.
*
* @return the reason
*/
public @NotNull SkinLoadException.Reason getReason() {
return reason;
}
public enum Reason {
INVALID_URL, INVALID_FILE, INVALID_PLACEHOLDER, INVALID_USERNAME
}
}

View File

@@ -10,6 +10,7 @@ import de.oliver.fancynpcs.api.actions.ActionTrigger;
import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.events.NpcsLoadedEvent;
import de.oliver.fancynpcs.api.skins.SkinData;
import de.oliver.fancynpcs.api.skins.SkinLoadException;
import de.oliver.fancynpcs.api.utils.NpcEquipmentSlot;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
@@ -275,8 +276,13 @@ public class NpcManagerImpl implements NpcManager {
String skinVariantStr = npcConfig.getString("npcs." + id + ".skin.variant", SkinData.SkinVariant.AUTO.name());
SkinData.SkinVariant skinVariant = SkinData.SkinVariant.valueOf(skinVariantStr);
if (!skinIdentifier.isEmpty()) {
skin = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skinIdentifier, skinVariant);
skin.setIdentifier(skinIdentifier);
try {
skin = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skinIdentifier, skinVariant);
skin.setIdentifier(skinIdentifier);
} catch (final SkinLoadException e) {
logger.error("NPC named '" + name + "' identified by '" + id + "' could not have their skin loaded.");
logger.error(" " + e.getReason() + " " + e.getMessage());
}
}

View File

@@ -5,6 +5,8 @@ import de.oliver.fancynpcs.FancyNpcs;
import de.oliver.fancynpcs.api.Npc;
import de.oliver.fancynpcs.api.events.NpcModifyEvent;
import de.oliver.fancynpcs.api.skins.SkinData;
import de.oliver.fancynpcs.api.skins.SkinLoadException;
import de.oliver.fancynpcs.skins.SkinUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
@@ -18,7 +20,9 @@ import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public enum SkinCMD {
@@ -64,27 +68,35 @@ public enum SkinCMD {
} else {
translator.translate("command_npc_modification_cancelled").send(sender);
}
} else {
} else try {
SkinData.SkinVariant variant = slim ? SkinData.SkinVariant.SLIM : SkinData.SkinVariant.AUTO;
SkinData skinData = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skin, variant);
skinData.setIdentifier(skin);
if (!skinData.hasTexture()) {
translator.translate("npc_skin_set_later").replace("npc", npc.getData().getName()).send(sender);
}
if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, false, sender).callEvent() && new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, skinData, sender).callEvent()) {
translator.translate("npc_skin_set")
.replace("npc", npc.getData().getName())
.replace("name", skinData.getIdentifier())
.send(sender);
if (!skinData.hasTexture()) {
translator.translate("npc_skin_set_later").replace("npc", npc.getData().getName()).send(sender);
}
npc.getData().setMirrorSkin(false);
npc.getData().setSkinData(skinData);
npc.removeForAll();
npc.create();
npc.spawnForAll();
translator.translate("npc_skin_set")
.replace("npc", npc.getData().getName())
.replace("name", skinData.getIdentifier())
.send(sender);
} else {
translator.translate("command_npc_modification_cancelled").send(sender);
}
} catch (final SkinLoadException e) {
switch (e.getReason()) {
case INVALID_URL -> translator.translate("npc_skin_failure_invalid_url").replace("npc", npc.getData().getName()).send(sender);
case INVALID_FILE -> translator.translate("npc_skin_failure_invalid_file").replace("npc", npc.getData().getName()).send(sender);
case INVALID_USERNAME -> translator.translate("npc_skin_failure_invalid_username").replace("npc", npc.getData().getName()).send(sender);
case INVALID_PLACEHOLDER -> translator.translate("npc_skin_failure_invalid_placeholder").replace("npc", npc.getData().getName()).send(sender);
}
}
}
@@ -96,6 +108,11 @@ public enum SkinCMD {
add("@none");
add("@mirror");
Bukkit.getOnlinePlayers().stream().map(Player::getName).forEach(this::add);
// Adding file names inside 'plugins/FancyNpcs/skins' to the list of completions.
final File[] files = new File(FancyNpcs.getInstance().getDataFolder(), "skins").listFiles();
if (files != null) {
Arrays.stream(files).map(File::getName).filter(SkinUtils::isFile).forEach(this::add);
}
}};
}

View File

@@ -6,6 +6,7 @@ import de.oliver.fancynpcs.FancyNpcs;
import de.oliver.fancynpcs.api.Npc;
import de.oliver.fancynpcs.api.skins.SkinData;
import de.oliver.fancynpcs.api.skins.SkinGeneratedEvent;
import de.oliver.fancynpcs.api.skins.SkinLoadException;
import de.oliver.fancynpcs.api.skins.SkinManager;
import de.oliver.fancynpcs.skins.cache.SkinCache;
import de.oliver.fancynpcs.skins.cache.SkinCacheData;
@@ -48,7 +49,7 @@ public class SkinManagerImpl implements SkinManager, Listener {
}
@Override
public SkinData getByIdentifier(String identifier, SkinData.SkinVariant variant) {
public SkinData getByIdentifier(String identifier, SkinData.SkinVariant variant) throws SkinLoadException {
if (SkinUtils.isUUID(identifier)) {
return getByUUID(UUID.fromString(identifier), variant);
}
@@ -64,8 +65,8 @@ public class SkinManagerImpl implements SkinManager, Listener {
if (SkinUtils.isPlaceholder(identifier)) {
String parsed = ChatColorHandler.translate(identifier);
if (SkinUtils.isPlaceholder(parsed)) {
return null;
if (parsed.isBlank() || parsed.equalsIgnoreCase("null") || SkinUtils.isPlaceholder(parsed)) {
throw new SkinLoadException(SkinLoadException.Reason.INVALID_PLACEHOLDER, "(RAW = '" + identifier + "'; PARSED = '" + parsed + "')");
}
return getByIdentifier(parsed, variant);
@@ -74,7 +75,7 @@ public class SkinManagerImpl implements SkinManager, Listener {
// is username
UUID uuid = UUIDFetcher.getUUID(identifier);
if (uuid == null) {
return null;
throw new SkinLoadException(SkinLoadException.Reason.INVALID_USERNAME, "(USERNAME = '" + identifier + "')");
}
return getByUUID(uuid, variant);
@@ -96,17 +97,17 @@ public class SkinManagerImpl implements SkinManager, Listener {
}
@Override
public SkinData getByUsername(String username, SkinData.SkinVariant variant) {
public SkinData getByUsername(String username, SkinData.SkinVariant variant) throws SkinLoadException {
UUID uuid = UUIDFetcher.getUUID(username);
if (uuid == null) {
return null;
throw new SkinLoadException(SkinLoadException.Reason.INVALID_USERNAME, "(USERNAME = '" + username + "')");
}
return getByUUID(uuid, variant);
}
@Override
public SkinData getByURL(String url, SkinData.SkinVariant variant) {
public SkinData getByURL(String url, SkinData.SkinVariant variant) throws SkinLoadException {
SkinData cached = tryToGetFromCache(url, variant);
if (cached != null) {
return cached;
@@ -115,9 +116,8 @@ public class SkinManagerImpl implements SkinManager, Listener {
GenerateRequest genReq;
try {
genReq = GenerateRequest.url(url);
} catch (MalformedURLException e) {
FancyNpcs.getInstance().getFancyLogger().error("Invalid URL: " + url);
return null;
} catch (final IllegalArgumentException | MalformedURLException e) {
throw new SkinLoadException(SkinLoadException.Reason.INVALID_URL, "(URL = '" + url + "')");
}
genReq.variant(Variant.valueOf(variant.name()));
MineSkinQueue.get().add(new MineSkinQueue.SkinRequest(url, genReq));
@@ -125,7 +125,7 @@ public class SkinManagerImpl implements SkinManager, Listener {
}
@Override
public SkinData getByFile(String filePath, SkinData.SkinVariant variant) {
public SkinData getByFile(String filePath, SkinData.SkinVariant variant) throws SkinLoadException {
SkinData cached = tryToGetFromCache(filePath, variant);
if (cached != null) {
return cached;
@@ -133,8 +133,7 @@ public class SkinManagerImpl implements SkinManager, Listener {
File file = new File(SKINS_DIRECTORY + filePath);
if (!file.exists()) {
FancyNpcs.getInstance().getFancyLogger().error("File does not exist: " + filePath);
return null;
throw new SkinLoadException(SkinLoadException.Reason.INVALID_FILE, "(FILE = '" + filePath + "')");
}
GenerateRequest genReq = GenerateRequest.upload(file);

View File

@@ -3,7 +3,7 @@ package de.oliver.fancynpcs.skins;
public class SkinUtils {
public static boolean isPlaceholder(String identifier) {
return identifier.startsWith("%") && identifier.endsWith("%") || identifier.startsWith("{") && identifier.endsWith("}");
return (identifier.startsWith("%") && identifier.endsWith("%")) || (identifier.startsWith("{") && identifier.endsWith("}"));
}
public static boolean isUUID(String identifier) {
@@ -11,11 +11,11 @@ public class SkinUtils {
}
public static boolean isURL(String identifier) {
return identifier.startsWith("http");
return identifier.startsWith("http://") || identifier.startsWith("https://");
}
public static boolean isFile(String identifier) {
return identifier.endsWith(".png") || identifier.endsWith(".jpg") || identifier.endsWith(".jpeg");
return !isURL(identifier) && identifier.split("\\.").length > 1;
}
public static boolean isUsername(String identifier) {

View File

@@ -316,7 +316,20 @@ messages:
npc_skin_set: "<dark_gray> <gray>NPC {warningColor}{npc}<gray> is now using the {warningColor}{name}<gray> skin."
npc_skin_set_mirror: "<dark_gray> <gray>NPC {warningColor}{npc}<gray> is now mirroring player skin."
npc_skin_set_none: "<dark_gray> <gray>NPC {warningColor}{npc}<gray> is no longer using any skin."
npc_skin_set_later: "<dark_gray> <gray>NPC {warningColor}{npc}<gray> will be updated with the new skin shortly."
npc_skin_set_later: "<hover:show_text:'{warningColor}Still loading after more than 30 seconds?<newline><gray>Check console for potential errors and verify everything is correct. Try again in a brief moment...'><dark_gray> <gray>Skin should be applied in a few seconds... <#848484></hover>"
npc_skin_failure_invalid_url:
- "<click:open_url:'https://docs.fancyplugins.de/fancynpcs/faq/#urls-as-skin'><hover:show_text:'{warningColor}What does that mean?<newline><gray>Click here to find out more about this message.'><dark_gray> {errorColor}Could not load skin from the specified URL."
- "<dark_gray> <gray>Ensure that the URL points directly to a texture file. <#848484></hover></click>"
npc_skin_failure_invalid_file:
- "<dark_gray> {errorColor}Could not load skin from the specified file."
- "<dark_gray> <gray>Ensure that the file at specified path exists."
npc_skin_failure_invalid_username:
- "<dark_gray> {errorColor}Could not load skin from the specified username."
- "<dark_gray> <gray>Ensure that the username is a valid Minecraft account."
npc_skin_failure_invalid_placeholder:
- "<click:open_url:'https://docs.fancyplugins.de/fancynpcs/faq/#placeholders-as-skin'><hover:show_text:'{warningColor}What does that mean?<newline><gray>Click here to find out more about this message.'><dark_gray> {errorColor}Could not load skin from the specified placeholder."
- "<dark_gray> <gray>Ensure that the placeholder does not require"
- "<dark_gray> <gray>a player instance to be parsed. <#848484></hover>"
# Commands (npc teleport)
npc_teleport_success: "<dark_gray> <gray>You have been teleported to NPC {warningColor}{npc}<gray>."