diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/NachoConfigMigration.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/NachoConfigMigration.java new file mode 100644 index 000000000..9878d3060 --- /dev/null +++ b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/NachoConfigMigration.java @@ -0,0 +1,190 @@ +package com.windpvp.windspigot.config; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +/** + * Migrates overlapping NachoSpigot ({@code nacho.yml}) options into WindSpigot + * ({@code windspigot.yml}), including persistence of YAML keys — not only in-memory fields. + */ +public final class NachoConfigMigration { + + public static final String MIGRATED_FLAG_PATH = "settings.migrated-from-nacho"; + + /** + * Nacho public static field name → WindSpigot YAML path. + * Intentionally omits options removed from WindSpigot (ProtocolLib shim, item-stack decode, etc.). + */ + private static final Map FIELD_TO_WIND_PATH; + + /** Fields that exist on NachoConfig but must not be migrated (removed or infrastructure). */ + private static final Set SKIP_FIELDS; + + static { + Map paths = new LinkedHashMap(); + paths.put("saveEmptyScoreboardTeams", "settings.save-empty-scoreboard-teams"); + paths.put("enableVersionCommand", "settings.command.version"); + paths.put("enablePluginsCommand", "settings.command.plugins"); + paths.put("enableReloadCommand", "settings.command.reload"); + paths.put("useFastOperators", "settings.fast-operators"); + paths.put("stopNotifyBungee", "settings.stop-notify-bungee"); + paths.put("checkForMalware", "settings.anti-malware"); + paths.put("kickOnIllegalBehavior", "settings.kick-on-illegal-behavior"); + paths.put("usePandaWire", "settings.panda-wire"); + paths.put("fireEntityExplodeEvent", "settings.event.fire-entity-explode-event"); + paths.put("firePlayerMoveEvent", "settings.event.fire-player-move-event"); + paths.put("leavesDecayEvent", "settings.event.fire-leaf-decay-event"); + paths.put("serverBrandName", "settings.brand-name"); + paths.put("enableAntiCrash", "settings.anti-crash.enabled"); + paths.put("chunkThreads", "settings.chunk.threads"); + paths.put("playersPerThread", "settings.chunk.players-per-thread"); + paths.put("enableTCPNODELAY", "settings.use-tcp-nodelay"); + paths.put("fixedPoolSize", "settings.async.explosions.threads"); + paths.put("useFasterCannonTracker", "settings.faster-cannon-tracker"); + paths.put("fixEatWhileRunning", "settings.fix-eat-while-running"); + paths.put("hideProjectilesFromHiddenPlayers", "settings.hide-projectiles-from-hidden-players"); + paths.put("lagCompensatedPotions", "settings.lag-compensated-potions"); + paths.put("smoothPotting", "settings.smooth-potting"); + paths.put("antiEnderPearlGlitch", "settings.anti-enderpearl-glitch"); + paths.put("disabledFallBlockAnimation", "settings.disable-block-fall-animation"); + paths.put("disableInfiniSleeperThreadUsage", "settings.disable-infinisleeper-thread-usage"); + paths.put("enableTcpFastOpen", "settings.tcp-fast-open.enabled"); + paths.put("modeTcpFastOpen", "settings.tcp-fast-open.mode"); + paths.put("instantPlayInUseEntity", "settings.instant-interaction"); + FIELD_TO_WIND_PATH = Collections.unmodifiableMap(paths); + + Set skip = new LinkedHashSet(); + skip.add("CONFIG_FILE"); + skip.add("hasMigrated"); + // Removed from WindSpigot / no longer wired into runtime + skip.add("patchProtocolLib"); + skip.add("stopDecodingItemStackOnPlace"); + skip.add("itemDirtyTicks"); + skip.add("enableProtocolLibShim"); + SKIP_FIELDS = Collections.unmodifiableSet(skip); + } + + private NachoConfigMigration() { + } + + public static Map fieldToWindPath() { + return FIELD_TO_WIND_PATH; + } + + public static Set skipFields() { + return SKIP_FIELDS; + } + + public static String windPathFor(String fieldName) { + return FIELD_TO_WIND_PATH.get(fieldName); + } + + /** + * Writes migrated option values into a YAML sink (path → value). + * + * @return number of options written + */ + public static int applyToYaml(Map fieldValues, BiConsumer yamlSink) { + int written = 0; + for (Map.Entry entry : fieldValues.entrySet()) { + String path = FIELD_TO_WIND_PATH.get(entry.getKey()); + if (path != null) { + yamlSink.accept(path, entry.getValue()); + written++; + } + } + return written; + } + + /** + * Copies overlapping public static fields from {@code nachoClass} onto + * {@code windClass}, writes matching keys into {@code windYaml}, and copies + * {@code world-settings} so world options are not dropped when {@code nacho.yml} + * is later removed. + * + * @return number of overlapping fields migrated + */ + public static int migrate(Class nachoClass, Class windClass, YamlConfiguration nachoYaml, + YamlConfiguration windYaml, BiConsumer warn) throws IllegalAccessException { + Map values = new LinkedHashMap(); + int migrated = 0; + + for (Field oldField : nachoClass.getDeclaredFields()) { + if (!Modifier.isPublic(oldField.getModifiers()) || !Modifier.isStatic(oldField.getModifiers())) { + continue; + } + String name = oldField.getName(); + if (SKIP_FIELDS.contains(name)) { + continue; + } + if (!FIELD_TO_WIND_PATH.containsKey(name)) { + if (warn != null) { + warn.accept(name, "no WindSpigot mapping (left on nacho.yml / ignored)"); + } + continue; + } + + oldField.setAccessible(true); + Object value = oldField.get(null); + values.put(name, value); + + Field newField = findPublicStaticField(windClass, name); + if (newField == null) { + if (warn != null) { + warn.accept(name, "missing matching WindSpigotConfig field"); + } + continue; + } + newField.setAccessible(true); + newField.set(null, value); + migrated++; + } + + applyToYaml(values, new BiConsumer() { + @Override + public void accept(String path, Object value) { + windYaml.set(path, value); + } + }); + + copyWorldSettings(nachoYaml, windYaml); + windYaml.set(MIGRATED_FLAG_PATH, true); + return migrated; + } + + static void copyWorldSettings(YamlConfiguration nachoYaml, YamlConfiguration windYaml) { + if (nachoYaml == null || windYaml == null) { + return; + } + ConfigurationSection worldSettings = nachoYaml.getConfigurationSection("world-settings"); + if (worldSettings == null) { + return; + } + for (String key : worldSettings.getKeys(true)) { + if (worldSettings.isConfigurationSection(key)) { + continue; + } + windYaml.set("world-settings." + key, worldSettings.get(key)); + } + } + + private static Field findPublicStaticField(Class clazz, String name) { + try { + Field field = clazz.getDeclaredField(name); + if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) { + return field; + } + } catch (NoSuchFieldException ignored) { + } + return null; + } +} diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java index 2276e6fcd..a8ef0b879 100644 --- a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java +++ b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java @@ -21,6 +21,8 @@ import com.windpvp.windspigot.WindSpigot; import com.windpvp.windspigot.async.pathsearch.AsyncNavigation; +import me.elier.nachospigot.config.NachoConfig; + @SuppressWarnings("unused") public class WindSpigotConfig { @@ -49,21 +51,26 @@ public static void init(File configFile) { } config.options().copyDefaults(true); - int configVersion = 29; // Update this every new configuration update + int configVersion = 30; // Update this every new configuration update version = getInt("config-version", configVersion); set("config-version", configVersion); c.setHeader(HEADER); readConfig(WindSpigotConfig.class, null); - // Move nacho config options to our config - /*if (!NachoConfig.hasMigrated) { - // TODO: finish config migration/loading - //NachoConfig.moveToWindSpigotConfig(NachoConfig.class, WindSpigotConfig.class); - LOGGER.info("Successfully loaded nacho.yml into memory!"); - LOGGER.warn("Loading of nacho.yml will be removed in the future, so transfer the config options into windspigot.yml, then delete nacho.yml"); - makeReadable(); - }*/ + // One-shot migration from nacho.yml → windspigot.yml (fields + YAML persistence) + boolean alreadyMigrated = config.getBoolean(NachoConfigMigration.MIGRATED_FLAG_PATH, false); + if (!NachoConfig.hasMigrated && !alreadyMigrated) { + NachoConfig.moveToWindSpigotConfig(NachoConfig.class, WindSpigotConfig.class); + LOGGER.info("nacho.yml settings were copied into windspigot.yml (including world-settings)."); + LOGGER.info("You can delete nacho.yml after confirming windspigot.yml looks correct."); + } else if (!NachoConfig.hasMigrated && alreadyMigrated) { + // Keep Wind values; do not re-overwrite after operators edit windspigot.yml + NachoConfig.hasMigrated = true; + } else if (!alreadyMigrated) { + // Fresh install / no nacho.yml — mark so future nacho.yml drops do not surprise-overwrite + set(NachoConfigMigration.MIGRATED_FLAG_PATH, true); + } try { config.save(CONFIG_FILE); @@ -158,6 +165,7 @@ static void loadComments() { c.addComment("settings.spawners.max-nearby-entities", "Maximum number of nearby entities to avoid new spawns (Any value lower than 1 will disable this check)"); c.addComment("settings.spawners.required-player-range", "Area in which the player is required to spawn entities"); c.addComment("settings.fishing-rod-multiplier", "The speed multiplier of fishing rod projectiles."); + c.addComment("settings.migrated-from-nacho", "Internal flag set after nacho.yml options were copied into this file. Do not remove unless you intend to re-run migration."); // NachoSpigot stuff c.addComment("settings.save-empty-scoreboard-teams", "Toggles whether or not the server should save empty scoreboard teams"); diff --git a/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java b/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java index 2c52e13b3..9429d0458 100644 --- a/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java +++ b/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java @@ -2,11 +2,11 @@ import java.io.File; import java.io.IOException; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; +import java.util.function.BiConsumer; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; @@ -17,6 +17,8 @@ import com.google.common.base.Throwables; import com.windpvp.windspigot.WindSpigot; +import com.windpvp.windspigot.config.NachoConfigMigration; +import com.windpvp.windspigot.config.WindSpigotConfig; public class NachoConfig { @@ -32,17 +34,20 @@ public class NachoConfig { static YamlConfiguration config; static int version; - // If the config has been migrated to windspigot.yml + // If the config has been migrated to windspigot.yml (or no nacho.yml to migrate) public static boolean hasMigrated = false; public static void init(File configFile) { - /*if (!configFile.exists()) { TODO: uncomment this after config migration is finished - hasMigrated = true; - return; - }*/ - CONFIG_FILE = configFile; config = new YamlConfiguration(); + + // No legacy file — nothing to migrate; keep an empty YAML for world-setting defaults + if (!configFile.exists()) { + hasMigrated = true; + config.options().copyDefaults(true); + return; + } + try { WindSpigot.LOGGER.info("Loading NachoSpigot config from " + configFile.getName()); config.load(CONFIG_FILE); @@ -78,34 +83,23 @@ static void readConfig(Class clazz, Object instance) { } } - // Migrates all read data into the WindSpigot config - // TODO: save this data to file instead of just putting it in memory + /** + * Migrates overlapping Nacho options into WindSpigotConfig fields and + * {@code windspigot.yml} (including {@code world-settings}). + */ public static void moveToWindSpigotConfig(Class clazz, Class windSpigotConfigClazz) { - for (Field oldField : clazz.getDeclaredFields()) { - - if (Modifier.isPublic(oldField.getModifiers()) && Modifier.isStatic(oldField.getModifiers()) && !oldField.getName().equals("CONFIG_FILE") && !oldField.getName().equals("hasMigrated")) { - oldField.setAccessible(true); - - boolean found = false; - - for (Field newField : windSpigotConfigClazz.getDeclaredFields()) { - if (newField.getName().equals(oldField.getName())) { - found = true; - newField.setAccessible(true); - - try { - newField.set(null, oldField.get(null)); - } catch (IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); + try { + int migrated = NachoConfigMigration.migrate(clazz, windSpigotConfigClazz, config, + WindSpigotConfig.config, new BiConsumer() { + @Override + public void accept(String option, String reason) { + WindSpigot.LOGGER.warn("Unable to migrate option " + option + ": " + reason); } - } - } - - if (!found) { - WindSpigot.LOGGER.warn("Unable to migrate option " + oldField.getName() + "!"); - } - } - } + }); + WindSpigot.LOGGER.info("Migrated " + migrated + " nacho.yml option(s) into windspigot.yml"); + } catch (IllegalAccessException e) { + LOGGER.log(Level.ERROR, "Failed to migrate nacho.yml into windspigot.yml", e); + } hasMigrated = true; } diff --git a/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoWorldConfig.java b/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoWorldConfig.java index 21bb29dc2..c54c52696 100644 --- a/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoWorldConfig.java +++ b/WindSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoWorldConfig.java @@ -5,6 +5,8 @@ import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; +import com.windpvp.windspigot.config.WindSpigotConfig; + public class NachoWorldConfig { private final String worldName; @@ -13,7 +15,14 @@ public class NachoWorldConfig { public NachoWorldConfig(String worldName) { this.worldName = worldName; - this.config = NachoConfig.config; + // Prefer windspigot.yml after migration copied world-settings there + if (WindSpigotConfig.config != null && WindSpigotConfig.config.contains("world-settings")) { + this.config = WindSpigotConfig.config; + } else if (NachoConfig.config != null) { + this.config = NachoConfig.config; + } else { + this.config = new YamlConfiguration(); + } init(); }