From 00b987bb069c0756e9093f0f1a2622a5de0a3eb2 Mon Sep 17 00:00:00 2001 From: mdaffa48 Date: Sun, 18 Oct 2026 10:09:57 +0800 Subject: [PATCH 1/2] fix: load the configured hologram provider before fallbacks --- .github/workflows/build-test-jar.yml | 9 +- docs/holograms.md | 40 +++++ scripts/HologramProviderCheck.java | 162 ++++++++++++++++++ .../nextgens/hologram/HologramManager.java | 19 +- 4 files changed, 220 insertions(+), 10 deletions(-) create mode 100644 docs/holograms.md create mode 100644 scripts/HologramProviderCheck.java diff --git a/.github/workflows/build-test-jar.yml b/.github/workflows/build-test-jar.yml index f466867..e3629f6 100644 --- a/.github/workflows/build-test-jar.yml +++ b/.github/workflows/build-test-jar.yml @@ -21,15 +21,18 @@ jobs: - name: Check out repository uses: actions/checkout@v7 - - name: Set up Java 21 + - name: Set up Java 25 uses: actions/setup-java@v6 with: distribution: temurin - java-version: "21" + java-version: "25" cache: maven - name: Build and verify - run: bash ./mvnw -B -ntp verify + run: bash ./mvnw -B -ntp clean verify + + - name: Check optional hologram provider selection + run: java scripts/HologramProviderCheck.java - name: Select distributable JAR shell: bash diff --git a/docs/holograms.md b/docs/holograms.md new file mode 100644 index 0000000..c842ea6 --- /dev/null +++ b/docs/holograms.md @@ -0,0 +1,40 @@ +# Generator holograms + +FancyHolograms is optional. DecentHolograms works on its own. To select it, +set this in `config.yml` and restart the server: + +```yaml +holograms: + type: DecentHolograms +``` + +NextGens first loads the configured provider. If it is unavailable, NextGens +tries other installed, enabled providers. With no usable provider, generator +holograms are disabled and a warning is logged. + +Supported configuration values are `DecentHolograms`, `FancyHologram` (also +`FancyHolograms`), and `HolographicDisplays`. The FancyHolograms integration +targets the 2.x API (compiled against 2.9.1). FancyHolograms 3 is not supported +by this integration; use DecentHolograms or a compatible FancyHolograms 2.x +release for your server. Only the selected provider needs to be installed. + +The `NoClassDefFoundError: de/oliver/fancyholograms/api/data/HologramData` +report in issue #16 concerns NextGens 1.3.3. Current source isolates optional +provider classes and skips providers that fail to load. The issue #16 follow-up +also avoids loading unselected providers while the configured one is usable. +Check your server's NextGens version and the selected provider before installing +additional hologram plugins. Current source requires Java 25; upgrading from an +older binary also requires checking server and Java compatibility. + +Run the dependency-free selection regression check with a JDK: + +```sh +java scripts/HologramProviderCheck.java +``` + +This check compiles the real manager and provider interface against small API +substitutes to exercise selection, fallback, and missing dependency behavior. +It does not run a Minecraft server or prove hologram rendering. Before merging, +test the built JAR on a separate server with only DecentHolograms installed, +trigger a generator corruption hologram, repair the generator, and confirm the +hologram is removed without any FancyHolograms class errors. diff --git a/scripts/HologramProviderCheck.java b/scripts/HologramProviderCheck.java new file mode 100644 index 0000000..c4c0f47 --- /dev/null +++ b/scripts/HologramProviderCheck.java @@ -0,0 +1,162 @@ +import javax.tools.ToolProvider; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class HologramProviderCheck { + + public static void main(String[] args) throws Exception { + Path output = Files.createTempDirectory(Files.createDirectories(Path.of("target")), "hologram-check-"); + List sources = new ArrayList<>(); + Map substitutes = Map.of( + "org.bukkit.Bukkit", """ + package org.bukkit; + public class Bukkit { + public static final org.bukkit.plugin.PluginManager manager = new org.bukkit.plugin.PluginManager(); + public static org.bukkit.plugin.PluginManager getPluginManager() { return manager; } + } + """, + "org.bukkit.plugin.PluginManager", """ + package org.bukkit.plugin; + public class PluginManager { + public final java.util.Set enabled = new java.util.HashSet<>(); + public boolean isPluginEnabled(String name) { return enabled.contains(name); } + public void disablePlugins() { throw new AssertionError("Must not disable other plugins"); } + } + """, + "org.bukkit.Location", "package org.bukkit; public class Location {}", + "org.bukkit.configuration.file.FileConfiguration", """ + package org.bukkit.configuration.file; + public class FileConfiguration { + public String type; + public String getString(String key) { return type; } + } + """, + "com.muhammaddaffa.mdlib.utils.Config", """ + package com.muhammaddaffa.mdlib.utils; + public class Config { + private final org.bukkit.configuration.file.FileConfiguration config = new org.bukkit.configuration.file.FileConfiguration(); + public org.bukkit.configuration.file.FileConfiguration getConfig() { return config; } + } + """, + "com.muhammaddaffa.mdlib.utils.Logger", """ + package com.muhammaddaffa.mdlib.utils; + public class Logger { + public static final java.util.List warnings = new java.util.ArrayList<>(); + public static void info(String message) {} + public static void warning(String message) { warnings.add(message); } + } + """, + "com.muhammaddaffa.nextgens.NextGens", """ + package com.muhammaddaffa.nextgens; + public class NextGens { + public static final com.muhammaddaffa.mdlib.utils.Config DEFAULT_CONFIG = new com.muhammaddaffa.mdlib.utils.Config(); + } + """, + "Check", """ + import com.muhammaddaffa.nextgens.NextGens; + import com.muhammaddaffa.nextgens.hologram.HologramManager; + import com.muhammaddaffa.mdlib.utils.Logger; + import org.bukkit.Bukkit; + public class Check { + public static final java.util.List attempted = new java.util.ArrayList<>(); + public static String broken; + public static void main(String[] args) { + NextGens.DEFAULT_CONFIG.getConfig().type = args[0].equals("null") ? null : args[0]; + Bukkit.manager.enabled.addAll(java.util.List.of(args[1].split(","))); + broken = args[2]; + if (args[0].equals("Custom")) { + HologramManager.register(new com.muhammaddaffa.nextgens.hologram.HologramProvider() { + public String id() { return "Custom"; } + public String pluginName() { return "Custom"; } + public void spawn(String name, org.bukkit.Location location, java.util.List lines) {} + public void destroy(String name, org.bukkit.Location location) {} + }); + } + HologramManager manager = new HologramManager(); + manager.load(); + String actual = manager.isEnabled() ? manager.getProvider().id() : "none"; + if (!actual.equals(args[3])) { + throw new AssertionError("Expected " + args[3] + ", got " + actual); + } + String attempts = String.join(",", attempted); + if (!attempts.equals(args[4])) { + throw new AssertionError("Unexpected provider loading: " + attempts); + } + boolean skipped = Logger.warnings.stream().anyMatch(line -> line.contains("skipping it")); + if (skipped != Boolean.parseBoolean(args[5])) { + throw new AssertionError("Unexpected compatibility warnings: " + Logger.warnings); + } + } + } + """ + ); + for (var entry : substitutes.entrySet()) { + sources.add(write(output, entry.getKey(), entry.getValue())); + } + for (String[] provider : List.of( + new String[]{"DecentHologramsProvider", "DecentHolograms", "DecentHolograms"}, + new String[]{"FancyHologramProvider", "FancyHologram", "FancyHolograms"}, + new String[]{"HolographicDisplaysProvider", "HolographicDisplays", "HolographicDisplays"})) { + String source = """ + package com.muhammaddaffa.nextgens.hologram.imp; + import com.muhammaddaffa.nextgens.hologram.HologramProvider; + public class %s implements HologramProvider { + public %s() { + try { + Class check = Class.forName("Check"); + ((java.util.List) check.getField("attempted").get(null)).add("%s"); + if ("%s".equals(check.getField("broken").get(null))) { + throw new NoClassDefFoundError("missing/provider/API"); + } + } catch (ReflectiveOperationException ex) { throw new AssertionError(ex); } + } + public String id() { return "%s"; } + public String pluginName() { return "%s"; } + public void spawn(String name, org.bukkit.Location location, java.util.List lines) {} + public void destroy(String name, org.bukkit.Location location) {} + } + """.formatted(provider[0], provider[0], provider[2], provider[2], provider[1], provider[2]); + sources.add(write(output, "com.muhammaddaffa.nextgens.hologram.imp." + provider[0], source)); + } + sources.add("src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java"); + sources.add("src/main/java/com/muhammaddaffa/nextgens/hologram/HologramProvider.java"); + List compilerArgs = new ArrayList<>(List.of("-d", output.toString())); + compilerArgs.addAll(sources); + int result = ToolProvider.getSystemJavaCompiler().run(null, null, null, compilerArgs.toArray(String[]::new)); + if (result != 0) { + throw new AssertionError("Regression check compilation failed: " + result); + } + String[][] cases = { + {"DecentHolograms", "DecentHolograms,FancyHolograms", "FancyHolograms", "DecentHolograms", "DecentHolograms", "false"}, + {"DecentHolograms", "DecentHolograms", "", "DecentHolograms", "DecentHolograms", "false"}, + {"FancyHologram", "FancyHolograms,DecentHolograms", "", "FancyHologram", "FancyHolograms", "false"}, + {"fancyholograms", "FancyHolograms", "", "FancyHologram", "FancyHolograms", "false"}, + {"FancyHolograms", "FancyHolograms,DecentHolograms", "FancyHolograms", "DecentHolograms", "FancyHolograms,DecentHolograms", "true"}, + {"FancyHologram", "DecentHolograms", "", "DecentHolograms", "DecentHolograms", "false"}, + {"null", "DecentHolograms", "", "DecentHolograms", "DecentHolograms", "false"}, + {"", "HolographicDisplays", "", "HolographicDisplays", "HolographicDisplays", "false"}, + {"DecentHolograms", "", "", "none", "", "false"}, + {"FancyHologram", "FancyHolograms", "FancyHolograms", "none", "FancyHolograms", "true"}, + {"Custom", "Custom,DecentHolograms,FancyHolograms", "FancyHolograms", "Custom", "", "false"}, + {"Unknown", "DecentHolograms", "", "DecentHolograms", "DecentHolograms", "false"} + }; + for (String[] scenario : cases) { + try (URLClassLoader loader = new URLClassLoader(new URL[]{output.toUri().toURL()}, ClassLoader.getPlatformClassLoader())) { + loader.loadClass("Check").getMethod("main", String[].class).invoke(null, (Object) scenario); + } + } + System.out.println("Passed " + cases.length + " hologram provider selection checks."); + } + + private static String write(Path output, String name, String source) throws Exception { + Path file = output.resolve(name.replace('.', '/') + ".java"); + Files.createDirectories(file.getParent()); + Files.writeString(file, source); + return file.toString(); + } +} diff --git a/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java b/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java index ac89d51..47aee9c 100644 --- a/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java +++ b/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java @@ -28,12 +28,12 @@ public class HologramManager { * right here in the static initializer, which is exactly what we are avoiding. */ private static final List BUILTIN_PROVIDERS = List.of( - new BuiltinProvider("DecentHolograms", () -> new DecentHologramsProvider()), - new BuiltinProvider("FancyHolograms", () -> new FancyHologramProvider()), - new BuiltinProvider("HolographicDisplays", () -> new HolographicDisplaysProvider()) + new BuiltinProvider("DecentHolograms", "DecentHolograms", () -> new DecentHologramsProvider()), + new BuiltinProvider("FancyHologram", "FancyHolograms", () -> new FancyHologramProvider()), + new BuiltinProvider("HolographicDisplays", "HolographicDisplays", () -> new HolographicDisplaysProvider()) ); - private record BuiltinProvider(String pluginName, Supplier factory) { + private record BuiltinProvider(String id, String pluginName, Supplier factory) { } public static void register(HologramProvider hologramProvider) { @@ -67,8 +67,13 @@ public static boolean unregister(String id) { * A provider that fails to load (e.g. an incompatible plugin version) is skipped instead of * taking the whole plugin down with it. */ - private static void registerBuiltinProviders() { + private static void registerBuiltinProviders(String configId, boolean fallback) { for (BuiltinProvider builtin : BUILTIN_PROVIDERS) { + boolean configured = builtin.id().equalsIgnoreCase(configId) + || builtin.pluginName().equalsIgnoreCase(configId); + if (configured == fallback) { + continue; + } if (!Bukkit.getPluginManager().isPluginEnabled(builtin.pluginName()) || isRegistered(builtin.pluginName())) { continue; } @@ -91,13 +96,12 @@ private static boolean isRegistered(String pluginName) { } public void load() { - registerBuiltinProviders(); - FileConfiguration config = NextGens.DEFAULT_CONFIG.getConfig(); String configId = config.getString("holograms.type"); // 1. Try to honour the configured provider first. if (configId != null && !configId.isBlank()) { + registerBuiltinProviders(configId, false); HologramProvider configured = findByConfigId(configId); if (configured != null && configured.isAvailable()) { this.provider = configured; @@ -110,6 +114,7 @@ public void load() { } // 2. Fallback: use the first registered provider whose plugin is installed. + registerBuiltinProviders(configId, true); for (HologramProvider candidate : HOLOGRAM_PROVIDER_LIST.values()) { if (candidate.isAvailable()) { this.provider = candidate; From b72cb9b620a1c2e4e507d864de40caedbebc69af Mon Sep 17 00:00:00 2001 From: mdaffa48 Date: Sun, 18 Oct 2026 10:24:20 +0800 Subject: [PATCH 2/2] fix: fall back to the first loaded hologram provider safely --- docs/holograms.md | 29 +++--- scripts/HologramProviderCheck.java | 58 +++++++++-- .../nextgens/hologram/HologramManager.java | 99 +++++++++++-------- src/main/resources/config.yml | 5 +- 4 files changed, 131 insertions(+), 60 deletions(-) diff --git a/docs/holograms.md b/docs/holograms.md index c842ea6..5ed38af 100644 --- a/docs/holograms.md +++ b/docs/holograms.md @@ -8,15 +8,22 @@ holograms: type: DecentHolograms ``` -NextGens first loads the configured provider. If it is unavailable, NextGens -tries other installed, enabled providers. With no usable provider, generator -holograms are disabled and a warning is logged. +NextGens uses the configured provider when its plugin is installed and enabled. +Otherwise it uses the first available registered provider. Built-in providers +are discovered in this order: DecentHolograms, FancyHolograms, HolographicDisplays. +Selection stops at the first usable provider instead of loading the rest. +Missing or broken providers are skipped. If none is available, NextGens logs a +warning and continues running with generator holograms disabled. Provider +registration never disables NextGens or other server plugins. Supported configuration values are `DecentHolograms`, `FancyHologram` (also `FancyHolograms`), and `HolographicDisplays`. The FancyHolograms integration -targets the 2.x API (compiled against 2.9.1). FancyHolograms 3 is not supported -by this integration; use DecentHolograms or a compatible FancyHolograms 2.x -release for your server. Only the selected provider needs to be installed. +uses `de.oliver:FancyHolograms:2.9.1`, the version shown in the +[official API getting-started guide](https://fancyinnovations.com/docs/minecraft-plugins/fancyholograms/api/getting-started) +when checked on October 18, 2026. No dependency upgrade is needed. Only the +selected provider needs to be installed. Diagnose a missing API class using +the actual loaded plugin and API, rather than inferring compatibility from a +plugin version string alone. The `NoClassDefFoundError: de/oliver/fancyholograms/api/data/HologramData` report in issue #16 concerns NextGens 1.3.3. Current source isolates optional @@ -33,8 +40,8 @@ java scripts/HologramProviderCheck.java ``` This check compiles the real manager and provider interface against small API -substitutes to exercise selection, fallback, and missing dependency behavior. -It does not run a Minecraft server or prove hologram rendering. Before merging, -test the built JAR on a separate server with only DecentHolograms installed, -trigger a generator corruption hologram, repair the generator, and confirm the -hologram is removed without any FancyHolograms class errors. +substitutes to exercise selection, deterministic fallback, missing dependency +behavior, and nonfatal registration. A clean verification build, the selection +regression checks, and GitHub CI are the acceptance checks for this change; +in-game testing is not required. These checks do not claim live rendering was +tested. diff --git a/scripts/HologramProviderCheck.java b/scripts/HologramProviderCheck.java index c4c0f47..dc193e3 100644 --- a/scripts/HologramProviderCheck.java +++ b/scripts/HologramProviderCheck.java @@ -60,6 +60,7 @@ public class NextGens { "Check", """ import com.muhammaddaffa.nextgens.NextGens; import com.muhammaddaffa.nextgens.hologram.HologramManager; + import com.muhammaddaffa.nextgens.hologram.HologramProvider; import com.muhammaddaffa.mdlib.utils.Logger; import org.bukkit.Bukkit; public class Check { @@ -70,12 +71,26 @@ public static void main(String[] args) { Bukkit.manager.enabled.addAll(java.util.List.of(args[1].split(","))); broken = args[2]; if (args[0].equals("Custom")) { - HologramManager.register(new com.muhammaddaffa.nextgens.hologram.HologramProvider() { - public String id() { return "Custom"; } - public String pluginName() { return "Custom"; } - public void spawn(String name, org.bukkit.Location location, java.util.List lines) {} - public void destroy(String name, org.bukkit.Location location) {} - }); + HologramManager.register(custom("Custom", "Custom", false)); + } + if (args.length > 6) { + switch (args[6]) { + case "invalid" -> { + HologramManager.register(null); + HologramManager.register(custom(null, "Custom", false)); + HologramManager.register(custom("", "Custom", false)); + HologramManager.register(custom("Custom", "Other", false)); + } + case "order" -> { + HologramManager.register(custom("ZProvider", "ZProvider", false)); + HologramManager.register(custom("AProvider", "AProvider", false)); + } + case "reverse" -> { + HologramManager.register(custom("AProvider", "AProvider", false)); + HologramManager.register(custom("ZProvider", "ZProvider", false)); + } + case "broken" -> HologramManager.register(custom("Broken", "Broken", true)); + } } HologramManager manager = new HologramManager(); manager.load(); @@ -83,6 +98,9 @@ public void destroy(String name, org.bukkit.Location location) {} if (!actual.equals(args[3])) { throw new AssertionError("Expected " + args[3] + ", got " + actual); } + if (actual.equals("Custom") && !manager.getProvider().pluginName().equals("Custom")) { + throw new AssertionError("Duplicate registration replaced the original provider"); + } String attempts = String.join(",", attempted); if (!attempts.equals(args[4])) { throw new AssertionError("Unexpected provider loading: " + attempts); @@ -91,6 +109,23 @@ public void destroy(String name, org.bukkit.Location location) {} if (skipped != Boolean.parseBoolean(args[5])) { throw new AssertionError("Unexpected compatibility warnings: " + Logger.warnings); } + if (actual.equals("none") && Logger.warnings.stream().noneMatch(line -> line.contains("No supported hologram"))) { + throw new AssertionError("No-provider warning missing"); + } + } + private static HologramProvider custom(String id, String plugin, boolean broken) { + return new HologramProvider() { + public String id() { return id; } + public String pluginName() { return plugin; } + public boolean isAvailable() { + if (broken) { + throw new NoClassDefFoundError("missing/custom/API"); + } + return HologramProvider.super.isAvailable(); + } + public void spawn(String name, org.bukkit.Location location, java.util.List lines) {} + public void destroy(String name, org.bukkit.Location location) {} + }; } } """ @@ -143,7 +178,16 @@ public void destroy(String name, org.bukkit.Location location) {} {"DecentHolograms", "", "", "none", "", "false"}, {"FancyHologram", "FancyHolograms", "FancyHolograms", "none", "FancyHolograms", "true"}, {"Custom", "Custom,DecentHolograms,FancyHolograms", "FancyHolograms", "Custom", "", "false"}, - {"Unknown", "DecentHolograms", "", "DecentHolograms", "DecentHolograms", "false"} + {"Unknown", "DecentHolograms", "", "DecentHolograms", "DecentHolograms", "false"}, + {"", "DecentHolograms,FancyHolograms,HolographicDisplays", "FancyHolograms", "DecentHolograms", "DecentHolograms", "false"}, + {"Unknown", "FancyHolograms,HolographicDisplays", "", "FancyHologram", "FancyHolograms", "false"}, + {"Unknown", "DecentHolograms,FancyHolograms,HolographicDisplays", "DecentHolograms", "FancyHologram", "DecentHolograms,FancyHolograms", "true"}, + {"null", "", "", "none", "", "false"}, + {"HolographicDisplays", "DecentHolograms,FancyHolograms", "", "DecentHolograms", "DecentHolograms", "false"}, + {"Custom", "Custom,Other,DecentHolograms", "", "Custom", "", "false", "invalid"}, + {"Unknown", "ZProvider,AProvider,DecentHolograms", "", "ZProvider", "", "false", "order"}, + {"Unknown", "ZProvider,AProvider,DecentHolograms", "", "AProvider", "", "false", "reverse"}, + {"Broken", "Broken,DecentHolograms", "", "DecentHolograms", "DecentHolograms", "true", "broken"} }; for (String[] scenario : cases) { try (URLClassLoader loader = new URLClassLoader(new URL[]{output.toUri().toURL()}, ClassLoader.getPlatformClassLoader())) { diff --git a/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java b/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java index 47aee9c..8dfd8ba 100644 --- a/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java +++ b/src/main/java/com/muhammaddaffa/nextgens/hologram/HologramManager.java @@ -8,17 +8,19 @@ import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; public class HologramManager { private HologramProvider provider; - private static final Map HOLOGRAM_PROVIDER_LIST = new ConcurrentHashMap<>(); + private static final Map HOLOGRAM_PROVIDER_LIST = + Collections.synchronizedMap(new LinkedHashMap<>()); /** * The providers shipped with the plugin. They are only instantiated once we know their backing @@ -38,21 +40,19 @@ private record BuiltinProvider(String id, String pluginName, Supplier registeredProviders() { + synchronized (HOLOGRAM_PROVIDER_LIST) { + return List.copyOf(HOLOGRAM_PROVIDER_LIST.values()); } - return false; } public void load() { @@ -101,9 +102,16 @@ public void load() { // 1. Try to honour the configured provider first. if (configId != null && !configId.isBlank()) { - registerBuiltinProviders(configId, false); HologramProvider configured = findByConfigId(configId); - if (configured != null && configured.isAvailable()) { + if (configured == null) { + for (BuiltinProvider builtin : BUILTIN_PROVIDERS) { + if (builtin.id().equalsIgnoreCase(configId) || builtin.pluginName().equalsIgnoreCase(configId)) { + configured = loadBuiltinProvider(builtin); + break; + } + } + } + if (configured != null && isAvailable(configured)) { this.provider = configured; Logger.info("Successfully hooked into hologram provider: " + configured.id()); return; @@ -114,9 +122,20 @@ public void load() { } // 2. Fallback: use the first registered provider whose plugin is installed. - registerBuiltinProviders(configId, true); - for (HologramProvider candidate : HOLOGRAM_PROVIDER_LIST.values()) { - if (candidate.isAvailable()) { + for (HologramProvider candidate : registeredProviders()) { + if (isAvailable(candidate)) { + this.provider = candidate; + Logger.info("Falling back to hologram provider: " + candidate.id()); + return; + } + } + + for (BuiltinProvider builtin : BUILTIN_PROVIDERS) { + if (builtin.id().equalsIgnoreCase(configId) || builtin.pluginName().equalsIgnoreCase(configId)) { + continue; + } + HologramProvider candidate = loadBuiltinProvider(builtin); + if (candidate != null) { this.provider = candidate; Logger.info("Falling back to hologram provider: " + candidate.id()); return; @@ -132,14 +151,14 @@ public void load() { * Finds a provider whose id() or backing plugin name matches the config value. * This lets the config accept either spelling (e.g. "FancyHologram" or "FancyHolograms"). */ - private HologramProvider findByConfigId(String configId) { + private static HologramProvider findByConfigId(String configId) { // fast path: direct match against the registered id HologramProvider byId = HOLOGRAM_PROVIDER_LIST.get(configId.toLowerCase(Locale.ROOT)); if (byId != null) { return byId; } // fallback: match against the backing plugin name - for (HologramProvider candidate : HOLOGRAM_PROVIDER_LIST.values()) { + for (HologramProvider candidate : registeredProviders()) { if (candidate.pluginName().equalsIgnoreCase(configId)) { return candidate; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 849f888..cc4fc29 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -86,8 +86,9 @@ holograms: # - DecentHolograms # - FancyHologram # - HolographicDisplays - # If the chosen plugin isn't installed (or this is left blank), NextGens will - # automatically fall back to any supported hologram plugin it can find. + # If the chosen plugin isn't loaded (or this is left blank), use the first + # available provider: DecentHolograms, FancyHolograms, HolographicDisplays. + # If none is available, warn and keep NextGens running without holograms. type: DecentHolograms # The options below ONLY apply when FancyHolograms is the active provider. fancyholograms: