Skip to content

Commit 2e8f2fb

Browse files
authored
fancynpcs: handle skin errors gracefully (#23)
1 parent 0d52abc commit 2e8f2fb

6 files changed

Lines changed: 92 additions & 27 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package de.oliver.fancynpcs.api.skins;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
/**
6+
* Exception that is thrown when a skin cannot be fetched or loaded.
7+
*/
8+
public final class SkinLoadException extends RuntimeException {
9+
10+
private final @NotNull SkinLoadException.Reason reason;
11+
12+
public SkinLoadException(final @NotNull SkinLoadException.Reason reason, final @NotNull String message) {
13+
super(message);
14+
this.reason = reason;
15+
}
16+
17+
/**
18+
* Returns the reason why the skin could not be loaded.
19+
*
20+
* @return the reason
21+
*/
22+
public @NotNull SkinLoadException.Reason getReason() {
23+
return reason;
24+
}
25+
26+
public enum Reason {
27+
INVALID_URL, INVALID_FILE, INVALID_PLACEHOLDER, INVALID_USERNAME
28+
}
29+
30+
}

plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/NpcManagerImpl.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import de.oliver.fancynpcs.api.actions.NpcAction;
1111
import de.oliver.fancynpcs.api.events.NpcsLoadedEvent;
1212
import de.oliver.fancynpcs.api.skins.SkinData;
13+
import de.oliver.fancynpcs.api.skins.SkinLoadException;
1314
import de.oliver.fancynpcs.api.utils.NpcEquipmentSlot;
1415
import net.kyori.adventure.text.format.NamedTextColor;
1516
import org.bukkit.Bukkit;
@@ -275,8 +276,13 @@ public void loadNpcs() {
275276
String skinVariantStr = npcConfig.getString("npcs." + id + ".skin.variant", SkinData.SkinVariant.AUTO.name());
276277
SkinData.SkinVariant skinVariant = SkinData.SkinVariant.valueOf(skinVariantStr);
277278
if (!skinIdentifier.isEmpty()) {
278-
skin = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skinIdentifier, skinVariant);
279-
skin.setIdentifier(skinIdentifier);
279+
try {
280+
skin = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skinIdentifier, skinVariant);
281+
skin.setIdentifier(skinIdentifier);
282+
} catch (final SkinLoadException e) {
283+
logger.error("NPC named '" + name + "' identified by '" + id + "' could not have their skin loaded.");
284+
logger.error(" " + e.getReason() + " " + e.getMessage());
285+
}
280286
}
281287

282288

plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/SkinCMD.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import de.oliver.fancynpcs.api.Npc;
66
import de.oliver.fancynpcs.api.events.NpcModifyEvent;
77
import de.oliver.fancynpcs.api.skins.SkinData;
8+
import de.oliver.fancynpcs.api.skins.SkinLoadException;
9+
import de.oliver.fancynpcs.skins.SkinUtils;
810
import org.bukkit.Bukkit;
911
import org.bukkit.command.CommandSender;
1012
import org.bukkit.entity.EntityType;
@@ -18,7 +20,9 @@
1820
import org.incendo.cloud.context.CommandInput;
1921
import org.jetbrains.annotations.NotNull;
2022

23+
import java.io.File;
2124
import java.util.ArrayList;
25+
import java.util.Arrays;
2226
import java.util.List;
2327

2428
public enum SkinCMD {
@@ -64,27 +68,35 @@ public void onSkin(
6468
} else {
6569
translator.translate("command_npc_modification_cancelled").send(sender);
6670
}
67-
} else {
71+
} else try {
6872
SkinData.SkinVariant variant = slim ? SkinData.SkinVariant.SLIM : SkinData.SkinVariant.AUTO;
6973
SkinData skinData = FancyNpcs.getInstance().getSkinManagerImpl().getByIdentifier(skin, variant);
7074
skinData.setIdentifier(skin);
71-
if (!skinData.hasTexture()) {
72-
translator.translate("npc_skin_set_later").replace("npc", npc.getData().getName()).send(sender);
73-
}
7475

7576
if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, false, sender).callEvent() && new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.SKIN, skinData, sender).callEvent()) {
77+
translator.translate("npc_skin_set")
78+
.replace("npc", npc.getData().getName())
79+
.replace("name", skinData.getIdentifier())
80+
.send(sender);
81+
if (!skinData.hasTexture()) {
82+
translator.translate("npc_skin_set_later").replace("npc", npc.getData().getName()).send(sender);
83+
}
7684
npc.getData().setMirrorSkin(false);
7785
npc.getData().setSkinData(skinData);
7886
npc.removeForAll();
7987
npc.create();
8088
npc.spawnForAll();
81-
translator.translate("npc_skin_set")
82-
.replace("npc", npc.getData().getName())
83-
.replace("name", skinData.getIdentifier())
84-
.send(sender);
89+
8590
} else {
8691
translator.translate("command_npc_modification_cancelled").send(sender);
8792
}
93+
} catch (final SkinLoadException e) {
94+
switch (e.getReason()) {
95+
case INVALID_URL -> translator.translate("npc_skin_failure_invalid_url").replace("npc", npc.getData().getName()).send(sender);
96+
case INVALID_FILE -> translator.translate("npc_skin_failure_invalid_file").replace("npc", npc.getData().getName()).send(sender);
97+
case INVALID_USERNAME -> translator.translate("npc_skin_failure_invalid_username").replace("npc", npc.getData().getName()).send(sender);
98+
case INVALID_PLACEHOLDER -> translator.translate("npc_skin_failure_invalid_placeholder").replace("npc", npc.getData().getName()).send(sender);
99+
}
88100
}
89101
}
90102

