Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<String, String> FIELD_TO_WIND_PATH;

/** Fields that exist on NachoConfig but must not be migrated (removed or infrastructure). */
private static final Set<String> SKIP_FIELDS;

static {
Map<String, String> paths = new LinkedHashMap<String, String>();
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<String> skip = new LinkedHashSet<String>();
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<String, String> fieldToWindPath() {
return FIELD_TO_WIND_PATH;
}

public static Set<String> 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<String, Object> fieldValues, BiConsumer<String, Object> yamlSink) {
int written = 0;
for (Map.Entry<String, Object> 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<String, String> warn) throws IllegalAccessException {
Map<String, Object> values = new LinkedHashMap<String, Object>();
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<String, Object>() {
@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -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);
Expand Down Expand Up @@ -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<String, String>() {
@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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}

Expand Down