@@ -96,6 +108,11 @@ public List<String> suggestSkin(final CommandContext<CommandSender> context, fin
96108
add("@none");
97109
add("@mirror");
98110
Bukkit.getOnlinePlayers().stream().map(Player::getName).forEach(this::add);
111+
// Adding file names inside 'plugins/FancyNpcs/skins' to the list of completions.
112+
final File[] files = new File(FancyNpcs.getInstance().getDataFolder(), "skins").listFiles();
113+
if (files != null) {
114+
Arrays.stream(files).map(File::getName).filter(SkinUtils::isFile).forEach(this::add);
115+
}
99116
}};
100117
}
101118

plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/skins/SkinManagerImpl.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import de.oliver.fancynpcs.api.Npc;
77
import de.oliver.fancynpcs.api.skins.SkinData;
88
import de.oliver.fancynpcs.api.skins.SkinGeneratedEvent;
9+
import de.oliver.fancynpcs.api.skins.SkinLoadException;
910
import de.oliver.fancynpcs.api.skins.SkinManager;
1011
import de.oliver.fancynpcs.skins.cache.SkinCache;
1112
import de.oliver.fancynpcs.skins.cache.SkinCacheData;
@@ -48,7 +49,7 @@ public SkinManagerImpl(SkinCache fileCache, SkinCache memCache) {
4849
}
4950

5051
@Override
51-
public SkinData getByIdentifier(String identifier, SkinData.SkinVariant variant) {
52+
public SkinData getByIdentifier(String identifier, SkinData.SkinVariant variant) throws SkinLoadException {
5253
if (SkinUtils.isUUID(identifier)) {
5354
return getByUUID(UUID.fromString(identifier), variant);
5455
}
@@ -64,8 +65,8 @@ public SkinData getByIdentifier(String identifier, SkinData.SkinVariant variant)
6465
if (SkinUtils.isPlaceholder(identifier)) {
6566
String parsed = ChatColorHandler.translate(identifier);
6667

67-
if (SkinUtils.isPlaceholder(parsed)) {
68-
return null;
68+
if (parsed.isBlank() || parsed.equalsIgnoreCase("null") || SkinUtils.isPlaceholder(parsed)) {
69+
throw new SkinLoadException(SkinLoadException.Reason.INVALID_PLACEHOLDER, "(RAW = '" + identifier + "'; PARSED = '" + parsed + "')");
6970
}
7071

7172
return getByIdentifier(parsed, variant);
@@ -74,7 +75,7 @@ public SkinData getByIdentifier(String identifier, SkinData.SkinVariant variant)
7475
// is username
7576
UUID uuid = UUIDFetcher.getUUID(identifier);
7677
if (uuid == null) {
77-
return null;
78+
throw new SkinLoadException(SkinLoadException.Reason.INVALID_USERNAME, "(USERNAME = '" + identifier + "')");
7879
}
7980

8081
return getByUUID(uuid, variant);
@@ -96,17 +97,17 @@ public SkinData getByUUID(UUID uuid, SkinData.SkinVariant variant) {
9697
}
9798

9899
@Override
99-
public SkinData getByUsername(String username, SkinData.SkinVariant variant) {
100+
public SkinData getByUsername(String username, SkinData.SkinVariant variant) throws SkinLoadException {
100101
UUID uuid = UUIDFetcher.getUUID(username);
101102
if (uuid == null) {
102-
return null;
103+
throw new SkinLoadException(SkinLoadException.Reason.INVALID_USERNAME, "(USERNAME = '" + username + "')");
103104
}
104105

105106
return getByUUID(uuid, variant);
106107
}
107108

108109
@Override
109-
public SkinData getByURL(String url, SkinData.SkinVariant variant) {
110+
public SkinData getByURL(String url, SkinData.SkinVariant variant) throws SkinLoadException {
110111
SkinData cached = tryToGetFromCache(url, variant);
111112
if (cached != null) {
112113
return cached;
@@ -115,26 +116,24 @@ public SkinData getByURL(String url, SkinData.SkinVariant variant) {
115116
GenerateRequest genReq;
116117
try {
117118
genReq = GenerateRequest.url(url);
118-
} catch (MalformedURLException e) {
119-
FancyNpcs.getInstance().getFancyLogger().error("Invalid URL: " + url);
120-
return null;
119+
} catch (final IllegalArgumentException | MalformedURLException e) {
120+
throw new SkinLoadException(SkinLoadException.Reason.INVALID_URL, "(URL = '" + url + "')");
121121
}
122122
genReq.variant(Variant.valueOf(variant.name()));
123123
MineSkinQueue.get().add(new MineSkinQueue.SkinRequest(url, genReq));
124124
return new SkinData(url, variant);
125125
}
126126

127127
@Override
128-
public SkinData getByFile(String filePath, SkinData.SkinVariant variant) {
128+
public SkinData getByFile(String filePath, SkinData.SkinVariant variant) throws SkinLoadException {
129129
SkinData cached = tryToGetFromCache(filePath, variant);
130130
if (cached != null) {
131131
return cached;
132132
}
133133

134134
File file = new File(SKINS_DIRECTORY + filePath);
135135
if (!file.exists()) {
136-
FancyNpcs.getInstance().getFancyLogger().error("File does not exist: " + filePath);
137-
return null;
136+
throw new SkinLoadException(SkinLoadException.Reason.INVALID_FILE, "(FILE = '" + filePath + "')");
138137
}
139138

140139
GenerateRequest genReq = GenerateRequest.upload(file);

plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/skins/SkinUtils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
public class SkinUtils {
44

55
public static boolean isPlaceholder(String identifier) {
6-
return identifier.startsWith("%") && identifier.endsWith("%") || identifier.startsWith("{") && identifier.endsWith("}");
6+
return (identifier.startsWith("%") && identifier.endsWith("%")) || (identifier.startsWith("{") && identifier.endsWith("}"));
77
}
88

99
public static boolean isUUID(String identifier) {
1010
return identifier.length() == 36 && identifier.contains("-");
1111
}
1212

1313
public static boolean isURL(String identifier) {
14-
return identifier.startsWith("http");
14+
return identifier.startsWith("http://") || identifier.startsWith("https://");
1515
}
1616

1717
public static boolean isFile(String identifier) {
18-
return identifier.endsWith(".png") || identifier.endsWith(".jpg") || identifier.endsWith(".jpeg");
18+
return !isURL(identifier) && identifier.split("\\.").length > 1;
1919
}
2020

2121
public static boolean isUsername(String identifier) {

plugins/fancynpcs/src/main/resources/languages/default.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,20 @@ messages:
316316
npc_skin_set: "<dark_gray>› <gray>NPC {warningColor}{npc}<gray> is now using the {warningColor}{name}<gray> skin."
317317
npc_skin_set_mirror: "<dark_gray>› <gray>NPC {warningColor}{npc}<gray> is now mirroring player skin."
318318
npc_skin_set_none: "<dark_gray>› <gray>NPC {warningColor}{npc}<gray> is no longer using any skin."
319-
npc_skin_set_later: "<dark_gray>› <gray>NPC {warningColor}{npc}<gray> will be updated with the new skin shortly."
319+
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>"
320+
npc_skin_failure_invalid_url:
321+
- "<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."
322+
- "<dark_gray>› <gray>Ensure that the URL points directly to a texture file. <#848484>ℹ</hover></click>"
323+
npc_skin_failure_invalid_file:
324+
- "<dark_gray>› {errorColor}Could not load skin from the specified file."
325+
- "<dark_gray>› <gray>Ensure that the file at specified path exists."
326+
npc_skin_failure_invalid_username:
327+
- "<dark_gray>› {errorColor}Could not load skin from the specified username."
328+
- "<dark_gray>› <gray>Ensure that the username is a valid Minecraft account."
329+
npc_skin_failure_invalid_placeholder:
330+
- "<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."
331+
- "<dark_gray>› <gray>Ensure that the placeholder does not require"
332+
- "<dark_gray>› <gray>a player instance to be parsed. <#848484>ℹ</hover>"
320333

321334
# Commands (npc teleport)
322335
npc_teleport_success: "<dark_gray>› <gray>You have been teleported to NPC {warningColor}{npc}<gray>."

0 commit comments

Comments
 (0)